From 1139b4516f3927ae835ed3b0a9c69a0dfaf4cdd4 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Mon, 30 Jul 2012 09:29:12 +0200 Subject: USB: ohci-pxa27x: add DT bindings Add DT bindings to the ohci-pxa27x driver and some documentation. Successfully tested on a PXA3xx board. Signed-off-by: Daniel Mack Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/pxa-usb.txt | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/pxa-usb.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/pxa-usb.txt b/Documentation/devicetree/bindings/usb/pxa-usb.txt new file mode 100644 index 000000000000..79729a948d5a --- /dev/null +++ b/Documentation/devicetree/bindings/usb/pxa-usb.txt @@ -0,0 +1,31 @@ +PXA USB controllers + +OHCI + +Required properties: + - compatible: Should be "marvell,pxa-ohci" for USB controllers + used in host mode. + +Optional properties: + - "marvell,enable-port1", "marvell,enable-port2", "marvell,enable-port3" + If present, enables the appropriate USB port of the controller. + - "marvell,port-mode" selects the mode of the ports: + 1 = PMM_NPS_MODE + 2 = PMM_GLOBAL_MODE + 3 = PMM_PERPORT_MODE + - "marvell,power-sense-low" - power sense pin is low-active. + - "marvell,power-control-low" - power control pin is low-active. + - "marvell,no-oc-protection" - disable over-current protection. + - "marvell,oc-mode-perport" - enable per-port over-current protection. + - "marvell,power_on_delay" Power On to Power Good time - in ms. + +Example: + + usb0: ohci@4c000000 { + compatible = "marvell,pxa-ohci", "usb-ohci"; + reg = <0x4c000000 0x100000>; + interrupts = <18>; + marvell,enable-port1; + marvell,port-mode = <2>; /* PMM_GLOBAL_MODE */ + }; + -- cgit v1.2.3-59-g8ed1b From 4d9e4088e074e674bc7a9fef4381e0910323c649 Mon Sep 17 00:00:00 2001 From: Lan Tianyu Date: Fri, 3 Aug 2012 16:30:36 +0800 Subject: usb: add decriptor of persist fail for some morph usb devices Some morph devices will morph after being reset and cause persist to fail. This patch is to add descriptor about this in the Document/usb/persist.txt Signed-off-by: Lan Tianyu Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/persist.txt | 3 +++ 1 file changed, 3 insertions(+) (limited to 'Documentation') diff --git a/Documentation/usb/persist.txt b/Documentation/usb/persist.txt index 074b159b77c2..35d70eda9ad6 100644 --- a/Documentation/usb/persist.txt +++ b/Documentation/usb/persist.txt @@ -155,6 +155,9 @@ If the kernel gets fooled in this way, it's almost certain to cause data corruption and to crash your system. You'll have no one to blame but yourself. +For those devices with avoid_reset_quirk attribute being set, persist +maybe fail because they may morph after reset. + YOU HAVE BEEN WARNED! USE AT YOUR OWN RISK! That having been said, most of the time there shouldn't be any trouble -- cgit v1.2.3-59-g8ed1b From 8ad551d150e3bb0902696496a9d2aa094335705a Mon Sep 17 00:00:00 2001 From: Tony Prisk Date: Sat, 21 Jul 2012 22:58:52 +1200 Subject: ARM: vt8500: Update vt8500-ehci driver to support device tree. Signed-off-by: Tony Prisk Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/vt8500-ehci.txt | 12 ++++++++++++ drivers/usb/host/ehci-vt8500.c | 9 +++++++++ 2 files changed, 21 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/vt8500-ehci.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/vt8500-ehci.txt b/Documentation/devicetree/bindings/usb/vt8500-ehci.txt new file mode 100644 index 000000000000..5fb8fd6e250c --- /dev/null +++ b/Documentation/devicetree/bindings/usb/vt8500-ehci.txt @@ -0,0 +1,12 @@ +VIA VT8500 and Wondermedia WM8xxx SoC USB controllers. + +Required properties: + - compatible: Should be "via,vt8500-ehci" or "wm,prizm-ehci". + - reg: Address range of the ehci registers. size should be 0x200 + - interrupts: Should contain the ehci interrupt. + +usb: ehci@D8007100 { + compatible = "wm,prizm-ehci", "usb-ehci"; + reg = <0xD8007100 0x200>; + interrupts = <1>; +}; diff --git a/drivers/usb/host/ehci-vt8500.c b/drivers/usb/host/ehci-vt8500.c index a8916177bed7..96722bfebc84 100644 --- a/drivers/usb/host/ehci-vt8500.c +++ b/drivers/usb/host/ehci-vt8500.c @@ -16,6 +16,7 @@ * */ +#include #include static int ehci_update_device(struct usb_hcd *hcd, struct usb_device *udev) @@ -139,6 +140,12 @@ static int vt8500_ehci_drv_remove(struct platform_device *pdev) return 0; } +static const struct of_device_id vt8500_ehci_ids[] = { + { .compatible = "via,vt8500-ehci", }, + { .compatible = "wm,prizm-ehci", }, + {} +}; + static struct platform_driver vt8500_ehci_driver = { .probe = vt8500_ehci_drv_probe, .remove = vt8500_ehci_drv_remove, @@ -146,7 +153,9 @@ static struct platform_driver vt8500_ehci_driver = { .driver = { .name = "vt8500-ehci", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(vt8500_ehci_ids), } }; MODULE_ALIAS("platform:vt8500-ehci"); +MODULE_DEVICE_TABLE(of, vt8500_ehci_ids); -- cgit v1.2.3-59-g8ed1b From 100d45970327f78584ff4846deeca14bba511e28 Mon Sep 17 00:00:00 2001 From: Tony Prisk Date: Sat, 21 Jul 2012 22:58:53 +1200 Subject: ARM: vt8500: Add support for UHCI companion controller Add support for a generic non-pci UHCI companion controller. Existing board files for arch-vt8500 updated to include UHCI support. Signed-off-by: Tony Prisk Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/platform-uhci.txt | 12 ++ arch/arm/mach-vt8500/bv07.c | 1 + arch/arm/mach-vt8500/devices-vt8500.c | 5 + arch/arm/mach-vt8500/devices-wm8505.c | 4 + arch/arm/mach-vt8500/devices.c | 11 ++ arch/arm/mach-vt8500/devices.h | 1 + arch/arm/mach-vt8500/wm8505_7in.c | 1 + drivers/usb/host/Kconfig | 12 +- drivers/usb/host/uhci-hcd.c | 5 + drivers/usb/host/uhci-platform.c | 157 +++++++++++++++++++++ 10 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/platform-uhci.txt create mode 100644 drivers/usb/host/uhci-platform.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/platform-uhci.txt b/Documentation/devicetree/bindings/usb/platform-uhci.txt new file mode 100644 index 000000000000..91477d6830ec --- /dev/null +++ b/Documentation/devicetree/bindings/usb/platform-uhci.txt @@ -0,0 +1,12 @@ +Generic Platform UHCI controllers. + +Required properties: + - compatible: Should be "platform-uhci". + - reg: Address range of the uhci registers + - interrupts: Should contain the uhci interrupt. + +usb: uhci@D8007301 { + compatible = "platform-uhci", "usb-uhci"; + reg = <0xD8007301 0x200>; + interrupts = <0>; +}; diff --git a/arch/arm/mach-vt8500/bv07.c b/arch/arm/mach-vt8500/bv07.c index f9fbeb2d10e9..6fd9d609ebaa 100644 --- a/arch/arm/mach-vt8500/bv07.c +++ b/arch/arm/mach-vt8500/bv07.c @@ -33,6 +33,7 @@ static struct platform_device *devices[] __initdata = { &vt8500_device_uart0, &vt8500_device_lcdc, &vt8500_device_ehci, + &vt8500_device_uhci, &vt8500_device_ge_rops, &vt8500_device_pwm, &vt8500_device_pwmbl, diff --git a/arch/arm/mach-vt8500/devices-vt8500.c b/arch/arm/mach-vt8500/devices-vt8500.c index 19519aeecf37..def7fe393a2c 100644 --- a/arch/arm/mach-vt8500/devices-vt8500.c +++ b/arch/arm/mach-vt8500/devices-vt8500.c @@ -48,6 +48,11 @@ void __init vt8500_set_resources(void) tmp[1] = wmt_irq_res(IRQ_EHCI); wmt_res_add(&vt8500_device_ehci, tmp, 2); + /* vt8500 uses a single IRQ for both EHCI and UHCI controllers */ + tmp[0] = wmt_mmio_res(VT8500_UHCI_BASE, SZ_512); + tmp[1] = wmt_irq_res(IRQ_EHCI); + wmt_res_add(&vt8500_device_uhci, tmp, 2); + tmp[0] = wmt_mmio_res(VT8500_GEGEA_BASE, SZ_256); wmt_res_add(&vt8500_device_ge_rops, tmp, 1); diff --git a/arch/arm/mach-vt8500/devices-wm8505.c b/arch/arm/mach-vt8500/devices-wm8505.c index db4594e029f4..c810454178dc 100644 --- a/arch/arm/mach-vt8500/devices-wm8505.c +++ b/arch/arm/mach-vt8500/devices-wm8505.c @@ -55,6 +55,10 @@ void __init wm8505_set_resources(void) tmp[1] = wmt_irq_res(IRQ_EHCI); wmt_res_add(&vt8500_device_ehci, tmp, 2); + tmp[0] = wmt_mmio_res(WM8505_UHCI_BASE, SZ_512); + tmp[1] = wmt_irq_res(IRQ_UHCI); + wmt_res_add(&vt8500_device_uhci, tmp, 2); + tmp[0] = wmt_mmio_res(WM8505_GEGEA_BASE, SZ_256); wmt_res_add(&vt8500_device_ge_rops, tmp, 1); diff --git a/arch/arm/mach-vt8500/devices.c b/arch/arm/mach-vt8500/devices.c index 1fcdc36b358d..46ff82dad544 100644 --- a/arch/arm/mach-vt8500/devices.c +++ b/arch/arm/mach-vt8500/devices.c @@ -204,6 +204,17 @@ struct platform_device vt8500_device_ehci = { }, }; +static u64 uhci_dma_mask = DMA_BIT_MASK(32); + +struct platform_device vt8500_device_uhci = { + .name = "platform-uhci", + .id = 0, + .dev = { + .dma_mask = &uhci_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + struct platform_device vt8500_device_ge_rops = { .name = "wmt_ge_rops", .id = -1, diff --git a/arch/arm/mach-vt8500/devices.h b/arch/arm/mach-vt8500/devices.h index 188d4e17f35c..0e6d9f904c77 100644 --- a/arch/arm/mach-vt8500/devices.h +++ b/arch/arm/mach-vt8500/devices.h @@ -81,6 +81,7 @@ extern struct platform_device vt8500_device_uart5; extern struct platform_device vt8500_device_lcdc; extern struct platform_device vt8500_device_wm8505_fb; extern struct platform_device vt8500_device_ehci; +extern struct platform_device vt8500_device_uhci; extern struct platform_device vt8500_device_ge_rops; extern struct platform_device vt8500_device_pwm; extern struct platform_device vt8500_device_pwmbl; diff --git a/arch/arm/mach-vt8500/wm8505_7in.c b/arch/arm/mach-vt8500/wm8505_7in.c index db19886caf7c..4804e2a45574 100644 --- a/arch/arm/mach-vt8500/wm8505_7in.c +++ b/arch/arm/mach-vt8500/wm8505_7in.c @@ -32,6 +32,7 @@ static void __iomem *pmc_hiber; static struct platform_device *devices[] __initdata = { &vt8500_device_uart0, &vt8500_device_ehci, + &vt8500_device_uhci, &vt8500_device_wm8505_fb, &vt8500_device_ge_rops, &vt8500_device_pwm, diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 075d2eca8108..c3f619b1f598 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -450,7 +450,7 @@ config USB_OHCI_LITTLE_ENDIAN config USB_UHCI_HCD tristate "UHCI HCD (most Intel and VIA) support" - depends on USB && (PCI || SPARC_LEON) + depends on USB && (PCI || SPARC_LEON || ARCH_VT8500) ---help--- The Universal Host Controller Interface is a standard by Intel for accessing the USB hardware in the PC (which is also called the USB @@ -468,7 +468,15 @@ config USB_UHCI_HCD config USB_UHCI_SUPPORT_NON_PCI_HC bool depends on USB_UHCI_HCD - default y if SPARC_LEON + default y if (SPARC_LEON || ARCH_VT8500) + +config USB_UHCI_PLATFORM + bool "Generic UHCI Platform Driver support" + depends on USB_UHCI_SUPPORT_NON_PCI_HC + default y if ARCH_VT8500 + ---help--- + Enable support for generic UHCI platform devices that require no + additional configuration. config USB_UHCI_BIG_ENDIAN_MMIO bool diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index e4db350602b8..4b9e9aba2665 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -846,6 +846,11 @@ static const char hcd_name[] = "uhci_hcd"; #define PLATFORM_DRIVER uhci_grlib_driver #endif +#ifdef CONFIG_USB_UHCI_PLATFORM +#include "uhci-platform.c" +#define PLATFORM_DRIVER uhci_platform_driver +#endif + #if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) #error "missing bus glue for uhci-hcd" #endif diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c new file mode 100644 index 000000000000..e4780491df4a --- /dev/null +++ b/drivers/usb/host/uhci-platform.c @@ -0,0 +1,157 @@ +/* + * Generic UHCI HCD (Host Controller Driver) for Platform Devices + * + * Copyright (c) 2011 Tony Prisk + * + * This file is based on uhci-grlib.c + * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu + */ + +#include +#include + +static int uhci_platform_init(struct usb_hcd *hcd) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + + uhci->rh_numports = uhci_count_ports(hcd); + + /* Set up pointers to to generic functions */ + uhci->reset_hc = uhci_generic_reset_hc; + uhci->check_and_reset_hc = uhci_generic_check_and_reset_hc; + + /* No special actions need to be taken for the functions below */ + uhci->configure_hc = NULL; + uhci->resume_detect_interrupts_are_broken = NULL; + uhci->global_suspend_mode_is_broken = NULL; + + /* Reset if the controller isn't already safely quiescent. */ + check_and_reset_hc(uhci); + return 0; +} + +static const struct hc_driver uhci_platform_hc_driver = { + .description = hcd_name, + .product_desc = "Generic UHCI Host Controller", + .hcd_priv_size = sizeof(struct uhci_hcd), + + /* Generic hardware linkage */ + .irq = uhci_irq, + .flags = HCD_MEMORY | HCD_USB11, + + /* Basic lifecycle operations */ + .reset = uhci_platform_init, + .start = uhci_start, +#ifdef CONFIG_PM + .pci_suspend = NULL, + .pci_resume = NULL, + .bus_suspend = uhci_rh_suspend, + .bus_resume = uhci_rh_resume, +#endif + .stop = uhci_stop, + + .urb_enqueue = uhci_urb_enqueue, + .urb_dequeue = uhci_urb_dequeue, + + .endpoint_disable = uhci_hcd_endpoint_disable, + .get_frame_number = uhci_hcd_get_frame_number, + + .hub_status_data = uhci_hub_status_data, + .hub_control = uhci_hub_control, +}; + + +static int __devinit uhci_hcd_platform_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct uhci_hcd *uhci; + struct resource *res; + int ret; + + if (usb_disabled()) + return -ENODEV; + + hcd = usb_create_hcd(&uhci_platform_hc_driver, &pdev->dev, + pdev->name); + if (!hcd) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + pr_err("%s: request_mem_region failed\n", __func__); + ret = -EBUSY; + goto err_rmr; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + pr_err("%s: ioremap failed\n", __func__); + ret = -ENOMEM; + goto err_irq; + } + uhci = hcd_to_uhci(hcd); + + uhci->regs = hcd->regs; + + ret = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_DISABLED | + IRQF_SHARED); + if (ret) + goto err_uhci; + + return 0; + +err_uhci: + iounmap(hcd->regs); +err_irq: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err_rmr: + usb_put_hcd(hcd); + + return ret; +} + +static int uhci_hcd_platform_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +/* Make sure the controller is quiescent and that we're not using it + * any more. This is mainly for the benefit of programs which, like kexec, + * expect the hardware to be idle: not doing DMA or generating IRQs. + * + * This routine may be called in a damaged or failing kernel. Hence we + * do not acquire the spinlock before shutting down the controller. + */ +static void uhci_hcd_platform_shutdown(struct platform_device *op) +{ + struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + + uhci_hc_died(hcd_to_uhci(hcd)); +} + +static const struct of_device_id platform_uhci_ids[] = { + { .compatible = "platform-uhci", }, + {} +}; + +static struct platform_driver uhci_platform_driver = { + .probe = uhci_hcd_platform_probe, + .remove = uhci_hcd_platform_remove, + .shutdown = uhci_hcd_platform_shutdown, + .driver = { + .name = "platform-uhci", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(platform_uhci_ids), + }, +}; -- cgit v1.2.3-59-g8ed1b From 68a5059ecf82cc9d52a86fb523584b4d485f1bbe Mon Sep 17 00:00:00 2001 From: Cong Wang Date: Sun, 26 Aug 2012 14:40:07 +0800 Subject: block: remove the deprecated ub driver It was scheduled to be removed in 3.6. Acked-by: Pete Zaitcev Cc: Jens Axboe Cc: Sebastian Andrzej Siewior Signed-off-by: Cong Wang Signed-off-by: Greg Kroah-Hartman --- Documentation/feature-removal-schedule.txt | 11 - drivers/block/Kconfig | 12 - drivers/block/Makefile | 1 - drivers/block/ub.c | 2474 ---------------------------- 4 files changed, 2498 deletions(-) delete mode 100644 drivers/block/ub.c (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index afaff312bf41..b9aa38bca04c 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -495,17 +495,6 @@ Who: Bjorn Helgaas ---------------------------- -What: Low Performance USB Block driver ("CONFIG_BLK_DEV_UB") -When: 3.6 -Why: This driver provides support for USB storage devices like "USB - sticks". As of now, it is deactivated in Debian, Fedora and - Ubuntu. All current users can switch over to usb-storage - (CONFIG_USB_STORAGE) which only drawback is the additional SCSI - stack. -Who: Sebastian Andrzej Siewior - ----------------------------- - What: get_robust_list syscall When: 2013 Why: There appear to be no production users of the get_robust_list syscall, diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index a796407123c7..f529407db93f 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -353,18 +353,6 @@ config BLK_DEV_SX8 Use devices /dev/sx8/$N and /dev/sx8/$Np$M. -config BLK_DEV_UB - tristate "Low Performance USB Block driver (deprecated)" - depends on USB - help - This driver supports certain USB attached storage devices - such as flash keys. - - If you enable this driver, it is recommended to avoid conflicts - with usb-storage by enabling USB_LIBUSUAL. - - If unsure, say N. - config BLK_DEV_RAM tristate "RAM block device support" ---help--- diff --git a/drivers/block/Makefile b/drivers/block/Makefile index 5b795059f8fb..17e82df3df74 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -33,7 +33,6 @@ obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o obj-$(CONFIG_VIODASD) += viodasd.o obj-$(CONFIG_BLK_DEV_SX8) += sx8.o -obj-$(CONFIG_BLK_DEV_UB) += ub.o obj-$(CONFIG_BLK_DEV_HD) += hd.o obj-$(CONFIG_XEN_BLKDEV_FRONTEND) += xen-blkfront.o diff --git a/drivers/block/ub.c b/drivers/block/ub.c deleted file mode 100644 index fcec0225ac76..000000000000 --- a/drivers/block/ub.c +++ /dev/null @@ -1,2474 +0,0 @@ -/* - * The low performance USB storage driver (ub). - * - * Copyright (c) 1999, 2000 Matthew Dharm (mdharm-usb@one-eyed-alien.net) - * Copyright (C) 2004 Pete Zaitcev (zaitcev@yahoo.com) - * - * This work is a part of Linux kernel, is derived from it, - * and is not licensed separately. See file COPYING for details. - * - * TODO (sorted by decreasing priority) - * -- Return sense now that rq allows it (we always auto-sense anyway). - * -- set readonly flag for CDs, set removable flag for CF readers - * -- do inquiry and verify we got a disk and not a tape (for LUN mismatch) - * -- verify the 13 conditions and do bulk resets - * -- highmem - * -- move top_sense and work_bcs into separate allocations (if they survive) - * for cache purists and esoteric architectures. - * -- Allocate structure for LUN 0 before the first ub_sync_tur, avoid NULL. ? - * -- prune comments, they are too volumnous - * -- Resove XXX's - * -- CLEAR, CLR2STS, CLRRS seem to be ripe for refactoring. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRV_NAME "ub" - -#define UB_MAJOR 180 - -/* - * The command state machine is the key model for understanding of this driver. - * - * The general rule is that all transitions are done towards the bottom - * of the diagram, thus preventing any loops. - * - * An exception to that is how the STAT state is handled. A counter allows it - * to be re-entered along the path marked with [C]. - * - * +--------+ - * ! INIT ! - * +--------+ - * ! - * ub_scsi_cmd_start fails ->--------------------------------------\ - * ! ! - * V ! - * +--------+ ! - * ! CMD ! ! - * +--------+ ! - * ! +--------+ ! - * was -EPIPE -->-------------------------------->! CLEAR ! ! - * ! +--------+ ! - * ! ! ! - * was error -->------------------------------------- ! --------->\ - * ! ! ! - * /--<-- cmd->dir == NONE ? ! ! - * ! ! ! ! - * ! V ! ! - * ! +--------+ ! ! - * ! ! DATA ! ! ! - * ! +--------+ ! ! - * ! ! +---------+ ! ! - * ! was -EPIPE -->--------------->! CLR2STS ! ! ! - * ! ! +---------+ ! ! - * ! ! ! ! ! - * ! ! was error -->---- ! --------->\ - * ! was error -->--------------------- ! ------------- ! --------->\ - * ! ! ! ! ! - * ! V ! ! ! - * \--->+--------+ ! ! ! - * ! STAT !<--------------------------/ ! ! - * /--->+--------+ ! ! - * ! ! ! ! - * [C] was -EPIPE -->-----------\ ! ! - * ! ! ! ! ! - * +<---- len == 0 ! ! ! - * ! ! ! ! ! - * ! was error -->--------------------------------------!---------->\ - * ! ! ! ! ! - * +<---- bad CSW ! ! ! - * +<---- bad tag ! ! ! - * ! ! V ! ! - * ! ! +--------+ ! ! - * ! ! ! CLRRS ! ! ! - * ! ! +--------+ ! ! - * ! ! ! ! ! - * \------- ! --------------------[C]--------\ ! ! - * ! ! ! ! - * cmd->error---\ +--------+ ! ! - * ! +--------------->! SENSE !<----------/ ! - * STAT_FAIL----/ +--------+ ! - * ! ! V - * ! V +--------+ - * \--------------------------------\--------------------->! DONE ! - * +--------+ - */ - -/* - * This many LUNs per USB device. - * Every one of them takes a host, see UB_MAX_HOSTS. - */ -#define UB_MAX_LUNS 9 - -/* - */ - -#define UB_PARTS_PER_LUN 8 - -#define UB_MAX_CDB_SIZE 16 /* Corresponds to Bulk */ - -#define UB_SENSE_SIZE 18 - -/* - */ -struct ub_dev; - -#define UB_MAX_REQ_SG 9 /* cdrecord requires 32KB and maybe a header */ -#define UB_MAX_SECTORS 64 - -/* - * A second is more than enough for a 32K transfer (UB_MAX_SECTORS) - * even if a webcam hogs the bus, but some devices need time to spin up. - */ -#define UB_URB_TIMEOUT (HZ*2) -#define UB_DATA_TIMEOUT (HZ*5) /* ZIP does spin-ups in the data phase */ -#define UB_STAT_TIMEOUT (HZ*5) /* Same spinups and eject for a dataless cmd. */ -#define UB_CTRL_TIMEOUT (HZ/2) /* 500ms ought to be enough to clear a stall */ - -/* - * An instance of a SCSI command in transit. - */ -#define UB_DIR_NONE 0 -#define UB_DIR_READ 1 -#define UB_DIR_ILLEGAL2 2 -#define UB_DIR_WRITE 3 - -#define UB_DIR_CHAR(c) (((c)==UB_DIR_WRITE)? 'w': \ - (((c)==UB_DIR_READ)? 'r': 'n')) - -enum ub_scsi_cmd_state { - UB_CMDST_INIT, /* Initial state */ - UB_CMDST_CMD, /* Command submitted */ - UB_CMDST_DATA, /* Data phase */ - UB_CMDST_CLR2STS, /* Clearing before requesting status */ - UB_CMDST_STAT, /* Status phase */ - UB_CMDST_CLEAR, /* Clearing a stall (halt, actually) */ - UB_CMDST_CLRRS, /* Clearing before retrying status */ - UB_CMDST_SENSE, /* Sending Request Sense */ - UB_CMDST_DONE /* Final state */ -}; - -struct ub_scsi_cmd { - unsigned char cdb[UB_MAX_CDB_SIZE]; - unsigned char cdb_len; - - unsigned char dir; /* 0 - none, 1 - read, 3 - write. */ - enum ub_scsi_cmd_state state; - unsigned int tag; - struct ub_scsi_cmd *next; - - int error; /* Return code - valid upon done */ - unsigned int act_len; /* Return size */ - unsigned char key, asc, ascq; /* May be valid if error==-EIO */ - - int stat_count; /* Retries getting status. */ - unsigned int timeo; /* jiffies until rq->timeout changes */ - - unsigned int len; /* Requested length */ - unsigned int current_sg; - unsigned int nsg; /* sgv[nsg] */ - struct scatterlist sgv[UB_MAX_REQ_SG]; - - struct ub_lun *lun; - void (*done)(struct ub_dev *, struct ub_scsi_cmd *); - void *back; -}; - -struct ub_request { - struct request *rq; - unsigned int current_try; - unsigned int nsg; /* sgv[nsg] */ - struct scatterlist sgv[UB_MAX_REQ_SG]; -}; - -/* - */ -struct ub_capacity { - unsigned long nsec; /* Linux size - 512 byte sectors */ - unsigned int bsize; /* Linux hardsect_size */ - unsigned int bshift; /* Shift between 512 and hard sects */ -}; - -/* - * This is a direct take-off from linux/include/completion.h - * The difference is that I do not wait on this thing, just poll. - * When I want to wait (ub_probe), I just use the stock completion. - * - * Note that INIT_COMPLETION takes no lock. It is correct. But why - * in the bloody hell that thing takes struct instead of pointer to struct - * is quite beyond me. I just copied it from the stock completion. - */ -struct ub_completion { - unsigned int done; - spinlock_t lock; -}; - -static DEFINE_MUTEX(ub_mutex); -static inline void ub_init_completion(struct ub_completion *x) -{ - x->done = 0; - spin_lock_init(&x->lock); -} - -#define UB_INIT_COMPLETION(x) ((x).done = 0) - -static void ub_complete(struct ub_completion *x) -{ - unsigned long flags; - - spin_lock_irqsave(&x->lock, flags); - x->done++; - spin_unlock_irqrestore(&x->lock, flags); -} - -static int ub_is_completed(struct ub_completion *x) -{ - unsigned long flags; - int ret; - - spin_lock_irqsave(&x->lock, flags); - ret = x->done; - spin_unlock_irqrestore(&x->lock, flags); - return ret; -} - -/* - */ -struct ub_scsi_cmd_queue { - int qlen, qmax; - struct ub_scsi_cmd *head, *tail; -}; - -/* - * The block device instance (one per LUN). - */ -struct ub_lun { - struct ub_dev *udev; - struct list_head link; - struct gendisk *disk; - int id; /* Host index */ - int num; /* LUN number */ - char name[16]; - - int changed; /* Media was changed */ - int removable; - int readonly; - - struct ub_request urq; - - /* Use Ingo's mempool if or when we have more than one command. */ - /* - * Currently we never need more than one command for the whole device. - * However, giving every LUN a command is a cheap and automatic way - * to enforce fairness between them. - */ - int cmda[1]; - struct ub_scsi_cmd cmdv[1]; - - struct ub_capacity capacity; -}; - -/* - * The USB device instance. - */ -struct ub_dev { - spinlock_t *lock; - atomic_t poison; /* The USB device is disconnected */ - int openc; /* protected by ub_lock! */ - /* kref is too implicit for our taste */ - int reset; /* Reset is running */ - int bad_resid; - unsigned int tagcnt; - char name[12]; - struct usb_device *dev; - struct usb_interface *intf; - - struct list_head luns; - - unsigned int send_bulk_pipe; /* cached pipe values */ - unsigned int recv_bulk_pipe; - unsigned int send_ctrl_pipe; - unsigned int recv_ctrl_pipe; - - struct tasklet_struct tasklet; - - struct ub_scsi_cmd_queue cmd_queue; - struct ub_scsi_cmd top_rqs_cmd; /* REQUEST SENSE */ - unsigned char top_sense[UB_SENSE_SIZE]; - - struct ub_completion work_done; - struct urb work_urb; - struct timer_list work_timer; - int last_pipe; /* What might need clearing */ - __le32 signature; /* Learned signature */ - struct bulk_cb_wrap work_bcb; - struct bulk_cs_wrap work_bcs; - struct usb_ctrlrequest work_cr; - - struct work_struct reset_work; - wait_queue_head_t reset_wait; -}; - -/* - */ -static void ub_cleanup(struct ub_dev *sc); -static int ub_request_fn_1(struct ub_lun *lun, struct request *rq); -static void ub_cmd_build_block(struct ub_dev *sc, struct ub_lun *lun, - struct ub_scsi_cmd *cmd, struct ub_request *urq); -static void ub_cmd_build_packet(struct ub_dev *sc, struct ub_lun *lun, - struct ub_scsi_cmd *cmd, struct ub_request *urq); -static void ub_rw_cmd_done(struct ub_dev *sc, struct ub_scsi_cmd *cmd); -static void ub_end_rq(struct request *rq, unsigned int status); -static int ub_rw_cmd_retry(struct ub_dev *sc, struct ub_lun *lun, - struct ub_request *urq, struct ub_scsi_cmd *cmd); -static int ub_submit_scsi(struct ub_dev *sc, struct ub_scsi_cmd *cmd); -static void ub_urb_complete(struct urb *urb); -static void ub_scsi_action(unsigned long _dev); -static void ub_scsi_dispatch(struct ub_dev *sc); -static void ub_scsi_urb_compl(struct ub_dev *sc, struct ub_scsi_cmd *cmd); -static void ub_data_start(struct ub_dev *sc, struct ub_scsi_cmd *cmd); -static void ub_state_done(struct ub_dev *sc, struct ub_scsi_cmd *cmd, int rc); -static int __ub_state_stat(struct ub_dev *sc, struct ub_scsi_cmd *cmd); -static void ub_state_stat(struct ub_dev *sc, struct ub_scsi_cmd *cmd); -static void ub_state_stat_counted(struct ub_dev *sc, struct ub_scsi_cmd *cmd); -static void ub_state_sense(struct ub_dev *sc, struct ub_scsi_cmd *cmd); -static int ub_submit_clear_stall(struct ub_dev *sc, struct ub_scsi_cmd *cmd, - int stalled_pipe); -static void ub_top_sense_done(struct ub_dev *sc, struct ub_scsi_cmd *scmd); -static void ub_reset_enter(struct ub_dev *sc, int try); -static void ub_reset_task(struct work_struct *work); -static int ub_sync_tur(struct ub_dev *sc, struct ub_lun *lun); -static int ub_sync_read_cap(struct ub_dev *sc, struct ub_lun *lun, - struct ub_capacity *ret); -static int ub_sync_reset(struct ub_dev *sc); -static int ub_probe_clear_stall(struct ub_dev *sc, int stalled_pipe); -static int ub_probe_lun(struct ub_dev *sc, int lnum); - -/* - */ -#ifdef CONFIG_USB_LIBUSUAL - -#define ub_usb_ids usb_storage_usb_ids -#else - -static const struct usb_device_id ub_usb_ids[] = { - { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, USB_SC_SCSI, USB_PR_BULK) }, - { } -}; - -MODULE_DEVICE_TABLE(usb, ub_usb_ids); -#endif /* CONFIG_USB_LIBUSUAL */ - -/* - * Find me a way to identify "next free minor" for add_disk(), - * and the array disappears the next day. However, the number of - * hosts has something to do with the naming and /proc/partitions. - * This has to be thought out in detail before changing. - * If UB_MAX_HOST was 1000, we'd use a bitmap. Or a better data structure. - */ -#define UB_MAX_HOSTS 26 -static char ub_hostv[UB_MAX_HOSTS]; - -#define UB_QLOCK_NUM 5 -static spinlock_t ub_qlockv[UB_QLOCK_NUM]; -static int ub_qlock_next = 0; - -static DEFINE_SPINLOCK(ub_lock); /* Locks globals and ->openc */ - -/* - * The id allocator. - * - * This also stores the host for indexing by minor, which is somewhat dirty. - */ -static int ub_id_get(void) -{ - unsigned long flags; - int i; - - spin_lock_irqsave(&ub_lock, flags); - for (i = 0; i < UB_MAX_HOSTS; i++) { - if (ub_hostv[i] == 0) { - ub_hostv[i] = 1; - spin_unlock_irqrestore(&ub_lock, flags); - return i; - } - } - spin_unlock_irqrestore(&ub_lock, flags); - return -1; -} - -static void ub_id_put(int id) -{ - unsigned long flags; - - if (id < 0 || id >= UB_MAX_HOSTS) { - printk(KERN_ERR DRV_NAME ": bad host ID %d\n", id); - return; - } - - spin_lock_irqsave(&ub_lock, flags); - if (ub_hostv[id] == 0) { - spin_unlock_irqrestore(&ub_lock, flags); - printk(KERN_ERR DRV_NAME ": freeing free host ID %d\n", id); - return; - } - ub_hostv[id] = 0; - spin_unlock_irqrestore(&ub_lock, flags); -} - -/* - * This is necessitated by the fact that blk_cleanup_queue does not - * necesserily destroy the queue. Instead, it may merely decrease q->refcnt. - * Since our blk_init_queue() passes a spinlock common with ub_dev, - * we have life time issues when ub_cleanup frees ub_dev. - */ -static spinlock_t *ub_next_lock(void) -{ - unsigned long flags; - spinlock_t *ret; - - spin_lock_irqsave(&ub_lock, flags); - ret = &ub_qlockv[ub_qlock_next]; - ub_qlock_next = (ub_qlock_next + 1) % UB_QLOCK_NUM; - spin_unlock_irqrestore(&ub_lock, flags); - return ret; -} - -/* - * Downcount for deallocation. This rides on two assumptions: - * - once something is poisoned, its refcount cannot grow - * - opens cannot happen at this time (del_gendisk was done) - * If the above is true, we can drop the lock, which we need for - * blk_cleanup_queue(): the silly thing may attempt to sleep. - * [Actually, it never needs to sleep for us, but it calls might_sleep()] - */ -static void ub_put(struct ub_dev *sc) -{ - unsigned long flags; - - spin_lock_irqsave(&ub_lock, flags); - --sc->openc; - if (sc->openc == 0 && atomic_read(&sc->poison)) { - spin_unlock_irqrestore(&ub_lock, flags); - ub_cleanup(sc); - } else { - spin_unlock_irqrestore(&ub_lock, flags); - } -} - -/* - * Final cleanup and deallocation. - */ -static void ub_cleanup(struct ub_dev *sc) -{ - struct list_head *p; - struct ub_lun *lun; - struct request_queue *q; - - while (!list_empty(&sc->luns)) { - p = sc->luns.next; - lun = list_entry(p, struct ub_lun, link); - list_del(p); - - /* I don't think queue can be NULL. But... Stolen from sx8.c */ - if ((q = lun->disk->queue) != NULL) - blk_cleanup_queue(q); - /* - * If we zero disk->private_data BEFORE put_disk, we have - * to check for NULL all over the place in open, release, - * check_media and revalidate, because the block level - * semaphore is well inside the put_disk. - * But we cannot zero after the call, because *disk is gone. - * The sd.c is blatantly racy in this area. - */ - /* disk->private_data = NULL; */ - put_disk(lun->disk); - lun->disk = NULL; - - ub_id_put(lun->id); - kfree(lun); - } - - usb_set_intfdata(sc->intf, NULL); - usb_put_intf(sc->intf); - usb_put_dev(sc->dev); - kfree(sc); -} - -/* - * The "command allocator". - */ -static struct ub_scsi_cmd *ub_get_cmd(struct ub_lun *lun) -{ - struct ub_scsi_cmd *ret; - - if (lun->cmda[0]) - return NULL; - ret = &lun->cmdv[0]; - lun->cmda[0] = 1; - return ret; -} - -static void ub_put_cmd(struct ub_lun *lun, struct ub_scsi_cmd *cmd) -{ - if (cmd != &lun->cmdv[0]) { - printk(KERN_WARNING "%s: releasing a foreign cmd %p\n", - lun->name, cmd); - return; - } - if (!lun->cmda[0]) { - printk(KERN_WARNING "%s: releasing a free cmd\n", lun->name); - return; - } - lun->cmda[0] = 0; -} - -/* - * The command queue. - */ -static void ub_cmdq_add(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - struct ub_scsi_cmd_queue *t = &sc->cmd_queue; - - if (t->qlen++ == 0) { - t->head = cmd; - t->tail = cmd; - } else { - t->tail->next = cmd; - t->tail = cmd; - } - - if (t->qlen > t->qmax) - t->qmax = t->qlen; -} - -static void ub_cmdq_insert(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - struct ub_scsi_cmd_queue *t = &sc->cmd_queue; - - if (t->qlen++ == 0) { - t->head = cmd; - t->tail = cmd; - } else { - cmd->next = t->head; - t->head = cmd; - } - - if (t->qlen > t->qmax) - t->qmax = t->qlen; -} - -static struct ub_scsi_cmd *ub_cmdq_pop(struct ub_dev *sc) -{ - struct ub_scsi_cmd_queue *t = &sc->cmd_queue; - struct ub_scsi_cmd *cmd; - - if (t->qlen == 0) - return NULL; - if (--t->qlen == 0) - t->tail = NULL; - cmd = t->head; - t->head = cmd->next; - cmd->next = NULL; - return cmd; -} - -#define ub_cmdq_peek(sc) ((sc)->cmd_queue.head) - -/* - * The request function is our main entry point - */ - -static void ub_request_fn(struct request_queue *q) -{ - struct ub_lun *lun = q->queuedata; - struct request *rq; - - while ((rq = blk_peek_request(q)) != NULL) { - if (ub_request_fn_1(lun, rq) != 0) { - blk_stop_queue(q); - break; - } - } -} - -static int ub_request_fn_1(struct ub_lun *lun, struct request *rq) -{ - struct ub_dev *sc = lun->udev; - struct ub_scsi_cmd *cmd; - struct ub_request *urq; - int n_elem; - - if (atomic_read(&sc->poison)) { - blk_start_request(rq); - ub_end_rq(rq, DID_NO_CONNECT << 16); - return 0; - } - - if (lun->changed && rq->cmd_type != REQ_TYPE_BLOCK_PC) { - blk_start_request(rq); - ub_end_rq(rq, SAM_STAT_CHECK_CONDITION); - return 0; - } - - if (lun->urq.rq != NULL) - return -1; - if ((cmd = ub_get_cmd(lun)) == NULL) - return -1; - memset(cmd, 0, sizeof(struct ub_scsi_cmd)); - - blk_start_request(rq); - - urq = &lun->urq; - memset(urq, 0, sizeof(struct ub_request)); - urq->rq = rq; - - /* - * get scatterlist from block layer - */ - sg_init_table(&urq->sgv[0], UB_MAX_REQ_SG); - n_elem = blk_rq_map_sg(lun->disk->queue, rq, &urq->sgv[0]); - if (n_elem < 0) { - /* Impossible, because blk_rq_map_sg should not hit ENOMEM. */ - printk(KERN_INFO "%s: failed request map (%d)\n", - lun->name, n_elem); - goto drop; - } - if (n_elem > UB_MAX_REQ_SG) { /* Paranoia */ - printk(KERN_WARNING "%s: request with %d segments\n", - lun->name, n_elem); - goto drop; - } - urq->nsg = n_elem; - - if (rq->cmd_type == REQ_TYPE_BLOCK_PC) { - ub_cmd_build_packet(sc, lun, cmd, urq); - } else { - ub_cmd_build_block(sc, lun, cmd, urq); - } - cmd->state = UB_CMDST_INIT; - cmd->lun = lun; - cmd->done = ub_rw_cmd_done; - cmd->back = urq; - - cmd->tag = sc->tagcnt++; - if (ub_submit_scsi(sc, cmd) != 0) - goto drop; - - return 0; - -drop: - ub_put_cmd(lun, cmd); - ub_end_rq(rq, DID_ERROR << 16); - return 0; -} - -static void ub_cmd_build_block(struct ub_dev *sc, struct ub_lun *lun, - struct ub_scsi_cmd *cmd, struct ub_request *urq) -{ - struct request *rq = urq->rq; - unsigned int block, nblks; - - if (rq_data_dir(rq) == WRITE) - cmd->dir = UB_DIR_WRITE; - else - cmd->dir = UB_DIR_READ; - - cmd->nsg = urq->nsg; - memcpy(cmd->sgv, urq->sgv, sizeof(struct scatterlist) * cmd->nsg); - - /* - * build the command - * - * The call to blk_queue_logical_block_size() guarantees that request - * is aligned, but it is given in terms of 512 byte units, always. - */ - block = blk_rq_pos(rq) >> lun->capacity.bshift; - nblks = blk_rq_sectors(rq) >> lun->capacity.bshift; - - cmd->cdb[0] = (cmd->dir == UB_DIR_READ)? READ_10: WRITE_10; - /* 10-byte uses 4 bytes of LBA: 2147483648KB, 2097152MB, 2048GB */ - cmd->cdb[2] = block >> 24; - cmd->cdb[3] = block >> 16; - cmd->cdb[4] = block >> 8; - cmd->cdb[5] = block; - cmd->cdb[7] = nblks >> 8; - cmd->cdb[8] = nblks; - cmd->cdb_len = 10; - - cmd->len = blk_rq_bytes(rq); -} - -static void ub_cmd_build_packet(struct ub_dev *sc, struct ub_lun *lun, - struct ub_scsi_cmd *cmd, struct ub_request *urq) -{ - struct request *rq = urq->rq; - - if (blk_rq_bytes(rq) == 0) { - cmd->dir = UB_DIR_NONE; - } else { - if (rq_data_dir(rq) == WRITE) - cmd->dir = UB_DIR_WRITE; - else - cmd->dir = UB_DIR_READ; - } - - cmd->nsg = urq->nsg; - memcpy(cmd->sgv, urq->sgv, sizeof(struct scatterlist) * cmd->nsg); - - memcpy(&cmd->cdb, rq->cmd, rq->cmd_len); - cmd->cdb_len = rq->cmd_len; - - cmd->len = blk_rq_bytes(rq); - - /* - * To reapply this to every URB is not as incorrect as it looks. - * In return, we avoid any complicated tracking calculations. - */ - cmd->timeo = rq->timeout; -} - -static void ub_rw_cmd_done(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - struct ub_lun *lun = cmd->lun; - struct ub_request *urq = cmd->back; - struct request *rq; - unsigned int scsi_status; - - rq = urq->rq; - - if (cmd->error == 0) { - if (rq->cmd_type == REQ_TYPE_BLOCK_PC) { - if (cmd->act_len >= rq->resid_len) - rq->resid_len = 0; - else - rq->resid_len -= cmd->act_len; - scsi_status = 0; - } else { - if (cmd->act_len != cmd->len) { - scsi_status = SAM_STAT_CHECK_CONDITION; - } else { - scsi_status = 0; - } - } - } else { - if (rq->cmd_type == REQ_TYPE_BLOCK_PC) { - /* UB_SENSE_SIZE is smaller than SCSI_SENSE_BUFFERSIZE */ - memcpy(rq->sense, sc->top_sense, UB_SENSE_SIZE); - rq->sense_len = UB_SENSE_SIZE; - if (sc->top_sense[0] != 0) - scsi_status = SAM_STAT_CHECK_CONDITION; - else - scsi_status = DID_ERROR << 16; - } else { - if (cmd->error == -EIO && - (cmd->key == 0 || - cmd->key == MEDIUM_ERROR || - cmd->key == UNIT_ATTENTION)) { - if (ub_rw_cmd_retry(sc, lun, urq, cmd) == 0) - return; - } - scsi_status = SAM_STAT_CHECK_CONDITION; - } - } - - urq->rq = NULL; - - ub_put_cmd(lun, cmd); - ub_end_rq(rq, scsi_status); - blk_start_queue(lun->disk->queue); -} - -static void ub_end_rq(struct request *rq, unsigned int scsi_status) -{ - int error; - - if (scsi_status == 0) { - error = 0; - } else { - error = -EIO; - rq->errors = scsi_status; - } - __blk_end_request_all(rq, error); -} - -static int ub_rw_cmd_retry(struct ub_dev *sc, struct ub_lun *lun, - struct ub_request *urq, struct ub_scsi_cmd *cmd) -{ - - if (atomic_read(&sc->poison)) - return -ENXIO; - - ub_reset_enter(sc, urq->current_try); - - if (urq->current_try >= 3) - return -EIO; - urq->current_try++; - - /* Remove this if anyone complains of flooding. */ - printk(KERN_DEBUG "%s: dir %c len/act %d/%d " - "[sense %x %02x %02x] retry %d\n", - sc->name, UB_DIR_CHAR(cmd->dir), cmd->len, cmd->act_len, - cmd->key, cmd->asc, cmd->ascq, urq->current_try); - - memset(cmd, 0, sizeof(struct ub_scsi_cmd)); - ub_cmd_build_block(sc, lun, cmd, urq); - - cmd->state = UB_CMDST_INIT; - cmd->lun = lun; - cmd->done = ub_rw_cmd_done; - cmd->back = urq; - - cmd->tag = sc->tagcnt++; - -#if 0 /* Wasteful */ - return ub_submit_scsi(sc, cmd); -#else - ub_cmdq_add(sc, cmd); - return 0; -#endif -} - -/* - * Submit a regular SCSI operation (not an auto-sense). - * - * The Iron Law of Good Submit Routine is: - * Zero return - callback is done, Nonzero return - callback is not done. - * No exceptions. - * - * Host is assumed locked. - */ -static int ub_submit_scsi(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - - if (cmd->state != UB_CMDST_INIT || - (cmd->dir != UB_DIR_NONE && cmd->len == 0)) { - return -EINVAL; - } - - ub_cmdq_add(sc, cmd); - /* - * We can call ub_scsi_dispatch(sc) right away here, but it's a little - * safer to jump to a tasklet, in case upper layers do something silly. - */ - tasklet_schedule(&sc->tasklet); - return 0; -} - -/* - * Submit the first URB for the queued command. - * This function does not deal with queueing in any way. - */ -static int ub_scsi_cmd_start(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - struct bulk_cb_wrap *bcb; - int rc; - - bcb = &sc->work_bcb; - - /* - * ``If the allocation length is eighteen or greater, and a device - * server returns less than eithteen bytes of data, the application - * client should assume that the bytes not transferred would have been - * zeroes had the device server returned those bytes.'' - * - * We zero sense for all commands so that when a packet request - * fails it does not return a stale sense. - */ - memset(&sc->top_sense, 0, UB_SENSE_SIZE); - - /* set up the command wrapper */ - bcb->Signature = cpu_to_le32(US_BULK_CB_SIGN); - bcb->Tag = cmd->tag; /* Endianness is not important */ - bcb->DataTransferLength = cpu_to_le32(cmd->len); - bcb->Flags = (cmd->dir == UB_DIR_READ) ? 0x80 : 0; - bcb->Lun = (cmd->lun != NULL) ? cmd->lun->num : 0; - bcb->Length = cmd->cdb_len; - - /* copy the command payload */ - memcpy(bcb->CDB, cmd->cdb, UB_MAX_CDB_SIZE); - - UB_INIT_COMPLETION(sc->work_done); - - sc->last_pipe = sc->send_bulk_pipe; - usb_fill_bulk_urb(&sc->work_urb, sc->dev, sc->send_bulk_pipe, - bcb, US_BULK_CB_WRAP_LEN, ub_urb_complete, sc); - - if ((rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC)) != 0) { - /* XXX Clear stalls */ - ub_complete(&sc->work_done); - return rc; - } - - sc->work_timer.expires = jiffies + UB_URB_TIMEOUT; - add_timer(&sc->work_timer); - - cmd->state = UB_CMDST_CMD; - return 0; -} - -/* - * Timeout handler. - */ -static void ub_urb_timeout(unsigned long arg) -{ - struct ub_dev *sc = (struct ub_dev *) arg; - unsigned long flags; - - spin_lock_irqsave(sc->lock, flags); - if (!ub_is_completed(&sc->work_done)) - usb_unlink_urb(&sc->work_urb); - spin_unlock_irqrestore(sc->lock, flags); -} - -/* - * Completion routine for the work URB. - * - * This can be called directly from usb_submit_urb (while we have - * the sc->lock taken) and from an interrupt (while we do NOT have - * the sc->lock taken). Therefore, bounce this off to a tasklet. - */ -static void ub_urb_complete(struct urb *urb) -{ - struct ub_dev *sc = urb->context; - - ub_complete(&sc->work_done); - tasklet_schedule(&sc->tasklet); -} - -static void ub_scsi_action(unsigned long _dev) -{ - struct ub_dev *sc = (struct ub_dev *) _dev; - unsigned long flags; - - spin_lock_irqsave(sc->lock, flags); - ub_scsi_dispatch(sc); - spin_unlock_irqrestore(sc->lock, flags); -} - -static void ub_scsi_dispatch(struct ub_dev *sc) -{ - struct ub_scsi_cmd *cmd; - int rc; - - while (!sc->reset && (cmd = ub_cmdq_peek(sc)) != NULL) { - if (cmd->state == UB_CMDST_DONE) { - ub_cmdq_pop(sc); - (*cmd->done)(sc, cmd); - } else if (cmd->state == UB_CMDST_INIT) { - if ((rc = ub_scsi_cmd_start(sc, cmd)) == 0) - break; - cmd->error = rc; - cmd->state = UB_CMDST_DONE; - } else { - if (!ub_is_completed(&sc->work_done)) - break; - del_timer(&sc->work_timer); - ub_scsi_urb_compl(sc, cmd); - } - } -} - -static void ub_scsi_urb_compl(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - struct urb *urb = &sc->work_urb; - struct bulk_cs_wrap *bcs; - int endp; - int len; - int rc; - - if (atomic_read(&sc->poison)) { - ub_state_done(sc, cmd, -ENODEV); - return; - } - - endp = usb_pipeendpoint(sc->last_pipe); - if (usb_pipein(sc->last_pipe)) - endp |= USB_DIR_IN; - - if (cmd->state == UB_CMDST_CLEAR) { - if (urb->status == -EPIPE) { - /* - * STALL while clearning STALL. - * The control pipe clears itself - nothing to do. - */ - printk(KERN_NOTICE "%s: stall on control pipe\n", - sc->name); - goto Bad_End; - } - - /* - * We ignore the result for the halt clear. - */ - - usb_reset_endpoint(sc->dev, endp); - - ub_state_sense(sc, cmd); - - } else if (cmd->state == UB_CMDST_CLR2STS) { - if (urb->status == -EPIPE) { - printk(KERN_NOTICE "%s: stall on control pipe\n", - sc->name); - goto Bad_End; - } - - /* - * We ignore the result for the halt clear. - */ - - usb_reset_endpoint(sc->dev, endp); - - ub_state_stat(sc, cmd); - - } else if (cmd->state == UB_CMDST_CLRRS) { - if (urb->status == -EPIPE) { - printk(KERN_NOTICE "%s: stall on control pipe\n", - sc->name); - goto Bad_End; - } - - /* - * We ignore the result for the halt clear. - */ - - usb_reset_endpoint(sc->dev, endp); - - ub_state_stat_counted(sc, cmd); - - } else if (cmd->state == UB_CMDST_CMD) { - switch (urb->status) { - case 0: - break; - case -EOVERFLOW: - goto Bad_End; - case -EPIPE: - rc = ub_submit_clear_stall(sc, cmd, sc->last_pipe); - if (rc != 0) { - printk(KERN_NOTICE "%s: " - "unable to submit clear (%d)\n", - sc->name, rc); - /* - * This is typically ENOMEM or some other such shit. - * Retrying is pointless. Just do Bad End on it... - */ - ub_state_done(sc, cmd, rc); - return; - } - cmd->state = UB_CMDST_CLEAR; - return; - case -ESHUTDOWN: /* unplug */ - case -EILSEQ: /* unplug timeout on uhci */ - ub_state_done(sc, cmd, -ENODEV); - return; - default: - goto Bad_End; - } - if (urb->actual_length != US_BULK_CB_WRAP_LEN) { - goto Bad_End; - } - - if (cmd->dir == UB_DIR_NONE || cmd->nsg < 1) { - ub_state_stat(sc, cmd); - return; - } - - // udelay(125); // usb-storage has this - ub_data_start(sc, cmd); - - } else if (cmd->state == UB_CMDST_DATA) { - if (urb->status == -EPIPE) { - rc = ub_submit_clear_stall(sc, cmd, sc->last_pipe); - if (rc != 0) { - printk(KERN_NOTICE "%s: " - "unable to submit clear (%d)\n", - sc->name, rc); - ub_state_done(sc, cmd, rc); - return; - } - cmd->state = UB_CMDST_CLR2STS; - return; - } - if (urb->status == -EOVERFLOW) { - /* - * A babble? Failure, but we must transfer CSW now. - */ - cmd->error = -EOVERFLOW; /* A cheap trick... */ - ub_state_stat(sc, cmd); - return; - } - - if (cmd->dir == UB_DIR_WRITE) { - /* - * Do not continue writes in case of a failure. - * Doing so would cause sectors to be mixed up, - * which is worse than sectors lost. - * - * We must try to read the CSW, or many devices - * get confused. - */ - len = urb->actual_length; - if (urb->status != 0 || - len != cmd->sgv[cmd->current_sg].length) { - cmd->act_len += len; - - cmd->error = -EIO; - ub_state_stat(sc, cmd); - return; - } - - } else { - /* - * If an error occurs on read, we record it, and - * continue to fetch data in order to avoid bubble. - * - * As a small shortcut, we stop if we detect that - * a CSW mixed into data. - */ - if (urb->status != 0) - cmd->error = -EIO; - - len = urb->actual_length; - if (urb->status != 0 || - len != cmd->sgv[cmd->current_sg].length) { - if ((len & 0x1FF) == US_BULK_CS_WRAP_LEN) - goto Bad_End; - } - } - - cmd->act_len += urb->actual_length; - - if (++cmd->current_sg < cmd->nsg) { - ub_data_start(sc, cmd); - return; - } - ub_state_stat(sc, cmd); - - } else if (cmd->state == UB_CMDST_STAT) { - if (urb->status == -EPIPE) { - rc = ub_submit_clear_stall(sc, cmd, sc->last_pipe); - if (rc != 0) { - printk(KERN_NOTICE "%s: " - "unable to submit clear (%d)\n", - sc->name, rc); - ub_state_done(sc, cmd, rc); - return; - } - - /* - * Having a stall when getting CSW is an error, so - * make sure uppper levels are not oblivious to it. - */ - cmd->error = -EIO; /* A cheap trick... */ - - cmd->state = UB_CMDST_CLRRS; - return; - } - - /* Catch everything, including -EOVERFLOW and other nasties. */ - if (urb->status != 0) - goto Bad_End; - - if (urb->actual_length == 0) { - ub_state_stat_counted(sc, cmd); - return; - } - - /* - * Check the returned Bulk protocol status. - * The status block has to be validated first. - */ - - bcs = &sc->work_bcs; - - if (sc->signature == cpu_to_le32(0)) { - /* - * This is the first reply, so do not perform the check. - * Instead, remember the signature the device uses - * for future checks. But do not allow a nul. - */ - sc->signature = bcs->Signature; - if (sc->signature == cpu_to_le32(0)) { - ub_state_stat_counted(sc, cmd); - return; - } - } else { - if (bcs->Signature != sc->signature) { - ub_state_stat_counted(sc, cmd); - return; - } - } - - if (bcs->Tag != cmd->tag) { - /* - * This usually happens when we disagree with the - * device's microcode about something. For instance, - * a few of them throw this after timeouts. They buffer - * commands and reply at commands we timed out before. - * Without flushing these replies we loop forever. - */ - ub_state_stat_counted(sc, cmd); - return; - } - - if (!sc->bad_resid) { - len = le32_to_cpu(bcs->Residue); - if (len != cmd->len - cmd->act_len) { - /* - * Only start ignoring if this cmd ended well. - */ - if (cmd->len == cmd->act_len) { - printk(KERN_NOTICE "%s: " - "bad residual %d of %d, ignoring\n", - sc->name, len, cmd->len); - sc->bad_resid = 1; - } - } - } - - switch (bcs->Status) { - case US_BULK_STAT_OK: - break; - case US_BULK_STAT_FAIL: - ub_state_sense(sc, cmd); - return; - case US_BULK_STAT_PHASE: - goto Bad_End; - default: - printk(KERN_INFO "%s: unknown CSW status 0x%x\n", - sc->name, bcs->Status); - ub_state_done(sc, cmd, -EINVAL); - return; - } - - /* Not zeroing error to preserve a babble indicator */ - if (cmd->error != 0) { - ub_state_sense(sc, cmd); - return; - } - cmd->state = UB_CMDST_DONE; - ub_cmdq_pop(sc); - (*cmd->done)(sc, cmd); - - } else if (cmd->state == UB_CMDST_SENSE) { - ub_state_done(sc, cmd, -EIO); - - } else { - printk(KERN_WARNING "%s: wrong command state %d\n", - sc->name, cmd->state); - ub_state_done(sc, cmd, -EINVAL); - return; - } - return; - -Bad_End: /* Little Excel is dead */ - ub_state_done(sc, cmd, -EIO); -} - -/* - * Factorization helper for the command state machine: - * Initiate a data segment transfer. - */ -static void ub_data_start(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - struct scatterlist *sg = &cmd->sgv[cmd->current_sg]; - int pipe; - int rc; - - UB_INIT_COMPLETION(sc->work_done); - - if (cmd->dir == UB_DIR_READ) - pipe = sc->recv_bulk_pipe; - else - pipe = sc->send_bulk_pipe; - sc->last_pipe = pipe; - usb_fill_bulk_urb(&sc->work_urb, sc->dev, pipe, sg_virt(sg), - sg->length, ub_urb_complete, sc); - - if ((rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC)) != 0) { - /* XXX Clear stalls */ - ub_complete(&sc->work_done); - ub_state_done(sc, cmd, rc); - return; - } - - if (cmd->timeo) - sc->work_timer.expires = jiffies + cmd->timeo; - else - sc->work_timer.expires = jiffies + UB_DATA_TIMEOUT; - add_timer(&sc->work_timer); - - cmd->state = UB_CMDST_DATA; -} - -/* - * Factorization helper for the command state machine: - * Finish the command. - */ -static void ub_state_done(struct ub_dev *sc, struct ub_scsi_cmd *cmd, int rc) -{ - - cmd->error = rc; - cmd->state = UB_CMDST_DONE; - ub_cmdq_pop(sc); - (*cmd->done)(sc, cmd); -} - -/* - * Factorization helper for the command state machine: - * Submit a CSW read. - */ -static int __ub_state_stat(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - int rc; - - UB_INIT_COMPLETION(sc->work_done); - - sc->last_pipe = sc->recv_bulk_pipe; - usb_fill_bulk_urb(&sc->work_urb, sc->dev, sc->recv_bulk_pipe, - &sc->work_bcs, US_BULK_CS_WRAP_LEN, ub_urb_complete, sc); - - if ((rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC)) != 0) { - /* XXX Clear stalls */ - ub_complete(&sc->work_done); - ub_state_done(sc, cmd, rc); - return -1; - } - - if (cmd->timeo) - sc->work_timer.expires = jiffies + cmd->timeo; - else - sc->work_timer.expires = jiffies + UB_STAT_TIMEOUT; - add_timer(&sc->work_timer); - return 0; -} - -/* - * Factorization helper for the command state machine: - * Submit a CSW read and go to STAT state. - */ -static void ub_state_stat(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - - if (__ub_state_stat(sc, cmd) != 0) - return; - - cmd->stat_count = 0; - cmd->state = UB_CMDST_STAT; -} - -/* - * Factorization helper for the command state machine: - * Submit a CSW read and go to STAT state with counter (along [C] path). - */ -static void ub_state_stat_counted(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - - if (++cmd->stat_count >= 4) { - ub_state_sense(sc, cmd); - return; - } - - if (__ub_state_stat(sc, cmd) != 0) - return; - - cmd->state = UB_CMDST_STAT; -} - -/* - * Factorization helper for the command state machine: - * Submit a REQUEST SENSE and go to SENSE state. - */ -static void ub_state_sense(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - struct ub_scsi_cmd *scmd; - struct scatterlist *sg; - int rc; - - if (cmd->cdb[0] == REQUEST_SENSE) { - rc = -EPIPE; - goto error; - } - - scmd = &sc->top_rqs_cmd; - memset(scmd, 0, sizeof(struct ub_scsi_cmd)); - scmd->cdb[0] = REQUEST_SENSE; - scmd->cdb[4] = UB_SENSE_SIZE; - scmd->cdb_len = 6; - scmd->dir = UB_DIR_READ; - scmd->state = UB_CMDST_INIT; - scmd->nsg = 1; - sg = &scmd->sgv[0]; - sg_init_table(sg, UB_MAX_REQ_SG); - sg_set_page(sg, virt_to_page(sc->top_sense), UB_SENSE_SIZE, - (unsigned long)sc->top_sense & (PAGE_SIZE-1)); - scmd->len = UB_SENSE_SIZE; - scmd->lun = cmd->lun; - scmd->done = ub_top_sense_done; - scmd->back = cmd; - - scmd->tag = sc->tagcnt++; - - cmd->state = UB_CMDST_SENSE; - - ub_cmdq_insert(sc, scmd); - return; - -error: - ub_state_done(sc, cmd, rc); -} - -/* - * A helper for the command's state machine: - * Submit a stall clear. - */ -static int ub_submit_clear_stall(struct ub_dev *sc, struct ub_scsi_cmd *cmd, - int stalled_pipe) -{ - int endp; - struct usb_ctrlrequest *cr; - int rc; - - endp = usb_pipeendpoint(stalled_pipe); - if (usb_pipein (stalled_pipe)) - endp |= USB_DIR_IN; - - cr = &sc->work_cr; - cr->bRequestType = USB_RECIP_ENDPOINT; - cr->bRequest = USB_REQ_CLEAR_FEATURE; - cr->wValue = cpu_to_le16(USB_ENDPOINT_HALT); - cr->wIndex = cpu_to_le16(endp); - cr->wLength = cpu_to_le16(0); - - UB_INIT_COMPLETION(sc->work_done); - - usb_fill_control_urb(&sc->work_urb, sc->dev, sc->send_ctrl_pipe, - (unsigned char*) cr, NULL, 0, ub_urb_complete, sc); - - if ((rc = usb_submit_urb(&sc->work_urb, GFP_ATOMIC)) != 0) { - ub_complete(&sc->work_done); - return rc; - } - - sc->work_timer.expires = jiffies + UB_CTRL_TIMEOUT; - add_timer(&sc->work_timer); - return 0; -} - -/* - */ -static void ub_top_sense_done(struct ub_dev *sc, struct ub_scsi_cmd *scmd) -{ - unsigned char *sense = sc->top_sense; - struct ub_scsi_cmd *cmd; - - /* - * Find the command which triggered the unit attention or a check, - * save the sense into it, and advance its state machine. - */ - if ((cmd = ub_cmdq_peek(sc)) == NULL) { - printk(KERN_WARNING "%s: sense done while idle\n", sc->name); - return; - } - if (cmd != scmd->back) { - printk(KERN_WARNING "%s: " - "sense done for wrong command 0x%x\n", - sc->name, cmd->tag); - return; - } - if (cmd->state != UB_CMDST_SENSE) { - printk(KERN_WARNING "%s: sense done with bad cmd state %d\n", - sc->name, cmd->state); - return; - } - - /* - * Ignoring scmd->act_len, because the buffer was pre-zeroed. - */ - cmd->key = sense[2] & 0x0F; - cmd->asc = sense[12]; - cmd->ascq = sense[13]; - - ub_scsi_urb_compl(sc, cmd); -} - -/* - * Reset management - */ - -static void ub_reset_enter(struct ub_dev *sc, int try) -{ - - if (sc->reset) { - /* This happens often on multi-LUN devices. */ - return; - } - sc->reset = try + 1; - -#if 0 /* Not needed because the disconnect waits for us. */ - unsigned long flags; - spin_lock_irqsave(&ub_lock, flags); - sc->openc++; - spin_unlock_irqrestore(&ub_lock, flags); -#endif - -#if 0 /* We let them stop themselves. */ - struct ub_lun *lun; - list_for_each_entry(lun, &sc->luns, link) { - blk_stop_queue(lun->disk->queue); - } -#endif - - schedule_work(&sc->reset_work); -} - -static void ub_reset_task(struct work_struct *work) -{ - struct ub_dev *sc = container_of(work, struct ub_dev, reset_work); - unsigned long flags; - struct ub_lun *lun; - int rc; - - if (!sc->reset) { - printk(KERN_WARNING "%s: Running reset unrequested\n", - sc->name); - return; - } - - if (atomic_read(&sc->poison)) { - ; - } else if ((sc->reset & 1) == 0) { - ub_sync_reset(sc); - msleep(700); /* usb-storage sleeps 6s (!) */ - ub_probe_clear_stall(sc, sc->recv_bulk_pipe); - ub_probe_clear_stall(sc, sc->send_bulk_pipe); - } else if (sc->dev->actconfig->desc.bNumInterfaces != 1) { - ; - } else { - rc = usb_lock_device_for_reset(sc->dev, sc->intf); - if (rc < 0) { - printk(KERN_NOTICE - "%s: usb_lock_device_for_reset failed (%d)\n", - sc->name, rc); - } else { - rc = usb_reset_device(sc->dev); - if (rc < 0) { - printk(KERN_NOTICE "%s: " - "usb_lock_device_for_reset failed (%d)\n", - sc->name, rc); - } - usb_unlock_device(sc->dev); - } - } - - /* - * In theory, no commands can be running while reset is active, - * so nobody can ask for another reset, and so we do not need any - * queues of resets or anything. We do need a spinlock though, - * to interact with block layer. - */ - spin_lock_irqsave(sc->lock, flags); - sc->reset = 0; - tasklet_schedule(&sc->tasklet); - list_for_each_entry(lun, &sc->luns, link) { - blk_start_queue(lun->disk->queue); - } - wake_up(&sc->reset_wait); - spin_unlock_irqrestore(sc->lock, flags); -} - -/* - * XXX Reset brackets are too much hassle to implement, so just stub them - * in order to prevent forced unbinding (which deadlocks solid when our - * ->disconnect method waits for the reset to complete and this kills keventd). - * - * XXX Tell Alan to move usb_unlock_device inside of usb_reset_device, - * or else the post_reset is invoked, and restats I/O on a locked device. - */ -static int ub_pre_reset(struct usb_interface *iface) { - return 0; -} - -static int ub_post_reset(struct usb_interface *iface) { - return 0; -} - -/* - * This is called from a process context. - */ -static void ub_revalidate(struct ub_dev *sc, struct ub_lun *lun) -{ - - lun->readonly = 0; /* XXX Query this from the device */ - - lun->capacity.nsec = 0; - lun->capacity.bsize = 512; - lun->capacity.bshift = 0; - - if (ub_sync_tur(sc, lun) != 0) - return; /* Not ready */ - lun->changed = 0; - - if (ub_sync_read_cap(sc, lun, &lun->capacity) != 0) { - /* - * The retry here means something is wrong, either with the - * device, with the transport, or with our code. - * We keep this because sd.c has retries for capacity. - */ - if (ub_sync_read_cap(sc, lun, &lun->capacity) != 0) { - lun->capacity.nsec = 0; - lun->capacity.bsize = 512; - lun->capacity.bshift = 0; - } - } -} - -/* - * The open funcion. - * This is mostly needed to keep refcounting, but also to support - * media checks on removable media drives. - */ -static int ub_bd_open(struct block_device *bdev, fmode_t mode) -{ - struct ub_lun *lun = bdev->bd_disk->private_data; - struct ub_dev *sc = lun->udev; - unsigned long flags; - int rc; - - spin_lock_irqsave(&ub_lock, flags); - if (atomic_read(&sc->poison)) { - spin_unlock_irqrestore(&ub_lock, flags); - return -ENXIO; - } - sc->openc++; - spin_unlock_irqrestore(&ub_lock, flags); - - if (lun->removable || lun->readonly) - check_disk_change(bdev); - - /* - * The sd.c considers ->media_present and ->changed not equivalent, - * under some pretty murky conditions (a failure of READ CAPACITY). - * We may need it one day. - */ - if (lun->removable && lun->changed && !(mode & FMODE_NDELAY)) { - rc = -ENOMEDIUM; - goto err_open; - } - - if (lun->readonly && (mode & FMODE_WRITE)) { - rc = -EROFS; - goto err_open; - } - - return 0; - -err_open: - ub_put(sc); - return rc; -} - -static int ub_bd_unlocked_open(struct block_device *bdev, fmode_t mode) -{ - int ret; - - mutex_lock(&ub_mutex); - ret = ub_bd_open(bdev, mode); - mutex_unlock(&ub_mutex); - - return ret; -} - - -/* - */ -static int ub_bd_release(struct gendisk *disk, fmode_t mode) -{ - struct ub_lun *lun = disk->private_data; - struct ub_dev *sc = lun->udev; - - mutex_lock(&ub_mutex); - ub_put(sc); - mutex_unlock(&ub_mutex); - - return 0; -} - -/* - * The ioctl interface. - */ -static int ub_bd_ioctl(struct block_device *bdev, fmode_t mode, - unsigned int cmd, unsigned long arg) -{ - void __user *usermem = (void __user *) arg; - int ret; - - mutex_lock(&ub_mutex); - ret = scsi_cmd_blk_ioctl(bdev, mode, cmd, usermem); - mutex_unlock(&ub_mutex); - - return ret; -} - -/* - * This is called by check_disk_change if we reported a media change. - * The main onjective here is to discover the features of the media such as - * the capacity, read-only status, etc. USB storage generally does not - * need to be spun up, but if we needed it, this would be the place. - * - * This call can sleep. - * - * The return code is not used. - */ -static int ub_bd_revalidate(struct gendisk *disk) -{ - struct ub_lun *lun = disk->private_data; - - ub_revalidate(lun->udev, lun); - - /* XXX Support sector size switching like in sr.c */ - blk_queue_logical_block_size(disk->queue, lun->capacity.bsize); - set_capacity(disk, lun->capacity.nsec); - // set_disk_ro(sdkp->disk, lun->readonly); - - return 0; -} - -/* - * The check is called by the block layer to verify if the media - * is still available. It is supposed to be harmless, lightweight and - * non-intrusive in case the media was not changed. - * - * This call can sleep. - * - * The return code is bool! - */ -static unsigned int ub_bd_check_events(struct gendisk *disk, - unsigned int clearing) -{ - struct ub_lun *lun = disk->private_data; - - if (!lun->removable) - return 0; - - /* - * We clean checks always after every command, so this is not - * as dangerous as it looks. If the TEST_UNIT_READY fails here, - * the device is actually not ready with operator or software - * intervention required. One dangerous item might be a drive which - * spins itself down, and come the time to write dirty pages, this - * will fail, then block layer discards the data. Since we never - * spin drives up, such devices simply cannot be used with ub anyway. - */ - if (ub_sync_tur(lun->udev, lun) != 0) { - lun->changed = 1; - return DISK_EVENT_MEDIA_CHANGE; - } - - return lun->changed ? DISK_EVENT_MEDIA_CHANGE : 0; -} - -static const struct block_device_operations ub_bd_fops = { - .owner = THIS_MODULE, - .open = ub_bd_unlocked_open, - .release = ub_bd_release, - .ioctl = ub_bd_ioctl, - .check_events = ub_bd_check_events, - .revalidate_disk = ub_bd_revalidate, -}; - -/* - * Common ->done routine for commands executed synchronously. - */ -static void ub_probe_done(struct ub_dev *sc, struct ub_scsi_cmd *cmd) -{ - struct completion *cop = cmd->back; - complete(cop); -} - -/* - * Test if the device has a check condition on it, synchronously. - */ -static int ub_sync_tur(struct ub_dev *sc, struct ub_lun *lun) -{ - struct ub_scsi_cmd *cmd; - enum { ALLOC_SIZE = sizeof(struct ub_scsi_cmd) }; - unsigned long flags; - struct completion compl; - int rc; - - init_completion(&compl); - - rc = -ENOMEM; - if ((cmd = kzalloc(ALLOC_SIZE, GFP_KERNEL)) == NULL) - goto err_alloc; - - cmd->cdb[0] = TEST_UNIT_READY; - cmd->cdb_len = 6; - cmd->dir = UB_DIR_NONE; - cmd->state = UB_CMDST_INIT; - cmd->lun = lun; /* This may be NULL, but that's ok */ - cmd->done = ub_probe_done; - cmd->back = &compl; - - spin_lock_irqsave(sc->lock, flags); - cmd->tag = sc->tagcnt++; - - rc = ub_submit_scsi(sc, cmd); - spin_unlock_irqrestore(sc->lock, flags); - - if (rc != 0) - goto err_submit; - - wait_for_completion(&compl); - - rc = cmd->error; - - if (rc == -EIO && cmd->key != 0) /* Retries for benh's key */ - rc = cmd->key; - -err_submit: - kfree(cmd); -err_alloc: - return rc; -} - -/* - * Read the SCSI capacity synchronously (for probing). - */ -static int ub_sync_read_cap(struct ub_dev *sc, struct ub_lun *lun, - struct ub_capacity *ret) -{ - struct ub_scsi_cmd *cmd; - struct scatterlist *sg; - char *p; - enum { ALLOC_SIZE = sizeof(struct ub_scsi_cmd) + 8 }; - unsigned long flags; - unsigned int bsize, shift; - unsigned long nsec; - struct completion compl; - int rc; - - init_completion(&compl); - - rc = -ENOMEM; - if ((cmd = kzalloc(ALLOC_SIZE, GFP_KERNEL)) == NULL) - goto err_alloc; - p = (char *)cmd + sizeof(struct ub_scsi_cmd); - - cmd->cdb[0] = 0x25; - cmd->cdb_len = 10; - cmd->dir = UB_DIR_READ; - cmd->state = UB_CMDST_INIT; - cmd->nsg = 1; - sg = &cmd->sgv[0]; - sg_init_table(sg, UB_MAX_REQ_SG); - sg_set_page(sg, virt_to_page(p), 8, (unsigned long)p & (PAGE_SIZE-1)); - cmd->len = 8; - cmd->lun = lun; - cmd->done = ub_probe_done; - cmd->back = &compl; - - spin_lock_irqsave(sc->lock, flags); - cmd->tag = sc->tagcnt++; - - rc = ub_submit_scsi(sc, cmd); - spin_unlock_irqrestore(sc->lock, flags); - - if (rc != 0) - goto err_submit; - - wait_for_completion(&compl); - - if (cmd->error != 0) { - rc = -EIO; - goto err_read; - } - if (cmd->act_len != 8) { - rc = -EIO; - goto err_read; - } - - /* sd.c special-cases sector size of 0 to mean 512. Needed? Safe? */ - nsec = be32_to_cpu(*(__be32 *)p) + 1; - bsize = be32_to_cpu(*(__be32 *)(p + 4)); - switch (bsize) { - case 512: shift = 0; break; - case 1024: shift = 1; break; - case 2048: shift = 2; break; - case 4096: shift = 3; break; - default: - rc = -EDOM; - goto err_inv_bsize; - } - - ret->bsize = bsize; - ret->bshift = shift; - ret->nsec = nsec << shift; - rc = 0; - -err_inv_bsize: -err_read: -err_submit: - kfree(cmd); -err_alloc: - return rc; -} - -/* - */ -static void ub_probe_urb_complete(struct urb *urb) -{ - struct completion *cop = urb->context; - complete(cop); -} - -static void ub_probe_timeout(unsigned long arg) -{ - struct completion *cop = (struct completion *) arg; - complete(cop); -} - -/* - * Reset with a Bulk reset. - */ -static int ub_sync_reset(struct ub_dev *sc) -{ - int ifnum = sc->intf->cur_altsetting->desc.bInterfaceNumber; - struct usb_ctrlrequest *cr; - struct completion compl; - struct timer_list timer; - int rc; - - init_completion(&compl); - - cr = &sc->work_cr; - cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE; - cr->bRequest = US_BULK_RESET_REQUEST; - cr->wValue = cpu_to_le16(0); - cr->wIndex = cpu_to_le16(ifnum); - cr->wLength = cpu_to_le16(0); - - usb_fill_control_urb(&sc->work_urb, sc->dev, sc->send_ctrl_pipe, - (unsigned char*) cr, NULL, 0, ub_probe_urb_complete, &compl); - - if ((rc = usb_submit_urb(&sc->work_urb, GFP_KERNEL)) != 0) { - printk(KERN_WARNING - "%s: Unable to submit a bulk reset (%d)\n", sc->name, rc); - return rc; - } - - init_timer(&timer); - timer.function = ub_probe_timeout; - timer.data = (unsigned long) &compl; - timer.expires = jiffies + UB_CTRL_TIMEOUT; - add_timer(&timer); - - wait_for_completion(&compl); - - del_timer_sync(&timer); - usb_kill_urb(&sc->work_urb); - - return sc->work_urb.status; -} - -/* - * Get number of LUNs by the way of Bulk GetMaxLUN command. - */ -static int ub_sync_getmaxlun(struct ub_dev *sc) -{ - int ifnum = sc->intf->cur_altsetting->desc.bInterfaceNumber; - unsigned char *p; - enum { ALLOC_SIZE = 1 }; - struct usb_ctrlrequest *cr; - struct completion compl; - struct timer_list timer; - int nluns; - int rc; - - init_completion(&compl); - - rc = -ENOMEM; - if ((p = kmalloc(ALLOC_SIZE, GFP_KERNEL)) == NULL) - goto err_alloc; - *p = 55; - - cr = &sc->work_cr; - cr->bRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE; - cr->bRequest = US_BULK_GET_MAX_LUN; - cr->wValue = cpu_to_le16(0); - cr->wIndex = cpu_to_le16(ifnum); - cr->wLength = cpu_to_le16(1); - - usb_fill_control_urb(&sc->work_urb, sc->dev, sc->recv_ctrl_pipe, - (unsigned char*) cr, p, 1, ub_probe_urb_complete, &compl); - - if ((rc = usb_submit_urb(&sc->work_urb, GFP_KERNEL)) != 0) - goto err_submit; - - init_timer(&timer); - timer.function = ub_probe_timeout; - timer.data = (unsigned long) &compl; - timer.expires = jiffies + UB_CTRL_TIMEOUT; - add_timer(&timer); - - wait_for_completion(&compl); - - del_timer_sync(&timer); - usb_kill_urb(&sc->work_urb); - - if ((rc = sc->work_urb.status) < 0) - goto err_io; - - if (sc->work_urb.actual_length != 1) { - nluns = 0; - } else { - if ((nluns = *p) == 55) { - nluns = 0; - } else { - /* GetMaxLUN returns the maximum LUN number */ - nluns += 1; - if (nluns > UB_MAX_LUNS) - nluns = UB_MAX_LUNS; - } - } - - kfree(p); - return nluns; - -err_io: -err_submit: - kfree(p); -err_alloc: - return rc; -} - -/* - * Clear initial stalls. - */ -static int ub_probe_clear_stall(struct ub_dev *sc, int stalled_pipe) -{ - int endp; - struct usb_ctrlrequest *cr; - struct completion compl; - struct timer_list timer; - int rc; - - init_completion(&compl); - - endp = usb_pipeendpoint(stalled_pipe); - if (usb_pipein (stalled_pipe)) - endp |= USB_DIR_IN; - - cr = &sc->work_cr; - cr->bRequestType = USB_RECIP_ENDPOINT; - cr->bRequest = USB_REQ_CLEAR_FEATURE; - cr->wValue = cpu_to_le16(USB_ENDPOINT_HALT); - cr->wIndex = cpu_to_le16(endp); - cr->wLength = cpu_to_le16(0); - - usb_fill_control_urb(&sc->work_urb, sc->dev, sc->send_ctrl_pipe, - (unsigned char*) cr, NULL, 0, ub_probe_urb_complete, &compl); - - if ((rc = usb_submit_urb(&sc->work_urb, GFP_KERNEL)) != 0) { - printk(KERN_WARNING - "%s: Unable to submit a probe clear (%d)\n", sc->name, rc); - return rc; - } - - init_timer(&timer); - timer.function = ub_probe_timeout; - timer.data = (unsigned long) &compl; - timer.expires = jiffies + UB_CTRL_TIMEOUT; - add_timer(&timer); - - wait_for_completion(&compl); - - del_timer_sync(&timer); - usb_kill_urb(&sc->work_urb); - - usb_reset_endpoint(sc->dev, endp); - - return 0; -} - -/* - * Get the pipe settings. - */ -static int ub_get_pipes(struct ub_dev *sc, struct usb_device *dev, - struct usb_interface *intf) -{ - struct usb_host_interface *altsetting = intf->cur_altsetting; - struct usb_endpoint_descriptor *ep_in = NULL; - struct usb_endpoint_descriptor *ep_out = NULL; - struct usb_endpoint_descriptor *ep; - int i; - - /* - * Find the endpoints we need. - * We are expecting a minimum of 2 endpoints - in and out (bulk). - * We will ignore any others. - */ - for (i = 0; i < altsetting->desc.bNumEndpoints; i++) { - ep = &altsetting->endpoint[i].desc; - - /* Is it a BULK endpoint? */ - if (usb_endpoint_xfer_bulk(ep)) { - /* BULK in or out? */ - if (usb_endpoint_dir_in(ep)) { - if (ep_in == NULL) - ep_in = ep; - } else { - if (ep_out == NULL) - ep_out = ep; - } - } - } - - if (ep_in == NULL || ep_out == NULL) { - printk(KERN_NOTICE "%s: failed endpoint check\n", sc->name); - return -ENODEV; - } - - /* Calculate and store the pipe values */ - sc->send_ctrl_pipe = usb_sndctrlpipe(dev, 0); - sc->recv_ctrl_pipe = usb_rcvctrlpipe(dev, 0); - sc->send_bulk_pipe = usb_sndbulkpipe(dev, - usb_endpoint_num(ep_out)); - sc->recv_bulk_pipe = usb_rcvbulkpipe(dev, - usb_endpoint_num(ep_in)); - - return 0; -} - -/* - * Probing is done in the process context, which allows us to cheat - * and not to build a state machine for the discovery. - */ -static int ub_probe(struct usb_interface *intf, - const struct usb_device_id *dev_id) -{ - struct ub_dev *sc; - int nluns; - int rc; - int i; - - if (usb_usual_check_type(dev_id, USB_US_TYPE_UB)) - return -ENXIO; - - rc = -ENOMEM; - if ((sc = kzalloc(sizeof(struct ub_dev), GFP_KERNEL)) == NULL) - goto err_core; - sc->lock = ub_next_lock(); - INIT_LIST_HEAD(&sc->luns); - usb_init_urb(&sc->work_urb); - tasklet_init(&sc->tasklet, ub_scsi_action, (unsigned long)sc); - atomic_set(&sc->poison, 0); - INIT_WORK(&sc->reset_work, ub_reset_task); - init_waitqueue_head(&sc->reset_wait); - - init_timer(&sc->work_timer); - sc->work_timer.data = (unsigned long) sc; - sc->work_timer.function = ub_urb_timeout; - - ub_init_completion(&sc->work_done); - sc->work_done.done = 1; /* A little yuk, but oh well... */ - - sc->dev = interface_to_usbdev(intf); - sc->intf = intf; - // sc->ifnum = intf->cur_altsetting->desc.bInterfaceNumber; - usb_set_intfdata(intf, sc); - usb_get_dev(sc->dev); - /* - * Since we give the interface struct to the block level through - * disk->driverfs_dev, we have to pin it. Otherwise, block_uevent - * oopses on close after a disconnect (kernels 2.6.16 and up). - */ - usb_get_intf(sc->intf); - - snprintf(sc->name, 12, DRV_NAME "(%d.%d)", - sc->dev->bus->busnum, sc->dev->devnum); - - /* XXX Verify that we can handle the device (from descriptors) */ - - if (ub_get_pipes(sc, sc->dev, intf) != 0) - goto err_dev_desc; - - /* - * At this point, all USB initialization is done, do upper layer. - * We really hate halfway initialized structures, so from the - * invariants perspective, this ub_dev is fully constructed at - * this point. - */ - - /* - * This is needed to clear toggles. It is a problem only if we do - * `rmmod ub && modprobe ub` without disconnects, but we like that. - */ -#if 0 /* iPod Mini fails if we do this (big white iPod works) */ - ub_probe_clear_stall(sc, sc->recv_bulk_pipe); - ub_probe_clear_stall(sc, sc->send_bulk_pipe); -#endif - - /* - * The way this is used by the startup code is a little specific. - * A SCSI check causes a USB stall. Our common case code sees it - * and clears the check, after which the device is ready for use. - * But if a check was not present, any command other than - * TEST_UNIT_READY ends with a lockup (including REQUEST_SENSE). - * - * If we neglect to clear the SCSI check, the first real command fails - * (which is the capacity readout). We clear that and retry, but why - * causing spurious retries for no reason. - * - * Revalidation may start with its own TEST_UNIT_READY, but that one - * has to succeed, so we clear checks with an additional one here. - * In any case it's not our business how revaliadation is implemented. - */ - for (i = 0; i < 3; i++) { /* Retries for the schwag key from KS'04 */ - if ((rc = ub_sync_tur(sc, NULL)) <= 0) break; - if (rc != 0x6) break; - msleep(10); - } - - nluns = 1; - for (i = 0; i < 3; i++) { - if ((rc = ub_sync_getmaxlun(sc)) < 0) - break; - if (rc != 0) { - nluns = rc; - break; - } - msleep(100); - } - - for (i = 0; i < nluns; i++) { - ub_probe_lun(sc, i); - } - return 0; - -err_dev_desc: - usb_set_intfdata(intf, NULL); - usb_put_intf(sc->intf); - usb_put_dev(sc->dev); - kfree(sc); -err_core: - return rc; -} - -static int ub_probe_lun(struct ub_dev *sc, int lnum) -{ - struct ub_lun *lun; - struct request_queue *q; - struct gendisk *disk; - int rc; - - rc = -ENOMEM; - if ((lun = kzalloc(sizeof(struct ub_lun), GFP_KERNEL)) == NULL) - goto err_alloc; - lun->num = lnum; - - rc = -ENOSR; - if ((lun->id = ub_id_get()) == -1) - goto err_id; - - lun->udev = sc; - - snprintf(lun->name, 16, DRV_NAME "%c(%d.%d.%d)", - lun->id + 'a', sc->dev->bus->busnum, sc->dev->devnum, lun->num); - - lun->removable = 1; /* XXX Query this from the device */ - lun->changed = 1; /* ub_revalidate clears only */ - ub_revalidate(sc, lun); - - rc = -ENOMEM; - if ((disk = alloc_disk(UB_PARTS_PER_LUN)) == NULL) - goto err_diskalloc; - - sprintf(disk->disk_name, DRV_NAME "%c", lun->id + 'a'); - disk->major = UB_MAJOR; - disk->first_minor = lun->id * UB_PARTS_PER_LUN; - disk->fops = &ub_bd_fops; - disk->private_data = lun; - disk->driverfs_dev = &sc->intf->dev; - - rc = -ENOMEM; - if ((q = blk_init_queue(ub_request_fn, sc->lock)) == NULL) - goto err_blkqinit; - - disk->queue = q; - - blk_queue_bounce_limit(q, BLK_BOUNCE_HIGH); - blk_queue_max_segments(q, UB_MAX_REQ_SG); - blk_queue_segment_boundary(q, 0xffffffff); /* Dubious. */ - blk_queue_max_hw_sectors(q, UB_MAX_SECTORS); - blk_queue_logical_block_size(q, lun->capacity.bsize); - - lun->disk = disk; - q->queuedata = lun; - list_add(&lun->link, &sc->luns); - - set_capacity(disk, lun->capacity.nsec); - if (lun->removable) - disk->flags |= GENHD_FL_REMOVABLE; - - add_disk(disk); - - return 0; - -err_blkqinit: - put_disk(disk); -err_diskalloc: - ub_id_put(lun->id); -err_id: - kfree(lun); -err_alloc: - return rc; -} - -static void ub_disconnect(struct usb_interface *intf) -{ - struct ub_dev *sc = usb_get_intfdata(intf); - struct ub_lun *lun; - unsigned long flags; - - /* - * Prevent ub_bd_release from pulling the rug from under us. - * XXX This is starting to look like a kref. - * XXX Why not to take this ref at probe time? - */ - spin_lock_irqsave(&ub_lock, flags); - sc->openc++; - spin_unlock_irqrestore(&ub_lock, flags); - - /* - * Fence stall clearings, operations triggered by unlinkings and so on. - * We do not attempt to unlink any URBs, because we do not trust the - * unlink paths in HC drivers. Also, we get -84 upon disconnect anyway. - */ - atomic_set(&sc->poison, 1); - - /* - * Wait for reset to end, if any. - */ - wait_event(sc->reset_wait, !sc->reset); - - /* - * Blow away queued commands. - * - * Actually, this never works, because before we get here - * the HCD terminates outstanding URB(s). It causes our - * SCSI command queue to advance, commands fail to submit, - * and the whole queue drains. So, we just use this code to - * print warnings. - */ - spin_lock_irqsave(sc->lock, flags); - { - struct ub_scsi_cmd *cmd; - int cnt = 0; - while ((cmd = ub_cmdq_peek(sc)) != NULL) { - cmd->error = -ENOTCONN; - cmd->state = UB_CMDST_DONE; - ub_cmdq_pop(sc); - (*cmd->done)(sc, cmd); - cnt++; - } - if (cnt != 0) { - printk(KERN_WARNING "%s: " - "%d was queued after shutdown\n", sc->name, cnt); - } - } - spin_unlock_irqrestore(sc->lock, flags); - - /* - * Unregister the upper layer. - */ - list_for_each_entry(lun, &sc->luns, link) { - del_gendisk(lun->disk); - /* - * I wish I could do: - * queue_flag_set(QUEUE_FLAG_DEAD, q); - * As it is, we rely on our internal poisoning and let - * the upper levels to spin furiously failing all the I/O. - */ - } - - /* - * Testing for -EINPROGRESS is always a bug, so we are bending - * the rules a little. - */ - spin_lock_irqsave(sc->lock, flags); - if (sc->work_urb.status == -EINPROGRESS) { /* janitors: ignore */ - printk(KERN_WARNING "%s: " - "URB is active after disconnect\n", sc->name); - } - spin_unlock_irqrestore(sc->lock, flags); - - /* - * There is virtually no chance that other CPU runs a timeout so long - * after ub_urb_complete should have called del_timer, but only if HCD - * didn't forget to deliver a callback on unlink. - */ - del_timer_sync(&sc->work_timer); - - /* - * At this point there must be no commands coming from anyone - * and no URBs left in transit. - */ - - ub_put(sc); -} - -static struct usb_driver ub_driver = { - .name = "ub", - .probe = ub_probe, - .disconnect = ub_disconnect, - .id_table = ub_usb_ids, - .pre_reset = ub_pre_reset, - .post_reset = ub_post_reset, -}; - -static int __init ub_init(void) -{ - int rc; - int i; - - pr_info("'Low Performance USB Block' driver is deprecated. " - "Please switch to usb-storage\n"); - for (i = 0; i < UB_QLOCK_NUM; i++) - spin_lock_init(&ub_qlockv[i]); - - if ((rc = register_blkdev(UB_MAJOR, DRV_NAME)) != 0) - goto err_regblkdev; - - if ((rc = usb_register(&ub_driver)) != 0) - goto err_register; - - usb_usual_set_present(USB_US_TYPE_UB); - return 0; - -err_register: - unregister_blkdev(UB_MAJOR, DRV_NAME); -err_regblkdev: - return rc; -} - -static void __exit ub_exit(void) -{ - usb_deregister(&ub_driver); - - unregister_blkdev(UB_MAJOR, DRV_NAME); - usb_usual_clear_present(USB_US_TYPE_UB); -} - -module_init(ub_init); -module_exit(ub_exit); - -MODULE_LICENSE("GPL"); -- cgit v1.2.3-59-g8ed1b From 657b306a7bdfca4ae1514b533a0e7c3c6d26dbc6 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Thu, 6 Sep 2012 20:27:06 +0530 Subject: usb: phy: add a new driver for omap usb2 phy All phy related programming like enabling/disabling the clocks, powering on/off the phy is taken care of by this driver. It is also used for OTG related functionality like srp. This also includes device tree support for usb2 phy driver and the documentation with device tree binding information is updated. Currently writing to control module register is taken care in this driver which will be removed once the control module driver is in place. Cc: Felipe Balbi Signed-off-by: Kishon Vijay Abraham I Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/usb-phy.txt | 17 ++ drivers/usb/phy/Kconfig | 9 + drivers/usb/phy/Makefile | 1 + drivers/usb/phy/omap-usb2.c | 271 ++++++++++++++++++++++ include/linux/usb/omap_usb.h | 46 ++++ include/linux/usb/phy_companion.h | 34 +++ 6 files changed, 378 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/usb-phy.txt create mode 100644 drivers/usb/phy/omap-usb2.c create mode 100644 include/linux/usb/omap_usb.h create mode 100644 include/linux/usb/phy_companion.h (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/usb-phy.txt b/Documentation/devicetree/bindings/usb/usb-phy.txt new file mode 100644 index 000000000000..80d4148cb661 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usb-phy.txt @@ -0,0 +1,17 @@ +USB PHY + +OMAP USB2 PHY + +Required properties: + - compatible: Should be "ti,omap-usb2" + - reg : Address and length of the register set for the device. Also +add the address of control module dev conf register until a driver for +control module is added + +This is usually a subnode of ocp2scp to which it is connected. + +usb2phy@4a0ad080 { + compatible = "ti,omap-usb2"; + reg = <0x4a0ad080 0x58>, + <0x4a002300 0x4>; +}; diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 2838adb225e8..63c339b3e676 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -4,6 +4,15 @@ comment "USB Physical Layer drivers" depends on USB || USB_GADGET +config OMAP_USB2 + tristate "OMAP USB2 PHY Driver" + select USB_OTG_UTILS + help + Enable this to support the transceiver that is part of SOC. This + driver takes care of all the PHY functionality apart from comparator. + The USB OTG controller communicates with the comparator using this + driver. + config USB_ISP1301 tristate "NXP ISP1301 USB transceiver support" depends on USB || USB_GADGET diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index bb948fb8654c..b069f29f1225 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -4,6 +4,7 @@ ccflags-$(CONFIG_USB_DEBUG) := -DDEBUG +obj-$(CONFIG_OMAP_USB2) += omap-usb2.o obj-$(CONFIG_USB_ISP1301) += isp1301.o obj-$(CONFIG_MV_U3D_PHY) += mv_u3d_phy.o obj-$(CONFIG_USB_EHCI_TEGRA) += tegra_usb_phy.o diff --git a/drivers/usb/phy/omap-usb2.c b/drivers/usb/phy/omap-usb2.c new file mode 100644 index 000000000000..15ab3d6f2e8c --- /dev/null +++ b/drivers/usb/phy/omap-usb2.c @@ -0,0 +1,271 @@ +/* + * omap-usb2.c - USB PHY, talking to musb controller in OMAP. + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * 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. + * + * Author: Kishon Vijay Abraham I + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * omap_usb2_set_comparator - links the comparator present in the sytem with + * this phy + * @comparator - the companion phy(comparator) for this phy + * + * The phy companion driver should call this API passing the phy_companion + * filled with set_vbus and start_srp to be used by usb phy. + * + * For use by phy companion driver + */ +int omap_usb2_set_comparator(struct phy_companion *comparator) +{ + struct omap_usb *phy; + struct usb_phy *x = usb_get_phy(USB_PHY_TYPE_USB2); + + if (IS_ERR(x)) + return -ENODEV; + + phy = phy_to_omapusb(x); + phy->comparator = comparator; + return 0; +} +EXPORT_SYMBOL_GPL(omap_usb2_set_comparator); + +/** + * omap_usb_phy_power - power on/off the phy using control module reg + * @phy: struct omap_usb * + * @on: 0 or 1, based on powering on or off the PHY + * + * XXX: Remove this function once control module driver gets merged + */ +static void omap_usb_phy_power(struct omap_usb *phy, int on) +{ + u32 val; + + if (on) { + val = readl(phy->control_dev); + if (val & PHY_PD) { + writel(~PHY_PD, phy->control_dev); + /* XXX: add proper documentation for this delay */ + mdelay(200); + } + } else { + writel(PHY_PD, phy->control_dev); + } +} + +static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled) +{ + struct omap_usb *phy = phy_to_omapusb(otg->phy); + + if (!phy->comparator) + return -ENODEV; + + return phy->comparator->set_vbus(phy->comparator, enabled); +} + +static int omap_usb_start_srp(struct usb_otg *otg) +{ + struct omap_usb *phy = phy_to_omapusb(otg->phy); + + if (!phy->comparator) + return -ENODEV; + + return phy->comparator->start_srp(phy->comparator); +} + +static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct usb_phy *phy = otg->phy; + + otg->host = host; + if (!host) + phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct usb_phy *phy = otg->phy; + + otg->gadget = gadget; + if (!gadget) + phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb2_suspend(struct usb_phy *x, int suspend) +{ + u32 ret; + struct omap_usb *phy = phy_to_omapusb(x); + + if (suspend && !phy->is_suspended) { + omap_usb_phy_power(phy, 0); + pm_runtime_put_sync(phy->dev); + phy->is_suspended = 1; + } else if (!suspend && phy->is_suspended) { + ret = pm_runtime_get_sync(phy->dev); + if (ret < 0) { + dev_err(phy->dev, "get_sync failed with err %d\n", + ret); + return ret; + } + omap_usb_phy_power(phy, 1); + phy->is_suspended = 0; + } + + return 0; +} + +static int __devinit omap_usb2_probe(struct platform_device *pdev) +{ + struct omap_usb *phy; + struct usb_otg *otg; + struct resource *res; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + dev_err(&pdev->dev, "unable to allocate memory for USB2 PHY\n"); + return -ENOMEM; + } + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) { + dev_err(&pdev->dev, "unable to allocate memory for USB OTG\n"); + return -ENOMEM; + } + + phy->dev = &pdev->dev; + + phy->phy.dev = phy->dev; + phy->phy.label = "omap-usb2"; + phy->phy.set_suspend = omap_usb2_suspend; + phy->phy.otg = otg; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + + phy->control_dev = devm_request_and_ioremap(&pdev->dev, res); + if (phy->control_dev == NULL) { + dev_err(&pdev->dev, "Failed to obtain io memory\n"); + return -ENXIO; + } + + phy->is_suspended = 1; + omap_usb_phy_power(phy, 0); + + otg->set_host = omap_usb_set_host; + otg->set_peripheral = omap_usb_set_peripheral; + otg->set_vbus = omap_usb_set_vbus; + otg->start_srp = omap_usb_start_srp; + otg->phy = &phy->phy; + + phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); + if (IS_ERR(phy->wkupclk)) { + dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); + return PTR_ERR(phy->wkupclk); + } + clk_prepare(phy->wkupclk); + + usb_add_phy(&phy->phy, USB_PHY_TYPE_USB2); + + platform_set_drvdata(pdev, phy); + + pm_runtime_enable(phy->dev); + + return 0; +} + +static int __devexit omap_usb2_remove(struct platform_device *pdev) +{ + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_unprepare(phy->wkupclk); + usb_remove_phy(&phy->phy); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME + +static int omap_usb2_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_disable(phy->wkupclk); + + return 0; +} + +static int omap_usb2_runtime_resume(struct device *dev) +{ + u32 ret = 0; + struct platform_device *pdev = to_platform_device(dev); + struct omap_usb *phy = platform_get_drvdata(pdev); + + ret = clk_enable(phy->wkupclk); + if (ret < 0) + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + + return ret; +} + +static const struct dev_pm_ops omap_usb2_pm_ops = { + SET_RUNTIME_PM_OPS(omap_usb2_runtime_suspend, omap_usb2_runtime_resume, + NULL) +}; + +#define DEV_PM_OPS (&omap_usb2_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id omap_usb2_id_table[] = { + { .compatible = "ti,omap-usb2" }, + {} +}; +MODULE_DEVICE_TABLE(of, omap_usb2_id_table); +#endif + +static struct platform_driver omap_usb2_driver = { + .probe = omap_usb2_probe, + .remove = __devexit_p(omap_usb2_remove), + .driver = { + .name = "omap-usb2", + .owner = THIS_MODULE, + .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(omap_usb2_id_table), + }, +}; + +module_platform_driver(omap_usb2_driver); + +MODULE_ALIAS("platform: omap_usb2"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP USB2 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/usb/omap_usb.h b/include/linux/usb/omap_usb.h new file mode 100644 index 000000000000..0ea17f8ae820 --- /dev/null +++ b/include/linux/usb/omap_usb.h @@ -0,0 +1,46 @@ +/* + * omap_usb.h -- omap usb2 phy header file + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * 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. + * + * Author: Kishon Vijay Abraham I + * + * 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 __DRIVERS_OMAP_USB2_H +#define __DRIVERS_OMAP_USB2_H + +#include + +struct omap_usb { + struct usb_phy phy; + struct phy_companion *comparator; + struct device *dev; + u32 __iomem *control_dev; + struct clk *wkupclk; + u8 is_suspended:1; +}; + +#define PHY_PD 0x1 + +#define phy_to_omapusb(x) container_of((x), struct omap_usb, phy) + +#if defined(CONFIG_OMAP_USB2) || defined(CONFIG_OMAP_USB2_MODULE) +extern int omap_usb2_set_comparator(struct phy_companion *comparator); +#else +static inline int omap_usb2_set_comparator(struct phy_companion *comparator) +{ + return -ENODEV; +} +#endif + +#endif /* __DRIVERS_OMAP_USB_H */ diff --git a/include/linux/usb/phy_companion.h b/include/linux/usb/phy_companion.h new file mode 100644 index 000000000000..edd2ec23d282 --- /dev/null +++ b/include/linux/usb/phy_companion.h @@ -0,0 +1,34 @@ +/* + * phy-companion.h -- phy companion to indicate the comparator part of PHY + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * 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. + * + * Author: Kishon Vijay Abraham I + * + * 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 __DRIVERS_PHY_COMPANION_H +#define __DRIVERS_PHY_COMPANION_H + +#include + +/* phy_companion to take care of VBUS, ID and srp capabilities */ +struct phy_companion { + + /* effective for A-peripheral, ignored for B devices */ + int (*set_vbus)(struct phy_companion *x, bool enabled); + + /* for B devices only: start session with A-Host */ + int (*start_srp)(struct phy_companion *x); +}; + +#endif /* __DRIVERS_PHY_COMPANION_H */ -- cgit v1.2.3-59-g8ed1b From ff0a1f39400a8d2c02343bd22d295517d72e58ec Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Thu, 6 Sep 2012 20:27:08 +0530 Subject: usb: twl6030: Add dt support for twl6030 usb Add device tree support for twl6030 usb driver. Update the Documentation with device tree binding information. Signed-off-by: Kishon Vijay Abraham I Signed-off-by: Felipe Balbi --- .../devicetree/bindings/usb/twlxxxx-usb.txt | 21 ++++++++++++ drivers/usb/otg/twl6030-usb.c | 39 ++++++++++++++-------- 2 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/twlxxxx-usb.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt b/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt new file mode 100644 index 000000000000..930f9ff2952a --- /dev/null +++ b/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt @@ -0,0 +1,21 @@ +USB COMPARATOR OF TWL CHIPS + +TWL6030 USB COMPARATOR + - compatible : Should be "ti,twl6030-usb" + - interrupts : Two interrupt numbers to the cpu should be specified. First + interrupt number is the otg interrupt number that raises ID interrupts when + the controller has to act as host and the second interrupt number is the + usb interrupt number that raises VBUS interrupts when the controller has to + act as device + - usb-supply : phandle to the regulator device tree node. It should be vusb + if it is twl6030 or ldousb if it is twl6025 subclass. + +twl6030-usb { + compatible = "ti,twl6030-usb"; + interrupts = < 4 10 >; +}; + +Board specific device node entry +&twl6030-usb { + usb-supply = <&vusb>; +}; diff --git a/drivers/usb/otg/twl6030-usb.c b/drivers/usb/otg/twl6030-usb.c index 32525bb667ee..fcadef7864f1 100644 --- a/drivers/usb/otg/twl6030-usb.c +++ b/drivers/usb/otg/twl6030-usb.c @@ -105,7 +105,7 @@ struct twl6030_usb { u8 asleep; bool irq_enabled; bool vbus_enable; - unsigned long features; + const char *regulator; }; #define comparator_to_twl(x) container_of((x), struct twl6030_usb, comparator) @@ -153,13 +153,6 @@ static int twl6030_start_srp(struct phy_companion *comparator) static int twl6030_usb_ldo_init(struct twl6030_usb *twl) { - char *regulator_name; - - if (twl->features & TWL6025_SUBCLASS) - regulator_name = "ldousb"; - else - regulator_name = "vusb"; - /* Set to OTG_REV 1.3 and turn on the ID_WAKEUP_COMP */ twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x1, TWL6030_BACKUP_REG); @@ -169,7 +162,7 @@ static int twl6030_usb_ldo_init(struct twl6030_usb *twl) /* Program MISC2 register and set bit VUSB_IN_VBAT */ twl6030_writeb(twl, TWL6030_MODULE_ID0 , 0x10, TWL6030_MISC2); - twl->usb3v3 = regulator_get(twl->dev, regulator_name); + twl->usb3v3 = regulator_get(twl->dev, twl->regulator); if (IS_ERR(twl->usb3v3)) return -ENODEV; @@ -322,9 +315,9 @@ static int __devinit twl6030_usb_probe(struct platform_device *pdev) u32 ret; struct twl6030_usb *twl; int status, err; - struct twl4030_usb_data *pdata; - struct device *dev = &pdev->dev; - pdata = dev->platform_data; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct twl4030_usb_data *pdata = dev->platform_data; twl = devm_kzalloc(dev, sizeof *twl, GFP_KERNEL); if (!twl) @@ -333,7 +326,6 @@ static int __devinit twl6030_usb_probe(struct platform_device *pdev) twl->dev = &pdev->dev; twl->irq1 = platform_get_irq(pdev, 0); twl->irq2 = platform_get_irq(pdev, 1); - twl->features = pdata->features; twl->linkstat = OMAP_MUSB_UNKNOWN; twl->comparator.set_vbus = twl6030_set_vbus; @@ -345,6 +337,18 @@ static int __devinit twl6030_usb_probe(struct platform_device *pdev) return -EPROBE_DEFER; } + if (np) { + twl->regulator = "usb"; + } else if (pdata) { + if (pdata->features & TWL6025_SUBCLASS) + twl->regulator = "ldousb"; + else + twl->regulator = "vusb"; + } else { + dev_err(&pdev->dev, "twl6030 initialized without pdata\n"); + return -EINVAL; + } + /* init spinlock for workqueue */ spin_lock_init(&twl->lock); @@ -406,12 +410,21 @@ static int __exit twl6030_usb_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id twl6030_usb_id_table[] = { + { .compatible = "ti,twl6030-usb" }, + {} +}; +MODULE_DEVICE_TABLE(of, twl6030_usb_id_table); +#endif + static struct platform_driver twl6030_usb_driver = { .probe = twl6030_usb_probe, .remove = __exit_p(twl6030_usb_remove), .driver = { .name = "twl6030_usb", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(twl6030_usb_id_table), }, }; -- cgit v1.2.3-59-g8ed1b From f8515f0639c337b6fac2f24073368ae834053a96 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Thu, 6 Sep 2012 20:27:09 +0530 Subject: usb: twl4030: Add device tree support for twl4030 usb Add device tree support for twl4030 usb driver. Update the Documentation with device tree binding information. Signed-off-by: Kishon Vijay Abraham I Signed-off-by: Felipe Balbi --- .../devicetree/bindings/usb/twlxxxx-usb.txt | 19 ++++++++++++++++ drivers/usb/otg/twl4030-usb.c | 26 +++++++++++++++++----- 2 files changed, 39 insertions(+), 6 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt b/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt index 930f9ff2952a..36b9aede3f40 100644 --- a/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt +++ b/Documentation/devicetree/bindings/usb/twlxxxx-usb.txt @@ -19,3 +19,22 @@ Board specific device node entry &twl6030-usb { usb-supply = <&vusb>; }; + +TWL4030 USB PHY AND COMPARATOR + - compatible : Should be "ti,twl4030-usb" + - interrupts : The interrupt numbers to the cpu should be specified. First + interrupt number is the otg interrupt number that raises ID interrupts + and VBUS interrupts. The second interrupt number is optional. + - -supply : phandle to the regulator device tree node. + should be vusb1v5, vusb1v8 and vusb3v1 + - usb_mode : The mode used by the phy to connect to the controller. "1" + specifies "ULPI" mode and "2" specifies "CEA2011_3PIN" mode. + +twl4030-usb { + compatible = "ti,twl4030-usb"; + interrupts = < 10 4 >; + usb1v5-supply = <&vusb1v5>; + usb1v8-supply = <&vusb1v8>; + usb3v1-supply = <&vusb3v1>; + usb_mode = <1>; +}; diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c index 523cad5bfea9..f0d2e7530cfe 100644 --- a/drivers/usb/otg/twl4030-usb.c +++ b/drivers/usb/otg/twl4030-usb.c @@ -585,23 +585,28 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev) struct twl4030_usb *twl; int status, err; struct usb_otg *otg; - - if (!pdata) { - dev_dbg(&pdev->dev, "platform_data not available\n"); - return -EINVAL; - } + struct device_node *np = pdev->dev.of_node; twl = devm_kzalloc(&pdev->dev, sizeof *twl, GFP_KERNEL); if (!twl) return -ENOMEM; + if (np) + of_property_read_u32(np, "usb_mode", + (enum twl4030_usb_mode *)&twl->usb_mode); + else if (pdata) + twl->usb_mode = pdata->usb_mode; + else { + dev_err(&pdev->dev, "twl4030 initialized without pdata\n"); + return -EINVAL; + } + otg = devm_kzalloc(&pdev->dev, sizeof *otg, GFP_KERNEL); if (!otg) return -ENOMEM; twl->dev = &pdev->dev; twl->irq = platform_get_irq(pdev, 0); - twl->usb_mode = pdata->usb_mode; twl->vbus_supplied = false; twl->asleep = 1; twl->linkstat = OMAP_MUSB_UNKNOWN; @@ -690,12 +695,21 @@ static int __exit twl4030_usb_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id twl4030_usb_id_table[] = { + { .compatible = "ti,twl4030-usb" }, + {} +}; +MODULE_DEVICE_TABLE(of, twl4030_usb_id_table); +#endif + static struct platform_driver twl4030_usb_driver = { .probe = twl4030_usb_probe, .remove = __exit_p(twl4030_usb_remove), .driver = { .name = "twl4030_usb", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(twl4030_usb_id_table), }, }; -- cgit v1.2.3-59-g8ed1b From fa2a9566257a3b62c328ea5d621ccf5952079dac Mon Sep 17 00:00:00 2001 From: Lan Tianyu Date: Wed, 5 Sep 2012 13:44:31 +0800 Subject: usb: make usb port a real device This patch turns each USB port on a hub into a new struct device. This new device has the USB hub interface device as its parent. The port devices are stored in a new structure (usb_port), and an array of usb_ports are dynamically allocated once we know how many ports the USB hub has. Move the port_owner variable out of usb_hub and into this new structure. A new file will be created in the hub interface sysfs directory, so add documentation. Acked-by: Alan Stern Signed-off-by: Lan Tianyu Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 7 +++ drivers/usb/core/hub.c | 91 +++++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 16 deletions(-) (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 5f75f8f7df34..b6fbe514a869 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -220,3 +220,10 @@ Description: If the device doesn't support LTM, the file will read "no". The file will be present for all speeds of USB devices, and will always read "no" for USB 1.1 and USB 2.0 devices. + +What: /sys/bus/usb/devices/.../(hub interface)/portX +Date: August 2012 +Contact: Lan Tianyu +Description: + The /sys/bus/usb/devices/.../(hub interface)/portX + is usb port device's sysfs directory. diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 128a804c42f4..87df22eef491 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -39,6 +39,11 @@ #endif #endif +struct usb_port { + struct device dev; + struct dev_state *port_owner; +}; + struct usb_hub { struct device *intfdev; /* the "interface" device */ struct usb_device *hdev; @@ -83,7 +88,7 @@ struct usb_hub { u8 indicator[USB_MAXCHILDREN]; struct delayed_work leds; struct delayed_work init_work; - struct dev_state **port_owners; + struct usb_port **ports; }; static inline int hub_is_superspeed(struct usb_device *hdev) @@ -156,6 +161,8 @@ EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem); #define HUB_DEBOUNCE_STEP 25 #define HUB_DEBOUNCE_STABLE 100 +#define to_usb_port(_dev) \ + container_of(_dev, struct usb_port, dev) static int usb_reset_and_verify_device(struct usb_device *udev); @@ -1222,6 +1229,52 @@ static int hub_post_reset(struct usb_interface *intf) return 0; } +static void usb_port_device_release(struct device *dev) +{ + struct usb_port *port_dev = to_usb_port(dev); + + kfree(port_dev); +} + +static void usb_hub_remove_port_device(struct usb_hub *hub, + int port1) +{ + device_unregister(&hub->ports[port1 - 1]->dev); +} + +struct device_type usb_port_device_type = { + .name = "usb_port", + .release = usb_port_device_release, +}; + +static int usb_hub_create_port_device(struct usb_hub *hub, + int port1) +{ + struct usb_port *port_dev = NULL; + int retval; + + port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); + if (!port_dev) { + retval = -ENOMEM; + goto exit; + } + + hub->ports[port1 - 1] = port_dev; + port_dev->dev.parent = hub->intfdev; + port_dev->dev.type = &usb_port_device_type; + dev_set_name(&port_dev->dev, "port%d", port1); + + retval = device_register(&port_dev->dev); + if (retval) + goto error_register; + return 0; + +error_register: + put_device(&port_dev->dev); +exit: + return retval; +} + static int hub_configure(struct usb_hub *hub, struct usb_endpoint_descriptor *endpoint) { @@ -1231,7 +1284,7 @@ static int hub_configure(struct usb_hub *hub, u16 hubstatus, hubchange; u16 wHubCharacteristics; unsigned int pipe; - int maxp, ret; + int maxp, ret, i; char *message = "out of memory"; hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL); @@ -1273,9 +1326,9 @@ static int hub_configure(struct usb_hub *hub, hdev->children = kzalloc(hdev->maxchild * sizeof(struct usb_device *), GFP_KERNEL); - hub->port_owners = kzalloc(hdev->maxchild * sizeof(struct dev_state *), - GFP_KERNEL); - if (!hdev->children || !hub->port_owners) { + hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *), + GFP_KERNEL); + if (!hdev->children || !hub->ports) { ret = -ENOMEM; goto fail; } @@ -1484,6 +1537,11 @@ static int hub_configure(struct usb_hub *hub, if (hub->has_indicators && blinkenlights) hub->indicator [0] = INDICATOR_CYCLE; + for (i = 0; i < hdev->maxchild; i++) + if (usb_hub_create_port_device(hub, i + 1) < 0) + dev_err(hub->intfdev, + "couldn't create port%d device.\n", i + 1); + hub_activate(hub, HUB_INIT); return 0; @@ -1508,6 +1566,10 @@ static void hub_disconnect(struct usb_interface *intf) { struct usb_hub *hub = usb_get_intfdata(intf); struct usb_device *hdev = interface_to_usbdev(intf); + int i; + + for (i = 0; i < hdev->maxchild; i++) + usb_hub_remove_port_device(hub, i + 1); /* Take the hub off the event list and don't let it be added again */ spin_lock_irq(&hub_event_lock); @@ -1530,7 +1592,7 @@ static void hub_disconnect(struct usb_interface *intf) usb_free_urb(hub->urb); kfree(hdev->children); - kfree(hub->port_owners); + kfree(hub->ports); kfree(hub->descriptor); kfree(hub->status); kfree(hub->buffer); @@ -1662,7 +1724,7 @@ static int find_port_owner(struct usb_device *hdev, unsigned port1, /* This assumes that devices not managed by the hub driver * will always have maxchild equal to 0. */ - *ppowner = &(hdev_to_hub(hdev)->port_owners[port1 - 1]); + *ppowner = &(hdev_to_hub(hdev)->ports[port1 - 1]->port_owner); return 0; } @@ -1699,16 +1761,14 @@ int usb_hub_release_port(struct usb_device *hdev, unsigned port1, void usb_hub_release_all_ports(struct usb_device *hdev, struct dev_state *owner) { + struct usb_hub *hub = hdev_to_hub(hdev); int n; - struct dev_state **powner; - n = find_port_owner(hdev, 1, &powner); - if (n == 0) { - for (; n < hdev->maxchild; (++n, ++powner)) { - if (*powner == owner) - *powner = NULL; - } + for (n = 0; n < hdev->maxchild; n++) { + if (hub->ports[n]->port_owner == owner) + hub->ports[n]->port_owner = NULL; } + } /* The caller must hold udev's lock */ @@ -1719,10 +1779,9 @@ bool usb_device_is_owned(struct usb_device *udev) if (udev->state == USB_STATE_NOTATTACHED || !udev->parent) return false; hub = hdev_to_hub(udev->parent); - return !!hub->port_owners[udev->portnum - 1]; + return !!hub->ports[udev->portnum - 1]->port_owner; } - static void recursively_mark_NOTATTACHED(struct usb_device *udev) { int i; -- cgit v1.2.3-59-g8ed1b From ca9c9d0c922e4f1dac80699ebab1269d6dbaff85 Mon Sep 17 00:00:00 2001 From: Lan Tianyu Date: Wed, 5 Sep 2012 13:44:38 +0800 Subject: usb : Add sysfs files to control port power. This patch adds two sysfs files for each usb hub port to allow userspace to control the port power policy. For an upcoming Intel xHCI roothub, this will translate into ACPI calls to completely power off or power on the port. As a reminder, when these ports are completely powered off, the USB host and device will see a physical disconnect. All future USB device connections will be lost, and the device will not be able to signal a remote wakeup. The control sysfs file can be written to with two options: "on" - port power must be on. "off" - port must be off. The state sysfs file reports usb port's power state: "on" - powered on "off" - powered off "error" - can't get power state For now, let userspace dictate the port power off policy. Future patches may add an in-kernel policy. Acked-by: Alan Stern Signed-off-by: Lan Tianyu Signed-off-by: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 24 ++++++ drivers/usb/core/hub.c | 133 +++++++++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index b6fbe514a869..13f3eeee1599 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -227,3 +227,27 @@ Contact: Lan Tianyu Description: The /sys/bus/usb/devices/.../(hub interface)/portX is usb port device's sysfs directory. + +What: /sys/bus/usb/devices/.../(hub interface)/portX/control +Date: August 2012 +Contact: Lan Tianyu +Description: + The /sys/bus/usb/devices/.../(hub interface)/portX/control + attribute allows user space to control the power policy on + the usb port. + + All ports have one of the following two values for control + "on" - port power must be on. + "off" - port power must be off. + +What: /sys/bus/usb/devices/.../(hub interface)/portX/state +Date: August 2012 +Contact: Lan Tianyu +Description: + The /sys/bus/usb/devices/.../(hub interface)/portX/state + attribute allows user space to check hub port's power state. + + All ports have three following states + "on" - port power on + "off" - port power off + "error" - can't get the hub port's power state diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 4ffe8371adac..3def91eecaad 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -39,11 +39,17 @@ #endif #endif +enum port_power_policy { + USB_PORT_POWER_ON = 0, + USB_PORT_POWER_OFF, +}; + struct usb_port { struct usb_device *child; struct device dev; struct dev_state *port_owner; enum usb_port_connect_type connect_type; + enum port_power_policy port_power_policy; }; struct usb_hub { @@ -93,6 +99,10 @@ struct usb_hub { struct usb_port **ports; }; +static const char on_string[] = "on"; +static const char off_string[] = "off"; +static const struct attribute_group *port_dev_group[]; + static inline int hub_is_superspeed(struct usb_device *hdev) { return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS); @@ -845,7 +855,12 @@ static unsigned hub_power_on(struct usb_hub *hub, bool do_delay) dev_dbg(hub->intfdev, "trying to enable port power on " "non-switchable hub\n"); for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++) - set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); + if (hub->ports[port1 - 1]->port_power_policy + == USB_PORT_POWER_ON) + set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); + else + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_POWER); /* Wait at least 100 msec for power to become stable */ delay = max(pgood_delay, (unsigned) 100); @@ -1263,6 +1278,7 @@ static int usb_hub_create_port_device(struct usb_hub *hub, hub->ports[port1 - 1] = port_dev; port_dev->dev.parent = hub->intfdev; + port_dev->dev.groups = port_dev_group; port_dev->dev.type = &usb_port_device_type; dev_set_name(&port_dev->dev, "port%d", port1); @@ -2627,6 +2643,25 @@ static int port_is_power_on(struct usb_hub *hub, unsigned portstatus) return ret; } +static int usb_get_hub_port_power_state(struct usb_device *hdev, int port1) +{ + struct usb_hub *hub = hdev_to_hub(hdev); + struct usb_port_status data; + u16 portstatus; + int ret; + + ret = get_port_status(hub->hdev, port1, &data); + if (ret < 4) { + dev_err(hub->intfdev, + "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; + return ret; + } else + portstatus = le16_to_cpu(data.wPortStatus); + return port_is_power_on(hub, portstatus); +} + #ifdef CONFIG_PM /* Check if a port is suspended(USB2.0 port) or in U3 state(USB3.0 port) */ @@ -4633,6 +4668,102 @@ static int hub_thread(void *__unused) return 0; } +static ssize_t show_port_power_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev = to_usb_device(dev->parent->parent); + struct usb_interface *intf = to_usb_interface(dev->parent); + int port1, power_state; + const char *result; + + sscanf(dev_name(dev), "port%d", &port1); + usb_autopm_get_interface(intf); + power_state = usb_get_hub_port_power_state(udev, port1); + usb_autopm_put_interface(intf); + if (power_state == 1) + result = on_string; + else if (!power_state) + result = off_string; + else + result = "error"; + return sprintf(buf, "%s\n", result); +} +static DEVICE_ATTR(state, S_IRUGO, show_port_power_state, NULL); + +static ssize_t show_port_power_control(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *hub_port = to_usb_port(dev); + const char *result; + + switch (hub_port->port_power_policy) { + case USB_PORT_POWER_ON: + result = on_string; + break; + case USB_PORT_POWER_OFF: + result = off_string; + break; + default: + return -EINVAL; + } + return sprintf(buf, "%s\n", result); +} + +static ssize_t store_port_power_control(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_device *hdev = to_usb_device(dev->parent->parent); + struct usb_interface *intf = to_usb_interface(dev->parent); + struct usb_port *hub_port = to_usb_port(dev); + int port1, ret, len = count; + char *cp; + + sscanf(dev_name(dev), "port%d", &port1); + cp = memchr(buf, '\n', count); + if (cp) + len = cp - buf; + if (len == sizeof(on_string) - 1 + && strncmp(buf, on_string, len) == 0) { + hub_port->port_power_policy = USB_PORT_POWER_ON; + usb_autopm_get_interface(intf); + ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); + usb_autopm_put_interface(intf); + if (ret < 0) + return -EIO; + } else if (len == sizeof(off_string) - 1 + && strncmp(buf, off_string, len) == 0) { + struct usb_hub *hub = hdev_to_hub(hdev); + + hub_port->port_power_policy = USB_PORT_POWER_OFF; + usb_autopm_get_interface(intf); + hub_port_logical_disconnect(hub, port1); + ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER); + usb_autopm_put_interface(intf); + if (ret < 0) + return -EIO; + } else + return -EINVAL; + + return count; +} +static DEVICE_ATTR(control, S_IWUSR | S_IRUGO, show_port_power_control, + store_port_power_control); + +static struct attribute *port_dev_attrs[] = { + &dev_attr_control.attr, + &dev_attr_state.attr, + NULL, +}; + +static struct attribute_group port_dev_attr_grp = { + .attrs = port_dev_attrs, +}; + +static const struct attribute_group *port_dev_group[] = { + &port_dev_attr_grp, + NULL, +}; + static const struct usb_device_id hub_id_table[] = { { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, .bDeviceClass = USB_CLASS_HUB}, -- cgit v1.2.3-59-g8ed1b From 00a0b1d58af873d842580dcac55f3b156c3a4077 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Tue, 11 Sep 2012 14:39:40 +0530 Subject: usb: musb: omap: Add device tree support for omap musb glue Added device tree support for omap musb driver and updated the Documentation with device tree binding information. Signed-off-by: Kishon Vijay Abraham I Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/omap-usb.txt | 33 +++++++++++++ drivers/usb/musb/omap2430.c | 54 ++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/omap-usb.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/omap-usb.txt b/Documentation/devicetree/bindings/usb/omap-usb.txt new file mode 100644 index 000000000000..29a043ecda52 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/omap-usb.txt @@ -0,0 +1,33 @@ +OMAP GLUE + +OMAP MUSB GLUE + - compatible : Should be "ti,omap4-musb" or "ti,omap3-musb" + - ti,hwmods : must be "usb_otg_hs" + - multipoint : Should be "1" indicating the musb controller supports + multipoint. This is a MUSB configuration-specific setting. + - num_eps : Specifies the number of endpoints. This is also a + MUSB configuration-specific setting. Should be set to "16" + - ram_bits : Specifies the ram address size. Should be set to "12" + - interface_type : This is a board specific setting to describe the type of + interface between the controller and the phy. It should be "0" or "1" + specifying ULPI and UTMI respectively. + - mode : Should be "3" to represent OTG. "1" signifies HOST and "2" + represents PERIPHERAL. + - power : Should be "50". This signifies the controller can supply upto + 100mA when operating in host mode. + +SOC specific device node entry +usb_otg_hs: usb_otg_hs@4a0ab000 { + compatible = "ti,omap4-musb"; + ti,hwmods = "usb_otg_hs"; + multipoint = <1>; + num_eps = <16>; + ram_bits = <12>; +}; + +Board specific device node entry +&usb_otg_hs { + interface_type = <1>; + mode = <3>; + power = <50>; +}; diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index f4d95037db5d..d96873ba97c7 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -470,8 +471,11 @@ static u64 omap2430_dmamask = DMA_BIT_MASK(32); static int __devinit omap2430_probe(struct platform_device *pdev) { struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct omap_musb_board_data *data; struct platform_device *musb; struct omap2430_glue *glue; + struct device_node *np = pdev->dev.of_node; + struct musb_hdrc_config *config; struct resource *res; int ret = -ENOMEM; @@ -501,6 +505,42 @@ static int __devinit omap2430_probe(struct platform_device *pdev) if (glue->control_otghs == NULL) dev_dbg(&pdev->dev, "Failed to obtain control memory\n"); + if (np) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, + "failed to allocate musb platfrom data\n"); + ret = -ENOMEM; + goto err1; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, + "failed to allocate musb board data\n"); + ret = -ENOMEM; + goto err1; + } + + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, + "failed to allocate musb hdrc config\n"); + goto err1; + } + + of_property_read_u32(np, "mode", (u32 *)&pdata->mode); + of_property_read_u32(np, "interface_type", + (u32 *)&data->interface_type); + of_property_read_u32(np, "num_eps", (u32 *)&config->num_eps); + of_property_read_u32(np, "ram_bits", (u32 *)&config->ram_bits); + of_property_read_u32(np, "power", (u32 *)&pdata->power); + config->multipoint = of_property_read_bool(np, "multipoint"); + + pdata->board_data = data; + pdata->config = config; + } + pdata->platform_ops = &omap2430_ops; platform_set_drvdata(pdev, glue); @@ -597,12 +637,26 @@ static struct dev_pm_ops omap2430_pm_ops = { #define DEV_PM_OPS NULL #endif +#ifdef CONFIG_OF +static const struct of_device_id omap2430_id_table[] = { + { + .compatible = "ti,omap4-musb" + }, + { + .compatible = "ti,omap3-musb" + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap2430_id_table); +#endif + static struct platform_driver omap2430_driver = { .probe = omap2430_probe, .remove = __devexit_p(omap2430_remove), .driver = { .name = "musb-omap2430", .pm = DEV_PM_OPS, + .of_match_table = of_match_ptr(omap2430_id_table), }, }; -- cgit v1.2.3-59-g8ed1b From 65145677a65c31a8fd2704e244801bdb11061f9a Mon Sep 17 00:00:00 2001 From: Ajay Kumar Gupta Date: Fri, 31 Aug 2012 11:09:53 +0000 Subject: usb: musb: dsps: add dt support Added device tree support for dsps musb glue driver and updated the Documentation with device tree binding information. Signed-off-by: Ajay Kumar Gupta Signed-off-by: Santhapuri, Damodar Signed-off-by: Ravi Babu [afzal@ti.com: use '-' instead of '_' for dt properties] Signed-off-by: Afzal Mohammed Signed-off-by: Felipe Balbi --- .../devicetree/bindings/usb/am33xx-usb.txt | 14 +++++ drivers/usb/musb/musb_dsps.c | 60 ++++++++++++++++++---- 2 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 Documentation/devicetree/bindings/usb/am33xx-usb.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/am33xx-usb.txt b/Documentation/devicetree/bindings/usb/am33xx-usb.txt new file mode 100644 index 000000000000..ca8fa56e9f03 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/am33xx-usb.txt @@ -0,0 +1,14 @@ +AM33XX MUSB GLUE + - compatible : Should be "ti,musb-am33xx" + - ti,hwmods : must be "usb_otg_hs" + - multipoint : Should be "1" indicating the musb controller supports + multipoint. This is a MUSB configuration-specific setting. + - num_eps : Specifies the number of endpoints. This is also a + MUSB configuration-specific setting. Should be set to "16" + - ram_bits : Specifies the ram address size. Should be set to "12" + - port0_mode : Should be "3" to represent OTG. "1" signifies HOST and "2" + represents PERIPHERAL. + - port1_mode : Should be "1" to represent HOST. "3" signifies OTG and "2" + represents PERIPHERAL. + - power : Should be "250". This signifies the controller can supply upto + 500mA when operating in host mode. diff --git a/drivers/usb/musb/musb_dsps.c b/drivers/usb/musb/musb_dsps.c index 796fc6085fce..b8aecbba7407 100644 --- a/drivers/usb/musb/musb_dsps.c +++ b/drivers/usb/musb/musb_dsps.c @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -45,6 +46,10 @@ #include "musb_core.h" +#ifdef CONFIG_OF +static const struct of_device_id musb_dsps_of_match[]; +#endif + /** * avoid using musb_readx()/musb_writex() as glue layer should not be * dependent on musb core layer symbols. @@ -448,6 +453,8 @@ static int __devinit dsps_create_musb_pdev(struct dsps_glue *glue, u8 id) struct device *dev = glue->dev; struct platform_device *pdev = to_platform_device(dev); struct musb_hdrc_platform_data *pdata = dev->platform_data; + struct device_node *np = pdev->dev.of_node; + struct musb_hdrc_config *config; struct platform_device *musb; struct resource *res; struct resource resources[2]; @@ -499,14 +506,40 @@ static int __devinit dsps_create_musb_pdev(struct dsps_glue *glue, u8 id) glue->musb[id] = musb; - pdata->platform_ops = &dsps_ops; - ret = platform_device_add_resources(musb, resources, 2); if (ret) { dev_err(dev, "failed to add resources\n"); goto err2; } + if (np) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, + "failed to allocate musb platfrom data\n"); + ret = -ENOMEM; + goto err2; + } + + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!config) { + dev_err(&pdev->dev, + "failed to allocate musb hdrc config\n"); + goto err2; + } + + of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps); + of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits); + sprintf(res_name, "port%d-mode", id); + of_property_read_u32(np, res_name, (u32 *)&pdata->mode); + of_property_read_u32(np, "power", (u32 *)&pdata->power); + config->multipoint = of_property_read_bool(np, "multipoint"); + + pdata->config = config; + } + + pdata->platform_ops = &dsps_ops; + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); if (ret) { dev_err(dev, "failed to add platform_data\n"); @@ -538,13 +571,21 @@ static void dsps_delete_musb_pdev(struct dsps_glue *glue, u8 id) static int __devinit dsps_probe(struct platform_device *pdev) { - const struct platform_device_id *id = platform_get_device_id(pdev); - const struct dsps_musb_wrapper *wrp = - (struct dsps_musb_wrapper *)id->driver_data; + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; + const struct dsps_musb_wrapper *wrp; struct dsps_glue *glue; struct resource *iomem; int ret, i; + match = of_match_node(musb_dsps_of_match, np); + if (!match) { + dev_err(&pdev->dev, "fail to get matching of_match struct\n"); + ret = -EINVAL; + goto err0; + } + wrp = match->data; + /* allocate glue */ glue = kzalloc(sizeof(*glue), GFP_KERNEL); if (!glue) { @@ -693,13 +734,14 @@ static const struct platform_device_id musb_dsps_id_table[] __devinitconst = { }; MODULE_DEVICE_TABLE(platform, musb_dsps_id_table); +#ifdef CONFIG_OF static const struct of_device_id musb_dsps_of_match[] __devinitconst = { - { .compatible = "musb-ti81xx", }, - { .compatible = "ti,ti81xx-musb", }, - { .compatible = "ti,am335x-musb", }, + { .compatible = "ti,musb-am33xx", + .data = (void *) &ti81xx_driver_data, }, { }, }; MODULE_DEVICE_TABLE(of, musb_dsps_of_match); +#endif static struct platform_driver dsps_usbss_driver = { .probe = dsps_probe, @@ -707,7 +749,7 @@ static struct platform_driver dsps_usbss_driver = { .driver = { .name = "musb-dsps", .pm = &dsps_pm_ops, - .of_match_table = musb_dsps_of_match, + .of_match_table = of_match_ptr(musb_dsps_of_match), }, .id_table = musb_dsps_id_table, }; -- cgit v1.2.3-59-g8ed1b From d142d6be231713330f9c0d7ee52f23e1b3f19ee4 Mon Sep 17 00:00:00 2001 From: Richard Zhao Date: Wed, 12 Sep 2012 14:58:05 +0300 Subject: USB: chipidea: add imx usbmisc support i.MX usb controllers share non-core registers, which may include SoC specific controls. We turn it into a usbmisc device and usbmisc driver set operations needed by ci13xxx_imx driver. For example, Sabrelite board has bad over-current design, we can usbmisc to disable over-current detection. Acked-by: Sascha Hauer Signed-off-by: Richard Zhao Signed-off-by: Alexander Shishkin Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/ci13xxx-imx.txt | 5 + .../devicetree/bindings/usb/usbmisc-imx.txt | 14 ++ drivers/usb/chipidea/Makefile | 2 +- drivers/usb/chipidea/ci13xxx_imx.c | 64 ++++++++ drivers/usb/chipidea/ci13xxx_imx.h | 28 ++++ drivers/usb/chipidea/usbmisc_imx6q.c | 162 +++++++++++++++++++++ 6 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/usb/usbmisc-imx.txt create mode 100644 drivers/usb/chipidea/ci13xxx_imx.h create mode 100644 drivers/usb/chipidea/usbmisc_imx6q.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/usb/ci13xxx-imx.txt b/Documentation/devicetree/bindings/usb/ci13xxx-imx.txt index 2c290418bb2d..5778b9c83bd8 100644 --- a/Documentation/devicetree/bindings/usb/ci13xxx-imx.txt +++ b/Documentation/devicetree/bindings/usb/ci13xxx-imx.txt @@ -7,7 +7,10 @@ Required properties: Optional properties: - fsl,usbphy: phandler of usb phy that connects to the only one port +- fsl,usbmisc: phandler of non-core register device, with one argument + that indicate usb controller index - vbus-supply: regulator for vbus +- disable-over-current: disable over current detect Examples: usb@02184000 { /* USB OTG */ @@ -15,4 +18,6 @@ usb@02184000 { /* USB OTG */ reg = <0x02184000 0x200>; interrupts = <0 43 0x04>; fsl,usbphy = <&usbphy1>; + fsl,usbmisc = <&usbmisc 0>; + disable-over-current; }; diff --git a/Documentation/devicetree/bindings/usb/usbmisc-imx.txt b/Documentation/devicetree/bindings/usb/usbmisc-imx.txt new file mode 100644 index 000000000000..97ce94e1a6cc --- /dev/null +++ b/Documentation/devicetree/bindings/usb/usbmisc-imx.txt @@ -0,0 +1,14 @@ +* Freescale i.MX non-core registers + +Required properties: +- #index-cells: Cells used to descibe usb controller index. Should be <1> +- compatible: Should be one of below: + "fsl,imx6q-usbmisc" for imx6q +- reg: Should contain registers location and length + +Examples: +usbmisc@02184800 { + #index-cells = <1>; + compatible = "fsl,imx6q-usbmisc"; + reg = <0x02184800 0x200>; +}; diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index 5c66d9c330ca..57e510f6c245 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -15,5 +15,5 @@ ifneq ($(CONFIG_PCI),) endif ifneq ($(CONFIG_OF_DEVICE),) - obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_imx.o + obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_imx.o usbmisc_imx6q.o endif diff --git a/drivers/usb/chipidea/ci13xxx_imx.c b/drivers/usb/chipidea/ci13xxx_imx.c index ef60d06835d0..96ac67bbd2db 100644 --- a/drivers/usb/chipidea/ci13xxx_imx.c +++ b/drivers/usb/chipidea/ci13xxx_imx.c @@ -22,6 +22,7 @@ #include #include "ci.h" +#include "ci13xxx_imx.h" #define pdev_to_phy(pdev) \ ((struct usb_phy *)platform_get_drvdata(pdev)) @@ -34,6 +35,55 @@ struct ci13xxx_imx_data { struct regulator *reg_vbus; }; +static const struct usbmisc_ops *usbmisc_ops; + +/* Common functions shared by usbmisc drivers */ + +int usbmisc_set_ops(const struct usbmisc_ops *ops) +{ + if (usbmisc_ops) + return -EBUSY; + + usbmisc_ops = ops; + + return 0; +} +EXPORT_SYMBOL_GPL(usbmisc_set_ops); + +void usbmisc_unset_ops(const struct usbmisc_ops *ops) +{ + usbmisc_ops = NULL; +} +EXPORT_SYMBOL_GPL(usbmisc_unset_ops); + +int usbmisc_get_init_data(struct device *dev, struct usbmisc_usb_device *usbdev) +{ + struct device_node *np = dev->of_node; + struct of_phandle_args args; + int ret; + + usbdev->dev = dev; + + ret = of_parse_phandle_with_args(np, "fsl,usbmisc", "#index-cells", + 0, &args); + if (ret) { + dev_err(dev, "Failed to parse property fsl,usbmisc, errno %d\n", + ret); + memset(usbdev, 0, sizeof(*usbdev)); + return ret; + } + usbdev->index = args.args[0]; + of_node_put(args.np); + + if (of_find_property(np, "disable-over-current", NULL)) + usbdev->disable_oc = 1; + + return 0; +} +EXPORT_SYMBOL_GPL(usbmisc_get_init_data); + +/* End of common functions shared by usbmisc drivers*/ + static struct ci13xxx_platform_data ci13xxx_imx_platdata __devinitdata = { .name = "ci13xxx_imx", .flags = CI13XXX_REQUIRE_TRANSCEIVER | @@ -51,6 +101,10 @@ static int __devinit ci13xxx_imx_probe(struct platform_device *pdev) struct regulator *reg_vbus; int ret; + if (of_find_property(pdev->dev.of_node, "fsl,usbmisc", NULL) + && !usbmisc_ops) + return -EPROBE_DEFER; + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) { dev_err(&pdev->dev, "Failed to allocate CI13xxx-IMX data!\n"); @@ -120,6 +174,16 @@ static int __devinit ci13xxx_imx_probe(struct platform_device *pdev) *pdev->dev.dma_mask = DMA_BIT_MASK(32); dma_set_coherent_mask(&pdev->dev, *pdev->dev.dma_mask); } + + if (usbmisc_ops && usbmisc_ops->init) { + ret = usbmisc_ops->init(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, + "usbmisc init failed, ret=%d\n", ret); + goto err; + } + } + plat_ci = ci13xxx_add_device(&pdev->dev, pdev->resource, pdev->num_resources, &ci13xxx_imx_platdata); diff --git a/drivers/usb/chipidea/ci13xxx_imx.h b/drivers/usb/chipidea/ci13xxx_imx.h new file mode 100644 index 000000000000..2e88accb3d67 --- /dev/null +++ b/drivers/usb/chipidea/ci13xxx_imx.h @@ -0,0 +1,28 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/* Used to set SoC specific callbacks */ +struct usbmisc_ops { + /* It's called once when probe a usb device */ + int (*init)(struct device *dev); +}; + +struct usbmisc_usb_device { + struct device *dev; /* usb controller device */ + int index; + + int disable_oc:1; /* over current detect disabled */ +}; + +int usbmisc_set_ops(const struct usbmisc_ops *ops); +void usbmisc_unset_ops(const struct usbmisc_ops *ops); +int +usbmisc_get_init_data(struct device *dev, struct usbmisc_usb_device *usbdev); diff --git a/drivers/usb/chipidea/usbmisc_imx6q.c b/drivers/usb/chipidea/usbmisc_imx6q.c new file mode 100644 index 000000000000..416e3fc58fd0 --- /dev/null +++ b/drivers/usb/chipidea/usbmisc_imx6q.c @@ -0,0 +1,162 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include + +#include "ci13xxx_imx.h" + +#define USB_DEV_MAX 4 + +#define BM_OVER_CUR_DIS BIT(7) + +struct imx6q_usbmisc { + void __iomem *base; + spinlock_t lock; + struct clk *clk; + struct usbmisc_usb_device usbdev[USB_DEV_MAX]; +}; + +static struct imx6q_usbmisc *usbmisc; + +static struct usbmisc_usb_device *get_usbdev(struct device *dev) +{ + int i, ret; + + for (i = 0; i < USB_DEV_MAX; i++) { + if (usbmisc->usbdev[i].dev == dev) + return &usbmisc->usbdev[i]; + else if (!usbmisc->usbdev[i].dev) + break; + } + + if (i >= USB_DEV_MAX) + return ERR_PTR(-EBUSY); + + ret = usbmisc_get_init_data(dev, &usbmisc->usbdev[i]); + if (ret) + return ERR_PTR(ret); + + return &usbmisc->usbdev[i]; +} + +static int usbmisc_imx6q_init(struct device *dev) +{ + + struct usbmisc_usb_device *usbdev; + unsigned long flags; + u32 reg; + + usbdev = get_usbdev(dev); + if (IS_ERR(usbdev)) + return PTR_ERR(usbdev); + + if (usbdev->disable_oc) { + spin_lock_irqsave(&usbmisc->lock, flags); + reg = readl(usbmisc->base + usbdev->index * 4); + writel(reg | BM_OVER_CUR_DIS, + usbmisc->base + usbdev->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + } + + return 0; +} + +static const struct usbmisc_ops imx6q_usbmisc_ops = { + .init = usbmisc_imx6q_init, +}; + +static const struct of_device_id usbmisc_imx6q_dt_ids[] = { + { .compatible = "fsl,imx6q-usbmisc"}, + { /* sentinel */ } +}; + +static int __devinit usbmisc_imx6q_probe(struct platform_device *pdev) +{ + struct resource *res; + struct imx6q_usbmisc *data; + int ret; + + if (usbmisc) + return -EBUSY; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_request_and_ioremap(&pdev->dev, res); + if (!data->base) + return -EADDRNOTAVAIL; + + data->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(data->clk)) { + dev_err(&pdev->dev, + "failed to get clock, err=%ld\n", PTR_ERR(data->clk)); + return PTR_ERR(data->clk); + } + + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(&pdev->dev, + "clk_prepare_enable failed, err=%d\n", ret); + return ret; + } + + ret = usbmisc_set_ops(&imx6q_usbmisc_ops); + if (ret) { + clk_disable_unprepare(data->clk); + return ret; + } + + usbmisc = data; + + return 0; +} + +static int __devexit usbmisc_imx6q_remove(struct platform_device *pdev) +{ + usbmisc_unset_ops(&imx6q_usbmisc_ops); + clk_disable_unprepare(usbmisc->clk); + return 0; +} + +static struct platform_driver usbmisc_imx6q_driver = { + .probe = usbmisc_imx6q_probe, + .remove = __devexit_p(usbmisc_imx6q_remove), + .driver = { + .name = "usbmisc_imx6q", + .owner = THIS_MODULE, + .of_match_table = usbmisc_imx6q_dt_ids, + }, +}; + +int __init usbmisc_imx6q_drv_init(void) +{ + return platform_driver_register(&usbmisc_imx6q_driver); +} +subsys_initcall(usbmisc_imx6q_drv_init); + +void __exit usbmisc_imx6q_drv_exit(void) +{ + platform_driver_unregister(&usbmisc_imx6q_driver); +} +module_exit(usbmisc_imx6q_drv_exit); + +MODULE_ALIAS("platform:usbmisc-imx6q"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("driver for imx6q usb non-core registers"); +MODULE_AUTHOR("Richard Zhao "); -- cgit v1.2.3-59-g8ed1b From a0693bd0aa95935bf1a720d7647619c2a4d789ed Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Mon, 24 Sep 2012 13:04:02 -0700 Subject: Revert "usb : Add sysfs files to control port power." This reverts commit ca9c9d0c922e4f1dac80699ebab1269d6dbaff85. Rafael wants more time to work on the user api to handle port power issues, so let's just revert the sysfs changes for now. Reported-by: Rafael J. Wysocki Cc: Lan Tianyu Cc: Alan Stern Cc: Sarah Sharp Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 24 ------ drivers/usb/core/hub.c | 133 +------------------------------- 2 files changed, 1 insertion(+), 156 deletions(-) (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 13f3eeee1599..b6fbe514a869 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -227,27 +227,3 @@ Contact: Lan Tianyu Description: The /sys/bus/usb/devices/.../(hub interface)/portX is usb port device's sysfs directory. - -What: /sys/bus/usb/devices/.../(hub interface)/portX/control -Date: August 2012 -Contact: Lan Tianyu -Description: - The /sys/bus/usb/devices/.../(hub interface)/portX/control - attribute allows user space to control the power policy on - the usb port. - - All ports have one of the following two values for control - "on" - port power must be on. - "off" - port power must be off. - -What: /sys/bus/usb/devices/.../(hub interface)/portX/state -Date: August 2012 -Contact: Lan Tianyu -Description: - The /sys/bus/usb/devices/.../(hub interface)/portX/state - attribute allows user space to check hub port's power state. - - All ports have three following states - "on" - port power on - "off" - port power off - "error" - can't get the hub port's power state diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 6dc41c6399de..673ee4696262 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -39,17 +39,11 @@ #endif #endif -enum port_power_policy { - USB_PORT_POWER_ON = 0, - USB_PORT_POWER_OFF, -}; - struct usb_port { struct usb_device *child; struct device dev; struct dev_state *port_owner; enum usb_port_connect_type connect_type; - enum port_power_policy port_power_policy; }; struct usb_hub { @@ -99,10 +93,6 @@ struct usb_hub { struct usb_port **ports; }; -static const char on_string[] = "on"; -static const char off_string[] = "off"; -static const struct attribute_group *port_dev_group[]; - static inline int hub_is_superspeed(struct usb_device *hdev) { return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS); @@ -855,12 +845,7 @@ static unsigned hub_power_on(struct usb_hub *hub, bool do_delay) dev_dbg(hub->intfdev, "trying to enable port power on " "non-switchable hub\n"); for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++) - if (hub->ports[port1 - 1]->port_power_policy - == USB_PORT_POWER_ON) - set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); - else - clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_POWER); + set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER); /* Wait at least 100 msec for power to become stable */ delay = max(pgood_delay, (unsigned) 100); @@ -1278,7 +1263,6 @@ static int usb_hub_create_port_device(struct usb_hub *hub, hub->ports[port1 - 1] = port_dev; port_dev->dev.parent = hub->intfdev; - port_dev->dev.groups = port_dev_group; port_dev->dev.type = &usb_port_device_type; dev_set_name(&port_dev->dev, "port%d", port1); @@ -2644,25 +2628,6 @@ static int port_is_power_on(struct usb_hub *hub, unsigned portstatus) return ret; } -static int usb_get_hub_port_power_state(struct usb_device *hdev, int port1) -{ - struct usb_hub *hub = hdev_to_hub(hdev); - struct usb_port_status data; - u16 portstatus; - int ret; - - ret = get_port_status(hub->hdev, port1, &data); - if (ret < 4) { - dev_err(hub->intfdev, - "%s failed (err = %d)\n", __func__, ret); - if (ret >= 0) - ret = -EIO; - return ret; - } else - portstatus = le16_to_cpu(data.wPortStatus); - return port_is_power_on(hub, portstatus); -} - #ifdef CONFIG_PM /* Check if a port is suspended(USB2.0 port) or in U3 state(USB3.0 port) */ @@ -4669,102 +4634,6 @@ static int hub_thread(void *__unused) return 0; } -static ssize_t show_port_power_state(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct usb_device *udev = to_usb_device(dev->parent->parent); - struct usb_interface *intf = to_usb_interface(dev->parent); - int port1, power_state; - const char *result; - - sscanf(dev_name(dev), "port%d", &port1); - usb_autopm_get_interface(intf); - power_state = usb_get_hub_port_power_state(udev, port1); - usb_autopm_put_interface(intf); - if (power_state == 1) - result = on_string; - else if (!power_state) - result = off_string; - else - result = "error"; - return sprintf(buf, "%s\n", result); -} -static DEVICE_ATTR(state, S_IRUGO, show_port_power_state, NULL); - -static ssize_t show_port_power_control(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct usb_port *hub_port = to_usb_port(dev); - const char *result; - - switch (hub_port->port_power_policy) { - case USB_PORT_POWER_ON: - result = on_string; - break; - case USB_PORT_POWER_OFF: - result = off_string; - break; - default: - return -EINVAL; - } - return sprintf(buf, "%s\n", result); -} - -static ssize_t store_port_power_control(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) -{ - struct usb_device *hdev = to_usb_device(dev->parent->parent); - struct usb_interface *intf = to_usb_interface(dev->parent); - struct usb_port *hub_port = to_usb_port(dev); - int port1, ret, len = count; - char *cp; - - sscanf(dev_name(dev), "port%d", &port1); - cp = memchr(buf, '\n', count); - if (cp) - len = cp - buf; - if (len == sizeof(on_string) - 1 - && strncmp(buf, on_string, len) == 0) { - hub_port->port_power_policy = USB_PORT_POWER_ON; - usb_autopm_get_interface(intf); - ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER); - usb_autopm_put_interface(intf); - if (ret < 0) - return -EIO; - } else if (len == sizeof(off_string) - 1 - && strncmp(buf, off_string, len) == 0) { - struct usb_hub *hub = hdev_to_hub(hdev); - - hub_port->port_power_policy = USB_PORT_POWER_OFF; - usb_autopm_get_interface(intf); - hub_port_logical_disconnect(hub, port1); - ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER); - usb_autopm_put_interface(intf); - if (ret < 0) - return -EIO; - } else - return -EINVAL; - - return count; -} -static DEVICE_ATTR(control, S_IWUSR | S_IRUGO, show_port_power_control, - store_port_power_control); - -static struct attribute *port_dev_attrs[] = { - &dev_attr_control.attr, - &dev_attr_state.attr, - NULL, -}; - -static struct attribute_group port_dev_attr_grp = { - .attrs = port_dev_attrs, -}; - -static const struct attribute_group *port_dev_group[] = { - &port_dev_attr_grp, - NULL, -}; - static const struct usb_device_id hub_id_table[] = { { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, .bDeviceClass = USB_CLASS_HUB}, -- cgit v1.2.3-59-g8ed1b