This patch adds support for EHCI compliant Host controller found on Fujitsu Socs. Signed-off-by: Sneeker Yeh <Sneeker.Yeh@xxxxxxxxxxxxxx> --- .../devicetree/bindings/usb/fujitsu-ehci.txt | 22 ++ drivers/usb/host/Kconfig | 11 + drivers/usb/host/Makefile | 1 + drivers/usb/host/f_usb20ho_hcd.c | 306 ++++++++++++++++++++ drivers/usb/host/f_usb20ho_hcd.h | 35 +++ 5 files changed, 375 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/fujitsu-ehci.txt create mode 100644 drivers/usb/host/f_usb20ho_hcd.c create mode 100644 drivers/usb/host/f_usb20ho_hcd.h diff --git a/Documentation/devicetree/bindings/usb/fujitsu-ehci.txt b/Documentation/devicetree/bindings/usb/fujitsu-ehci.txt new file mode 100644 index 0000000..e180860 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/fujitsu-ehci.txt @@ -0,0 +1,22 @@ +FUJITSU GLUE COMPONENTS + +MB86S7x EHCI GLUE + - compatible : Should be "fujitsu,f_usb20ho_hcd" + - reg : Address and length of the register set for the device. + - interrupts : The irq number of this device that is used to interrupt the + CPU + - clocks: from common clock binding, handle to usb clock. + - clock-names: from common clock binding. + - #stream-id-cells : handle to use "arm,mmu-400" ARM IOMMU driver + - fujitsu,power-domain : pd_usb2h node has to be builded, details can be + found in: + Documentation/devicetree/bindings/ + +hcd21: f_usb20ho_hcd { + compatible = "fujitsu,f_usb20ho_hcd"; + reg = <0 0x34200000 0x80000>; + interrupts = <0 419 0x4>; + clocks = <&clk_main_2_4>, <&clk_main_4_5>, <&clk_usb_0_0>; + clock-names = "h_clk", "p_clk", "p_cryclk"; + #stream-id-cells = <0>; +}; diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index fafc628..9482140 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -786,6 +786,17 @@ config USB_HCD_SSB If unsure, say N. +config USB_F_USB20HO_HCD + tristate "F_USB20HO USB Host Controller" + depends on USB && ARCH_MB86S7X + default y + help + This driver enables support for USB20HO USB Host Controller, + the driver supports High, Full and Low Speed USB. + + To compile this driver a module, choose M here: the module + will be called "f_usb20ho-hcd". + config USB_HCD_TEST_MODE bool "HCD test mode support" ---help--- diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index d6216a4..b89e536 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -78,3 +78,4 @@ obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o obj-$(CONFIG_USB_FUSBH200_HCD) += fusbh200-hcd.o obj-$(CONFIG_USB_FOTG210_HCD) += fotg210-hcd.o obj-$(CONFIG_USB_MAX3421_HCD) += max3421-hcd.o +obj-$(CONFIG_USB_F_USB20HO_HCD)+= f_usb20ho_hcd.o diff --git a/drivers/usb/host/f_usb20ho_hcd.c b/drivers/usb/host/f_usb20ho_hcd.c new file mode 100644 index 0000000..d665487 --- /dev/null +++ b/drivers/usb/host/f_usb20ho_hcd.c @@ -0,0 +1,306 @@ +/** + * f_usb20ho_hcd.c - Fujitsu EHCI platform driver + * + * Copyright (c) 2013 - 2014 FUJITSU SEMICONDUCTOR LIMITED + * http://jp.fujitsu.com/group/fsl + * + * based on bcma-hcd.c + * + * Author: Sneeker Yeh <Sneeker.Yeh@xxxxxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/usb/ehci_pdriver.h> +#include <linux/usb/ohci_pdriver.h> +#include <linux/dma-mapping.h> +#include <linux/of.h> +#include <linux/pm_runtime.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "f_usb20ho_hcd.h" + +static int f_usb20ho_clk_control(struct device *dev, bool on) +{ + int ret, i; + struct clk *clk; + + if (!on) + goto clock_off; + + for (i = 0;; i++) { + clk = of_clk_get(dev->of_node, i); + if (IS_ERR(clk)) + break; + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(dev, "failed to enable clock[%d]\n", i); + goto clock_off; + } + } + + return 0; + +clock_off: + for (i = 0;; i++) { + clk = of_clk_get(dev->of_node, i); + if (IS_ERR(clk)) + break; + + clk_disable_unprepare(clk); + } + + return on; +} + +static struct platform_device *f_usb20ho_hcd_create_pdev( + struct platform_device *pdev, bool ohci) +{ + struct resource *resource; + struct platform_device *hci_dev; + struct resource hci_res[2]; + int ret = -ENOMEM; + int irq; + resource_size_t resource_size; + + memset(hci_res, 0, sizeof(hci_res)); + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + dev_err(&pdev->dev, "%s() platform_get_resource() failed\n" + , __func__); + ret = -ENODEV; + goto err_res; + } + resource_size = resource->end - resource->start + 1; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, + "%s() platform_get_irq() for F_USB20HO failed at %d\n", + __func__, irq); + ret = -ENODEV; + goto err_res; + } + + hci_res[0].start = ohci ? resource->start + F_OHCI_OFFSET : + resource->start + F_EHCI_OFFSET; + hci_res[0].end = ohci ? + resource->start + F_OHCI_OFFSET + F_OHCI_SIZE - 1 : + resource->start + F_EHCI_OFFSET + F_EHCI_SIZE - 1; + hci_res[0].flags = IORESOURCE_MEM; + + hci_res[1].start = irq; + hci_res[1].flags = IORESOURCE_IRQ; + + hci_dev = platform_device_alloc(ohci ? "ohci-platform" : + "ehci-platform", 0); + if (!hci_dev) { + dev_err(&pdev->dev, "platform_device_alloc() failed\n"); + ret = -ENODEV; + goto err_res; + } + + dma_set_coherent_mask(&hci_dev->dev, pdev->dev.coherent_dma_mask); + hci_dev->dev.parent = &pdev->dev; + hci_dev->dev.dma_mask = pdev->dev.dma_mask; + + ret = platform_device_add_resources(hci_dev, hci_res, + ARRAY_SIZE(hci_res)); + if (ret) { + dev_err(&pdev->dev + , "platform_device_add_resources() failed\n"); + goto err_alloc; + } + + ret = platform_device_add(hci_dev); + if (ret) { + dev_err(&pdev->dev, "platform_device_add() failed\n"); + goto err_alloc; + } + + return hci_dev; + +err_alloc: + platform_device_put(hci_dev); +err_res: + return ERR_PTR(ret); +} + +static u64 f_usb20ho_dma_mask = DMA_BIT_MASK(32); + +static int f_usb20ho_hcd_probe(struct platform_device *pdev) +{ + int err; + struct f_usb20ho_hcd *usb_dev; + struct device *dev = &pdev->dev; + + dev->dma_mask = &f_usb20ho_dma_mask; + if (!dev->coherent_dma_mask) + dev->coherent_dma_mask = DMA_BIT_MASK(32); + + usb_dev = kzalloc(sizeof(*usb_dev), GFP_KERNEL); + if (!usb_dev) + return -ENOMEM; + + usb_dev->dev = &pdev->dev; + platform_set_drvdata(pdev, usb_dev); + + usb_dev->irq = platform_get_irq(pdev, 0); + if (usb_dev->irq < 0) { + dev_err(&pdev->dev, + "%s() platform_get_irq() for F_USB20HO failed at %d\n", + __func__, usb_dev->irq); + err = -ENODEV; + goto err_free_usb_dev; + } + disable_irq(usb_dev->irq); + + /* resume driver for clock, power, irq */ + pm_runtime_enable(&pdev->dev); + err = pm_runtime_get_sync(&pdev->dev); + if (err < 0) { + dev_err(&pdev->dev, "get_sync failed with err %d\n", err); + goto err_unregister_ohci_dev; + } + + usb_dev->ehci_dev = f_usb20ho_hcd_create_pdev(pdev, false); + if (IS_ERR(usb_dev->ehci_dev)) { + dev_err(&pdev->dev, "failed to create EHCI driver\n"); + err = -ENODEV; + goto err_free_usb_dev; + } + + usb_dev->ohci_dev = f_usb20ho_hcd_create_pdev(pdev, true); + if (IS_ERR(usb_dev->ohci_dev)) { + dev_err(&pdev->dev, "failed to create OHCI driver\n"); + err = -ENODEV; + goto err_unregister_ehci_dev; + } + + return 0; + +err_unregister_ohci_dev: + platform_device_unregister(usb_dev->ohci_dev); +err_unregister_ehci_dev: + platform_device_unregister(usb_dev->ehci_dev); + pm_runtime_put_sync(&pdev->dev); +err_free_usb_dev: + kfree(usb_dev); + + return err; +} + +static int f_usb20ho_hcd_remove(struct platform_device *pdev) +{ + struct f_usb20ho_hcd *usb_dev = platform_get_drvdata(pdev); + struct platform_device *ohci_dev = usb_dev->ohci_dev; + struct platform_device *ehci_dev = usb_dev->ehci_dev; + + if (ohci_dev) + platform_device_unregister(ohci_dev); + if (ehci_dev) + platform_device_unregister(ehci_dev); + + /* disable power,clock,irq */ + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +#ifdef CONFIG_PM_RUNTIME +static int f_usb20ho_runtime_suspend(struct device *dev) +{ + struct f_usb20ho_hcd *usb_dev = dev_get_drvdata(dev); + + disable_irq(usb_dev->irq); + f_usb20ho_clk_control(dev, false); + + return 0; +} + +static int f_usb20ho_runtime_resume(struct device *dev) +{ + struct f_usb20ho_hcd *usb_dev = dev_get_drvdata(dev); + + f_usb20ho_clk_control(dev, true); + enable_irq(usb_dev->irq); + + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +static int f_usb20ho_hcd_suspend(struct device *dev) +{ + if (pm_runtime_status_suspended(dev)) + return 0; + + return f_usb20ho_runtime_suspend(dev); +} + +static int f_usb20ho_hcd_resume(struct device *dev) +{ + if (pm_runtime_status_suspended(dev)) + return 0; + + return f_usb20ho_runtime_resume(dev); +} + +static const struct dev_pm_ops f_usb20ho_hcd_ops = { + .suspend = f_usb20ho_hcd_suspend, + .resume = f_usb20ho_hcd_resume, + SET_RUNTIME_PM_OPS(f_usb20ho_runtime_suspend + , f_usb20ho_runtime_resume, NULL) +}; + +#define DEV_PM (&f_usb20ho_hcd_ops) +#else /* !CONFIG_PM */ +#define DEV_PM NULL +#endif /* CONFIG_PM */ + +static void f_usb20ho_hcd_shutdown(struct platform_device *pdev) +{ +#ifdef CONFIG_PM + f_usb20ho_hcd_suspend(&pdev->dev); +#endif +} + +static const struct of_device_id f_usb20ho_hcd_dt_ids[] = { + { .compatible = "fujitsu,f_usb20ho_hcd" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, f_usb20ho_hcd_dt_ids); + +static struct platform_driver f_usb20ho_hcd_driver = { + .probe = f_usb20ho_hcd_probe, + .remove = __exit_p(f_usb20ho_hcd_remove), + .shutdown = f_usb20ho_hcd_shutdown, + .driver = { + .name = "f_usb20ho_hcd", + .owner = THIS_MODULE, + .pm = DEV_PM, + .of_match_table = f_usb20ho_hcd_dt_ids, + }, +}; + +module_platform_driver(f_usb20ho_hcd_driver); + +MODULE_ALIAS("platform:f_usb20ho_hcd"); +MODULE_AUTHOR("Sneeker Yeh <Sneeker.Yeh@xxxxxxxxxxxxxx>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("USB platform driver for f_usb20ho_lap IP "); diff --git a/drivers/usb/host/f_usb20ho_hcd.h b/drivers/usb/host/f_usb20ho_hcd.h new file mode 100644 index 0000000..046c636 --- /dev/null +++ b/drivers/usb/host/f_usb20ho_hcd.h @@ -0,0 +1,35 @@ +/* + * linux/drivers/usb/host/f_usb20ho_hcd.h - F_USB20HDC USB + * host controller driver + * + * Copyright (C) FUJITSU ELECTRONICS INC. 2011. All rights reserved. + * Copyright (C) 2013 - 2014 FUJITSU SEMICONDUCTOR LIMITED. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _F_USB20HO_HCD_H +#define _F_USB20HO_HCD_H + +#define F_EHCI_OFFSET 0x40000 +#define F_EHCI_SIZE 0x1000 +#define F_OHCI_OFFSET 0x41000 +#define F_OHCI_SIZE 0x1000 +#define F_OTHER_OFFSET 0x42000 +#define F_OTHER_SIZE 0x1000 + +struct f_usb20ho_hcd { + struct device *dev; + struct platform_device *ehci_dev; + struct platform_device *ohci_dev; + int irq; +}; +#endif -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html