From dc93b41a15614f51bc1ea47d12b245d60f79731d Mon Sep 17 00:00:00 2001 From: Mickael Maison Date: Tue, 23 Dec 2014 17:34:43 +0100 Subject: usb: dwc3: Fixed a typo in comments Fixed a typo in comments Signed-off-by: Mickael Maison Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f03b136..bd9f48d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2077,7 +2077,7 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) * We have discussed this with the IP Provider and it was * suggested to giveback all requests here, but give HW some * extra time to synchronize with the interconnect. We're using - * an arbitraty 100us delay for that. + * an arbitrary 100us delay for that. * * Note also that a similar handling was tested by Synopsys * (thanks a lot Paul) and nothing bad has come out of it. -- cgit v0.10.2 From d16cd0b8f70062f7d0f96adc42f86421b71eb21c Mon Sep 17 00:00:00 2001 From: Rickard Strandqvist Date: Sat, 20 Dec 2014 23:30:11 +0100 Subject: usb: gadget: udc: s3c2410_udc.c: Remove some unused functions Removes some functions that are not used anywhere: s3c2410_udc_clear_ep_state() s3c2410_udc_set_ep0_sse_out() This was partially found by using a static code analysis program called cppcheck. Signed-off-by: Rickard Strandqvist Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/s3c2410_udc.c b/drivers/usb/gadget/udc/s3c2410_udc.c index 824cf12..256a67b 100644 --- a/drivers/usb/gadget/udc/s3c2410_udc.c +++ b/drivers/usb/gadget/udc/s3c2410_udc.c @@ -238,14 +238,6 @@ static inline void s3c2410_udc_set_ep0_de_out(void __iomem *base) S3C2410_UDC_EP0_CSR_REG); } -static inline void s3c2410_udc_set_ep0_sse_out(void __iomem *base) -{ - udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); - udc_writeb(base, (S3C2410_UDC_EP0_CSR_SOPKTRDY - | S3C2410_UDC_EP0_CSR_SSE), - S3C2410_UDC_EP0_CSR_REG); -} - static inline void s3c2410_udc_set_ep0_de_in(void __iomem *base) { udc_writeb(base, S3C2410_UDC_INDEX_EP0, S3C2410_UDC_INDEX_REG); @@ -291,18 +283,6 @@ static void s3c2410_udc_nuke(struct s3c2410_udc *udc, } } -static inline void s3c2410_udc_clear_ep_state(struct s3c2410_udc *dev) -{ - unsigned i; - - /* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint - * fifos, and pending transactions mustn't be continued in any case. - */ - - for (i = 1; i < S3C2410_ENDPOINTS; i++) - s3c2410_udc_nuke(dev, &dev->ep[i], -ECONNABORTED); -} - static inline int s3c2410_udc_fifo_count_out(void) { int tmp; -- cgit v0.10.2 From 997f4f81dff243858b6cdd8c3ddb39e05a050c22 Mon Sep 17 00:00:00 2001 From: Mickael Maison Date: Tue, 23 Dec 2014 17:39:45 +0100 Subject: usb: dwc2: Fixed a few typos in comments Fixed 3 typos in comments Signed-off-by: Mickael Maison Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 7605850b..d5197d4 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -462,7 +462,7 @@ int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq) dwc2_enable_common_interrupts(hsotg); /* - * Do device or host intialization based on mode during PCD and + * Do device or host initialization based on mode during PCD and * HCD initialization */ if (dwc2_is_host_mode(hsotg)) { diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 7a70a13..0d2ee29 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -381,7 +381,7 @@ struct dwc2_core_params { * @power_optimized Are power optimizations enabled? * @num_dev_ep Number of device endpoints available * @num_dev_perio_in_ep Number of device periodic IN endpoints - * avaialable + * available * @dev_token_q_depth Device Mode IN Token Sequence Learning Queue * Depth * 0 to 30 diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 200168e..ede69dc 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -65,7 +65,7 @@ static inline void __bic32(void __iomem *ptr, u32 val) writel(readl(ptr) & ~val, ptr); } -/* forward decleration of functions */ +/* forward declaration of functions */ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg); /** -- cgit v0.10.2 From 110381e11b5fbf84bc0994d4c3f4629bc362ab4c Mon Sep 17 00:00:00 2001 From: Amit Virdi Date: Fri, 19 Dec 2014 12:40:17 +0530 Subject: usb: dwc3: gadget: Remove redundant check dwc3_gadget_init_hw_endpoints calls dwc3_alloc_trb_pool only if epnum is not equal to 0 or 1. Hence, rechecking it in the called function is redundant. Signed-off-by: Amit Virdi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index bd9f48d..4e25939 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -352,9 +352,6 @@ static int dwc3_alloc_trb_pool(struct dwc3_ep *dep) if (dep->trb_pool) return 0; - if (dep->number == 0 || dep->number == 1) - return 0; - dep->trb_pool = dma_alloc_coherent(dwc->dev, sizeof(struct dwc3_trb) * DWC3_TRB_NUM, &dep->trb_pool_dma, GFP_KERNEL); -- cgit v0.10.2 From 3cd0e29d5e28331dc78416bf7acd4e7cb63664aa Mon Sep 17 00:00:00 2001 From: Amit Virdi Date: Fri, 19 Dec 2014 12:40:18 +0530 Subject: usb: dwc3: Remove current_trb as it is unused This field was introduced but never used. So, remove it. Signed-off-by: Amit Virdi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 4bb9aa6..0842aa8 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -431,7 +431,6 @@ struct dwc3_event_buffer { * @dwc: pointer to DWC controller * @saved_state: ep state saved during hibernation * @flags: endpoint flags (wedged, stalled, ...) - * @current_trb: index of current used trb * @number: endpoint number (1 - 15) * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK * @resource_index: Resource transfer index @@ -464,8 +463,6 @@ struct dwc3_ep { /* This last one is specific to EP0 */ #define DWC3_EP0_DIR_IN (1 << 31) - unsigned current_trb; - u8 number; u8 type; u8 resource_index; -- cgit v0.10.2 From b797ef4e167b68dc5d6bfcb1b77f7f077dddb825 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:24 +0100 Subject: Documentation: usb: gadget_serial: update generic serial setup instruction Using module parameters to specify accepted Vendor ID, Product ID is considered legacy now. Update the documentation to reflect it. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget_serial.txt b/Documentation/usb/gadget_serial.txt index 61e67f6..6b4a88a 100644 --- a/Documentation/usb/gadget_serial.txt +++ b/Documentation/usb/gadget_serial.txt @@ -236,8 +236,12 @@ I: If#= 0 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=serial E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms -You must explicitly load the usbserial driver with parameters to -configure it to recognize the gadget serial device, like this: +You must load the usbserial driver and explicitly set its parameters +to configure it to recognize the gadget serial device, like this: + + echo 0x0525 0xA4A6 >/sys/bus/usb-serial/drivers/generic/new_id + +The legacy way is to use module parameters: modprobe usbserial vendor=0x0525 product=0xA4A6 -- cgit v0.10.2 From e38eb2c8cb435729579576b2b5bc5247ebcd6f5b Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:25 +0100 Subject: Documentation: usb: ACM function testing The newly added file will be used to provide descriptions of how to test the functions of USB gadgets. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt new file mode 100644 index 0000000..2a448f8 --- /dev/null +++ b/Documentation/usb/gadget-testing.txt @@ -0,0 +1,34 @@ +This file summarizes information on basic testing of USB functions +provided by gadgets. + +1. ACM function + + +1. ACM function +=============== + +The function is provided by usb_f_acm.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "acm". +The ACM function provides just one attribute in its function directory: + + port_num + +The attribute is read-only. + +There can be at most 4 ACM/generic serial/OBEX ports in the system. + + +Testing the ACM function +------------------------ + +On the host: cat > /dev/ttyACM +On the device : cat /dev/ttyGS + +then the other way round + +On the device: cat > /dev/ttyGS +On the host: cat /dev/ttyACM -- cgit v0.10.2 From d5862ca6da545ec63ec2d2a8c687bf214c87e00e Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:26 +0100 Subject: Documentation: usb: ECM function testing Summary of how to test ECM function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 2a448f8..7df8785 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -2,6 +2,7 @@ This file summarizes information on basic testing of USB functions provided by gadgets. 1. ACM function +2. ECM function 1. ACM function @@ -32,3 +33,36 @@ then the other way round On the device: cat > /dev/ttyGS On the host: cat /dev/ttyACM + +2. ECM function +=============== + +The function is provided by usb_f_ecm.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "ecm". +The ECM function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/ecm. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +Testing the ECM function +------------------------ + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From 7bfbc6e3fbd9ae35b28db1839689b7eb4025c93d Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:27 +0100 Subject: Documentation: usb: ECM subset function testing Summary of how to test ECM subset function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 7df8785..b40db75 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -3,6 +3,7 @@ provided by gadgets. 1. ACM function 2. ECM function +3. ECM subset function 1. ACM function @@ -66,3 +67,36 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +3. ECM subset function +====================== + +The function is provided by usb_f_ecm_subset.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "geth". +The ECM subset function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/ecm. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +Testing the ECM subset function +------------------------------- + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From 4ca560a6d31d1deae004a9977adf64766e87a021 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:28 +0100 Subject: Documentation: usb: EEM function testing Summary of how to test EEM function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index b40db75..50b0a6c 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -4,6 +4,7 @@ provided by gadgets. 1. ACM function 2. ECM function 3. ECM subset function +4. EEM function 1. ACM function @@ -100,3 +101,36 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +4. EEM function +=============== + +The function is provided by usb_f_eem.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "eem". +The EEM function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/eem. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +Testing the EEM function +------------------------ + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From 2c0f62f9e4f2c450342369dfd5858db51bc90fe3 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:29 +0100 Subject: Documentation: usb: FFS function testing Summary of how to test FFS (FunctionFS) function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 50b0a6c..141a1d0 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -5,6 +5,7 @@ provided by gadgets. 2. ECM function 3. ECM subset function 4. EEM function +5. FFS function 1. ACM function @@ -134,3 +135,26 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +5. FFS function +=============== + +The function is provided by usb_f_fs.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "ffs". +The function directory is intentionally empty and not modifiable. + +After creating the directory there is a new instance (a "device") of FunctionFS +available in the system. Once a "device" is available, the user should follow +the standard procedure for using FunctionFS (mount it, run the userspace +process which implements the function proper). The gadget should be enabled +by writing a suitable string to usb_gadget//UDC. + +Testing the FFS function +------------------------ + +On the device: start the function's userspace daemon, enable the gadget +On the host: use the USB function provided by the device -- cgit v0.10.2 From f7e3c3cd2401b3531a3ac4e279a08d03adb722e3 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:30 +0100 Subject: Documentation: usb: HID function testing Summary of how to test HID function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 141a1d0..f117e5c 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -6,6 +6,7 @@ provided by gadgets. 3. ECM subset function 4. EEM function 5. FFS function +6. HID function 1. ACM function @@ -158,3 +159,49 @@ Testing the FFS function On the device: start the function's userspace daemon, enable the gadget On the host: use the USB function provided by the device + +6. HID function +=============== + +The function is provided by usb_f_hid.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "hid". +The HID function provides these attributes in its function directory: + + protocol - HID protocol to use + report_desc - data to be used in HID reports, except data + passed with /dev/hidg + report_length - HID report length + subclass - HID subclass to use + +For a keyboard the protocol and the subclass are 1, the report_length is 8, +while the report_desc is: + +$ hd my_report_desc +00000000 05 01 09 06 a1 01 05 07 19 e0 29 e7 15 00 25 01 |..........)...%.| +00000010 75 01 95 08 81 02 95 01 75 08 81 03 95 05 75 01 |u.......u.....u.| +00000020 05 08 19 01 29 05 91 02 95 01 75 03 91 03 95 06 |....).....u.....| +00000030 75 08 15 00 25 65 05 07 19 00 29 65 81 00 c0 |u...%e....)e...| +0000003f + +Such a sequence of bytes can be stored to the attribute with echo: + +$ echo -ne \\x05\\x01\\x09\\x06\\xa1..... + +Testing the HID function +------------------------ + +Device: +- create the gadget +- connect the gadget to a host, preferably not the one used +to control the gadget +- run a program which writes to /dev/hidg, e.g. +a userspace program found in Documentation/usb/gadget_hid.txt: + +$ ./hid_gadget_test /dev/hidg0 keyboard + +Host: +- observe the keystrokes from the gadget -- cgit v0.10.2 From ec91aff7630df1e1864d95f5efd25c212d484837 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:31 +0100 Subject: Documentation: usb: LOOPBACK function testing Summary of how to test LOOPBACK function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index f117e5c..b13491d 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -7,6 +7,7 @@ provided by gadgets. 4. EEM function 5. FFS function 6. HID function +7. LOOPBACK function 1. ACM function @@ -205,3 +206,25 @@ $ ./hid_gadget_test /dev/hidg0 keyboard Host: - observe the keystrokes from the gadget + +7. LOOPBACK function +==================== + +The function is provided by usb_f_ss_lb.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "Loopback". +The LOOPBACK function provides these attributes in its function directory: + + qlen - depth of loopback queue + bulk_buflen - buffer length + +Testing the LOOPBACK function +----------------------------- + +device: run the gadget +host: test-usb + +http://www.linux-usb.org/usbtest/testusb.c -- cgit v0.10.2 From cdbe287d271637cb90f239d6a826e2aed555c1d7 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:32 +0100 Subject: Documentation: usb: MASS STORAGE function testing Summary of how to test MASS STORAGE function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index b13491d..01b9ffe 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -8,6 +8,7 @@ provided by gadgets. 5. FFS function 6. HID function 7. LOOPBACK function +8. MASS STORAGE function 1. ACM function @@ -228,3 +229,56 @@ device: run the gadget host: test-usb http://www.linux-usb.org/usbtest/testusb.c + +8. MASS STORAGE function +======================== + +The function is provided by usb_f_mass_storage.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "mass_storage". +The MASS STORAGE function provides these attributes in its directory: +files: + + stall - Set to permit function to halt bulk endpoints. + Disabled on some USB devices known not to work + correctly. You should set it to true. + num_buffers - Number of pipeline buffers. Valid numbers + are 2..4. Available only if + CONFIG_USB_GADGET_DEBUG_FILES is set. + +and a default lun.0 directory corresponding to SCSI LUN #0. + +A new lun can be added with mkdir: + +$ mkdir functions/mass_storage.0/partition.5 + +Lun numbering does not have to be continuous, except for lun #0 which is +created by default. A maximum of 8 luns can be specified and they all must be +named following the . scheme. The numbers can be 0..8. +Probably a good convention is to name the luns "lun.", +although it is not mandatory. + +In each lun directory there are the following attribute files: + + file - The path to the backing file for the LUN. + Required if LUN is not marked as removable. + ro - Flag specifying access to the LUN shall be + read-only. This is implied if CD-ROM emulation + is enabled as well as when it was impossible + to open "filename" in R/W mode. + removable - Flag specifying that LUN shall be indicated as + being removable. + cdrom - Flag specifying that LUN shall be reported as + being a CD-ROM. + nofua - Flag specifying that FUA flag + in SCSI WRITE(10,12) + +Testing the MASS STORAGE function +--------------------------------- + +device: connect the gadget, enable it +host: dmesg, see the USB drives appear (if system configured to automatically +mount) -- cgit v0.10.2 From 0d6be59a1d8a93086cd81e1c59f8d60ba35b40a2 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:33 +0100 Subject: Documentation: usb: MIDI function testing Summary of how to test MIDI function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 01b9ffe..c2f148d 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -9,6 +9,7 @@ provided by gadgets. 6. HID function 7. LOOPBACK function 8. MASS STORAGE function +9. MIDI function 1. ACM function @@ -282,3 +283,86 @@ Testing the MASS STORAGE function device: connect the gadget, enable it host: dmesg, see the USB drives appear (if system configured to automatically mount) + +9. MIDI function +================ + +The function is provided by usb_f_midi.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "midi". +The MIDI function provides these attributes in its function directory: + + buflen - MIDI buffer length + id - ID string for the USB MIDI adapter + in_ports - number of MIDI input ports + index - index value for the USB MIDI adapter + out_ports - number of MIDI output ports + qlen - USB read request queue length + +Testing the MIDI function +------------------------- + +There are two cases: playing a mid from the gadget to +the host and playing a mid from the host to the gadget. + +1) Playing a mid from the gadget to the host +host) + +$ arecordmidi -l + Port Client name Port name + 14:0 Midi Through Midi Through Port-0 + 24:0 MIDI Gadget MIDI Gadget MIDI 1 +$ arecordmidi -p 24:0 from_gadget.mid + +gadget) + +$ aplaymidi -l + Port Client name Port name + 20:0 f_midi f_midi + +$ aplaymidi -p 20:0 to_host.mid + +2) Playing a mid from the host to the gadget +gadget) + +$ arecordmidi -l + Port Client name Port name + 20:0 f_midi f_midi + +$ arecordmidi -p 20:0 from_host.mid + +host) + +$ aplaymidi -l + Port Client name Port name + 14:0 Midi Through Midi Through Port-0 + 24:0 MIDI Gadget MIDI Gadget MIDI 1 + +$ aplaymidi -p24:0 to_gadget.mid + +The from_gadget.mid should sound identical to the to_host.mid. +The from_host.id should sound identical to the to_gadget.mid. + +MIDI files can be played to speakers/headphones with e.g. timidity installed + +$ aplaymidi -l + Port Client name Port name + 14:0 Midi Through Midi Through Port-0 + 24:0 MIDI Gadget MIDI Gadget MIDI 1 +128:0 TiMidity TiMidity port 0 +128:1 TiMidity TiMidity port 1 +128:2 TiMidity TiMidity port 2 +128:3 TiMidity TiMidity port 3 + +$ aplaymidi -p 128:0 file.mid + +MIDI ports can be logically connected using the aconnect utility, e.g.: + +$ aconnect 24:0 128:0 # try it on the host + +After the gadget's MIDI port is connected to timidity's MIDI port, +whatever is played at the gadget side with aplaymidi -l is audible +in host's speakers/headphones. -- cgit v0.10.2 From 4d0fa79e0ef78f516888068e240d38650eb404d2 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:34 +0100 Subject: Documentation: usb: NCM function testing Summary of how to test NCM function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index c2f148d..493f79b 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -10,6 +10,7 @@ provided by gadgets. 7. LOOPBACK function 8. MASS STORAGE function 9. MIDI function +10. NCM function 1. ACM function @@ -366,3 +367,36 @@ $ aconnect 24:0 128:0 # try it on the host After the gadget's MIDI port is connected to timidity's MIDI port, whatever is played at the gadget side with aplaymidi -l is audible in host's speakers/headphones. + +10. NCM function +================ + +The function is provided by usb_f_ncm.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "ncm". +The NCM function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/ncm. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +Testing the NCM function +------------------------ + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From d81b85dcf186f6fa4b59e5b8181e85366ebfdd61 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:35 +0100 Subject: Documentation: usb: OBEX function testing Summary of how to test OBEX function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 493f79b..c8ae540 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -11,6 +11,7 @@ provided by gadgets. 8. MASS STORAGE function 9. MIDI function 10. NCM function +11. OBEX function 1. ACM function @@ -400,3 +401,31 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +11. OBEX function +================= + +The function is provided by usb_f_obex.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "obex". +The OBEX function provides just one attribute in its function directory: + + port_num + +The attribute is read-only. + +There can be at most 4 ACM/generic serial/OBEX ports in the system. + +Testing the OBEX function +------------------------- + +On device: seriald -f /dev/ttyGS -s 1024 +On host: serialc -v -p -i -a1 -s1024 \ + -t -r + +where seriald and serialc are Felipe's utilities found here: + +https://git.gitorious.org/usb/usb-tools.git master -- cgit v0.10.2 From da2907d2b08c2495c2e39f799b9ccbf76fd1aa58 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:36 +0100 Subject: Documentation: usb: PHONET function testing Summary of how to test PHONET function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index c8ae540..02d4366 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -12,6 +12,7 @@ provided by gadgets. 9. MIDI function 10. NCM function 11. OBEX function +12. PHONET function 1. ACM function @@ -429,3 +430,66 @@ On host: serialc -v -p -i -a1 -s1024 \ where seriald and serialc are Felipe's utilities found here: https://git.gitorious.org/usb/usb-tools.git master + +12. PHONET function +=================== + +The function is provided by usb_f_phonet.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "phonet". +The PHONET function provides just one attribute in its function directory: + + ifname - network device interface name associated with this + function instance + +Testing the PHONET function +--------------------------- + +It is not possible to test the SOCK_STREAM protocol without a specific piece +of hardware, so only SOCK_DGRAM has been tested. For the latter to work, +in the past I had to apply the patch mentioned here: + +http://www.spinics.net/lists/linux-usb/msg85689.html + +These tools are required: + +git://git.gitorious.org/meego-cellular/phonet-utils.git + +On the host: + +$ ./phonet -a 0x10 -i usbpn0 +$ ./pnroute add 0x6c usbpn0 +$./pnroute add 0x10 usbpn0 +$ ifconfig usbpn0 up + +On the device: + +$ ./phonet -a 0x6c -i upnlink0 +$ ./pnroute add 0x10 upnlink0 +$ ifconfig upnlink0 up + +Then a test program can be used: + +http://www.spinics.net/lists/linux-usb/msg85690.html + +On the device: + +$ ./pnxmit -a 0x6c -r + +On the host: + +$ ./pnxmit -a 0x10 -s 0x6c + +As a result some data should be sent from host to device. +Then the other way round: + +On the host: + +$ ./pnxmit -a 0x10 -r + +On the device: + +$ ./pnxmit -a 0x6c -s 0x10 -- cgit v0.10.2 From ddb722449cfcfe30d3c3ed2cd404551e1c3c3670 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:37 +0100 Subject: Documentation: usb: RNDIS function testing Summary of how to test RNDIS function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 02d4366..61bc5f5 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -13,6 +13,7 @@ provided by gadgets. 10. NCM function 11. OBEX function 12. PHONET function +13. RNDIS function 1. ACM function @@ -493,3 +494,38 @@ $ ./pnxmit -a 0x10 -r On the device: $ ./pnxmit -a 0x6c -s 0x10 + +13. RNDIS function +================== + +The function is provided by usb_f_rndis.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "rndis". +The RNDIS function provides these attributes in its function directory: + + ifname - network device interface name associated with this + function instance + qmult - queue length multiplier for high and super speed + host_addr - MAC address of host's end of this + Ethernet over USB link + dev_addr - MAC address of device's end of this + Ethernet over USB link + +and after creating the functions/rndis. they contain default +values: qmult is 5, dev_addr and host_addr are randomly selected. +Except for ifname they can be written to until the function is linked to a +configuration. The ifname is read-only and contains the name of the interface +which was assigned by the net core, e. g. usb0. + +By default there can be only 1 RNDIS interface in the system. + +Testing the RNDIS function +-------------------------- + +Configure IP addresses of the device and the host. Then: + +On the device: ping +On the host: ping -- cgit v0.10.2 From 4dfcec8a2f9e22ac409ae512d5ff6acced8c588f Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:38 +0100 Subject: Documentation: usb: SERIAL function testing Summary of how to test SERIAL function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 61bc5f5..bc57c65 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -14,6 +14,7 @@ provided by gadgets. 11. OBEX function 12. PHONET function 13. RNDIS function +14. SERIAL function 1. ACM function @@ -529,3 +530,33 @@ Configure IP addresses of the device and the host. Then: On the device: ping On the host: ping + +14. SERIAL function +=================== + +The function is provided by usb_f_gser.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "gser". +The SERIAL function provides just one attribute in its function directory: + + port_num + +The attribute is read-only. + +There can be at most 4 ACM/generic serial/OBEX ports in the system. + +Testing the SERIAL function +--------------------------- + +On host: insmod usbserial + echo VID PID >/sys/bus/usb-serial/drivers/generic/new_id +On host: cat > /dev/ttyUSB +On target: cat /dev/ttyGS + +then the other way round + +On target: cat > /dev/ttyGS +On host: cat /dev/ttyUSB -- cgit v0.10.2 From 480a912be4eb51e30aec721731b9cf476b2ee861 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:39 +0100 Subject: Documentation: usb: SOURCESINK function testing Summary of how to test SOURCESINK function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index bc57c65..73a5b0c 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -15,6 +15,7 @@ provided by gadgets. 12. PHONET function 13. RNDIS function 14. SERIAL function +15. SOURCESINK function 1. ACM function @@ -560,3 +561,29 @@ then the other way round On target: cat > /dev/ttyGS On host: cat /dev/ttyUSB + +15. SOURCESINK function +======================= + +The function is provided by usb_f_ss_lb.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "SourceSink". +The SOURCESINK function provides these attributes in its function directory: + + pattern - 0 (all zeros), 1 (mod63), 2 (none) + isoc_interval - 1..16 + isoc_maxpacket - 0 - 1023 (fs), 0 - 1024 (hs/ss) + isoc_mult - 0..2 (hs/ss only) + isoc_maxburst - 0..15 (ss only) + bulk_buflen - buffer length + +Testing the SOURCESINK function +------------------------------- + +device: run the gadget +host: test-usb + +http://www.linux-usb.org/usbtest/testusb.c -- cgit v0.10.2 From dae0358694abe0bbca3b83a922698b1f24c4299a Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:40 +0100 Subject: Documentation: usb: UAC1 function testing Summary of how to test UAC1 function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 73a5b0c..40d22d8 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -16,6 +16,7 @@ provided by gadgets. 13. RNDIS function 14. SERIAL function 15. SOURCESINK function +16. UAC1 function 1. ACM function @@ -587,3 +588,29 @@ device: run the gadget host: test-usb http://www.linux-usb.org/usbtest/testusb.c + +16. UAC1 function +================= + +The function is provided by usb_f_uac1.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "uac1". +The uac1 function provides these attributes in its function directory: + + audio_buf_size - audio buffer size + fn_cap - capture pcm device file name + fn_cntl - control device file name + fn_play - playback pcm device file name + req_buf_size - ISO OUT endpoint request buffer size + req_count - ISO OUT endpoint request count + +The attributes have sane default values. + +Testing the UAC1 function +------------------------- + +device: run the gadget +host: aplay -l # should list our USB Audio Gadget -- cgit v0.10.2 From 020c6f93484370672060fabebc49aed5d1a7c58f Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:41 +0100 Subject: Documentation: usb: UAC2 function testing Summary of how to test UAC2 function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 40d22d8..5ac9dc7 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -17,6 +17,7 @@ provided by gadgets. 14. SERIAL function 15. SOURCESINK function 16. UAC1 function +17. UAC2 function 1. ACM function @@ -614,3 +615,41 @@ Testing the UAC1 function device: run the gadget host: aplay -l # should list our USB Audio Gadget + +17. UAC2 function +================= + +The function is provided by usb_f_uac2.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "uac2". +The uac2 function provides these attributes in its function directory: + + chmask - capture channel mask + c_srate - capture sampling rate + c_ssize - capture sample size (bytes) + p_chmask - playback channel mask + p_srate - playback sampling rate + p_ssize - playback sample size (bytes) + +The attributes have sane default values. + +Testing the UAC2 function +------------------------- + +device: run the gadget +host: aplay -l # should list our USB Audio Gadget + +This function does not require real hardware support, it just +sends a stream of audio data to/from the host. In order to +actually hear something at the device side, a command similar +to this must be used at the device side: + +$ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 & + +e.g.: + +$ arecord -f dat -t wav -D hw:CARD=UAC2Gadget,DEV=0 | \ +aplay -D default:CARD=OdroidU3 -- cgit v0.10.2 From 9c4f538bfaf696c5cb24ec58fbeba12155c56c31 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 16 Dec 2014 14:56:42 +0100 Subject: Documentation: usb: UVC function testing Summary of how to test UVC function of USB gadget. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index 5ac9dc7..076ac7b 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -18,6 +18,7 @@ provided by gadgets. 15. SOURCESINK function 16. UAC1 function 17. UAC2 function +18. UVC function 1. ACM function @@ -653,3 +654,75 @@ e.g.: $ arecord -f dat -t wav -D hw:CARD=UAC2Gadget,DEV=0 | \ aplay -D default:CARD=OdroidU3 + +18. UVC function +================ + +The function is provided by usb_f_uvc.ko module. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "uvc". +The uvc function provides these attributes in its function directory: + + streaming_interval - interval for polling endpoint for data transfers + streaming_maxburst - bMaxBurst for super speed companion descriptor + streaming_maxpacket - maximum packet size this endpoint is capable of + sending or receiving when this configuration is + selected + +There are also "control" and "streaming" subdirectories, each of which contain +a number of their subdirectories. There are some sane defaults provided, but +the user must provide the following: + + control header - create in control/header, link from control/class/fs + and/or control/class/ss + streaming header - create in streaming/header, link from + streaming/class/fs and/or streaming/class/hs and/or + streaming/class/ss + format description - create in streaming/mjpeg and/or + streaming/uncompressed + frame description - create in streaming/mjpeg/ and/or in + streaming/uncompressed/ + +Each frame description contains frame interval specification, and each +such specification consists of a number of lines with an inverval value +in each line. The rules stated above are best illustrated with an example: + +# mkdir functions/uvc.usb0/control/header/h +# cd functions/uvc.usb0/control/header/h +# ln -s header/h class/fs +# ln -s header/h class/ss +# mkdir -p functions/uvc.usb0/streaming/uncompressed/u/360p +# cat < functions/uvc.usb0/streaming/uncompressed/u/360p/dwFrameInterval +666666 +1000000 +5000000 +EOF +# cd $GADGET_CONFIGFS_ROOT +# mkdir functions/uvc.usb0/streaming/header/h +# cd functions/uvc.usb0/streaming/header/h +# ln -s ../../uncompressed/u +# cd ../../class/fs +# ln -s ../../header/h +# cd ../../class/hs +# ln -s ../../header/h +# cd ../../class/ss +# ln -s ../../header/h + + +Testing the UVC function +------------------------ + +device: run the gadget, modprobe vivid + +# uvc-gadget -u /dev/video -v /dev/video + +where uvc-gadget is this program: +http://git.ideasonboard.org/uvc-gadget.git + +with these patches: +http://www.spinics.net/lists/linux-usb/msg99220.html + +host: luvcview -f yuv -- cgit v0.10.2 From 39a2ac2738658fd3e56987fb8b43b604298e9ad2 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 15 Dec 2014 13:50:05 +0100 Subject: usb: gadget: hid: consistently use 2^n - 1 for max values A maximum value which fits in 16 bits, unsigned, is 65535. Acked-by: Geert Uytterhoeven Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 6e04e30..9660ade 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -758,7 +758,7 @@ static struct f_hid_opts_attribute f_hid_opts_##name = \ F_HID_OPT(subclass, 8, 255); F_HID_OPT(protocol, 8, 255); -F_HID_OPT(report_length, 16, 65536); +F_HID_OPT(report_length, 16, 65535); static ssize_t f_hid_opts_report_desc_show(struct f_hid_opts *opts, char *page) { -- cgit v0.10.2 From 368504c00a2dfce637a55fcbf0cc0339f9ff5ca3 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Fri, 12 Dec 2014 22:53:18 +0300 Subject: usb: renesas_usbhs: kill dead code in usbhs_probe() usbhsc_drvcllbck_notify_hotplug() always returns 0, so it's rather pointless to store and check its result for being < 0. Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 3714787..a706fc4 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -632,16 +632,12 @@ static int usbhs_probe(struct platform_device *pdev) /* * manual call notify_hotplug for cold plug */ - ret = usbhsc_drvcllbck_notify_hotplug(pdev); - if (ret < 0) - goto probe_end_call_remove; + usbhsc_drvcllbck_notify_hotplug(pdev); dev_info(&pdev->dev, "probed\n"); return ret; -probe_end_call_remove: - usbhs_platform_call(priv, hardware_exit, pdev); probe_end_mod_exit: usbhs_mod_remove(priv); probe_end_fifo_exit: -- cgit v0.10.2 From 6b1b0fe0698f0e4d528a33f43d5c56f7e6e8f756 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 12 Dec 2014 13:08:26 -0200 Subject: Documentation: usb: phy: nop: Fix the description of 'vcc-supply' Since bd27fa44e13830d2b ("usb: phy: generic: Don't use regulator framework for RESET line") we no longer model the reset line as a regulator supply, so adapt the documentation accordingly. Acked-by: Roger Quadros Signed-off-by: Fabio Estevam Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt index 1bd37fa..8db5b33 100644 --- a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt +++ b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt @@ -13,7 +13,7 @@ Optional properties: - clock-frequency: the clock frequency (in Hz) that the PHY clock must be configured to. -- vcc-supply: phandle to the regulator that provides RESET to the PHY. +- vcc-supply: phandle to the regulator that provides power to the PHY. - reset-gpios: Should specify the GPIO for reset. -- cgit v0.10.2 From bbea6de1bd12dd509f774f2a3165216eeac7bc54 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Wed, 10 Dec 2014 12:34:00 +0100 Subject: usb: gadget: f_uvc: rename a macro to avoid conflicts When configfs is integrated, CONFIGFS_ATTR_STRUCT and CONFIGFS_ATTR_OPS macros should be used, but the latter expects that tere is a to_f_uvc_opts function accepting a config_item, whereas the macro being changed can be applied to a different type of argument. Signed-off-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 945b3bd..5b4ab39 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -605,7 +605,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) INFO(cdev, "uvc_function_bind\n"); - opts = to_f_uvc_opts(f->fi); + opts = fi_to_f_uvc_opts(f->fi); /* Sanity check the streaming endpoint module parameters. */ opts->streaming_interval = clamp(opts->streaming_interval, 1U, 16U); @@ -766,7 +766,7 @@ error: static void uvc_free_inst(struct usb_function_instance *f) { - struct f_uvc_opts *opts = to_f_uvc_opts(f); + struct f_uvc_opts *opts = fi_to_f_uvc_opts(f); kfree(opts); } @@ -818,7 +818,7 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) return ERR_PTR(-ENOMEM); uvc->state = UVC_STATE_DISCONNECTED; - opts = to_f_uvc_opts(fi); + opts = fi_to_f_uvc_opts(fi); uvc->desc.fs_control = opts->fs_control; uvc->desc.ss_control = opts->ss_control; diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 2a8dfdf..c0706a3 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -18,7 +18,7 @@ #include -#define to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst) +#define fi_to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst) struct f_uvc_opts { struct usb_function_instance func_inst; -- cgit v0.10.2 From 6c25955ed632227d28b85db274e519b766e26ddd Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Wed, 10 Dec 2014 12:34:01 +0100 Subject: usb: gadget: uvc: verify descriptors presence If the caller of uvc_alloc() does not provide enough descriptors, binding the function should fail, so appropriate code is returned from uvc_copy_descriptors(). uvc_function_bind() is modified accordingly to account for possible errors from uvc_copy_descriptors(). Signed-off-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 5b4ab39..62ca0c5 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -509,6 +509,9 @@ uvc_copy_descriptors(struct uvc_device *uvc, enum usb_device_speed speed) break; } + if (!uvc_control_desc || !uvc_streaming_cls) + return ERR_PTR(-ENODEV); + /* Descriptors layout * * uvc_iad @@ -700,10 +703,27 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) /* Copy descriptors */ f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL); - if (gadget_is_dualspeed(cdev->gadget)) + if (IS_ERR(f->fs_descriptors)) { + ret = PTR_ERR(f->fs_descriptors); + f->fs_descriptors = NULL; + goto error; + } + if (gadget_is_dualspeed(cdev->gadget)) { f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH); - if (gadget_is_superspeed(c->cdev->gadget)) + if (IS_ERR(f->hs_descriptors)) { + ret = PTR_ERR(f->hs_descriptors); + f->hs_descriptors = NULL; + goto error; + } + } + if (gadget_is_superspeed(c->cdev->gadget)) { f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER); + if (IS_ERR(f->ss_descriptors)) { + ret = PTR_ERR(f->ss_descriptors); + f->ss_descriptors = NULL; + goto error; + } + } /* Preallocate control endpoint request. */ uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); -- cgit v0.10.2 From 46919a23ee87bbc4eeb6d958471174e26836f0e1 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Wed, 10 Dec 2014 12:34:02 +0100 Subject: usb: gadget: uvc: configfs support in uvc function Add support for using the uvc function as a component of USB gadgets composed with configfs. Acked-by: Laurent Pinchart Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc new file mode 100644 index 0000000..2f4a005 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -0,0 +1,265 @@ +What: /config/usb-gadget/gadget/functions/uvc.name +Date: Dec 2014 +KernelVersion: 3.20 +Description: UVC function directory + + streaming_maxburst - 0..15 (ss only) + streaming_maxpacket - 1..1023 (fs), 1..3072 (hs/ss) + streaming_interval - 1..16 + +What: /config/usb-gadget/gadget/functions/uvc.name/control +Date: Dec 2014 +KernelVersion: 3.20 +Description: Control descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/class +Date: Dec 2014 +KernelVersion: 3.20 +Description: Class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/class/ss +Date: Dec 2014 +KernelVersion: 3.20 +Description: Super speed control class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/class/fs +Date: Dec 2014 +KernelVersion: 3.20 +Description: Full speed control class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal +Date: Dec 2014 +KernelVersion: 3.20 +Description: Terminal descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal/output +Date: Dec 2014 +KernelVersion: 3.20 +Description: Output terminal descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal/output/default +Date: Dec 2014 +KernelVersion: 3.20 +Description: Default output terminal descriptors + + All attributes read only: + iTerminal - index of string descriptor + bSourceID - id of the terminal to which this terminal + is connected + bAssocTerminal - id of the input terminal to which this output + terminal is associated + wTerminalType - terminal type + bTerminalID - a non-zero id of this terminal + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal/camera +Date: Dec 2014 +KernelVersion: 3.20 +Description: Camera terminal descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/terminal/camera/default +Date: Dec 2014 +KernelVersion: 3.20 +Description: Default camera terminal descriptors + + All attributes read only: + bmControls - bitmap specifying which controls are + supported for the video stream + wOcularFocalLength - the value of Locular + wObjectiveFocalLengthMax- the value of Lmin + wObjectiveFocalLengthMin- the value of Lmax + iTerminal - index of string descriptor + bAssocTerminal - id of the output terminal to which + this terminal is connected + wTerminalType - terminal type + bTerminalID - a non-zero id of this terminal + +What: /config/usb-gadget/gadget/functions/uvc.name/control/processing +Date: Dec 2014 +KernelVersion: 3.20 +Description: Processing unit descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/processing/default +Date: Dec 2014 +KernelVersion: 3.20 +Description: Default processing unit descriptors + + All attributes read only: + iProcessing - index of string descriptor + bmControls - bitmap specifying which controls are + supported for the video stream + wMaxMultiplier - maximum digital magnification x100 + bSourceID - id of the terminal to which this unit is + connected + bUnitID - a non-zero id of this unit + +What: /config/usb-gadget/gadget/functions/uvc.name/control/header +Date: Dec 2014 +KernelVersion: 3.20 +Description: Control header descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/control/header/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific control header descriptors + +dwClockFrequency +bcdUVC +What: /config/usb-gadget/gadget/functions/uvc.name/streaming +Date: Dec 2014 +KernelVersion: 3.20 +Description: Streaming descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class +Date: Dec 2014 +KernelVersion: 3.20 +Description: Streaming class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class/ss +Date: Dec 2014 +KernelVersion: 3.20 +Description: Super speed streaming class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class/hs +Date: Dec 2014 +KernelVersion: 3.20 +Description: High speed streaming class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class/fs +Date: Dec 2014 +KernelVersion: 3.20 +Description: Full speed streaming class descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/color_matching +Date: Dec 2014 +KernelVersion: 3.20 +Description: Color matching descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/color_matching/default +Date: Dec 2014 +KernelVersion: 3.20 +Description: Default color matching descriptors + + All attributes read only: + bMatrixCoefficients - matrix used to compute luma and + chroma values from the color primaries + bTransferCharacteristics- optoelectronic transfer + characteristic of the source picutre, + also called the gamma function + bColorPrimaries - color primaries and the reference + white + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/mjpeg +Date: Dec 2014 +KernelVersion: 3.20 +Description: MJPEG format descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/mjpeg/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific MJPEG format descriptors + + All attributes read only, + except bmaControls and bDefaultFrameIndex: + bmaControls - this format's data for bmaControls in + the streaming header + bmInterfaceFlags - specifies interlace information, + read-only + bAspectRatioY - the X dimension of the picture aspect + ratio, read-only + bAspectRatioX - the Y dimension of the picture aspect + ratio, read-only + bmFlags - characteristics of this format, + read-only + bDefaultFrameIndex - optimum frame index for this stream + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/mjpeg/name/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific MJPEG frame descriptors + + dwFrameInterval - indicates how frame interval can be + programmed; a number of values + separated by newline can be specified + dwDefaultFrameInterval - the frame interval the device would + like to use as default + dwMaxVideoFrameBufferSize- the maximum number of bytes the + compressor will produce for a video + frame or still image + dwMaxBitRate - the maximum bit rate at the shortest + frame interval in bps + dwMinBitRate - the minimum bit rate at the longest + frame interval in bps + wHeight - height of decoded bitmap frame in px + wWidth - width of decoded bitmam frame in px + bmCapabilities - still image support, fixed frame-rate + support + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/uncompressed +Date: Dec 2014 +KernelVersion: 3.20 +Description: Uncompressed format descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/uncompressed/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific uncompressed format descriptors + + bmaControls - this format's data for bmaControls in + the streaming header + bmInterfaceFlags - specifies interlace information, + read-only + bAspectRatioY - the X dimension of the picture aspect + ratio, read-only + bAspectRatioX - the Y dimension of the picture aspect + ratio, read-only + bDefaultFrameIndex - optimum frame index for this stream + bBitsPerPixel - number of bits per pixel used to + specify color in the decoded video + frame + guidFormat - globally unique id used to identify + stream-encoding format + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/uncompressed/name/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific uncompressed frame descriptors + + dwFrameInterval - indicates how frame interval can be + programmed; a number of values + separated by newline can be specified + dwDefaultFrameInterval - the frame interval the device would + like to use as default + dwMaxVideoFrameBufferSize- the maximum number of bytes the + compressor will produce for a video + frame or still image + dwMaxBitRate - the maximum bit rate at the shortest + frame interval in bps + dwMinBitRate - the minimum bit rate at the longest + frame interval in bps + wHeight - height of decoded bitmap frame in px + wWidth - width of decoded bitmam frame in px + bmCapabilities - still image support, fixed frame-rate + support + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/header +Date: Dec 2014 +KernelVersion: 3.20 +Description: Streaming header descriptors + +What: /config/usb-gadget/gadget/functions/uvc.name/streaming/header/name +Date: Dec 2014 +KernelVersion: 3.20 +Description: Specific streaming header descriptors + + All attributes read only: + bTriggerUsage - how the host software will respond to + a hardware trigger interrupt event + bTriggerSupport - flag specifying if hardware + triggering is supported + bStillCaptureMethod - method of still image caputre + supported + bTerminalLink - id of the output terminal to which + the video endpoint of this interface + is connected + bmInfo - capabilities of this video streaming + interface diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 747ef53..65f7f12 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -423,6 +423,17 @@ config USB_CONFIGFS_F_HID For more information, see Documentation/usb/gadget_hid.txt. +config USB_CONFIGFS_F_UVC + boolean "USB Webcam function" + depends on USB_CONFIGFS + depends on VIDEO_DEV + select VIDEOBUF2_VMALLOC + select USB_F_UVC + help + The Webcam function acts as a composite USB Audio and Video Class + device. It provides a userspace API to process UVC control requests + and stream video data to the host. + source "drivers/usb/gadget/legacy/Kconfig" endchoice diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index dd68091..f71b1aa 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -36,7 +36,7 @@ usb_f_uac1-y := f_uac1.o u_uac1.o obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o usb_f_uac2-y := f_uac2.o obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o -usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o +usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o obj-$(CONFIG_USB_F_UVC) += usb_f_uvc.o usb_f_midi-y := f_midi.o obj-$(CONFIG_USB_F_MIDI) += usb_f_midi.o diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 62ca0c5..76891ad 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -27,10 +27,11 @@ #include #include +#include "u_uvc.h" #include "uvc.h" +#include "uvc_configfs.h" #include "uvc_v4l2.h" #include "uvc_video.h" -#include "u_uvc.h" unsigned int uvc_gadget_trace_param; @@ -788,25 +789,104 @@ static void uvc_free_inst(struct usb_function_instance *f) { struct f_uvc_opts *opts = fi_to_f_uvc_opts(f); + mutex_destroy(&opts->lock); kfree(opts); } static struct usb_function_instance *uvc_alloc_inst(void) { struct f_uvc_opts *opts; + struct uvc_camera_terminal_descriptor *cd; + struct uvc_processing_unit_descriptor *pd; + struct uvc_output_terminal_descriptor *od; + struct uvc_color_matching_descriptor *md; + struct uvc_descriptor_header **ctl_cls; opts = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) return ERR_PTR(-ENOMEM); opts->func_inst.free_func_inst = uvc_free_inst; - + mutex_init(&opts->lock); + + cd = &opts->uvc_camera_terminal; + cd->bLength = UVC_DT_CAMERA_TERMINAL_SIZE(3); + cd->bDescriptorType = USB_DT_CS_INTERFACE; + cd->bDescriptorSubType = UVC_VC_INPUT_TERMINAL; + cd->bTerminalID = 1; + cd->wTerminalType = cpu_to_le16(0x0201); + cd->bAssocTerminal = 0; + cd->iTerminal = 0; + cd->wObjectiveFocalLengthMin = cpu_to_le16(0); + cd->wObjectiveFocalLengthMax = cpu_to_le16(0); + cd->wOcularFocalLength = cpu_to_le16(0); + cd->bControlSize = 3; + cd->bmControls[0] = 2; + cd->bmControls[1] = 0; + cd->bmControls[2] = 0; + + pd = &opts->uvc_processing; + pd->bLength = UVC_DT_PROCESSING_UNIT_SIZE(2); + pd->bDescriptorType = USB_DT_CS_INTERFACE; + pd->bDescriptorSubType = UVC_VC_PROCESSING_UNIT; + pd->bUnitID = 2; + pd->bSourceID = 1; + pd->wMaxMultiplier = cpu_to_le16(16*1024); + pd->bControlSize = 2; + pd->bmControls[0] = 1; + pd->bmControls[1] = 0; + pd->iProcessing = 0; + + od = &opts->uvc_output_terminal; + od->bLength = UVC_DT_OUTPUT_TERMINAL_SIZE; + od->bDescriptorType = USB_DT_CS_INTERFACE; + od->bDescriptorSubType = UVC_VC_OUTPUT_TERMINAL; + od->bTerminalID = 3; + od->wTerminalType = cpu_to_le16(0x0101); + od->bAssocTerminal = 0; + od->bSourceID = 2; + od->iTerminal = 0; + + md = &opts->uvc_color_matching; + md->bLength = UVC_DT_COLOR_MATCHING_SIZE; + md->bDescriptorType = USB_DT_CS_INTERFACE; + md->bDescriptorSubType = UVC_VS_COLORFORMAT; + md->bColorPrimaries = 1; + md->bTransferCharacteristics = 1; + md->bMatrixCoefficients = 4; + + /* Prepare fs control class descriptors for configfs-based gadgets */ + ctl_cls = opts->uvc_fs_control_cls; + ctl_cls[0] = NULL; /* assigned elsewhere by configfs */ + ctl_cls[1] = (struct uvc_descriptor_header *)cd; + ctl_cls[2] = (struct uvc_descriptor_header *)pd; + ctl_cls[3] = (struct uvc_descriptor_header *)od; + ctl_cls[4] = NULL; /* NULL-terminate */ + opts->fs_control = + (const struct uvc_descriptor_header * const *)ctl_cls; + + /* Prepare hs control class descriptors for configfs-based gadgets */ + ctl_cls = opts->uvc_ss_control_cls; + ctl_cls[0] = NULL; /* assigned elsewhere by configfs */ + ctl_cls[1] = (struct uvc_descriptor_header *)cd; + ctl_cls[2] = (struct uvc_descriptor_header *)pd; + ctl_cls[3] = (struct uvc_descriptor_header *)od; + ctl_cls[4] = NULL; /* NULL-terminate */ + opts->ss_control = + (const struct uvc_descriptor_header * const *)ctl_cls; + + opts->streaming_interval = 1; + opts->streaming_maxpacket = 1024; + + uvcg_attach_configfs(opts); return &opts->func_inst; } static void uvc_free(struct usb_function *f) { struct uvc_device *uvc = to_uvc(f); - + struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts, + func_inst); + --opts->refcnt; kfree(uvc); } @@ -832,6 +912,7 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) { struct uvc_device *uvc; struct f_uvc_opts *opts; + struct uvc_descriptor_header **strm_cls; uvc = kzalloc(sizeof(*uvc), GFP_KERNEL); if (uvc == NULL) @@ -840,11 +921,30 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) uvc->state = UVC_STATE_DISCONNECTED; opts = fi_to_f_uvc_opts(fi); + mutex_lock(&opts->lock); + if (opts->uvc_fs_streaming_cls) { + strm_cls = opts->uvc_fs_streaming_cls; + opts->fs_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + if (opts->uvc_hs_streaming_cls) { + strm_cls = opts->uvc_hs_streaming_cls; + opts->hs_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + if (opts->uvc_ss_streaming_cls) { + strm_cls = opts->uvc_ss_streaming_cls; + opts->ss_streaming = + (const struct uvc_descriptor_header * const *)strm_cls; + } + uvc->desc.fs_control = opts->fs_control; uvc->desc.ss_control = opts->ss_control; uvc->desc.fs_streaming = opts->fs_streaming; uvc->desc.hs_streaming = opts->hs_streaming; uvc->desc.ss_streaming = opts->ss_streaming; + ++opts->refcnt; + mutex_unlock(&opts->lock); /* Register the function. */ uvc->func.name = "uvc"; diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index c0706a3..4676b60 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -17,6 +17,7 @@ #define U_UVC_H #include +#include #define fi_to_f_uvc_opts(f) container_of(f, struct f_uvc_opts, func_inst) @@ -26,11 +27,60 @@ struct f_uvc_opts { unsigned int streaming_interval; unsigned int streaming_maxpacket; unsigned int streaming_maxburst; + + /* + * Control descriptors array pointers for full-/high-speed and + * super-speed. They point by default to the uvc_fs_control_cls and + * uvc_ss_control_cls arrays respectively. Legacy gadgets must + * override them in their gadget bind callback. + */ const struct uvc_descriptor_header * const *fs_control; const struct uvc_descriptor_header * const *ss_control; + + /* + * Streaming descriptors array pointers for full-speed, high-speed and + * super-speed. They will point to the uvc_[fhs]s_streaming_cls arrays + * for configfs-based gadgets. Legacy gadgets must initialize them in + * their gadget bind callback. + */ const struct uvc_descriptor_header * const *fs_streaming; const struct uvc_descriptor_header * const *hs_streaming; const struct uvc_descriptor_header * const *ss_streaming; + + /* Default control descriptors for configfs-based gadgets. */ + struct uvc_camera_terminal_descriptor uvc_camera_terminal; + struct uvc_processing_unit_descriptor uvc_processing; + struct uvc_output_terminal_descriptor uvc_output_terminal; + struct uvc_color_matching_descriptor uvc_color_matching; + + /* + * Control descriptors pointers arrays for full-/high-speed and + * super-speed. The first element is a configurable control header + * descriptor, the other elements point to the fixed default control + * descriptors. Used by configfs only, must not be touched by legacy + * gadgets. + */ + struct uvc_descriptor_header *uvc_fs_control_cls[5]; + struct uvc_descriptor_header *uvc_ss_control_cls[5]; + + /* + * Streaming descriptors for full-speed, high-speed and super-speed. + * Used by configfs only, must not be touched by legacy gadgets. The + * arrays are allocated at runtime as the number of descriptors isn't + * known in advance. + */ + struct uvc_descriptor_header **uvc_fs_streaming_cls; + struct uvc_descriptor_header **uvc_hs_streaming_cls; + struct uvc_descriptor_header **uvc_ss_streaming_cls; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This lock protects the descriptors from concurrent access by + * read/write and symlink creation/removal. + */ + struct mutex lock; + int refcnt; }; void uvc_set_trace_param(unsigned int trace); diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c new file mode 100644 index 0000000..33d92ab --- /dev/null +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -0,0 +1,2439 @@ +/* + * uvc_configfs.c + * + * Configfs support for the uvc function. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include "u_uvc.h" +#include "uvc_configfs.h" + +#define UVCG_STREAMING_CONTROL_SIZE 1 + +#define CONFIGFS_ATTR_OPS_RO(_item) \ +static ssize_t _item##_attr_show(struct config_item *item, \ + struct configfs_attribute *attr, \ + char *page) \ +{ \ + struct _item *_item = to_##_item(item); \ + struct _item##_attribute *_item##_attr = \ + container_of(attr, struct _item##_attribute, attr); \ + ssize_t ret = 0; \ + \ + if (_item##_attr->show) \ + ret = _item##_attr->show(_item, page); \ + return ret; \ +} + +static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item); + +/* control/header/ */ +DECLARE_UVC_HEADER_DESCRIPTOR(1); + +struct uvcg_control_header { + struct config_item item; + struct UVC_HEADER_DESCRIPTOR(1) desc; + unsigned linked; +}; + +struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) +{ + return container_of(item, struct uvcg_control_header, item); +} + +CONFIGFS_ATTR_STRUCT(uvcg_control_header); +CONFIGFS_ATTR_OPS(uvcg_control_header); + +static struct configfs_item_operations uvcg_control_header_item_ops = { + .show_attribute = uvcg_control_header_attr_show, + .store_attribute = uvcg_control_header_attr_store, +}; + +#define UVCG_CTRL_HDR_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ +static ssize_t uvcg_control_header_##cname##_show( \ + struct uvcg_control_header *ch, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = ch->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(ch->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_control_header_##cname##_store(struct uvcg_control_header *ch, \ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\ + int ret; \ + uxx num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = ch->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (ch->linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = str2u(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + ch->desc.aname = vnoc(num); \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +static struct uvcg_control_header_attribute \ + uvcg_control_header_##cname = \ + __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \ + uvcg_control_header_##cname##_show, \ + uvcg_control_header_##cname##_store) + +UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, le16_to_cpu, kstrtou16, u16, cpu_to_le16, + 0xffff); + +UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, le32_to_cpu, kstrtou32, + u32, cpu_to_le32, 0x7fffffff); + +#undef UVCG_CTRL_HDR_ATTR + +static struct configfs_attribute *uvcg_control_header_attrs[] = { + &uvcg_control_header_bcd_uvc.attr, + &uvcg_control_header_dw_clock_frequency.attr, + NULL, +}; + +struct config_item_type uvcg_control_header_type = { + .ct_item_ops = &uvcg_control_header_item_ops, + .ct_attrs = uvcg_control_header_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *uvcg_control_header_make(struct config_group *group, + const char *name) +{ + struct uvcg_control_header *h; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + h->desc.bLength = UVC_DT_HEADER_SIZE(1); + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VC_HEADER; + h->desc.bcdUVC = cpu_to_le16(0x0100); + h->desc.dwClockFrequency = cpu_to_le32(48000000); + + config_item_init_type_name(&h->item, name, &uvcg_control_header_type); + + return &h->item; +} + +void uvcg_control_header_drop(struct config_group *group, + struct config_item *item) +{ + struct uvcg_control_header *h = to_uvcg_control_header(item); + + kfree(h); +} + +/* control/header */ +static struct uvcg_control_header_grp { + struct config_group group; +} uvcg_control_header_grp; + +static struct configfs_group_operations uvcg_control_header_grp_ops = { + .make_item = uvcg_control_header_make, + .drop_item = uvcg_control_header_drop, +}; + +static struct config_item_type uvcg_control_header_grp_type = { + .ct_group_ops = &uvcg_control_header_grp_ops, + .ct_owner = THIS_MODULE, +}; + +/* control/processing/default */ +static struct uvcg_default_processing { + struct config_group group; +} uvcg_default_processing; + +static inline struct uvcg_default_processing +*to_uvcg_default_processing(struct config_item *item) +{ + return container_of(to_config_group(item), + struct uvcg_default_processing, group); +} + +CONFIGFS_ATTR_STRUCT(uvcg_default_processing); +CONFIGFS_ATTR_OPS_RO(uvcg_default_processing); + +static struct configfs_item_operations uvcg_default_processing_item_ops = { + .show_attribute = uvcg_default_processing_attr_show, +}; + +#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv) \ +static ssize_t uvcg_default_processing_##cname##_show( \ + struct uvcg_default_processing *dp, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex; \ + struct uvc_processing_unit_descriptor *pd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + pd = &opts->uvc_processing; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(pd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_default_processing_attribute \ + uvcg_default_processing_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_default_processing_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, identity_conv); +UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, identity_conv); +UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, le16_to_cpu); +UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, identity_conv); + +#undef identity_conv + +#undef UVCG_DEFAULT_PROCESSING_ATTR + +static ssize_t uvcg_default_processing_bm_controls_show( + struct uvcg_default_processing *dp, char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex; + struct uvc_processing_unit_descriptor *pd; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + pd = &opts->uvc_processing; + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < pd->bControlSize; ++i) { + result += sprintf(pg, "%d\n", pd->bmControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +static struct uvcg_default_processing_attribute + uvcg_default_processing_bm_controls = + __CONFIGFS_ATTR_RO(bmControls, + uvcg_default_processing_bm_controls_show); + +static struct configfs_attribute *uvcg_default_processing_attrs[] = { + &uvcg_default_processing_b_unit_id.attr, + &uvcg_default_processing_b_source_id.attr, + &uvcg_default_processing_w_max_multiplier.attr, + &uvcg_default_processing_bm_controls.attr, + &uvcg_default_processing_i_processing.attr, + NULL, +}; + +static struct config_item_type uvcg_default_processing_type = { + .ct_item_ops = &uvcg_default_processing_item_ops, + .ct_attrs = uvcg_default_processing_attrs, + .ct_owner = THIS_MODULE, +}; + +/* struct uvcg_processing {}; */ + +static struct config_group *uvcg_processing_default_groups[] = { + &uvcg_default_processing.group, + NULL, +}; + +/* control/processing */ +static struct uvcg_processing_grp { + struct config_group group; +} uvcg_processing_grp; + +static struct config_item_type uvcg_processing_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* control/terminal/camera/default */ +static struct uvcg_default_camera { + struct config_group group; +} uvcg_default_camera; + +static inline struct uvcg_default_camera +*to_uvcg_default_camera(struct config_item *item) +{ + return container_of(to_config_group(item), + struct uvcg_default_camera, group); +} + +CONFIGFS_ATTR_STRUCT(uvcg_default_camera); +CONFIGFS_ATTR_OPS_RO(uvcg_default_camera); + +static struct configfs_item_operations uvcg_default_camera_item_ops = { + .show_attribute = uvcg_default_camera_attr_show, +}; + +#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv) \ +static ssize_t uvcg_default_camera_##cname##_show( \ + struct uvcg_default_camera *dc, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \ + struct uvc_camera_terminal_descriptor *cd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent-> \ + ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + cd = &opts->uvc_camera_terminal; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(cd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return result; \ +} \ + \ +static struct uvcg_default_camera_attribute \ + uvcg_default_camera_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_default_camera_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, identity_conv); +UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, le16_to_cpu); +UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv); +UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, identity_conv); +UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_min, wObjectiveFocalLengthMin, + le16_to_cpu); +UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_max, wObjectiveFocalLengthMax, + le16_to_cpu); +UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength, + le16_to_cpu); + +#undef identity_conv + +#undef UVCG_DEFAULT_CAMERA_ATTR + +static ssize_t uvcg_default_camera_bm_controls_show( + struct uvcg_default_camera *dc, char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; + struct uvc_camera_terminal_descriptor *cd; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent-> + ci_parent; + opts = to_f_uvc_opts(opts_item); + cd = &opts->uvc_camera_terminal; + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < cd->bControlSize; ++i) { + result += sprintf(pg, "%d\n", cd->bmControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +static struct uvcg_default_camera_attribute + uvcg_default_camera_bm_controls = + __CONFIGFS_ATTR_RO(bmControls, uvcg_default_camera_bm_controls_show); + +static struct configfs_attribute *uvcg_default_camera_attrs[] = { + &uvcg_default_camera_b_terminal_id.attr, + &uvcg_default_camera_w_terminal_type.attr, + &uvcg_default_camera_b_assoc_terminal.attr, + &uvcg_default_camera_i_terminal.attr, + &uvcg_default_camera_w_objective_focal_length_min.attr, + &uvcg_default_camera_w_objective_focal_length_max.attr, + &uvcg_default_camera_w_ocular_focal_length.attr, + &uvcg_default_camera_bm_controls.attr, + NULL, +}; + +static struct config_item_type uvcg_default_camera_type = { + .ct_item_ops = &uvcg_default_camera_item_ops, + .ct_attrs = uvcg_default_camera_attrs, + .ct_owner = THIS_MODULE, +}; + +/* struct uvcg_camera {}; */ + +static struct config_group *uvcg_camera_default_groups[] = { + &uvcg_default_camera.group, + NULL, +}; + +/* control/terminal/camera */ +static struct uvcg_camera_grp { + struct config_group group; +} uvcg_camera_grp; + +static struct config_item_type uvcg_camera_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* control/terminal/output/default */ +static struct uvcg_default_output { + struct config_group group; +} uvcg_default_output; + +static inline struct uvcg_default_output +*to_uvcg_default_output(struct config_item *item) +{ + return container_of(to_config_group(item), + struct uvcg_default_output, group); +} + +CONFIGFS_ATTR_STRUCT(uvcg_default_output); +CONFIGFS_ATTR_OPS_RO(uvcg_default_output); + +static struct configfs_item_operations uvcg_default_output_item_ops = { + .show_attribute = uvcg_default_output_attr_show, +}; + +#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv) \ +static ssize_t uvcg_default_output_##cname##_show( \ + struct uvcg_default_output *dout, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &dout->group.cg_subsys->su_mutex; \ + struct uvc_output_terminal_descriptor *cd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = dout->group.cg_item.ci_parent->ci_parent-> \ + ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + cd = &opts->uvc_output_terminal; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(cd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + \ + return result; \ +} \ + \ +static struct uvcg_default_output_attribute \ + uvcg_default_output_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_default_output_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, identity_conv); +UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, le16_to_cpu); +UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv); +UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, identity_conv); +UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, identity_conv); + +#undef identity_conv + +#undef UVCG_DEFAULT_OUTPUT_ATTR + +static struct configfs_attribute *uvcg_default_output_attrs[] = { + &uvcg_default_output_b_terminal_id.attr, + &uvcg_default_output_w_terminal_type.attr, + &uvcg_default_output_b_assoc_terminal.attr, + &uvcg_default_output_b_source_id.attr, + &uvcg_default_output_i_terminal.attr, + NULL, +}; + +static struct config_item_type uvcg_default_output_type = { + .ct_item_ops = &uvcg_default_output_item_ops, + .ct_attrs = uvcg_default_output_attrs, + .ct_owner = THIS_MODULE, +}; + +/* struct uvcg_output {}; */ + +static struct config_group *uvcg_output_default_groups[] = { + &uvcg_default_output.group, + NULL, +}; + +/* control/terminal/output */ +static struct uvcg_output_grp { + struct config_group group; +} uvcg_output_grp; + +static struct config_item_type uvcg_output_grp_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_terminal_default_groups[] = { + &uvcg_camera_grp.group, + &uvcg_output_grp.group, + NULL, +}; + +/* control/terminal */ +static struct uvcg_terminal_grp { + struct config_group group; +} uvcg_terminal_grp; + +static struct config_item_type uvcg_terminal_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* control/class/{fs} */ +static struct uvcg_control_class { + struct config_group group; +} uvcg_control_class_fs, uvcg_control_class_ss; + + +static inline struct uvc_descriptor_header +**uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o) +{ + struct uvcg_control_class *cl = container_of(to_config_group(i), + struct uvcg_control_class, group); + + if (cl == &uvcg_control_class_fs) + return o->uvc_fs_control_cls; + + if (cl == &uvcg_control_class_ss) + return o->uvc_ss_control_cls; + + return NULL; +} + +static int uvcg_control_class_allow_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *control, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header **class_array; + struct uvcg_control_header *target_hdr; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + control = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(control), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(control->ci_parent); + + mutex_lock(&opts->lock); + + class_array = uvcg_get_ctl_class_arr(src, opts); + if (!class_array) + goto unlock; + if (opts->refcnt || class_array[0]) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_control_header(target); + ++target_hdr->linked; + class_array[0] = (struct uvc_descriptor_header *)&target_hdr->desc; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + mutex_unlock(su_mutex); + return ret; +} + +static int uvcg_control_class_drop_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *control, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header **class_array; + struct uvcg_control_header *target_hdr; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + control = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(control), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(control->ci_parent); + + mutex_lock(&opts->lock); + + class_array = uvcg_get_ctl_class_arr(src, opts); + if (!class_array) + goto unlock; + if (opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_control_header(target); + --target_hdr->linked; + class_array[0] = NULL; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + mutex_unlock(su_mutex); + return ret; +} + +static struct configfs_item_operations uvcg_control_class_item_ops = { + .allow_link = uvcg_control_class_allow_link, + .drop_link = uvcg_control_class_drop_link, +}; + +static struct config_item_type uvcg_control_class_type = { + .ct_item_ops = &uvcg_control_class_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_control_class_default_groups[] = { + &uvcg_control_class_fs.group, + &uvcg_control_class_ss.group, + NULL, +}; + +/* control/class */ +static struct uvcg_control_class_grp { + struct config_group group; +} uvcg_control_class_grp; + +static struct config_item_type uvcg_control_class_grp_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_control_default_groups[] = { + &uvcg_control_header_grp.group, + &uvcg_processing_grp.group, + &uvcg_terminal_grp.group, + &uvcg_control_class_grp.group, + NULL, +}; + +/* control */ +static struct uvcg_control_grp { + struct config_group group; +} uvcg_control_grp; + +static struct config_item_type uvcg_control_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* streaming/uncompressed */ +static struct uvcg_uncompressed_grp { + struct config_group group; +} uvcg_uncompressed_grp; + +/* streaming/mjpeg */ +static struct uvcg_mjpeg_grp { + struct config_group group; +} uvcg_mjpeg_grp; + +static struct config_item *fmt_parent[] = { + &uvcg_uncompressed_grp.group.cg_item, + &uvcg_mjpeg_grp.group.cg_item, +}; + +enum uvcg_format_type { + UVCG_UNCOMPRESSED = 0, + UVCG_MJPEG, +}; + +struct uvcg_format { + struct config_group group; + enum uvcg_format_type type; + unsigned linked; + unsigned num_frames; + __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; +}; + +struct uvcg_format *to_uvcg_format(struct config_item *item) +{ + return container_of(to_config_group(item), struct uvcg_format, group); +} + +static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &f->group.cg_subsys->su_mutex; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = f->group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result = sprintf(pg, "0x"); + pg += result; + for (i = 0; i < UVCG_STREAMING_CONTROL_SIZE; ++i) { + result += sprintf(pg, "%x\n", f->bmaControls[i]); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +static ssize_t uvcg_format_bma_controls_store(struct uvcg_format *ch, + const char *page, size_t len) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->group.cg_subsys->su_mutex; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + if (ch->linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + if (len < 4 || *page != '0' || + (*(page + 1) != 'x' && *(page + 1) != 'X')) + goto end; + ret = hex2bin(ch->bmaControls, page + 2, 1); + if (ret < 0) + goto end; + ret = len; +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +struct uvcg_format_ptr { + struct uvcg_format *fmt; + struct list_head entry; +}; + +/* streaming/header/ */ +struct uvcg_streaming_header { + struct config_item item; + struct uvc_input_header_descriptor desc; + unsigned linked; + struct list_head formats; + unsigned num_fmt; +}; + +struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) +{ + return container_of(item, struct uvcg_streaming_header, item); +} + +CONFIGFS_ATTR_STRUCT(uvcg_streaming_header); +CONFIGFS_ATTR_OPS(uvcg_streaming_header); + +static int uvcg_streaming_header_allow_link(struct config_item *src, + struct config_item *target) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + struct uvcg_streaming_header *src_hdr; + struct uvcg_format *target_fmt = NULL; + struct uvcg_format_ptr *format_ptr; + int i, ret = -EINVAL; + + src_hdr = to_uvcg_streaming_header(src); + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + + if (src_hdr->linked) { + ret = -EBUSY; + goto out; + } + + for (i = 0; i < ARRAY_SIZE(fmt_parent); ++i) + if (target->ci_parent == fmt_parent[i]) + break; + if (i == ARRAY_SIZE(fmt_parent)) + goto out; + + target_fmt = container_of(to_config_group(target), struct uvcg_format, + group); + if (!target_fmt) + goto out; + + format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL); + if (!format_ptr) { + ret = PTR_ERR(format_ptr); + goto out; + } + ret = 0; + format_ptr->fmt = target_fmt; + list_add_tail(&format_ptr->entry, &src_hdr->formats); + ++src_hdr->num_fmt; + +out: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static int uvcg_streaming_header_drop_link(struct config_item *src, + struct config_item *target) +{ + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + struct uvcg_streaming_header *src_hdr; + struct uvcg_format *target_fmt = NULL; + struct uvcg_format_ptr *format_ptr, *tmp; + int ret = -EINVAL; + + src_hdr = to_uvcg_streaming_header(src); + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = src->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + target_fmt = container_of(to_config_group(target), struct uvcg_format, + group); + if (!target_fmt) + goto out; + + list_for_each_entry_safe(format_ptr, tmp, &src_hdr->formats, entry) + if (format_ptr->fmt == target_fmt) { + list_del(&format_ptr->entry); + kfree(format_ptr); + --src_hdr->num_fmt; + break; + } + +out: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; + +} + +static struct configfs_item_operations uvcg_streaming_header_item_ops = { + .show_attribute = uvcg_streaming_header_attr_show, + .store_attribute = uvcg_streaming_header_attr_store, + .allow_link = uvcg_streaming_header_allow_link, + .drop_link = uvcg_streaming_header_drop_link, +}; + +#define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv) \ +static ssize_t uvcg_streaming_header_##cname##_show( \ + struct uvcg_streaming_header *sh, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &sh->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = sh->item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(sh->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_streaming_header_attribute \ + uvcg_streaming_header_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_streaming_header_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, identity_conv); +UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, identity_conv); +UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod, + identity_conv); +UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, identity_conv); +UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, identity_conv); + +#undef identity_conv + +#undef UVCG_STREAMING_HEADER_ATTR + +static struct configfs_attribute *uvcg_streaming_header_attrs[] = { + &uvcg_streaming_header_bm_info.attr, + &uvcg_streaming_header_b_terminal_link.attr, + &uvcg_streaming_header_b_still_capture_method.attr, + &uvcg_streaming_header_b_trigger_support.attr, + &uvcg_streaming_header_b_trigger_usage.attr, + NULL, +}; + +struct config_item_type uvcg_streaming_header_type = { + .ct_item_ops = &uvcg_streaming_header_item_ops, + .ct_attrs = uvcg_streaming_header_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item +*uvcg_streaming_header_make(struct config_group *group, const char *name) +{ + struct uvcg_streaming_header *h; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + INIT_LIST_HEAD(&h->formats); + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_INPUT_HEADER; + h->desc.bTerminalLink = 3; + h->desc.bControlSize = UVCG_STREAMING_CONTROL_SIZE; + + config_item_init_type_name(&h->item, name, &uvcg_streaming_header_type); + + return &h->item; +} + +void uvcg_streaming_header_drop(struct config_group *group, + struct config_item *item) +{ + struct uvcg_streaming_header *h = to_uvcg_streaming_header(item); + + kfree(h); +} + +/* streaming/header */ +static struct uvcg_streaming_header_grp { + struct config_group group; +} uvcg_streaming_header_grp; + +static struct configfs_group_operations uvcg_streaming_header_grp_ops = { + .make_item = uvcg_streaming_header_make, + .drop_item = uvcg_streaming_header_drop, +}; + +static struct config_item_type uvcg_streaming_header_grp_type = { + .ct_group_ops = &uvcg_streaming_header_grp_ops, + .ct_owner = THIS_MODULE, +}; + +/* streaming/// */ +struct uvcg_frame { + struct { + u8 b_length; + u8 b_descriptor_type; + u8 b_descriptor_subtype; + u8 b_frame_index; + u8 bm_capabilities; + u16 w_width; + u16 w_height; + u32 dw_min_bit_rate; + u32 dw_max_bit_rate; + u32 dw_max_video_frame_buffer_size; + u32 dw_default_frame_interval; + u8 b_frame_interval_type; + } __attribute__((packed)) frame; + u32 *dw_frame_interval; + enum uvcg_format_type fmt_type; + struct config_item item; +}; + +struct uvcg_frame *to_uvcg_frame(struct config_item *item) +{ + return container_of(item, struct uvcg_frame, item); +} + +CONFIGFS_ATTR_STRUCT(uvcg_frame); +CONFIGFS_ATTR_OPS(uvcg_frame); + +static struct configfs_item_operations uvcg_frame_item_ops = { + .show_attribute = uvcg_frame_attr_show, + .store_attribute = uvcg_frame_attr_store, +}; + +#define UVCG_FRAME_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ +static ssize_t uvcg_frame_##cname##_show(struct uvcg_frame *f, char *page)\ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(f->frame.cname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t uvcg_frame_##cname##_store(struct uvcg_frame *f, \ + const char *page, size_t len)\ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct uvcg_format *fmt; \ + struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ + int ret; \ + uxx num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + fmt = to_uvcg_format(f->item.ci_parent); \ + \ + mutex_lock(&opts->lock); \ + if (fmt->linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = str2u(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + f->frame.cname = vnoc(num); \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +static struct uvcg_frame_attribute \ + uvcg_frame_##cname = \ + __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \ + uvcg_frame_##cname##_show, \ + uvcg_frame_##cname##_store) + +#define identity_conv(x) (x) + +UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, identity_conv, kstrtou8, u8, + identity_conv, 0xFF); +UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, kstrtou16, u16, cpu_to_le16, + 0xFFFF); +UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, kstrtou16, u16, cpu_to_le16, + 0xFFFF); +UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, kstrtou32, u32, + cpu_to_le32, 0xFFFFFFFF); +UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, kstrtou32, u32, + cpu_to_le32, 0xFFFFFFFF); +UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, + le32_to_cpu, kstrtou32, u32, cpu_to_le32, 0xFFFFFFFF); +UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, + le32_to_cpu, kstrtou32, u32, cpu_to_le32, 0xFFFFFFFF); + +#undef identity_conv + +#undef UVCG_FRAME_ATTR + +static ssize_t uvcg_frame_dw_frame_interval_show(struct uvcg_frame *frm, + char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &frm->item.ci_group->cg_subsys->su_mutex; + int result, i; + char *pg = page; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = frm->item.ci_parent->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + for (result = 0, i = 0; i < frm->frame.b_frame_interval_type; ++i) { + result += sprintf(pg, "%d\n", + le32_to_cpu(frm->dw_frame_interval[i])); + pg = page + result; + } + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + return result; +} + +static inline int __uvcg_count_frm_intrv(char *buf, void *priv) +{ + ++*((int *)priv); + return 0; +} + +static inline int __uvcg_fill_frm_intrv(char *buf, void *priv) +{ + u32 num, **interv; + int ret; + + ret = kstrtou32(buf, 0, &num); + if (ret) + return ret; + if (num > 0xFFFFFFFF) + return -EINVAL; + + interv = priv; + **interv = cpu_to_le32(num); + ++*interv; + + return 0; +} + +static int __uvcg_iter_frm_intrv(const char *page, size_t len, + int (*fun)(char *, void *), void *priv) +{ + /* sign, base 2 representation, newline, terminator */ + char buf[1 + sizeof(u32) * 8 + 1 + 1]; + const char *pg = page; + int i, ret; + + if (!fun) + return -EINVAL; + + while (pg - page < len) { + i = 0; + while (i < sizeof(buf) && (pg - page < len) && + *pg != '\0' && *pg != '\n') + buf[i++] = *pg++; + if (i == sizeof(buf)) + return -EINVAL; + while ((pg - page < len) && (*pg == '\0' || *pg == '\n')) + ++pg; + buf[i] = '\0'; + ret = fun(buf, priv); + if (ret) + return ret; + } + + return 0; +} + +static ssize_t uvcg_frame_dw_frame_interval_store(struct uvcg_frame *ch, + const char *page, size_t len) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct uvcg_format *fmt; + struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex; + int ret = 0, n = 0; + u32 *frm_intrv, *tmp; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->item.ci_parent->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + fmt = to_uvcg_format(ch->item.ci_parent); + + mutex_lock(&opts->lock); + if (fmt->linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + ret = __uvcg_iter_frm_intrv(page, len, __uvcg_count_frm_intrv, &n); + if (ret) + goto end; + + tmp = frm_intrv = kcalloc(n, sizeof(u32), GFP_KERNEL); + if (!frm_intrv) { + ret = -ENOMEM; + goto end; + } + + ret = __uvcg_iter_frm_intrv(page, len, __uvcg_fill_frm_intrv, &tmp); + if (ret) { + kfree(frm_intrv); + goto end; + } + + kfree(ch->dw_frame_interval); + ch->dw_frame_interval = frm_intrv; + ch->frame.b_frame_interval_type = n; + ret = len; + +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static struct uvcg_frame_attribute + uvcg_frame_dw_frame_interval = + __CONFIGFS_ATTR(dwFrameInterval, S_IRUGO | S_IWUSR, + uvcg_frame_dw_frame_interval_show, + uvcg_frame_dw_frame_interval_store); + +static struct configfs_attribute *uvcg_frame_attrs[] = { + &uvcg_frame_bm_capabilities.attr, + &uvcg_frame_w_width.attr, + &uvcg_frame_w_height.attr, + &uvcg_frame_dw_min_bit_rate.attr, + &uvcg_frame_dw_max_bit_rate.attr, + &uvcg_frame_dw_max_video_frame_buffer_size.attr, + &uvcg_frame_dw_default_frame_interval.attr, + &uvcg_frame_dw_frame_interval.attr, + NULL, +}; + +struct config_item_type uvcg_frame_type = { + .ct_item_ops = &uvcg_frame_item_ops, + .ct_attrs = uvcg_frame_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *uvcg_frame_make(struct config_group *group, + const char *name) +{ + struct uvcg_frame *h; + struct uvcg_format *fmt; + struct f_uvc_opts *opts; + struct config_item *opts_item; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + h->frame.b_descriptor_type = USB_DT_CS_INTERFACE; + h->frame.b_frame_index = 1; + h->frame.w_width = cpu_to_le16(640); + h->frame.w_height = cpu_to_le16(360); + h->frame.dw_min_bit_rate = cpu_to_le32(18432000); + h->frame.dw_max_bit_rate = cpu_to_le32(55296000); + h->frame.dw_max_video_frame_buffer_size = cpu_to_le32(460800); + h->frame.dw_default_frame_interval = cpu_to_le32(666666); + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + fmt = to_uvcg_format(&group->cg_item); + if (fmt->type == UVCG_UNCOMPRESSED) { + h->frame.b_descriptor_subtype = UVC_VS_FRAME_UNCOMPRESSED; + h->fmt_type = UVCG_UNCOMPRESSED; + } else if (fmt->type == UVCG_MJPEG) { + h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG; + h->fmt_type = UVCG_MJPEG; + } else { + mutex_unlock(&opts->lock); + return ERR_PTR(-EINVAL); + } + ++fmt->num_frames; + mutex_unlock(&opts->lock); + + config_item_init_type_name(&h->item, name, &uvcg_frame_type); + + return &h->item; +} + +void uvcg_frame_drop(struct config_group *group, struct config_item *item) +{ + struct uvcg_frame *h = to_uvcg_frame(item); + struct uvcg_format *fmt; + struct f_uvc_opts *opts; + struct config_item *opts_item; + + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + fmt = to_uvcg_format(&group->cg_item); + --fmt->num_frames; + kfree(h); + mutex_unlock(&opts->lock); +} + +/* streaming/uncompressed/ */ +struct uvcg_uncompressed { + struct uvcg_format fmt; + struct uvc_format_uncompressed desc; +}; + +struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) +{ + return container_of( + container_of(to_config_group(item), struct uvcg_format, group), + struct uvcg_uncompressed, fmt); +} + +CONFIGFS_ATTR_STRUCT(uvcg_uncompressed); +CONFIGFS_ATTR_OPS(uvcg_uncompressed); + +static struct configfs_item_operations uvcg_uncompressed_item_ops = { + .show_attribute = uvcg_uncompressed_attr_show, + .store_attribute = uvcg_uncompressed_attr_store, +}; + +static struct configfs_group_operations uvcg_uncompressed_group_ops = { + .make_item = uvcg_frame_make, + .drop_item = uvcg_frame_drop, +}; + +static ssize_t uvcg_uncompressed_guid_format_show(struct uvcg_uncompressed *ch, + char *page) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat)); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return sizeof(ch->desc.guidFormat); +} + +static ssize_t uvcg_uncompressed_guid_format_store(struct uvcg_uncompressed *ch, + const char *page, size_t len) +{ + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex; + int ret; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + if (ch->fmt.linked || opts->refcnt) { + ret = -EBUSY; + goto end; + } + + memcpy(ch->desc.guidFormat, page, + min(sizeof(ch->desc.guidFormat), len)); + ret = sizeof(ch->desc.guidFormat); + +end: + mutex_unlock(&opts->lock); + mutex_unlock(su_mutex); + return ret; +} + +static struct uvcg_uncompressed_attribute uvcg_uncompressed_guid_format = + __CONFIGFS_ATTR(guidFormat, S_IRUGO | S_IWUSR, + uvcg_uncompressed_guid_format_show, + uvcg_uncompressed_guid_format_store); + + +#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, conv) \ +static ssize_t uvcg_uncompressed_##cname##_show( \ + struct uvcg_uncompressed *u, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_uncompressed_attribute \ + uvcg_uncompressed_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_uncompressed_##cname##_show) + +#define UVCG_UNCOMPRESSED_ATTR(cname, aname, conv) \ +static ssize_t uvcg_uncompressed_##cname##_show( \ + struct uvcg_uncompressed *u, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_uncompressed_##cname##_store(struct uvcg_uncompressed *u, \ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int ret; \ + u8 num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (u->fmt.linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou8(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > 255) { \ + ret = -EINVAL; \ + goto end; \ + } \ + u->desc.aname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +static struct uvcg_uncompressed_attribute \ + uvcg_uncompressed_##cname = \ + __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \ + uvcg_uncompressed_##cname##_show, \ + uvcg_uncompressed_##cname##_store) + +#define identity_conv(x) (x) + +UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, identity_conv); +UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex, + identity_conv); +UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv); +UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv); +UVCG_UNCOMPRESSED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv); + +#undef identity_conv + +#undef UVCG_UNCOMPRESSED_ATTR +#undef UVCG_UNCOMPRESSED_ATTR_RO + +static inline ssize_t +uvcg_uncompressed_bma_controls_show(struct uvcg_uncompressed *unc, char *page) +{ + return uvcg_format_bma_controls_show(&unc->fmt, page); +} + +static inline ssize_t +uvcg_uncompressed_bma_controls_store(struct uvcg_uncompressed *ch, + const char *page, size_t len) +{ + return uvcg_format_bma_controls_store(&ch->fmt, page, len); +} + +static struct uvcg_uncompressed_attribute uvcg_uncompressed_bma_controls = + __CONFIGFS_ATTR(bmaControls, S_IRUGO | S_IWUSR, + uvcg_uncompressed_bma_controls_show, + uvcg_uncompressed_bma_controls_store); + +static struct configfs_attribute *uvcg_uncompressed_attrs[] = { + &uvcg_uncompressed_guid_format.attr, + &uvcg_uncompressed_b_bits_per_pixel.attr, + &uvcg_uncompressed_b_default_frame_index.attr, + &uvcg_uncompressed_b_aspect_ratio_x.attr, + &uvcg_uncompressed_b_aspect_ratio_y.attr, + &uvcg_uncompressed_bm_interface_flags.attr, + &uvcg_uncompressed_bma_controls.attr, + NULL, +}; + +struct config_item_type uvcg_uncompressed_type = { + .ct_item_ops = &uvcg_uncompressed_item_ops, + .ct_group_ops = &uvcg_uncompressed_group_ops, + .ct_attrs = uvcg_uncompressed_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_uncompressed_make(struct config_group *group, + const char *name) +{ + static char guid[] = { + 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 + }; + struct uvcg_uncompressed *h; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + h->desc.bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE; + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_FORMAT_UNCOMPRESSED; + memcpy(h->desc.guidFormat, guid, sizeof(guid)); + h->desc.bBitsPerPixel = 16; + h->desc.bDefaultFrameIndex = 1; + h->desc.bAspectRatioX = 0; + h->desc.bAspectRatioY = 0; + h->desc.bmInterfaceFlags = 0; + h->desc.bCopyProtect = 0; + + h->fmt.type = UVCG_UNCOMPRESSED; + config_group_init_type_name(&h->fmt.group, name, + &uvcg_uncompressed_type); + + return &h->fmt.group; +} + +void uvcg_uncompressed_drop(struct config_group *group, + struct config_item *item) +{ + struct uvcg_uncompressed *h = to_uvcg_uncompressed(item); + + kfree(h); +} + +static struct configfs_group_operations uvcg_uncompressed_grp_ops = { + .make_group = uvcg_uncompressed_make, + .drop_item = uvcg_uncompressed_drop, +}; + +static struct config_item_type uvcg_uncompressed_grp_type = { + .ct_group_ops = &uvcg_uncompressed_grp_ops, + .ct_owner = THIS_MODULE, +}; + +/* streaming/mjpeg/ */ +struct uvcg_mjpeg { + struct uvcg_format fmt; + struct uvc_format_mjpeg desc; +}; + +struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) +{ + return container_of( + container_of(to_config_group(item), struct uvcg_format, group), + struct uvcg_mjpeg, fmt); +} + +CONFIGFS_ATTR_STRUCT(uvcg_mjpeg); +CONFIGFS_ATTR_OPS(uvcg_mjpeg); + +static struct configfs_item_operations uvcg_mjpeg_item_ops = { + .show_attribute = uvcg_mjpeg_attr_show, + .store_attribute = uvcg_mjpeg_attr_store, +}; + +static struct configfs_group_operations uvcg_mjpeg_group_ops = { + .make_item = uvcg_frame_make, + .drop_item = uvcg_frame_drop, +}; + +#define UVCG_MJPEG_ATTR_RO(cname, aname, conv) \ +static ssize_t uvcg_mjpeg_##cname##_show(struct uvcg_mjpeg *u, char *page)\ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_mjpeg_attribute \ + uvcg_mjpeg_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_mjpeg_##cname##_show) + +#define UVCG_MJPEG_ATTR(cname, aname, conv) \ +static ssize_t uvcg_mjpeg_##cname##_show(struct uvcg_mjpeg *u, char *page)\ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static ssize_t \ +uvcg_mjpeg_##cname##_store(struct uvcg_mjpeg *u, \ + const char *page, size_t len) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex; \ + int ret; \ + u8 num; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\ + opts = to_f_uvc_opts(opts_item); \ + \ + mutex_lock(&opts->lock); \ + if (u->fmt.linked || opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou8(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > 255) { \ + ret = -EINVAL; \ + goto end; \ + } \ + u->desc.aname = num; \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + mutex_unlock(su_mutex); \ + return ret; \ +} \ + \ +static struct uvcg_mjpeg_attribute \ + uvcg_mjpeg_##cname = \ + __CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR, \ + uvcg_mjpeg_##cname##_show, \ + uvcg_mjpeg_##cname##_store) + +#define identity_conv(x) (x) + +UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex, + identity_conv); +UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, identity_conv); +UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv); +UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv); +UVCG_MJPEG_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv); + +#undef identity_conv + +#undef UVCG_MJPEG_ATTR +#undef UVCG_MJPEG_ATTR_RO + +static inline ssize_t +uvcg_mjpeg_bma_controls_show(struct uvcg_mjpeg *unc, char *page) +{ + return uvcg_format_bma_controls_show(&unc->fmt, page); +} + +static inline ssize_t +uvcg_mjpeg_bma_controls_store(struct uvcg_mjpeg *ch, + const char *page, size_t len) +{ + return uvcg_format_bma_controls_store(&ch->fmt, page, len); +} + +static struct uvcg_mjpeg_attribute uvcg_mjpeg_bma_controls = + __CONFIGFS_ATTR(bmaControls, S_IRUGO | S_IWUSR, + uvcg_mjpeg_bma_controls_show, + uvcg_mjpeg_bma_controls_store); + +static struct configfs_attribute *uvcg_mjpeg_attrs[] = { + &uvcg_mjpeg_b_default_frame_index.attr, + &uvcg_mjpeg_bm_flags.attr, + &uvcg_mjpeg_b_aspect_ratio_x.attr, + &uvcg_mjpeg_b_aspect_ratio_y.attr, + &uvcg_mjpeg_bm_interface_flags.attr, + &uvcg_mjpeg_bma_controls.attr, + NULL, +}; + +struct config_item_type uvcg_mjpeg_type = { + .ct_item_ops = &uvcg_mjpeg_item_ops, + .ct_group_ops = &uvcg_mjpeg_group_ops, + .ct_attrs = uvcg_mjpeg_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_mjpeg_make(struct config_group *group, + const char *name) +{ + struct uvcg_mjpeg *h; + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return ERR_CAST(h); + + h->desc.bLength = UVC_DT_FORMAT_MJPEG_SIZE; + h->desc.bDescriptorType = USB_DT_CS_INTERFACE; + h->desc.bDescriptorSubType = UVC_VS_FORMAT_MJPEG; + h->desc.bDefaultFrameIndex = 1; + h->desc.bAspectRatioX = 0; + h->desc.bAspectRatioY = 0; + h->desc.bmInterfaceFlags = 0; + h->desc.bCopyProtect = 0; + + h->fmt.type = UVCG_MJPEG; + config_group_init_type_name(&h->fmt.group, name, + &uvcg_mjpeg_type); + + return &h->fmt.group; +} + +void uvcg_mjpeg_drop(struct config_group *group, + struct config_item *item) +{ + struct uvcg_mjpeg *h = to_uvcg_mjpeg(item); + + kfree(h); +} + +static struct configfs_group_operations uvcg_mjpeg_grp_ops = { + .make_group = uvcg_mjpeg_make, + .drop_item = uvcg_mjpeg_drop, +}; + +static struct config_item_type uvcg_mjpeg_grp_type = { + .ct_group_ops = &uvcg_mjpeg_grp_ops, + .ct_owner = THIS_MODULE, +}; + +/* streaming/color_matching/default */ +static struct uvcg_default_color_matching { + struct config_group group; +} uvcg_default_color_matching; + +static inline struct uvcg_default_color_matching +*to_uvcg_default_color_matching(struct config_item *item) +{ + return container_of(to_config_group(item), + struct uvcg_default_color_matching, group); +} + +CONFIGFS_ATTR_STRUCT(uvcg_default_color_matching); +CONFIGFS_ATTR_OPS_RO(uvcg_default_color_matching); + +static struct configfs_item_operations uvcg_default_color_matching_item_ops = { + .show_attribute = uvcg_default_color_matching_attr_show, +}; + +#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv) \ +static ssize_t uvcg_default_color_matching_##cname##_show( \ + struct uvcg_default_color_matching *dc, char *page) \ +{ \ + struct f_uvc_opts *opts; \ + struct config_item *opts_item; \ + struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \ + struct uvc_color_matching_descriptor *cd; \ + int result; \ + \ + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ + \ + opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent; \ + opts = to_f_uvc_opts(opts_item); \ + cd = &opts->uvc_color_matching; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(cd->aname)); \ + mutex_unlock(&opts->lock); \ + \ + mutex_unlock(su_mutex); \ + return result; \ +} \ + \ +static struct uvcg_default_color_matching_attribute \ + uvcg_default_color_matching_##cname = \ + __CONFIGFS_ATTR_RO(aname, uvcg_default_color_matching_##cname##_show) + +#define identity_conv(x) (x) + +UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, + identity_conv); +UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_transfer_characteristics, + bTransferCharacteristics, identity_conv); +UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, + identity_conv); + +#undef identity_conv + +#undef UVCG_DEFAULT_COLOR_MATCHING_ATTR + +static struct configfs_attribute *uvcg_default_color_matching_attrs[] = { + &uvcg_default_color_matching_b_color_primaries.attr, + &uvcg_default_color_matching_b_transfer_characteristics.attr, + &uvcg_default_color_matching_b_matrix_coefficients.attr, + NULL, +}; + +static struct config_item_type uvcg_default_color_matching_type = { + .ct_item_ops = &uvcg_default_color_matching_item_ops, + .ct_attrs = uvcg_default_color_matching_attrs, + .ct_owner = THIS_MODULE, +}; + +/* struct uvcg_color_matching {}; */ + +static struct config_group *uvcg_color_matching_default_groups[] = { + &uvcg_default_color_matching.group, + NULL, +}; + +/* streaming/color_matching */ +static struct uvcg_color_matching_grp { + struct config_group group; +} uvcg_color_matching_grp; + +static struct config_item_type uvcg_color_matching_grp_type = { + .ct_owner = THIS_MODULE, +}; + +/* streaming/class/{fs|hs|ss} */ +static struct uvcg_streaming_class { + struct config_group group; +} uvcg_streaming_class_fs, uvcg_streaming_class_hs, uvcg_streaming_class_ss; + + +static inline struct uvc_descriptor_header +***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o) +{ + struct uvcg_streaming_class *cl = container_of(to_config_group(i), + struct uvcg_streaming_class, group); + + if (cl == &uvcg_streaming_class_fs) + return &o->uvc_fs_streaming_cls; + + if (cl == &uvcg_streaming_class_hs) + return &o->uvc_hs_streaming_cls; + + if (cl == &uvcg_streaming_class_ss) + return &o->uvc_ss_streaming_cls; + + return NULL; +} + +enum uvcg_strm_type { + UVCG_HEADER = 0, + UVCG_FORMAT, + UVCG_FRAME +}; + +static int __uvcg_iter_strm_cls(void *priv1, void *priv2, void *priv3, + int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type)) +{ + struct uvcg_streaming_header *h = priv1; + struct uvcg_format_ptr *f; + struct config_group *grp; + struct config_item *item; + struct uvcg_frame *frm; + int ret, i, j; + + if (!fun) + return -EINVAL; + + i = j = 0; + ret = fun(h, priv2, priv3, 0, UVCG_HEADER); + if (ret) + return ret; + list_for_each_entry(f, &h->formats, entry) { + ret = fun(f->fmt, priv2, priv3, i++, UVCG_FORMAT); + if (ret) + return ret; + grp = &f->fmt->group; + list_for_each_entry(item, &grp->cg_children, ci_entry) { + frm = to_uvcg_frame(item); + ret = fun(frm, priv2, priv3, j++, UVCG_FRAME); + if (ret) + return ret; + } + } + + return ret; +} + +static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, + enum uvcg_strm_type type) +{ + size_t *size = priv2; + size_t *count = priv3; + + switch (type) { + case UVCG_HEADER: { + struct uvcg_streaming_header *h = priv1; + + *size += sizeof(h->desc); + /* bmaControls */ + *size += h->num_fmt * UVCG_STREAMING_CONTROL_SIZE; + } + break; + case UVCG_FORMAT: { + struct uvcg_format *fmt = priv1; + + if (fmt->type == UVCG_UNCOMPRESSED) { + struct uvcg_uncompressed *u = + container_of(fmt, struct uvcg_uncompressed, + fmt); + + *size += sizeof(u->desc); + } else if (fmt->type == UVCG_MJPEG) { + struct uvcg_mjpeg *m = + container_of(fmt, struct uvcg_mjpeg, fmt); + + *size += sizeof(m->desc); + } else { + return -EINVAL; + } + } + break; + case UVCG_FRAME: { + struct uvcg_frame *frm = priv1; + int sz = sizeof(frm->dw_frame_interval); + + *size += sizeof(frm->frame); + *size += frm->frame.b_frame_interval_type * sz; + } + break; + } + + ++*count; + + return 0; +} + +static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, + enum uvcg_strm_type type) +{ + void **dest = priv2; + struct uvc_descriptor_header ***array = priv3; + size_t sz; + + **array = *dest; + ++*array; + + switch (type) { + case UVCG_HEADER: { + struct uvc_input_header_descriptor *ihdr = *dest; + struct uvcg_streaming_header *h = priv1; + struct uvcg_format_ptr *f; + + memcpy(*dest, &h->desc, sizeof(h->desc)); + *dest += sizeof(h->desc); + sz = UVCG_STREAMING_CONTROL_SIZE; + list_for_each_entry(f, &h->formats, entry) { + memcpy(*dest, f->fmt->bmaControls, sz); + *dest += sz; + } + ihdr->bLength = sizeof(h->desc) + h->num_fmt * sz; + ihdr->bNumFormats = h->num_fmt; + } + break; + case UVCG_FORMAT: { + struct uvcg_format *fmt = priv1; + + if (fmt->type == UVCG_UNCOMPRESSED) { + struct uvc_format_uncompressed *unc = *dest; + struct uvcg_uncompressed *u = + container_of(fmt, struct uvcg_uncompressed, + fmt); + + memcpy(*dest, &u->desc, sizeof(u->desc)); + *dest += sizeof(u->desc); + unc->bNumFrameDescriptors = fmt->num_frames; + unc->bFormatIndex = n + 1; + } else if (fmt->type == UVCG_MJPEG) { + struct uvc_format_mjpeg *mjp = *dest; + struct uvcg_mjpeg *m = + container_of(fmt, struct uvcg_mjpeg, fmt); + + memcpy(*dest, &m->desc, sizeof(m->desc)); + *dest += sizeof(m->desc); + mjp->bNumFrameDescriptors = fmt->num_frames; + mjp->bFormatIndex = n + 1; + } else { + return -EINVAL; + } + } + break; + case UVCG_FRAME: { + struct uvcg_frame *frm = priv1; + struct uvc_descriptor_header *h = *dest; + + sz = sizeof(frm->frame); + memcpy(*dest, &frm->frame, sz); + *dest += sz; + sz = frm->frame.b_frame_interval_type * + sizeof(*frm->dw_frame_interval); + memcpy(*dest, frm->dw_frame_interval, sz); + *dest += sz; + if (frm->fmt_type == UVCG_UNCOMPRESSED) + h->bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE( + frm->frame.b_frame_interval_type); + else if (frm->fmt_type == UVCG_MJPEG) + h->bLength = UVC_DT_FRAME_MJPEG_SIZE( + frm->frame.b_frame_interval_type); + } + break; + } + + return 0; +} + +static int uvcg_streaming_class_allow_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *streaming, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header ***class_array, **cl_arr; + struct uvcg_streaming_header *target_hdr; + void *data; + size_t size = 0, count = 0; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + streaming = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(streaming), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(streaming->ci_parent); + + mutex_lock(&opts->lock); + + class_array = __uvcg_get_stream_class_arr(src, opts); + if (!class_array || *class_array || opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_streaming_header(target); + ret = __uvcg_iter_strm_cls(target_hdr, &size, &count, __uvcg_cnt_strm); + if (ret) + goto unlock; + + count += 2; /* color_matching, NULL */ + *class_array = kcalloc(count, sizeof(void *), GFP_KERNEL); + if (!*class_array) { + ret = PTR_ERR(*class_array); + goto unlock; + } + + data = kzalloc(size, GFP_KERNEL); + if (!data) { + kfree(*class_array); + *class_array = NULL; + ret = PTR_ERR(data); + goto unlock; + } + cl_arr = *class_array; + ret = __uvcg_iter_strm_cls(target_hdr, &data, &cl_arr, + __uvcg_fill_strm); + if (ret) { + kfree(*class_array); + *class_array = NULL; + kfree(data); + goto unlock; + } + *cl_arr = (struct uvc_descriptor_header *)&opts->uvc_color_matching; + + ++target_hdr->linked; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + mutex_unlock(su_mutex); + return ret; +} + +static int uvcg_streaming_class_drop_link(struct config_item *src, + struct config_item *target) +{ + struct config_item *streaming, *header; + struct f_uvc_opts *opts; + struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; + struct uvc_descriptor_header ***class_array; + struct uvcg_streaming_header *target_hdr; + int ret = -EINVAL; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + streaming = src->ci_parent->ci_parent; + header = config_group_find_item(to_config_group(streaming), "header"); + if (!header || target->ci_parent != header) + goto out; + + opts = to_f_uvc_opts(streaming->ci_parent); + + mutex_lock(&opts->lock); + + class_array = __uvcg_get_stream_class_arr(src, opts); + if (!class_array || !*class_array) + goto unlock; + + if (opts->refcnt) { + ret = -EBUSY; + goto unlock; + } + + target_hdr = to_uvcg_streaming_header(target); + --target_hdr->linked; + kfree(**class_array); + kfree(*class_array); + *class_array = NULL; + ret = 0; + +unlock: + mutex_unlock(&opts->lock); +out: + mutex_unlock(su_mutex); + return ret; +} + +static struct configfs_item_operations uvcg_streaming_class_item_ops = { + .allow_link = uvcg_streaming_class_allow_link, + .drop_link = uvcg_streaming_class_drop_link, +}; + +static struct config_item_type uvcg_streaming_class_type = { + .ct_item_ops = &uvcg_streaming_class_item_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_streaming_class_default_groups[] = { + &uvcg_streaming_class_fs.group, + &uvcg_streaming_class_hs.group, + &uvcg_streaming_class_ss.group, + NULL, +}; + +/* streaming/class */ +static struct uvcg_streaming_class_grp { + struct config_group group; +} uvcg_streaming_class_grp; + +static struct config_item_type uvcg_streaming_class_grp_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_streaming_default_groups[] = { + &uvcg_streaming_header_grp.group, + &uvcg_uncompressed_grp.group, + &uvcg_mjpeg_grp.group, + &uvcg_color_matching_grp.group, + &uvcg_streaming_class_grp.group, + NULL, +}; + +/* streaming */ +static struct uvcg_streaming_grp { + struct config_group group; +} uvcg_streaming_grp; + +static struct config_item_type uvcg_streaming_grp_type = { + .ct_owner = THIS_MODULE, +}; + +static struct config_group *uvcg_default_groups[] = { + &uvcg_control_grp.group, + &uvcg_streaming_grp.group, + NULL, +}; + +static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uvc_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_uvc_opts); +CONFIGFS_ATTR_OPS(f_uvc_opts); + +static void uvc_attr_release(struct config_item *item) +{ + struct f_uvc_opts *opts = to_f_uvc_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations uvc_item_ops = { + .release = uvc_attr_release, + .show_attribute = f_uvc_opts_attr_show, + .store_attribute = f_uvc_opts_attr_store, +}; + +#define UVCG_OPTS_ATTR(cname, conv, str2u, uxx, vnoc, limit) \ +static ssize_t f_uvc_opts_##cname##_show( \ + struct f_uvc_opts *opts, char *page) \ +{ \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%d\n", conv(opts->cname)); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t \ +f_uvc_opts_##cname##_store(struct f_uvc_opts *opts, \ + const char *page, size_t len) \ +{ \ + int ret; \ + uxx num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = str2u(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + if (num > limit) { \ + ret = -EINVAL; \ + goto end; \ + } \ + opts->cname = vnoc(num); \ + ret = len; \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +static struct f_uvc_opts_attribute \ + f_uvc_opts_attribute_##cname = \ + __CONFIGFS_ATTR(cname, S_IRUGO | S_IWUSR, \ + f_uvc_opts_##cname##_show, \ + f_uvc_opts_##cname##_store) + +#define identity_conv(x) (x) + +UVCG_OPTS_ATTR(streaming_interval, identity_conv, kstrtou8, u8, identity_conv, + 16); +UVCG_OPTS_ATTR(streaming_maxpacket, le16_to_cpu, kstrtou16, u16, le16_to_cpu, + 3072); +UVCG_OPTS_ATTR(streaming_maxburst, identity_conv, kstrtou8, u8, identity_conv, + 15); + +#undef identity_conv + +#undef UVCG_OPTS_ATTR + +static struct configfs_attribute *uvc_attrs[] = { + &f_uvc_opts_attribute_streaming_interval.attr, + &f_uvc_opts_attribute_streaming_maxpacket.attr, + &f_uvc_opts_attribute_streaming_maxburst.attr, + NULL, +}; + +static struct config_item_type uvc_func_type = { + .ct_item_ops = &uvc_item_ops, + .ct_attrs = uvc_attrs, + .ct_owner = THIS_MODULE, +}; + +static inline void uvcg_init_group(struct config_group *g, + struct config_group **default_groups, + const char *name, + struct config_item_type *type) +{ + g->default_groups = default_groups; + config_group_init_type_name(g, name, type); +} + +int uvcg_attach_configfs(struct f_uvc_opts *opts) +{ + config_group_init_type_name(&uvcg_control_header_grp.group, + "header", + &uvcg_control_header_grp_type); + config_group_init_type_name(&uvcg_default_processing.group, + "default", + &uvcg_default_processing_type); + uvcg_init_group(&uvcg_processing_grp.group, + uvcg_processing_default_groups, + "processing", + &uvcg_processing_grp_type); + config_group_init_type_name(&uvcg_default_camera.group, + "default", + &uvcg_default_camera_type); + uvcg_init_group(&uvcg_camera_grp.group, + uvcg_camera_default_groups, + "camera", + &uvcg_camera_grp_type); + config_group_init_type_name(&uvcg_default_output.group, + "default", + &uvcg_default_output_type); + uvcg_init_group(&uvcg_output_grp.group, + uvcg_output_default_groups, + "output", + &uvcg_output_grp_type); + uvcg_init_group(&uvcg_terminal_grp.group, + uvcg_terminal_default_groups, + "terminal", + &uvcg_terminal_grp_type); + config_group_init_type_name(&uvcg_control_class_fs.group, + "fs", + &uvcg_control_class_type); + config_group_init_type_name(&uvcg_control_class_ss.group, + "ss", + &uvcg_control_class_type); + uvcg_init_group(&uvcg_control_class_grp.group, + uvcg_control_class_default_groups, + "class", + &uvcg_control_class_grp_type); + uvcg_init_group(&uvcg_control_grp.group, + uvcg_control_default_groups, + "control", + &uvcg_control_grp_type); + config_group_init_type_name(&uvcg_streaming_header_grp.group, + "header", + &uvcg_streaming_header_grp_type); + config_group_init_type_name(&uvcg_uncompressed_grp.group, + "uncompressed", + &uvcg_uncompressed_grp_type); + config_group_init_type_name(&uvcg_mjpeg_grp.group, + "mjpeg", + &uvcg_mjpeg_grp_type); + config_group_init_type_name(&uvcg_default_color_matching.group, + "default", + &uvcg_default_color_matching_type); + uvcg_init_group(&uvcg_color_matching_grp.group, + uvcg_color_matching_default_groups, + "color_matching", + &uvcg_color_matching_grp_type); + config_group_init_type_name(&uvcg_streaming_class_fs.group, + "fs", + &uvcg_streaming_class_type); + config_group_init_type_name(&uvcg_streaming_class_hs.group, + "hs", + &uvcg_streaming_class_type); + config_group_init_type_name(&uvcg_streaming_class_ss.group, + "ss", + &uvcg_streaming_class_type); + uvcg_init_group(&uvcg_streaming_class_grp.group, + uvcg_streaming_class_default_groups, + "class", + &uvcg_streaming_class_grp_type); + uvcg_init_group(&uvcg_streaming_grp.group, + uvcg_streaming_default_groups, + "streaming", + &uvcg_streaming_grp_type); + uvcg_init_group(&opts->func_inst.group, + uvcg_default_groups, + "", + &uvc_func_type); + return 0; +} diff --git a/drivers/usb/gadget/function/uvc_configfs.h b/drivers/usb/gadget/function/uvc_configfs.h new file mode 100644 index 0000000..085e67b --- /dev/null +++ b/drivers/usb/gadget/function/uvc_configfs.h @@ -0,0 +1,22 @@ +/* + * uvc_configfs.h + * + * Configfs support for the uvc function. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef UVC_CONFIGFS_H +#define UVC_CONFIGFS_H + +struct f_uvc_opts; + +int uvcg_attach_configfs(struct f_uvc_opts *opts); + +#endif /* UVC_CONFIGFS_H */ -- cgit v0.10.2 From 135b3c4304dacd8bb01f1aa6fe4165126bee3812 Mon Sep 17 00:00:00 2001 From: Yunzhi Li Date: Mon, 8 Dec 2014 17:46:26 +0800 Subject: usb: dwc2: platform: add generic PHY framework support Get PHY parameters from devicetree and power off usb PHY during system suspend. Acked-by: Paul Zimmerman Signed-off-by: Yunzhi Li Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index ede69dc..a461406 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3410,8 +3410,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) { struct device *dev = hsotg->dev; struct s3c_hsotg_plat *plat = dev->platform_data; - struct phy *phy; - struct usb_phy *uphy; struct s3c_hsotg_ep *eps; int epnum; int ret; @@ -3421,30 +3419,23 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) hsotg->phyif = GUSBCFG_PHYIF16; /* - * Attempt to find a generic PHY, then look for an old style - * USB PHY, finally fall back to pdata + * If platform probe couldn't find a generic PHY or an old style + * USB PHY, fall back to pdata */ - phy = devm_phy_get(dev, "usb2-phy"); - if (IS_ERR(phy)) { - uphy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); - if (IS_ERR(uphy)) { - /* Fallback for pdata */ - plat = dev_get_platdata(dev); - if (!plat) { - dev_err(dev, - "no platform data or transceiver defined\n"); - return -EPROBE_DEFER; - } - hsotg->plat = plat; - } else - hsotg->uphy = uphy; - } else { - hsotg->phy = phy; + if (IS_ERR_OR_NULL(hsotg->phy) && IS_ERR_OR_NULL(hsotg->uphy)) { + plat = dev_get_platdata(dev); + if (!plat) { + dev_err(dev, + "no platform data or transceiver defined\n"); + return -EPROBE_DEFER; + } + hsotg->plat = plat; + } else if (hsotg->phy) { /* * If using the generic PHY framework, check if the PHY bus * width is 8-bit and set the phyif appropriately. */ - if (phy_get_bus_width(phy) == 8) + if (phy_get_bus_width(hsotg->phy) == 8) hsotg->phyif = GUSBCFG_PHYIF8; } diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 6a795aa..ae095f0 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -155,6 +155,8 @@ static int dwc2_driver_probe(struct platform_device *dev) struct dwc2_core_params defparams; struct dwc2_hsotg *hsotg; struct resource *res; + struct phy *phy; + struct usb_phy *uphy; int retval; int irq; @@ -212,6 +214,24 @@ static int dwc2_driver_probe(struct platform_device *dev) hsotg->dr_mode = of_usb_get_dr_mode(dev->dev.of_node); + /* + * Attempt to find a generic PHY, then look for an old style + * USB PHY + */ + phy = devm_phy_get(&dev->dev, "usb2-phy"); + if (IS_ERR(phy)) { + hsotg->phy = NULL; + uphy = devm_usb_get_phy(&dev->dev, USB_PHY_TYPE_USB2); + if (IS_ERR(uphy)) + hsotg->uphy = NULL; + else + hsotg->uphy = uphy; + } else { + hsotg->phy = phy; + phy_power_on(hsotg->phy); + phy_init(hsotg->phy); + } + spin_lock_init(&hsotg->lock); mutex_init(&hsotg->init_mutex); retval = dwc2_gadget_init(hsotg, irq); @@ -231,8 +251,15 @@ static int __maybe_unused dwc2_suspend(struct device *dev) struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); int ret = 0; - if (dwc2_is_device_mode(dwc2)) + if (dwc2_is_device_mode(dwc2)) { ret = s3c_hsotg_suspend(dwc2); + } else { + if (dwc2->lx_state == DWC2_L0) + return 0; + phy_exit(dwc2->phy); + phy_power_off(dwc2->phy); + + } return ret; } @@ -241,8 +268,13 @@ static int __maybe_unused dwc2_resume(struct device *dev) struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev); int ret = 0; - if (dwc2_is_device_mode(dwc2)) + if (dwc2_is_device_mode(dwc2)) { ret = s3c_hsotg_resume(dwc2); + } else { + phy_power_on(dwc2->phy); + phy_init(dwc2->phy); + + } return ret; } -- cgit v0.10.2 From 5e841efef7543209c1d62526311fbbcf85f3613d Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 25 Nov 2014 18:55:57 +0300 Subject: usb: gadget: udc: clean up a printk We already know what "value" is, so there is no need to check. It puzzles static checkers to have the unneeded condition. Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index ff67cea..ebf82bd 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -881,8 +881,8 @@ static int ep_set_halt(struct bdc_ep *ep, u32 value) ret = bdc_ep_set_stall(bdc, ep->ep_num); if (ret) - dev_err(bdc->dev, "failed to %s STALL on %s\n", - value ? "set" : "clear", ep->name); + dev_err(bdc->dev, "failed to set STALL on %s\n", + ep->name); else ep->flags |= BDC_EP_STALL; } else { @@ -890,8 +890,8 @@ static int ep_set_halt(struct bdc_ep *ep, u32 value) dev_dbg(bdc->dev, "Before Clear\n"); ret = bdc_ep_clear_stall(bdc, ep->ep_num); if (ret) - dev_err(bdc->dev, "failed to %s STALL on %s\n", - value ? "set" : "clear", ep->name); + dev_err(bdc->dev, "failed to clear STALL on %s\n", + ep->name); else ep->flags &= ~BDC_EP_STALL; dev_dbg(bdc->dev, "After Clear\n"); -- cgit v0.10.2 From d71b0d7764a7b018bf73ae44f67dc8545e614eef Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 25 Nov 2014 18:56:22 +0300 Subject: usb: gadget: udc: remove bogus NULL check "ep" isn't NULL here, and static checkers complain because we dereferenced it on the previous line. Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/bdc/bdc_ep.c b/drivers/usb/gadget/udc/bdc/bdc_ep.c index ebf82bd..fdc0e9f 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_ep.c +++ b/drivers/usb/gadget/udc/bdc/bdc_ep.c @@ -719,7 +719,7 @@ static int ep_queue(struct bdc_ep *ep, struct bdc_req *req) int ret = 0; bdc = ep->bdc; - if (!req || !ep || !ep->usb_ep.desc) + if (!req || !ep->usb_ep.desc) return -EINVAL; req->usb_req.actual = 0; -- cgit v0.10.2 From 90f9e53345d307f53b040e7a50193f20f88ab87a Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Wed, 26 Nov 2014 07:00:45 +0100 Subject: usb: gadget: fix platform_no_drv_owner.cocci warnings Remove .owner field if calls are used which set it automatically Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci Signed-off-by: Julia Lawall Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index c6dfef8..5c8f4ef 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -521,7 +521,6 @@ static int bdc_remove(struct platform_device *pdev) static struct platform_driver bdc_driver = { .driver = { .name = BRCM_BDC_NAME, - .owner = THIS_MODULE }, .probe = bdc_probe, .remove = bdc_remove, -- cgit v0.10.2 From 625763d101c7aa706b35f052cd978a61f1ef26bc Mon Sep 17 00:00:00 2001 From: Xuebing Wang Date: Wed, 10 Dec 2014 16:28:14 +0800 Subject: usb: gadget: f_uac1: configure endpoint before using it UAC1 forget to do it. Signed-off-by: Xuebing Wang Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index f7b2032..380b137 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -584,6 +584,7 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (intf == 1) { if (alt == 1) { + config_ep_by_speed(cdev->gadget, f, out_ep); usb_ep_enable(out_ep); out_ep->driver_data = audio; audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); -- cgit v0.10.2 From 8d252db174ab27485126f540f368149e3c875e5a Mon Sep 17 00:00:00 2001 From: Xuebing Wang Date: Wed, 10 Dec 2014 16:28:15 +0800 Subject: usb: gadget: f_uac1: update Class-Specific AudioControl Interface Header Descriptor Update this according to USB Audio Class 1.0 spec. This fixes the Windows 7 detection issue. Cc: Rafael Brune Signed-off-by: Xuebing Wang Signed-off-by: Fabio Estevam Signed-off-by: Peter Chen (Fixed some code style issues) Tested-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index 380b137..d8b4af6 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -31,7 +31,7 @@ static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); */ #define F_AUDIO_AC_INTERFACE 0 #define F_AUDIO_AS_INTERFACE 1 -#define F_AUDIO_NUM_INTERFACES 2 +#define F_AUDIO_NUM_INTERFACES 1 /* B.3.1 Standard AC Interface Descriptor */ static struct usb_interface_descriptor ac_interface_desc = { @@ -42,14 +42,18 @@ static struct usb_interface_descriptor ac_interface_desc = { .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, }; -DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); +/* + * The number of AudioStreaming and MIDIStreaming interfaces + * in the Audio Interface Collection + */ +DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); #define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) /* 1 input terminal, 1 output terminal and 1 feature unit */ #define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \ + UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0)) /* B.3.2 Class-Specific AC Interface Descriptor */ -static struct uac1_ac_header_descriptor_2 ac_header_desc = { +static struct uac1_ac_header_descriptor_1 ac_header_desc = { .bLength = UAC_DT_AC_HEADER_LENGTH, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_HEADER, @@ -57,8 +61,8 @@ static struct uac1_ac_header_descriptor_2 ac_header_desc = { .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), .bInCollection = F_AUDIO_NUM_INTERFACES, .baInterfaceNr = { - [0] = F_AUDIO_AC_INTERFACE, - [1] = F_AUDIO_AS_INTERFACE, + /* Interface number of the first AudioStream interface */ + [0] = 1, } }; -- cgit v0.10.2 From 9f56ce075dd83d6b69e26d4d4fcf49ec703119cf Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire Date: Tue, 23 Dec 2014 19:50:14 +0100 Subject: usb: gadget: cleanup on stack DECLARE_COMPLETIONs fixups for incorrect use of DECLARE_COMPLETION. see also commit 6e9a4738 ("completions: lockdep annotate on stack completions") patch is against 3.18.0 linux-next This was only code reviewed and compile tested Signed-off-by: Nicholas Mc Guire Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/fsl_qe_udc.c b/drivers/usb/gadget/udc/fsl_qe_udc.c index 795c99c..e0822f1 100644 --- a/drivers/usb/gadget/udc/fsl_qe_udc.c +++ b/drivers/usb/gadget/udc/fsl_qe_udc.c @@ -2630,7 +2630,7 @@ static int qe_udc_remove(struct platform_device *ofdev) struct qe_udc *udc = platform_get_drvdata(ofdev); struct qe_ep *ep; unsigned int size; - DECLARE_COMPLETION(done); + DECLARE_COMPLETION_ONSTACK(done); usb_del_gadget_udc(&udc->gadget); diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 2df8074..c3830ad 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2529,7 +2529,7 @@ static int __exit fsl_udc_remove(struct platform_device *pdev) struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev); - DECLARE_COMPLETION(done); + DECLARE_COMPLETION_ONSTACK(done); if (!udc_controller) return -ENODEV; -- cgit v0.10.2 From 48ac1e57665fa351b2bde534fd64cce2e1b3a4a1 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Wed, 5 Nov 2014 01:48:48 +0300 Subject: usb: renesas_usbhs: fix platform init error message There is a typo ("prove" instead of "probe") in the error message printed when the platform initialization fails. Replace that word with more fitting "init". Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index a706fc4..76d14f5 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -615,7 +615,7 @@ static int usbhs_probe(struct platform_device *pdev) */ ret = usbhs_platform_call(priv, hardware_init, pdev); if (ret < 0) { - dev_err(&pdev->dev, "platform prove failed.\n"); + dev_err(&pdev->dev, "platform init failed.\n"); goto probe_end_mod_exit; } -- cgit v0.10.2 From 906641980c3ae4e4175268168897ebdc7314c73c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:46 +0100 Subject: usb: gadget: udc: net2280: Remove obsolete module param use_dma_chaining use_dma_chaining module parameter was designed to avoid creating one irq per package on a group of packages (with the help of the driver's flag no_interrupt). Unfortunately, when this parameter is enabled, the driver fails to work on both net2280 and 3380 chips. This patch removes this parameter, which was disabled by default. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index d6411e0..08c16ee 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -12,11 +12,7 @@ * the Mass Storage, Serial, and Ethernet/RNDIS gadget drivers * as well as Gadget Zero and Gadgetfs. * - * DMA is enabled by default. Drivers using transfer queues might use - * DMA chaining to remove IRQ latencies between transfers. (Except when - * short OUT transfers happen.) Drivers can use the req->no_interrupt - * hint to completely eliminate some IRQs, if a later IRQ is guaranteed - * and DMA chaining is enabled. + * DMA is enabled by default. * * MSI is enabled by default. The legacy IRQ is used if MSI couldn't * be enabled. @@ -85,7 +81,6 @@ static const char *const ep_name[] = { }; /* use_dma -- general goodness, fewer interrupts, less cpu load (vs PIO) - * use_dma_chaining -- dma descriptor queueing gives even more irq reduction * * The net2280 DMA engines are not tightly integrated with their FIFOs; * not all cases are (yet) handled well in this driver or the silicon. @@ -93,12 +88,10 @@ static const char *const ep_name[] = { * These two parameters let you use PIO or more aggressive DMA. */ static bool use_dma = true; -static bool use_dma_chaining; static bool use_msi = true; /* "modprobe net2280 use_dma=n" etc */ module_param(use_dma, bool, 0444); -module_param(use_dma_chaining, bool, 0444); module_param(use_msi, bool, 0444); /* mode 0 == ep-{a,b,c,d} 1K fifo each @@ -202,15 +195,6 @@ net2280_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) /* set speed-dependent max packet; may kick in high bandwidth */ set_max_speed(ep, max); - /* FIFO lines can't go to different packets. PIO is ok, so - * use it instead of troublesome (non-bulk) multi-packet DMA. - */ - if (ep->dma && (max % 4) != 0 && use_dma_chaining) { - ep_dbg(ep->dev, "%s, no dma for maxpacket %d\n", - ep->ep.name, ep->ep.maxpacket); - ep->dma = NULL; - } - /* set type, direction, address; reset fifo counters */ writel(BIT(FIFO_FLUSH), &ep->regs->ep_stat); tmp = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK); @@ -747,8 +731,7 @@ static void fill_dma_desc(struct net2280_ep *ep, req->valid = valid; if (valid) dmacount |= BIT(VALID_BIT); - if (likely(!req->req.no_interrupt || !use_dma_chaining)) - dmacount |= BIT(DMA_DONE_INTERRUPT_ENABLE); + dmacount |= BIT(DMA_DONE_INTERRUPT_ENABLE); /* td->dmadesc = previously set by caller */ td->dmaaddr = cpu_to_le32 (req->req.dma); @@ -862,8 +845,7 @@ static void start_dma(struct net2280_ep *ep, struct net2280_request *req) req->td->dmadesc = cpu_to_le32 (ep->td_dma); fill_dma_desc(ep, req, 1); - if (!use_dma_chaining) - req->td->dmacount |= cpu_to_le32(BIT(END_OF_CHAIN)); + req->td->dmacount |= cpu_to_le32(BIT(END_OF_CHAIN)); start_queue(ep, tmp, req->td_dma); } @@ -1150,64 +1132,12 @@ static void scan_dma_completions(struct net2280_ep *ep) static void restart_dma(struct net2280_ep *ep) { struct net2280_request *req; - u32 dmactl = dmactl_default; if (ep->stopped) return; req = list_entry(ep->queue.next, struct net2280_request, queue); - if (!use_dma_chaining) { - start_dma(ep, req); - return; - } - - /* the 2280 will be processing the queue unless queue hiccups after - * the previous transfer: - * IN: wanted automagic zlp, head doesn't (or vice versa) - * DMA_FIFO_VALIDATE doesn't init from dma descriptors. - * OUT: was "usb-short", we must restart. - */ - if (ep->is_in && !req->valid) { - struct net2280_request *entry, *prev = NULL; - int reqmode, done = 0; - - ep_dbg(ep->dev, "%s dma hiccup td %p\n", ep->ep.name, req->td); - ep->in_fifo_validate = likely(req->req.zero || - (req->req.length % ep->ep.maxpacket) != 0); - if (ep->in_fifo_validate) - dmactl |= BIT(DMA_FIFO_VALIDATE); - list_for_each_entry(entry, &ep->queue, queue) { - __le32 dmacount; - - if (entry == req) - continue; - dmacount = entry->td->dmacount; - if (!done) { - reqmode = likely(entry->req.zero || - (entry->req.length % ep->ep.maxpacket)); - if (reqmode == ep->in_fifo_validate) { - entry->valid = 1; - dmacount |= valid_bit; - entry->td->dmacount = dmacount; - prev = entry; - continue; - } else { - /* force a hiccup */ - prev->td->dmacount |= dma_done_ie; - done = 1; - } - } - - /* walk the rest of the queue so unlinks behave */ - entry->valid = 0; - dmacount &= ~valid_bit; - entry->td->dmacount = dmacount; - prev = entry; - } - } - - writel(0, &ep->dma->dmactl); - start_queue(ep, dmactl, req->td_dma); + start_dma(ep, req); } static void abort_dma_228x(struct net2280_ep *ep) @@ -1306,25 +1236,6 @@ static int net2280_dequeue(struct usb_ep *_ep, struct usb_request *_req) done(ep, req, -ECONNRESET); } req = NULL; - - /* patch up hardware chaining data */ - } else if (ep->dma && use_dma_chaining) { - if (req->queue.prev == ep->queue.next) { - writel(le32_to_cpu(req->td->dmadesc), - &ep->dma->dmadesc); - if (req->td->dmacount & dma_done_ie) - writel(readl(&ep->dma->dmacount) | - le32_to_cpu(dma_done_ie), - &ep->dma->dmacount); - } else { - struct net2280_request *prev; - - prev = list_entry(req->queue.prev, - struct net2280_request, queue); - prev->td->dmadesc = req->td->dmadesc; - if (req->td->dmacount & dma_done_ie) - prev->td->dmacount |= dma_done_ie; - } } if (req) @@ -1609,9 +1520,7 @@ static ssize_t registers_show(struct device *_dev, "pci irqenb0 %02x irqenb1 %08x " "irqstat0 %04x irqstat1 %08x\n", driver_name, dev->chiprev, - use_dma - ? (use_dma_chaining ? "chaining" : "enabled") - : "disabled", + use_dma ? "enabled" : "disabled", readl(&dev->regs->devinit), readl(&dev->regs->fifoctl), s, @@ -3423,17 +3332,12 @@ static void handle_stat1_irqs(struct net2280 *dev, u32 stat) continue; } - /* chaining should stop on abort, short OUT from fifo, - * or (stat0 codepath) short OUT transfer. - */ - if (!use_dma_chaining) { - if (!(tmp & BIT(DMA_TRANSACTION_DONE_INTERRUPT))) { - ep_dbg(ep->dev, "%s no xact done? %08x\n", - ep->ep.name, tmp); - continue; - } - stop_dma(ep->dma); + if (!(tmp & BIT(DMA_TRANSACTION_DONE_INTERRUPT))) { + ep_dbg(ep->dev, "%s no xact done? %08x\n", + ep->ep.name, tmp); + continue; } + stop_dma(ep->dma); /* OUT transfers terminate when the data from the * host is in our memory. Process whatever's done. @@ -3448,30 +3352,9 @@ static void handle_stat1_irqs(struct net2280 *dev, u32 stat) scan_dma_completions(ep); /* disable dma on inactive queues; else maybe restart */ - if (list_empty(&ep->queue)) { - if (use_dma_chaining) - stop_dma(ep->dma); - } else { + if (!list_empty(&ep->queue)) { tmp = readl(&dma->dmactl); - if (!use_dma_chaining || (tmp & BIT(DMA_ENABLE)) == 0) - restart_dma(ep); - else if (ep->is_in && use_dma_chaining) { - struct net2280_request *req; - __le32 dmacount; - - /* the descriptor at the head of the chain - * may still have VALID_BIT clear; that's - * used to trigger changing DMA_FIFO_VALIDATE - * (affects automagic zlp writes). - */ - req = list_entry(ep->queue.next, - struct net2280_request, queue); - dmacount = req->td->dmacount; - dmacount &= cpu_to_le32(BIT(VALID_BIT) | - DMA_BYTE_COUNT_MASK); - if (dmacount && (dmacount & valid_bit) == 0) - restart_dma(ep); - } + restart_dma(ep); } ep->irqs++; } @@ -3581,9 +3464,6 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) void __iomem *base = NULL; int retval, i; - if (!use_dma) - use_dma_chaining = 0; - /* alloc, and start init */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (dev == NULL) { @@ -3742,8 +3622,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) ep_info(dev, "irq %d, pci mem %p, chip rev %04x\n", pdev->irq, base, dev->chiprev); ep_info(dev, "version: " DRIVER_VERSION "; dma %s %s\n", - use_dma ? (use_dma_chaining ? "chaining" : "enabled") - : "disabled", + use_dma ? "enabled" : "disabled", dev->enhanced_mode ? "enhanced mode" : "legacy mode"); retval = device_create_file(&pdev->dev, &dev_attr_registers); if (retval) -- cgit v0.10.2 From 7a74c48172d8fad6da0f9be5d353cf322305af07 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:47 +0100 Subject: usb: gadget: udc: net2280: remove full_speed module parameter This patch removes the full_speed parameter used force full-speed operation. It was designed exclusively for testing purposes, and there is no reason to maintain this in a production kernel. Reverts: 2f0760774711c957c395b31131b848043af98edf Suggested-by: Alan Stern Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 08c16ee..27b94c6 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -113,11 +113,6 @@ static bool enable_suspend; /* "modprobe net2280 enable_suspend=1" etc */ module_param(enable_suspend, bool, 0444); -/* force full-speed operation */ -static bool full_speed; -module_param(full_speed, bool, 0444); -MODULE_PARM_DESC(full_speed, "force full-speed mode -- for testing only!"); - #define DIR_STRING(bAddress) (((bAddress) & USB_DIR_IN) ? "in" : "out") static char *type_string(u8 bmAttributes) @@ -2291,11 +2286,7 @@ static int net2280_start(struct usb_gadget *_gadget, if (retval) goto err_func; - /* Enable force-full-speed testing mode, if desired */ - if (full_speed && (dev->quirks & PLX_LEGACY)) - writel(BIT(FORCE_FULL_SPEED_MODE), &dev->usb->xcvrdiag); - - /* ... then enable host detection and ep0; and we're ready + /* enable host detection and ep0; and we're ready * for set_configuration as well as eventual disconnect. */ net2280_led_active(dev, 1); @@ -2353,10 +2344,6 @@ static int net2280_stop(struct usb_gadget *_gadget) net2280_led_active(dev, 0); - /* Disable full-speed test mode */ - if (dev->quirks & PLX_LEGACY) - writel(0, &dev->usb->xcvrdiag); - device_remove_file(&dev->pdev->dev, &dev_attr_function); device_remove_file(&dev->pdev->dev, &dev_attr_queues); @@ -3655,9 +3642,6 @@ static void net2280_shutdown(struct pci_dev *pdev) /* disable the pullup so the host will think we're gone */ writel(0, &dev->usb->usbctl); - /* Disable full-speed test mode */ - if (dev->quirks & PLX_LEGACY) - writel(0, &dev->usb->xcvrdiag); } -- cgit v0.10.2 From 9c864c23424fa6a2e23b8e81faa6a100ab01481c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:48 +0100 Subject: usb: gadget: udc: net2280: Remove module parameter use_msi Parameter use_msi was used to enable msi irq on usb338x chips, it was enabled by default. There is no reason to prefer non-msi irq on usb338x, and it falls back to non msi on error. Therefore remove this option. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 27b94c6..fc5c0a2 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -88,11 +88,9 @@ static const char *const ep_name[] = { * These two parameters let you use PIO or more aggressive DMA. */ static bool use_dma = true; -static bool use_msi = true; /* "modprobe net2280 use_dma=n" etc */ module_param(use_dma, bool, 0444); -module_param(use_msi, bool, 0444); /* mode 0 == ep-{a,b,c,d} 1K fifo each * mode 1 == ep-{a,b} 2K fifo each, ep-{c,d} unavailable @@ -3426,7 +3424,7 @@ static void net2280_remove(struct pci_dev *pdev) } if (dev->got_irq) free_irq(pdev->irq, dev); - if (use_msi && dev->quirks & PLX_SUPERSPEED) + if (dev->quirks & PLX_SUPERSPEED) pci_disable_msi(pdev); if (dev->regs) iounmap(dev->regs); @@ -3549,7 +3547,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto done; } - if (use_msi && (dev->quirks & PLX_SUPERSPEED)) + if (dev->quirks & PLX_SUPERSPEED) if (pci_enable_msi(pdev)) ep_err(dev, "Failed to enable MSI mode\n"); -- cgit v0.10.2 From d588ff58c20d8503f789b9cbbed55e796ae6d04e Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:49 +0100 Subject: usb: gadget: udc: net2280: Remove use_dma module parameter use_dma parameter was designed to enable the dma on the chip. It was enabled by default. It comes from the time when the dma was not reliable. Now it has been working ok in production. This patch removes this parameter. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index fc5c0a2..230b8d4 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -80,18 +80,6 @@ static const char *const ep_name[] = { "ep-e", "ep-f", "ep-g", "ep-h", }; -/* use_dma -- general goodness, fewer interrupts, less cpu load (vs PIO) - * - * The net2280 DMA engines are not tightly integrated with their FIFOs; - * not all cases are (yet) handled well in this driver or the silicon. - * Some gadget drivers work better with the dma support here than others. - * These two parameters let you use PIO or more aggressive DMA. - */ -static bool use_dma = true; - -/* "modprobe net2280 use_dma=n" etc */ -module_param(use_dma, bool, 0444); - /* mode 0 == ep-{a,b,c,d} 1K fifo each * mode 1 == ep-{a,b} 2K fifo each, ep-{c,d} unavailable * mode 2 == ep-a 2K fifo, ep-{b,c} 1K each, ep-d unavailable @@ -455,7 +443,7 @@ static int net2280_disable(struct usb_ep *_ep) /* synch memory views with the device */ (void)readl(&ep->cfg->ep_cfg); - if (use_dma && !ep->dma && ep->num >= 1 && ep->num <= 4) + if (!ep->dma && ep->num >= 1 && ep->num <= 4) ep->dma = &ep->dev->dma[ep->num - 1]; spin_unlock_irqrestore(&ep->dev->lock, flags); @@ -1508,12 +1496,11 @@ static ssize_t registers_show(struct device *_dev, /* Main Control Registers */ t = scnprintf(next, size, "%s version " DRIVER_VERSION - ", chiprev %04x, dma %s\n\n" + ", chiprev %04x\n\n" "devinit %03x fifoctl %08x gadget '%s'\n" "pci irqenb0 %02x irqenb1 %08x " "irqstat0 %04x irqstat1 %08x\n", driver_name, dev->chiprev, - use_dma ? "enabled" : "disabled", readl(&dev->regs->devinit), readl(&dev->regs->fifoctl), s, @@ -1995,10 +1982,6 @@ static void usb_reset(struct net2280 *dev) static void usb_reinit_228x(struct net2280 *dev) { u32 tmp; - int init_dma; - - /* use_dma changes are ignored till next device re-init */ - init_dma = use_dma; /* basic endpoint init */ for (tmp = 0; tmp < 7; tmp++) { @@ -2010,8 +1993,7 @@ static void usb_reinit_228x(struct net2280 *dev) if (tmp > 0 && tmp <= 4) { ep->fifo_size = 1024; - if (init_dma) - ep->dma = &dev->dma[tmp - 1]; + ep->dma = &dev->dma[tmp - 1]; } else ep->fifo_size = 64; ep->regs = &dev->epregs[tmp]; @@ -2035,7 +2017,6 @@ static void usb_reinit_228x(struct net2280 *dev) static void usb_reinit_338x(struct net2280 *dev) { - int init_dma; int i; u32 tmp, val; u32 fsmvalue; @@ -2043,9 +2024,6 @@ static void usb_reinit_338x(struct net2280 *dev) static const u32 ep_reg_addr[9] = { 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0xC0 }; - /* use_dma changes are ignored till next device re-init */ - init_dma = use_dma; - /* basic endpoint init */ for (i = 0; i < dev->n_ep; i++) { struct net2280_ep *ep = &dev->ep[i]; @@ -2054,7 +2032,7 @@ static void usb_reinit_338x(struct net2280 *dev) ep->dev = dev; ep->num = i; - if (i > 0 && i <= 4 && init_dma) + if (i > 0 && i <= 4) ep->dma = &dev->dma[i - 1]; if (dev->enhanced_mode) { @@ -3606,8 +3584,7 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) ep_info(dev, "%s\n", driver_desc); ep_info(dev, "irq %d, pci mem %p, chip rev %04x\n", pdev->irq, base, dev->chiprev); - ep_info(dev, "version: " DRIVER_VERSION "; dma %s %s\n", - use_dma ? "enabled" : "disabled", + ep_info(dev, "version: " DRIVER_VERSION "; %s\n", dev->enhanced_mode ? "enhanced mode" : "legacy mode"); retval = device_create_file(&pdev->dev, &dev_attr_registers); if (retval) -- cgit v0.10.2 From cb52c698c6030c88733c87def5c359a09b76bc1e Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:50 +0100 Subject: usb: gadget: udc: net2280: Remove dma_started field Remove dma_started field from net2280_ep structure, since it is not used by any function. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 230b8d4..0340462 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -834,16 +834,12 @@ static void start_dma(struct net2280_ep *ep, struct net2280_request *req) static inline void resume_dma(struct net2280_ep *ep) { writel(readl(&ep->dma->dmactl) | BIT(DMA_ENABLE), &ep->dma->dmactl); - - ep->dma_started = true; } static inline void ep_stop_dma(struct net2280_ep *ep) { writel(readl(&ep->dma->dmactl) & ~BIT(DMA_ENABLE), &ep->dma->dmactl); spin_stop_dma(ep->dma); - - ep->dma_started = false; } static inline void diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index 03f1524..4c53b83 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -100,7 +100,7 @@ struct net2280_ep { dma_addr_t td_dma; /* of dummy */ struct net2280 *dev; unsigned long irqs; - unsigned is_halt:1, dma_started:1; + unsigned is_halt:1; /* analogous to a host-side qh */ struct list_head queue; -- cgit v0.10.2 From 61e72dc65a9a4937d351a4b67386a6bbf70a1146 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:51 +0100 Subject: usb: gadget: udc: net2280: Remove restart_dma inline function definition restart_dma is not used before it is declaration. Therefore we can remove this definition. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 0340462..e8c36db 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1043,8 +1043,6 @@ dma_done(struct net2280_ep *ep, struct net2280_request *req, u32 dmacount, done(ep, req, status); } -static void restart_dma(struct net2280_ep *ep); - static void scan_dma_completions(struct net2280_ep *ep) { /* only look at descriptors that were "naturally" retired, -- cgit v0.10.2 From cf8b1cdebab3c6fb01d37e62e14f9dd8cc99d09b Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:52 +0100 Subject: usb: gadget: udc: net2280: Code cleanout remove ep_stdrsp function ep_stdrsp was only called by handle_stat0_irqs_superspeed and with always the same flags. Remove the function and replace the call by the code inside the function, since it is very simple once the dead code is removed. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index e8c36db..653a8a2 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2668,46 +2668,6 @@ static void ep_stall(struct net2280_ep *ep, int stall) } } -static void ep_stdrsp(struct net2280_ep *ep, int value, int wedged) -{ - /* set/clear, then synch memory views with the device */ - if (value) { - ep->stopped = 1; - if (ep->num == 0) - ep->dev->protocol_stall = 1; - else { - if (ep->dma) - ep_stop_dma(ep); - ep_stall(ep, true); - } - - if (wedged) - ep->wedged = 1; - } else { - ep->stopped = 0; - ep->wedged = 0; - - ep_stall(ep, false); - - /* Flush the queue */ - if (!list_empty(&ep->queue)) { - struct net2280_request *req = - list_entry(ep->queue.next, struct net2280_request, - queue); - if (ep->dma) - resume_dma(ep); - else { - if (ep->is_in) - write_fifo(ep, &req->req); - else { - if (read_fifo(ep, req)) - done(ep, req, 0); - } - } - } - } -} - static void handle_stat0_irqs_superspeed(struct net2280 *dev, struct net2280_ep *ep, struct usb_ctrlrequest r) { @@ -2864,7 +2824,14 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, e = get_ep_by_addr(dev, w_index); if (!e || (w_value != USB_ENDPOINT_HALT)) goto do_stall3; - ep_stdrsp(e, true, false); + ep->stopped = 1; + if (ep->num == 0) + ep->dev->protocol_stall = 1; + else { + if (ep->dma) + ep_stop_dma(ep); + ep_stall(ep, true); + } allow_status_338x(ep); break; -- cgit v0.10.2 From 5d1b6840fd871b51765e7edee82876f760c46adf Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:53 +0100 Subject: usb: gadget: udc: net2280: Remove field is_halt Field is_halt is never used by any function. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 653a8a2..48e8d82 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2643,7 +2643,6 @@ static void ep_stall(struct net2280_ep *ep, int stall) /* BIT(SET_NAK_PACKETS) | */ BIT(CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE), &ep->regs->ep_rsp); - ep->is_halt = 1; } else { if (dev->gadget.speed == USB_SPEED_SUPER) { /* @@ -2663,7 +2662,6 @@ static void ep_stall(struct net2280_ep *ep, int stall) writel(val, /* | BIT(CLEAR_NAK_PACKETS),*/ &ep->regs->ep_rsp); - ep->is_halt = 0; val = readl(&ep->regs->ep_rsp); } } @@ -2924,9 +2922,7 @@ static void handle_stat0_irqs(struct net2280 *dev, u32 stat) } ep->stopped = 0; dev->protocol_stall = 0; - if (dev->quirks & PLX_SUPERSPEED) - ep->is_halt = 0; - else{ + if (!(dev->quirks & PLX_SUPERSPEED)) { if (ep->dev->quirks & PLX_2280) tmp = BIT(FIFO_OVERFLOW) | BIT(FIFO_UNDERFLOW); diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index 4c53b83..a16494a 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -100,7 +100,6 @@ struct net2280_ep { dma_addr_t td_dma; /* of dummy */ struct net2280 *dev; unsigned long irqs; - unsigned is_halt:1; /* analogous to a host-side qh */ struct list_head queue; -- cgit v0.10.2 From e0cbb04627556a953c6fe8d5ba42b9dda68146af Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:54 +0100 Subject: usb: gadget: udc: net2280: Remove function ep_stall irqs_superspeed calls ep_stall instead of set/clear_halt, due to a workaround for SS seqnum. Create a function with the workaround and call set/clear_halt instead. This way we can compare the code of super/normal speed and it is easier to follow the code. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 48e8d82..5ba5132 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2632,38 +2632,19 @@ restore_data_eps: return; } -static void ep_stall(struct net2280_ep *ep, int stall) +static void ep_clear_seqnum(struct net2280_ep *ep) { struct net2280 *dev = ep->dev; u32 val; static const u32 ep_pl[9] = { 0, 3, 4, 7, 8, 2, 5, 6, 9 }; - if (stall) { - writel(BIT(SET_ENDPOINT_HALT) | - /* BIT(SET_NAK_PACKETS) | */ - BIT(CLEAR_CONTROL_STATUS_PHASE_HANDSHAKE), - &ep->regs->ep_rsp); - } else { - if (dev->gadget.speed == USB_SPEED_SUPER) { - /* - * Workaround for SS SeqNum not cleared via - * Endpoint Halt (Clear) bit. select endpoint - */ - val = readl(&dev->plregs->pl_ep_ctrl); - val = (val & ~0x1f) | ep_pl[ep->num]; - writel(val, &dev->plregs->pl_ep_ctrl); + val = readl(&dev->plregs->pl_ep_ctrl) & ~0x1f; + val |= ep_pl[ep->num]; + writel(val, &dev->plregs->pl_ep_ctrl); + val |= BIT(SEQUENCE_NUMBER_RESET); + writel(val, &dev->plregs->pl_ep_ctrl); - val |= BIT(SEQUENCE_NUMBER_RESET); - writel(val, &dev->plregs->pl_ep_ctrl); - } - val = readl(&ep->regs->ep_rsp); - val |= BIT(CLEAR_ENDPOINT_HALT) | - BIT(CLEAR_ENDPOINT_TOGGLE); - writel(val, - /* | BIT(CLEAR_NAK_PACKETS),*/ - &ep->regs->ep_rsp); - val = readl(&ep->regs->ep_rsp); - } + return; } static void handle_stat0_irqs_superspeed(struct net2280 *dev, @@ -2764,7 +2745,12 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, if (w_value != USB_ENDPOINT_HALT) goto do_stall3; ep_vdbg(dev, "%s clear halt\n", e->ep.name); - ep_stall(e, false); + /* + * Workaround for SS SeqNum not cleared via + * Endpoint Halt (Clear) bit. select endpoint + */ + ep_clear_seqnum(e); + clear_halt(e); if (!list_empty(&e->queue) && e->td_dma) restart_dma(e); allow_status(ep); @@ -2828,7 +2814,7 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, else { if (ep->dma) ep_stop_dma(ep); - ep_stall(ep, true); + set_halt(ep); } allow_status_338x(ep); break; @@ -2857,7 +2843,7 @@ do_stall3: r.bRequestType, r.bRequest, tmp); dev->protocol_stall = 1; /* TD 9.9 Halt Endpoint test. TD 9.22 Set feature test */ - ep_stall(ep, true); + set_halt(ep); } next_endpoints3: -- cgit v0.10.2 From e721c4575db77a543d0a28c56e95b84ee37a551c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:55 +0100 Subject: usb: gadget: udc: net2280: Merge abort_dma for 228x and 338x We can use the same function for both families of chips and also remove the ep_stop_dma() function. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 5ba5132..46b66b3 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -836,12 +836,6 @@ static inline void resume_dma(struct net2280_ep *ep) writel(readl(&ep->dma->dmactl) | BIT(DMA_ENABLE), &ep->dma->dmactl); } -static inline void ep_stop_dma(struct net2280_ep *ep) -{ - writel(readl(&ep->dma->dmactl) & ~BIT(DMA_ENABLE), &ep->dma->dmactl); - spin_stop_dma(ep->dma); -} - static inline void queue_dma(struct net2280_ep *ep, struct net2280_request *req, int valid) { @@ -1115,7 +1109,7 @@ static void restart_dma(struct net2280_ep *ep) start_dma(ep, req); } -static void abort_dma_228x(struct net2280_ep *ep) +static void abort_dma(struct net2280_ep *ep) { /* abort the current transfer */ if (likely(!list_empty(&ep->queue))) { @@ -1127,19 +1121,6 @@ static void abort_dma_228x(struct net2280_ep *ep) scan_dma_completions(ep); } -static void abort_dma_338x(struct net2280_ep *ep) -{ - writel(BIT(DMA_ABORT), &ep->dma->dmastat); - spin_stop_dma(ep->dma); -} - -static void abort_dma(struct net2280_ep *ep) -{ - if (ep->dev->quirks & PLX_LEGACY) - return abort_dma_228x(ep); - return abort_dma_338x(ep); -} - /* dequeue ALL requests */ static void nuke(struct net2280_ep *ep) { @@ -2813,7 +2794,7 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, ep->dev->protocol_stall = 1; else { if (ep->dma) - ep_stop_dma(ep); + abort_dma(ep); set_halt(ep); } allow_status_338x(ep); -- cgit v0.10.2 From 485f44d06b352a788d07e79a5744cc7420adc1e4 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:56 +0100 Subject: usb: gadget: udc: net2280: Clean function net2280_queue Do not duplicate the code for the else branch of the condition. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 46b66b3..ade4693 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -937,24 +937,12 @@ net2280_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) _req->actual = 0; /* kickstart this i/o queue? */ - if (list_empty(&ep->queue) && !ep->stopped) { - /* DMA request while EP halted */ - if (ep->dma && - (readl(&ep->regs->ep_rsp) & BIT(CLEAR_ENDPOINT_HALT)) && - (dev->quirks & PLX_SUPERSPEED)) { - int valid = 1; - if (ep->is_in) { - int expect; - expect = likely(req->req.zero || - ((req->req.length % - ep->ep.maxpacket) != 0)); - if (expect != ep->in_fifo_validate) - valid = 0; - } - queue_dma(ep, req, valid); - } + if (list_empty(&ep->queue) && !ep->stopped && + !((dev->quirks & PLX_SUPERSPEED) && ep->dma && + (readl(&ep->regs->ep_rsp) & BIT(CLEAR_ENDPOINT_HALT)))) { + /* use DMA if the endpoint supports it, else pio */ - else if (ep->dma) + if (ep->dma) start_dma(ep, req); else { /* maybe there's no control data, just status ack */ -- cgit v0.10.2 From 5517525e05b5d21c26e2b729a7e977f7d69714af Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:57 +0100 Subject: usb: gadget: udc: net2280: Improve patching of defect 7374 Once the defect 7374 is patched, there is no reason the keep reading the idx scratch register. Cache the content of the scratch idx register on device flag. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index ade4693..d4255f9 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1765,76 +1765,73 @@ static void defect7374_disable_data_eps(struct net2280 *dev) static void defect7374_enable_data_eps_zero(struct net2280 *dev) { u32 tmp = 0, tmp_reg; - u32 fsmvalue, scratch; + u32 scratch; int i; unsigned char ep_sel; scratch = get_idx_reg(dev->regs, SCRATCH); - fsmvalue = scratch & (0xf << DEFECT7374_FSM_FIELD); + + WARN_ON((scratch & (0xf << DEFECT7374_FSM_FIELD)) + == DEFECT7374_FSM_SS_CONTROL_READ); + scratch &= ~(0xf << DEFECT7374_FSM_FIELD); - /*See if firmware needs to set up for workaround*/ - if (fsmvalue != DEFECT7374_FSM_SS_CONTROL_READ) { - ep_warn(dev, "Operate Defect 7374 workaround soft this time"); - ep_warn(dev, "It will operate on cold-reboot and SS connect"); - - /*GPEPs:*/ - tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_DIRECTION) | - (2 << OUT_ENDPOINT_TYPE) | (2 << IN_ENDPOINT_TYPE) | - ((dev->enhanced_mode) ? - BIT(OUT_ENDPOINT_ENABLE) : BIT(ENDPOINT_ENABLE)) | - BIT(IN_ENDPOINT_ENABLE)); - - for (i = 1; i < 5; i++) - writel(tmp, &dev->ep[i].cfg->ep_cfg); - - /* CSRIN, PCIIN, STATIN, RCIN*/ - tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_ENABLE)); - writel(tmp, &dev->dep[1].dep_cfg); - writel(tmp, &dev->dep[3].dep_cfg); - writel(tmp, &dev->dep[4].dep_cfg); - writel(tmp, &dev->dep[5].dep_cfg); - - /*Implemented for development and debug. - * Can be refined/tuned later.*/ - for (ep_sel = 0; ep_sel <= 21; ep_sel++) { - /* Select an endpoint for subsequent operations: */ - tmp_reg = readl(&dev->plregs->pl_ep_ctrl); - writel(((tmp_reg & ~0x1f) | ep_sel), - &dev->plregs->pl_ep_ctrl); - - if (ep_sel == 1) { - tmp = - (readl(&dev->plregs->pl_ep_ctrl) | - BIT(CLEAR_ACK_ERROR_CODE) | 0); - writel(tmp, &dev->plregs->pl_ep_ctrl); - continue; - } + ep_warn(dev, "Operate Defect 7374 workaround soft this time"); + ep_warn(dev, "It will operate on cold-reboot and SS connect"); - if (ep_sel == 0 || (ep_sel > 9 && ep_sel < 14) || - ep_sel == 18 || ep_sel == 20) - continue; + /*GPEPs:*/ + tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_DIRECTION) | + (2 << OUT_ENDPOINT_TYPE) | (2 << IN_ENDPOINT_TYPE) | + ((dev->enhanced_mode) ? + BIT(OUT_ENDPOINT_ENABLE) : BIT(ENDPOINT_ENABLE)) | + BIT(IN_ENDPOINT_ENABLE)); - tmp = (readl(&dev->plregs->pl_ep_cfg_4) | - BIT(NON_CTRL_IN_TOLERATE_BAD_DIR) | 0); - writel(tmp, &dev->plregs->pl_ep_cfg_4); + for (i = 1; i < 5; i++) + writel(tmp, &dev->ep[i].cfg->ep_cfg); - tmp = readl(&dev->plregs->pl_ep_ctrl) & - ~BIT(EP_INITIALIZED); - writel(tmp, &dev->plregs->pl_ep_ctrl); + /* CSRIN, PCIIN, STATIN, RCIN*/ + tmp = ((0 << ENDPOINT_NUMBER) | BIT(ENDPOINT_ENABLE)); + writel(tmp, &dev->dep[1].dep_cfg); + writel(tmp, &dev->dep[3].dep_cfg); + writel(tmp, &dev->dep[4].dep_cfg); + writel(tmp, &dev->dep[5].dep_cfg); + /*Implemented for development and debug. + * Can be refined/tuned later.*/ + for (ep_sel = 0; ep_sel <= 21; ep_sel++) { + /* Select an endpoint for subsequent operations: */ + tmp_reg = readl(&dev->plregs->pl_ep_ctrl); + writel(((tmp_reg & ~0x1f) | ep_sel), + &dev->plregs->pl_ep_ctrl); + + if (ep_sel == 1) { + tmp = + (readl(&dev->plregs->pl_ep_ctrl) | + BIT(CLEAR_ACK_ERROR_CODE) | 0); + writel(tmp, &dev->plregs->pl_ep_ctrl); + continue; } - /* Set FSM to focus on the first Control Read: - * - Tip: Connection speed is known upon the first - * setup request.*/ - scratch |= DEFECT7374_FSM_WAITING_FOR_CONTROL_READ; - set_idx_reg(dev->regs, SCRATCH, scratch); + if (ep_sel == 0 || (ep_sel > 9 && ep_sel < 14) || + ep_sel == 18 || ep_sel == 20) + continue; + + tmp = (readl(&dev->plregs->pl_ep_cfg_4) | + BIT(NON_CTRL_IN_TOLERATE_BAD_DIR) | 0); + writel(tmp, &dev->plregs->pl_ep_cfg_4); + + tmp = readl(&dev->plregs->pl_ep_ctrl) & + ~BIT(EP_INITIALIZED); + writel(tmp, &dev->plregs->pl_ep_ctrl); - } else{ - ep_warn(dev, "Defect 7374 workaround soft will NOT operate"); - ep_warn(dev, "It will operate on cold-reboot and SS connect"); } + + /* Set FSM to focus on the first Control Read: + * - Tip: Connection speed is known upon the first + * setup request.*/ + scratch |= DEFECT7374_FSM_WAITING_FOR_CONTROL_READ; + set_idx_reg(dev->regs, SCRATCH, scratch); + } /* keeping it simple: @@ -1885,21 +1882,13 @@ static void usb_reset_228x(struct net2280 *dev) static void usb_reset_338x(struct net2280 *dev) { u32 tmp; - u32 fsmvalue; dev->gadget.speed = USB_SPEED_UNKNOWN; (void)readl(&dev->usb->usbctl); net2280_led_init(dev); - fsmvalue = get_idx_reg(dev->regs, SCRATCH) & - (0xf << DEFECT7374_FSM_FIELD); - - /* See if firmware needs to set up for workaround: */ - if (fsmvalue != DEFECT7374_FSM_SS_CONTROL_READ) { - ep_info(dev, "%s: Defect 7374 FsmValue 0x%08x\n", __func__, - fsmvalue); - } else { + if (dev->bug7734_patched) { /* disable automatic responses, and irqs */ writel(0, &dev->usb->stdrsp); writel(0, &dev->regs->pciirqenb0); @@ -1916,7 +1905,7 @@ static void usb_reset_338x(struct net2280 *dev) writel(~0, &dev->regs->irqstat0), writel(~0, &dev->regs->irqstat1); - if (fsmvalue == DEFECT7374_FSM_SS_CONTROL_READ) { + if (dev->bug7734_patched) { /* reset, and enable pci */ tmp = readl(&dev->regs->devinit) | BIT(PCI_ENABLE) | @@ -1982,7 +1971,6 @@ static void usb_reinit_338x(struct net2280 *dev) { int i; u32 tmp, val; - u32 fsmvalue; static const u32 ne[9] = { 0, 1, 2, 3, 4, 1, 2, 3, 4 }; static const u32 ep_reg_addr[9] = { 0x00, 0xC0, 0x00, 0xC0, 0x00, 0x00, 0xC0, 0x00, 0xC0 }; @@ -2020,14 +2008,7 @@ static void usb_reinit_338x(struct net2280 *dev) dev->ep[0].stopped = 0; /* Link layer set up */ - fsmvalue = get_idx_reg(dev->regs, SCRATCH) & - (0xf << DEFECT7374_FSM_FIELD); - - /* See if driver needs to set up for workaround: */ - if (fsmvalue != DEFECT7374_FSM_SS_CONTROL_READ) - ep_info(dev, "%s: Defect 7374 FsmValue %08x\n", - __func__, fsmvalue); - else { + if (dev->bug7734_patched) { tmp = readl(&dev->usb_ext->usbctl2) & ~(BIT(U1_ENABLE) | BIT(U2_ENABLE) | BIT(LTM_ENABLE)); writel(tmp, &dev->usb_ext->usbctl2); @@ -2134,15 +2115,8 @@ static void ep0_start_228x(struct net2280 *dev) static void ep0_start_338x(struct net2280 *dev) { - u32 fsmvalue; - fsmvalue = get_idx_reg(dev->regs, SCRATCH) & - (0xf << DEFECT7374_FSM_FIELD); - - if (fsmvalue != DEFECT7374_FSM_SS_CONTROL_READ) - ep_info(dev, "%s: Defect 7374 FsmValue %08x\n", __func__, - fsmvalue); - else + if (dev->bug7734_patched) writel(BIT(CLEAR_NAK_OUT_PACKETS_MODE) | BIT(SET_EP_HIDE_STATUS_PHASE), &dev->epregs[0].ep_rsp); @@ -2230,7 +2204,7 @@ static int net2280_start(struct usb_gadget *_gadget, */ net2280_led_active(dev, 1); - if (dev->quirks & PLX_SUPERSPEED) + if ((dev->quirks & PLX_SUPERSPEED) && !dev->bug7734_patched) defect7374_enable_data_eps_zero(dev); ep0_start(dev); @@ -2552,6 +2526,7 @@ static void defect7374_workaround(struct net2280 *dev, struct usb_ctrlrequest r) * run after the next USB connection. */ scratch |= DEFECT7374_FSM_NON_SS_CONTROL_READ; + dev->bug7734_patched = 1; goto restore_data_eps; } @@ -2565,6 +2540,7 @@ static void defect7374_workaround(struct net2280 *dev, struct usb_ctrlrequest r) if ((state >= (ACK_GOOD_NORMAL << STATE)) && (state <= (ACK_GOOD_MORE_ACKS_TO_COME << STATE))) { scratch |= DEFECT7374_FSM_SS_CONTROL_READ; + dev->bug7734_patched = 1; break; } @@ -2904,7 +2880,7 @@ static void handle_stat0_irqs(struct net2280 *dev, u32 stat) cpu_to_le32s(&u.raw[0]); cpu_to_le32s(&u.raw[1]); - if (dev->quirks & PLX_SUPERSPEED) + if ((dev->quirks & PLX_SUPERSPEED) && !dev->bug7734_patched) defect7374_workaround(dev, u.r); tmp = 0; @@ -3418,9 +3394,12 @@ static int net2280_probe(struct pci_dev *pdev, const struct pci_device_id *id) fsmvalue = get_idx_reg(dev->regs, SCRATCH) & (0xf << DEFECT7374_FSM_FIELD); /* See if firmware needs to set up for workaround: */ - if (fsmvalue == DEFECT7374_FSM_SS_CONTROL_READ) + if (fsmvalue == DEFECT7374_FSM_SS_CONTROL_READ) { + dev->bug7734_patched = 1; writel(0, &dev->usb->usbctl); - } else{ + } else + dev->bug7734_patched = 0; + } else { dev->enhanced_mode = 0; dev->n_ep = 7; /* put into initial config, link up all endpoints */ diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index a16494a..c7c7981 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -165,7 +165,8 @@ struct net2280 { ltm_enable:1, wakeup_enable:1, selfpowered:1, - addressed_state:1; + addressed_state:1, + bug7734_patched:1; u16 chiprev; int enhanced_mode; int n_ep; -- cgit v0.10.2 From 5153c219e77985be2fb861814184729bb40b0987 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:58 +0100 Subject: usb: gadget: udc: net2280: Remove function resume_dma Function resume_dma is not used, remove it. The reason the compiler did not catch this dead code is the inline modifier. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index d4255f9..cc76181 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -831,11 +831,6 @@ static void start_dma(struct net2280_ep *ep, struct net2280_request *req) start_queue(ep, tmp, req->td_dma); } -static inline void resume_dma(struct net2280_ep *ep) -{ - writel(readl(&ep->dma->dmactl) | BIT(DMA_ENABLE), &ep->dma->dmactl); -} - static inline void queue_dma(struct net2280_ep *ep, struct net2280_request *req, int valid) { -- cgit v0.10.2 From 6897d4b2bafe189863b2fe448c4afde37844cdbf Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:50:59 +0100 Subject: usb: gadget: udc: net2280: Declare allow_status_338x as inline The function is very simple, does not declare any variable and it is called in the irq path. The counterpart for net228x is already declared as inline. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index c7c7981..b31deb0 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -125,7 +125,7 @@ static inline void allow_status(struct net2280_ep *ep) ep->stopped = 1; } -static void allow_status_338x(struct net2280_ep *ep) +static inline void allow_status_338x(struct net2280_ep *ep) { /* * Control Status Phase Handshake was set by the chip when the setup -- cgit v0.10.2 From 43780aaa1c23c572ef57267d43d520affa7b4723 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:51:00 +0100 Subject: usb: gadget: udc: net2280: Simplify scan_dma_completions After fix superspeed dma_done was applied we can simplify the code by removing the duplicated dma_done and letting the function check if there are more completed dma transactions. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index cc76181..a978e6e 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1051,9 +1051,8 @@ static void scan_dma_completions(struct net2280_ep *ep) dma_done(ep, req, tmp, 0); break; } else if (!ep->is_in && - (req->req.length % ep->ep.maxpacket) != 0) { - if (ep->dev->quirks & PLX_SUPERSPEED) - return dma_done(ep, req, tmp, 0); + (req->req.length % ep->ep.maxpacket) && + !(ep->dev->quirks & PLX_SUPERSPEED)) { tmp = readl(&ep->regs->ep_stat); /* AVOID TROUBLE HERE by not issuing short reads from -- cgit v0.10.2 From d82f3db266f3be7f85de33905dba2a6caccb97f0 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:51:01 +0100 Subject: usb: gadget: udc: net2280: Move ASSERT_OUT_NAKING into out_flush ASSERT_OUT_NAKING was only called by out_flush and was hidden behind a ifdef. This patch moves the inline function into out_flush and remove the ifdef. The user can decide to print the debug message or not via dynamic printk Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index a978e6e..b1c253e 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -575,9 +575,15 @@ static void out_flush(struct net2280_ep *ep) u32 __iomem *statp; u32 tmp; - ASSERT_OUT_NAKING(ep); - statp = &ep->regs->ep_stat; + + tmp = readl(statp); + if (tmp & BIT(NAK_OUT_PACKETS)) { + ep_dbg(ep->dev, "%s %s %08x !NAK\n", + ep->ep.name, __func__, tmp); + writel(BIT(SET_NAK_OUT_PACKETS), &ep->regs->ep_rsp); + } + writel(BIT(DATA_OUT_PING_TOKEN_INTERRUPT) | BIT(DATA_PACKET_RECEIVED_INTERRUPT), statp); diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index b31deb0..a307dce 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -356,23 +356,6 @@ static inline void start_out_naking(struct net2280_ep *ep) readl(&ep->regs->ep_rsp); } -#ifdef DEBUG -static inline void assert_out_naking(struct net2280_ep *ep, const char *where) -{ - u32 tmp = readl(&ep->regs->ep_stat); - - if ((tmp & BIT(NAK_OUT_PACKETS)) == 0) { - ep_dbg(ep->dev, "%s %s %08x !NAK\n", - ep->ep.name, where, tmp); - writel(BIT(SET_NAK_OUT_PACKETS), - &ep->regs->ep_rsp); - } -} -#define ASSERT_OUT_NAKING(ep) assert_out_naking(ep, __func__) -#else -#define ASSERT_OUT_NAKING(ep) do {} while (0) -#endif - static inline void stop_out_naking(struct net2280_ep *ep) { u32 tmp; -- cgit v0.10.2 From cb442ee1592d26815156201afa986a4ec3fb4bbf Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Fri, 28 Nov 2014 14:51:02 +0100 Subject: usb: gadget: udc: net2280: Re-enable dynamic debug messages Some debug messages were not build due to unconditional #if 0. These messages are very useful for debugging and the user can enable them on demand via dynamic debug. If they are not enabled the performance is not affected. Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index b1c253e..d2fabdc 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -927,10 +927,8 @@ net2280_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) return ret; } -#if 0 ep_vdbg(dev, "%s queue req %p, len %d buf %p\n", _ep->name, _req, _req->length, _req->buf); -#endif spin_lock_irqsave(&dev->lock, flags); @@ -2287,10 +2285,10 @@ static void handle_ep_small(struct net2280_ep *ep) /* ack all, and handle what we care about */ t = readl(&ep->regs->ep_stat); ep->irqs++; -#if 0 + ep_vdbg(ep->dev, "%s ack ep_stat %08x, req %p\n", ep->ep.name, t, req ? &req->req : 0); -#endif + if (!ep->is_in || (ep->dev->quirks & PLX_2280)) writel(t & ~BIT(NAK_OUT_PACKETS), &ep->regs->ep_stat); else -- cgit v0.10.2 From 6b448af46e2a2fc9563fd86fb3b7cf06fcf2dd47 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 16 Dec 2014 11:51:44 +0100 Subject: drivers: usb: dwc2: remove 'force' parameter from kill_all_requests() This patch fixes in simpler way the bug described in [1] and [2]. It looks like DWC2 is the only UDC driver that doesn't force usb requests to complete in ep_disable() function. This causes described problem, because we have no guarantee that all requests will be completed before unbind of usb function. To fix this problem we force all requests of disabled endpoint to complete. Also currently running request is not handled. This allowed to simplify code of kill_all_requests() function, because 'force' parameter is always set to true, so we don't need it anymore. In s3c_hsotg_rx_data() we change function used to print message when active request is NULL from dev_warn() to dev_dbg(), because such situation is harmless for driver and now it can take place during normal endpoint disabling. [1] https://lkml.org/lkml/2014/12/9/283 [2] https://lkml.org/lkml/2014/12/12/360 Acked-by: Paul Zimmerman Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index a461406..1a8e0a6 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1305,7 +1305,7 @@ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) u32 epctl = readl(hsotg->regs + DOEPCTL(ep_idx)); int ptr; - dev_warn(hsotg->dev, + dev_dbg(hsotg->dev, "%s: FIFO %d bytes on ep%d but no req (DXEPCTl=0x%08x)\n", __func__, size, ep_idx, epctl); @@ -1988,30 +1988,23 @@ static void s3c_hsotg_irq_enumdone(struct dwc2_hsotg *hsotg) * @hsotg: The device state. * @ep: The endpoint the requests may be on. * @result: The result code to use. - * @force: Force removal of any current requests * * Go through the requests on the given endpoint and mark them * completed with the given result code. */ static void kill_all_requests(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *ep, - int result, bool force) + int result) { struct s3c_hsotg_req *req, *treq; unsigned size; - list_for_each_entry_safe(req, treq, &ep->queue, queue) { - /* - * currently, we can't do much about an already - * running request on an in endpoint - */ - - if (ep->req == req && ep->dir_in && !force) - continue; + ep->req = NULL; + list_for_each_entry_safe(req, treq, &ep->queue, queue) s3c_hsotg_complete_request(hsotg, ep, req, result); - } + if (!hsotg->dedicated_fifos) return; size = (readl(hsotg->regs + DTXFSTS(ep->index)) & 0xffff) * 4; @@ -2036,7 +2029,7 @@ void s3c_hsotg_disconnect(struct dwc2_hsotg *hsotg) hsotg->connected = 0; for (ep = 0; ep < hsotg->num_of_eps; ep++) - kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN, true); + kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN); call_gadget(hsotg, disconnect); } @@ -2334,7 +2327,7 @@ irq_retry: msecs_to_jiffies(200))) { kill_all_requests(hsotg, &hsotg->eps[0], - -ECONNRESET, true); + -ECONNRESET); s3c_hsotg_core_init_disconnected(hsotg); s3c_hsotg_core_connect(hsotg); @@ -2588,7 +2581,7 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) spin_lock_irqsave(&hsotg->lock, flags); /* terminate all requests with shutdown */ - kill_all_requests(hsotg, hs_ep, -ESHUTDOWN, false); + kill_all_requests(hsotg, hs_ep, -ESHUTDOWN); hsotg->fifo_map &= ~(1<fifo_index); hs_ep->fifo_index = 0; -- cgit v0.10.2 From fdf80e78acbaa6a50531490b3a6ba027a52a69ad Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 24 Dec 2014 13:48:02 +0800 Subject: usb: phy: phy-mxs-usb: add power down and disable wakeup for .shutdown When we shut down the PHY, we need to power down all PHY's functions as well as disable wakeup, it is the opposite operation we do at .init. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index b9589f6..eaf94b0 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -293,6 +293,17 @@ static int mxs_phy_init(struct usb_phy *phy) static void mxs_phy_shutdown(struct usb_phy *phy) { struct mxs_phy *mxs_phy = to_mxs_phy(phy); + u32 value = BM_USBPHY_CTRL_ENVBUSCHG_WKUP | + BM_USBPHY_CTRL_ENDPDMCHG_WKUP | + BM_USBPHY_CTRL_ENIDCHG_WKUP | + BM_USBPHY_CTRL_ENAUTOSET_USBCLKS | + BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE | + BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD | + BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE | + BM_USBPHY_CTRL_ENAUTO_PWRON_PLL; + + writel(value, phy->io_priv + HW_USBPHY_CTRL_CLR); + writel(0xffffffff, phy->io_priv + HW_USBPHY_PWD); writel(BM_USBPHY_CTRL_CLKGATE, phy->io_priv + HW_USBPHY_CTRL_SET); -- cgit v0.10.2 From f78c0957674bda6687992058554c07683418ebc2 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 24 Dec 2014 13:48:03 +0800 Subject: usb: phy: phy-mxs-usb: do not depend on speed for disconnect notifier For some user cases, like plug out and replug in usb device during the system suspend, the speed negotiation will be error due to host doesn't know the device's disconnection, and it still hopes the high speed device, but the device backs to "powered" state which its high speed termination is not enabled, the usb core calls the PHY's disconnect notifier with "full speed", it will NOT take effect at all. If the usb core calls disconnect notifer, the port change must happen, so it is safe to disable high speed disconenct detector, since connect notifier will be called soon if the device is still connected on the port, and we will enable high speed disconnect detector at that time. Acked-by: Li Jun Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index eaf94b0..58cae78 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -370,7 +370,9 @@ static int mxs_phy_on_disconnect(struct usb_phy *phy, dev_dbg(phy->dev, "%s device has disconnected\n", (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); - if (speed == USB_SPEED_HIGH) + /* Sometimes, the speed is not high speed when the error occurs */ + if (readl(phy->io_priv + HW_USBPHY_CTRL) & + BM_USBPHY_CTRL_ENHOSTDISCONDETECT) writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, phy->io_priv + HW_USBPHY_CTRL_CLR); -- cgit v0.10.2 From 07673204777abe893092cc6e42ea04e5c0ac8cb7 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 30 Dec 2014 14:02:28 +0800 Subject: usb: phy: change some comments - Delete the OTG stuffs - .set_suspend is for controller, not for A-device or B-device. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index f499c23..bc91b5d 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -1,5 +1,5 @@ -/* USB OTG (On The Go) defines */ /* + * USB PHY defines * * These APIs may be used between USB controllers. USB device drivers * (for either host or peripheral roles) don't use these calls; they @@ -106,7 +106,7 @@ struct usb_phy { int (*set_power)(struct usb_phy *x, unsigned mA); - /* for non-OTG B devices: set transceiver into suspend mode */ + /* Set transceiver into suspend mode */ int (*set_suspend)(struct usb_phy *x, int suspend); -- cgit v0.10.2 From 6f15e2dc710cc604cc81f0ff596d83c332b5cbe8 Mon Sep 17 00:00:00 2001 From: Rickard Strandqvist Date: Thu, 1 Jan 2015 20:15:34 +0100 Subject: usb: phy: phy-fsl-usb: Remove some unused functions Removes some functions that are not used anywhere: fsl_otg_tick_timer() view_ulpi() This was partially found by using a static code analysis program called cppcheck. Signed-off-by: Rickard Strandqvist Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-fsl-usb.c b/drivers/usb/phy/phy-fsl-usb.c index ab38aa3..94eb292 100644 --- a/drivers/usb/phy/phy-fsl-usb.c +++ b/drivers/usb/phy/phy-fsl-usb.c @@ -107,19 +107,6 @@ static void (*_fsl_writel)(u32 v, unsigned __iomem *p); #define fsl_writel(val, addr) writel(val, addr) #endif /* CONFIG_PPC32 */ -/* Routines to access transceiver ULPI registers */ -u8 view_ulpi(u8 addr) -{ - u32 temp; - - temp = 0x40000000 | (addr << 16); - fsl_writel(temp, &usb_dr_regs->ulpiview); - udelay(1000); - while (temp & 0x40) - temp = fsl_readl(&usb_dr_regs->ulpiview); - return (le32_to_cpu(temp) & 0x0000ff00) >> 8; -} - int write_ulpi(u8 addr, u8 data) { u32 temp; @@ -460,28 +447,6 @@ static void fsl_otg_fsm_del_timer(struct otg_fsm *fsm, enum otg_fsm_timer t) fsl_otg_del_timer(fsm, timer); } -/* - * Reduce timer count by 1, and find timeout conditions. - * Called by fsl_otg 1ms timer interrupt - */ -int fsl_otg_tick_timer(void) -{ - struct fsl_otg_timer *tmp_timer, *del_tmp; - int expired = 0; - - list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { - tmp_timer->count--; - /* check if timer expires */ - if (!tmp_timer->count) { - list_del(&tmp_timer->list); - tmp_timer->function(tmp_timer->data); - expired = 1; - } - } - - return expired; -} - /* Reset controller, not reset the bus */ void otg_reset_controller(void) { -- cgit v0.10.2 From 3280e67536f8a4d4adf8dcde10cb4c4b577c34f4 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Jan 2015 14:46:58 +0100 Subject: usb: atmel_usba_udc: Rework at91sam9rl errata handling at91sam9rl SoC has an erratum forcing us to toggle the BIAS on USB suspend/resume events. This specific handling is only activated when CONFIG_ARCH_AT91SAM9RL is set and this option is only set when building a non-DT kernel, which is problematic since non-DT support for at91sam9rl SoC has been removed. Rework the toggle_bias implementation to attach it to the "at91sam9rl-udc" compatible string. Add new compatible strings to avoid executing at91sam9rl erratum handling on other SoCs. Acked-by: Alexandre Belloni Signed-off-by: Boris Brezillon Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/atmel-usb.txt b/Documentation/devicetree/bindings/usb/atmel-usb.txt index bc2222c..38fee0f 100644 --- a/Documentation/devicetree/bindings/usb/atmel-usb.txt +++ b/Documentation/devicetree/bindings/usb/atmel-usb.txt @@ -51,7 +51,10 @@ usb1: gadget@fffa4000 { Atmel High-Speed USB device controller Required properties: - - compatible: Should be "atmel,at91sam9rl-udc" + - compatible: Should be one of the following + "at91sam9rl-udc" + "at91sam9g45-udc" + "sama5d3-udc" - reg: Address and length of the register set for the device - interrupts: Should contain usba interrupt - ep childnode: To specify the number of endpoints and their properties. diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index ce88237..36fd34b 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -8,6 +8,7 @@ * published by the Free Software Foundation. */ #include +#include #include #include #include @@ -324,28 +325,12 @@ static int vbus_is_present(struct usba_udc *udc) return 1; } -#if defined(CONFIG_ARCH_AT91SAM9RL) - -#include - -static void toggle_bias(int is_on) -{ - unsigned int uckr = at91_pmc_read(AT91_CKGR_UCKR); - - if (is_on) - at91_pmc_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); - else - at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); -} - -#else - -static void toggle_bias(int is_on) +static void toggle_bias(struct usba_udc *udc, int is_on) { + if (udc->errata && udc->errata->toggle_bias) + udc->errata->toggle_bias(udc, is_on); } -#endif /* CONFIG_ARCH_AT91SAM9RL */ - static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) { unsigned int transaction_len; @@ -1620,7 +1605,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) DBG(DBG_INT, "irq, status=%#08x\n", status); if (status & USBA_DET_SUSPEND) { - toggle_bias(0); + toggle_bias(udc, 0); usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); DBG(DBG_BUS, "Suspend detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN @@ -1632,7 +1617,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) } if (status & USBA_WAKE_UP) { - toggle_bias(1); + toggle_bias(udc, 1); usba_writel(udc, INT_CLR, USBA_WAKE_UP); DBG(DBG_BUS, "Wake Up CPU detected\n"); } @@ -1736,13 +1721,13 @@ static irqreturn_t usba_vbus_irq(int irq, void *devid) vbus = vbus_is_present(udc); if (vbus != udc->vbus_prev) { if (vbus) { - toggle_bias(1); + toggle_bias(udc, 1); usba_writel(udc, CTRL, USBA_ENABLE_MASK); usba_writel(udc, INT_ENB, USBA_END_OF_RESET); } else { udc->gadget.speed = USB_SPEED_UNKNOWN; reset_all_endpoints(udc); - toggle_bias(0); + toggle_bias(udc, 0); usba_writel(udc, CTRL, USBA_DISABLE_MASK); if (udc->driver->disconnect) { spin_unlock(&udc->lock); @@ -1788,7 +1773,7 @@ static int atmel_usba_start(struct usb_gadget *gadget, /* If Vbus is present, enable the controller and wait for reset */ spin_lock_irqsave(&udc->lock, flags); if (vbus_is_present(udc) && udc->vbus_prev == 0) { - toggle_bias(1); + toggle_bias(udc, 1); usba_writel(udc, CTRL, USBA_ENABLE_MASK); usba_writel(udc, INT_ENB, USBA_END_OF_RESET); } @@ -1811,7 +1796,7 @@ static int atmel_usba_stop(struct usb_gadget *gadget) spin_unlock_irqrestore(&udc->lock, flags); /* This will also disable the DP pullup */ - toggle_bias(0); + toggle_bias(udc, 0); usba_writel(udc, CTRL, USBA_DISABLE_MASK); clk_disable_unprepare(udc->hclk); @@ -1823,6 +1808,29 @@ static int atmel_usba_stop(struct usb_gadget *gadget) } #ifdef CONFIG_OF +static void at91sam9rl_toggle_bias(struct usba_udc *udc, int is_on) +{ + unsigned int uckr = at91_pmc_read(AT91_CKGR_UCKR); + + if (is_on) + at91_pmc_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); + else + at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); +} + +static const struct usba_udc_errata at91sam9rl_errata = { + .toggle_bias = at91sam9rl_toggle_bias, +}; + +static const struct of_device_id atmel_udc_dt_ids[] = { + { .compatible = "atmel,at91sam9rl-udc", .data = &at91sam9rl_errata }, + { .compatible = "atmel,at91sam9g45-udc" }, + { .compatible = "atmel,sama5d3-udc" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_udc_dt_ids); + static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, struct usba_udc *udc) { @@ -1830,10 +1838,17 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, const char *name; enum of_gpio_flags flags; struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match; struct device_node *pp; int i, ret; struct usba_ep *eps, *ep; + match = of_match_node(atmel_udc_dt_ids, np); + if (!match) + return ERR_PTR(-EINVAL); + + udc->errata = match->data; + udc->num_ep = 0; udc->vbus_pin = of_get_named_gpio_flags(np, "atmel,vbus-gpio", 0, @@ -2024,7 +2039,7 @@ static int usba_udc_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Unable to enable pclk, aborting.\n"); return ret; } - toggle_bias(0); + usba_writel(udc, CTRL, USBA_DISABLE_MASK); clk_disable_unprepare(pclk); @@ -2033,6 +2048,8 @@ static int usba_udc_probe(struct platform_device *pdev) else udc->usba_ep = usba_udc_pdata(pdev, udc); + toggle_bias(udc, 0); + if (IS_ERR(udc->usba_ep)) return PTR_ERR(udc->usba_ep); @@ -2092,15 +2109,6 @@ static int __exit usba_udc_remove(struct platform_device *pdev) return 0; } -#if defined(CONFIG_OF) -static const struct of_device_id atmel_udc_dt_ids[] = { - { .compatible = "atmel,at91sam9rl-udc" }, - { /* sentinel */ } -}; - -MODULE_DEVICE_TABLE(of, atmel_udc_dt_ids); -#endif - static struct platform_driver udc_driver = { .remove = __exit_p(usba_udc_remove), .driver = { diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index a70706e..456899e 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -304,6 +304,10 @@ struct usba_request { unsigned int mapped:1; }; +struct usba_udc_errata { + void (*toggle_bias)(struct usba_udc *udc, int is_on); +}; + struct usba_udc { /* Protect hw registers from concurrent modifications */ spinlock_t lock; @@ -314,6 +318,7 @@ struct usba_udc { struct usb_gadget gadget; struct usb_gadget_driver *driver; struct platform_device *pdev; + const struct usba_udc_errata *errata; int irq; int vbus_pin; int vbus_pin_inverted; -- cgit v0.10.2 From 258e2ddd634c20065d1c41290d10d2e5cf2f56e2 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Jan 2015 14:46:59 +0100 Subject: usb: atmel_usba_udc: Add at91sam9g45 and at91sam9x5 errata handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit at91sam9g45 and at91sam9x5 SoCs have an hardware bug forcing us to generate a pulse on the BIAS signal on "USB end of reset” and “USB end of resume" events. Acked-by: Alexandre Belloni Reported-by: Patrice VILCHEZ Signed-off-by: Boris Brezillon Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 36fd34b..55c8dde 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -331,6 +331,17 @@ static void toggle_bias(struct usba_udc *udc, int is_on) udc->errata->toggle_bias(udc, is_on); } +static void generate_bias_pulse(struct usba_udc *udc) +{ + if (!udc->bias_pulse_needed) + return; + + if (udc->errata && udc->errata->pulse_bias) + udc->errata->pulse_bias(udc); + + udc->bias_pulse_needed = false; +} + static void next_fifo_transaction(struct usba_ep *ep, struct usba_request *req) { unsigned int transaction_len; @@ -1607,6 +1618,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_DET_SUSPEND) { toggle_bias(udc, 0); usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); + udc->bias_pulse_needed = true; DBG(DBG_BUS, "Suspend detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN && udc->driver && udc->driver->suspend) { @@ -1624,6 +1636,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_END_OF_RESUME) { usba_writel(udc, INT_CLR, USBA_END_OF_RESUME); + generate_bias_pulse(udc); DBG(DBG_BUS, "Resume detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN && udc->driver && udc->driver->resume) { @@ -1659,6 +1672,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) struct usba_ep *ep0; usba_writel(udc, INT_CLR, USBA_END_OF_RESET); + generate_bias_pulse(udc); reset_all_endpoints(udc); if (udc->gadget.speed != USB_SPEED_UNKNOWN && udc->driver) { @@ -1818,13 +1832,25 @@ static void at91sam9rl_toggle_bias(struct usba_udc *udc, int is_on) at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); } +static void at91sam9g45_pulse_bias(struct usba_udc *udc) +{ + unsigned int uckr = at91_pmc_read(AT91_CKGR_UCKR); + + at91_pmc_write(AT91_CKGR_UCKR, uckr & ~(AT91_PMC_BIASEN)); + at91_pmc_write(AT91_CKGR_UCKR, uckr | AT91_PMC_BIASEN); +} + static const struct usba_udc_errata at91sam9rl_errata = { .toggle_bias = at91sam9rl_toggle_bias, }; +static const struct usba_udc_errata at91sam9g45_errata = { + .pulse_bias = at91sam9g45_pulse_bias, +}; + static const struct of_device_id atmel_udc_dt_ids[] = { { .compatible = "atmel,at91sam9rl-udc", .data = &at91sam9rl_errata }, - { .compatible = "atmel,at91sam9g45-udc" }, + { .compatible = "atmel,at91sam9g45-udc", .data = &at91sam9g45_errata }, { .compatible = "atmel,sama5d3-udc" }, { /* sentinel */ } }; diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 456899e..72b3537 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -306,6 +306,7 @@ struct usba_request { struct usba_udc_errata { void (*toggle_bias)(struct usba_udc *udc, int is_on); + void (*pulse_bias)(struct usba_udc *udc); }; struct usba_udc { @@ -326,6 +327,7 @@ struct usba_udc { struct clk *pclk; struct clk *hclk; struct usba_ep *usba_ep; + bool bias_pulse_needed; u16 devstatus; -- cgit v0.10.2 From 9870d895ad87efae130b2990db9ba265271f113b Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Jan 2015 14:47:01 +0100 Subject: usb: atmel_usba_udc: Mask status with enabled irqs Avoid interpreting useless status flags when we're not waiting for such events by masking the status variable with the interrupt enabled register value. Acked-by: Alexandre Belloni Reported-by: Patrice VILCHEZ Signed-off-by: Boris Brezillon Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 55c8dde..bc3a532 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -1612,12 +1612,14 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) spin_lock(&udc->lock); - status = usba_readl(udc, INT_STA); + status = usba_readl(udc, INT_STA) & usba_readl(udc, INT_ENB); DBG(DBG_INT, "irq, status=%#08x\n", status); if (status & USBA_DET_SUSPEND) { toggle_bias(udc, 0); usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); + usba_writel(udc, INT_ENB, + usba_readl(udc, INT_ENB) | USBA_WAKE_UP); udc->bias_pulse_needed = true; DBG(DBG_BUS, "Suspend detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN @@ -1631,6 +1633,8 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_WAKE_UP) { toggle_bias(udc, 1); usba_writel(udc, INT_CLR, USBA_WAKE_UP); + usba_writel(udc, INT_ENB, + usba_readl(udc, INT_ENB) & ~USBA_WAKE_UP); DBG(DBG_BUS, "Wake Up CPU detected\n"); } -- cgit v0.10.2 From e3a912a124c380db61eff762faa0547ea4c90eb4 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 6 Jan 2015 14:47:02 +0100 Subject: usb: gadget: atmel_usba: Cache INT_ENB register value Cache INT_ENB register value in order to avoid uncached iomem access, and thus improve access time to INT_ENB value. Acked-by: Alexandre Belloni Signed-off-by: Boris Brezillon Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index bc3a532..6dfe17b 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -316,6 +316,17 @@ static inline void usba_cleanup_debugfs(struct usba_udc *udc) } #endif +static inline u32 usba_int_enb_get(struct usba_udc *udc) +{ + return udc->int_enb_cache; +} + +static inline void usba_int_enb_set(struct usba_udc *udc, u32 val) +{ + usba_writel(udc, INT_ENB, val); + udc->int_enb_cache = val; +} + static int vbus_is_present(struct usba_udc *udc) { if (gpio_is_valid(udc->vbus_pin)) @@ -597,16 +608,14 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) if (ep->can_dma) { u32 ctrl; - usba_writel(udc, INT_ENB, - (usba_readl(udc, INT_ENB) - | USBA_BF(EPT_INT, 1 << ep->index) - | USBA_BF(DMA_INT, 1 << ep->index))); + usba_int_enb_set(udc, usba_int_enb_get(udc) | + USBA_BF(EPT_INT, 1 << ep->index) | + USBA_BF(DMA_INT, 1 << ep->index)); ctrl = USBA_AUTO_VALID | USBA_INTDIS_DMA; usba_ep_writel(ep, CTL_ENB, ctrl); } else { - usba_writel(udc, INT_ENB, - (usba_readl(udc, INT_ENB) - | USBA_BF(EPT_INT, 1 << ep->index))); + usba_int_enb_set(udc, usba_int_enb_get(udc) | + USBA_BF(EPT_INT, 1 << ep->index)); } spin_unlock_irqrestore(&udc->lock, flags); @@ -614,7 +623,7 @@ usba_ep_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) DBG(DBG_HW, "EPT_CFG%d after init: %#08lx\n", ep->index, (unsigned long)usba_ep_readl(ep, CFG)); DBG(DBG_HW, "INT_ENB after init: %#08lx\n", - (unsigned long)usba_readl(udc, INT_ENB)); + (unsigned long)usba_int_enb_get(udc)); return 0; } @@ -650,9 +659,8 @@ static int usba_ep_disable(struct usb_ep *_ep) usba_dma_readl(ep, STATUS); } usba_ep_writel(ep, CTL_DIS, USBA_EPT_ENABLE); - usba_writel(udc, INT_ENB, - usba_readl(udc, INT_ENB) - & ~USBA_BF(EPT_INT, 1 << ep->index)); + usba_int_enb_set(udc, usba_int_enb_get(udc) & + ~USBA_BF(EPT_INT, 1 << ep->index)); request_complete_list(ep, &req_list, -ESHUTDOWN); @@ -1606,20 +1614,20 @@ static void usba_dma_irq(struct usba_udc *udc, struct usba_ep *ep) static irqreturn_t usba_udc_irq(int irq, void *devid) { struct usba_udc *udc = devid; - u32 status; + u32 status, int_enb; u32 dma_status; u32 ep_status; spin_lock(&udc->lock); - status = usba_readl(udc, INT_STA) & usba_readl(udc, INT_ENB); + int_enb = usba_int_enb_get(udc); + status = usba_readl(udc, INT_STA) & int_enb; DBG(DBG_INT, "irq, status=%#08x\n", status); if (status & USBA_DET_SUSPEND) { toggle_bias(udc, 0); usba_writel(udc, INT_CLR, USBA_DET_SUSPEND); - usba_writel(udc, INT_ENB, - usba_readl(udc, INT_ENB) | USBA_WAKE_UP); + usba_int_enb_set(udc, int_enb | USBA_WAKE_UP); udc->bias_pulse_needed = true; DBG(DBG_BUS, "Suspend detected\n"); if (udc->gadget.speed != USB_SPEED_UNKNOWN @@ -1633,8 +1641,7 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) if (status & USBA_WAKE_UP) { toggle_bias(udc, 1); usba_writel(udc, INT_CLR, USBA_WAKE_UP); - usba_writel(udc, INT_ENB, - usba_readl(udc, INT_ENB) & ~USBA_WAKE_UP); + usba_int_enb_set(udc, int_enb & ~USBA_WAKE_UP); DBG(DBG_BUS, "Wake Up CPU detected\n"); } @@ -1702,11 +1709,8 @@ static irqreturn_t usba_udc_irq(int irq, void *devid) | USBA_BF(BK_NUMBER, USBA_BK_NUMBER_ONE))); usba_ep_writel(ep0, CTL_ENB, USBA_EPT_ENABLE | USBA_RX_SETUP); - usba_writel(udc, INT_ENB, - (usba_readl(udc, INT_ENB) - | USBA_BF(EPT_INT, 1) - | USBA_DET_SUSPEND - | USBA_END_OF_RESUME)); + usba_int_enb_set(udc, int_enb | USBA_BF(EPT_INT, 1) | + USBA_DET_SUSPEND | USBA_END_OF_RESUME); /* * Unclear why we hit this irregularly, e.g. in usbtest, @@ -1741,7 +1745,7 @@ static irqreturn_t usba_vbus_irq(int irq, void *devid) if (vbus) { toggle_bias(udc, 1); usba_writel(udc, CTRL, USBA_ENABLE_MASK); - usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + usba_int_enb_set(udc, USBA_END_OF_RESET); } else { udc->gadget.speed = USB_SPEED_UNKNOWN; reset_all_endpoints(udc); @@ -1793,7 +1797,7 @@ static int atmel_usba_start(struct usb_gadget *gadget, if (vbus_is_present(udc) && udc->vbus_prev == 0) { toggle_bias(udc, 1); usba_writel(udc, CTRL, USBA_ENABLE_MASK); - usba_writel(udc, INT_ENB, USBA_END_OF_RESET); + usba_int_enb_set(udc, USBA_END_OF_RESET); } spin_unlock_irqrestore(&udc->lock, flags); diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 72b3537..497cd18 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -334,6 +334,8 @@ struct usba_udc { u16 test_mode; int vbus_prev; + u32 int_enb_cache; + #ifdef CONFIG_USB_GADGET_DEBUG_FS struct dentry *debugfs_root; struct dentry *debugfs_regs; -- cgit v0.10.2 From e9f2cefb0cdc2aea8b70dbf68a3f78b88e57cf34 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sat, 6 Dec 2014 22:05:13 +0100 Subject: usb: phy: generic: migrate to gpio_desc Change internal gpio handling from integer gpios into gpio descriptors. This change only addresses the internal API and device-tree/ACPI, while the legacy platform data remains integer space based. This change is only build compile tested, and very prone to error. I leave this comment for now in the commit message so that this patch gets some testing as I'm pretty sure it's buggy. Signed-off-by: Robert Jarzmik Acked-by: Linus Walleij Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index f1b719b..d53928f 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -59,16 +59,8 @@ static int nop_set_suspend(struct usb_phy *x, int suspend) static void nop_reset_set(struct usb_phy_generic *nop, int asserted) { - int value; - - if (!gpio_is_valid(nop->gpio_reset)) - return; - - value = asserted; - if (nop->reset_active_low) - value = !value; - - gpio_set_value_cansleep(nop->gpio_reset, value); + if (nop->gpiod_reset) + gpiod_set_value(nop->gpiod_reset, asserted); if (!asserted) usleep_range(10000, 20000); @@ -143,35 +135,38 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, struct usb_phy_generic_platform_data *pdata) { enum usb_phy_type type = USB_PHY_TYPE_USB2; - int err; + int err = 0; u32 clk_rate = 0; bool needs_vcc = false; - nop->reset_active_low = true; /* default behaviour */ - if (dev->of_node) { struct device_node *node = dev->of_node; - enum of_gpio_flags flags = 0; if (of_property_read_u32(node, "clock-frequency", &clk_rate)) clk_rate = 0; needs_vcc = of_property_read_bool(node, "vcc-supply"); - nop->gpio_reset = of_get_named_gpio_flags(node, "reset-gpios", - 0, &flags); - if (nop->gpio_reset == -EPROBE_DEFER) - return -EPROBE_DEFER; - - nop->reset_active_low = flags & OF_GPIO_ACTIVE_LOW; - + nop->gpiod_reset = devm_gpiod_get(dev, "reset-gpios"); + err = PTR_ERR(nop->gpiod_reset); } else if (pdata) { type = pdata->type; clk_rate = pdata->clk_rate; needs_vcc = pdata->needs_vcc; - nop->gpio_reset = pdata->gpio_reset; - } else { - nop->gpio_reset = -1; + if (gpio_is_valid(gpio->gpio_reset)) { + err = devm_gpio_request_one(dev, pdata->gpio_reset, 0, + dev_name(dev)); + if (!err) + nop->gpiod_reset = + gpio_to_desc(pdata->gpio_reset); + } + } + + if (err == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (err) { + dev_err(dev, "Error requesting RESET GPIO\n"); + return err; } nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg), @@ -201,24 +196,6 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, return -EPROBE_DEFER; } - if (gpio_is_valid(nop->gpio_reset)) { - unsigned long gpio_flags; - - /* Assert RESET */ - if (nop->reset_active_low) - gpio_flags = GPIOF_OUT_INIT_LOW; - else - gpio_flags = GPIOF_OUT_INIT_HIGH; - - err = devm_gpio_request_one(dev, nop->gpio_reset, - gpio_flags, dev_name(dev)); - if (err) { - dev_err(dev, "Error requesting RESET GPIO %d\n", - nop->gpio_reset); - return err; - } - } - nop->dev = dev; nop->phy.dev = nop->dev; nop->phy.label = "nop-xceiv"; diff --git a/drivers/usb/phy/phy-generic.h b/drivers/usb/phy/phy-generic.h index d8feacc..09924fd 100644 --- a/drivers/usb/phy/phy-generic.h +++ b/drivers/usb/phy/phy-generic.h @@ -2,14 +2,14 @@ #define _PHY_GENERIC_H_ #include +#include struct usb_phy_generic { struct usb_phy phy; struct device *dev; struct clk *clk; struct regulator *vcc; - int gpio_reset; - bool reset_active_low; + struct gpio_desc *gpiod_reset; }; int usb_gen_phy_init(struct usb_phy *phy); -- cgit v0.10.2 From 7bdea87a871f217a8a463f9203e6d0c702b7fda9 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sat, 6 Dec 2014 22:05:14 +0100 Subject: usb: phy: nop: device tree documentation for vbus Enhance the phy documentation by adding 2 new optional bindings : - the vbus gpio, which detects usb insertion - the vbus regulator, which provides current drawn from the usb cable Signed-off-by: Robert Jarzmik Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt index 8db5b33..5be01c8 100644 --- a/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt +++ b/Documentation/devicetree/bindings/usb/usb-nop-xceiv.txt @@ -17,6 +17,11 @@ Optional properties: - reset-gpios: Should specify the GPIO for reset. +- vbus-detect-gpio: should specify the GPIO detecting a VBus insertion + (see Documentation/devicetree/bindings/gpio/gpio.txt) +- vbus-regulator : should specifiy the regulator supplying current drawn from + the VBus line (see Documentation/devicetree/bindings/regulator/regulator.txt). + Example: hsusb1_phy { @@ -26,8 +31,11 @@ Example: clock-names = "main_clk"; vcc-supply = <&hsusb1_vcc_regulator>; reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; + vbus-detect-gpio = <&gpio2 13 GPIO_ACTIVE_HIGH>; + vbus-regulator = <&vbus_regulator>; }; hsusb1_phy is a NOP USB PHY device that gets its clock from an oscillator and expects that clock to be configured to 19.2MHz by the NOP PHY driver. hsusb1_vcc_regulator provides power to the PHY and GPIO 7 controls RESET. +GPIO 13 detects VBus insertion, and accordingly notifies the vbus-regulator. -- cgit v0.10.2 From 7acc9973e3c42de9926b28eec8ae3434dfdde3be Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Sat, 6 Dec 2014 22:05:15 +0100 Subject: usb: phy: generic: add vbus support Add support for vbus detection and power supply. This code is more or less stolen from phy-gpio-vbus-usb.c, and aims at providing a detection mechanism for VBus (ie. usb cable plug) based on a GPIO line, and a power supply activation which draws current from the VBus. [ balbi@ti.com : fix build break ] Signed-off-by: Robert Jarzmik Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index d53928f..dd05254 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,9 @@ #include "phy-generic.h" +#define VBUS_IRQ_FLAGS \ + (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING) + struct platform_device *usb_phy_generic_register(void) { return platform_device_register_simple("usb_phy_generic", @@ -66,6 +70,73 @@ static void nop_reset_set(struct usb_phy_generic *nop, int asserted) usleep_range(10000, 20000); } +/* interface to regulator framework */ +static void nop_set_vbus_draw(struct usb_phy_generic *nop, unsigned mA) +{ + struct regulator *vbus_draw = nop->vbus_draw; + int enabled; + int ret; + + if (!vbus_draw) + return; + + enabled = nop->vbus_draw_enabled; + if (mA) { + regulator_set_current_limit(vbus_draw, 0, 1000 * mA); + if (!enabled) { + ret = regulator_enable(vbus_draw); + if (ret < 0) + return; + nop->vbus_draw_enabled = 1; + } + } else { + if (enabled) { + ret = regulator_disable(vbus_draw); + if (ret < 0) + return; + nop->vbus_draw_enabled = 0; + } + } + nop->mA = mA; +} + + +static irqreturn_t nop_gpio_vbus_thread(int irq, void *data) +{ + struct usb_phy_generic *nop = data; + struct usb_otg *otg = nop->phy.otg; + int vbus, status; + + vbus = gpiod_get_value(nop->gpiod_vbus); + if ((vbus ^ nop->vbus) == 0) + return IRQ_HANDLED; + nop->vbus = vbus; + + if (vbus) { + status = USB_EVENT_VBUS; + otg->state = OTG_STATE_B_PERIPHERAL; + nop->phy.last_event = status; + usb_gadget_vbus_connect(otg->gadget); + + /* drawing a "unit load" is *always* OK, except for OTG */ + nop_set_vbus_draw(nop, 100); + + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } else { + nop_set_vbus_draw(nop, 0); + + usb_gadget_vbus_disconnect(otg->gadget); + status = USB_EVENT_NONE; + otg->state = OTG_STATE_B_IDLE; + nop->phy.last_event = status; + + atomic_notifier_call_chain(&nop->phy.notifier, status, + otg->gadget); + } + return IRQ_HANDLED; +} + int usb_gen_phy_init(struct usb_phy *phy) { struct usb_phy_generic *nop = dev_get_drvdata(phy->dev); @@ -149,17 +220,23 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, needs_vcc = of_property_read_bool(node, "vcc-supply"); nop->gpiod_reset = devm_gpiod_get(dev, "reset-gpios"); err = PTR_ERR(nop->gpiod_reset); + if (!err) { + nop->gpiod_vbus = devm_gpiod_get(dev, + "vbus-detect-gpio"); + err = PTR_ERR(nop->gpiod_vbus); + } } else if (pdata) { type = pdata->type; clk_rate = pdata->clk_rate; needs_vcc = pdata->needs_vcc; - if (gpio_is_valid(gpio->gpio_reset)) { + if (gpio_is_valid(pdata->gpio_reset)) { err = devm_gpio_request_one(dev, pdata->gpio_reset, 0, dev_name(dev)); if (!err) nop->gpiod_reset = gpio_to_desc(pdata->gpio_reset); } + nop->gpiod_vbus = pdata->gpiod_vbus; } if (err == -EPROBE_DEFER) @@ -224,6 +301,18 @@ static int usb_phy_generic_probe(struct platform_device *pdev) err = usb_phy_gen_create_phy(dev, nop, dev_get_platdata(&pdev->dev)); if (err) return err; + if (nop->gpiod_vbus) { + err = devm_request_threaded_irq(&pdev->dev, + gpiod_to_irq(nop->gpiod_vbus), + NULL, nop_gpio_vbus_thread, + VBUS_IRQ_FLAGS, "vbus_detect", + nop); + if (err) { + dev_err(&pdev->dev, "can't request irq %i, err: %d\n", + gpiod_to_irq(nop->gpiod_vbus), err); + return err; + } + } nop->phy.init = usb_gen_phy_init; nop->phy.shutdown = usb_gen_phy_shutdown; diff --git a/drivers/usb/phy/phy-generic.h b/drivers/usb/phy/phy-generic.h index 09924fd..0d0eadd 100644 --- a/drivers/usb/phy/phy-generic.h +++ b/drivers/usb/phy/phy-generic.h @@ -3,6 +3,7 @@ #include #include +#include struct usb_phy_generic { struct usb_phy phy; @@ -10,6 +11,11 @@ struct usb_phy_generic { struct clk *clk; struct regulator *vcc; struct gpio_desc *gpiod_reset; + struct gpio_desc *gpiod_vbus; + struct regulator *vbus_draw; + bool vbus_draw_enabled; + unsigned long mA; + unsigned int vbus; }; int usb_gen_phy_init(struct usb_phy *phy); diff --git a/include/linux/usb/usb_phy_generic.h b/include/linux/usb/usb_phy_generic.h index 68adae8..c13632d5 100644 --- a/include/linux/usb/usb_phy_generic.h +++ b/include/linux/usb/usb_phy_generic.h @@ -2,6 +2,7 @@ #define __LINUX_USB_NOP_XCEIV_H #include +#include struct usb_phy_generic_platform_data { enum usb_phy_type type; @@ -11,6 +12,7 @@ struct usb_phy_generic_platform_data { unsigned int needs_vcc:1; unsigned int needs_reset:1; /* deprecated */ int gpio_reset; + struct gpio_desc *gpiod_vbus; }; #if IS_ENABLED(CONFIG_NOP_USB_XCEIV) -- cgit v0.10.2 From a7657a93d63d87399b53d15999e77973b4f02cf3 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Mon, 5 Jan 2015 10:44:44 +0100 Subject: tools: ffs-aio-example: add missing wMaxPacketSize for HS descs It's needed, to have more than 64 bytes of maxpacketsize. Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c index adc310a..1f44a29 100644 --- a/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c +++ b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c @@ -103,12 +103,14 @@ static const struct { .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 1 | USB_DIR_IN, .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = htole16(512), }, .bulk_source = { .bLength = sizeof(descriptors.hs_descs.bulk_source), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 2 | USB_DIR_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = htole16(512), }, }, }; -- cgit v0.10.2 From 67913bbd0b62d297520667cd3057aff5a5890ca6 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Wed, 10 Sep 2014 17:50:24 +0200 Subject: usb: gadget: f_fs: refactor and document __ffs_ep0_read_events better Instead of using variable length array, use a static length equal to the size of the ffs->ev.types array. This gets rid of a sparse warning: drivers/usb/gadget/function/f_fs.c:401:44: warning: Variable length array is used. and makes it more explicit that the array has a very tight upper size limit. Also add some more documentation about the ev.types array and how its size is limited and affects the rest of the code. Reported-by: Dan Carpenter Reported-by: Rohith Seelaboyina Signed-off-by: Michal Nazarewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 63314ed..a00ee97 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -390,17 +390,20 @@ done_spin: return ret; } +/* Called with ffs->ev.waitq.lock and ffs->mutex held, both released on exit. */ static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf, size_t n) { /* - * We are holding ffs->ev.waitq.lock and ffs->mutex and we need - * to release them. + * n cannot be bigger than ffs->ev.count, which cannot be bigger than + * size of ffs->ev.types array (which is four) so that's how much space + * we reserve. */ - struct usb_functionfs_event events[n]; + struct usb_functionfs_event events[ARRAY_SIZE(ffs->ev.types)]; + const size_t size = n * sizeof *events; unsigned i = 0; - memset(events, 0, sizeof events); + memset(events, 0, size); do { events[i].type = ffs->ev.types[i]; @@ -410,19 +413,15 @@ static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf, } } while (++i < n); - if (n < ffs->ev.count) { - ffs->ev.count -= n; + ffs->ev.count -= n; + if (ffs->ev.count) memmove(ffs->ev.types, ffs->ev.types + n, ffs->ev.count * sizeof *ffs->ev.types); - } else { - ffs->ev.count = 0; - } spin_unlock_irq(&ffs->ev.waitq.lock); mutex_unlock(&ffs->mutex); - return unlikely(__copy_to_user(buf, events, sizeof events)) - ? -EFAULT : sizeof events; + return unlikely(__copy_to_user(buf, events, size)) ? -EFAULT : size; } static ssize_t ffs_ep0_read(struct file *file, char __user *buf, @@ -2377,6 +2376,13 @@ static void __ffs_event_add(struct ffs_data *ffs, if (ffs->setup_state == FFS_SETUP_PENDING) ffs->setup_state = FFS_SETUP_CANCELLED; + /* + * Logic of this function guarantees that there are at most four pending + * evens on ffs->ev.types queue. This is important because the queue + * has space for four elements only and __ffs_ep0_read_events function + * depends on that limit as well. If more event types are added, those + * limits have to be revisited or guaranteed to still hold. + */ switch (type) { case FUNCTIONFS_RESUME: rem_type2 = FUNCTIONFS_SUSPEND; -- cgit v0.10.2 From 42c1ecff0e5bdd35659d143ad3d0e40e190b4050 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 12 Jan 2015 16:37:55 +0800 Subject: usb: gadget: pxa27x_udc: delete pullup operation at .udc_start and .udc_stop UDC core has already done it before .udc_stop and after .udc_start. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index c61a896..6a855fc 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -1809,7 +1809,6 @@ static int pxa27x_udc_start(struct usb_gadget *g, /* first hook up the driver ... */ udc->driver = driver; - dplus_pullup(udc, 1); if (!IS_ERR_OR_NULL(udc->transceiver)) { retval = otg_set_peripheral(udc->transceiver->otg, @@ -1862,7 +1861,6 @@ static int pxa27x_udc_stop(struct usb_gadget *g) stop_activity(udc, NULL); udc_disable(udc); - dplus_pullup(udc, 0); udc->driver = NULL; -- cgit v0.10.2 From 14d19d9f05184c38001be47b7567c8d53fce88ff Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 12 Jan 2015 16:37:56 +0800 Subject: usb: gadget: pxa27x_udc: delete pullup operation at .udc_start and .udc_stop UDC core has already done it before .udc_stop and after .udc_start. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/pxa25x_udc.c b/drivers/usb/gadget/udc/pxa25x_udc.c index 8550b2d..f6cbe66 100644 --- a/drivers/usb/gadget/udc/pxa25x_udc.c +++ b/drivers/usb/gadget/udc/pxa25x_udc.c @@ -1272,7 +1272,6 @@ static int pxa25x_udc_start(struct usb_gadget *g, goto bind_fail; } - pullup(dev); dump_state(dev); return 0; bind_fail: @@ -1339,7 +1338,6 @@ static int pxa25x_udc_stop(struct usb_gadget*g) local_irq_disable(); dev->pullup = 0; - pullup(dev); stop_activity(dev, NULL); local_irq_enable(); -- cgit v0.10.2 From 487d60cc09237d24f93ec2892cd8437ae689d7e0 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 12 Jan 2015 16:37:57 +0800 Subject: usb: gadget: mv_udc_core: delete pullup operation at .udc_start UDC core has already done it after .udc_start. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c index 253f3df..d32160d 100644 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ b/drivers/usb/gadget/udc/mv_udc_core.c @@ -1378,9 +1378,6 @@ static int mv_udc_start(struct usb_gadget *gadget, } } - /* pullup is always on */ - mv_udc_pullup(&udc->gadget, 1); - /* When boot with cable attached, there will be no vbus irq occurred */ if (udc->qwork) queue_work(udc->qwork, &udc->vbus_work); -- cgit v0.10.2 From f6c015922c1dc99c53fb2ca95361086e65897b7c Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:41 +0100 Subject: usb: dwc2: gadget: register gadget handle to the phy Bind peripheral controller to the phy on udc_start. Unbind on udc_stop. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 1a8e0a6..b5332f0 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2878,6 +2878,8 @@ static int s3c_hsotg_udc_start(struct usb_gadget *gadget, } s3c_hsotg_phy_enable(hsotg); + if (!IS_ERR_OR_NULL(hsotg->uphy)) + otg_set_peripheral(hsotg->uphy->otg, &hsotg->gadget); spin_lock_irqsave(&hsotg->lock, flags); s3c_hsotg_init(hsotg); @@ -2927,6 +2929,8 @@ static int s3c_hsotg_udc_stop(struct usb_gadget *gadget) spin_unlock_irqrestore(&hsotg->lock, flags); + if (!IS_ERR_OR_NULL(hsotg->uphy)) + otg_set_peripheral(hsotg->uphy->otg, NULL); s3c_hsotg_phy_disable(hsotg); regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); -- cgit v0.10.2 From 6ff2e8326fb50aee80f93e12de367065cc089aae Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:42 +0100 Subject: usb: dwc2: gadget: mask fifo empty irq with dma When using DMA, keep fifo empty interrupt disabled. Otherwise core is flooded by interrupts. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b5332f0..843f3ee 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2170,8 +2170,8 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) * interrupts. */ - writel(((hsotg->dedicated_fifos) ? DIEPMSK_TXFIFOEMPTY | - DIEPMSK_INTKNTXFEMPMSK : 0) | + writel(((hsotg->dedicated_fifos && !using_dma(hsotg)) ? + DIEPMSK_TXFIFOEMPTY | DIEPMSK_INTKNTXFEMPMSK : 0) | DIEPMSK_EPDISBLDMSK | DIEPMSK_XFERCOMPLMSK | DIEPMSK_TIMEOUTMSK | DIEPMSK_AHBERRMSK | DIEPMSK_INTKNEPMISMSK, -- cgit v0.10.2 From b787d75503fb6f68aea5220ca23bb0c02d14ae74 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:43 +0100 Subject: usb: dwc2: gadget: don't process XferCompl on setup packet Only process DOEPINT.XferCompl on data packet as DOEPINTn.SetUp can occur with or without DOEPINT.XferCompl. When DOEPINT.SetUp occurs with DOEPINT.XferCompl, only DOEPINT.SetUp needs to be handled. Moreover, ignore DOEPINT.XferCompl when it occurs with DOEPINT.StupPktRcvd as driver needs to wait for DOEPINT.SetUp to continue. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 843f3ee..45afbf8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1810,6 +1810,10 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, dev_dbg(hsotg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n", __func__, idx, dir_in ? "in" : "out", ints); + /* Don't process XferCompl interrupt if it is a setup packet */ + if (idx == 0 && (ints & (DXEPINT_SETUP | DXEPINT_SETUP_RCVD))) + ints &= ~DXEPINT_XFERCOMPL; + if (ints & DXEPINT_XFERCOMPL) { if (hs_ep->isochronous && hs_ep->interval == 1) { if (ctrl & DXEPCTL_EOFRNUM) diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 51248b9..d018ebe 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -541,6 +541,7 @@ #define DIEPINT(_a) HSOTG_REG(0x908 + ((_a) * 0x20)) #define DOEPINT(_a) HSOTG_REG(0xB08 + ((_a) * 0x20)) +#define DXEPINT_SETUP_RCVD (1 << 15) #define DXEPINT_INEPNAKEFF (1 << 6) #define DXEPINT_BACK2BACKSETUP (1 << 6) #define DXEPINT_INTKNEPMIS (1 << 5) -- cgit v0.10.2 From 3f95001db7998c350422708a437694dda07bc04e Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:44 +0100 Subject: usb: dwc2: gadget: don't embed ep0 buffers When using DMA, data of the previous setup packet can be read back from cache because ep0 and ctrl buffers are embedded in struct s3c_hsotg. Allocate buffers instead of embedding them. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Reviewed-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 0d2ee29..7db83d0 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -434,6 +434,9 @@ struct dwc2_hw_params { u32 snpsid; }; +/* Size of control and EP0 buffers */ +#define DWC2_CTRL_BUFF_SIZE 8 + /** * struct dwc2_hsotg - Holds the state of the driver, including the non-periodic * and periodic schedules @@ -684,8 +687,8 @@ struct dwc2_hsotg { struct usb_request *ep0_reply; struct usb_request *ctrl_req; - u8 ep0_buff[8]; - u8 ctrl_buff[8]; + void *ep0_buff; + void *ctrl_buff; struct usb_gadget gadget; unsigned int enabled:1; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 45afbf8..35d346f 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3486,6 +3486,22 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) s3c_hsotg_hw_cfg(hsotg); s3c_hsotg_init(hsotg); + hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, + DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); + if (!hsotg->ctrl_buff) { + dev_err(dev, "failed to allocate ctrl request buff\n"); + ret = -ENOMEM; + goto err_supplies; + } + + hsotg->ep0_buff = devm_kzalloc(hsotg->dev, + DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); + if (!hsotg->ep0_buff) { + dev_err(dev, "failed to allocate ctrl reply buff\n"); + ret = -ENOMEM; + goto err_supplies; + } + ret = devm_request_irq(hsotg->dev, irq, s3c_hsotg_irq, IRQF_SHARED, dev_name(hsotg->dev), hsotg); if (ret < 0) { -- cgit v0.10.2 From c139ec27f9c77c7c215d7989f6d77e2dbaba63df Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:45 +0100 Subject: usb: dwc2: gadget: fix error path in dwc2_gadget_init In the error path, s3c_hsotg_phy_disable should be called after a call to s3c_hsotg_phy_enable is made. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 35d346f..a4d2c26 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3476,7 +3476,7 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) if (ret) { dev_err(dev, "failed to enable supplies: %d\n", ret); - goto err_supplies; + goto err_clk; } /* usb phy enable */ @@ -3510,7 +3510,7 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); dev_err(dev, "cannot claim IRQ for gadget\n"); - goto err_clk; + goto err_supplies; } /* hsotg->num_of_eps holds number of EPs other than ep0 */ -- cgit v0.10.2 From 5f05048e5473a0570b1db9c2828d2a469e8bbbee Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:46 +0100 Subject: usb: dwc2: gadget: write correct value in ahbcfg register HBstLen is GAHBCFG[4:1]. Use GAHBCFG_HBSTLEN_SHIFT to write burst- length at correct position. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index a4d2c26..b69b8fa8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2160,7 +2160,7 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) if (using_dma(hsotg)) writel(GAHBCFG_GLBL_INTR_EN | GAHBCFG_DMA_EN | - GAHBCFG_HBSTLEN_INCR4, + (GAHBCFG_HBSTLEN_INCR4 << GAHBCFG_HBSTLEN_SHIFT), hsotg->regs + GAHBCFG); else writel(((hsotg->dedicated_fifos) ? (GAHBCFG_NP_TXF_EMP_LVL | -- cgit v0.10.2 From f5090044763fd954a002a6ef466fcbc21033518f Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:47 +0100 Subject: usb: dwc2: gadget: don't erase gahbcfg register when enabling dma Do a read-modify-write instead of only setting DMAEn bit. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b69b8fa8..b98622f 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2827,8 +2827,8 @@ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) writel(GUSBCFG_PHYIF16 | GUSBCFG_TOUTCAL(7) | (0x5 << 10), hsotg->regs + GUSBCFG); - writel(using_dma(hsotg) ? GAHBCFG_DMA_EN : 0x0, - hsotg->regs + GAHBCFG); + if (using_dma(hsotg)) + __orr32(hsotg->regs + GAHBCFG, GAHBCFG_DMA_EN); } /** -- cgit v0.10.2 From edd74be83047f439e9507cedbbbef0891f3a9a48 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:48 +0100 Subject: usb: dwc2: gadget: add device tree property to enable dma * Add an of specific function to parse device node properties. * Enable dma usage only if device tree property 'g_use_dma' is present. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 7db83d0..376a008 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -563,6 +563,7 @@ struct dwc2_hw_params { * @setup: NAK management for EP0 SETUP * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework + * @g_using_dma: Indicate if dma usage is enabled */ struct dwc2_hsotg { struct device *dev; @@ -696,6 +697,7 @@ struct dwc2_hsotg { unsigned int setup:1; unsigned long last_rst; struct s3c_hsotg_ep *eps; + u32 g_using_dma; #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ }; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b98622f..c24cb3b 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -85,11 +85,11 @@ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg); * a core reset. This means we either need to fix the gadgets to take * account of DMA alignment, or add bounce buffers (yuerk). * - * Until this issue is sorted out, we always return 'false'. + * g_using_dma is set depending on dts flag. */ static inline bool using_dma(struct dwc2_hsotg *hsotg) { - return false; /* support is not complete */ + return hsotg->g_using_dma; } /** @@ -3402,6 +3402,18 @@ static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) debugfs_remove(hsotg->debug_root); } +#ifdef CONFIG_OF +static void s3c_hsotg_of_probe(struct dwc2_hsotg *hsotg) +{ + struct device_node *np = hsotg->dev->of_node; + + /* Enable dma if requested in device tree */ + hsotg->g_using_dma = of_property_read_bool(np, "g-use-dma"); +} +#else +static inline void s3c_hsotg_of_probe(struct dwc2_hsotg *hsotg) { } +#endif + /** * dwc2_gadget_init - init function for gadget * @dwc2: The data structure for the DWC2 driver. @@ -3419,6 +3431,8 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) /* Set default UTMI width */ hsotg->phyif = GUSBCFG_PHYIF16; + s3c_hsotg_of_probe(hsotg); + /* * If platform probe couldn't find a generic PHY or an old style * USB PHY, fall back to pdata -- cgit v0.10.2 From b2f93ef30fc64dab53f64bcabe23d9377abf454e Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:49 +0100 Subject: Documentation: dt-bindings: add dt binding info for dwc2 g-use-dma Indicate if gadget driver must use dma for usb transfers. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/dwc2.txt b/Documentation/devicetree/bindings/usb/dwc2.txt index 482f815..a7a9cb6 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.txt +++ b/Documentation/devicetree/bindings/usb/dwc2.txt @@ -20,6 +20,7 @@ Optional properties: Refer to phy/phy-bindings.txt for generic phy consumer properties - dr_mode: shall be one of "host", "peripheral" and "otg" Refer to usb/generic.txt +- g-use-dma: enable dma usage in gadget driver. Example: -- cgit v0.10.2 From c6f5c050e2a7f6776ffa5594b6b2eedbf0137fe8 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:50 +0100 Subject: usb: dwc2: gadget: add bi-directional endpoint support GHWCFG1 provides hardware configuration of each endpoint. Use it to configure the endpoints instead of assuming all even endpoint are OUT and all odd endpoints are IN. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 376a008..7a0309d 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -696,7 +696,8 @@ struct dwc2_hsotg { unsigned int connected:1; unsigned int setup:1; unsigned long last_rst; - struct s3c_hsotg_ep *eps; + struct s3c_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; + struct s3c_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; u32 g_using_dma; #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ }; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c24cb3b..530c246 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -65,6 +65,15 @@ static inline void __bic32(void __iomem *ptr, u32 val) writel(readl(ptr) & ~val, ptr); } +static inline struct s3c_hsotg_ep *index_to_ep(struct dwc2_hsotg *hsotg, + u32 ep_index, u32 dir_in) +{ + if (dir_in) + return hsotg->eps_in[ep_index]; + else + return hsotg->eps_out[ep_index]; +} + /* forward declaration of functions */ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg); @@ -819,7 +828,7 @@ static void s3c_hsotg_complete_oursetup(struct usb_ep *ep, static struct s3c_hsotg_ep *ep_from_windex(struct dwc2_hsotg *hsotg, u32 windex) { - struct s3c_hsotg_ep *ep = &hsotg->eps[windex & 0x7F]; + struct s3c_hsotg_ep *ep; int dir = (windex & USB_DIR_IN) ? 1 : 0; int idx = windex & 0x7F; @@ -829,6 +838,8 @@ static struct s3c_hsotg_ep *ep_from_windex(struct dwc2_hsotg *hsotg, if (idx > hsotg->num_of_eps) return NULL; + ep = index_to_ep(hsotg, idx, dir); + if (idx && ep->dir_in != dir) return NULL; @@ -889,7 +900,7 @@ static int s3c_hsotg_send_reply(struct dwc2_hsotg *hsotg, static int s3c_hsotg_process_req_status(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { - struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep0 = hsotg->eps_out[0]; struct s3c_hsotg_ep *ep; __le16 reply; int ret; @@ -960,7 +971,7 @@ static struct s3c_hsotg_req *get_ep_head(struct s3c_hsotg_ep *hs_ep) static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { - struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep0 = hsotg->eps_out[0]; struct s3c_hsotg_req *hs_req; bool restart; bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE); @@ -1040,7 +1051,7 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg); */ static void s3c_hsotg_stall_ep0(struct dwc2_hsotg *hsotg) { - struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep0 = hsotg->eps_out[0]; u32 reg; u32 ctrl; @@ -1080,7 +1091,7 @@ static void s3c_hsotg_stall_ep0(struct dwc2_hsotg *hsotg) static void s3c_hsotg_process_control(struct dwc2_hsotg *hsotg, struct usb_ctrlrequest *ctrl) { - struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; + struct s3c_hsotg_ep *ep0 = hsotg->eps_out[0]; int ret = 0; u32 dcfg; @@ -1201,9 +1212,9 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) return; } - hsotg->eps[0].dir_in = 0; + hsotg->eps_out[0]->dir_in = 0; - ret = s3c_hsotg_ep_queue(&hsotg->eps[0].ep, req, GFP_ATOMIC); + ret = s3c_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC); if (ret < 0) { dev_err(hsotg->dev, "%s: failed queue (%d)\n", __func__, ret); /* @@ -1293,7 +1304,7 @@ static void s3c_hsotg_complete_request(struct dwc2_hsotg *hsotg, */ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) { - struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep_idx]; + struct s3c_hsotg_ep *hs_ep = hsotg->eps_out[ep_idx]; struct s3c_hsotg_req *hs_req = hs_ep->req; void __iomem *fifo = hsotg->regs + EPFIFO(ep_idx); int to_read; @@ -1367,13 +1378,14 @@ static void s3c_hsotg_send_zlp(struct dwc2_hsotg *hsotg, } if (req->req.length == 0) { - hsotg->eps[0].sent_zlp = 1; + hsotg->eps_out[0]->sent_zlp = 1; s3c_hsotg_enqueue_setup(hsotg); return; } - hsotg->eps[0].dir_in = 1; - hsotg->eps[0].sent_zlp = 1; + /* eps_out[0] is used in both directions */ + hsotg->eps_out[0]->dir_in = 1; + hsotg->eps_out[0]->sent_zlp = 1; dev_dbg(hsotg->dev, "sending zero-length packet\n"); @@ -1402,7 +1414,7 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum, bool was_setup) { u32 epsize = readl(hsotg->regs + DOEPTSIZ(epnum)); - struct s3c_hsotg_ep *hs_ep = &hsotg->eps[epnum]; + struct s3c_hsotg_ep *hs_ep = hsotg->eps_out[epnum]; struct s3c_hsotg_req *hs_req = hs_ep->req; struct usb_request *req = &hs_req->req; unsigned size_left = DXEPTSIZ_XFERSIZE_GET(epsize); @@ -1591,14 +1603,18 @@ static u32 s3c_hsotg_ep0_mps(unsigned int mps) * the hardware control registers to reflect this. */ static void s3c_hsotg_set_ep_maxpacket(struct dwc2_hsotg *hsotg, - unsigned int ep, unsigned int mps) + unsigned int ep, unsigned int mps, unsigned int dir_in) { - struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep]; + struct s3c_hsotg_ep *hs_ep; void __iomem *regs = hsotg->regs; u32 mpsval; u32 mcval; u32 reg; + hs_ep = index_to_ep(hsotg, ep, dir_in); + if (!hs_ep) + return; + if (ep == 0) { /* EP0 is a special case */ mpsval = s3c_hsotg_ep0_mps(mps); @@ -1617,17 +1633,12 @@ static void s3c_hsotg_set_ep_maxpacket(struct dwc2_hsotg *hsotg, hs_ep->ep.maxpacket = mpsval; } - /* - * update both the in and out endpoint controldir_ registers, even - * if one of the directions may not be in use. - */ - - reg = readl(regs + DIEPCTL(ep)); - reg &= ~DXEPCTL_MPS_MASK; - reg |= mpsval; - writel(reg, regs + DIEPCTL(ep)); - - if (ep) { + if (dir_in) { + reg = readl(regs + DIEPCTL(ep)); + reg &= ~DXEPCTL_MPS_MASK; + reg |= mpsval; + writel(reg, regs + DIEPCTL(ep)); + } else { reg = readl(regs + DOEPCTL(ep)); reg &= ~DXEPCTL_MPS_MASK; reg |= mpsval; @@ -1727,7 +1738,7 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, } /* Finish ZLP handling for IN EP0 transactions */ - if (hsotg->eps[0].sent_zlp) { + if (hsotg->eps_out[0]->sent_zlp) { dev_dbg(hsotg->dev, "zlp packet received\n"); s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); return; @@ -1794,7 +1805,7 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, int dir_in) { - struct s3c_hsotg_ep *hs_ep = &hsotg->eps[idx]; + struct s3c_hsotg_ep *hs_ep = index_to_ep(hsotg, idx, dir_in); u32 epint_reg = dir_in ? DIEPINT(idx) : DOEPINT(idx); u32 epctl_reg = dir_in ? DIEPCTL(idx) : DOEPCTL(idx); u32 epsiz_reg = dir_in ? DIEPTSIZ(idx) : DOEPTSIZ(idx); @@ -1807,6 +1818,12 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, /* Clear endpoint interrupts */ writel(ints, hsotg->regs + epint_reg); + if (!hs_ep) { + dev_err(hsotg->dev, "%s:Interrupt for unconfigured ep%d(%s)\n", + __func__, idx, dir_in ? "in" : "out"); + return; + } + dev_dbg(hsotg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n", __func__, idx, dir_in ? "in" : "out", ints); @@ -1973,9 +1990,15 @@ static void s3c_hsotg_irq_enumdone(struct dwc2_hsotg *hsotg) if (ep0_mps) { int i; - s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps); - for (i = 1; i < hsotg->num_of_eps; i++) - s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps); + /* Initialize ep0 for both in and out directions */ + s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps, 1); + s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps, 0); + for (i = 1; i < hsotg->num_of_eps; i++) { + if (hsotg->eps_in[i]) + s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps, 1); + if (hsotg->eps_out[i]) + s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps, 0); + } } /* ensure after enumeration our EP0 is active */ @@ -2032,8 +2055,15 @@ void s3c_hsotg_disconnect(struct dwc2_hsotg *hsotg) return; hsotg->connected = 0; - for (ep = 0; ep < hsotg->num_of_eps; ep++) - kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN); + + for (ep = 0; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + kill_all_requests(hsotg, hsotg->eps_in[ep], + -ESHUTDOWN); + if (hsotg->eps_out[ep]) + kill_all_requests(hsotg, hsotg->eps_out[ep], + -ESHUTDOWN); + } call_gadget(hsotg, disconnect); } @@ -2050,9 +2080,11 @@ static void s3c_hsotg_irq_fifoempty(struct dwc2_hsotg *hsotg, bool periodic) int epno, ret; /* look through for any more data to transmit */ - for (epno = 0; epno < hsotg->num_of_eps; epno++) { - ep = &hsotg->eps[epno]; + ep = index_to_ep(hsotg, epno, 1); + + if (!ep) + continue; if (!ep->dir_in) continue; @@ -2227,13 +2259,13 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | DXEPTSIZ_XFERSIZE(8), hsotg->regs + DOEPTSIZ0); - writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | + writel(s3c_hsotg_ep0_mps(hsotg->eps_out[0]->ep.maxpacket) | DXEPCTL_CNAK | DXEPCTL_EPENA | DXEPCTL_USBACTEP, hsotg->regs + DOEPCTL0); /* enable, but don't activate EP0in */ - writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | + writel(s3c_hsotg_ep0_mps(hsotg->eps_out[0]->ep.maxpacket) | DXEPCTL_USBACTEP, hsotg->regs + DIEPCTL0); s3c_hsotg_enqueue_setup(hsotg); @@ -2330,7 +2362,7 @@ irq_retry: if (time_after(jiffies, hsotg->last_rst + msecs_to_jiffies(200))) { - kill_all_requests(hsotg, &hsotg->eps[0], + kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); s3c_hsotg_core_init_disconnected(hsotg); @@ -2479,7 +2511,7 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, epctrl |= DXEPCTL_SNAK; /* update the endpoint state */ - s3c_hsotg_set_ep_maxpacket(hsotg, hs_ep->index, mps); + s3c_hsotg_set_ep_maxpacket(hsotg, hs_ep->index, mps, dir_in); /* default, set to non-periodic */ hs_ep->isochronous = 0; @@ -2576,7 +2608,7 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) dev_dbg(hsotg->dev, "%s(ep %p)\n", __func__, ep); - if (ep == &hsotg->eps[0].ep) { + if (ep == &hsotg->eps_out[0]->ep) { dev_err(hsotg->dev, "%s: called for ep0\n", __func__); return -EINVAL; } @@ -2675,40 +2707,39 @@ static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value) return 0; } - /* write both IN and OUT control registers */ - - epreg = DIEPCTL(index); - epctl = readl(hs->regs + epreg); - - if (value) { - epctl |= DXEPCTL_STALL + DXEPCTL_SNAK; - if (epctl & DXEPCTL_EPENA) - epctl |= DXEPCTL_EPDIS; + if (hs_ep->dir_in) { + epreg = DIEPCTL(index); + epctl = readl(hs->regs + epreg); + + if (value) { + epctl |= DXEPCTL_STALL + DXEPCTL_SNAK; + if (epctl & DXEPCTL_EPENA) + epctl |= DXEPCTL_EPDIS; + } else { + epctl &= ~DXEPCTL_STALL; + xfertype = epctl & DXEPCTL_EPTYPE_MASK; + if (xfertype == DXEPCTL_EPTYPE_BULK || + xfertype == DXEPCTL_EPTYPE_INTERRUPT) + epctl |= DXEPCTL_SETD0PID; + } + writel(epctl, hs->regs + epreg); } else { - epctl &= ~DXEPCTL_STALL; - xfertype = epctl & DXEPCTL_EPTYPE_MASK; - if (xfertype == DXEPCTL_EPTYPE_BULK || - xfertype == DXEPCTL_EPTYPE_INTERRUPT) - epctl |= DXEPCTL_SETD0PID; - } - - writel(epctl, hs->regs + epreg); - epreg = DOEPCTL(index); - epctl = readl(hs->regs + epreg); + epreg = DOEPCTL(index); + epctl = readl(hs->regs + epreg); - if (value) - epctl |= DXEPCTL_STALL; - else { - epctl &= ~DXEPCTL_STALL; - xfertype = epctl & DXEPCTL_EPTYPE_MASK; - if (xfertype == DXEPCTL_EPTYPE_BULK || - xfertype == DXEPCTL_EPTYPE_INTERRUPT) - epctl |= DXEPCTL_SETD0PID; + if (value) + epctl |= DXEPCTL_STALL; + else { + epctl &= ~DXEPCTL_STALL; + xfertype = epctl & DXEPCTL_EPTYPE_MASK; + if (xfertype == DXEPCTL_EPTYPE_BULK || + xfertype == DXEPCTL_EPTYPE_INTERRUPT) + epctl |= DXEPCTL_SETD0PID; + } + writel(epctl, hs->regs + epreg); } - writel(epctl, hs->regs + epreg); - hs_ep->halted = value; return 0; @@ -2922,8 +2953,12 @@ static int s3c_hsotg_udc_stop(struct usb_gadget *gadget) mutex_lock(&hsotg->init_mutex); /* all endpoints should be shutdown */ - for (ep = 1; ep < hsotg->num_of_eps; ep++) - s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + for (ep = 1; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + s3c_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + s3c_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); + } spin_lock_irqsave(&hsotg->lock, flags); @@ -3009,19 +3044,19 @@ static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { */ static void s3c_hsotg_initep(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *hs_ep, - int epnum) + int epnum, + bool dir_in) { char *dir; if (epnum == 0) dir = ""; - else if ((epnum % 2) == 0) { - dir = "out"; - } else { + else if (dir_in) dir = "in"; - hs_ep->dir_in = 1; - } + else + dir = "out"; + hs_ep->dir_in = dir_in; hs_ep->index = epnum; snprintf(hs_ep->name, sizeof(hs_ep->name), "ep%d%s", epnum, dir); @@ -3045,8 +3080,10 @@ static void s3c_hsotg_initep(struct dwc2_hsotg *hsotg, if (using_dma(hsotg)) { u32 next = DXEPCTL_NEXTEP((epnum + 1) % 15); - writel(next, hsotg->regs + DIEPCTL(epnum)); - writel(next, hsotg->regs + DOEPCTL(epnum)); + if (dir_in) + writel(next, hsotg->regs + DIEPCTL(epnum)); + else + writel(next, hsotg->regs + DOEPCTL(epnum)); } } @@ -3056,24 +3093,56 @@ static void s3c_hsotg_initep(struct dwc2_hsotg *hsotg, * * Read the USB core HW configuration registers */ -static void s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) +static int s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) { - u32 cfg2, cfg3, cfg4; + u32 cfg; + u32 ep_type; + u32 i; + /* check hardware configuration */ - cfg2 = readl(hsotg->regs + 0x48); - hsotg->num_of_eps = (cfg2 >> 10) & 0xF; + cfg = readl(hsotg->regs + GHWCFG2); + hsotg->num_of_eps = (cfg >> 10) & 0xF; + /* Add ep0 */ + hsotg->num_of_eps++; - cfg3 = readl(hsotg->regs + 0x4C); - hsotg->fifo_mem = (cfg3 >> 16); + hsotg->eps_in[0] = devm_kzalloc(hsotg->dev, sizeof(struct s3c_hsotg_ep), + GFP_KERNEL); + if (!hsotg->eps_in[0]) + return -ENOMEM; + /* Same s3c_hsotg_ep is used in both directions for ep0 */ + hsotg->eps_out[0] = hsotg->eps_in[0]; + + cfg = readl(hsotg->regs + GHWCFG1); + for (i = 1; i < hsotg->num_of_eps; i++, cfg >>= 2) { + ep_type = cfg & 3; + /* Direction in or both */ + if (!(ep_type & 2)) { + hsotg->eps_in[i] = devm_kzalloc(hsotg->dev, + sizeof(struct s3c_hsotg_ep), GFP_KERNEL); + if (!hsotg->eps_in[i]) + return -ENOMEM; + } + /* Direction out or both */ + if (!(ep_type & 1)) { + hsotg->eps_out[i] = devm_kzalloc(hsotg->dev, + sizeof(struct s3c_hsotg_ep), GFP_KERNEL); + if (!hsotg->eps_out[i]) + return -ENOMEM; + } + } + + cfg = readl(hsotg->regs + GHWCFG3); + hsotg->fifo_mem = (cfg >> 16); - cfg4 = readl(hsotg->regs + 0x50); - hsotg->dedicated_fifos = (cfg4 >> 25) & 1; + cfg = readl(hsotg->regs + GHWCFG4); + hsotg->dedicated_fifos = (cfg >> 25) & 1; dev_info(hsotg->dev, "EPs: %d, %s fifos, %d entries in SPRAM\n", hsotg->num_of_eps, hsotg->dedicated_fifos ? "dedicated" : "shared", hsotg->fifo_mem); + return 0; } /** @@ -3368,17 +3437,33 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) if (IS_ERR(hsotg->debug_fifo)) dev_err(hsotg->dev, "%s: failed to create fifo\n", __func__); - /* create one file for each endpoint */ - + /* Create one file for each out endpoint */ for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { - struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; + struct s3c_hsotg_ep *ep; - ep->debugfs = debugfs_create_file(ep->name, 0444, - root, ep, &ep_fops); + ep = hsotg->eps_out[epidx]; + if (ep) { + ep->debugfs = debugfs_create_file(ep->name, 0444, + root, ep, &ep_fops); - if (IS_ERR(ep->debugfs)) - dev_err(hsotg->dev, "failed to create %s debug file\n", - ep->name); + if (IS_ERR(ep->debugfs)) + dev_err(hsotg->dev, "failed to create %s debug file\n", + ep->name); + } + } + /* Create one file for each in endpoint. EP0 is handled with out eps */ + for (epidx = 1; epidx < hsotg->num_of_eps; epidx++) { + struct s3c_hsotg_ep *ep; + + ep = hsotg->eps_in[epidx]; + if (ep) { + ep->debugfs = debugfs_create_file(ep->name, 0444, + root, ep, &ep_fops); + + if (IS_ERR(ep->debugfs)) + dev_err(hsotg->dev, "failed to create %s debug file\n", + ep->name); + } } } @@ -3393,8 +3478,10 @@ static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) unsigned epidx; for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { - struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; - debugfs_remove(ep->debugfs); + if (hsotg->eps_in[epidx]) + debugfs_remove(hsotg->eps_in[epidx]->debugfs); + if (hsotg->eps_out[epidx]) + debugfs_remove(hsotg->eps_out[epidx]->debugfs); } debugfs_remove(hsotg->debug_file); @@ -3423,7 +3510,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) { struct device *dev = hsotg->dev; struct s3c_hsotg_plat *plat = dev->platform_data; - struct s3c_hsotg_ep *eps; int epnum; int ret; int i; @@ -3497,7 +3583,12 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) s3c_hsotg_phy_enable(hsotg); s3c_hsotg_corereset(hsotg); - s3c_hsotg_hw_cfg(hsotg); + ret = s3c_hsotg_hw_cfg(hsotg); + if (ret) { + dev_err(hsotg->dev, "Hardware configuration failed: %d\n", ret); + goto err_clk; + } + s3c_hsotg_init(hsotg); hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, @@ -3535,33 +3626,30 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) goto err_supplies; } - eps = kcalloc(hsotg->num_of_eps + 1, sizeof(struct s3c_hsotg_ep), - GFP_KERNEL); - if (!eps) { - ret = -ENOMEM; - goto err_supplies; - } - - hsotg->eps = eps; - /* setup endpoint information */ INIT_LIST_HEAD(&hsotg->gadget.ep_list); - hsotg->gadget.ep0 = &hsotg->eps[0].ep; + hsotg->gadget.ep0 = &hsotg->eps_out[0]->ep; /* allocate EP0 request */ - hsotg->ctrl_req = s3c_hsotg_ep_alloc_request(&hsotg->eps[0].ep, + hsotg->ctrl_req = s3c_hsotg_ep_alloc_request(&hsotg->eps_out[0]->ep, GFP_KERNEL); if (!hsotg->ctrl_req) { dev_err(dev, "failed to allocate ctrl req\n"); ret = -ENOMEM; - goto err_ep_mem; + goto err_supplies; } /* initialise the endpoints now the core has been initialised */ - for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) - s3c_hsotg_initep(hsotg, &hsotg->eps[epnum], epnum); + for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) { + if (hsotg->eps_in[epnum]) + s3c_hsotg_initep(hsotg, hsotg->eps_in[epnum], + epnum, 1); + if (hsotg->eps_out[epnum]) + s3c_hsotg_initep(hsotg, hsotg->eps_out[epnum], + epnum, 0); + } /* disable power and clock */ s3c_hsotg_phy_disable(hsotg); @@ -3570,12 +3658,12 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) hsotg->supplies); if (ret) { dev_err(dev, "failed to disable supplies: %d\n", ret); - goto err_ep_mem; + goto err_supplies; } ret = usb_add_gadget_udc(dev, &hsotg->gadget); if (ret) - goto err_ep_mem; + goto err_supplies; s3c_hsotg_create_debug(hsotg); @@ -3583,8 +3671,6 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) return 0; -err_ep_mem: - kfree(eps); err_supplies: s3c_hsotg_phy_disable(hsotg); err_clk: @@ -3630,8 +3716,12 @@ int s3c_hsotg_suspend(struct dwc2_hsotg *hsotg) s3c_hsotg_phy_disable(hsotg); - for (ep = 0; ep < hsotg->num_of_eps; ep++) - s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + for (ep = 0; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + s3c_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + s3c_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); + } ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); -- cgit v0.10.2 From cec87f1d2686057654e4565b8eaab4f539e9afca Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:51 +0100 Subject: usb: dwc2: gadget: check interrupts for all endpoints Current code does not check endpoint 15 interrupt. Use number of endpoint configured in hardware instead of the hardcoded value. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 530c246..b7879fd 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2337,12 +2337,14 @@ irq_retry: dev_dbg(hsotg->dev, "%s: daint=%08x\n", __func__, daint); - for (ep = 0; ep < 15 && daint_out; ep++, daint_out >>= 1) { + for (ep = 0; ep < hsotg->num_of_eps && daint_out; + ep++, daint_out >>= 1) { if (daint_out & 1) s3c_hsotg_epint(hsotg, ep, 0); } - for (ep = 0; ep < 15 && daint_in; ep++, daint_in >>= 1) { + for (ep = 0; ep < hsotg->num_of_eps && daint_in; + ep++, daint_in >>= 1) { if (daint_in & 1) s3c_hsotg_epint(hsotg, ep, 1); } -- cgit v0.10.2 From 0a176279db6817cf865ed4c685e4a414be8226fd Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:52 +0100 Subject: usb: dwc2: gadget: configure fifos from device tree As fifo size can vary between SOCs, add possibility to configure them from device tree. Fifo sizes used by the legacy driver will be used If they are not provided by the device tree. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Reviewed-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 7a0309d..df6a64f 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -193,6 +193,13 @@ enum dwc2_lx_state { DWC2_L3, /* Off state */ }; +/* + * Gadget periodic tx fifo sizes as used by legacy driver + * EP0 is not included + */ +#define DWC2_G_P_LEGACY_TX_FIFO_SIZE {256, 256, 256, 256, 768, 768, 768, \ + 768, 0, 0, 0, 0, 0, 0, 0} + /** * struct dwc2_core_params - Parameters for configuring the core * @@ -564,6 +571,9 @@ struct dwc2_hw_params { * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework * @g_using_dma: Indicate if dma usage is enabled + * @g_rx_fifo_sz: Contains rx fifo size value + * @g_np_g_tx_fifo_sz: Contains Non-Periodic tx fifo size value + * @g_tx_fifo_sz: Contains tx fifo size value per endpoints */ struct dwc2_hsotg { struct device *dev; @@ -699,6 +709,9 @@ struct dwc2_hsotg { struct s3c_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; struct s3c_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; u32 g_using_dma; + u32 g_rx_fifo_sz; + u32 g_np_g_tx_fifo_sz; + u32 g_tx_fifo_sz[MAX_EPS_CHANNELS]; #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */ }; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index b7879fd..c43ce7e 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -174,15 +174,14 @@ static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) { unsigned int ep; unsigned int addr; - unsigned int size; int timeout; u32 val; - /* set FIFO sizes to 2048/1024 */ - - writel(2048, hsotg->regs + GRXFSIZ); - writel((2048 << FIFOSIZE_STARTADDR_SHIFT) | - (1024 << FIFOSIZE_DEPTH_SHIFT), hsotg->regs + GNPTXFSIZ); + /* set RX/NPTX FIFO sizes */ + writel(hsotg->g_rx_fifo_sz, hsotg->regs + GRXFSIZ); + writel((hsotg->g_rx_fifo_sz << FIFOSIZE_STARTADDR_SHIFT) | + (hsotg->g_np_g_tx_fifo_sz << FIFOSIZE_DEPTH_SHIFT), + hsotg->regs + GNPTXFSIZ); /* * arange all the rest of the TX FIFOs, as some versions of this @@ -192,35 +191,21 @@ static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) */ /* start at the end of the GNPTXFSIZ, rounded up */ - addr = 2048 + 1024; + addr = hsotg->g_rx_fifo_sz + hsotg->g_np_g_tx_fifo_sz; /* - * Because we have not enough memory to have each TX FIFO of size at - * least 3072 bytes (the maximum single packet size), we create four - * FIFOs of lenght 1024, and four of length 3072 bytes, and assing + * Configure fifos sizes from provided configuration and assign * them to endpoints dynamically according to maxpacket size value of * given endpoint. */ - - /* 256*4=1024 bytes FIFO length */ - size = 256; - for (ep = 1; ep <= 4; ep++) { - val = addr; - val |= size << FIFOSIZE_DEPTH_SHIFT; - WARN_ONCE(addr + size > hsotg->fifo_mem, - "insufficient fifo memory"); - addr += size; - - writel(val, hsotg->regs + DPTXFSIZN(ep)); - } - /* 768*4=3072 bytes FIFO length */ - size = 768; - for (ep = 5; ep <= 8; ep++) { + for (ep = 1; ep < MAX_EPS_CHANNELS; ep++) { + if (!hsotg->g_tx_fifo_sz[ep]) + continue; val = addr; - val |= size << FIFOSIZE_DEPTH_SHIFT; - WARN_ONCE(addr + size > hsotg->fifo_mem, + val |= hsotg->g_tx_fifo_sz[ep] << FIFOSIZE_DEPTH_SHIFT; + WARN_ONCE(addr + hsotg->g_tx_fifo_sz[ep] > hsotg->fifo_mem, "insufficient fifo memory"); - addr += size; + addr += hsotg->g_tx_fifo_sz[ep]; writel(val, hsotg->regs + DPTXFSIZN(ep)); } @@ -3495,9 +3480,42 @@ static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) static void s3c_hsotg_of_probe(struct dwc2_hsotg *hsotg) { struct device_node *np = hsotg->dev->of_node; + u32 len = 0; + u32 i = 0; /* Enable dma if requested in device tree */ hsotg->g_using_dma = of_property_read_bool(np, "g-use-dma"); + + /* + * Register TX periodic fifo size per endpoint. + * EP0 is excluded since it has no fifo configuration. + */ + if (!of_find_property(np, "g-tx-fifo-size", &len)) + goto rx_fifo; + + len /= sizeof(u32); + + /* Read tx fifo sizes other than ep0 */ + if (of_property_read_u32_array(np, "g-tx-fifo-size", + &hsotg->g_tx_fifo_sz[1], len)) + goto rx_fifo; + + /* Add ep0 */ + len++; + + /* Make remaining TX fifos unavailable */ + if (len < MAX_EPS_CHANNELS) { + for (i = len; i < MAX_EPS_CHANNELS; i++) + hsotg->g_tx_fifo_sz[i] = 0; + } + +rx_fifo: + /* Register RX fifo size */ + of_property_read_u32(np, "g-rx-fifo-size", &hsotg->g_rx_fifo_sz); + + /* Register NPTX fifo size */ + of_property_read_u32(np, "g-np-tx-fifo-size", + &hsotg->g_np_g_tx_fifo_sz); } #else static inline void s3c_hsotg_of_probe(struct dwc2_hsotg *hsotg) { } @@ -3515,12 +3533,26 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) int epnum; int ret; int i; + u32 p_tx_fifo[] = DWC2_G_P_LEGACY_TX_FIFO_SIZE; /* Set default UTMI width */ hsotg->phyif = GUSBCFG_PHYIF16; s3c_hsotg_of_probe(hsotg); + /* Initialize to legacy fifo configuration values */ + hsotg->g_rx_fifo_sz = 2048; + hsotg->g_np_g_tx_fifo_sz = 1024; + memcpy(&hsotg->g_tx_fifo_sz[1], p_tx_fifo, sizeof(p_tx_fifo)); + /* Device tree specific probe */ + s3c_hsotg_of_probe(hsotg); + /* Dump fifo information */ + dev_dbg(dev, "NonPeriodic TXFIFO size: %d\n", + hsotg->g_np_g_tx_fifo_sz); + dev_dbg(dev, "RXFIFO size: %d\n", hsotg->g_rx_fifo_sz); + for (i = 0; i < MAX_EPS_CHANNELS; i++) + dev_dbg(dev, "Periodic TXFIFO%2d size: %d\n", i, + hsotg->g_tx_fifo_sz[i]); /* * If platform probe couldn't find a generic PHY or an old style * USB PHY, fall back to pdata -- cgit v0.10.2 From bb8693a98ebbe863401108d1cc4f8c0bc72e1900 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:38:53 +0100 Subject: Documentation: dt-bindings: add dt binding info for dwc2 fifo resizing Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/dwc2.txt b/Documentation/devicetree/bindings/usb/dwc2.txt index a7a9cb6..fd132cb 100644 --- a/Documentation/devicetree/bindings/usb/dwc2.txt +++ b/Documentation/devicetree/bindings/usb/dwc2.txt @@ -21,6 +21,9 @@ Refer to phy/phy-bindings.txt for generic phy consumer properties - dr_mode: shall be one of "host", "peripheral" and "otg" Refer to usb/generic.txt - g-use-dma: enable dma usage in gadget driver. +- g-rx-fifo-size: size of rx fifo size in gadget mode. +- g-np-tx-fifo-size: size of non-periodic tx fifo size in gadget mode. +- g-tx-fifo-size: size of periodic tx fifo per endpoint (except ep0) in gadget mode. Example: -- cgit v0.10.2 From ea5a8774a273c62047abb3cb5cca69c8368d05da Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:54 +0100 Subject: usb: dwc2: gadget: remove unused members from hsotg_req These members are only occupying space. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index df6a64f..db5348c 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -158,14 +158,10 @@ struct s3c_hsotg_ep { * struct s3c_hsotg_req - data transfer request * @req: The USB gadget request * @queue: The list of requests for the endpoint this is queued for. - * @in_progress: Has already had size/packets written to core - * @mapped: DMA buffer for this request has been mapped via dma_map_single(). */ struct s3c_hsotg_req { struct usb_request req; struct list_head queue; - unsigned char in_progress; - unsigned char mapped; }; #if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) -- cgit v0.10.2 From 364f8e933869699167db935e13b41b1d92ab369d Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:55 +0100 Subject: usb: dwc2: gadget: fix debug loop limits < 15 check doesn't show debug information for endpoint 15. It is possible to have less than 15 endpoints so use limit provided by hardware configuration. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c43ce7e..7dfda95 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3156,14 +3156,14 @@ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg) /* show periodic fifo settings */ - for (idx = 1; idx <= 15; idx++) { + for (idx = 1; idx < hsotg->num_of_eps; idx++) { val = readl(regs + DPTXFSIZN(idx)); dev_info(dev, "DPTx[%d] FSize=%d, StAddr=0x%08x\n", idx, val >> FIFOSIZE_DEPTH_SHIFT, val & FIFOSIZE_STARTADDR_MASK); } - for (idx = 0; idx < 15; idx++) { + for (idx = 0; idx < hsotg->num_of_eps; idx++) { dev_info(dev, "ep%d-in: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", idx, readl(regs + DIEPCTL(idx)), @@ -3221,7 +3221,7 @@ static int state_show(struct seq_file *seq, void *v) seq_puts(seq, "\nEndpoint status:\n"); - for (idx = 0; idx < 15; idx++) { + for (idx = 0; idx < hsotg->num_of_eps; idx++) { u32 in, out; in = readl(regs + DIEPCTL(idx)); @@ -3280,7 +3280,7 @@ static int fifo_show(struct seq_file *seq, void *v) seq_puts(seq, "\nPeriodic TXFIFOs:\n"); - for (idx = 1; idx <= 15; idx++) { + for (idx = 1; idx < hsotg->num_of_eps; idx++) { val = readl(regs + DPTXFSIZN(idx)); seq_printf(seq, "\tDPTXFIFO%2d: Size %d, Start 0x%08x\n", idx, -- cgit v0.10.2 From 5f2196bd7c72cbead9d47da32816dd69a06b692e Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:56 +0100 Subject: usb: dwc2: gadget: consider all tx fifos When matching tx fifo to endpoint, consider all fifos instead of hard limiting to 8 Moreover, print error in case no fifo could be found. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 7dfda95..cd8880d 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2540,7 +2540,7 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, */ if (dir_in && hsotg->dedicated_fifos) { size = hs_ep->ep.maxpacket*hs_ep->mc; - for (i = 1; i <= 8; ++i) { + for (i = 1; i < hsotg->num_of_eps; ++i) { if (hsotg->fifo_map & (1<regs + DPTXFSIZN(i)); @@ -2554,7 +2554,9 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, hs_ep->fifo_size = val; break; } - if (i == 8) { + if (i == hsotg->num_of_eps) { + dev_err(hsotg->dev, + "%s: No suitable fifo found\n", __func__); ret = -ENOMEM; goto error; } -- cgit v0.10.2 From 1141ea01d5faa82f4910b4a8f896d10490860f24 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:57 +0100 Subject: usb: dwc2: gadget: kill requests after disabling ep kill_all_requests() can flush the fifo. Call it after disabling the endpoint. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index cd8880d..3dda17e 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2605,8 +2605,6 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); spin_lock_irqsave(&hsotg->lock, flags); - /* terminate all requests with shutdown */ - kill_all_requests(hsotg, hs_ep, -ESHUTDOWN); hsotg->fifo_map &= ~(1<fifo_index); hs_ep->fifo_index = 0; @@ -2623,6 +2621,9 @@ static int s3c_hsotg_ep_disable(struct usb_ep *ep) /* disable endpoint interrupts */ s3c_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 0); + /* terminate all requests with shutdown */ + kill_all_requests(hsotg, hs_ep, -ESHUTDOWN); + spin_unlock_irqrestore(&hsotg->lock, flags); return 0; } -- cgit v0.10.2 From fe0b94abcdf6d80bd575e91baf1e0f85a4978d49 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:58 +0100 Subject: usb: dwc2: gadget: manage ep0 state in software Manage ep0 state in software to add handling of status OUT stage. Just toggling hsotg->setup in s3c_hsotg_handle_outdone leaves it in wrong state in 2-stage control transfers. Moreover, ensure that for setup-packet s3c_hsotg_handle_outdone is called either from SetupDone or OutDone but not both. Dwc2 ip v3.00a generates both SetupDone and OutDone on setup packets. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index db5348c..e963aef 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -196,6 +196,15 @@ enum dwc2_lx_state { #define DWC2_G_P_LEGACY_TX_FIFO_SIZE {256, 256, 256, 256, 768, 768, 768, \ 768, 0, 0, 0, 0, 0, 0, 0} +/* Gadget ep0 states */ +enum dwc2_ep0_state { + DWC2_EP0_SETUP, + DWC2_EP0_DATA_IN, + DWC2_EP0_DATA_OUT, + DWC2_EP0_STATUS_IN, + DWC2_EP0_STATUS_OUT, +}; + /** * struct dwc2_core_params - Parameters for configuring the core * @@ -563,7 +572,7 @@ struct dwc2_hw_params { * @ep0_buff: Buffer for EP0 reply data, if needed. * @ctrl_buff: Buffer for EP0 control requests. * @ctrl_req: Request for EP0 control packets. - * @setup: NAK management for EP0 SETUP + * @ep0_state: EP0 control transfers state * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework * @g_using_dma: Indicate if dma usage is enabled @@ -696,11 +705,11 @@ struct dwc2_hsotg { struct usb_request *ctrl_req; void *ep0_buff; void *ctrl_buff; + enum dwc2_ep0_state ep0_state; struct usb_gadget gadget; unsigned int enabled:1; unsigned int connected:1; - unsigned int setup:1; unsigned long last_rst; struct s3c_hsotg_ep *eps_in[MAX_EPS_CHANNELS]; struct s3c_hsotg_ep *eps_out[MAX_EPS_CHANNELS]; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 3dda17e..c3977a8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -638,15 +638,12 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ ctrl |= DXEPCTL_USBACTEP; - dev_dbg(hsotg->dev, "setup req:%d\n", hsotg->setup); + dev_dbg(hsotg->dev, "ep0 state:%d\n", hsotg->ep0_state); /* For Setup request do not clear NAK */ - if (hsotg->setup && index == 0) - hsotg->setup = 0; - else + if (!(index == 0 && hsotg->ep0_state == DWC2_EP0_SETUP)) ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ - dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); writel(ctrl, hsotg->regs + epctrl_reg); @@ -865,8 +862,6 @@ static int s3c_hsotg_send_reply(struct dwc2_hsotg *hsotg, if (length) memcpy(req->buf, buff, length); - else - ep->sent_zlp = 1; ret = s3c_hsotg_ep_queue(&ep->ep, req, GFP_ATOMIC); if (ret) { @@ -1080,26 +1075,20 @@ static void s3c_hsotg_process_control(struct dwc2_hsotg *hsotg, int ret = 0; u32 dcfg; - ep0->sent_zlp = 0; - dev_dbg(hsotg->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n", ctrl->bRequest, ctrl->bRequestType, ctrl->wValue, ctrl->wLength); - /* - * record the direction of the request, for later use when enquing - * packets onto EP0. - */ - - ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0; - dev_dbg(hsotg->dev, "ctrl: dir_in=%d\n", ep0->dir_in); - - /* - * if we've no data with this request, then the last part of the - * transaction is going to implicitly be IN. - */ - if (ctrl->wLength == 0) + if (ctrl->wLength == 0) { + ep0->dir_in = 1; + hsotg->ep0_state = DWC2_EP0_STATUS_IN; + } else if (ctrl->bRequestType & USB_DIR_IN) { ep0->dir_in = 1; + hsotg->ep0_state = DWC2_EP0_DATA_IN; + } else { + ep0->dir_in = 0; + hsotg->ep0_state = DWC2_EP0_DATA_OUT; + } if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { switch (ctrl->bRequest) { @@ -1198,6 +1187,8 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) } hsotg->eps_out[0]->dir_in = 0; + hsotg->eps_out[0]->sent_zlp = 0; + hsotg->ep0_state = DWC2_EP0_SETUP; ret = s3c_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC); if (ret < 0) { @@ -1209,6 +1200,27 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) } } +static void s3c_hsotg_program_zlp(struct dwc2_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep) +{ + u32 ctrl; + u8 index = hs_ep->index; + u32 epctl_reg = hs_ep->dir_in ? DIEPCTL(index) : DOEPCTL(index); + u32 epsiz_reg = hs_ep->dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index); + + dev_dbg(hsotg->dev, "Sending zero-length packet on ep%d\n", index); + + writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | + DXEPTSIZ_XFERSIZE(0), hsotg->regs + + epsiz_reg); + + ctrl = readl(hsotg->regs + epctl_reg); + ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ + ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ + ctrl |= DXEPCTL_USBACTEP; + writel(ctrl, hsotg->regs + epctl_reg); +} + /** * s3c_hsotg_complete_request - complete a request given to us * @hsotg: The device state. @@ -1341,9 +1353,9 @@ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) } /** - * s3c_hsotg_send_zlp - send zero-length packet on control endpoint + * s3c_hsotg_ep0_zlp - send/receive zero-length packet on control endpoint * @hsotg: The device instance - * @req: The request currently on this endpoint + * @dir_in: If IN zlp * * Generate a zero-length IN packet request for terminating a SETUP * transaction. @@ -1352,51 +1364,25 @@ static void s3c_hsotg_rx_data(struct dwc2_hsotg *hsotg, int ep_idx, int size) * currently believed that we do not need to wait for any space in * the TxFIFO. */ -static void s3c_hsotg_send_zlp(struct dwc2_hsotg *hsotg, - struct s3c_hsotg_req *req) +static void s3c_hsotg_ep0_zlp(struct dwc2_hsotg *hsotg, bool dir_in) { - u32 ctrl; - - if (!req) { - dev_warn(hsotg->dev, "%s: no request?\n", __func__); - return; - } - - if (req->req.length == 0) { - hsotg->eps_out[0]->sent_zlp = 1; - s3c_hsotg_enqueue_setup(hsotg); - return; - } - /* eps_out[0] is used in both directions */ - hsotg->eps_out[0]->dir_in = 1; - hsotg->eps_out[0]->sent_zlp = 1; - - dev_dbg(hsotg->dev, "sending zero-length packet\n"); - - /* issue a zero-sized packet to terminate this */ - writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | - DXEPTSIZ_XFERSIZE(0), hsotg->regs + DIEPTSIZ(0)); + hsotg->eps_out[0]->dir_in = dir_in; + hsotg->ep0_state = dir_in ? DWC2_EP0_STATUS_IN : DWC2_EP0_STATUS_OUT; - ctrl = readl(hsotg->regs + DIEPCTL0); - ctrl |= DXEPCTL_CNAK; /* clear NAK set by core */ - ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ - ctrl |= DXEPCTL_USBACTEP; - writel(ctrl, hsotg->regs + DIEPCTL0); + s3c_hsotg_program_zlp(hsotg, hsotg->eps_out[0]); } /** * s3c_hsotg_handle_outdone - handle receiving OutDone/SetupDone from RXFIFO * @hsotg: The device instance * @epnum: The endpoint received from - * @was_setup: Set if processing a SetupDone event. * * The RXFIFO has delivered an OutDone event, which means that the data * transfer for an OUT endpoint has been completed, either by a short * packet or by the finish of a transfer. */ -static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, - int epnum, bool was_setup) +static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum) { u32 epsize = readl(hsotg->regs + DOEPTSIZ(epnum)); struct s3c_hsotg_ep *hs_ep = hsotg->eps_out[epnum]; @@ -1410,6 +1396,13 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, return; } + if (epnum == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_OUT) { + dev_dbg(hsotg->dev, "zlp packet received\n"); + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + s3c_hsotg_enqueue_setup(hsotg); + return; + } + if (using_dma(hsotg)) { unsigned size_done; @@ -1432,12 +1425,6 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, if (req->actual < req->length && size_left == 0) { s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); return; - } else if (epnum == 0) { - /* - * After was_setup = 1 => - * set CNAK for non Setup requests - */ - hsotg->setup = was_setup ? 0 : 1; } if (req->actual < req->length && req->short_not_ok) { @@ -1450,13 +1437,10 @@ static void s3c_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, */ } - if (epnum == 0) { - /* - * Condition req->complete != s3c_hsotg_complete_setup says: - * send ZLP when we have an asynchronous request from gadget - */ - if (!was_setup && req->complete != s3c_hsotg_complete_setup) - s3c_hsotg_send_zlp(hsotg, hs_req); + if (epnum == 0 && hsotg->ep0_state == DWC2_EP0_DATA_OUT) { + /* Move to STATUS IN */ + s3c_hsotg_ep0_zlp(hsotg, true); + return; } s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, result); @@ -1522,7 +1506,7 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) s3c_hsotg_read_frameno(hsotg)); if (!using_dma(hsotg)) - s3c_hsotg_handle_outdone(hsotg, epnum, false); + s3c_hsotg_handle_outdone(hsotg, epnum); break; case GRXSTS_PKTSTS_SETUPDONE: @@ -1530,8 +1514,13 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) "SetupDone (Frame=0x%08x, DOPEPCTL=0x%08x)\n", s3c_hsotg_read_frameno(hsotg), readl(hsotg->regs + DOEPCTL(0))); - - s3c_hsotg_handle_outdone(hsotg, epnum, true); + /* + * Call s3c_hsotg_handle_outdone here if it was not called from + * GRXSTS_PKTSTS_OUTDONE. That is, if the core didn't + * generate GRXSTS_PKTSTS_OUTDONE for setup packet. + */ + if (hsotg->ep0_state == DWC2_EP0_SETUP) + s3c_hsotg_handle_outdone(hsotg, epnum); break; case GRXSTS_PKTSTS_OUTRX: @@ -1544,6 +1533,8 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) s3c_hsotg_read_frameno(hsotg), readl(hsotg->regs + DOEPCTL(0))); + WARN_ON(hsotg->ep0_state != DWC2_EP0_SETUP); + s3c_hsotg_rx_data(hsotg, epnum, size); break; @@ -1723,9 +1714,10 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, } /* Finish ZLP handling for IN EP0 transactions */ - if (hsotg->eps_out[0]->sent_zlp) { - dev_dbg(hsotg->dev, "zlp packet received\n"); + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_IN) { + dev_dbg(hsotg->dev, "zlp packet sent\n"); s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + s3c_hsotg_enqueue_setup(hsotg); return; } @@ -1767,7 +1759,7 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, !(hs_req->req.length % hs_ep->ep.maxpacket)) { dev_dbg(hsotg->dev, "ep0 zlp IN packet sent\n"); - s3c_hsotg_send_zlp(hsotg, hs_req); + s3c_hsotg_program_zlp(hsotg, hs_ep); return; } @@ -1775,8 +1767,16 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, if (!size_left && hs_req->req.actual < hs_req->req.length) { dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); - } else - s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + return; + } + + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_DATA_IN) { + /* Move to STATUS OUT */ + s3c_hsotg_ep0_zlp(hsotg, false); + return; + } + + s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); } /** @@ -1845,7 +1845,7 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, * as we ignore the RXFIFO. */ - s3c_hsotg_handle_outdone(hsotg, idx, false); + s3c_hsotg_handle_outdone(hsotg, idx); } } @@ -1884,7 +1884,7 @@ static void s3c_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx, if (dir_in) WARN_ON_ONCE(1); else - s3c_hsotg_handle_outdone(hsotg, 0, true); + s3c_hsotg_handle_outdone(hsotg, 0); } } -- cgit v0.10.2 From f71b5e2533debe564f21c068aaabc42c6293d18b Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:38:59 +0100 Subject: usb: dwc2: gadget: fix zero length packet transfers According to programming guide, zero length packet should be programmed on its own and should not be counted in DIEPTSIZ.PktCnt with other packets. For ep0, this is the zlp for DATA IN stage (if required) and not for the STATUS stage. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index c3977a8..992a2eb 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -598,14 +598,15 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, else epsize = 0; - if (index != 0 && ureq->zero) { - /* - * test for the packets being exactly right for the - * transfer - */ - - if (length == (packets * hs_ep->ep.maxpacket)) - packets++; + /* + * zero length packet should be programmed on its own and should not + * be counted in DIEPTSIZ.PktCnt with other packets. + */ + if (dir_in && ureq->zero && !continuing) { + /* Test if zlp is actually required. */ + if ((ureq->length >= hs_ep->ep.maxpacket) && + !(ureq->length % hs_ep->ep.maxpacket)) + hs_ep->sent_zlp = 1; } epsize |= DXEPTSIZ_PKTCNT(packets); @@ -857,7 +858,11 @@ static int s3c_hsotg_send_reply(struct dwc2_hsotg *hsotg, req->buf = hsotg->ep0_buff; req->length = length; - req->zero = 1; /* always do zero-length final transfer */ + /* + * zero flag is for sending zlp in DATA IN stage. It has no impact on + * STATUS stage. + */ + req->zero = 0; req->complete = s3c_hsotg_complete_oursetup; if (length) @@ -1744,32 +1749,20 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, dev_dbg(hsotg->dev, "req->length:%d req->actual:%d req->zero:%d\n", hs_req->req.length, hs_req->req.actual, hs_req->req.zero); - /* - * Check if dealing with Maximum Packet Size(MPS) IN transfer at EP0 - * When sent data is a multiple MPS size (e.g. 64B ,128B ,192B - * ,256B ... ), after last MPS sized packet send IN ZLP packet to - * inform the host that no more data is available. - * The state of req.zero member is checked to be sure that the value to - * send is smaller than wValue expected from host. - * Check req.length to NOT send another ZLP when the current one is - * under completion (the one for which this completion has been called). - */ - if (hs_req->req.length && hs_ep->index == 0 && hs_req->req.zero && - hs_req->req.length == hs_req->req.actual && - !(hs_req->req.length % hs_ep->ep.maxpacket)) { - - dev_dbg(hsotg->dev, "ep0 zlp IN packet sent\n"); - s3c_hsotg_program_zlp(hsotg, hs_ep); - - return; - } - if (!size_left && hs_req->req.actual < hs_req->req.length) { dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); return; } + /* Zlp for all endpoints, for ep0 only in DATA IN stage */ + if (hs_ep->sent_zlp) { + s3c_hsotg_program_zlp(hsotg, hs_ep); + hs_ep->sent_zlp = 0; + /* transfer will be completed on next complete interrupt */ + return; + } + if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_DATA_IN) { /* Move to STATUS OUT */ s3c_hsotg_ep0_zlp(hsotg, false); -- cgit v0.10.2 From 1a0ed863eef7ea6af63afd60ca4fa16edf6b4244 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:00 +0100 Subject: usb: dwc2: gadget: dont warn if endpoint is not enabled The warning is probably good but it has false positives in both dma and non-dma cases. So its not very helpful in either. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 992a2eb..6d76e28 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -678,7 +678,7 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, /* check ep is enabled */ if (!(readl(hsotg->regs + epctrl_reg) & DXEPCTL_EPENA)) - dev_warn(hsotg->dev, + dev_dbg(hsotg->dev, "ep%d: failed to become enabled (DXEPCTL=0x%08x)?\n", index, readl(hsotg->regs + epctrl_reg)); -- cgit v0.10.2 From 48b20bcb3b2e9b15be1ebb9b574b003cffb033d5 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:01 +0100 Subject: usb: dwc2: gadget: don't block after fifo flush timeout Can't stay in the loop forever. Break it after timeout. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 6d76e28..e3bb525 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -230,6 +230,7 @@ static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) dev_err(hsotg->dev, "%s: timeout flushing fifos (GRSTCTL=%08x)\n", __func__, val); + break; } udelay(1); -- cgit v0.10.2 From 83d982234ee65900521e763161f409b29f89b8f6 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:02 +0100 Subject: usb: dwc2: gadget: add vbus_session support Enable phy driver to report vbus session. This allows us to remove D+ pullup when vbus is not present. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index e3bb525..65ec0f2 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3009,11 +3009,35 @@ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) return 0; } +static int s3c_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + unsigned long flags; + + dev_dbg(hsotg->dev, "%s: is_active: %d\n", __func__, is_active); + spin_lock_irqsave(&hsotg->lock, flags); + + if (is_active) { + /* Kill any ep0 requests as controller will be reinitialized */ + kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); + s3c_hsotg_core_init_disconnected(hsotg); + if (hsotg->enabled) + s3c_hsotg_core_connect(hsotg); + } else { + s3c_hsotg_core_disconnect(hsotg); + s3c_hsotg_disconnect(hsotg); + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; +} + static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { .get_frame = s3c_hsotg_gadget_getframe, .udc_start = s3c_hsotg_udc_start, .udc_stop = s3c_hsotg_udc_stop, .pullup = s3c_hsotg_pullup, + .vbus_session = s3c_hsotg_vbus_session, }; /** -- cgit v0.10.2 From 8a20fa457eb5a8d247fd82d4bd3c04225fd2f253 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:03 +0100 Subject: usb: dwc2: gadget: rename sent_zlp to send_zlp This flag is set before sending the zlp. So use present tense instead of the past tense. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index e963aef..f09b3de 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -108,7 +108,7 @@ struct s3c_hsotg_req; * @halted: Set if the endpoint has been halted. * @periodic: Set if this is a periodic ep, such as Interrupt * @isochronous: Set if this is a isochronous ep - * @sent_zlp: Set if we've sent a zero-length packet. + * @send_zlp: Set if we need to send a zero-length packet. * @total_data: The total number of data bytes done. * @fifo_size: The size of the FIFO (for periodic IN endpoints) * @fifo_load: The amount of data loaded into the FIFO (periodic IN) @@ -149,7 +149,7 @@ struct s3c_hsotg_ep { unsigned int halted:1; unsigned int periodic:1; unsigned int isochronous:1; - unsigned int sent_zlp:1; + unsigned int send_zlp:1; char name[10]; }; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 65ec0f2..5fdc3f5 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -607,7 +607,7 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, /* Test if zlp is actually required. */ if ((ureq->length >= hs_ep->ep.maxpacket) && !(ureq->length % hs_ep->ep.maxpacket)) - hs_ep->sent_zlp = 1; + hs_ep->send_zlp = 1; } epsize |= DXEPTSIZ_PKTCNT(packets); @@ -1193,7 +1193,7 @@ static void s3c_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg) } hsotg->eps_out[0]->dir_in = 0; - hsotg->eps_out[0]->sent_zlp = 0; + hsotg->eps_out[0]->send_zlp = 0; hsotg->ep0_state = DWC2_EP0_SETUP; ret = s3c_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC); @@ -1757,9 +1757,9 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, } /* Zlp for all endpoints, for ep0 only in DATA IN stage */ - if (hs_ep->sent_zlp) { + if (hs_ep->send_zlp) { s3c_hsotg_program_zlp(hsotg, hs_ep); - hs_ep->sent_zlp = 0; + hs_ep->send_zlp = 0; /* transfer will be completed on next complete interrupt */ return; } -- cgit v0.10.2 From ca4c55ad89edb54b1eb04fe2d4c6915c9f8d69b9 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:04 +0100 Subject: usb: dwc2: gadget: pick smallest acceptable fifo Current algorithm picks the first fifo which is equal to or greater than the required size. This can result in bigger fifos assigned to endpoints with smaller maxps. Change the algorithm to pick the smallest fifo which is greater than or equal to the required size. Moreover, only use signed variables when required. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5fdc3f5..54e6d15 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2439,12 +2439,12 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, struct s3c_hsotg_ep *hs_ep = our_ep(ep); struct dwc2_hsotg *hsotg = hs_ep->parent; unsigned long flags; - int index = hs_ep->index; + unsigned int index = hs_ep->index; u32 epctrl_reg; u32 epctrl; u32 mps; - int dir_in; - int i, val, size; + unsigned int dir_in; + unsigned int i, val, size; int ret = 0; dev_dbg(hsotg->dev, @@ -2533,6 +2533,8 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, * a unique tx-fifo even if it is non-periodic. */ if (dir_in && hsotg->dedicated_fifos) { + u32 fifo_index = 0; + u32 fifo_size = UINT_MAX; size = hs_ep->ep.maxpacket*hs_ep->mc; for (i = 1; i < hsotg->num_of_eps; ++i) { if (hsotg->fifo_map & (1<> FIFOSIZE_DEPTH_SHIFT)*4; if (val < size) continue; - hsotg->fifo_map |= 1<fifo_index = i; - hs_ep->fifo_size = val; - break; + /* Search for smallest acceptable fifo */ + if (val < fifo_size) { + fifo_size = val; + fifo_index = i; + } } - if (i == hsotg->num_of_eps) { + if (!fifo_index) { dev_err(hsotg->dev, "%s: No suitable fifo found\n", __func__); ret = -ENOMEM; goto error; } + hsotg->fifo_map |= 1 << fifo_index; + epctrl |= DXEPCTL_TXFNUM(fifo_index); + hs_ep->fifo_index = fifo_index; + hs_ep->fifo_size = fifo_size; } /* for non control endpoints, set PID to D0 */ -- cgit v0.10.2 From 4556e12c9cf732343224308866dad2a12887dcd6 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:05 +0100 Subject: usb: dwc2: gadget: fix fifo allocation leak When selecting different alt setting, s3c_hsotg_ep_enable can be called with fifo already allocated. Allocate fifo again only if required and after deallocating the previous fifo. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 54e6d15..de5da92 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2528,11 +2528,22 @@ static int s3c_hsotg_ep_enable(struct usb_ep *ep, break; } + /* If fifo is already allocated for this ep */ + if (hs_ep->fifo_index) { + size = hs_ep->ep.maxpacket * hs_ep->mc; + /* If bigger fifo is required deallocate current one */ + if (size > hs_ep->fifo_size) { + hsotg->fifo_map &= ~(1 << hs_ep->fifo_index); + hs_ep->fifo_index = 0; + hs_ep->fifo_size = 0; + } + } + /* * if the hardware has dedicated fifos, we must give each IN EP * a unique tx-fifo even if it is non-periodic. */ - if (dir_in && hsotg->dedicated_fifos) { + if (dir_in && hsotg->dedicated_fifos && !hs_ep->fifo_index) { u32 fifo_index = 0; u32 fifo_size = UINT_MAX; size = hs_ep->ep.maxpacket*hs_ep->mc; -- cgit v0.10.2 From 7fcbc95c1fa4d6e3f4645f9552d358f07ab93ad1 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:06 +0100 Subject: usb: dwc2: gadget: reset fifo_map when initializing fifos After all endpoints are disabled, fifo_map should have reached 0. Its a bug if if didn't, so warn about it and reset it to 0 so that driver can continue using all the fifos. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index de5da92..a1fd86f 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -177,6 +177,10 @@ static void s3c_hsotg_init_fifo(struct dwc2_hsotg *hsotg) int timeout; u32 val; + /* Reset fifo map if not correctly cleared during previous session */ + WARN_ON(hsotg->fifo_map); + hsotg->fifo_map = 0; + /* set RX/NPTX FIFO sizes */ writel(hsotg->g_rx_fifo_sz, hsotg->regs + GRXFSIZ); writel((hsotg->g_rx_fifo_sz << FIFOSIZE_STARTADDR_SHIFT) | -- cgit v0.10.2 From 6d13673e6bc5b666913e92ca59518a449da3b4a9 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:07 +0100 Subject: usb: dwc2: gadget: fix pullup handling Gadget must be informed about disconnection when pullup is removed. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index a1fd86f..2719d48 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3018,6 +3018,7 @@ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) s3c_hsotg_core_connect(hsotg); } else { s3c_hsotg_core_disconnect(hsotg); + s3c_hsotg_disconnect(hsotg); hsotg->enabled = 0; clk_disable(hsotg->clk); } -- cgit v0.10.2 From 596d696a5d0e066f2ed4bd6b0e40dedd52513c7c Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:08 +0100 Subject: usb: dwc2: gadget: add vbus_draw support This callback informs the driver about the total amount of current it is allowed to draw. Share this information with the phy so that current limits can be set for charging for example. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 2719d48..f477709 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3053,12 +3053,29 @@ static int s3c_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) return 0; } +/** + * s3c_hsotg_vbus_draw - report bMaxPower field + * @gadget: The usb gadget state + * @mA: Amount of current + * + * Report how much power the device may consume to the phy. + */ +static int s3c_hsotg_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct dwc2_hsotg *hsotg = to_hsotg(gadget); + + if (IS_ERR_OR_NULL(hsotg->uphy)) + return -ENOTSUPP; + return usb_phy_set_power(hsotg->uphy, mA); +} + static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { .get_frame = s3c_hsotg_gadget_getframe, .udc_start = s3c_hsotg_udc_start, .udc_stop = s3c_hsotg_udc_stop, .pullup = s3c_hsotg_pullup, .vbus_session = s3c_hsotg_vbus_session, + .vbus_draw = s3c_hsotg_vbus_draw, }; /** -- cgit v0.10.2 From 1b7a66b4d3399da5e0b13cf90fb4bd33b7197ff2 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 9 Jan 2015 13:39:09 +0100 Subject: usb: dwc2: gadget: force gadget initialization in dev mode When booting with id pin grounded, dwc2 default to host mode. Thus, force device mode prior initializing gadget part. Else fifo init will fail since fifo values are not correct in host mode. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index f477709..df9080d 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3675,6 +3675,19 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) /* usb phy enable */ s3c_hsotg_phy_enable(hsotg); + /* + * Force Device mode before initialization. + * This allows correctly configuring fifo for device mode. + */ + __bic32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEHOSTMODE); + __orr32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEDEVMODE); + + /* + * According to Synopsys databook, this sleep is needed for the force + * device mode to take effect. + */ + msleep(25); + s3c_hsotg_corereset(hsotg); ret = s3c_hsotg_hw_cfg(hsotg); if (ret) { @@ -3684,6 +3697,9 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) s3c_hsotg_init(hsotg); + /* Switch back to default configuration */ + __bic32(hsotg->regs + GUSBCFG, GUSBCFG_FORCEDEVMODE); + hsotg->ctrl_buff = devm_kzalloc(hsotg->dev, DWC2_CTRL_BUFF_SIZE, GFP_KERNEL); if (!hsotg->ctrl_buff) { -- cgit v0.10.2 From 6d713c1531638df8d459d248a89948318cbeec4c Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 9 Jan 2015 13:39:10 +0100 Subject: usb: dwc2: gadget: report disconnection after reset If usb bus is reset without a physical disconnection, all endpoints will remain open. Call s3c_hsotg_disconnect() from reset handler to report a disconnect to gadget framework. hsotg->connected is checked in s3c_hsotg_disconnect() before processing disconnect. In some cases, USBRst is seen before EnumDone and after it as well. So move setting of hsotg->connected to set-address to avoid reporting extra disconnection in this case. Tested-by: Robert Baldyga Acked-by: Paul Zimmerman Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index df9080d..882a1a8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1103,6 +1103,7 @@ static void s3c_hsotg_process_control(struct dwc2_hsotg *hsotg, if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { switch (ctrl->bRequest) { case USB_REQ_SET_ADDRESS: + hsotg->connected = 1; dcfg = readl(hsotg->regs + DCFG); dcfg &= ~DCFG_DEVADDR_MASK; dcfg |= (le16_to_cpu(ctrl->wValue) << @@ -2305,7 +2306,6 @@ irq_retry: writel(GINTSTS_ENUMDONE, hsotg->regs + GINTSTS); s3c_hsotg_irq_enumdone(hsotg); - hsotg->connected = 1; } if (gintsts & (GINTSTS_OEPINT | GINTSTS_IEPINT)) { @@ -2343,6 +2343,9 @@ irq_retry: writel(GINTSTS_USBRST, hsotg->regs + GINTSTS); + /* Report disconnection if it is not already done. */ + s3c_hsotg_disconnect(hsotg); + if (usb_status & GOTGCTL_BSESVLD) { if (time_after(jiffies, hsotg->last_rst + msecs_to_jiffies(200))) { -- cgit v0.10.2 From d7b3968dbcbb620acf92164752031fa276917db9 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Tue, 16 Dec 2014 01:42:13 +0300 Subject: usb: renesas_usbhs: add OTG ID signal sensing On the Renesas R8A7791 SoC based boards there's MAX3355 USB OTG chip and mini-AB USB connector corresponding to USB port 0 driven either by EHCI/OHCI or Renesas USBHS gadget controller. And we'd like the host/gadget drivers to work based on the cable type connected. An 'extcon' driver for MAX3355 has been written, so we only need to bind to it via device tree which I'm doing in this patch. (Perhaps, it would also make sense to use OTG HNP when the USBHS host mode is active and a B-cable is connected but I don't have access to host-capable USBHS, so wouldn't be able to test it.) Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 76d14f5..4cf77d3 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -363,6 +363,7 @@ static void usbhsc_hotplug(struct usbhs_priv *priv) struct usbhs_mod *mod = usbhs_mod_get_current(priv); int id; int enable; + int cable; int ret; /* @@ -376,6 +377,16 @@ static void usbhsc_hotplug(struct usbhs_priv *priv) id = usbhs_platform_call(priv, get_id, pdev); if (enable && !mod) { + if (priv->edev) { + cable = extcon_get_cable_state(priv->edev, "USB-HOST"); + if ((cable > 0 && id != USBHS_HOST) || + (!cable && id != USBHS_GADGET)) { + dev_info(&pdev->dev, + "USB cable plugged in doesn't match the selected role!\n"); + return; + } + } + ret = usbhs_mod_change(priv, id); if (ret < 0) return; @@ -514,6 +525,12 @@ static int usbhs_probe(struct platform_device *pdev) if (IS_ERR(priv->base)) return PTR_ERR(priv->base); + if (of_property_read_bool(pdev->dev.of_node, "extcon")) { + priv->edev = extcon_get_edev_by_phandle(&pdev->dev, 0); + if (IS_ERR(priv->edev)) + return PTR_ERR(priv->edev); + } + /* * care platform info */ diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index 0427cdd..fc96e92 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -17,6 +17,7 @@ #ifndef RENESAS_USB_DRIVER_H #define RENESAS_USB_DRIVER_H +#include #include #include @@ -254,6 +255,8 @@ struct usbhs_priv { struct delayed_work notify_hotplug_work; struct platform_device *pdev; + struct extcon_dev *edev; + spinlock_t lock; u32 flags; -- cgit v0.10.2 From 3703cfe78295b1ccbad091871ccc0fa0e5d0589f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 1 Dec 2014 16:09:28 +0800 Subject: usb: gadget: uac1: struct gaudio is useless for struct f_uac1_opts Since we call gaudio_cleanup at f_audio_free, the f_uac1_opts doesn't need to use gaudio any more. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index d8b4af6..9cf2252 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -674,7 +674,6 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst); audio->card.gadget = c->cdev->gadget; - audio_opts->card = &audio->card; /* set up ASLA audio devices */ if (!audio_opts->bound) { status = gaudio_setup(&audio->card); diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h index f8b17fe..fe386df 100644 --- a/drivers/usb/gadget/function/u_uac1.h +++ b/drivers/usb/gadget/function/u_uac1.h @@ -70,7 +70,6 @@ struct f_uac1_opts { unsigned fn_play_alloc:1; unsigned fn_cap_alloc:1; unsigned fn_cntl_alloc:1; - struct gaudio *card; struct mutex lock; int refcnt; }; -- cgit v0.10.2 From c76abecc42b63f70d33f9e266c62002c2b1234d1 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 1 Dec 2014 16:09:29 +0800 Subject: usb: gadget: u_uac1: fix one code style problem Fix one code style problem. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/u_uac1.c b/drivers/usb/gadget/function/u_uac1.c index 53842a1..c78c841 100644 --- a/drivers/usb/gadget/function/u_uac1.c +++ b/drivers/usb/gadget/function/u_uac1.c @@ -308,8 +308,7 @@ int gaudio_setup(struct gaudio *card) */ void gaudio_cleanup(struct gaudio *the_card) { - if (the_card) { + if (the_card) gaudio_close_snd_dev(the_card); - } } -- cgit v0.10.2 From fc12c68b4ff2904c3322b7a12089f0b9808e2383 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Tue, 13 Jan 2015 10:54:58 +0100 Subject: usb: gadget: net2280: Dont use 0 as NULL pointer Fix sparse warning Fixes: cb442ee1592d2681 (usb: gadget: udc: net2280: Re-enable dynamic debug messages) Reported-by: kbuild test robot Signed-off-by: Ricardo Ribalda Delgado Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index d2fabdc..6411ed8 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -2287,7 +2287,7 @@ static void handle_ep_small(struct net2280_ep *ep) ep->irqs++; ep_vdbg(ep->dev, "%s ack ep_stat %08x, req %p\n", - ep->ep.name, t, req ? &req->req : 0); + ep->ep.name, t, req ? &req->req : NULL); if (!ep->is_in || (ep->dev->quirks & PLX_2280)) writel(t & ~BIT(NAK_OUT_PACKETS), &ep->regs->ep_stat); -- cgit v0.10.2 From 969678c09877fd1116be6ae304e59a27658226c2 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 13 Jan 2015 14:56:28 +0100 Subject: tools: ffs-aio-example: use endpoint addresses from descriptors This makes examples more platform independent and more compatible with USB standard, as endpoint addresses in given interface may differ between hardware platforms or even between configurations in single USB device. Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/tools/usb/ffs-aio-example/multibuff/host_app/test.c b/tools/usb/ffs-aio-example/multibuff/host_app/test.c index daa3abe..2cbcce6 100644 --- a/tools/usb/ffs-aio-example/multibuff/host_app/test.c +++ b/tools/usb/ffs-aio-example/multibuff/host_app/test.c @@ -33,11 +33,6 @@ #define VENDOR 0x1d6b #define PRODUCT 0x0105 -/* endpoints indexes */ - -#define EP_BULK_IN (1 | LIBUSB_ENDPOINT_IN) -#define EP_BULK_OUT (2 | LIBUSB_ENDPOINT_OUT) - #define BUF_LEN 8192 /* @@ -159,14 +154,21 @@ void test_exit(struct test_state *state) int main(void) { struct test_state state; + struct libusb_config_descriptor *conf; + struct libusb_interface_descriptor const *iface; + unsigned char addr; if (test_init(&state)) return 1; + libusb_get_config_descriptor(state.found, 0, &conf); + iface = &conf->interface[0].altsetting[0]; + addr = iface->endpoint[0].bEndpointAddress; + while (1) { static unsigned char buffer[BUF_LEN]; int bytes; - libusb_bulk_transfer(state.handle, EP_BULK_IN, buffer, BUF_LEN, + libusb_bulk_transfer(state.handle, addr, buffer, BUF_LEN, &bytes, 500); } test_exit(&state); diff --git a/tools/usb/ffs-aio-example/simple/host_app/test.c b/tools/usb/ffs-aio-example/simple/host_app/test.c index acd6332..aed86ff 100644 --- a/tools/usb/ffs-aio-example/simple/host_app/test.c +++ b/tools/usb/ffs-aio-example/simple/host_app/test.c @@ -33,11 +33,6 @@ #define VENDOR 0x1d6b #define PRODUCT 0x0105 -/* endpoints indexes */ - -#define EP_BULK_IN (1 | LIBUSB_ENDPOINT_IN) -#define EP_BULK_OUT (2 | LIBUSB_ENDPOINT_OUT) - #define BUF_LEN 8192 /* @@ -159,16 +154,24 @@ void test_exit(struct test_state *state) int main(void) { struct test_state state; + struct libusb_config_descriptor *conf; + struct libusb_interface_descriptor const *iface; + unsigned char in_addr, out_addr; if (test_init(&state)) return 1; + libusb_get_config_descriptor(state.found, 0, &conf); + iface = &conf->interface[0].altsetting[0]; + in_addr = iface->endpoint[0].bEndpointAddress; + out_addr = iface->endpoint[1].bEndpointAddress; + while (1) { static unsigned char buffer[BUF_LEN]; int bytes; - libusb_bulk_transfer(state.handle, EP_BULK_IN, buffer, BUF_LEN, + libusb_bulk_transfer(state.handle, in_addr, buffer, BUF_LEN, &bytes, 500); - libusb_bulk_transfer(state.handle, EP_BULK_OUT, buffer, BUF_LEN, + libusb_bulk_transfer(state.handle, out_addr, buffer, BUF_LEN, &bytes, 500); } test_exit(&state); -- cgit v0.10.2 From f093a2d465371d106384559a3ac0abf701be820e Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Tue, 13 Jan 2015 16:55:38 +0800 Subject: usb: gadget: uvc: to_uvcg_control_header() can be static drivers/usb/gadget/function/uvc_configfs.c:46:28: sparse: symbol 'to_uvcg_control_header' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:138:25: sparse: symbol 'uvcg_control_header_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:164:6: sparse: symbol 'uvcg_control_header_drop' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:721:20: sparse: symbol 'to_uvcg_format' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:798:30: sparse: symbol 'to_uvcg_streaming_header' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:950:25: sparse: symbol 'uvcg_streaming_header_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:976:6: sparse: symbol 'uvcg_streaming_header_drop' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1020:19: sparse: symbol 'to_uvcg_frame' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1265:25: sparse: symbol 'uvcg_frame_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1315:6: sparse: symbol 'uvcg_frame_drop' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1338:26: sparse: symbol 'to_uvcg_uncompressed' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1548:25: sparse: symbol 'uvcg_uncompressed_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1586:6: sparse: symbol 'uvcg_uncompressed_drop' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1610:19: sparse: symbol 'to_uvcg_mjpeg' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1761:25: sparse: symbol 'uvcg_mjpeg_type' was not declared. Should it be static? drivers/usb/gadget/function/uvc_configfs.c:1793:6: sparse: symbol 'uvcg_mjpeg_drop' was not declared. Should it be static? Signed-off-by: Fengguang Wu Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 33d92ab..f69f47a 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -43,7 +43,7 @@ struct uvcg_control_header { unsigned linked; }; -struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) +static struct uvcg_control_header *to_uvcg_control_header(struct config_item *item) { return container_of(item, struct uvcg_control_header, item); } @@ -135,7 +135,7 @@ static struct configfs_attribute *uvcg_control_header_attrs[] = { NULL, }; -struct config_item_type uvcg_control_header_type = { +static struct config_item_type uvcg_control_header_type = { .ct_item_ops = &uvcg_control_header_item_ops, .ct_attrs = uvcg_control_header_attrs, .ct_owner = THIS_MODULE, @@ -161,7 +161,7 @@ static struct config_item *uvcg_control_header_make(struct config_group *group, return &h->item; } -void uvcg_control_header_drop(struct config_group *group, +static void uvcg_control_header_drop(struct config_group *group, struct config_item *item) { struct uvcg_control_header *h = to_uvcg_control_header(item); @@ -718,7 +718,7 @@ struct uvcg_format { __u8 bmaControls[UVCG_STREAMING_CONTROL_SIZE]; }; -struct uvcg_format *to_uvcg_format(struct config_item *item) +static struct uvcg_format *to_uvcg_format(struct config_item *item) { return container_of(to_config_group(item), struct uvcg_format, group); } @@ -795,7 +795,7 @@ struct uvcg_streaming_header { unsigned num_fmt; }; -struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) +static struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item) { return container_of(item, struct uvcg_streaming_header, item); } @@ -947,7 +947,7 @@ static struct configfs_attribute *uvcg_streaming_header_attrs[] = { NULL, }; -struct config_item_type uvcg_streaming_header_type = { +static struct config_item_type uvcg_streaming_header_type = { .ct_item_ops = &uvcg_streaming_header_item_ops, .ct_attrs = uvcg_streaming_header_attrs, .ct_owner = THIS_MODULE, @@ -973,7 +973,7 @@ static struct config_item return &h->item; } -void uvcg_streaming_header_drop(struct config_group *group, +static void uvcg_streaming_header_drop(struct config_group *group, struct config_item *item) { struct uvcg_streaming_header *h = to_uvcg_streaming_header(item); @@ -1017,7 +1017,7 @@ struct uvcg_frame { struct config_item item; }; -struct uvcg_frame *to_uvcg_frame(struct config_item *item) +static struct uvcg_frame *to_uvcg_frame(struct config_item *item) { return container_of(item, struct uvcg_frame, item); } @@ -1262,7 +1262,7 @@ static struct configfs_attribute *uvcg_frame_attrs[] = { NULL, }; -struct config_item_type uvcg_frame_type = { +static struct config_item_type uvcg_frame_type = { .ct_item_ops = &uvcg_frame_item_ops, .ct_attrs = uvcg_frame_attrs, .ct_owner = THIS_MODULE, @@ -1312,7 +1312,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group, return &h->item; } -void uvcg_frame_drop(struct config_group *group, struct config_item *item) +static void uvcg_frame_drop(struct config_group *group, struct config_item *item) { struct uvcg_frame *h = to_uvcg_frame(item); struct uvcg_format *fmt; @@ -1335,7 +1335,7 @@ struct uvcg_uncompressed { struct uvc_format_uncompressed desc; }; -struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) +static struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item) { return container_of( container_of(to_config_group(item), struct uvcg_format, group), @@ -1545,7 +1545,7 @@ static struct configfs_attribute *uvcg_uncompressed_attrs[] = { NULL, }; -struct config_item_type uvcg_uncompressed_type = { +static struct config_item_type uvcg_uncompressed_type = { .ct_item_ops = &uvcg_uncompressed_item_ops, .ct_group_ops = &uvcg_uncompressed_group_ops, .ct_attrs = uvcg_uncompressed_attrs, @@ -1583,7 +1583,7 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, return &h->fmt.group; } -void uvcg_uncompressed_drop(struct config_group *group, +static void uvcg_uncompressed_drop(struct config_group *group, struct config_item *item) { struct uvcg_uncompressed *h = to_uvcg_uncompressed(item); @@ -1607,7 +1607,7 @@ struct uvcg_mjpeg { struct uvc_format_mjpeg desc; }; -struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) +static struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item) { return container_of( container_of(to_config_group(item), struct uvcg_format, group), @@ -1758,7 +1758,7 @@ static struct configfs_attribute *uvcg_mjpeg_attrs[] = { NULL, }; -struct config_item_type uvcg_mjpeg_type = { +static struct config_item_type uvcg_mjpeg_type = { .ct_item_ops = &uvcg_mjpeg_item_ops, .ct_group_ops = &uvcg_mjpeg_group_ops, .ct_attrs = uvcg_mjpeg_attrs, @@ -1790,7 +1790,7 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group, return &h->fmt.group; } -void uvcg_mjpeg_drop(struct config_group *group, +static void uvcg_mjpeg_drop(struct config_group *group, struct config_item *item) { struct uvcg_mjpeg *h = to_uvcg_mjpeg(item); -- cgit v0.10.2 From d3fdcc78b2d6f1338fa9abea2d37bc10da7f71b8 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 12 Jan 2015 14:20:12 +0200 Subject: usb: dwc3: pci: remove registration of NOP PHYs None of the PCI platforms need the NOP transceivers, and since we can now live without the PHYs, removing registration of the platform devices for them. Acked-by: Huang Rui Signed-off-by: Heikki Krogerus Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 7c4faf7..82a439b 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -22,9 +22,6 @@ #include #include -#include -#include - #include "platform_data.h" /* FIXME define these in */ @@ -37,65 +34,8 @@ struct dwc3_pci { struct device *dev; struct platform_device *dwc3; - struct platform_device *usb2_phy; - struct platform_device *usb3_phy; }; -static int dwc3_pci_register_phys(struct dwc3_pci *glue) -{ - struct usb_phy_generic_platform_data pdata; - struct platform_device *pdev; - int ret; - - memset(&pdata, 0x00, sizeof(pdata)); - - pdev = platform_device_alloc("usb_phy_generic", 0); - if (!pdev) - return -ENOMEM; - - glue->usb2_phy = pdev; - pdata.type = USB_PHY_TYPE_USB2; - pdata.gpio_reset = -1; - - ret = platform_device_add_data(glue->usb2_phy, &pdata, sizeof(pdata)); - if (ret) - goto err1; - - pdev = platform_device_alloc("usb_phy_generic", 1); - if (!pdev) { - ret = -ENOMEM; - goto err1; - } - - glue->usb3_phy = pdev; - pdata.type = USB_PHY_TYPE_USB3; - - ret = platform_device_add_data(glue->usb3_phy, &pdata, sizeof(pdata)); - if (ret) - goto err2; - - ret = platform_device_add(glue->usb2_phy); - if (ret) - goto err2; - - ret = platform_device_add(glue->usb3_phy); - if (ret) - goto err3; - - return 0; - -err3: - platform_device_del(glue->usb2_phy); - -err2: - platform_device_put(glue->usb3_phy); - -err1: - platform_device_put(glue->usb2_phy); - - return ret; -} - static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) { @@ -122,12 +62,6 @@ static int dwc3_pci_probe(struct pci_dev *pci, pci_set_master(pci); - ret = dwc3_pci_register_phys(glue); - if (ret) { - dev_err(dev, "couldn't register PHYs\n"); - return ret; - } - dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO); if (!dwc3) { dev_err(dev, "couldn't allocate dwc3 device\n"); @@ -207,8 +141,6 @@ static void dwc3_pci_remove(struct pci_dev *pci) struct dwc3_pci *glue = pci_get_drvdata(pci); platform_device_unregister(glue->dwc3); - platform_device_unregister(glue->usb2_phy); - platform_device_unregister(glue->usb3_phy); } static const struct pci_device_id dwc3_pci_id_table[] = { -- cgit v0.10.2 From 3b44ed90cdb26ce3a06a1b41c5fcbb01c7d63b3c Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 12 Jan 2015 14:20:13 +0200 Subject: usb: dwc3: pci: rely on default PM callbacks from PCI driver utility There is nothing specific being done in the suspend and resume callbacks that is not already taken care of in PCI driver core, so dropping the functions. Acked-by: Huang Rui Signed-off-by: Heikki Krogerus Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 82a439b..5c471d2 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -156,45 +156,11 @@ static const struct pci_device_id dwc3_pci_id_table[] = { }; MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table); -#ifdef CONFIG_PM_SLEEP -static int dwc3_pci_suspend(struct device *dev) -{ - struct pci_dev *pci = to_pci_dev(dev); - - pci_disable_device(pci); - - return 0; -} - -static int dwc3_pci_resume(struct device *dev) -{ - struct pci_dev *pci = to_pci_dev(dev); - int ret; - - ret = pci_enable_device(pci); - if (ret) { - dev_err(dev, "can't re-enable device --> %d\n", ret); - return ret; - } - - pci_set_master(pci); - - return 0; -} -#endif /* CONFIG_PM_SLEEP */ - -static const struct dev_pm_ops dwc3_pci_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(dwc3_pci_suspend, dwc3_pci_resume) -}; - static struct pci_driver dwc3_pci_driver = { .name = "dwc3-pci", .id_table = dwc3_pci_id_table, .probe = dwc3_pci_probe, .remove = dwc3_pci_remove, - .driver = { - .pm = &dwc3_pci_dev_pm_ops, - }, }; MODULE_AUTHOR("Felipe Balbi "); -- cgit v0.10.2 From 2cd9ddf77e0da465da4535b685587338260eca99 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Mon, 12 Jan 2015 14:20:14 +0200 Subject: usb: dwc3: pci: code cleanup Removing a few items that are not needed anymore and adding separate function for quirks. Acked-by: Huang Rui Signed-off-by: Heikki Krogerus Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 5c471d2..5f5fa98 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -31,28 +31,50 @@ #define PCI_DEVICE_ID_INTEL_MRFLD 0x119e #define PCI_DEVICE_ID_INTEL_BSW 0x22B7 -struct dwc3_pci { - struct device *dev; - struct platform_device *dwc3; -}; +static int dwc3_pci_quirks(struct pci_dev *pdev) +{ + if (pdev->vendor == PCI_VENDOR_ID_AMD && + pdev->device == PCI_DEVICE_ID_AMD_NL_USB) { + struct dwc3_platform_data pdata; + + memset(&pdata, 0, sizeof(pdata)); + + pdata.has_lpm_erratum = true; + pdata.lpm_nyet_threshold = 0xf; + + pdata.u2exit_lfps_quirk = true; + pdata.u2ss_inp3_quirk = true; + pdata.req_p1p2p3_quirk = true; + pdata.del_p1p2p3_quirk = true; + pdata.del_phy_power_chg_quirk = true; + pdata.lfps_filter_quirk = true; + pdata.rx_detect_poll_quirk = true; + + pdata.tx_de_emphasis_quirk = true; + pdata.tx_de_emphasis = 1; + + /* + * FIXME these quirks should be removed when AMD NL + * taps out + */ + pdata.disable_scramble_quirk = true; + pdata.dis_u3_susphy_quirk = true; + pdata.dis_u2_susphy_quirk = true; + + return platform_device_add_data(pci_get_drvdata(pdev), &pdata, + sizeof(pdata)); + } + + return 0; +} static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id) { struct resource res[2]; struct platform_device *dwc3; - struct dwc3_pci *glue; int ret; struct device *dev = &pci->dev; - struct dwc3_platform_data dwc3_pdata; - - memset(&dwc3_pdata, 0x00, sizeof(dwc3_pdata)); - - glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); - if (!glue) - return -ENOMEM; - - glue->dev = dev; ret = pcim_enable_device(pci); if (ret) { @@ -79,68 +101,34 @@ static int dwc3_pci_probe(struct pci_dev *pci, res[1].name = "dwc_usb3"; res[1].flags = IORESOURCE_IRQ; - if (pci->vendor == PCI_VENDOR_ID_AMD && - pci->device == PCI_DEVICE_ID_AMD_NL_USB) { - dwc3_pdata.has_lpm_erratum = true; - dwc3_pdata.lpm_nyet_threshold = 0xf; - - dwc3_pdata.u2exit_lfps_quirk = true; - dwc3_pdata.u2ss_inp3_quirk = true; - dwc3_pdata.req_p1p2p3_quirk = true; - dwc3_pdata.del_p1p2p3_quirk = true; - dwc3_pdata.del_phy_power_chg_quirk = true; - dwc3_pdata.lfps_filter_quirk = true; - dwc3_pdata.rx_detect_poll_quirk = true; - - dwc3_pdata.tx_de_emphasis_quirk = true; - dwc3_pdata.tx_de_emphasis = 1; - - /* - * FIXME these quirks should be removed when AMD NL - * taps out - */ - dwc3_pdata.disable_scramble_quirk = true; - dwc3_pdata.dis_u3_susphy_quirk = true; - dwc3_pdata.dis_u2_susphy_quirk = true; - } - ret = platform_device_add_resources(dwc3, res, ARRAY_SIZE(res)); if (ret) { dev_err(dev, "couldn't add resources to dwc3 device\n"); return ret; } - pci_set_drvdata(pci, glue); - - ret = platform_device_add_data(dwc3, &dwc3_pdata, sizeof(dwc3_pdata)); + pci_set_drvdata(pci, dwc3); + ret = dwc3_pci_quirks(pci); if (ret) - goto err3; - - dma_set_coherent_mask(&dwc3->dev, dev->coherent_dma_mask); + goto err; - dwc3->dev.dma_mask = dev->dma_mask; - dwc3->dev.dma_parms = dev->dma_parms; dwc3->dev.parent = dev; - glue->dwc3 = dwc3; ret = platform_device_add(dwc3); if (ret) { dev_err(dev, "failed to register dwc3 device\n"); - goto err3; + goto err; } return 0; - -err3: +err: platform_device_put(dwc3); return ret; } static void dwc3_pci_remove(struct pci_dev *pci) { - struct dwc3_pci *glue = pci_get_drvdata(pci); - - platform_device_unregister(glue->dwc3); + platform_device_unregister(pci_get_drvdata(pci)); } static const struct pci_device_id dwc3_pci_id_table[] = { -- cgit v0.10.2 From 18d6b32fca3841f7cd9479b4024abd8a9b299281 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Thu, 18 Dec 2014 09:55:10 +0100 Subject: usb: gadget: f_fs: add "no_disconnect" mode Since we can compose gadgets from many functions, there is the problem related to gadget breakage while FunctionFS daemon being closed. FFS function is userspace code so there is no way to know when it will close files (it doesn't matter what is the reason of this situation, it can be daemon logic, program breakage, process kill or any other). So when we have another function in gadget which, for example, sends some amount of data, does some software update or implements some real-time functionality, we may want to keep the gadget connected despite FFS function is no longer functional. We can't just remove one of functions from gadget since it has been enumerated, so the only way to keep entire gadget working is to make broken FFS function deactivated but still visible to host. For this purpose this patch introduces "no_disconnect" mode. It can be enabled by setting mount option "no_disconnect=1", and results with defering function disconnect to the moment of reopen ep0 file or filesystem unmount. After closing all endpoint files, FunctionFS is set to state FFS_DEACTIVATED. When ffs->state == FFS_DEACTIVATED: - function is still bound and visible to host, - setup requests are automatically stalled, - transfers on other endpoints are refused, - epfiles, except ep0, are deleted from the filesystem, - opening ep0 causes the function to be closed, and then FunctionFS is ready for descriptors and string write, - altsetting change causes the function to be closed - we want to keep function alive until another functions are potentialy used, altsetting change means that another configuration is being selected or USB cable was unplugged, which indicates that we don't need to stay longer in FFS_DEACTIVATED state - unmounting of the FunctionFS instance causes the function to be closed. Tested-by: David Cohen Acked-by: Michal Nazarewicz Signed-off-by: Robert Baldyga Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index a00ee97..e78a2c6 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -605,6 +605,8 @@ static unsigned int ffs_ep0_poll(struct file *file, poll_table *wait) } case FFS_CLOSING: break; + case FFS_DEACTIVATED: + break; } mutex_unlock(&ffs->mutex); @@ -1179,6 +1181,7 @@ struct ffs_sb_fill_data { struct ffs_file_perms perms; umode_t root_mode; const char *dev_name; + bool no_disconnect; struct ffs_data *ffs_data; }; @@ -1249,6 +1252,12 @@ static int ffs_fs_parse_opts(struct ffs_sb_fill_data *data, char *opts) /* Interpret option */ switch (eq - opts) { + case 13: + if (!memcmp(opts, "no_disconnect", 13)) + data->no_disconnect = !!value; + else + goto invalid; + break; case 5: if (!memcmp(opts, "rmode", 5)) data->root_mode = (value & 0555) | S_IFDIR; @@ -1313,6 +1322,7 @@ ffs_fs_mount(struct file_system_type *t, int flags, .gid = GLOBAL_ROOT_GID, }, .root_mode = S_IFDIR | 0500, + .no_disconnect = false, }; struct dentry *rv; int ret; @@ -1329,6 +1339,7 @@ ffs_fs_mount(struct file_system_type *t, int flags, if (unlikely(!ffs)) return ERR_PTR(-ENOMEM); ffs->file_perms = data.perms; + ffs->no_disconnect = data.no_disconnect; ffs->dev_name = kstrdup(dev_name, GFP_KERNEL); if (unlikely(!ffs->dev_name)) { @@ -1360,6 +1371,7 @@ ffs_fs_kill_sb(struct super_block *sb) kill_litter_super(sb); if (sb->s_fs_info) { ffs_release_dev(sb->s_fs_info); + ffs_data_closed(sb->s_fs_info); ffs_data_put(sb->s_fs_info); } } @@ -1416,7 +1428,11 @@ static void ffs_data_opened(struct ffs_data *ffs) ENTER(); atomic_inc(&ffs->ref); - atomic_inc(&ffs->opened); + if (atomic_add_return(1, &ffs->opened) == 1 && + ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + } } static void ffs_data_put(struct ffs_data *ffs) @@ -1438,6 +1454,21 @@ static void ffs_data_closed(struct ffs_data *ffs) ENTER(); if (atomic_dec_and_test(&ffs->opened)) { + if (ffs->no_disconnect) { + ffs->state = FFS_DEACTIVATED; + if (ffs->epfiles) { + ffs_epfiles_destroy(ffs->epfiles, + ffs->eps_count); + ffs->epfiles = NULL; + } + if (ffs->setup_state == FFS_SETUP_PENDING) + __ffs_ep0_stall(ffs); + } else { + ffs->state = FFS_CLOSING; + ffs_data_reset(ffs); + } + } + if (atomic_read(&ffs->opened) < 0) { ffs->state = FFS_CLOSING; ffs_data_reset(ffs); } @@ -1615,7 +1646,6 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) kfree(epfiles); } - static void ffs_func_eps_disable(struct ffs_function *func) { struct ffs_ep *ep = func->eps; @@ -1628,10 +1658,12 @@ static void ffs_func_eps_disable(struct ffs_function *func) /* pending requests get nuked */ if (likely(ep->ep)) usb_ep_disable(ep->ep); - epfile->ep = NULL; - ++ep; - ++epfile; + + if (epfile) { + epfile->ep = NULL; + ++epfile; + } } while (--count); spin_unlock_irqrestore(&func->ffs->eps_lock, flags); } @@ -2894,6 +2926,13 @@ static int ffs_func_bind(struct usb_configuration *c, /* Other USB function hooks *************************************************/ +static void ffs_reset_work(struct work_struct *work) +{ + struct ffs_data *ffs = container_of(work, + struct ffs_data, reset_work); + ffs_data_reset(ffs); +} + static int ffs_func_set_alt(struct usb_function *f, unsigned interface, unsigned alt) { @@ -2910,6 +2949,13 @@ static int ffs_func_set_alt(struct usb_function *f, if (ffs->func) ffs_func_eps_disable(ffs->func); + if (ffs->state == FFS_DEACTIVATED) { + ffs->state = FFS_CLOSING; + INIT_WORK(&ffs->reset_work, ffs_reset_work); + schedule_work(&ffs->reset_work); + return -ENODEV; + } + if (ffs->state != FFS_ACTIVE) return -ENODEV; diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index cd128e3..284a1f0 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -19,6 +19,7 @@ #include #include #include +#include #ifdef VERBOSE_DEBUG #ifndef pr_vdebug @@ -93,6 +94,26 @@ enum ffs_state { FFS_ACTIVE, /* + * Function is visible to host, but it's not functional. All + * setup requests are stalled and transfers on another endpoints + * are refused. All epfiles, except ep0, are deleted so there + * is no way to perform any operations on them. + * + * This state is set after closing all functionfs files, when + * mount parameter "no_disconnect=1" has been set. Function will + * remain in deactivated state until filesystem is umounted or + * ep0 is opened again. In the second case functionfs state will + * be reset, and it will be ready for descriptors and strings + * writing. + * + * This is useful only when functionfs is composed to gadget + * with another function which can perform some critical + * operations, and it's strongly desired to have this operations + * completed, even after functionfs files closure. + */ + FFS_DEACTIVATED, + + /* * All endpoints have been closed. This state is also set if * we encounter an unrecoverable error. The only * unrecoverable error is situation when after reading strings @@ -251,6 +272,9 @@ struct ffs_data { kgid_t gid; } file_perms; + bool no_disconnect; + struct work_struct reset_work; + /* * The endpoint files, filled by ffs_epfiles_create(), * destroyed by ffs_epfiles_destroy(). -- cgit v0.10.2 From fe198e34a44ce7f27e39c303d62a754129938194 Mon Sep 17 00:00:00 2001 From: Markus Pargmann Date: Wed, 14 Jan 2015 16:08:38 +0100 Subject: usb: musb: debugfs: improve copy_from_user() argument While the code is correct and functions well, it's still a bit misleading to add the reference operator in from of the buf argument. This patch simply removes that operator in order to make use of buf slightly better to the eyes. Signed-off-by: Markus Pargmann Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_debugfs.c b/drivers/usb/musb/musb_debugfs.c index ad3701a..bb13e04 100644 --- a/drivers/usb/musb/musb_debugfs.c +++ b/drivers/usb/musb/musb_debugfs.c @@ -194,7 +194,7 @@ static ssize_t musb_test_mode_write(struct file *file, memset(buf, 0x00, sizeof(buf)); - if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + if (copy_from_user(buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) return -EFAULT; if (!strncmp(buf, "force host", 9)) -- cgit v0.10.2 From df90f8381972369ff33580e5c4b3c3eea91cc91b Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 14 Jan 2015 23:59:48 +0300 Subject: usb: gadget: uvc: fix some error codes We're basically saying ERR_CAST(NULL) and PTR_ERR(NULL) here, which is nonsensical. Acked-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index f69f47a..ff30588 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -148,7 +148,7 @@ static struct config_item *uvcg_control_header_make(struct config_group *group, h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); h->desc.bLength = UVC_DT_HEADER_SIZE(1); h->desc.bDescriptorType = USB_DT_CS_INTERFACE; @@ -840,7 +840,7 @@ static int uvcg_streaming_header_allow_link(struct config_item *src, format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL); if (!format_ptr) { - ret = PTR_ERR(format_ptr); + ret = -ENOMEM; goto out; } ret = 0; @@ -960,7 +960,7 @@ static struct config_item h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&h->formats); h->desc.bDescriptorType = USB_DT_CS_INTERFACE; @@ -1278,7 +1278,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group, h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); h->frame.b_descriptor_type = USB_DT_CS_INTERFACE; h->frame.b_frame_index = 1; @@ -1563,7 +1563,7 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); h->desc.bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE; h->desc.bDescriptorType = USB_DT_CS_INTERFACE; @@ -1772,7 +1772,7 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group, h = kzalloc(sizeof(*h), GFP_KERNEL); if (!h) - return ERR_CAST(h); + return ERR_PTR(-ENOMEM); h->desc.bLength = UVC_DT_FORMAT_MJPEG_SIZE; h->desc.bDescriptorType = USB_DT_CS_INTERFACE; @@ -2124,7 +2124,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, count += 2; /* color_matching, NULL */ *class_array = kcalloc(count, sizeof(void *), GFP_KERNEL); if (!*class_array) { - ret = PTR_ERR(*class_array); + ret = -ENOMEM; goto unlock; } -- cgit v0.10.2 From ceeb010ba2534f0b78efde6b3aa7d208148df92f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 15 Jan 2015 00:02:18 +0300 Subject: usb: gadget: uvc: remove an impossible condition "num" is a u32 so "(num > 0xFFFFFFFF)" is never true. Also the range is already checked in kstrtou32(). Acked-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index ff30588..6d9c681 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1156,8 +1156,6 @@ static inline int __uvcg_fill_frm_intrv(char *buf, void *priv) ret = kstrtou32(buf, 0, &num); if (ret) return ret; - if (num > 0xFFFFFFFF) - return -EINVAL; interv = priv; **interv = cpu_to_le32(num); -- cgit v0.10.2 From c5b2dc68a7a7e31d8e88b3bf9d45bd9e78e9fb78 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 15 Jan 2015 00:03:08 +0300 Subject: usb: gadget: uvc: memory leak in uvcg_frame_make() We need to add a kfree(h) on an error path. Acked-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 6d9c681..09028d2 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1300,6 +1300,7 @@ static struct config_item *uvcg_frame_make(struct config_group *group, h->fmt_type = UVCG_MJPEG; } else { mutex_unlock(&opts->lock); + kfree(h); return ERR_PTR(-EINVAL); } ++fmt->num_frames; -- cgit v0.10.2 From 3c4c733ca9e3f88e3107b6e23b9789bf7769e1f4 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 15 Jan 2015 00:06:35 +0300 Subject: usb: gadget: uvc: cleanup UVCG_FRAME_ATTR macro 1) Change "conv" an "vnoc" to "to_cpu_endian" to "to_little_endian". 2) No need to check the "limit" because that is already handled in kstrtoXX so delete that parameter along with the check. 3) By using a "bits" parameter, we can combine the "uxx" parameter and the "str2u" parameters. 4) The kstrtou##bits() conversion does not need to be done under the mutex so move it to the start of the function. 5) Change the name of "identity_conv" to "noop_conversion". Acked-by: Andrzej Pietrasiewicz Acked-by: Laurent Pinchart Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 09028d2..cc2a613 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1030,7 +1030,7 @@ static struct configfs_item_operations uvcg_frame_item_ops = { .store_attribute = uvcg_frame_attr_store, }; -#define UVCG_FRAME_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ +#define UVCG_FRAME_ATTR(cname, aname, to_cpu_endian, to_little_endian, bits) \ static ssize_t uvcg_frame_##cname##_show(struct uvcg_frame *f, char *page)\ { \ struct f_uvc_opts *opts; \ @@ -1044,7 +1044,7 @@ static ssize_t uvcg_frame_##cname##_show(struct uvcg_frame *f, char *page)\ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(f->frame.cname)); \ + result = sprintf(page, "%d\n", to_cpu_endian(f->frame.cname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1059,7 +1059,11 @@ static ssize_t uvcg_frame_##cname##_store(struct uvcg_frame *f, \ struct uvcg_format *fmt; \ struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ int ret; \ - uxx num; \ + u##bits num; \ + \ + ret = kstrtou##bits(page, 0, &num); \ + if (ret) \ + return ret; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ @@ -1073,15 +1077,7 @@ static ssize_t uvcg_frame_##cname##_store(struct uvcg_frame *f, \ goto end; \ } \ \ - ret = str2u(page, 0, &num); \ - if (ret) \ - goto end; \ - \ - if (num > limit) { \ - ret = -EINVAL; \ - goto end; \ - } \ - f->frame.cname = vnoc(num); \ + f->frame.cname = to_little_endian(num); \ ret = len; \ end: \ mutex_unlock(&opts->lock); \ @@ -1095,24 +1091,20 @@ static struct uvcg_frame_attribute \ uvcg_frame_##cname##_show, \ uvcg_frame_##cname##_store) -#define identity_conv(x) (x) +#define noop_conversion(x) (x) -UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, identity_conv, kstrtou8, u8, - identity_conv, 0xFF); -UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, kstrtou16, u16, cpu_to_le16, - 0xFFFF); -UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, kstrtou16, u16, cpu_to_le16, - 0xFFFF); -UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, kstrtou32, u32, - cpu_to_le32, 0xFFFFFFFF); -UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, kstrtou32, u32, - cpu_to_le32, 0xFFFFFFFF); +UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, noop_conversion, + noop_conversion, 8); +UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, cpu_to_le16, 16); +UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, cpu_to_le16, 16); +UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, cpu_to_le32, 32); +UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, cpu_to_le32, 32); UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, - le32_to_cpu, kstrtou32, u32, cpu_to_le32, 0xFFFFFFFF); + le32_to_cpu, cpu_to_le32, 32); UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, - le32_to_cpu, kstrtou32, u32, cpu_to_le32, 0xFFFFFFFF); + le32_to_cpu, cpu_to_le32, 32); -#undef identity_conv +#undef noop_conversion #undef UVCG_FRAME_ATTR -- cgit v0.10.2 From da89dba1a0c69ed01e33ebf41327b90993d08492 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Thu, 15 Jan 2015 02:45:12 +0000 Subject: usb: phy: make GPIOs optional for the generic phy The use of GPIOs should be optional for the generic phy, otherwise the Altera SOCFPGA platform at least is broken. Fixes breakage caused by a combination of e9f2cefb0cd "usb: phy: generic: migrate to gpio_desc" and 135b3c4304d "usb: dwc2: platform: add generic PHY framework support". Reviewed-by: Robert Jarzmik Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index dd05254..9a826ff 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -218,10 +218,10 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, clk_rate = 0; needs_vcc = of_property_read_bool(node, "vcc-supply"); - nop->gpiod_reset = devm_gpiod_get(dev, "reset-gpios"); + nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset-gpios"); err = PTR_ERR(nop->gpiod_reset); if (!err) { - nop->gpiod_vbus = devm_gpiod_get(dev, + nop->gpiod_vbus = devm_gpiod_get_optional(dev, "vbus-detect-gpio"); err = PTR_ERR(nop->gpiod_vbus); } -- cgit v0.10.2 From 27f387059bedc3c4b3eb5b4c8bfe229dba59ce24 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 15 Jan 2015 13:40:04 +0200 Subject: usb: gadget: ethernet: re-use %pM specifier to print MAC Instead of custom approach the patch converts code to use %pM specifier to print MAC. Signed-off-by: Andy Shevchenko Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index 6e6f876..f1fd777 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -729,9 +729,7 @@ static int get_ether_addr_str(u8 dev_addr[ETH_ALEN], char *str, int len) if (len < 18) return -EINVAL; - snprintf(str, len, "%02x:%02x:%02x:%02x:%02x:%02x", - dev_addr[0], dev_addr[1], dev_addr[2], - dev_addr[3], dev_addr[4], dev_addr[5]); + snprintf(str, len, "%pM", dev_addr); return 18; } -- cgit v0.10.2 From b0bb9bb6ce01e37e47db04a31fd93dbe2d2be3d3 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Thu, 15 Jan 2015 19:21:46 +0000 Subject: Revert "usb: dwc2: add bus suspend/resume for dwc2" This reverts commit 0cf884e819e05437287a668b9bfcc198bab6329c. Even after applying the follow-on patch at https://patchwork.kernel.org/patch/5325111 there are still problems with device connect on the Altera SOCFPGA platform at least. One possible fix would be to add a whitelist to enable suspend/resume on platforms where it does work correctly. Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index a0cd9db..755e16b 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1473,30 +1473,6 @@ static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) } } -static void dwc2_port_resume(struct dwc2_hsotg *hsotg) -{ - u32 hprt0; - - /* After clear the Stop PHY clock bit, we should wait for a moment - * for PLL work stable with clock output. - */ - writel(0, hsotg->regs + PCGCTL); - usleep_range(2000, 4000); - - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_RES; - writel(hprt0, hsotg->regs + HPRT0); - hprt0 &= ~HPRT0_SUSP; - /* according to USB2.0 Spec 7.1.7.7, the host must send the resume - * signal for at least 20ms - */ - usleep_range(20000, 25000); - - hprt0 &= ~HPRT0_RES; - writel(hprt0, hsotg->regs + HPRT0); - hsotg->lx_state = DWC2_L0; -} - /* Handles hub class-specific requests */ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, u16 wvalue, u16 windex, char *buf, u16 wlength) @@ -1542,7 +1518,17 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, case USB_PORT_FEAT_SUSPEND: dev_dbg(hsotg->dev, "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); - dwc2_port_resume(hsotg); + writel(0, hsotg->regs + PCGCTL); + usleep_range(20000, 40000); + + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RES; + writel(hprt0, hsotg->regs + HPRT0); + hprt0 &= ~HPRT0_SUSP; + usleep_range(100000, 150000); + + hprt0 &= ~HPRT0_RES; + writel(hprt0, hsotg->regs + HPRT0); break; case USB_PORT_FEAT_POWER: @@ -2315,55 +2301,6 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd) usleep_range(1000, 3000); } -static int _dwc2_hcd_suspend(struct usb_hcd *hcd) -{ - struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); - u32 hprt0; - - if (!((hsotg->op_state == OTG_STATE_B_HOST) || - (hsotg->op_state == OTG_STATE_A_HOST))) - return 0; - - /* TODO: We get into suspend from 'on' state, maybe we need to do - * something if we get here from DWC2_L1(LPM sleep) state one day. - */ - if (hsotg->lx_state != DWC2_L0) - return 0; - - hprt0 = dwc2_read_hprt0(hsotg); - if (hprt0 & HPRT0_CONNSTS) { - dwc2_port_suspend(hsotg, 1); - } else { - u32 pcgctl = readl(hsotg->regs + PCGCTL); - - pcgctl |= PCGCTL_STOPPCLK; - writel(pcgctl, hsotg->regs + PCGCTL); - } - - return 0; -} - -static int _dwc2_hcd_resume(struct usb_hcd *hcd) -{ - struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); - u32 hprt0; - - if (!((hsotg->op_state == OTG_STATE_B_HOST) || - (hsotg->op_state == OTG_STATE_A_HOST))) - return 0; - - if (hsotg->lx_state != DWC2_L2) - return 0; - - hprt0 = dwc2_read_hprt0(hsotg); - if ((hprt0 & HPRT0_CONNSTS) && (hprt0 & HPRT0_SUSP)) - dwc2_port_resume(hsotg); - else - writel(0, hsotg->regs + PCGCTL); - - return 0; -} - /* Returns the current frame number */ static int _dwc2_hcd_get_frame_number(struct usb_hcd *hcd) { @@ -2734,9 +2671,6 @@ static struct hc_driver dwc2_hc_driver = { .hub_status_data = _dwc2_hcd_hub_status_data, .hub_control = _dwc2_hcd_hub_control, .clear_tt_buffer_complete = _dwc2_hcd_clear_tt_buffer_complete, - - .bus_suspend = _dwc2_hcd_suspend, - .bus_resume = _dwc2_hcd_resume, }; /* -- cgit v0.10.2 From 16272ae77e0f167af70eabd476059137d64569a8 Mon Sep 17 00:00:00 2001 From: Paul Zimmerman Date: Thu, 15 Jan 2015 20:14:10 +0000 Subject: MAINTAINERS: update maintainer entry for dwc2 driver Update the MAINTAINERS entry for the dwc2 driver to show John Youn as the new maintainer Signed-off-by: Paul Zimmerman Signed-off-by: Felipe Balbi diff --git a/MAINTAINERS b/MAINTAINERS index 2fa3853..97de006 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3028,7 +3028,7 @@ S: Maintained F: drivers/platform/x86/dell-wmi.c DESIGNWARE USB2 DRD IP DRIVER -M: Paul Zimmerman +M: John Youn L: linux-usb@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb.git S: Maintained -- cgit v0.10.2 From 06ab8b04d232c80cb4b23984290ce413058cc975 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Fri, 16 Jan 2015 15:14:27 +0100 Subject: usb: gadget: uvc: preserve the address passed to kfree() __uvcg_fill_strm() called from __uvcg_iter_stream_cls() might have advanced the "data" even if __uvcg_iter_stream_cls() returns an error, so use a backup copy as an argument to kfree(). Acked-by: Laurent Pinchart Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index cc2a613..49f25e8 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -2086,7 +2086,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex; struct uvc_descriptor_header ***class_array, **cl_arr; struct uvcg_streaming_header *target_hdr; - void *data; + void *data, *data_save; size_t size = 0, count = 0; int ret = -EINVAL; @@ -2119,7 +2119,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, goto unlock; } - data = kzalloc(size, GFP_KERNEL); + data = data_save = kzalloc(size, GFP_KERNEL); if (!data) { kfree(*class_array); *class_array = NULL; @@ -2132,7 +2132,11 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, if (ret) { kfree(*class_array); *class_array = NULL; - kfree(data); + /* + * __uvcg_fill_strm() called from __uvcg_iter_stream_cls() + * might have advanced the "data", so use a backup copy + */ + kfree(data_save); goto unlock; } *cl_arr = (struct uvc_descriptor_header *)&opts->uvc_color_matching; -- cgit v0.10.2 From 3d040de802d82fa744f599d8a886dda3462fb6e6 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:54:15 +0300 Subject: usb: dwc2: hcd: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 755e16b..c3e66f0 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1608,7 +1608,9 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, hub_desc->bDescLength = 9; hub_desc->bDescriptorType = 0x29; hub_desc->bNbrPorts = 1; - hub_desc->wHubCharacteristics = cpu_to_le16(0x08); + hub_desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_COMMON_LPSM | + HUB_CHAR_INDV_PORT_OCPM); hub_desc->bPwrOn2PwrGood = 1; hub_desc->bHubContrCurrent = 0; hub_desc->u.hs.DeviceRemovable[0] = 0; -- cgit v0.10.2 From d906d61cf8a3080b3bf7fb2e12efc2b645d514d4 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:55:55 +0300 Subject: usb: dummy_hcd: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Acked-by: Alan Stern Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 9c59880..270c1ec 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -1924,7 +1924,9 @@ ss_hub_descriptor(struct usb_hub_descriptor *desc) memset(desc, 0, sizeof *desc); desc->bDescriptorType = 0x2a; desc->bDescLength = 12; - desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); desc->bNbrPorts = 1; desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ desc->u.ss.DeviceRemovable = 0xffff; @@ -1935,7 +1937,9 @@ static inline void hub_descriptor(struct usb_hub_descriptor *desc) memset(desc, 0, sizeof *desc); desc->bDescriptorType = 0x29; desc->bDescLength = 9; - desc->wHubCharacteristics = cpu_to_le16(0x0001); + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | + HUB_CHAR_COMMON_OCPM); desc->bNbrPorts = 1; desc->u.hs.DeviceRemovable[0] = 0xff; desc->u.hs.DeviceRemovable[1] = 0xff; -- cgit v0.10.2 From c509ba6e48d394aa7fe0f04c38493967c1268317 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:57:18 +0300 Subject: usb: musb: virthub: use HUB_CHAR_* Fix using the bare numbers to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index b072420..662de58 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -349,9 +349,9 @@ int musb_hub_control( desc->bDescriptorType = 0x29; desc->bNbrPorts = 1; desc->wHubCharacteristics = cpu_to_le16( - 0x0001 /* per-port power switching */ - | 0x0010 /* no overcurrent reporting */ - ); + HUB_CHAR_INDV_PORT_LPSM /* per-port power switching */ + | HUB_CHAR_NO_OCPM /* no overcurrent reporting */ + ); desc->bPwrOn2PwrGood = 5; /* msec/2 */ desc->bHubContrCurrent = 0; -- cgit v0.10.2 From 16eb1a67f3e6f8b90474b882fe210df229da7573 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Mon, 19 Jan 2015 01:59:09 +0300 Subject: usb: renesas_usbhs: mod_host: use HUB_CHAR_* Fix using the bare number to set the 'wHubCharacteristics' field of the Hub Descriptor while the values are #define'd in . Signed-off-by: Sergei Shtylyov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/mod_host.c b/drivers/usb/renesas_usbhs/mod_host.c index f0d3231..96eead6 100644 --- a/drivers/usb/renesas_usbhs/mod_host.c +++ b/drivers/usb/renesas_usbhs/mod_host.c @@ -1234,7 +1234,8 @@ static int __usbhsh_hub_get_status(struct usbhsh_hpriv *hpriv, desc->bNbrPorts = roothub_id; desc->bDescLength = 9; desc->bPwrOn2PwrGood = 0; - desc->wHubCharacteristics = cpu_to_le16(0x0011); + desc->wHubCharacteristics = + cpu_to_le16(HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_NO_OCPM); desc->u.hs.DeviceRemovable[0] = (roothub_id << 1); desc->u.hs.DeviceRemovable[1] = ~0; dev_dbg(dev, "%s :: GetHubDescriptor\n", __func__); -- cgit v0.10.2 From 6e3f53ab5bd3092e195b290802208a39261fdf26 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 19 Jan 2015 12:53:16 +0900 Subject: usb: renesas_usbhs: add usbhsf_dma_init_pdev() function To add support for requesting DT DMA in the future, this patch adds usbhsf_dma_init_pdev() function. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index f46271c..48e31b9 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -1054,10 +1054,8 @@ static void usbhsf_dma_quit(struct usbhs_priv *priv, struct usbhs_fifo *fifo) fifo->rx_chan = NULL; } -static void usbhsf_dma_init(struct usbhs_priv *priv, - struct usbhs_fifo *fifo) +static void usbhsf_dma_init_pdev(struct usbhs_fifo *fifo) { - struct device *dev = usbhs_priv_to_dev(priv); dma_cap_mask_t mask; dma_cap_zero(mask); @@ -1069,6 +1067,14 @@ static void usbhsf_dma_init(struct usbhs_priv *priv, dma_cap_set(DMA_SLAVE, mask); fifo->rx_chan = dma_request_channel(mask, usbhsf_dma_filter, &fifo->rx_slave); +} + +static void usbhsf_dma_init(struct usbhs_priv *priv, + struct usbhs_fifo *fifo) +{ + struct device *dev = usbhs_priv_to_dev(priv); + + usbhsf_dma_init_pdev(fifo); if (fifo->tx_chan || fifo->rx_chan) dev_dbg(dev, "enable DMAEngine (%s%s%s)\n", -- cgit v0.10.2 From abd2dbf6bb1b5f3a03a8c76b1a8879da1dd30caa Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 19 Jan 2015 12:53:17 +0900 Subject: usb: renesas_usbhs: add support for requesting DT DMA This patch adds dma_request_slave_channel_reason() calling to request dma slave channels for multiplatform environment. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi diff --git a/Documentation/devicetree/bindings/usb/renesas_usbhs.txt b/Documentation/devicetree/bindings/usb/renesas_usbhs.txt index b08c903..61b045b 100644 --- a/Documentation/devicetree/bindings/usb/renesas_usbhs.txt +++ b/Documentation/devicetree/bindings/usb/renesas_usbhs.txt @@ -14,6 +14,8 @@ Optional properties: function should be enabled - phys: phandle + phy specifier pair - phy-names: must be "usb" + - dmas: Must contain a list of references to DMA specifiers. + - dma-names : Must contain a list of DMA names, "tx" or "rx". Example: usbhs: usb@e6590000 { diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index 48e31b9..4c086b1 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -1069,12 +1069,21 @@ static void usbhsf_dma_init_pdev(struct usbhs_fifo *fifo) &fifo->rx_slave); } +static void usbhsf_dma_init_dt(struct device *dev, struct usbhs_fifo *fifo) +{ + fifo->tx_chan = dma_request_slave_channel_reason(dev, "tx"); + fifo->rx_chan = dma_request_slave_channel_reason(dev, "rx"); +} + static void usbhsf_dma_init(struct usbhs_priv *priv, struct usbhs_fifo *fifo) { struct device *dev = usbhs_priv_to_dev(priv); - usbhsf_dma_init_pdev(fifo); + if (dev->of_node) + usbhsf_dma_init_dt(dev, fifo); + else + usbhsf_dma_init_pdev(fifo); if (fifo->tx_chan || fifo->rx_chan) dev_dbg(dev, "enable DMAEngine (%s%s%s)\n", -- cgit v0.10.2 From 0149b07a9e28b0d8bd2fc1c238ffe7d530c2673f Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Mon, 26 Jan 2015 16:22:06 -0600 Subject: usb: musb: cppi41: correct the macro name EP_MODE_AUTOREG_* The macro EP_MODE_AUTOREG_* should be called EP_MODE_AUTOREQ_*, as they are used for register AUTOREQ. Signed-off-by: Bin Liu Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index c39a16a..6af10e7 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -9,9 +9,9 @@ #define RNDIS_REG(x) (0x80 + ((x - 1) * 4)) -#define EP_MODE_AUTOREG_NONE 0 -#define EP_MODE_AUTOREG_ALL_NEOP 1 -#define EP_MODE_AUTOREG_ALWAYS 3 +#define EP_MODE_AUTOREQ_NONE 0 +#define EP_MODE_AUTOREQ_ALL_NEOP 1 +#define EP_MODE_AUTOREQ_ALWAYS 3 #define EP_MODE_DMA_TRANSPARENT 0 #define EP_MODE_DMA_RNDIS 1 @@ -404,19 +404,19 @@ static bool cppi41_configure_channel(struct dma_channel *channel, /* auto req */ cppi41_set_autoreq_mode(cppi41_channel, - EP_MODE_AUTOREG_ALL_NEOP); + EP_MODE_AUTOREQ_ALL_NEOP); } else { musb_writel(musb->ctrl_base, RNDIS_REG(cppi41_channel->port_num), 0); cppi41_set_dma_mode(cppi41_channel, EP_MODE_DMA_TRANSPARENT); cppi41_set_autoreq_mode(cppi41_channel, - EP_MODE_AUTOREG_NONE); + EP_MODE_AUTOREQ_NONE); } } else { /* fallback mode */ cppi41_set_dma_mode(cppi41_channel, EP_MODE_DMA_TRANSPARENT); - cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREG_NONE); + cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREQ_NONE); len = min_t(u32, packet_sz, len); } cppi41_channel->prog_len = len; -- cgit v0.10.2 From cb83df77f3ec151d68a1b6be957207e6fc7b7f50 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Mon, 26 Jan 2015 16:22:07 -0600 Subject: usb: musb: cppi41: improve rx channel abort routine 1. set AUTOREQ to NONE at the beginning of teardown; 2. add delay for dma pipeline to drain; 3. Do not set USB_TDOWN bit for RX teardown. The CPPI hw has an issue that when tearing down a RX channel, if another RX channel is receiving data, the CPPI will lockup. To workaround the issue, do not set the CPPI TD bit. The steps before this point ensures the CPPI channel will be torn down properly. Signed-off-by: Bin Liu Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index 6af10e7..be84562 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -549,10 +549,15 @@ static int cppi41_dma_channel_abort(struct dma_channel *channel) csr &= ~MUSB_TXCSR_DMAENAB; musb_writew(epio, MUSB_TXCSR, csr); } else { + cppi41_set_autoreq_mode(cppi41_channel, EP_MODE_AUTOREQ_NONE); + csr = musb_readw(epio, MUSB_RXCSR); csr &= ~(MUSB_RXCSR_H_REQPKT | MUSB_RXCSR_DMAENAB); musb_writew(epio, MUSB_RXCSR, csr); + /* wait to drain cppi dma pipe line */ + udelay(50); + csr = musb_readw(epio, MUSB_RXCSR); if (csr & MUSB_RXCSR_RXPKTRDY) { csr |= MUSB_RXCSR_FLUSHFIFO; @@ -566,13 +571,14 @@ static int cppi41_dma_channel_abort(struct dma_channel *channel) tdbit <<= 16; do { - musb_writel(musb->ctrl_base, USB_TDOWN, tdbit); + if (is_tx) + musb_writel(musb->ctrl_base, USB_TDOWN, tdbit); ret = dmaengine_terminate_all(cppi41_channel->dc); } while (ret == -EAGAIN); - musb_writel(musb->ctrl_base, USB_TDOWN, tdbit); - if (is_tx) { + musb_writel(musb->ctrl_base, USB_TDOWN, tdbit); + csr = musb_readw(epio, MUSB_TXCSR); if (csr & MUSB_TXCSR_TXPKTRDY) { csr |= MUSB_TXCSR_FLUSHFIFO; -- cgit v0.10.2 From df6738d0d23321da834e9025845e26e09b0f6615 Mon Sep 17 00:00:00 2001 From: Mario Schuknecht Date: Mon, 26 Jan 2015 20:30:27 +0100 Subject: usb: gadget: Fix os desc test USB vendor type is encoded in field bmRequestType. Make test USB_TYPE_VENDOR with bRequestType instead of bRequest. Signed-off-by: Mario Schuknecht Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 6178353..13adfd1 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -1655,7 +1655,7 @@ unknown: * OS descriptors handling */ if (cdev->use_os_string && cdev->os_desc_config && - (ctrl->bRequest & USB_TYPE_VENDOR) && + (ctrl->bRequestType & USB_TYPE_VENDOR) && ctrl->bRequest == cdev->b_vendor_code) { struct usb_request *req; struct usb_configuration *os_desc_cfg; -- cgit v0.10.2 From acba23fec527012e901636e4ba091ee25461c943 Mon Sep 17 00:00:00 2001 From: Mario Schuknecht Date: Mon, 26 Jan 2015 20:40:21 +0100 Subject: usb: gadget: f_fs: Fix loop variable Use if-loop variable 'epfile' instead of start variable 'epfiles'. Now the correct endpoint file name is stored. Signed-off-by: Mario Schuknecht Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index e78a2c6..14e44d7 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1611,10 +1611,10 @@ static int ffs_epfiles_create(struct ffs_data *ffs) mutex_init(&epfile->mutex); init_waitqueue_head(&epfile->wait); if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) - sprintf(epfiles->name, "ep%02x", ffs->eps_addrmap[i]); + sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]); else - sprintf(epfiles->name, "ep%u", i); - epfile->dentry = ffs_sb_create_file(ffs->sb, epfiles->name, + sprintf(epfile->name, "ep%u", i); + epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name, epfile, &ffs_epfile_operations); if (unlikely(!epfile->dentry)) { -- cgit v0.10.2 From 5e33f6fdf735cda1d4580fe6f1878da05718fe73 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Fri, 23 Jan 2015 13:41:01 +0100 Subject: usb: gadget: ffs: add eventfd notification about ffs events Add eventfd which notifies userspace about ep0 events and AIO completion events. It simplifies using of FunctionFS with event loop, because now we need to poll on single file (instead of polling on ep0 and eventfd's supplied to AIO layer). FunctionFS eventfd is not triggered if another eventfd is supplied to AIO layer (in AIO request). It can be useful, for example, when we want to handle AIO transations for chosen endpoint in separate thread. Signed-off-by: Robert Baldyga Acked-by: Michal Nazarewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 14e44d7..af98b09 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "u_fs.h" #include "u_f.h" @@ -153,6 +154,8 @@ struct ffs_io_data { struct usb_ep *ep; struct usb_request *req; + + struct ffs_data *ffs; }; struct ffs_desc_helper { @@ -674,6 +677,9 @@ static void ffs_user_copy_worker(struct work_struct *work) aio_complete(io_data->kiocb, ret, ret); + if (io_data->ffs->ffs_eventfd && !io_data->kiocb->ki_eventfd) + eventfd_signal(io_data->ffs->ffs_eventfd, 1); + usb_ep_free_request(io_data->ep, io_data->req); io_data->kiocb->private = NULL; @@ -827,6 +833,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) io_data->buf = data; io_data->ep = ep->ep; io_data->req = req; + io_data->ffs = epfile->ffs; req->context = io_data; req->complete = ffs_epfile_async_io_complete; @@ -1510,6 +1517,9 @@ static void ffs_data_clear(struct ffs_data *ffs) if (ffs->epfiles) ffs_epfiles_destroy(ffs->epfiles, ffs->eps_count); + if (ffs->ffs_eventfd) + eventfd_ctx_put(ffs->ffs_eventfd); + kfree(ffs->raw_descs_data); kfree(ffs->raw_strings); kfree(ffs->stringtabs); @@ -2169,7 +2179,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, FUNCTIONFS_HAS_HS_DESC | FUNCTIONFS_HAS_SS_DESC | FUNCTIONFS_HAS_MS_OS_DESC | - FUNCTIONFS_VIRTUAL_ADDR)) { + FUNCTIONFS_VIRTUAL_ADDR | + FUNCTIONFS_EVENTFD)) { ret = -ENOSYS; goto error; } @@ -2180,6 +2191,20 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, goto error; } + if (flags & FUNCTIONFS_EVENTFD) { + if (len < 4) + goto error; + ffs->ffs_eventfd = + eventfd_ctx_fdget((int)get_unaligned_le32(data)); + if (IS_ERR(ffs->ffs_eventfd)) { + ret = PTR_ERR(ffs->ffs_eventfd); + ffs->ffs_eventfd = NULL; + goto error; + } + data += 4; + len -= 4; + } + /* Read fs_count, hs_count and ss_count (if present) */ for (i = 0; i < 3; ++i) { if (!(flags & (1 << i))) { @@ -2454,6 +2479,8 @@ static void __ffs_event_add(struct ffs_data *ffs, pr_vdebug("adding event %d\n", type); ffs->ev.types[ffs->ev.count++] = type; wake_up_locked(&ffs->ev.waitq); + if (ffs->ffs_eventfd) + eventfd_signal(ffs->ffs_eventfd, 1); } static void ffs_event_add(struct ffs_data *ffs, diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index 284a1f0..6013985 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -272,6 +272,7 @@ struct ffs_data { kgid_t gid; } file_perms; + struct eventfd_ctx *ffs_eventfd; bool no_disconnect; struct work_struct reset_work; diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 295ba29..108dd79 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -20,6 +20,7 @@ enum functionfs_flags { FUNCTIONFS_HAS_SS_DESC = 4, FUNCTIONFS_HAS_MS_OS_DESC = 8, FUNCTIONFS_VIRTUAL_ADDR = 16, + FUNCTIONFS_EVENTFD = 32, }; /* Descriptor of an non-audio endpoint */ -- cgit v0.10.2 From 9fdd84d23c76825f3fc3c0782ea433c52d41971a Mon Sep 17 00:00:00 2001 From: Asaf Vertz Date: Wed, 21 Jan 2015 11:06:34 +0200 Subject: usb: gadget: zero: fix format string warnings Fixed the following warnings (reported by cppcheck): [drivers/usb/gadget/function/f_sourcesink.c:1217]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1261]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1305]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1349]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1393]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1437]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1476]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1520]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1564]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'. [drivers/usb/gadget/function/f_sourcesink.c:1608]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int' Reviewed-by: Amit Virdi Signed-off-by: Asaf Vertz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/f_sourcesink.c b/drivers/usb/gadget/function/f_sourcesink.c index 80be25b..e07c50c 100644 --- a/drivers/usb/gadget/function/f_sourcesink.c +++ b/drivers/usb/gadget/function/f_sourcesink.c @@ -1214,7 +1214,7 @@ static ssize_t f_ss_opts_pattern_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->pattern); + result = sprintf(page, "%u", opts->pattern); mutex_unlock(&opts->lock); return result; @@ -1258,7 +1258,7 @@ static ssize_t f_ss_opts_isoc_interval_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->isoc_interval); + result = sprintf(page, "%u", opts->isoc_interval); mutex_unlock(&opts->lock); return result; @@ -1302,7 +1302,7 @@ static ssize_t f_ss_opts_isoc_maxpacket_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->isoc_maxpacket); + result = sprintf(page, "%u", opts->isoc_maxpacket); mutex_unlock(&opts->lock); return result; @@ -1346,7 +1346,7 @@ static ssize_t f_ss_opts_isoc_mult_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->isoc_mult); + result = sprintf(page, "%u", opts->isoc_mult); mutex_unlock(&opts->lock); return result; @@ -1390,7 +1390,7 @@ static ssize_t f_ss_opts_isoc_maxburst_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->isoc_maxburst); + result = sprintf(page, "%u", opts->isoc_maxburst); mutex_unlock(&opts->lock); return result; @@ -1434,7 +1434,7 @@ static ssize_t f_ss_opts_bulk_buflen_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->bulk_buflen); + result = sprintf(page, "%u", opts->bulk_buflen); mutex_unlock(&opts->lock); return result; @@ -1473,7 +1473,7 @@ static ssize_t f_ss_opts_int_interval_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->int_interval); + result = sprintf(page, "%u", opts->int_interval); mutex_unlock(&opts->lock); return result; @@ -1517,7 +1517,7 @@ static ssize_t f_ss_opts_int_maxpacket_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->int_maxpacket); + result = sprintf(page, "%u", opts->int_maxpacket); mutex_unlock(&opts->lock); return result; @@ -1561,7 +1561,7 @@ static ssize_t f_ss_opts_int_mult_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->int_mult); + result = sprintf(page, "%u", opts->int_mult); mutex_unlock(&opts->lock); return result; @@ -1605,7 +1605,7 @@ static ssize_t f_ss_opts_int_maxburst_show(struct f_ss_opts *opts, char *page) int result; mutex_lock(&opts->lock); - result = sprintf(page, "%d", opts->int_maxburst); + result = sprintf(page, "%u", opts->int_maxburst); mutex_unlock(&opts->lock); return result; -- cgit v0.10.2 From 7eb42c6e808a0a18b8493268d06e05c1654fbbb2 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:40 +0200 Subject: usb: isp1760: Use the gpio descriptor API Switching to the managed gpio descriptor API simplifies both error and cleanup code paths (by removing the need to free the gpio) and runtime code (by removing manual handling of the active low flag). It also permits handling the reset gpio entirely from within the HCD code, sharing it between the different glue layers. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 395649f..e4a9424 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -11,6 +11,7 @@ * (c) 2011 Arvid Brodin * */ +#include #include #include #include @@ -24,7 +25,6 @@ #include #include #include -#include #include "isp1760-hcd.h" @@ -57,7 +57,7 @@ struct isp1760_hcd { unsigned long next_statechange; unsigned int devflags; - int rst_gpio; + struct gpio_desc *rst_gpio; }; static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) @@ -444,15 +444,10 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) u32 scratch, hwmode; /* low-level chip reset */ - if (gpio_is_valid(priv->rst_gpio)) { - unsigned int rst_lvl; - - rst_lvl = (priv->devflags & - ISP1760_FLAG_RESET_ACTIVE_HIGH) ? 1 : 0; - - gpio_set_value(priv->rst_gpio, rst_lvl); + if (priv->rst_gpio) { + gpiod_set_value_cansleep(priv->rst_gpio, 1); mdelay(50); - gpio_set_value(priv->rst_gpio, !rst_lvl); + gpiod_set_value_cansleep(priv->rst_gpio, 0); } /* Setup HW Mode Control: This assumes a level active-low interrupt */ @@ -2215,7 +2210,6 @@ void deinit_kmem_cache(void) struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, - int rst_gpio, struct device *dev, const char *busname, unsigned int devflags) { @@ -2235,7 +2229,13 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, priv = hcd_to_priv(hcd); priv->devflags = devflags; - priv->rst_gpio = rst_gpio; + + priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); + if (IS_ERR(priv->rst_gpio)) { + ret = PTR_ERR(priv->rst_gpio); + goto err_put; + } + init_memory(priv); hcd->regs = ioremap(res_start, res_len); if (!hcd->regs) { diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 33dc79c..fda0f2d 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -4,7 +4,6 @@ /* exports for if */ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, - int rst_gpio, struct device *dev, const char *busname, unsigned int devflags); int init_kmem_once(void); @@ -127,7 +126,6 @@ typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, #define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ #define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ #define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ -#define ISP1760_FLAG_RESET_ACTIVE_HIGH 0x80000000 /* RESET GPIO active high */ /* chip memory management */ struct memory_chunk { diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 09254a4..7e8e7f6 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -24,7 +24,6 @@ #include #include #include -#include #endif #ifdef CONFIG_PCI @@ -34,7 +33,6 @@ #if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) struct isp1760 { struct usb_hcd *hcd; - int rst_gpio; }; static int of_isp1760_probe(struct platform_device *dev) @@ -47,7 +45,6 @@ static int of_isp1760_probe(struct platform_device *dev) resource_size_t res_len; int ret; unsigned int devflags = 0; - enum of_gpio_flags gpio_flags; u32 bus_width = 0; drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); @@ -94,36 +91,17 @@ static int of_isp1760_probe(struct platform_device *dev) if (of_get_property(dp, "dreq-polarity", NULL) != NULL) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - drvdata->rst_gpio = of_get_gpio_flags(dp, 0, &gpio_flags); - if (gpio_is_valid(drvdata->rst_gpio)) { - ret = gpio_request(drvdata->rst_gpio, dev_name(&dev->dev)); - if (!ret) { - if (!(gpio_flags & OF_GPIO_ACTIVE_LOW)) { - devflags |= ISP1760_FLAG_RESET_ACTIVE_HIGH; - gpio_direction_output(drvdata->rst_gpio, 0); - } else { - gpio_direction_output(drvdata->rst_gpio, 1); - } - } else { - drvdata->rst_gpio = ret; - } - } - drvdata->hcd = isp1760_register(memory.start, res_len, virq, - IRQF_SHARED, drvdata->rst_gpio, - &dev->dev, dev_name(&dev->dev), - devflags); + IRQF_SHARED, &dev->dev, + dev_name(&dev->dev), devflags); if (IS_ERR(drvdata->hcd)) { ret = PTR_ERR(drvdata->hcd); - goto free_gpio; + goto release_reg; } platform_set_drvdata(dev, drvdata); return ret; -free_gpio: - if (gpio_is_valid(drvdata->rst_gpio)) - gpio_free(drvdata->rst_gpio); release_reg: release_mem_region(memory.start, res_len); free_data: @@ -140,9 +118,6 @@ static int of_isp1760_remove(struct platform_device *dev) release_mem_region(drvdata->hcd->rsrc_start, drvdata->hcd->rsrc_len); usb_put_hcd(drvdata->hcd); - if (gpio_is_valid(drvdata->rst_gpio)) - gpio_free(drvdata->rst_gpio); - kfree(drvdata); return 0; } @@ -279,8 +254,8 @@ static int isp1761_pci_probe(struct pci_dev *dev, dev->dev.dma_mask = NULL; hcd = isp1760_register(pci_mem_phy0, memlength, dev->irq, - IRQF_SHARED, -ENOENT, &dev->dev, dev_name(&dev->dev), - devflags); + IRQF_SHARED, &dev->dev, dev_name(&dev->dev), + devflags); if (IS_ERR(hcd)) { ret_status = -ENODEV; goto cleanup3; @@ -392,8 +367,8 @@ static int isp1760_plat_probe(struct platform_device *pdev) } hcd = isp1760_register(mem_res->start, mem_size, irq_res->start, - irqflags, -ENOENT, - &pdev->dev, dev_name(&pdev->dev), devflags); + irqflags, &pdev->dev, dev_name(&pdev->dev), + devflags); platform_set_drvdata(pdev, hcd); -- cgit v0.10.2 From 8a8b96f487e8b7ba0a9384df8e7ecc514152114c Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:41 +0200 Subject: usb: isp1760: Remove isp1760 glue structure The structure is allocated for the sole purpose of holding a pointer to the HCD and being stored in platform device driver data. Store the HCD pointer directly instead. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 7e8e7f6..b5bcb99 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -31,13 +31,9 @@ #endif #if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) -struct isp1760 { - struct usb_hcd *hcd; -}; - static int of_isp1760_probe(struct platform_device *dev) { - struct isp1760 *drvdata; + struct usb_hcd *hcd; struct device_node *dp = dev->dev.of_node; struct resource *res; struct resource memory; @@ -47,23 +43,15 @@ static int of_isp1760_probe(struct platform_device *dev) unsigned int devflags = 0; u32 bus_width = 0; - drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); - if (!drvdata) - return -ENOMEM; - ret = of_address_to_resource(dp, 0, &memory); - if (ret) { - ret = -ENXIO; - goto free_data; - } + if (ret) + return -ENXIO; res_len = resource_size(&memory); res = request_mem_region(memory.start, res_len, dev_name(&dev->dev)); - if (!res) { - ret = -EBUSY; - goto free_data; - } + if (!res) + return -EBUSY; virq = irq_of_parse_and_map(dp, 0); if (!virq) { @@ -91,34 +79,30 @@ static int of_isp1760_probe(struct platform_device *dev) if (of_get_property(dp, "dreq-polarity", NULL) != NULL) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - drvdata->hcd = isp1760_register(memory.start, res_len, virq, - IRQF_SHARED, &dev->dev, - dev_name(&dev->dev), devflags); - if (IS_ERR(drvdata->hcd)) { - ret = PTR_ERR(drvdata->hcd); + hcd = isp1760_register(memory.start, res_len, virq, IRQF_SHARED, + &dev->dev, dev_name(&dev->dev), devflags); + if (IS_ERR(hcd)) { + ret = PTR_ERR(hcd); goto release_reg; } - platform_set_drvdata(dev, drvdata); + platform_set_drvdata(dev, hcd); return ret; release_reg: release_mem_region(memory.start, res_len); -free_data: - kfree(drvdata); return ret; } static int of_isp1760_remove(struct platform_device *dev) { - struct isp1760 *drvdata = platform_get_drvdata(dev); + struct usb_hcd *hcd = platform_get_drvdata(dev); - usb_remove_hcd(drvdata->hcd); - iounmap(drvdata->hcd->regs); - release_mem_region(drvdata->hcd->rsrc_start, drvdata->hcd->rsrc_len); - usb_put_hcd(drvdata->hcd); + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); - kfree(drvdata); return 0; } -- cgit v0.10.2 From fcead8431d245af312afcbcbfbfc34a0fec77cd9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:42 +0200 Subject: usb: isp1760: Retrieve pdev memory resource from hcd at remove time The platform driver remove function needs to release the memory resource requested at probe time. Instead of retrieving the resource from the platform device, retrieve it from the usb_hcd. This mimics the behaviour of the PCI and OF glues, and will make it easier to share code between all three glue layers. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b5bcb99..025edf2 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -373,15 +373,11 @@ out: static int isp1760_plat_remove(struct platform_device *pdev) { - struct resource *mem_res; - resource_size_t mem_size; struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); - mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - mem_size = resource_size(mem_res); - release_mem_region(mem_res->start, mem_size); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); -- cgit v0.10.2 From 5324713c998d6615d858606188fe69a23d1de9d8 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:43 +0200 Subject: usb: isp1760: Unmap I/O registers at platform device removal The I/O registers are mapped in the HCD code but must be unmapped by glue code. The OF and PCI glue code unmap them correctly but the platform glue code is missing an iounmap() call. Fix it. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 025edf2..730caa1 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -376,9 +376,8 @@ static int isp1760_plat_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); return 0; -- cgit v0.10.2 From c3cc40ccea2cd3533a90d915b40f304843642df9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:44 +0200 Subject: usb: isp1760: Merge platform and OF glue codes Both handle platform devices, merge them. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 730caa1..3db98da 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -12,121 +12,18 @@ #include #include #include +#include #include +#include #include #include #include "isp1760-hcd.h" -#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) -#include -#include -#include -#include -#include -#endif - #ifdef CONFIG_PCI #include #endif -#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) -static int of_isp1760_probe(struct platform_device *dev) -{ - struct usb_hcd *hcd; - struct device_node *dp = dev->dev.of_node; - struct resource *res; - struct resource memory; - int virq; - resource_size_t res_len; - int ret; - unsigned int devflags = 0; - u32 bus_width = 0; - - ret = of_address_to_resource(dp, 0, &memory); - if (ret) - return -ENXIO; - - res_len = resource_size(&memory); - - res = request_mem_region(memory.start, res_len, dev_name(&dev->dev)); - if (!res) - return -EBUSY; - - virq = irq_of_parse_and_map(dp, 0); - if (!virq) { - ret = -ENODEV; - goto release_reg; - } - - if (of_device_is_compatible(dp, "nxp,usb-isp1761")) - devflags |= ISP1760_FLAG_ISP1761; - - /* Some systems wire up only 16 of the 32 data lines */ - of_property_read_u32(dp, "bus-width", &bus_width); - if (bus_width == 16) - devflags |= ISP1760_FLAG_BUS_WIDTH_16; - - if (of_get_property(dp, "port1-otg", NULL) != NULL) - devflags |= ISP1760_FLAG_OTG_EN; - - if (of_get_property(dp, "analog-oc", NULL) != NULL) - devflags |= ISP1760_FLAG_ANALOG_OC; - - if (of_get_property(dp, "dack-polarity", NULL) != NULL) - devflags |= ISP1760_FLAG_DACK_POL_HIGH; - - if (of_get_property(dp, "dreq-polarity", NULL) != NULL) - devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - - hcd = isp1760_register(memory.start, res_len, virq, IRQF_SHARED, - &dev->dev, dev_name(&dev->dev), devflags); - if (IS_ERR(hcd)) { - ret = PTR_ERR(hcd); - goto release_reg; - } - - platform_set_drvdata(dev, hcd); - return ret; - -release_reg: - release_mem_region(memory.start, res_len); - return ret; -} - -static int of_isp1760_remove(struct platform_device *dev) -{ - struct usb_hcd *hcd = platform_get_drvdata(dev); - - usb_remove_hcd(hcd); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - usb_put_hcd(hcd); - - return 0; -} - -static const struct of_device_id of_isp1760_match[] = { - { - .compatible = "nxp,usb-isp1760", - }, - { - .compatible = "nxp,usb-isp1761", - }, - { }, -}; -MODULE_DEVICE_TABLE(of, of_isp1760_match); - -static struct platform_driver isp1760_of_driver = { - .driver = { - .name = "nxp-isp1760", - .of_match_table = of_isp1760_match, - }, - .probe = of_isp1760_probe, - .remove = of_isp1760_remove, -}; -#endif - #ifdef CONFIG_PCI static int isp1761_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) @@ -304,26 +201,23 @@ static struct pci_driver isp1761_pci_driver = { static int isp1760_plat_probe(struct platform_device *pdev) { - int ret = 0; - struct usb_hcd *hcd; + unsigned long irqflags = IRQF_SHARED; + unsigned int devflags = 0; struct resource *mem_res; struct resource *irq_res; resource_size_t mem_size; - struct isp1760_platform_data *priv = dev_get_platdata(&pdev->dev); - unsigned int devflags = 0; - unsigned long irqflags = IRQF_SHARED; + struct usb_hcd *hcd; + int ret; mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem_res) { pr_warning("isp1760: Memory resource not available\n"); - ret = -ENODEV; - goto out; + return -ENODEV; } mem_size = resource_size(mem_res); if (!request_mem_region(mem_res->start, mem_size, "isp1760")) { pr_warning("isp1760: Cannot reserve the memory resource\n"); - ret = -EBUSY; - goto out; + return -EBUSY; } irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); @@ -335,39 +229,63 @@ static int isp1760_plat_probe(struct platform_device *pdev) irqflags |= irq_res->flags & IRQF_TRIGGER_MASK; - if (priv) { - if (priv->is_isp1761) + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + struct device_node *dp = pdev->dev.of_node; + u32 bus_width = 0; + + if (of_device_is_compatible(dp, "nxp,usb-isp1761")) devflags |= ISP1760_FLAG_ISP1761; - if (priv->bus_width_16) + + /* Some systems wire up only 16 of the 32 data lines */ + of_property_read_u32(dp, "bus-width", &bus_width); + if (bus_width == 16) devflags |= ISP1760_FLAG_BUS_WIDTH_16; - if (priv->port1_otg) + + if (of_property_read_bool(dp, "port1-otg")) devflags |= ISP1760_FLAG_OTG_EN; - if (priv->analog_oc) + + if (of_property_read_bool(dp, "analog-oc")) devflags |= ISP1760_FLAG_ANALOG_OC; - if (priv->dack_polarity_high) + + if (of_property_read_bool(dp, "dack-polarity")) devflags |= ISP1760_FLAG_DACK_POL_HIGH; - if (priv->dreq_polarity_high) + + if (of_property_read_bool(dp, "dreq-polarity")) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + } else if (dev_get_platdata(&pdev->dev)) { + struct isp1760_platform_data *pdata = + dev_get_platdata(&pdev->dev); + + if (pdata->is_isp1761) + devflags |= ISP1760_FLAG_ISP1761; + if (pdata->bus_width_16) + devflags |= ISP1760_FLAG_BUS_WIDTH_16; + if (pdata->port1_otg) + devflags |= ISP1760_FLAG_OTG_EN; + if (pdata->analog_oc) + devflags |= ISP1760_FLAG_ANALOG_OC; + if (pdata->dack_polarity_high) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + if (pdata->dreq_polarity_high) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; } hcd = isp1760_register(mem_res->start, mem_size, irq_res->start, irqflags, &pdev->dev, dev_name(&pdev->dev), devflags); - - platform_set_drvdata(pdev, hcd); - if (IS_ERR(hcd)) { pr_warning("isp1760: Failed to register the HCD device\n"); - ret = -ENODEV; + ret = PTR_ERR(hcd); goto cleanup; } + platform_set_drvdata(pdev, hcd); + pr_info("ISP1760 USB device initialised\n"); - return ret; + return 0; cleanup: release_mem_region(mem_res->start, mem_size); -out: return ret; } @@ -383,11 +301,21 @@ static int isp1760_plat_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id isp1760_of_match[] = { + { .compatible = "nxp,usb-isp1760", }, + { .compatible = "nxp,usb-isp1761", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isp1760_of_match); +#endif + static struct platform_driver isp1760_plat_driver = { .probe = isp1760_plat_probe, .remove = isp1760_plat_remove, .driver = { .name = "isp1760", + .of_match_table = of_match_ptr(isp1760_of_match), }, }; @@ -400,11 +328,6 @@ static int __init isp1760_init(void) ret = platform_driver_register(&isp1760_plat_driver); if (!ret) any_ret = 0; -#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) - ret = platform_driver_register(&isp1760_of_driver); - if (!ret) - any_ret = 0; -#endif #ifdef CONFIG_PCI ret = pci_register_driver(&isp1761_pci_driver); if (!ret) @@ -420,9 +343,6 @@ module_init(isp1760_init); static void __exit isp1760_exit(void) { platform_driver_unregister(&isp1760_plat_driver); -#if defined(CONFIG_OF) && defined(CONFIG_OF_IRQ) - platform_driver_unregister(&isp1760_of_driver); -#endif #ifdef CONFIG_PCI pci_unregister_driver(&isp1761_pci_driver); #endif -- cgit v0.10.2 From 10c73f09dc9be7c7373483f9965dd535c5788f09 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:45 +0200 Subject: usb: isp1760: Move removal cleanup code to isp1760-hcd.c The removal cleanup code is duplicated between the different bus glues. Move it to a central location. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index e4a9424..7498727 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2263,6 +2263,16 @@ err_put: return ERR_PTR(ret); } +void isp1760_unregister(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); MODULE_AUTHOR("Sebastian Siewior "); MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index fda0f2d..ea13a58 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -6,6 +6,8 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, struct device *dev, const char *busname, unsigned int devflags); +void isp1760_unregister(struct device *dev); + int init_kmem_once(void); void deinit_kmem_cache(void); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 3db98da..b96c62f 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -160,14 +160,7 @@ cleanup1: static void isp1761_pci_remove(struct pci_dev *dev) { - struct usb_hcd *hcd; - - hcd = pci_get_drvdata(dev); - - usb_remove_hcd(hcd); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - usb_put_hcd(hcd); + isp1760_unregister(&dev->dev); pci_disable_device(dev); } @@ -291,12 +284,7 @@ cleanup: static int isp1760_plat_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); + isp1760_unregister(&pdev->dev); return 0; } -- cgit v0.10.2 From 57f068bedca8b92157d09f971f2fb3adb76f8f96 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:46 +0200 Subject: usb: isp1760: Manage device driver data in common code Don't duplicate *_set_drvdata calls in glue code. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 7498727..19fbd69 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2252,6 +2252,8 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, goto err_unmap; device_wakeup_enable(hcd->self.controller); + dev_set_drvdata(dev, hcd); + return hcd; err_unmap: diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b96c62f..64eaf5d 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -146,7 +146,6 @@ static int isp1761_pci_probe(struct pci_dev *dev, iounmap(iobase); release_mem_region(nxp_pci_io_base, iolength); - pci_set_drvdata(dev, hcd); return 0; cleanup3: @@ -272,8 +271,6 @@ static int isp1760_plat_probe(struct platform_device *pdev) goto cleanup; } - platform_set_drvdata(pdev, hcd); - pr_info("ISP1760 USB device initialised\n"); return 0; -- cgit v0.10.2 From f0bdbb0ec6be7465eecbdbea058c027485a0d966 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:47 +0200 Subject: usb: isp1760: Don't expose hcd to glue code from isp1760_register The glue code probe functions don't need to access the hcd structure anymore. Modify isp1760_register to return an integer error code instead of the hcd pointer. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 19fbd69..4d6e50b 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -60,6 +60,9 @@ struct isp1760_hcd { struct gpio_desc *rst_gpio; }; +typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd); + static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) { return (struct isp1760_hcd *) (hcd->hcd_priv); @@ -2208,24 +2211,23 @@ void deinit_kmem_cache(void) kmem_cache_destroy(urb_listitem_cachep); } -struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, - int irq, unsigned long irqflags, - struct device *dev, const char *busname, - unsigned int devflags) +int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, + unsigned long irqflags, struct device *dev, + const char *busname, unsigned int devflags) { struct usb_hcd *hcd; struct isp1760_hcd *priv; int ret; if (usb_disabled()) - return ERR_PTR(-ENODEV); + return -ENODEV; /* prevent usb-core allocating DMA pages */ dev->dma_mask = NULL; hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); if (!hcd) - return ERR_PTR(-ENOMEM); + return -ENOMEM; priv = hcd_to_priv(hcd); priv->devflags = devflags; @@ -2254,7 +2256,7 @@ struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, dev_set_drvdata(dev, hcd); - return hcd; + return 0; err_unmap: iounmap(hcd->regs); @@ -2262,7 +2264,7 @@ err_unmap: err_put: usb_put_hcd(hcd); - return ERR_PTR(ret); + return ret; } void isp1760_unregister(struct device *dev) diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index ea13a58..372d2e5 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -2,10 +2,9 @@ #define _ISP1760_HCD_H_ /* exports for if */ -struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, - int irq, unsigned long irqflags, - struct device *dev, const char *busname, - unsigned int devflags); +int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, + unsigned long irqflags, struct device *dev, + const char *busname, unsigned int devflags); void isp1760_unregister(struct device *dev); int init_kmem_once(void); @@ -112,9 +111,6 @@ struct slotinfo { }; -typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, - struct isp1760_qtd *qtd); - /* * Device flags that can vary from board to board. All of these * indicate the most "atypical" case, so that a devflags of 0 is diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 64eaf5d..03243b0 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -31,7 +31,6 @@ static int isp1761_pci_probe(struct pci_dev *dev, u8 latency, limit; __u32 reg_data; int retry_count; - struct usb_hcd *hcd; unsigned int devflags = 0; int ret_status = 0; @@ -134,13 +133,11 @@ static int isp1761_pci_probe(struct pci_dev *dev, writel(reg_data, iobase + PLX_INT_CSR_REG); dev->dev.dma_mask = NULL; - hcd = isp1760_register(pci_mem_phy0, memlength, dev->irq, - IRQF_SHARED, &dev->dev, dev_name(&dev->dev), - devflags); - if (IS_ERR(hcd)) { - ret_status = -ENODEV; + ret_status = isp1760_register(pci_mem_phy0, memlength, dev->irq, + IRQF_SHARED, &dev->dev, + dev_name(&dev->dev), devflags); + if (ret_status < 0) goto cleanup3; - } /* done with PLX IO access */ iounmap(iobase); @@ -198,7 +195,6 @@ static int isp1760_plat_probe(struct platform_device *pdev) struct resource *mem_res; struct resource *irq_res; resource_size_t mem_size; - struct usb_hcd *hcd; int ret; mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -262,14 +258,11 @@ static int isp1760_plat_probe(struct platform_device *pdev) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; } - hcd = isp1760_register(mem_res->start, mem_size, irq_res->start, + ret = isp1760_register(mem_res->start, mem_size, irq_res->start, irqflags, &pdev->dev, dev_name(&pdev->dev), devflags); - if (IS_ERR(hcd)) { - pr_warning("isp1760: Failed to register the HCD device\n"); - ret = PTR_ERR(hcd); + if (ret < 0) goto cleanup; - } pr_info("ISP1760 USB device initialised\n"); return 0; -- cgit v0.10.2 From 30573751daf60835ad1dba8b040b6a345f49120a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:48 +0200 Subject: usb: isp1760: Fix indentation in probe error path Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 4d6e50b..aa894a1 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2259,12 +2259,12 @@ int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, return 0; err_unmap: - iounmap(hcd->regs); + iounmap(hcd->regs); err_put: - usb_put_hcd(hcd); + usb_put_hcd(hcd); - return ret; + return ret; } void isp1760_unregister(struct device *dev) -- cgit v0.10.2 From 5a6356ac62c8fa732e260123feb73655f7d919e6 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:49 +0200 Subject: usb: isp1760: Prefix init_kmem_once and deinit_kmem_cache with isp1760_ The two functions are specific to the driver but have very generic names, subject to collisions. Rename them. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index aa894a1..0ae1719 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2177,7 +2177,7 @@ static const struct hc_driver isp1760_hc_driver = { .clear_tt_buffer_complete = isp1760_clear_tt_buffer_complete, }; -int __init init_kmem_once(void) +int __init isp1760_init_kmem_once(void) { urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", sizeof(struct urb_listitem), 0, SLAB_TEMPORARY | @@ -2204,7 +2204,7 @@ int __init init_kmem_once(void) return 0; } -void deinit_kmem_cache(void) +void isp1760_deinit_kmem_cache(void) { kmem_cache_destroy(qtd_cachep); kmem_cache_destroy(qh_cachep); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 372d2e5..f97c9d6 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -7,8 +7,8 @@ int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, const char *busname, unsigned int devflags); void isp1760_unregister(struct device *dev); -int init_kmem_once(void); -void deinit_kmem_cache(void); +int isp1760_init_kmem_once(void); +void isp1760_deinit_kmem_cache(void); /* EHCI capability registers */ #define HC_CAPLENGTH 0x00 diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 03243b0..e268b20 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -301,7 +301,7 @@ static int __init isp1760_init(void) { int ret, any_ret = -ENODEV; - init_kmem_once(); + isp1760_init_kmem_once(); ret = platform_driver_register(&isp1760_plat_driver); if (!ret) @@ -313,7 +313,7 @@ static int __init isp1760_init(void) #endif if (any_ret) - deinit_kmem_cache(); + isp1760_deinit_kmem_cache(); return any_ret; } module_init(isp1760_init); @@ -324,6 +324,6 @@ static void __exit isp1760_exit(void) #ifdef CONFIG_PCI pci_unregister_driver(&isp1761_pci_driver); #endif - deinit_kmem_cache(); + isp1760_deinit_kmem_cache(); } module_exit(isp1760_exit); -- cgit v0.10.2 From d69292a8f5eed074412b70bb59b745fc17213661 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:50 +0200 Subject: usb: isp1760: Remove busname argument to isp1760_register The argument is unused, remove it. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 0ae1719..9ba3023 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2213,7 +2213,7 @@ void isp1760_deinit_kmem_cache(void) int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, struct device *dev, - const char *busname, unsigned int devflags) + unsigned int devflags) { struct usb_hcd *hcd; struct isp1760_hcd *priv; diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index f97c9d6..cc65602 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -4,7 +4,7 @@ /* exports for if */ int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, unsigned long irqflags, struct device *dev, - const char *busname, unsigned int devflags); + unsigned int devflags); void isp1760_unregister(struct device *dev); int isp1760_init_kmem_once(void); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index e268b20..ce572cc 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -134,8 +134,7 @@ static int isp1761_pci_probe(struct pci_dev *dev, dev->dev.dma_mask = NULL; ret_status = isp1760_register(pci_mem_phy0, memlength, dev->irq, - IRQF_SHARED, &dev->dev, - dev_name(&dev->dev), devflags); + IRQF_SHARED, &dev->dev, devflags); if (ret_status < 0) goto cleanup3; @@ -259,8 +258,7 @@ static int isp1760_plat_probe(struct platform_device *pdev) } ret = isp1760_register(mem_res->start, mem_size, irq_res->start, - irqflags, &pdev->dev, dev_name(&pdev->dev), - devflags); + irqflags, &pdev->dev, devflags); if (ret < 0) goto cleanup; -- cgit v0.10.2 From 4942e00e5582dcb45d432c35d47980ced72cdb8e Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:51 +0200 Subject: usb: isp1760: Pass resource pointer to isp1760_register The function takes quite a few arguments, passing the resource pointer instead of the start address and length simplifies it a bit. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 9ba3023..e99dafa 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2211,9 +2211,8 @@ void isp1760_deinit_kmem_cache(void) kmem_cache_destroy(urb_listitem_cachep); } -int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, - unsigned long irqflags, struct device *dev, - unsigned int devflags) +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) { struct usb_hcd *hcd; struct isp1760_hcd *priv; @@ -2239,15 +2238,15 @@ int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, } init_memory(priv); - hcd->regs = ioremap(res_start, res_len); + hcd->regs = ioremap(mem->start, resource_size(mem)); if (!hcd->regs) { ret = -EIO; goto err_put; } hcd->irq = irq; - hcd->rsrc_start = res_start; - hcd->rsrc_len = res_len; + hcd->rsrc_start = mem->start; + hcd->rsrc_len = resource_size(mem); ret = usb_add_hcd(hcd, irq, irqflags); if (ret) diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index cc65602..7fc510f 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -2,9 +2,8 @@ #define _ISP1760_HCD_H_ /* exports for if */ -int isp1760_register(phys_addr_t res_start, resource_size_t res_len, int irq, - unsigned long irqflags, struct device *dev, - unsigned int devflags); +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); void isp1760_unregister(struct device *dev); int isp1760_init_kmem_once(void); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index ce572cc..3a9b4f6 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -133,8 +133,8 @@ static int isp1761_pci_probe(struct pci_dev *dev, writel(reg_data, iobase + PLX_INT_CSR_REG); dev->dev.dma_mask = NULL; - ret_status = isp1760_register(pci_mem_phy0, memlength, dev->irq, - IRQF_SHARED, &dev->dev, devflags); + ret_status = isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, + &dev->dev, devflags); if (ret_status < 0) goto cleanup3; @@ -257,8 +257,8 @@ static int isp1760_plat_probe(struct platform_device *pdev) devflags |= ISP1760_FLAG_DREQ_POL_HIGH; } - ret = isp1760_register(mem_res->start, mem_size, irq_res->start, - irqflags, &pdev->dev, devflags); + ret = isp1760_register(mem_res, irq_res->start, irqflags, &pdev->dev, + devflags); if (ret < 0) goto cleanup; -- cgit v0.10.2 From 10feee144664a87a07f75756f1ee577ce17616c4 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:52 +0200 Subject: usb: isp1760: Use the managed devm_ioremap_resource() API This simplifies error and remove code paths. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index e99dafa..2e38efe 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2234,14 +2234,14 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); if (IS_ERR(priv->rst_gpio)) { ret = PTR_ERR(priv->rst_gpio); - goto err_put; + goto error; } init_memory(priv); - hcd->regs = ioremap(mem->start, resource_size(mem)); - if (!hcd->regs) { - ret = -EIO; - goto err_put; + hcd->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(hcd->regs)) { + ret = PTR_ERR(hcd->regs); + goto error; } hcd->irq = irq; @@ -2250,19 +2250,15 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, ret = usb_add_hcd(hcd, irq, irqflags); if (ret) - goto err_unmap; + goto error; device_wakeup_enable(hcd->self.controller); dev_set_drvdata(dev, hcd); return 0; -err_unmap: - iounmap(hcd->regs); - -err_put: +error: usb_put_hcd(hcd); - return ret; } @@ -2271,8 +2267,6 @@ void isp1760_unregister(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); usb_remove_hcd(hcd); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); } diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 3a9b4f6..dbb287c 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -85,8 +85,9 @@ static int isp1761_pci_probe(struct pci_dev *dev, chip_addr = ioremap_nocache(pci_mem_phy0,memlength); if (!chip_addr) { printk(KERN_ERR "Error ioremap failed\n"); + release_mem_region(pci_mem_phy0, memlength); ret_status = -ENOMEM; - goto cleanup3; + goto cleanup2; } /* bad pci latencies can contribute to overruns */ @@ -114,6 +115,7 @@ static int isp1761_pci_probe(struct pci_dev *dev, } iounmap(chip_addr); + release_mem_region(pci_mem_phy0, memlength); /* Host Controller presence is detected by writing to scratch register * and reading back and checking the contents are same or not @@ -121,7 +123,7 @@ static int isp1761_pci_probe(struct pci_dev *dev, if (reg_data != 0xFACE) { dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); ret_status = -ENOMEM; - goto cleanup3; + goto cleanup2; } pci_set_master(dev); @@ -132,20 +134,14 @@ static int isp1761_pci_probe(struct pci_dev *dev, reg_data |= 0x900; writel(reg_data, iobase + PLX_INT_CSR_REG); - dev->dev.dma_mask = NULL; - ret_status = isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, - &dev->dev, devflags); - if (ret_status < 0) - goto cleanup3; - /* done with PLX IO access */ iounmap(iobase); release_mem_region(nxp_pci_io_base, iolength); - return 0; + dev->dev.dma_mask = NULL; + return isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, + &dev->dev, devflags); -cleanup3: - release_mem_region(pci_mem_phy0, memlength); cleanup2: iounmap(iobase); cleanup1: @@ -193,25 +189,14 @@ static int isp1760_plat_probe(struct platform_device *pdev) unsigned int devflags = 0; struct resource *mem_res; struct resource *irq_res; - resource_size_t mem_size; int ret; mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem_res) { - pr_warning("isp1760: Memory resource not available\n"); - return -ENODEV; - } - mem_size = resource_size(mem_res); - if (!request_mem_region(mem_res->start, mem_size, "isp1760")) { - pr_warning("isp1760: Cannot reserve the memory resource\n"); - return -EBUSY; - } irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!irq_res) { pr_warning("isp1760: IRQ resource not available\n"); - ret = -ENODEV; - goto cleanup; + return -ENODEV; } irqflags |= irq_res->flags & IRQF_TRIGGER_MASK; @@ -260,14 +245,10 @@ static int isp1760_plat_probe(struct platform_device *pdev) ret = isp1760_register(mem_res, irq_res->start, irqflags, &pdev->dev, devflags); if (ret < 0) - goto cleanup; + return ret; pr_info("ISP1760 USB device initialised\n"); return 0; - -cleanup: - release_mem_region(mem_res->start, mem_size); - return ret; } static int isp1760_plat_remove(struct platform_device *pdev) -- cgit v0.10.2 From c770e00122a939f4ec9eeb663b6be6a131065926 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:53 +0200 Subject: usb: isp1760: Refactor PCI initialization code Move the initialization code out of the PCI probe function to a new function in order to simplify and fix error handling. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index dbb287c..7c03320 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -25,69 +25,34 @@ #endif #ifdef CONFIG_PCI -static int isp1761_pci_probe(struct pci_dev *dev, - const struct pci_device_id *id) +static int isp1761_pci_init(struct pci_dev *dev) { + resource_size_t mem_start; + resource_size_t mem_length; + u8 __iomem *iobase; u8 latency, limit; - __u32 reg_data; int retry_count; - unsigned int devflags = 0; - int ret_status = 0; - - resource_size_t pci_mem_phy0; - resource_size_t memlength; + u32 reg_data; - u8 __iomem *chip_addr; - u8 __iomem *iobase; - resource_size_t nxp_pci_io_base; - resource_size_t iolength; - - if (usb_disabled()) - return -ENODEV; - - if (pci_enable_device(dev) < 0) - return -ENODEV; - - if (!dev->irq) - return -ENODEV; - - /* Grab the PLX PCI mem maped port start address we need */ - nxp_pci_io_base = pci_resource_start(dev, 0); - iolength = pci_resource_len(dev, 0); - - if (!request_mem_region(nxp_pci_io_base, iolength, "ISP1761 IO MEM")) { - printk(KERN_ERR "request region #1\n"); - return -EBUSY; - } - - iobase = ioremap_nocache(nxp_pci_io_base, iolength); - if (!iobase) { - printk(KERN_ERR "ioremap #1\n"); - ret_status = -ENOMEM; - goto cleanup1; - } /* Grab the PLX PCI shared memory of the ISP 1761 we need */ - pci_mem_phy0 = pci_resource_start(dev, 3); - memlength = pci_resource_len(dev, 3); - if (memlength < 0xffff) { + mem_start = pci_resource_start(dev, 3); + mem_length = pci_resource_len(dev, 3); + if (mem_length < 0xffff) { printk(KERN_ERR "memory length for this resource is wrong\n"); - ret_status = -ENOMEM; - goto cleanup2; + return -ENOMEM; } - if (!request_mem_region(pci_mem_phy0, memlength, "ISP-PCI")) { + if (!request_mem_region(mem_start, mem_length, "ISP-PCI")) { printk(KERN_ERR "host controller already in use\n"); - ret_status = -EBUSY; - goto cleanup2; + return -EBUSY; } /* map available memory */ - chip_addr = ioremap_nocache(pci_mem_phy0,memlength); - if (!chip_addr) { + iobase = ioremap_nocache(mem_start, mem_length); + if (!iobase) { printk(KERN_ERR "Error ioremap failed\n"); - release_mem_region(pci_mem_phy0, memlength); - ret_status = -ENOMEM; - goto cleanup2; + release_mem_region(mem_start, mem_length); + return -ENOMEM; } /* bad pci latencies can contribute to overruns */ @@ -108,25 +73,38 @@ static int isp1761_pci_probe(struct pci_dev *dev, /*by default host is in 16bit mode, so * io operations at this stage must be 16 bit * */ - writel(0xface, chip_addr + HC_SCRATCH_REG); + writel(0xface, iobase + HC_SCRATCH_REG); udelay(100); - reg_data = readl(chip_addr + HC_SCRATCH_REG) & 0x0000ffff; + reg_data = readl(iobase + HC_SCRATCH_REG) & 0x0000ffff; retry_count--; } - iounmap(chip_addr); - release_mem_region(pci_mem_phy0, memlength); + iounmap(iobase); + release_mem_region(mem_start, mem_length); /* Host Controller presence is detected by writing to scratch register * and reading back and checking the contents are same or not */ if (reg_data != 0xFACE) { dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); - ret_status = -ENOMEM; - goto cleanup2; + return -ENOMEM; } - pci_set_master(dev); + /* Grab the PLX PCI mem maped port start address we need */ + mem_start = pci_resource_start(dev, 0); + mem_length = pci_resource_len(dev, 0); + + if (!request_mem_region(mem_start, mem_length, "ISP1761 IO MEM")) { + printk(KERN_ERR "request region #1\n"); + return -EBUSY; + } + + iobase = ioremap_nocache(mem_start, mem_length); + if (!iobase) { + printk(KERN_ERR "ioremap #1\n"); + release_mem_region(mem_start, mem_length); + return -ENOMEM; + } /* configure PLX PCI chip to pass interrupts */ #define PLX_INT_CSR_REG 0x68 @@ -136,17 +114,43 @@ static int isp1761_pci_probe(struct pci_dev *dev, /* done with PLX IO access */ iounmap(iobase); - release_mem_region(nxp_pci_io_base, iolength); + release_mem_region(mem_start, mem_length); + + return 0; +} + +static int isp1761_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + unsigned int devflags = 0; + int ret; + + if (usb_disabled()) + return -ENODEV; + + if (!dev->irq) + return -ENODEV; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + ret = isp1761_pci_init(dev); + if (ret < 0) + goto error; + + pci_set_master(dev); dev->dev.dma_mask = NULL; - return isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, - &dev->dev, devflags); + ret = isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, + &dev->dev, devflags); + if (ret < 0) + goto error; -cleanup2: - iounmap(iobase); -cleanup1: - release_mem_region(nxp_pci_io_base, iolength); - return ret_status; + return 0; + +error: + pci_disable_device(dev); + return ret; } static void isp1761_pci_remove(struct pci_dev *dev) -- cgit v0.10.2 From cdd36e872c55603af60a9dc1f9e4b3de31ffc21a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:54 +0200 Subject: usb: isp1760: Decouple usb_hdc and isp1760_priv Allocate the driver private data structure manually instead of using the usb_hcd private space. This will allow skipping hcd registration for the isp1761 when used in device mode only. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 2e38efe..55c0add 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -40,6 +40,8 @@ enum queue_head_types { }; struct isp1760_hcd { + struct usb_hcd *hcd; + u32 hcs_params; spinlock_t lock; struct slotinfo atl_slots[32]; @@ -65,7 +67,7 @@ typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) { - return (struct isp1760_hcd *) (hcd->hcd_priv); + return *(struct isp1760_hcd **)hcd->hcd_priv; } /* Section 2.2 Host Controller Capability Registers */ @@ -2161,7 +2163,7 @@ static void isp1760_clear_tt_buffer_complete(struct usb_hcd *hcd, static const struct hc_driver isp1760_hc_driver = { .description = "isp1760-hcd", .product_desc = "NXP ISP1760 USB Host Controller", - .hcd_priv_size = sizeof(struct isp1760_hcd), + .hcd_priv_size = sizeof(struct isp1760_hcd *), .irq = isp1760_irq, .flags = HCD_MEMORY | HCD_USB2, .reset = isp1760_hc_setup, @@ -2214,13 +2216,17 @@ void isp1760_deinit_kmem_cache(void) int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags) { - struct usb_hcd *hcd; + struct usb_hcd *hcd = NULL; struct isp1760_hcd *priv; int ret; if (usb_disabled()) return -ENODEV; + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + /* prevent usb-core allocating DMA pages */ dev->dma_mask = NULL; @@ -2228,7 +2234,9 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, if (!hcd) return -ENOMEM; - priv = hcd_to_priv(hcd); + priv->hcd = hcd; + *(struct isp1760_hcd **)hcd->hcd_priv = priv; + priv->devflags = devflags; priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); @@ -2253,7 +2261,7 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, goto error; device_wakeup_enable(hcd->self.controller); - dev_set_drvdata(dev, hcd); + dev_set_drvdata(dev, priv); return 0; @@ -2264,7 +2272,8 @@ error: void isp1760_unregister(struct device *dev) { - struct usb_hcd *hcd = dev_get_drvdata(dev); + struct isp1760_hcd *priv = dev_get_drvdata(dev); + struct usb_hcd *hcd = priv->hcd; usb_remove_hcd(hcd); usb_put_hcd(hcd); -- cgit v0.10.2 From ea0b1fabc77420261c57c4ac4f0ea384f5a8c438 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:55 +0200 Subject: usb: isp1760: Prefix driver data structures with isp1760_ The slotinfo and memory_chunk structures are specific to the driver and defined in a header file. Prefix them with isp1760_ to avoid namespace clashes. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 55c0add..99f56c6 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -44,11 +44,11 @@ struct isp1760_hcd { u32 hcs_params; spinlock_t lock; - struct slotinfo atl_slots[32]; + struct isp1760_slotinfo atl_slots[32]; int atl_done_map; - struct slotinfo int_slots[32]; + struct isp1760_slotinfo int_slots[32]; int int_done_map; - struct memory_chunk memory_pool[BLOCKS]; + struct isp1760_memory_chunk memory_pool[BLOCKS]; struct list_head qh_list[QH_END]; /* periodic schedule support */ @@ -743,8 +743,9 @@ static void qtd_free(struct isp1760_qtd *qtd) } static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, - struct slotinfo *slots, struct isp1760_qtd *qtd, - struct isp1760_qh *qh, struct ptd *ptd) + struct isp1760_slotinfo *slots, + struct isp1760_qtd *qtd, struct isp1760_qh *qh, + struct ptd *ptd) { struct isp1760_hcd *priv = hcd_to_priv(hcd); int skip_map; @@ -857,7 +858,7 @@ static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh) { struct isp1760_hcd *priv = hcd_to_priv(hcd); int ptd_offset; - struct slotinfo *slots; + struct isp1760_slotinfo *slots; int curr_slot, free_slot; int n; struct ptd ptd; @@ -1097,7 +1098,7 @@ static void handle_done_ptds(struct usb_hcd *hcd) struct isp1760_qh *qh; int slot; int state; - struct slotinfo *slots; + struct isp1760_slotinfo *slots; u32 ptd_offset; struct isp1760_qtd *qtd; int modified; diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 7fc510f..3056bcd 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -103,7 +103,7 @@ struct ptd { #define ATL_PTD_OFFSET 0x0c00 #define PAYLOAD_OFFSET 0x1000 -struct slotinfo { +struct isp1760_slotinfo { struct isp1760_qh *qh; struct isp1760_qtd *qtd; unsigned long timestamp; @@ -125,7 +125,7 @@ struct slotinfo { #define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ /* chip memory management */ -struct memory_chunk { +struct isp1760_memory_chunk { unsigned int start; unsigned int size; unsigned int free; -- cgit v0.10.2 From e19c99e7592e06b6fdf558aa8877b671f8cf0329 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:56 +0200 Subject: usb: isp1760: Reorganize header files The isp1760-rhcd.h header contains PTD constants and structures only useful for the HCD implementation. It also contains register definitions that will be needed by common code when implementing support for the ISP1761 device controller, but doesn't contain the isp1760_hcd structure definition that will also be used by common code. Move definitions to the right location and create an isp1760-regs.h to store register definitions. No change other than moving definitions and modifying indentation is performed. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 99f56c6..50434cc 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -27,41 +27,12 @@ #include #include "isp1760-hcd.h" +#include "isp1760-regs.h" static struct kmem_cache *qtd_cachep; static struct kmem_cache *qh_cachep; static struct kmem_cache *urb_listitem_cachep; -enum queue_head_types { - QH_CONTROL, - QH_BULK, - QH_INTERRUPT, - QH_END -}; - -struct isp1760_hcd { - struct usb_hcd *hcd; - - u32 hcs_params; - spinlock_t lock; - struct isp1760_slotinfo atl_slots[32]; - int atl_done_map; - struct isp1760_slotinfo int_slots[32]; - int int_done_map; - struct isp1760_memory_chunk memory_pool[BLOCKS]; - struct list_head qh_list[QH_END]; - - /* periodic schedule support */ -#define DEFAULT_I_TDPS 1024 - unsigned periodic_size; - unsigned i_thresh; - unsigned long reset_done; - unsigned long next_statechange; - unsigned int devflags; - - struct gpio_desc *rst_gpio; -}; - typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, struct isp1760_qtd *qtd); @@ -70,32 +41,79 @@ static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) return *(struct isp1760_hcd **)hcd->hcd_priv; } -/* Section 2.2 Host Controller Capability Registers */ -#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ -#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ -#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ -#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ -#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ -#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ -#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ - -/* Section 2.3 Host Controller Operational Registers */ -#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ -#define CMD_RESET (1<<1) /* reset HC not bus */ -#define CMD_RUN (1<<0) /* start/stop HC */ -#define STS_PCD (1<<2) /* port change detect */ -#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ - -#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ -#define PORT_POWER (1<<12) /* true: has power (see PPC) */ -#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ -#define PORT_RESET (1<<8) /* reset port */ -#define PORT_SUSPEND (1<<7) /* suspend port */ -#define PORT_RESUME (1<<6) /* resume it */ -#define PORT_PE (1<<2) /* port enable */ -#define PORT_CSC (1<<1) /* connect status change */ -#define PORT_CONNECT (1<<0) /* device connected */ -#define PORT_RWC_BITS (PORT_CSC) +/* urb state*/ +#define DELETE_URB (0x0008) +#define NO_TRANSFER_ACTIVE (0xffffffff) + +/* Philips Proprietary Transfer Descriptor (PTD) */ +typedef __u32 __bitwise __dw; +struct ptd { + __dw dw0; + __dw dw1; + __dw dw2; + __dw dw3; + __dw dw4; + __dw dw5; + __dw dw6; + __dw dw7; +}; +#define PTD_OFFSET 0x0400 +#define ISO_PTD_OFFSET 0x0400 +#define INT_PTD_OFFSET 0x0800 +#define ATL_PTD_OFFSET 0x0c00 +#define PAYLOAD_OFFSET 0x1000 + + +/* ATL */ +/* DW0 */ +#define DW0_VALID_BIT 1 +#define FROM_DW0_VALID(x) ((x) & 0x01) +#define TO_DW0_LENGTH(x) (((u32) x) << 3) +#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) +#define TO_DW0_MULTI(x) (((u32) x) << 29) +#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) +/* DW1 */ +#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) +#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) +#define DW1_TRANS_BULK ((u32) 2 << 12) +#define DW1_TRANS_INT ((u32) 3 << 12) +#define DW1_TRANS_SPLIT ((u32) 1 << 14) +#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) +#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) +#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) +/* DW2 */ +#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) +#define TO_DW2_RL(x) ((x) << 25) +#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) +/* DW3 */ +#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) +#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) +#define TO_DW3_NAKCOUNT(x) ((x) << 19) +#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) +#define TO_DW3_CERR(x) ((x) << 23) +#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) +#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) +#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) +#define TO_DW3_PING(x) ((x) << 26) +#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) +#define DW3_ERROR_BIT (1 << 28) +#define DW3_BABBLE_BIT (1 << 29) +#define DW3_HALT_BIT (1 << 30) +#define DW3_ACTIVE_BIT (1 << 31) +#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) + +#define INT_UNDERRUN (1 << 2) +#define INT_BABBLE (1 << 1) +#define INT_EXACT (1 << 0) + +#define SETUP_PID (2) +#define IN_PID (1) +#define OUT_PID (0) + +/* Errata 1 */ +#define RL_COUNTER (0) +#define NAK_COUNTER (0) +#define ERR_COUNTER (2) struct isp1760_qtd { u8 packet_type; diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 3056bcd..44486c8 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -1,114 +1,31 @@ #ifndef _ISP1760_HCD_H_ #define _ISP1760_HCD_H_ -/* exports for if */ -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags); -void isp1760_unregister(struct device *dev); +#include -int isp1760_init_kmem_once(void); -void isp1760_deinit_kmem_cache(void); +struct gpio_desc; +struct isp1760_qh; +struct isp1760_qtd; +struct resource; +struct usb_hcd; -/* EHCI capability registers */ -#define HC_CAPLENGTH 0x00 -#define HC_HCSPARAMS 0x04 -#define HC_HCCPARAMS 0x08 - -/* EHCI operational registers */ -#define HC_USBCMD 0x20 -#define HC_USBSTS 0x24 -#define HC_FRINDEX 0x2c -#define HC_CONFIGFLAG 0x60 -#define HC_PORTSC1 0x64 -#define HC_ISO_PTD_DONEMAP_REG 0x130 -#define HC_ISO_PTD_SKIPMAP_REG 0x134 -#define HC_ISO_PTD_LASTPTD_REG 0x138 -#define HC_INT_PTD_DONEMAP_REG 0x140 -#define HC_INT_PTD_SKIPMAP_REG 0x144 -#define HC_INT_PTD_LASTPTD_REG 0x148 -#define HC_ATL_PTD_DONEMAP_REG 0x150 -#define HC_ATL_PTD_SKIPMAP_REG 0x154 -#define HC_ATL_PTD_LASTPTD_REG 0x158 - -/* Configuration Register */ -#define HC_HW_MODE_CTRL 0x300 -#define ALL_ATX_RESET (1 << 31) -#define HW_ANA_DIGI_OC (1 << 15) -#define HW_DATA_BUS_32BIT (1 << 8) -#define HW_DACK_POL_HIGH (1 << 6) -#define HW_DREQ_POL_HIGH (1 << 5) -#define HW_INTR_HIGH_ACT (1 << 2) -#define HW_INTR_EDGE_TRIG (1 << 1) -#define HW_GLOBAL_INTR_EN (1 << 0) - -#define HC_CHIP_ID_REG 0x304 -#define HC_SCRATCH_REG 0x308 - -#define HC_RESET_REG 0x30c -#define SW_RESET_RESET_HC (1 << 1) -#define SW_RESET_RESET_ALL (1 << 0) - -#define HC_BUFFER_STATUS_REG 0x334 -#define ISO_BUF_FILL (1 << 2) -#define INT_BUF_FILL (1 << 1) -#define ATL_BUF_FILL (1 << 0) - -#define HC_MEMORY_REG 0x33c -#define ISP_BANK(x) ((x) << 16) - -#define HC_PORT1_CTRL 0x374 -#define PORT1_POWER (3 << 3) -#define PORT1_INIT1 (1 << 7) -#define PORT1_INIT2 (1 << 23) -#define HW_OTG_CTRL_SET 0x374 -#define HW_OTG_CTRL_CLR 0x376 - -/* Interrupt Register */ -#define HC_INTERRUPT_REG 0x310 - -#define HC_INTERRUPT_ENABLE 0x314 -#define HC_ISO_INT (1 << 9) -#define HC_ATL_INT (1 << 8) -#define HC_INTL_INT (1 << 7) -#define HC_EOT_INT (1 << 3) -#define HC_SOT_INT (1 << 1) -#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) - -#define HC_ISO_IRQ_MASK_OR_REG 0x318 -#define HC_INT_IRQ_MASK_OR_REG 0x31C -#define HC_ATL_IRQ_MASK_OR_REG 0x320 -#define HC_ISO_IRQ_MASK_AND_REG 0x324 -#define HC_INT_IRQ_MASK_AND_REG 0x328 -#define HC_ATL_IRQ_MASK_AND_REG 0x32C - -/* urb state*/ -#define DELETE_URB (0x0008) -#define NO_TRANSFER_ACTIVE (0xffffffff) - -/* Philips Proprietary Transfer Descriptor (PTD) */ -typedef __u32 __bitwise __dw; -struct ptd { - __dw dw0; - __dw dw1; - __dw dw2; - __dw dw3; - __dw dw4; - __dw dw5; - __dw dw6; - __dw dw7; -}; -#define PTD_OFFSET 0x0400 -#define ISO_PTD_OFFSET 0x0400 -#define INT_PTD_OFFSET 0x0800 -#define ATL_PTD_OFFSET 0x0c00 -#define PAYLOAD_OFFSET 0x1000 +/* + * 60kb divided in: + * - 32 blocks @ 256 bytes + * - 20 blocks @ 1024 bytes + * - 4 blocks @ 8192 bytes + */ -struct isp1760_slotinfo { - struct isp1760_qh *qh; - struct isp1760_qtd *qtd; - unsigned long timestamp; -}; +#define BLOCK_1_NUM 32 +#define BLOCK_2_NUM 20 +#define BLOCK_3_NUM 4 +#define BLOCK_1_SIZE 256 +#define BLOCK_2_SIZE 1024 +#define BLOCK_3_SIZE 8192 +#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) +#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE +#define PAYLOAD_AREA_SIZE 0xf000 /* * Device flags that can vary from board to board. All of these @@ -124,6 +41,12 @@ struct isp1760_slotinfo { #define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ #define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ +struct isp1760_slotinfo { + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + unsigned long timestamp; +}; + /* chip memory management */ struct isp1760_memory_chunk { unsigned int start; @@ -131,73 +54,42 @@ struct isp1760_memory_chunk { unsigned int free; }; -/* - * 60kb divided in: - * - 32 blocks @ 256 bytes - * - 20 blocks @ 1024 bytes - * - 4 blocks @ 8192 bytes - */ +enum isp1760_queue_head_types { + QH_CONTROL, + QH_BULK, + QH_INTERRUPT, + QH_END +}; -#define BLOCK_1_NUM 32 -#define BLOCK_2_NUM 20 -#define BLOCK_3_NUM 4 +struct isp1760_hcd { + struct usb_hcd *hcd; + + u32 hcs_params; + spinlock_t lock; + struct isp1760_slotinfo atl_slots[32]; + int atl_done_map; + struct isp1760_slotinfo int_slots[32]; + int int_done_map; + struct isp1760_memory_chunk memory_pool[BLOCKS]; + struct list_head qh_list[QH_END]; + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 + unsigned periodic_size; + unsigned i_thresh; + unsigned long reset_done; + unsigned long next_statechange; + unsigned int devflags; + + struct gpio_desc *rst_gpio; +}; -#define BLOCK_1_SIZE 256 -#define BLOCK_2_SIZE 1024 -#define BLOCK_3_SIZE 8192 -#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) -#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE -#define PAYLOAD_AREA_SIZE 0xf000 +/* exports for if */ +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_unregister(struct device *dev); -/* ATL */ -/* DW0 */ -#define DW0_VALID_BIT 1 -#define FROM_DW0_VALID(x) ((x) & 0x01) -#define TO_DW0_LENGTH(x) (((u32) x) << 3) -#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) -#define TO_DW0_MULTI(x) (((u32) x) << 29) -#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) -/* DW1 */ -#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) -#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) -#define DW1_TRANS_BULK ((u32) 2 << 12) -#define DW1_TRANS_INT ((u32) 3 << 12) -#define DW1_TRANS_SPLIT ((u32) 1 << 14) -#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) -#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) -#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) -/* DW2 */ -#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) -#define TO_DW2_RL(x) ((x) << 25) -#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) -/* DW3 */ -#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) -#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) -#define TO_DW3_NAKCOUNT(x) ((x) << 19) -#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) -#define TO_DW3_CERR(x) ((x) << 23) -#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) -#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) -#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) -#define TO_DW3_PING(x) ((x) << 26) -#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) -#define DW3_ERROR_BIT (1 << 28) -#define DW3_BABBLE_BIT (1 << 29) -#define DW3_HALT_BIT (1 << 30) -#define DW3_ACTIVE_BIT (1 << 31) -#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) - -#define INT_UNDERRUN (1 << 2) -#define INT_BABBLE (1 << 1) -#define INT_EXACT (1 << 0) - -#define SETUP_PID (2) -#define IN_PID (1) -#define OUT_PID (0) - -/* Errata 1 */ -#define RL_COUNTER (0) -#define NAK_COUNTER (0) -#define ERR_COUNTER (2) +int isp1760_init_kmem_once(void); +void isp1760_deinit_kmem_cache(void); #endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 7c03320..b1591c6c 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -19,6 +19,7 @@ #include #include "isp1760-hcd.h" +#include "isp1760-regs.h" #ifdef CONFIG_PCI #include diff --git a/drivers/usb/host/isp1760-regs.h b/drivers/usb/host/isp1760-regs.h new file mode 100644 index 0000000..c2a3900 --- /dev/null +++ b/drivers/usb/host/isp1760-regs.h @@ -0,0 +1,120 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef _ISP1760_REGS_H_ +#define _ISP1760_REGS_H_ + +/* EHCI capability registers */ +#define HC_CAPLENGTH 0x000 +#define HC_LENGTH(p) (((p) >> 00) & 0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) /* bits 31:16 */ + +#define HC_HCSPARAMS 0x004 +#define HCS_INDICATOR(p) ((p) & (1 << 16)) /* true: has port indicators */ +#define HCS_PPC(p) ((p) & (1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p) >> 0) & 0xf) /* bits 3:0, ports on HC */ + +#define HC_HCCPARAMS 0x008 +#define HCC_ISOC_CACHE(p) ((p) & (1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p) >> 4) & 0x7) /* bits 6:4, uframes cached */ + +/* EHCI operational registers */ +#define HC_USBCMD 0x020 +#define CMD_LRESET (1 << 7) /* partial reset (no ports, etc) */ +#define CMD_RESET (1 << 1) /* reset HC not bus */ +#define CMD_RUN (1 << 0) /* start/stop HC */ + +#define HC_USBSTS 0x024 +#define STS_PCD (1 << 2) /* port change detect */ + +#define HC_FRINDEX 0x02c + +#define HC_CONFIGFLAG 0x060 +#define FLAG_CF (1 << 0) /* true: we'll support "high speed" */ + +#define HC_PORTSC1 0x064 +#define PORT_OWNER (1 << 13) /* true: companion hc owns this port */ +#define PORT_POWER (1 << 12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ +#define PORT_RESET (1 << 8) /* reset port */ +#define PORT_SUSPEND (1 << 7) /* suspend port */ +#define PORT_RESUME (1 << 6) /* resume it */ +#define PORT_PE (1 << 2) /* port enable */ +#define PORT_CSC (1 << 1) /* connect status change */ +#define PORT_CONNECT (1 << 0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC) + +#define HC_ISO_PTD_DONEMAP_REG 0x130 +#define HC_ISO_PTD_SKIPMAP_REG 0x134 +#define HC_ISO_PTD_LASTPTD_REG 0x138 +#define HC_INT_PTD_DONEMAP_REG 0x140 +#define HC_INT_PTD_SKIPMAP_REG 0x144 +#define HC_INT_PTD_LASTPTD_REG 0x148 +#define HC_ATL_PTD_DONEMAP_REG 0x150 +#define HC_ATL_PTD_SKIPMAP_REG 0x154 +#define HC_ATL_PTD_LASTPTD_REG 0x158 + +/* Configuration Register */ +#define HC_HW_MODE_CTRL 0x300 +#define ALL_ATX_RESET (1 << 31) +#define HW_ANA_DIGI_OC (1 << 15) +#define HW_DATA_BUS_32BIT (1 << 8) +#define HW_DACK_POL_HIGH (1 << 6) +#define HW_DREQ_POL_HIGH (1 << 5) +#define HW_INTR_HIGH_ACT (1 << 2) +#define HW_INTR_EDGE_TRIG (1 << 1) +#define HW_GLOBAL_INTR_EN (1 << 0) + +#define HC_CHIP_ID_REG 0x304 +#define HC_SCRATCH_REG 0x308 + +#define HC_RESET_REG 0x30c +#define SW_RESET_RESET_HC (1 << 1) +#define SW_RESET_RESET_ALL (1 << 0) + +#define HC_BUFFER_STATUS_REG 0x334 +#define ISO_BUF_FILL (1 << 2) +#define INT_BUF_FILL (1 << 1) +#define ATL_BUF_FILL (1 << 0) + +#define HC_MEMORY_REG 0x33c +#define ISP_BANK(x) ((x) << 16) + +#define HC_PORT1_CTRL 0x374 +#define PORT1_POWER (3 << 3) +#define PORT1_INIT1 (1 << 7) +#define PORT1_INIT2 (1 << 23) +#define HW_OTG_CTRL_SET 0x374 +#define HW_OTG_CTRL_CLR 0x376 + +/* Interrupt Register */ +#define HC_INTERRUPT_REG 0x310 + +#define HC_INTERRUPT_ENABLE 0x314 +#define HC_ISO_INT (1 << 9) +#define HC_ATL_INT (1 << 8) +#define HC_INTL_INT (1 << 7) +#define HC_EOT_INT (1 << 3) +#define HC_SOT_INT (1 << 1) +#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) + +#define HC_ISO_IRQ_MASK_OR_REG 0x318 +#define HC_INT_IRQ_MASK_OR_REG 0x31c +#define HC_ATL_IRQ_MASK_OR_REG 0x320 +#define HC_ISO_IRQ_MASK_AND_REG 0x324 +#define HC_INT_IRQ_MASK_AND_REG 0x328 +#define HC_ATL_IRQ_MASK_AND_REG 0x32c + +#endif -- cgit v0.10.2 From 4b1a577d41c99f2aa548e8de3effe1033d9ca40b Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:57 +0200 Subject: usb: isp1760: Move core code to isp1760-core.c Move core device initialization to a central location in order to share it with the device mode implementation. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index d6216a4..4dea9b1 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -5,7 +5,7 @@ # tell define_trace.h where to find the xhci trace header CFLAGS_xhci-trace.o := -I$(src) -isp1760-y := isp1760-hcd.o isp1760-if.o +isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o fhci-y := fhci-hcd.o fhci-hub.o fhci-q.o fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c new file mode 100644 index 0000000..d38efa0 --- /dev/null +++ b/drivers/usb/host/isp1760-core.c @@ -0,0 +1,65 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-hcd.h" + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) +{ + struct isp1760_device *isp; + int ret; + + if (usb_disabled()) + return -ENODEV; + + /* prevent usb-core allocating DMA pages */ + dev->dma_mask = NULL; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(isp->regs)) + return PTR_ERR(isp->regs); + + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, irqflags, + dev, devflags); + if (ret < 0) + return ret; + + dev_set_drvdata(dev, isp); + + return 0; +} + +void isp1760_unregister(struct device *dev) +{ + struct isp1760_device *isp = dev_get_drvdata(dev); + + isp1760_hcd_unregister(&isp->hcd); +} + +MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); +MODULE_AUTHOR("Sebastian Siewior "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-core.h b/drivers/usb/host/isp1760-core.h new file mode 100644 index 0000000..0caeb11 --- /dev/null +++ b/drivers/usb/host/isp1760-core.h @@ -0,0 +1,33 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef _ISP1760_CORE_H_ +#define _ISP1760_CORE_H_ + +#include + +#include "isp1760-hcd.h" + +struct isp1760_device { + void __iomem *regs; + + struct isp1760_hcd hcd; +}; + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_unregister(struct device *dev); + +#endif diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 50434cc..0cf620b 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2232,30 +2232,20 @@ void isp1760_deinit_kmem_cache(void) kmem_cache_destroy(urb_listitem_cachep); } -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags) +int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, + struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) { - struct usb_hcd *hcd = NULL; - struct isp1760_hcd *priv; + struct usb_hcd *hcd; int ret; - if (usb_disabled()) - return -ENODEV; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - /* prevent usb-core allocating DMA pages */ - dev->dma_mask = NULL; - hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); if (!hcd) return -ENOMEM; - priv->hcd = hcd; *(struct isp1760_hcd **)hcd->hcd_priv = priv; + priv->hcd = hcd; priv->devflags = devflags; priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); @@ -2265,22 +2255,17 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, } init_memory(priv); - hcd->regs = devm_ioremap_resource(dev, mem); - if (IS_ERR(hcd->regs)) { - ret = PTR_ERR(hcd->regs); - goto error; - } hcd->irq = irq; + hcd->regs = regs; hcd->rsrc_start = mem->start; hcd->rsrc_len = resource_size(mem); ret = usb_add_hcd(hcd, irq, irqflags); if (ret) goto error; - device_wakeup_enable(hcd->self.controller); - dev_set_drvdata(dev, priv); + device_wakeup_enable(hcd->self.controller); return 0; @@ -2289,15 +2274,8 @@ error: return ret; } -void isp1760_unregister(struct device *dev) +void isp1760_hcd_unregister(struct isp1760_hcd *priv) { - struct isp1760_hcd *priv = dev_get_drvdata(dev); - struct usb_hcd *hcd = priv->hcd; - - usb_remove_hcd(hcd); - usb_put_hcd(hcd); + usb_remove_hcd(priv->hcd); + usb_put_hcd(priv->hcd); } - -MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); -MODULE_AUTHOR("Sebastian Siewior "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 44486c8..dcd2232 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -84,10 +84,10 @@ struct isp1760_hcd { struct gpio_desc *rst_gpio; }; -/* exports for if */ -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags); -void isp1760_unregister(struct device *dev); +int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, + struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_hcd_unregister(struct isp1760_hcd *priv); int isp1760_init_kmem_once(void); void isp1760_deinit_kmem_cache(void); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b1591c6c..f2a3990 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -18,7 +18,7 @@ #include #include -#include "isp1760-hcd.h" +#include "isp1760-core.h" #include "isp1760-regs.h" #ifdef CONFIG_PCI -- cgit v0.10.2 From 667c45c2f159d3c4e1d592df42ffbc7d4d73e07b Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:58 +0200 Subject: usb: isp1760: Set IRQF_SHARED flag in core code The IRQF_SHARED flag needs to be set regardless of the bus type. Don't require glue code to set it manually. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c index d38efa0..35278a8 100644 --- a/drivers/usb/host/isp1760-core.c +++ b/drivers/usb/host/isp1760-core.c @@ -43,8 +43,8 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, if (IS_ERR(isp->regs)) return PTR_ERR(isp->regs); - ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, irqflags, - dev, devflags); + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, + irqflags | IRQF_SHARED, dev, devflags); if (ret < 0) return ret; diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index f2a3990..c2a94c9 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -142,8 +142,8 @@ static int isp1761_pci_probe(struct pci_dev *dev, pci_set_master(dev); dev->dev.dma_mask = NULL; - ret = isp1760_register(&dev->resource[3], dev->irq, IRQF_SHARED, - &dev->dev, devflags); + ret = isp1760_register(&dev->resource[3], dev->irq, 0, &dev->dev, + devflags); if (ret < 0) goto error; @@ -190,7 +190,7 @@ static struct pci_driver isp1761_pci_driver = { static int isp1760_plat_probe(struct platform_device *pdev) { - unsigned long irqflags = IRQF_SHARED; + unsigned long irqflags; unsigned int devflags = 0; struct resource *mem_res; struct resource *irq_res; @@ -203,8 +203,7 @@ static int isp1760_plat_probe(struct platform_device *pdev) pr_warning("isp1760: IRQ resource not available\n"); return -ENODEV; } - - irqflags |= irq_res->flags & IRQF_TRIGGER_MASK; + irqflags = irq_res->flags & IRQF_TRIGGER_MASK; if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { struct device_node *dp = pdev->dev.of_node; -- cgit v0.10.2 From 5171446a3aec607c4f94a32758f51a68bc627fe3 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:55:59 +0200 Subject: usb: isp1760: Initialize the bus interface in core code Although the corresponding register is part of the HCD register space, processor bus initialization is not specific to the HCD. To prepare for device controller support, move bus interface initialization to core code. Signed-off-by: Laurent Pinchart Reviewed-by: Felipe Balbi Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c index 35278a8..e840a1d 100644 --- a/drivers/usb/host/isp1760-core.c +++ b/drivers/usb/host/isp1760-core.c @@ -13,7 +13,8 @@ * version 2 as published by the Free Software Foundation. */ -#include +#include +#include #include #include #include @@ -22,6 +23,54 @@ #include "isp1760-core.h" #include "isp1760-hcd.h" +#include "isp1760-regs.h" + +static void isp1760_init_core(struct isp1760_device *isp) +{ + u32 hwmode; + + /* Low-level chip reset */ + if (isp->rst_gpio) { + gpiod_set_value_cansleep(isp->rst_gpio, 1); + mdelay(50); + gpiod_set_value_cansleep(isp->rst_gpio, 0); + } + + /* + * Reset the host controller, including the CPU interface + * configuration. + */ + isp1760_write32(isp->regs, HC_RESET_REG, SW_RESET_RESET_ALL); + msleep(100); + + /* Setup HW Mode Control: This assumes a level active-low interrupt */ + hwmode = HW_DATA_BUS_32BIT; + + if (isp->devflags & ISP1760_FLAG_BUS_WIDTH_16) + hwmode &= ~HW_DATA_BUS_32BIT; + if (isp->devflags & ISP1760_FLAG_ANALOG_OC) + hwmode |= HW_ANA_DIGI_OC; + if (isp->devflags & ISP1760_FLAG_DACK_POL_HIGH) + hwmode |= HW_DACK_POL_HIGH; + if (isp->devflags & ISP1760_FLAG_DREQ_POL_HIGH) + hwmode |= HW_DREQ_POL_HIGH; + if (isp->devflags & ISP1760_FLAG_INTR_POL_HIGH) + hwmode |= HW_INTR_HIGH_ACT; + if (isp->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) + hwmode |= HW_INTR_EDGE_TRIG; + + /* + * We have to set this first in case we're in 16-bit mode. + * Write it twice to ensure correct upper bits if switching + * to 16-bit mode. + */ + isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + + dev_info(isp->dev, "bus width: %u, oc: %s\n", + isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, + isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); +} int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags) @@ -39,12 +88,21 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, if (!isp) return -ENOMEM; + isp->dev = dev; + isp->devflags = devflags; + + isp->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); + if (IS_ERR(isp->rst_gpio)) + return PTR_ERR(isp->rst_gpio); + isp->regs = devm_ioremap_resource(dev, mem); if (IS_ERR(isp->regs)) return PTR_ERR(isp->regs); + isp1760_init_core(isp); + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, - irqflags | IRQF_SHARED, dev, devflags); + irqflags | IRQF_SHARED, dev); if (ret < 0) return ret; diff --git a/drivers/usb/host/isp1760-core.h b/drivers/usb/host/isp1760-core.h index 0caeb11..cd4a0f3 100644 --- a/drivers/usb/host/isp1760-core.h +++ b/drivers/usb/host/isp1760-core.h @@ -20,8 +20,29 @@ #include "isp1760-hcd.h" +struct device; +struct gpio_desc; + +/* + * Device flags that can vary from board to board. All of these + * indicate the most "atypical" case, so that a devflags of 0 is + * a sane default configuration. + */ +#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ +#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ +#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ +#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ +#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ +#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ +#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ +#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ + struct isp1760_device { + struct device *dev; + void __iomem *regs; + unsigned int devflags; + struct gpio_desc *rst_gpio; struct isp1760_hcd hcd; }; @@ -30,4 +51,14 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags); void isp1760_unregister(struct device *dev); +static inline u32 isp1760_read32(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void isp1760_write32(void __iomem *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + #endif diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 0cf620b..5309d73 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -26,6 +26,7 @@ #include #include +#include "isp1760-core.h" #include "isp1760-hcd.h" #include "isp1760-regs.h" @@ -160,12 +161,12 @@ struct urb_listitem { */ static u32 reg_read32(void __iomem *base, u32 reg) { - return readl(base + reg); + return isp1760_read32(base, reg); } static void reg_write32(void __iomem *base, u32 reg, u32 val) { - writel(val, base + reg); + isp1760_write32(base, reg, val); } /* @@ -466,37 +467,6 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) int result; u32 scratch, hwmode; - /* low-level chip reset */ - if (priv->rst_gpio) { - gpiod_set_value_cansleep(priv->rst_gpio, 1); - mdelay(50); - gpiod_set_value_cansleep(priv->rst_gpio, 0); - } - - /* Setup HW Mode Control: This assumes a level active-low interrupt */ - hwmode = HW_DATA_BUS_32BIT; - - if (priv->devflags & ISP1760_FLAG_BUS_WIDTH_16) - hwmode &= ~HW_DATA_BUS_32BIT; - if (priv->devflags & ISP1760_FLAG_ANALOG_OC) - hwmode |= HW_ANA_DIGI_OC; - if (priv->devflags & ISP1760_FLAG_DACK_POL_HIGH) - hwmode |= HW_DACK_POL_HIGH; - if (priv->devflags & ISP1760_FLAG_DREQ_POL_HIGH) - hwmode |= HW_DREQ_POL_HIGH; - if (priv->devflags & ISP1760_FLAG_INTR_POL_HIGH) - hwmode |= HW_INTR_HIGH_ACT; - if (priv->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) - hwmode |= HW_INTR_EDGE_TRIG; - - /* - * We have to set this first in case we're in 16-bit mode. - * Write it twice to ensure correct upper bits if switching - * to 16-bit mode. - */ - reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); - reg_write32(hcd->regs, HC_SCRATCH_REG, 0xdeadbabe); /* Change bus pattern */ scratch = reg_read32(hcd->regs, HC_CHIP_ID_REG); @@ -506,31 +476,27 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) return -ENODEV; } - /* pre reset */ + /* + * The RESET_HC bit in the SW_RESET register is supposed to reset the + * host controller without touching the CPU interface registers, but at + * least on the ISP1761 it seems to behave as the RESET_ALL bit and + * reset the whole device. We thus can't use it here, so let's reset + * the host controller through the EHCI USB Command register. The device + * has been reset in core code anyway, so this shouldn't matter. + */ reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, 0); reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); - /* reset */ - reg_write32(hcd->regs, HC_RESET_REG, SW_RESET_RESET_ALL); - mdelay(100); - - reg_write32(hcd->regs, HC_RESET_REG, SW_RESET_RESET_HC); - mdelay(100); - result = ehci_reset(hcd); if (result) return result; /* Step 11 passed */ - dev_info(hcd->self.controller, "bus width: %d, oc: %s\n", - (priv->devflags & ISP1760_FLAG_BUS_WIDTH_16) ? - 16 : 32, (priv->devflags & ISP1760_FLAG_ANALOG_OC) ? - "analog" : "digital"); - /* ATL reset */ + hwmode = reg_read32(hcd->regs, HC_HW_MODE_CTRL) & ~ALL_ATX_RESET; reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET); mdelay(10); reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); @@ -2234,7 +2200,7 @@ void isp1760_deinit_kmem_cache(void) int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags) + struct device *dev) { struct usb_hcd *hcd; int ret; @@ -2246,13 +2212,6 @@ int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, *(struct isp1760_hcd **)hcd->hcd_priv = priv; priv->hcd = hcd; - priv->devflags = devflags; - - priv->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); - if (IS_ERR(priv->rst_gpio)) { - ret = PTR_ERR(priv->rst_gpio); - goto error; - } init_memory(priv); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index dcd2232..df7ea36 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -3,7 +3,6 @@ #include -struct gpio_desc; struct isp1760_qh; struct isp1760_qtd; struct resource; @@ -27,20 +26,6 @@ struct usb_hcd; #define MAX_PAYLOAD_SIZE BLOCK_3_SIZE #define PAYLOAD_AREA_SIZE 0xf000 -/* - * Device flags that can vary from board to board. All of these - * indicate the most "atypical" case, so that a devflags of 0 is - * a sane default configuration. - */ -#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ -#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ -#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ -#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ -#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ -#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ -#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ -#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ - struct isp1760_slotinfo { struct isp1760_qh *qh; struct isp1760_qtd *qtd; @@ -79,14 +64,11 @@ struct isp1760_hcd { unsigned i_thresh; unsigned long reset_done; unsigned long next_statechange; - unsigned int devflags; - - struct gpio_desc *rst_gpio; }; int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags); + struct device *dev); void isp1760_hcd_unregister(struct isp1760_hcd *priv); int isp1760_init_kmem_once(void); -- cgit v0.10.2 From 9a66e13290f16be59ac38e1955e15e8929076fc6 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:00 +0200 Subject: usb: isp1760: Move PORT1 configuration to core code Configuring the mode of operation of port 1 doesn't belong to the HCD code, as it's related to the soon to come UDC support. Move the configuration to core code. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c index e840a1d..1cba3e0 100644 --- a/drivers/usb/host/isp1760-core.c +++ b/drivers/usb/host/isp1760-core.c @@ -67,6 +67,14 @@ static void isp1760_init_core(struct isp1760_device *isp) isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + /* + * PORT 1 Control register of the ISP1760 is the OTG control register on + * ISP1761. Since there is no OTG or device controller support in this + * driver, we use port 1 as a "normal" USB host port on both chips. + */ + isp1760_write32(isp->regs, HC_PORT1_CTRL, PORT1_POWER | PORT1_INIT2); + usleep_range(10000, 11000); + dev_info(isp->dev, "bus width: %u, oc: %s\n", isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 5309d73..568446c 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -503,15 +503,6 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) reg_write32(hcd->regs, HC_INTERRUPT_ENABLE, INTERRUPT_ENABLE_MASK); - /* - * PORT 1 Control register of the ISP1760 is the OTG control - * register on ISP1761. Since there is no OTG or device controller - * support in this driver, we use port 1 as a "normal" USB host port on - * both chips. - */ - reg_write32(hcd->regs, HC_PORT1_CTRL, PORT1_POWER | PORT1_INIT2); - mdelay(10); - priv->hcs_params = reg_read32(hcd->regs, HC_HCSPARAMS); return priv_init(hcd); -- cgit v0.10.2 From 0316ca6319b98e485325be98a47d08fed07ead43 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:01 +0200 Subject: usb: isp1760: Add device controller support The ISP1761 is a dual-mode host and device controller backward compatible on the host side with the ISP1760. Add support for the device controller. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index b8e213e..c9152e2 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -109,6 +109,13 @@ config USB_GR_UDC Select this to support Aeroflex Gaisler GRUSBDC cores from the GRLIB VHDL IP core library. +config USB_ISP1761_UDC + boolean "NXP ISP1761 USB Device Controller" + depends on USB_ISP1760_HCD + help + The NXP ISP1761 is a dual-role high-speed USB host and device + controller. + config USB_OMAP tristate "OMAP USB Device Controller" depends on ARCH_OMAP1 diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 4dea9b1..67d3f18 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -6,6 +6,7 @@ CFLAGS_xhci-trace.o := -I$(src) isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o +isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o fhci-y := fhci-hcd.o fhci-hub.o fhci-q.o fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c index 1cba3e0..727e90a 100644 --- a/drivers/usb/host/isp1760-core.c +++ b/drivers/usb/host/isp1760-core.c @@ -24,9 +24,11 @@ #include "isp1760-core.h" #include "isp1760-hcd.h" #include "isp1760-regs.h" +#include "isp1760-udc.h" static void isp1760_init_core(struct isp1760_device *isp) { + u32 otgctrl; u32 hwmode; /* Low-level chip reset */ @@ -60,6 +62,17 @@ static void isp1760_init_core(struct isp1760_device *isp) hwmode |= HW_INTR_EDGE_TRIG; /* + * The ISP1761 has a dedicated DC IRQ line but supports sharing the HC + * IRQ line for both the host and device controllers. Hardcode IRQ + * sharing for now and disable the DC interrupts globally to avoid + * spurious interrupts during HCD registration. + */ + if (isp->devflags & ISP1760_FLAG_ISP1761) { + isp1760_write32(isp->regs, DC_MODE, 0); + hwmode |= HW_COMN_IRQ; + } + + /* * We have to set this first in case we're in 16-bit mode. * Write it twice to ensure correct upper bits if switching * to 16-bit mode. @@ -68,18 +81,33 @@ static void isp1760_init_core(struct isp1760_device *isp) isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); /* - * PORT 1 Control register of the ISP1760 is the OTG control register on - * ISP1761. Since there is no OTG or device controller support in this - * driver, we use port 1 as a "normal" USB host port on both chips. + * PORT 1 Control register of the ISP1760 is the OTG control register + * on ISP1761. + * + * TODO: Really support OTG. For now we configure port 1 in device mode + * when OTG is requested. */ - isp1760_write32(isp->regs, HC_PORT1_CTRL, PORT1_POWER | PORT1_INIT2); - usleep_range(10000, 11000); + if ((isp->devflags & ISP1760_FLAG_ISP1761) && + (isp->devflags & ISP1760_FLAG_OTG_EN)) + otgctrl = ((HW_DM_PULLDOWN | HW_DP_PULLDOWN) << 16) + | HW_OTG_DISABLE; + else + otgctrl = (HW_SW_SEL_HC_DC << 16) + | (HW_VBUS_DRV | HW_SEL_CP_EXT); + + isp1760_write32(isp->regs, HC_PORT1_CTRL, otgctrl); dev_info(isp->dev, "bus width: %u, oc: %s\n", isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); } +void isp1760_set_pullup(struct isp1760_device *isp, bool enable) +{ + isp1760_write32(isp->regs, HW_OTG_CTRL_SET, + enable ? HW_DP_PULLUP : HW_DP_PULLUP << 16); +} + int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags) { @@ -114,6 +142,15 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, if (ret < 0) return ret; + if (devflags & ISP1760_FLAG_ISP1761) { + ret = isp1760_udc_register(isp, irq, irqflags | IRQF_SHARED | + IRQF_DISABLED); + if (ret < 0) { + isp1760_hcd_unregister(&isp->hcd); + return ret; + } + } + dev_set_drvdata(dev, isp); return 0; @@ -123,6 +160,9 @@ void isp1760_unregister(struct device *dev) { struct isp1760_device *isp = dev_get_drvdata(dev); + if (isp->devflags & ISP1760_FLAG_ISP1761) + isp1760_udc_unregister(isp); + isp1760_hcd_unregister(&isp->hcd); } diff --git a/drivers/usb/host/isp1760-core.h b/drivers/usb/host/isp1760-core.h index cd4a0f3..c70f836 100644 --- a/drivers/usb/host/isp1760-core.h +++ b/drivers/usb/host/isp1760-core.h @@ -19,6 +19,7 @@ #include #include "isp1760-hcd.h" +#include "isp1760-udc.h" struct device; struct gpio_desc; @@ -45,12 +46,15 @@ struct isp1760_device { struct gpio_desc *rst_gpio; struct isp1760_hcd hcd; + struct isp1760_udc udc; }; int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags); void isp1760_unregister(struct device *dev); +void isp1760_set_pullup(struct isp1760_device *isp, bool enable); + static inline u32 isp1760_read32(void __iomem *base, u32 reg) { return readl(base + reg); diff --git a/drivers/usb/host/isp1760-regs.h b/drivers/usb/host/isp1760-regs.h index c2a3900..b67095c 100644 --- a/drivers/usb/host/isp1760-regs.h +++ b/drivers/usb/host/isp1760-regs.h @@ -16,6 +16,10 @@ #ifndef _ISP1760_REGS_H_ #define _ISP1760_REGS_H_ +/* ----------------------------------------------------------------------------- + * Host Controller + */ + /* EHCI capability registers */ #define HC_CAPLENGTH 0x000 #define HC_LENGTH(p) (((p) >> 00) & 0x00ff) /* bits 7:0 */ @@ -70,6 +74,9 @@ #define HC_HW_MODE_CTRL 0x300 #define ALL_ATX_RESET (1 << 31) #define HW_ANA_DIGI_OC (1 << 15) +#define HW_DEV_DMA (1 << 11) +#define HW_COMN_IRQ (1 << 10) +#define HW_COMN_DMA (1 << 9) #define HW_DATA_BUS_32BIT (1 << 8) #define HW_DACK_POL_HIGH (1 << 6) #define HW_DREQ_POL_HIGH (1 << 5) @@ -98,6 +105,17 @@ #define PORT1_INIT2 (1 << 23) #define HW_OTG_CTRL_SET 0x374 #define HW_OTG_CTRL_CLR 0x376 +#define HW_OTG_DISABLE (1 << 10) +#define HW_OTG_SE0_EN (1 << 9) +#define HW_BDIS_ACON_EN (1 << 8) +#define HW_SW_SEL_HC_DC (1 << 7) +#define HW_VBUS_CHRG (1 << 6) +#define HW_VBUS_DISCHRG (1 << 5) +#define HW_VBUS_DRV (1 << 4) +#define HW_SEL_CP_EXT (1 << 3) +#define HW_DM_PULLDOWN (1 << 2) +#define HW_DP_PULLDOWN (1 << 1) +#define HW_DP_PULLUP (1 << 0) /* Interrupt Register */ #define HC_INTERRUPT_REG 0x310 @@ -117,4 +135,96 @@ #define HC_INT_IRQ_MASK_AND_REG 0x328 #define HC_ATL_IRQ_MASK_AND_REG 0x32c +/* ----------------------------------------------------------------------------- + * Peripheral Controller + */ + +/* Initialization Registers */ +#define DC_ADDRESS 0x0200 +#define DC_DEVEN (1 << 7) + +#define DC_MODE 0x020c +#define DC_DMACLKON (1 << 9) +#define DC_VBUSSTAT (1 << 8) +#define DC_CLKAON (1 << 7) +#define DC_SNDRSU (1 << 6) +#define DC_GOSUSP (1 << 5) +#define DC_SFRESET (1 << 4) +#define DC_GLINTENA (1 << 3) +#define DC_WKUPCS (1 << 2) + +#define DC_INTCONF 0x0210 +#define DC_CDBGMOD_ACK_NAK (0 << 6) +#define DC_CDBGMOD_ACK (1 << 6) +#define DC_CDBGMOD_ACK_1NAK (2 << 6) +#define DC_DDBGMODIN_ACK_NAK (0 << 4) +#define DC_DDBGMODIN_ACK (1 << 4) +#define DC_DDBGMODIN_ACK_1NAK (2 << 4) +#define DC_DDBGMODOUT_ACK_NYET_NAK (0 << 2) +#define DC_DDBGMODOUT_ACK_NYET (1 << 2) +#define DC_DDBGMODOUT_ACK_NYET_1NAK (2 << 2) +#define DC_INTLVL (1 << 1) +#define DC_INTPOL (1 << 0) + +#define DC_DEBUG 0x0212 +#define DC_INTENABLE 0x0214 +#define DC_IEPTX(n) (1 << (11 + 2 * (n))) +#define DC_IEPRX(n) (1 << (10 + 2 * (n))) +#define DC_IEPRXTX(n) (3 << (10 + 2 * (n))) +#define DC_IEP0SETUP (1 << 8) +#define DC_IEVBUS (1 << 7) +#define DC_IEDMA (1 << 6) +#define DC_IEHS_STA (1 << 5) +#define DC_IERESM (1 << 4) +#define DC_IESUSP (1 << 3) +#define DC_IEPSOF (1 << 2) +#define DC_IESOF (1 << 1) +#define DC_IEBRST (1 << 0) + +/* Data Flow Registers */ +#define DC_EPINDEX 0x022c +#define DC_EP0SETUP (1 << 5) +#define DC_ENDPIDX(n) ((n) << 1) +#define DC_EPDIR (1 << 0) + +#define DC_CTRLFUNC 0x0228 +#define DC_CLBUF (1 << 4) +#define DC_VENDP (1 << 3) +#define DC_DSEN (1 << 2) +#define DC_STATUS (1 << 1) +#define DC_STALL (1 << 0) + +#define DC_DATAPORT 0x0220 +#define DC_BUFLEN 0x021c +#define DC_DATACOUNT_MASK 0xffff +#define DC_BUFSTAT 0x021e +#define DC_EPMAXPKTSZ 0x0204 + +#define DC_EPTYPE 0x0208 +#define DC_NOEMPKT (1 << 4) +#define DC_EPENABLE (1 << 3) +#define DC_DBLBUF (1 << 2) +#define DC_ENDPTYP_ISOC (1 << 0) +#define DC_ENDPTYP_BULK (2 << 0) +#define DC_ENDPTYP_INTERRUPT (3 << 0) + +/* DMA Registers */ +#define DC_DMACMD 0x0230 +#define DC_DMATXCOUNT 0x0234 +#define DC_DMACONF 0x0238 +#define DC_DMAHW 0x023c +#define DC_DMAINTREASON 0x0250 +#define DC_DMAINTEN 0x0254 +#define DC_DMAEP 0x0258 +#define DC_DMABURSTCOUNT 0x0264 + +/* General Registers */ +#define DC_INTERRUPT 0x0218 +#define DC_CHIPID 0x0270 +#define DC_FRAMENUM 0x0274 +#define DC_SCRATCH 0x0278 +#define DC_UNLOCKDEV 0x027c +#define DC_INTPULSEWIDTH 0x0280 +#define DC_TESTMODE 0x0284 + #endif diff --git a/drivers/usb/host/isp1760-udc.c b/drivers/usb/host/isp1760-udc.c new file mode 100644 index 0000000..6bfda30 --- /dev/null +++ b/drivers/usb/host/isp1760-udc.c @@ -0,0 +1,1495 @@ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-regs.h" +#include "isp1760-udc.h" + +#define ISP1760_VBUS_POLL_INTERVAL msecs_to_jiffies(500) + +struct isp1760_request { + struct usb_request req; + struct list_head queue; + struct isp1760_ep *ep; + unsigned int packet_size; +}; + +static inline struct isp1760_udc *gadget_to_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct isp1760_udc, gadget); +} + +static inline struct isp1760_ep *ep_to_udc_ep(struct usb_ep *ep) +{ + return container_of(ep, struct isp1760_ep, ep); +} + +static inline struct isp1760_request *req_to_udc_req(struct usb_request *req) +{ + return container_of(req, struct isp1760_request, req); +} + +static inline u32 isp1760_udc_read(struct isp1760_udc *udc, u16 reg) +{ + return isp1760_read32(udc->regs, reg); +} + +static inline void isp1760_udc_write(struct isp1760_udc *udc, u16 reg, u32 val) +{ + isp1760_write32(udc->regs, reg, val); +} + +/* ----------------------------------------------------------------------------- + * Endpoint Management + */ + +static struct isp1760_ep *isp1760_udc_find_ep(struct isp1760_udc *udc, + u16 index) +{ + unsigned int i; + + if (index == 0) + return &udc->ep[0]; + + for (i = 1; i < ARRAY_SIZE(udc->ep); ++i) { + if (udc->ep[i].addr == index) + return udc->ep[i].desc ? &udc->ep[i] : NULL; + } + + return NULL; +} + +static void __isp1760_udc_select_ep(struct isp1760_ep *ep, int dir) +{ + isp1760_udc_write(ep->udc, DC_EPINDEX, + DC_ENDPIDX(ep->addr & USB_ENDPOINT_NUMBER_MASK) | + (dir == USB_DIR_IN ? DC_EPDIR : 0)); +} + +/** + * isp1760_udc_select_ep - Select an endpoint for register access + * @ep: The endpoint + * + * The ISP1761 endpoint registers are banked. This function selects the target + * endpoint for banked register access. The selection remains valid until the + * next call to this function, the next direct access to the EPINDEX register + * or the next reset, whichever comes first. + * + * Called with the UDC spinlock held. + */ +static void isp1760_udc_select_ep(struct isp1760_ep *ep) +{ + __isp1760_udc_select_ep(ep, ep->addr & USB_ENDPOINT_DIR_MASK); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_ctrl_send_status(struct isp1760_ep *ep, int dir) +{ + struct isp1760_udc *udc = ep->udc; + + /* + * Proceed to the status stage. The status stage data packet flows in + * the direction opposite to the data stage data packets, we thus need + * to select the OUT/IN endpoint for IN/OUT transfers. + */ + isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | + (dir == USB_DIR_IN ? 0 : DC_EPDIR)); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STATUS); + + /* + * The hardware will terminate the request automatically and go back to + * the setup stage without notifying us. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; +} + +/* Called without the UDC spinlock held. */ +static void isp1760_udc_request_complete(struct isp1760_ep *ep, + struct isp1760_request *req, + int status) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "completing request %p with status %d\n", + req, status); + + req->ep = NULL; + req->req.status = status; + req->req.complete(&ep->ep, &req->req); + + spin_lock_irqsave(&udc->lock, flags); + + /* + * When completing control OUT requests, move to the status stage after + * calling the request complete callback. This gives the gadget an + * opportunity to stall the control transfer if needed. + */ + if (status == 0 && ep->addr == 0 && udc->ep0_dir == USB_DIR_OUT) + isp1760_udc_ctrl_send_status(ep, USB_DIR_OUT); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_ctrl_send_stall(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "%s(ep%02x)\n", __func__, ep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + /* Stall both the IN and OUT endpoints. */ + __isp1760_udc_select_ep(ep, USB_DIR_OUT); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); + __isp1760_udc_select_ep(ep, USB_DIR_IN); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); + + /* A protocol stall completes the control transaction. */ + udc->ep0_state = ISP1760_CTRL_SETUP; + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Data Endpoints + */ + +/* Called with the UDC spinlock held. */ +static bool isp1760_udc_receive(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + unsigned int len; + u32 *buf; + int i; + + isp1760_udc_select_ep(ep); + len = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; + + dev_dbg(udc->isp->dev, "%s: received %u bytes (%u/%u done)\n", + __func__, len, req->req.actual, req->req.length); + + len = min(len, req->req.length - req->req.actual); + + if (!len) { + /* + * There's no data to be read from the FIFO, acknowledge the RX + * interrupt by clearing the buffer. + * + * TODO: What if another packet arrives in the meantime ? The + * datasheet doesn't clearly document how this should be + * handled. + */ + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + return false; + } + + buf = req->req.buf + req->req.actual; + + /* + * Make sure not to read more than one extra byte, otherwise data from + * the next packet might be removed from the FIFO. + */ + for (i = len; i > 2; i -= 4, ++buf) + *buf = le32_to_cpu(isp1760_udc_read(udc, DC_DATAPORT)); + if (i > 0) + *(u16 *)buf = le16_to_cpu(readw(udc->regs + DC_DATAPORT)); + + req->req.actual += len; + + /* + * TODO: The short_not_ok flag isn't supported yet, but isn't used by + * any gadget driver either. + */ + + dev_dbg(udc->isp->dev, + "%s: req %p actual/length %u/%u maxpacket %u packet size %u\n", + __func__, req, req->req.actual, req->req.length, ep->maxpacket, + len); + + ep->rx_pending = false; + + /* + * Complete the request if all data has been received or if a short + * packet has been received. + */ + if (req->req.actual == req->req.length || len < ep->maxpacket) { + list_del(&req->queue); + return true; + } + + return false; +} + +static void isp1760_udc_transmit(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + u32 *buf = req->req.buf + req->req.actual; + int i; + + req->packet_size = min(req->req.length - req->req.actual, + ep->maxpacket); + + dev_dbg(udc->isp->dev, "%s: transferring %u bytes (%u/%u done)\n", + __func__, req->packet_size, req->req.actual, + req->req.length); + + __isp1760_udc_select_ep(ep, USB_DIR_IN); + + if (req->packet_size) + isp1760_udc_write(udc, DC_BUFLEN, req->packet_size); + + /* + * Make sure not to write more than one extra byte, otherwise extra data + * will stay in the FIFO and will be transmitted during the next control + * request. The endpoint control CLBUF bit is supposed to allow flushing + * the FIFO for this kind of conditions, but doesn't seem to work. + */ + for (i = req->packet_size; i > 2; i -= 4, ++buf) + isp1760_udc_write(udc, DC_DATAPORT, cpu_to_le32(*buf)); + if (i > 0) + writew(cpu_to_le16(*(u16 *)buf), udc->regs + DC_DATAPORT); + + if (ep->addr == 0) + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + if (!req->packet_size) + isp1760_udc_write(udc, DC_CTRLFUNC, DC_VENDP); +} + +static void isp1760_ep_rx_ready(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *req; + bool complete; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_OUT) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: invalid ep0 state %u\n", __func__, + udc->ep0_state); + return; + } + + if (ep->addr != 0 && !ep->desc) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + ep->addr); + return; + } + + if (list_empty(&ep->queue)) { + ep->rx_pending = true; + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x (%p) has no request queued\n", + __func__, ep->addr, ep); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + complete = isp1760_udc_receive(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, req, 0); +} + +static void isp1760_ep_tx_complete(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *complete = NULL; + struct isp1760_request *req; + bool need_zlp; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_IN) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "TX IRQ: invalid endpoint state %u\n", + udc->ep0_state); + return; + } + + if (list_empty(&ep->queue)) { + /* + * This can happen for the control endpoint when the reply to + * the GET_STATUS IN control request is sent directly by the + * setup IRQ handler. Just proceed to the status stage. + */ + if (ep->addr == 0) { + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + spin_unlock(&udc->lock); + return; + } + + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x has no request queued\n", + __func__, ep->addr); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + req->req.actual += req->packet_size; + + need_zlp = req->req.actual == req->req.length && + !(req->req.length % ep->maxpacket) && + req->packet_size && req->req.zero; + + dev_dbg(udc->isp->dev, + "TX IRQ: req %p actual/length %u/%u maxpacket %u packet size %u zero %u need zlp %u\n", + req, req->req.actual, req->req.length, ep->maxpacket, + req->packet_size, req->req.zero, need_zlp); + + /* + * Complete the request if all data has been sent and we don't need to + * transmit a zero length packet. + */ + if (req->req.actual == req->req.length && !need_zlp) { + complete = req; + list_del(&req->queue); + + if (ep->addr == 0) + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + + if (!list_empty(&ep->queue)) + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + else + req = NULL; + } + + /* + * Transmit the next packet or start the next request, if any. + * + * TODO: If the endpoint is stalled the next request shouldn't be + * started, but what about the next packet ? + */ + if (req) + isp1760_udc_transmit(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, complete, 0); +} + +static int __isp1760_udc_set_halt(struct isp1760_ep *ep, bool halt) +{ + struct isp1760_udc *udc = ep->udc; + + dev_dbg(udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + halt ? "set" : "clear", ep->addr); + + if (ep->desc && usb_endpoint_xfer_isoc(ep->desc)) { + dev_dbg(udc->isp->dev, "%s: ep%02x is isochronous\n", __func__, + ep->addr); + return -EINVAL; + } + + isp1760_udc_select_ep(ep); + isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); + + if (ep->addr == 0) { + /* When halting the control endpoint, stall both IN and OUT. */ + __isp1760_udc_select_ep(ep, USB_DIR_IN); + isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); + } else if (!halt) { + /* Reset the data PID by cycling the endpoint enable bit. */ + u16 eptype = isp1760_udc_read(udc, DC_EPTYPE); + + isp1760_udc_write(udc, DC_EPTYPE, eptype & ~DC_EPENABLE); + isp1760_udc_write(udc, DC_EPTYPE, eptype); + + /* + * Disabling the endpoint emptied the transmit FIFO, fill it + * again if a request is pending. + * + * TODO: Does the gadget framework require synchronizatino with + * the TX IRQ handler ? + */ + if ((ep->addr & USB_DIR_IN) && !list_empty(&ep->queue)) { + struct isp1760_request *req; + + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + isp1760_udc_transmit(ep, req); + } + } + + ep->halted = halt; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Control Endpoint + */ + +static int isp1760_udc_get_status(struct isp1760_udc *udc, + const struct usb_ctrlrequest *req) +{ + struct isp1760_ep *ep; + u16 status; + + if (req->wLength != cpu_to_le16(2) || req->wValue != cpu_to_le16(0)) + return -EINVAL; + + switch (req->bRequestType) { + case USB_DIR_IN | USB_RECIP_DEVICE: + status = udc->devstatus; + break; + + case USB_DIR_IN | USB_RECIP_INTERFACE: + status = 0; + break; + + case USB_DIR_IN | USB_RECIP_ENDPOINT: + ep = isp1760_udc_find_ep(udc, le16_to_cpu(req->wIndex)); + if (!ep) + return -EINVAL; + + status = 0; + if (ep->halted) + status |= 1 << USB_ENDPOINT_HALT; + break; + + default: + return -EINVAL; + } + + isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | DC_EPDIR); + isp1760_udc_write(udc, DC_BUFLEN, 2); + + writew(cpu_to_le16(status), udc->regs + DC_DATAPORT); + + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + + dev_dbg(udc->isp->dev, "%s: status 0x%04x\n", __func__, status); + + return 0; +} + +static int isp1760_udc_set_address(struct isp1760_udc *udc, u16 addr) +{ + if (addr > 127) { + dev_dbg(udc->isp->dev, "invalid device address %u\n", addr); + return -EINVAL; + } + + if (udc->gadget.state != USB_STATE_DEFAULT && + udc->gadget.state != USB_STATE_ADDRESS) { + dev_dbg(udc->isp->dev, "can't set address in state %u\n", + udc->gadget.state); + return -EINVAL; + } + + usb_gadget_set_state(&udc->gadget, addr ? USB_STATE_ADDRESS : + USB_STATE_DEFAULT); + + isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN | addr); + + spin_lock(&udc->lock); + isp1760_udc_ctrl_send_status(&udc->ep[0], USB_DIR_OUT); + spin_unlock(&udc->lock); + + return 0; +} + +static bool isp1760_ep0_setup_standard(struct isp1760_udc *udc, + struct usb_ctrlrequest *req) +{ + bool stall; + + switch (req->bRequest) { + case USB_REQ_GET_STATUS: + return isp1760_udc_get_status(udc, req); + + case USB_REQ_CLEAR_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup feature. */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + /* + * If the endpoint is wedged only the gadget can clear + * the halt feature. Pretend success in that case, but + * keep the endpoint halted. + */ + if (!ep->wedged) + stall = __isp1760_udc_set_halt(ep, false); + else + stall = false; + + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup and test mode features */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + stall = __isp1760_udc_set_halt(ep, true); + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_ADDRESS: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + return isp1760_udc_set_address(udc, le16_to_cpu(req->wValue)); + + case USB_REQ_SET_CONFIGURATION: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + if (udc->gadget.state != USB_STATE_ADDRESS && + udc->gadget.state != USB_STATE_CONFIGURED) + return true; + + stall = udc->driver->setup(&udc->gadget, req) < 0; + if (stall) + return true; + + usb_gadget_set_state(&udc->gadget, req->wValue ? + USB_STATE_CONFIGURED : USB_STATE_ADDRESS); + + /* + * SET_CONFIGURATION (and SET_INTERFACE) must reset the halt + * feature on all endpoints. There is however no need to do so + * explicitly here as the gadget driver will disable and + * reenable endpoints, clearing the halt feature. + */ + return false; + + default: + return udc->driver->setup(&udc->gadget, req) < 0; + } +} + +static void isp1760_ep0_setup(struct isp1760_udc *udc) +{ + union { + struct usb_ctrlrequest r; + u32 data[2]; + } req; + unsigned int count; + bool stall = false; + + spin_lock(&udc->lock); + + isp1760_udc_write(udc, DC_EPINDEX, DC_EP0SETUP); + + count = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; + if (count != sizeof(req)) { + spin_unlock(&udc->lock); + + dev_err(udc->isp->dev, "invalid length %u for setup packet\n", + count); + + isp1760_udc_ctrl_send_stall(&udc->ep[0]); + return; + } + + req.data[0] = isp1760_udc_read(udc, DC_DATAPORT); + req.data[1] = isp1760_udc_read(udc, DC_DATAPORT); + + if (udc->ep0_state != ISP1760_CTRL_SETUP) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "unexpected SETUP packet\n"); + return; + } + + /* Move to the data stage. */ + if (!req.r.wLength) + udc->ep0_state = ISP1760_CTRL_STATUS; + else if (req.r.bRequestType & USB_DIR_IN) + udc->ep0_state = ISP1760_CTRL_DATA_IN; + else + udc->ep0_state = ISP1760_CTRL_DATA_OUT; + + udc->ep0_dir = req.r.bRequestType & USB_DIR_IN; + udc->ep0_length = le16_to_cpu(req.r.wLength); + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, + "%s: bRequestType 0x%02x bRequest 0x%02x wValue 0x%04x wIndex 0x%04x wLength 0x%04x\n", + __func__, req.r.bRequestType, req.r.bRequest, + le16_to_cpu(req.r.wValue), le16_to_cpu(req.r.wIndex), + le16_to_cpu(req.r.wLength)); + + if ((req.r.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + stall = isp1760_ep0_setup_standard(udc, &req.r); + else + stall = udc->driver->setup(&udc->gadget, &req.r) < 0; + + if (stall) + isp1760_udc_ctrl_send_stall(&udc->ep[0]); +} + +/* ----------------------------------------------------------------------------- + * Gadget Endpoint Operations + */ + +static int isp1760_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + unsigned int type; + + dev_dbg(uep->udc->isp->dev, "%s\n", __func__); + + /* + * Validate the descriptor. The control endpoint can't be enabled + * manually. + */ + if (desc->bDescriptorType != USB_DT_ENDPOINT || + desc->bEndpointAddress == 0 || + desc->bEndpointAddress != uep->addr || + le16_to_cpu(desc->wMaxPacketSize) > ep->maxpacket) { + dev_dbg(udc->isp->dev, + "%s: invalid descriptor type %u addr %02x ep addr %02x max packet size %u/%u\n", + __func__, desc->bDescriptorType, + desc->bEndpointAddress, uep->addr, + le16_to_cpu(desc->wMaxPacketSize), ep->maxpacket); + return -EINVAL; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_ISOC: + type = DC_ENDPTYP_ISOC; + break; + case USB_ENDPOINT_XFER_BULK: + type = DC_ENDPTYP_BULK; + break; + case USB_ENDPOINT_XFER_INT: + type = DC_ENDPTYP_INTERRUPT; + break; + case USB_ENDPOINT_XFER_CONTROL: + default: + dev_dbg(udc->isp->dev, "%s: control endpoints unsupported\n", + __func__); + return -EINVAL; + } + + spin_lock_irqsave(&udc->lock, flags); + + uep->desc = desc; + uep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); + uep->rx_pending = false; + uep->halted = false; + uep->wedged = false; + + isp1760_udc_select_ep(uep); + isp1760_udc_write(udc, DC_EPMAXPKTSZ, uep->maxpacket); + isp1760_udc_write(udc, DC_BUFLEN, uep->maxpacket); + isp1760_udc_write(udc, DC_EPTYPE, DC_EPENABLE | type); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int isp1760_ep_disable(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + struct isp1760_request *req, *nreq; + LIST_HEAD(req_list); + unsigned long flags; + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + spin_lock_irqsave(&udc->lock, flags); + + if (!uep->desc) { + dev_dbg(udc->isp->dev, "%s: endpoint not enabled\n", __func__); + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + uep->desc = NULL; + uep->maxpacket = 0; + + isp1760_udc_select_ep(uep); + isp1760_udc_write(udc, DC_EPTYPE, 0); + + /* TODO Synchronize with the IRQ handler */ + + list_splice_init(&uep->queue, &req_list); + + spin_unlock_irqrestore(&udc->lock, flags); + + list_for_each_entry_safe(req, nreq, &req_list, queue) { + list_del(&req->queue); + isp1760_udc_request_complete(uep, req, -ESHUTDOWN); + } + + return 0; +} + +static struct usb_request *isp1760_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct isp1760_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + + return &req->req; +} + +static void isp1760_ep_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + + kfree(req); +} + +static int isp1760_ep_queue(struct usb_ep *ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + bool complete = false; + unsigned long flags; + int ret = 0; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + dev_dbg(udc->isp->dev, + "%s: req %p (%u bytes%s) ep %p(0x%02x)\n", __func__, _req, + _req->length, _req->zero ? " (zlp)" : "", uep, uep->addr); + + req->ep = uep; + + if (uep->addr == 0) { + if (_req->length != udc->ep0_length && + udc->ep0_state != ISP1760_CTRL_DATA_IN) { + dev_dbg(udc->isp->dev, + "%s: invalid length %u for req %p\n", + __func__, _req->length, req); + ret = -EINVAL; + goto done; + } + + switch (udc->ep0_state) { + case ISP1760_CTRL_DATA_IN: + dev_dbg(udc->isp->dev, "%s: transmitting req %p\n", + __func__, req); + + list_add_tail(&req->queue, &uep->queue); + isp1760_udc_transmit(uep, req); + break; + + case ISP1760_CTRL_DATA_OUT: + list_add_tail(&req->queue, &uep->queue); + __isp1760_udc_select_ep(uep, USB_DIR_OUT); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + break; + + case ISP1760_CTRL_STATUS: + complete = true; + break; + + default: + dev_dbg(udc->isp->dev, "%s: invalid ep0 state\n", + __func__); + ret = -EINVAL; + break; + } + } else if (uep->desc) { + bool empty = list_empty(&uep->queue); + + list_add_tail(&req->queue, &uep->queue); + if ((uep->addr & USB_DIR_IN) && !uep->halted && empty) + isp1760_udc_transmit(uep, req); + else if (!(uep->addr & USB_DIR_IN) && uep->rx_pending) + complete = isp1760_udc_receive(uep, req); + } else { + dev_dbg(udc->isp->dev, + "%s: can't queue request to disabled ep%02x\n", + __func__, uep->addr); + ret = -ESHUTDOWN; + } + +done: + if (ret < 0) + req->ep = NULL; + + spin_unlock_irqrestore(&udc->lock, flags); + + if (complete) + isp1760_udc_request_complete(uep, req, 0); + + return ret; +} + +static int isp1760_ep_dequeue(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + dev_dbg(uep->udc->isp->dev, "%s(ep%02x)\n", __func__, uep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + if (req->ep != uep) + req = NULL; + else + list_del(&req->queue); + + spin_unlock_irqrestore(&udc->lock, flags); + + if (!req) + return -EINVAL; + + isp1760_udc_request_complete(uep, req, -ECONNRESET); + return 0; +} + +static int __isp1760_ep_set_halt(struct isp1760_ep *uep, bool stall, bool wedge) +{ + struct isp1760_udc *udc = uep->udc; + int ret; + + if (!uep->addr) { + /* + * Halting the control endpoint is only valid as a delayed error + * response to a SETUP packet. Make sure EP0 is in the right + * stage and that the gadget isn't trying to clear the halt + * condition. + */ + if (WARN_ON(udc->ep0_state == ISP1760_CTRL_SETUP || !stall || + wedge)) { + return -EINVAL; + } + } + + if (uep->addr && !uep->desc) { + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + uep->addr); + return -EINVAL; + } + + if (uep->addr & USB_DIR_IN) { + /* Refuse to halt IN endpoints with active transfers. */ + if (!list_empty(&uep->queue)) { + dev_dbg(udc->isp->dev, + "%s: ep%02x has request pending\n", __func__, + uep->addr); + return -EAGAIN; + } + } + + ret = __isp1760_udc_set_halt(uep, stall); + if (ret < 0) + return ret; + + if (!uep->addr) { + /* + * Stalling EP0 completes the control transaction, move back to + * the SETUP state. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; + return 0; + } + + if (wedge) + uep->wedged = true; + else if (!stall) + uep->wedged = false; + + return 0; +} + +static int isp1760_ep_set_halt(struct usb_ep *ep, int value) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + value ? "set" : "clear", uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, value, false); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static int isp1760_ep_set_wedge(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: set wedge on ep%02x)\n", __func__, + uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, true, true); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static void isp1760_ep_fifo_flush(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + isp1760_udc_select_ep(uep); + + /* + * Set the CLBUF bit twice to flush both buffers in case double + * buffering is enabled. + */ + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static const struct usb_ep_ops isp1760_ep_ops = { + .enable = isp1760_ep_enable, + .disable = isp1760_ep_disable, + .alloc_request = isp1760_ep_alloc_request, + .free_request = isp1760_ep_free_request, + .queue = isp1760_ep_queue, + .dequeue = isp1760_ep_dequeue, + .set_halt = isp1760_ep_set_halt, + .set_wedge = isp1760_ep_set_wedge, + .fifo_flush = isp1760_ep_fifo_flush, +}; + +/* ----------------------------------------------------------------------------- + * Device States + */ + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_connect(struct isp1760_udc *udc) +{ + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + mod_timer(&udc->vbus_timer, jiffies + ISP1760_VBUS_POLL_INTERVAL); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_disconnect(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_POWERED) + return; + + dev_dbg(udc->isp->dev, "Device disconnected in state %u\n", + udc->gadget.state); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + if (udc->driver->disconnect) + udc->driver->disconnect(&udc->gadget); + + del_timer(&udc->vbus_timer); + + /* TODO Reset all endpoints ? */ +} + +static void isp1760_udc_init_hw(struct isp1760_udc *udc) +{ + /* + * The device controller currently shares its interrupt with the host + * controller, the DC_IRQ polarity and signaling mode are ignored. Set + * the to active-low level-triggered. + * + * Configure the control, in and out pipes to generate interrupts on + * ACK tokens only (and NYET for the out pipe). The default + * configuration also generates an interrupt on the first NACK token. + */ + isp1760_udc_write(udc, DC_INTCONF, DC_CDBGMOD_ACK | DC_DDBGMODIN_ACK | + DC_DDBGMODOUT_ACK_NYET); + + isp1760_udc_write(udc, DC_INTENABLE, DC_IEPRXTX(7) | DC_IEPRXTX(6) | + DC_IEPRXTX(5) | DC_IEPRXTX(4) | DC_IEPRXTX(3) | + DC_IEPRXTX(2) | DC_IEPRXTX(1) | DC_IEPRXTX(0) | + DC_IEP0SETUP | DC_IEVBUS | DC_IERESM | DC_IESUSP | + DC_IEHS_STA | DC_IEBRST); + + if (udc->connected) + isp1760_set_pullup(udc->isp, true); + + isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN); +} + +static void isp1760_udc_reset(struct isp1760_udc *udc) +{ + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* + * The bus reset has reset most registers to their default value, + * reinitialize the UDC hardware. + */ + isp1760_udc_init_hw(udc); + + udc->ep0_state = ISP1760_CTRL_SETUP; + udc->gadget.speed = USB_SPEED_FULL; + + usb_gadget_udc_reset(&udc->gadget, udc->driver); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_suspend(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void isp1760_udc_resume(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* ----------------------------------------------------------------------------- + * Gadget Operations + */ + +static int isp1760_udc_get_frame(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + return isp1760_udc_read(udc, DC_FRAMENUM) & ((1 << 11) - 1); +} + +static int isp1760_udc_wakeup(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + return -ENOTSUPP; +} + +static int isp1760_udc_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + if (is_selfpowered) + udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + + return 0; +} + +static int isp1760_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + isp1760_set_pullup(udc->isp, is_on); + udc->connected = is_on; + + return 0; +} + +static int isp1760_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + /* The hardware doesn't support low speed. */ + if (driver->max_speed < USB_SPEED_FULL) { + dev_err(udc->isp->dev, "Invalid gadget driver\n"); + return -EINVAL; + } + + spin_lock(&udc->lock); + + if (udc->driver) { + dev_err(udc->isp->dev, "UDC already has a gadget driver\n"); + spin_unlock(&udc->lock); + return -EBUSY; + } + + udc->driver = driver; + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, "starting UDC with driver %s\n", + driver->function); + + udc->devstatus = 0; + udc->connected = true; + + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + /* DMA isn't supported yet, don't enable the DMA clock. */ + isp1760_udc_write(udc, DC_MODE, DC_GLINTENA); + + isp1760_udc_init_hw(udc); + + dev_dbg(udc->isp->dev, "UDC started with driver %s\n", + driver->function); + + return 0; +} + +static int isp1760_udc_stop(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + del_timer_sync(&udc->vbus_timer); + + isp1760_udc_write(udc, DC_MODE, 0); + + spin_lock(&udc->lock); + udc->driver = NULL; + spin_unlock(&udc->lock); + + return 0; +} + +static struct usb_gadget_ops isp1760_udc_ops = { + .get_frame = isp1760_udc_get_frame, + .wakeup = isp1760_udc_wakeup, + .set_selfpowered = isp1760_udc_set_selfpowered, + .pullup = isp1760_udc_pullup, + .udc_start = isp1760_udc_start, + .udc_stop = isp1760_udc_stop, +}; + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t isp1760_udc_irq(int irq, void *dev) +{ + struct isp1760_udc *udc = dev; + unsigned int i; + u32 status; + + status = isp1760_udc_read(udc, DC_INTERRUPT) + & isp1760_udc_read(udc, DC_INTENABLE); + isp1760_udc_write(udc, DC_INTERRUPT, status); + + if (status & DC_IEVBUS) { + dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__); + /* The VBUS interrupt is only triggered when VBUS appears. */ + spin_lock(&udc->lock); + isp1760_udc_connect(udc); + spin_unlock(&udc->lock); + } + + if (status & DC_IEBRST) { + dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__); + + isp1760_udc_reset(udc); + } + + for (i = 0; i <= 7; ++i) { + struct isp1760_ep *ep = &udc->ep[i*2]; + + if (status & DC_IEPTX(i)) { + dev_dbg(udc->isp->dev, "%s(EPTX%u)\n", __func__, i); + isp1760_ep_tx_complete(ep); + } + + if (status & DC_IEPRX(i)) { + dev_dbg(udc->isp->dev, "%s(EPRX%u)\n", __func__, i); + isp1760_ep_rx_ready(i ? ep - 1 : ep); + } + } + + if (status & DC_IEP0SETUP) { + dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__); + + isp1760_ep0_setup(udc); + } + + if (status & DC_IERESM) { + dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__); + isp1760_udc_resume(udc); + } + + if (status & DC_IESUSP) { + dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__); + + spin_lock(&udc->lock); + if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else + isp1760_udc_suspend(udc); + spin_unlock(&udc->lock); + } + + if (status & DC_IEHS_STA) { + dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__); + udc->gadget.speed = USB_SPEED_HIGH; + } + + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static void isp1760_udc_vbus_poll(unsigned long data) +{ + struct isp1760_udc *udc = (struct isp1760_udc *)data; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else if (udc->gadget.state >= USB_STATE_POWERED) + mod_timer(&udc->vbus_timer, + jiffies + ISP1760_VBUS_POLL_INTERVAL); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Registration + */ + +static void isp1760_udc_init_eps(struct isp1760_udc *udc) +{ + unsigned int i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (i = 0; i < ARRAY_SIZE(udc->ep); ++i) { + struct isp1760_ep *ep = &udc->ep[i]; + unsigned int ep_num = (i + 1) / 2; + bool is_in = !(i & 1); + + ep->udc = udc; + + INIT_LIST_HEAD(&ep->queue); + + ep->addr = (ep_num && is_in ? USB_DIR_IN : USB_DIR_OUT) + | ep_num; + ep->desc = NULL; + + sprintf(ep->name, "ep%u%s", ep_num, + ep_num ? (is_in ? "in" : "out") : ""); + + ep->ep.ops = &isp1760_ep_ops; + ep->ep.name = ep->name; + + /* + * Hardcode the maximum packet sizes for now, to 64 bytes for + * the control endpoint and 512 bytes for all other endpoints. + * This fits in the 8kB FIFO without double-buffering. + */ + if (ep_num == 0) { + ep->ep.maxpacket = 64; + ep->maxpacket = 64; + udc->gadget.ep0 = &ep->ep; + } else { + ep->ep.maxpacket = 512; + ep->maxpacket = 0; + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + } +} + +static int isp1760_udc_init(struct isp1760_udc *udc) +{ + u16 scratch; + u32 chipid; + + /* + * Check that the controller is present by writing to the scratch + * register, modifying the bus pattern by reading from the chip ID + * register, and reading the scratch register value back. The chip ID + * and scratch register contents must match the expected values. + */ + isp1760_udc_write(udc, DC_SCRATCH, 0xbabe); + chipid = isp1760_udc_read(udc, DC_CHIPID); + scratch = isp1760_udc_read(udc, DC_SCRATCH); + + if (scratch != 0xbabe) { + dev_err(udc->isp->dev, + "udc: scratch test failed (0x%04x/0x%08x)\n", + scratch, chipid); + return -ENODEV; + } + + if (chipid != 0x00011582) { + dev_err(udc->isp->dev, "udc: invalid chip ID 0x%08x\n", chipid); + return -ENODEV; + } + + /* Reset the device controller. */ + isp1760_udc_write(udc, DC_MODE, DC_SFRESET); + usleep_range(10000, 11000); + isp1760_udc_write(udc, DC_MODE, 0); + usleep_range(10000, 11000); + + return 0; +} + +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + struct isp1760_udc *udc = &isp->udc; + const char *devname; + int ret; + + udc->irq = -1; + udc->isp = isp; + udc->regs = isp->regs; + + spin_lock_init(&udc->lock); + setup_timer(&udc->vbus_timer, isp1760_udc_vbus_poll, + (unsigned long)udc); + + ret = isp1760_udc_init(udc); + if (ret < 0) + return ret; + + devname = dev_name(isp->dev); + udc->irqname = kmalloc(strlen(devname) + 7, GFP_KERNEL); + if (!udc->irqname) + return -ENOMEM; + + sprintf(udc->irqname, "%s (udc)", devname); + + ret = request_irq(irq, isp1760_udc_irq, IRQF_SHARED | IRQF_DISABLED | + irqflags, udc->irqname, udc); + if (ret < 0) + goto error; + + udc->irq = irq; + + /* + * Initialize the gadget static fields and register its device. Gadget + * fields that vary during the life time of the gadget are initialized + * by the UDC core. + */ + udc->gadget.ops = &isp1760_udc_ops; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.name = "isp1761_udc"; + + isp1760_udc_init_eps(udc); + + ret = usb_add_gadget_udc(isp->dev, &udc->gadget); + if (ret < 0) + goto error; + + return 0; + +error: + if (udc->irq >= 0) + free_irq(udc->irq, udc); + kfree(udc->irqname); + + return ret; +} + +void isp1760_udc_unregister(struct isp1760_device *isp) +{ + struct isp1760_udc *udc = &isp->udc; + + usb_del_gadget_udc(&udc->gadget); + + free_irq(udc->irq, udc); + kfree(udc->irqname); +} diff --git a/drivers/usb/host/isp1760-udc.h b/drivers/usb/host/isp1760-udc.h new file mode 100644 index 0000000..4af6ba6 --- /dev/null +++ b/drivers/usb/host/isp1760-udc.h @@ -0,0 +1,106 @@ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef _ISP1760_UDC_H_ +#define _ISP1760_UDC_H_ + +#include +#include +#include +#include +#include + +struct isp1760_device; +struct isp1760_udc; + +enum isp1760_ctrl_state { + ISP1760_CTRL_SETUP, /* Waiting for a SETUP transaction */ + ISP1760_CTRL_DATA_IN, /* Setup received, data IN stage */ + ISP1760_CTRL_DATA_OUT, /* Setup received, data OUT stage */ + ISP1760_CTRL_STATUS, /* 0-length request in status stage */ +}; + +struct isp1760_ep { + struct isp1760_udc *udc; + struct usb_ep ep; + + struct list_head queue; + + unsigned int addr; + unsigned int maxpacket; + char name[7]; + + const struct usb_endpoint_descriptor *desc; + + bool rx_pending; + bool halted; + bool wedged; +}; + +/** + * struct isp1760_udc - UDC state information + * irq: IRQ number + * irqname: IRQ name (as passed to request_irq) + * regs: Base address of the UDC registers + * driver: Gadget driver + * gadget: Gadget device + * lock: Protects driver, vbus_timer, ep, ep0_*, DC_EPINDEX register + * ep: Array of endpoints + * ep0_state: Control request state for endpoint 0 + * ep0_dir: Direction of the current control request + * ep0_length: Length of the current control request + * connected: Tracks gadget driver bus connection state + */ +struct isp1760_udc { +#if CONFIG_USB_ISP1761_UDC + struct isp1760_device *isp; + + int irq; + char *irqname; + void __iomem *regs; + + struct usb_gadget_driver *driver; + struct usb_gadget gadget; + + spinlock_t lock; + struct timer_list vbus_timer; + + struct isp1760_ep ep[15]; + + enum isp1760_ctrl_state ep0_state; + u8 ep0_dir; + u16 ep0_length; + + bool connected; + + unsigned int devstatus; +#endif +}; + +#if CONFIG_USB_ISP1761_UDC +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags); +void isp1760_udc_unregister(struct isp1760_device *isp); +#else +static inline int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + return 0; +} + +static inline void isp1760_udc_unregister(struct isp1760_device *isp) +{ +} +#endif + +#endif -- cgit v0.10.2 From 7ef077a8ad3557f030d0407c4f56c5a0cf1e418a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:02 +0200 Subject: usb: isp1760: Move driver from drivers/usb/host/ to drivers/usb/isp1760/ Now that this is DRD, it doesn't make sense to keep it under drivers/usb/host. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index ae481c3..8ed451d 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -104,6 +104,8 @@ source "drivers/usb/dwc2/Kconfig" source "drivers/usb/chipidea/Kconfig" +source "drivers/usb/isp1760/Kconfig" + comment "USB port drivers" if USB diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index d7be717..2f1e2aa 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_USB) += core/ obj-$(CONFIG_USB_DWC3) += dwc3/ obj-$(CONFIG_USB_DWC2) += dwc2/ +obj-$(CONFIG_USB_ISP1760) += isp1760/ obj-$(CONFIG_USB_MON) += mon/ @@ -23,7 +24,6 @@ obj-$(CONFIG_USB_ISP1362_HCD) += host/ obj-$(CONFIG_USB_U132_HCD) += host/ obj-$(CONFIG_USB_R8A66597_HCD) += host/ obj-$(CONFIG_USB_HWA_HCD) += host/ -obj-$(CONFIG_USB_ISP1760_HCD) += host/ obj-$(CONFIG_USB_IMX21_HCD) += host/ obj-$(CONFIG_USB_FSL_MPH_DR_OF) += host/ obj-$(CONFIG_USB_FUSBH200_HCD) += host/ diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index c9152e2..b8e213e 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -109,13 +109,6 @@ config USB_GR_UDC Select this to support Aeroflex Gaisler GRUSBDC cores from the GRLIB VHDL IP core library. -config USB_ISP1761_UDC - boolean "NXP ISP1761 USB Device Controller" - depends on USB_ISP1760_HCD - help - The NXP ISP1761 is a dual-role high-speed USB host and device - controller. - config USB_OMAP tristate "OMAP USB Device Controller" depends on ARCH_OMAP1 diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index fafc628..3de291b6 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -331,20 +331,6 @@ config USB_ISP116X_HCD To compile this driver as a module, choose M here: the module will be called isp116x-hcd. -config USB_ISP1760_HCD - tristate "ISP 1760 HCD support" - ---help--- - The ISP1760 chip is a USB 2.0 host controller. - - This driver does not support isochronous transfers or OTG. - This USB controller is usually attached to a non-DMA-Master - capable bus. NXP's eval kit brings this chip on PCI card - where the chip itself is behind a PLB to simulate such - a bus. - - To compile this driver as a module, choose M here: the - module will be called isp1760. - config USB_ISP1362_HCD tristate "ISP1362 HCD support" ---help--- diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 67d3f18..65b0b6a 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -5,9 +5,6 @@ # tell define_trace.h where to find the xhci trace header CFLAGS_xhci-trace.o := -I$(src) -isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o -isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o - fhci-y := fhci-hcd.o fhci-hub.o fhci-q.o fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o @@ -70,7 +67,6 @@ obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o -obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o obj-$(CONFIG_USB_IMX21_HCD) += imx21-hcd.o obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o diff --git a/drivers/usb/host/isp1760-core.c b/drivers/usb/host/isp1760-core.c deleted file mode 100644 index 727e90a..0000000 --- a/drivers/usb/host/isp1760-core.c +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Driver for the NXP ISP1760 chip - * - * Copyright 2014 Laurent Pinchart - * Copyright 2007 Sebastian Siewior - * - * Contacts: - * Sebastian Siewior - * Laurent Pinchart - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "isp1760-core.h" -#include "isp1760-hcd.h" -#include "isp1760-regs.h" -#include "isp1760-udc.h" - -static void isp1760_init_core(struct isp1760_device *isp) -{ - u32 otgctrl; - u32 hwmode; - - /* Low-level chip reset */ - if (isp->rst_gpio) { - gpiod_set_value_cansleep(isp->rst_gpio, 1); - mdelay(50); - gpiod_set_value_cansleep(isp->rst_gpio, 0); - } - - /* - * Reset the host controller, including the CPU interface - * configuration. - */ - isp1760_write32(isp->regs, HC_RESET_REG, SW_RESET_RESET_ALL); - msleep(100); - - /* Setup HW Mode Control: This assumes a level active-low interrupt */ - hwmode = HW_DATA_BUS_32BIT; - - if (isp->devflags & ISP1760_FLAG_BUS_WIDTH_16) - hwmode &= ~HW_DATA_BUS_32BIT; - if (isp->devflags & ISP1760_FLAG_ANALOG_OC) - hwmode |= HW_ANA_DIGI_OC; - if (isp->devflags & ISP1760_FLAG_DACK_POL_HIGH) - hwmode |= HW_DACK_POL_HIGH; - if (isp->devflags & ISP1760_FLAG_DREQ_POL_HIGH) - hwmode |= HW_DREQ_POL_HIGH; - if (isp->devflags & ISP1760_FLAG_INTR_POL_HIGH) - hwmode |= HW_INTR_HIGH_ACT; - if (isp->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) - hwmode |= HW_INTR_EDGE_TRIG; - - /* - * The ISP1761 has a dedicated DC IRQ line but supports sharing the HC - * IRQ line for both the host and device controllers. Hardcode IRQ - * sharing for now and disable the DC interrupts globally to avoid - * spurious interrupts during HCD registration. - */ - if (isp->devflags & ISP1760_FLAG_ISP1761) { - isp1760_write32(isp->regs, DC_MODE, 0); - hwmode |= HW_COMN_IRQ; - } - - /* - * We have to set this first in case we're in 16-bit mode. - * Write it twice to ensure correct upper bits if switching - * to 16-bit mode. - */ - isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); - isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); - - /* - * PORT 1 Control register of the ISP1760 is the OTG control register - * on ISP1761. - * - * TODO: Really support OTG. For now we configure port 1 in device mode - * when OTG is requested. - */ - if ((isp->devflags & ISP1760_FLAG_ISP1761) && - (isp->devflags & ISP1760_FLAG_OTG_EN)) - otgctrl = ((HW_DM_PULLDOWN | HW_DP_PULLDOWN) << 16) - | HW_OTG_DISABLE; - else - otgctrl = (HW_SW_SEL_HC_DC << 16) - | (HW_VBUS_DRV | HW_SEL_CP_EXT); - - isp1760_write32(isp->regs, HC_PORT1_CTRL, otgctrl); - - dev_info(isp->dev, "bus width: %u, oc: %s\n", - isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, - isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); -} - -void isp1760_set_pullup(struct isp1760_device *isp, bool enable) -{ - isp1760_write32(isp->regs, HW_OTG_CTRL_SET, - enable ? HW_DP_PULLUP : HW_DP_PULLUP << 16); -} - -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags) -{ - struct isp1760_device *isp; - int ret; - - if (usb_disabled()) - return -ENODEV; - - /* prevent usb-core allocating DMA pages */ - dev->dma_mask = NULL; - - isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); - if (!isp) - return -ENOMEM; - - isp->dev = dev; - isp->devflags = devflags; - - isp->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); - if (IS_ERR(isp->rst_gpio)) - return PTR_ERR(isp->rst_gpio); - - isp->regs = devm_ioremap_resource(dev, mem); - if (IS_ERR(isp->regs)) - return PTR_ERR(isp->regs); - - isp1760_init_core(isp); - - ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, - irqflags | IRQF_SHARED, dev); - if (ret < 0) - return ret; - - if (devflags & ISP1760_FLAG_ISP1761) { - ret = isp1760_udc_register(isp, irq, irqflags | IRQF_SHARED | - IRQF_DISABLED); - if (ret < 0) { - isp1760_hcd_unregister(&isp->hcd); - return ret; - } - } - - dev_set_drvdata(dev, isp); - - return 0; -} - -void isp1760_unregister(struct device *dev) -{ - struct isp1760_device *isp = dev_get_drvdata(dev); - - if (isp->devflags & ISP1760_FLAG_ISP1761) - isp1760_udc_unregister(isp); - - isp1760_hcd_unregister(&isp->hcd); -} - -MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); -MODULE_AUTHOR("Sebastian Siewior "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/isp1760-core.h b/drivers/usb/host/isp1760-core.h deleted file mode 100644 index c70f836..0000000 --- a/drivers/usb/host/isp1760-core.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Driver for the NXP ISP1760 chip - * - * Copyright 2014 Laurent Pinchart - * Copyright 2007 Sebastian Siewior - * - * Contacts: - * Sebastian Siewior - * Laurent Pinchart - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - */ - -#ifndef _ISP1760_CORE_H_ -#define _ISP1760_CORE_H_ - -#include - -#include "isp1760-hcd.h" -#include "isp1760-udc.h" - -struct device; -struct gpio_desc; - -/* - * Device flags that can vary from board to board. All of these - * indicate the most "atypical" case, so that a devflags of 0 is - * a sane default configuration. - */ -#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ -#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ -#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ -#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ -#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ -#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ -#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ -#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ - -struct isp1760_device { - struct device *dev; - - void __iomem *regs; - unsigned int devflags; - struct gpio_desc *rst_gpio; - - struct isp1760_hcd hcd; - struct isp1760_udc udc; -}; - -int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, - struct device *dev, unsigned int devflags); -void isp1760_unregister(struct device *dev); - -void isp1760_set_pullup(struct isp1760_device *isp, bool enable); - -static inline u32 isp1760_read32(void __iomem *base, u32 reg) -{ - return readl(base + reg); -} - -static inline void isp1760_write32(void __iomem *base, u32 reg, u32 val) -{ - writel(val, base + reg); -} - -#endif diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c deleted file mode 100644 index 568446c..0000000 --- a/drivers/usb/host/isp1760-hcd.c +++ /dev/null @@ -1,2231 +0,0 @@ -/* - * Driver for the NXP ISP1760 chip - * - * However, the code might contain some bugs. What doesn't work for sure is: - * - ISO - * - OTG - e The interrupt line is configured as active low, level. - * - * (c) 2007 Sebastian Siewior - * - * (c) 2011 Arvid Brodin - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "isp1760-core.h" -#include "isp1760-hcd.h" -#include "isp1760-regs.h" - -static struct kmem_cache *qtd_cachep; -static struct kmem_cache *qh_cachep; -static struct kmem_cache *urb_listitem_cachep; - -typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, - struct isp1760_qtd *qtd); - -static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) -{ - return *(struct isp1760_hcd **)hcd->hcd_priv; -} - -/* urb state*/ -#define DELETE_URB (0x0008) -#define NO_TRANSFER_ACTIVE (0xffffffff) - -/* Philips Proprietary Transfer Descriptor (PTD) */ -typedef __u32 __bitwise __dw; -struct ptd { - __dw dw0; - __dw dw1; - __dw dw2; - __dw dw3; - __dw dw4; - __dw dw5; - __dw dw6; - __dw dw7; -}; -#define PTD_OFFSET 0x0400 -#define ISO_PTD_OFFSET 0x0400 -#define INT_PTD_OFFSET 0x0800 -#define ATL_PTD_OFFSET 0x0c00 -#define PAYLOAD_OFFSET 0x1000 - - -/* ATL */ -/* DW0 */ -#define DW0_VALID_BIT 1 -#define FROM_DW0_VALID(x) ((x) & 0x01) -#define TO_DW0_LENGTH(x) (((u32) x) << 3) -#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) -#define TO_DW0_MULTI(x) (((u32) x) << 29) -#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) -/* DW1 */ -#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) -#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) -#define DW1_TRANS_BULK ((u32) 2 << 12) -#define DW1_TRANS_INT ((u32) 3 << 12) -#define DW1_TRANS_SPLIT ((u32) 1 << 14) -#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) -#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) -#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) -/* DW2 */ -#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) -#define TO_DW2_RL(x) ((x) << 25) -#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) -/* DW3 */ -#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) -#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) -#define TO_DW3_NAKCOUNT(x) ((x) << 19) -#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) -#define TO_DW3_CERR(x) ((x) << 23) -#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) -#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) -#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) -#define TO_DW3_PING(x) ((x) << 26) -#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) -#define DW3_ERROR_BIT (1 << 28) -#define DW3_BABBLE_BIT (1 << 29) -#define DW3_HALT_BIT (1 << 30) -#define DW3_ACTIVE_BIT (1 << 31) -#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) - -#define INT_UNDERRUN (1 << 2) -#define INT_BABBLE (1 << 1) -#define INT_EXACT (1 << 0) - -#define SETUP_PID (2) -#define IN_PID (1) -#define OUT_PID (0) - -/* Errata 1 */ -#define RL_COUNTER (0) -#define NAK_COUNTER (0) -#define ERR_COUNTER (2) - -struct isp1760_qtd { - u8 packet_type; - void *data_buffer; - u32 payload_addr; - - /* the rest is HCD-private */ - struct list_head qtd_list; - struct urb *urb; - size_t length; - size_t actual_length; - - /* QTD_ENQUEUED: waiting for transfer (inactive) */ - /* QTD_PAYLOAD_ALLOC: chip mem has been allocated for payload */ - /* QTD_XFER_STARTED: valid ptd has been written to isp176x - only - interrupt handler may touch this qtd! */ - /* QTD_XFER_COMPLETE: payload has been transferred successfully */ - /* QTD_RETIRE: transfer error/abort qtd */ -#define QTD_ENQUEUED 0 -#define QTD_PAYLOAD_ALLOC 1 -#define QTD_XFER_STARTED 2 -#define QTD_XFER_COMPLETE 3 -#define QTD_RETIRE 4 - u32 status; -}; - -/* Queue head, one for each active endpoint */ -struct isp1760_qh { - struct list_head qh_list; - struct list_head qtd_list; - u32 toggle; - u32 ping; - int slot; - int tt_buffer_dirty; /* See USB2.0 spec section 11.17.5 */ -}; - -struct urb_listitem { - struct list_head urb_list; - struct urb *urb; -}; - -/* - * Access functions for isp176x registers (addresses 0..0x03FF). - */ -static u32 reg_read32(void __iomem *base, u32 reg) -{ - return isp1760_read32(base, reg); -} - -static void reg_write32(void __iomem *base, u32 reg, u32 val) -{ - isp1760_write32(base, reg, val); -} - -/* - * Access functions for isp176x memory (offset >= 0x0400). - * - * bank_reads8() reads memory locations prefetched by an earlier write to - * HC_MEMORY_REG (see isp176x datasheet). Unless you want to do fancy multi- - * bank optimizations, you should use the more generic mem_reads8() below. - * - * For access to ptd memory, use the specialized ptd_read() and ptd_write() - * below. - * - * These functions copy via MMIO data to/from the device. memcpy_{to|from}io() - * doesn't quite work because some people have to enforce 32-bit access - */ -static void bank_reads8(void __iomem *src_base, u32 src_offset, u32 bank_addr, - __u32 *dst, u32 bytes) -{ - __u32 __iomem *src; - u32 val; - __u8 *src_byteptr; - __u8 *dst_byteptr; - - src = src_base + (bank_addr | src_offset); - - if (src_offset < PAYLOAD_OFFSET) { - while (bytes >= 4) { - *dst = le32_to_cpu(__raw_readl(src)); - bytes -= 4; - src++; - dst++; - } - } else { - while (bytes >= 4) { - *dst = __raw_readl(src); - bytes -= 4; - src++; - dst++; - } - } - - if (!bytes) - return; - - /* in case we have 3, 2 or 1 by left. The dst buffer may not be fully - * allocated. - */ - if (src_offset < PAYLOAD_OFFSET) - val = le32_to_cpu(__raw_readl(src)); - else - val = __raw_readl(src); - - dst_byteptr = (void *) dst; - src_byteptr = (void *) &val; - while (bytes > 0) { - *dst_byteptr = *src_byteptr; - dst_byteptr++; - src_byteptr++; - bytes--; - } -} - -static void mem_reads8(void __iomem *src_base, u32 src_offset, void *dst, - u32 bytes) -{ - reg_write32(src_base, HC_MEMORY_REG, src_offset + ISP_BANK(0)); - ndelay(90); - bank_reads8(src_base, src_offset, ISP_BANK(0), dst, bytes); -} - -static void mem_writes8(void __iomem *dst_base, u32 dst_offset, - __u32 const *src, u32 bytes) -{ - __u32 __iomem *dst; - - dst = dst_base + dst_offset; - - if (dst_offset < PAYLOAD_OFFSET) { - while (bytes >= 4) { - __raw_writel(cpu_to_le32(*src), dst); - bytes -= 4; - src++; - dst++; - } - } else { - while (bytes >= 4) { - __raw_writel(*src, dst); - bytes -= 4; - src++; - dst++; - } - } - - if (!bytes) - return; - /* in case we have 3, 2 or 1 bytes left. The buffer is allocated and the - * extra bytes should not be read by the HW. - */ - - if (dst_offset < PAYLOAD_OFFSET) - __raw_writel(cpu_to_le32(*src), dst); - else - __raw_writel(*src, dst); -} - -/* - * Read and write ptds. 'ptd_offset' should be one of ISO_PTD_OFFSET, - * INT_PTD_OFFSET, and ATL_PTD_OFFSET. 'slot' should be less than 32. - */ -static void ptd_read(void __iomem *base, u32 ptd_offset, u32 slot, - struct ptd *ptd) -{ - reg_write32(base, HC_MEMORY_REG, - ISP_BANK(0) + ptd_offset + slot*sizeof(*ptd)); - ndelay(90); - bank_reads8(base, ptd_offset + slot*sizeof(*ptd), ISP_BANK(0), - (void *) ptd, sizeof(*ptd)); -} - -static void ptd_write(void __iomem *base, u32 ptd_offset, u32 slot, - struct ptd *ptd) -{ - mem_writes8(base, ptd_offset + slot*sizeof(*ptd) + sizeof(ptd->dw0), - &ptd->dw1, 7*sizeof(ptd->dw1)); - /* Make sure dw0 gets written last (after other dw's and after payload) - since it contains the enable bit */ - wmb(); - mem_writes8(base, ptd_offset + slot*sizeof(*ptd), &ptd->dw0, - sizeof(ptd->dw0)); -} - - -/* memory management of the 60kb on the chip from 0x1000 to 0xffff */ -static void init_memory(struct isp1760_hcd *priv) -{ - int i, curr; - u32 payload_addr; - - payload_addr = PAYLOAD_OFFSET; - for (i = 0; i < BLOCK_1_NUM; i++) { - priv->memory_pool[i].start = payload_addr; - priv->memory_pool[i].size = BLOCK_1_SIZE; - priv->memory_pool[i].free = 1; - payload_addr += priv->memory_pool[i].size; - } - - curr = i; - for (i = 0; i < BLOCK_2_NUM; i++) { - priv->memory_pool[curr + i].start = payload_addr; - priv->memory_pool[curr + i].size = BLOCK_2_SIZE; - priv->memory_pool[curr + i].free = 1; - payload_addr += priv->memory_pool[curr + i].size; - } - - curr = i; - for (i = 0; i < BLOCK_3_NUM; i++) { - priv->memory_pool[curr + i].start = payload_addr; - priv->memory_pool[curr + i].size = BLOCK_3_SIZE; - priv->memory_pool[curr + i].free = 1; - payload_addr += priv->memory_pool[curr + i].size; - } - - WARN_ON(payload_addr - priv->memory_pool[0].start > PAYLOAD_AREA_SIZE); -} - -static void alloc_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int i; - - WARN_ON(qtd->payload_addr); - - if (!qtd->length) - return; - - for (i = 0; i < BLOCKS; i++) { - if (priv->memory_pool[i].size >= qtd->length && - priv->memory_pool[i].free) { - priv->memory_pool[i].free = 0; - qtd->payload_addr = priv->memory_pool[i].start; - return; - } - } -} - -static void free_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int i; - - if (!qtd->payload_addr) - return; - - for (i = 0; i < BLOCKS; i++) { - if (priv->memory_pool[i].start == qtd->payload_addr) { - WARN_ON(priv->memory_pool[i].free); - priv->memory_pool[i].free = 1; - qtd->payload_addr = 0; - return; - } - } - - dev_err(hcd->self.controller, "%s: Invalid pointer: %08x\n", - __func__, qtd->payload_addr); - WARN_ON(1); - qtd->payload_addr = 0; -} - -static int handshake(struct usb_hcd *hcd, u32 reg, - u32 mask, u32 done, int usec) -{ - u32 result; - - do { - result = reg_read32(hcd->regs, reg); - if (result == ~0) - return -ENODEV; - result &= mask; - if (result == done) - return 0; - udelay(1); - usec--; - } while (usec > 0); - return -ETIMEDOUT; -} - -/* reset a non-running (STS_HALT == 1) controller */ -static int ehci_reset(struct usb_hcd *hcd) -{ - int retval; - struct isp1760_hcd *priv = hcd_to_priv(hcd); - - u32 command = reg_read32(hcd->regs, HC_USBCMD); - - command |= CMD_RESET; - reg_write32(hcd->regs, HC_USBCMD, command); - hcd->state = HC_STATE_HALT; - priv->next_statechange = jiffies; - retval = handshake(hcd, HC_USBCMD, - CMD_RESET, 0, 250 * 1000); - return retval; -} - -static struct isp1760_qh *qh_alloc(gfp_t flags) -{ - struct isp1760_qh *qh; - - qh = kmem_cache_zalloc(qh_cachep, flags); - if (!qh) - return NULL; - - INIT_LIST_HEAD(&qh->qh_list); - INIT_LIST_HEAD(&qh->qtd_list); - qh->slot = -1; - - return qh; -} - -static void qh_free(struct isp1760_qh *qh) -{ - WARN_ON(!list_empty(&qh->qtd_list)); - WARN_ON(qh->slot > -1); - kmem_cache_free(qh_cachep, qh); -} - -/* one-time init, only for memory state */ -static int priv_init(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 hcc_params; - int i; - - spin_lock_init(&priv->lock); - - for (i = 0; i < QH_END; i++) - INIT_LIST_HEAD(&priv->qh_list[i]); - - /* - * hw default: 1K periodic list heads, one per frame. - * periodic_size can shrink by USBCMD update if hcc_params allows. - */ - priv->periodic_size = DEFAULT_I_TDPS; - - /* controllers may cache some of the periodic schedule ... */ - hcc_params = reg_read32(hcd->regs, HC_HCCPARAMS); - /* full frame cache */ - if (HCC_ISOC_CACHE(hcc_params)) - priv->i_thresh = 8; - else /* N microframes cached */ - priv->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); - - return 0; -} - -static int isp1760_hc_setup(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int result; - u32 scratch, hwmode; - - reg_write32(hcd->regs, HC_SCRATCH_REG, 0xdeadbabe); - /* Change bus pattern */ - scratch = reg_read32(hcd->regs, HC_CHIP_ID_REG); - scratch = reg_read32(hcd->regs, HC_SCRATCH_REG); - if (scratch != 0xdeadbabe) { - dev_err(hcd->self.controller, "Scratch test failed.\n"); - return -ENODEV; - } - - /* - * The RESET_HC bit in the SW_RESET register is supposed to reset the - * host controller without touching the CPU interface registers, but at - * least on the ISP1761 it seems to behave as the RESET_ALL bit and - * reset the whole device. We thus can't use it here, so let's reset - * the host controller through the EHCI USB Command register. The device - * has been reset in core code anyway, so this shouldn't matter. - */ - reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, 0); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); - reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); - - result = ehci_reset(hcd); - if (result) - return result; - - /* Step 11 passed */ - - /* ATL reset */ - hwmode = reg_read32(hcd->regs, HC_HW_MODE_CTRL) & ~ALL_ATX_RESET; - reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET); - mdelay(10); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); - - reg_write32(hcd->regs, HC_INTERRUPT_ENABLE, INTERRUPT_ENABLE_MASK); - - priv->hcs_params = reg_read32(hcd->regs, HC_HCSPARAMS); - - return priv_init(hcd); -} - -static u32 base_to_chip(u32 base) -{ - return ((base - 0x400) >> 3); -} - -static int last_qtd_of_urb(struct isp1760_qtd *qtd, struct isp1760_qh *qh) -{ - struct urb *urb; - - if (list_is_last(&qtd->qtd_list, &qh->qtd_list)) - return 1; - - urb = qtd->urb; - qtd = list_entry(qtd->qtd_list.next, typeof(*qtd), qtd_list); - return (qtd->urb != urb); -} - -/* magic numbers that can affect system performance */ -#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ -#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ -#define EHCI_TUNE_RL_TT 0 -#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ -#define EHCI_TUNE_MULT_TT 1 -#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ - -static void create_ptd_atl(struct isp1760_qh *qh, - struct isp1760_qtd *qtd, struct ptd *ptd) -{ - u32 maxpacket; - u32 multi; - u32 rl = RL_COUNTER; - u32 nak = NAK_COUNTER; - - memset(ptd, 0, sizeof(*ptd)); - - /* according to 3.6.2, max packet len can not be > 0x400 */ - maxpacket = usb_maxpacket(qtd->urb->dev, qtd->urb->pipe, - usb_pipeout(qtd->urb->pipe)); - multi = 1 + ((maxpacket >> 11) & 0x3); - maxpacket &= 0x7ff; - - /* DW0 */ - ptd->dw0 = DW0_VALID_BIT; - ptd->dw0 |= TO_DW0_LENGTH(qtd->length); - ptd->dw0 |= TO_DW0_MAXPACKET(maxpacket); - ptd->dw0 |= TO_DW0_ENDPOINT(usb_pipeendpoint(qtd->urb->pipe)); - - /* DW1 */ - ptd->dw1 = usb_pipeendpoint(qtd->urb->pipe) >> 1; - ptd->dw1 |= TO_DW1_DEVICE_ADDR(usb_pipedevice(qtd->urb->pipe)); - ptd->dw1 |= TO_DW1_PID_TOKEN(qtd->packet_type); - - if (usb_pipebulk(qtd->urb->pipe)) - ptd->dw1 |= DW1_TRANS_BULK; - else if (usb_pipeint(qtd->urb->pipe)) - ptd->dw1 |= DW1_TRANS_INT; - - if (qtd->urb->dev->speed != USB_SPEED_HIGH) { - /* split transaction */ - - ptd->dw1 |= DW1_TRANS_SPLIT; - if (qtd->urb->dev->speed == USB_SPEED_LOW) - ptd->dw1 |= DW1_SE_USB_LOSPEED; - - ptd->dw1 |= TO_DW1_PORT_NUM(qtd->urb->dev->ttport); - ptd->dw1 |= TO_DW1_HUB_NUM(qtd->urb->dev->tt->hub->devnum); - - /* SE bit for Split INT transfers */ - if (usb_pipeint(qtd->urb->pipe) && - (qtd->urb->dev->speed == USB_SPEED_LOW)) - ptd->dw1 |= 2 << 16; - - rl = 0; - nak = 0; - } else { - ptd->dw0 |= TO_DW0_MULTI(multi); - if (usb_pipecontrol(qtd->urb->pipe) || - usb_pipebulk(qtd->urb->pipe)) - ptd->dw3 |= TO_DW3_PING(qh->ping); - } - /* DW2 */ - ptd->dw2 = 0; - ptd->dw2 |= TO_DW2_DATA_START_ADDR(base_to_chip(qtd->payload_addr)); - ptd->dw2 |= TO_DW2_RL(rl); - - /* DW3 */ - ptd->dw3 |= TO_DW3_NAKCOUNT(nak); - ptd->dw3 |= TO_DW3_DATA_TOGGLE(qh->toggle); - if (usb_pipecontrol(qtd->urb->pipe)) { - if (qtd->data_buffer == qtd->urb->setup_packet) - ptd->dw3 &= ~TO_DW3_DATA_TOGGLE(1); - else if (last_qtd_of_urb(qtd, qh)) - ptd->dw3 |= TO_DW3_DATA_TOGGLE(1); - } - - ptd->dw3 |= DW3_ACTIVE_BIT; - /* Cerr */ - ptd->dw3 |= TO_DW3_CERR(ERR_COUNTER); -} - -static void transform_add_int(struct isp1760_qh *qh, - struct isp1760_qtd *qtd, struct ptd *ptd) -{ - u32 usof; - u32 period; - - /* - * Most of this is guessing. ISP1761 datasheet is quite unclear, and - * the algorithm from the original Philips driver code, which was - * pretty much used in this driver before as well, is quite horrendous - * and, i believe, incorrect. The code below follows the datasheet and - * USB2.0 spec as far as I can tell, and plug/unplug seems to be much - * more reliable this way (fingers crossed...). - */ - - if (qtd->urb->dev->speed == USB_SPEED_HIGH) { - /* urb->interval is in units of microframes (1/8 ms) */ - period = qtd->urb->interval >> 3; - - if (qtd->urb->interval > 4) - usof = 0x01; /* One bit set => - interval 1 ms * uFrame-match */ - else if (qtd->urb->interval > 2) - usof = 0x22; /* Two bits set => interval 1/2 ms */ - else if (qtd->urb->interval > 1) - usof = 0x55; /* Four bits set => interval 1/4 ms */ - else - usof = 0xff; /* All bits set => interval 1/8 ms */ - } else { - /* urb->interval is in units of frames (1 ms) */ - period = qtd->urb->interval; - usof = 0x0f; /* Execute Start Split on any of the - four first uFrames */ - - /* - * First 8 bits in dw5 is uSCS and "specifies which uSOF the - * complete split needs to be sent. Valid only for IN." Also, - * "All bits can be set to one for every transfer." (p 82, - * ISP1761 data sheet.) 0x1c is from Philips driver. Where did - * that number come from? 0xff seems to work fine... - */ - /* ptd->dw5 = 0x1c; */ - ptd->dw5 = 0xff; /* Execute Complete Split on any uFrame */ - } - - period = period >> 1;/* Ensure equal or shorter period than requested */ - period &= 0xf8; /* Mask off too large values and lowest unused 3 bits */ - - ptd->dw2 |= period; - ptd->dw4 = usof; -} - -static void create_ptd_int(struct isp1760_qh *qh, - struct isp1760_qtd *qtd, struct ptd *ptd) -{ - create_ptd_atl(qh, qtd, ptd); - transform_add_int(qh, qtd, ptd); -} - -static void isp1760_urb_done(struct usb_hcd *hcd, struct urb *urb) -__releases(priv->lock) -__acquires(priv->lock) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - - if (!urb->unlinked) { - if (urb->status == -EINPROGRESS) - urb->status = 0; - } - - if (usb_pipein(urb->pipe) && usb_pipetype(urb->pipe) != PIPE_CONTROL) { - void *ptr; - for (ptr = urb->transfer_buffer; - ptr < urb->transfer_buffer + urb->transfer_buffer_length; - ptr += PAGE_SIZE) - flush_dcache_page(virt_to_page(ptr)); - } - - /* complete() can reenter this HCD */ - usb_hcd_unlink_urb_from_ep(hcd, urb); - spin_unlock(&priv->lock); - usb_hcd_giveback_urb(hcd, urb, urb->status); - spin_lock(&priv->lock); -} - -static struct isp1760_qtd *qtd_alloc(gfp_t flags, struct urb *urb, - u8 packet_type) -{ - struct isp1760_qtd *qtd; - - qtd = kmem_cache_zalloc(qtd_cachep, flags); - if (!qtd) - return NULL; - - INIT_LIST_HEAD(&qtd->qtd_list); - qtd->urb = urb; - qtd->packet_type = packet_type; - qtd->status = QTD_ENQUEUED; - qtd->actual_length = 0; - - return qtd; -} - -static void qtd_free(struct isp1760_qtd *qtd) -{ - WARN_ON(qtd->payload_addr); - kmem_cache_free(qtd_cachep, qtd); -} - -static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, - struct isp1760_slotinfo *slots, - struct isp1760_qtd *qtd, struct isp1760_qh *qh, - struct ptd *ptd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int skip_map; - - WARN_ON((slot < 0) || (slot > 31)); - WARN_ON(qtd->length && !qtd->payload_addr); - WARN_ON(slots[slot].qtd); - WARN_ON(slots[slot].qh); - WARN_ON(qtd->status != QTD_PAYLOAD_ALLOC); - - /* Make sure done map has not triggered from some unlinked transfer */ - if (ptd_offset == ATL_PTD_OFFSET) { - priv->atl_done_map |= reg_read32(hcd->regs, - HC_ATL_PTD_DONEMAP_REG); - priv->atl_done_map &= ~(1 << slot); - } else { - priv->int_done_map |= reg_read32(hcd->regs, - HC_INT_PTD_DONEMAP_REG); - priv->int_done_map &= ~(1 << slot); - } - - qh->slot = slot; - qtd->status = QTD_XFER_STARTED; - slots[slot].timestamp = jiffies; - slots[slot].qtd = qtd; - slots[slot].qh = qh; - ptd_write(hcd->regs, ptd_offset, slot, ptd); - - if (ptd_offset == ATL_PTD_OFFSET) { - skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); - skip_map &= ~(1 << qh->slot); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); - } else { - skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); - skip_map &= ~(1 << qh->slot); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); - } -} - -static int is_short_bulk(struct isp1760_qtd *qtd) -{ - return (usb_pipebulk(qtd->urb->pipe) && - (qtd->actual_length < qtd->length)); -} - -static void collect_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh, - struct list_head *urb_list) -{ - int last_qtd; - struct isp1760_qtd *qtd, *qtd_next; - struct urb_listitem *urb_listitem; - - list_for_each_entry_safe(qtd, qtd_next, &qh->qtd_list, qtd_list) { - if (qtd->status < QTD_XFER_COMPLETE) - break; - - last_qtd = last_qtd_of_urb(qtd, qh); - - if ((!last_qtd) && (qtd->status == QTD_RETIRE)) - qtd_next->status = QTD_RETIRE; - - if (qtd->status == QTD_XFER_COMPLETE) { - if (qtd->actual_length) { - switch (qtd->packet_type) { - case IN_PID: - mem_reads8(hcd->regs, qtd->payload_addr, - qtd->data_buffer, - qtd->actual_length); - /* Fall through (?) */ - case OUT_PID: - qtd->urb->actual_length += - qtd->actual_length; - /* Fall through ... */ - case SETUP_PID: - break; - } - } - - if (is_short_bulk(qtd)) { - if (qtd->urb->transfer_flags & URB_SHORT_NOT_OK) - qtd->urb->status = -EREMOTEIO; - if (!last_qtd) - qtd_next->status = QTD_RETIRE; - } - } - - if (qtd->payload_addr) - free_mem(hcd, qtd); - - if (last_qtd) { - if ((qtd->status == QTD_RETIRE) && - (qtd->urb->status == -EINPROGRESS)) - qtd->urb->status = -EPIPE; - /* Defer calling of urb_done() since it releases lock */ - urb_listitem = kmem_cache_zalloc(urb_listitem_cachep, - GFP_ATOMIC); - if (unlikely(!urb_listitem)) - break; /* Try again on next call */ - urb_listitem->urb = qtd->urb; - list_add_tail(&urb_listitem->urb_list, urb_list); - } - - list_del(&qtd->qtd_list); - qtd_free(qtd); - } -} - -#define ENQUEUE_DEPTH 2 -static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int ptd_offset; - struct isp1760_slotinfo *slots; - int curr_slot, free_slot; - int n; - struct ptd ptd; - struct isp1760_qtd *qtd; - - if (unlikely(list_empty(&qh->qtd_list))) { - WARN_ON(1); - return; - } - - /* Make sure this endpoint's TT buffer is clean before queueing ptds */ - if (qh->tt_buffer_dirty) - return; - - if (usb_pipeint(list_entry(qh->qtd_list.next, struct isp1760_qtd, - qtd_list)->urb->pipe)) { - ptd_offset = INT_PTD_OFFSET; - slots = priv->int_slots; - } else { - ptd_offset = ATL_PTD_OFFSET; - slots = priv->atl_slots; - } - - free_slot = -1; - for (curr_slot = 0; curr_slot < 32; curr_slot++) { - if ((free_slot == -1) && (slots[curr_slot].qtd == NULL)) - free_slot = curr_slot; - if (slots[curr_slot].qh == qh) - break; - } - - n = 0; - list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { - if (qtd->status == QTD_ENQUEUED) { - WARN_ON(qtd->payload_addr); - alloc_mem(hcd, qtd); - if ((qtd->length) && (!qtd->payload_addr)) - break; - - if ((qtd->length) && - ((qtd->packet_type == SETUP_PID) || - (qtd->packet_type == OUT_PID))) { - mem_writes8(hcd->regs, qtd->payload_addr, - qtd->data_buffer, qtd->length); - } - - qtd->status = QTD_PAYLOAD_ALLOC; - } - - if (qtd->status == QTD_PAYLOAD_ALLOC) { -/* - if ((curr_slot > 31) && (free_slot == -1)) - dev_dbg(hcd->self.controller, "%s: No slot " - "available for transfer\n", __func__); -*/ - /* Start xfer for this endpoint if not already done */ - if ((curr_slot > 31) && (free_slot > -1)) { - if (usb_pipeint(qtd->urb->pipe)) - create_ptd_int(qh, qtd, &ptd); - else - create_ptd_atl(qh, qtd, &ptd); - - start_bus_transfer(hcd, ptd_offset, free_slot, - slots, qtd, qh, &ptd); - curr_slot = free_slot; - } - - n++; - if (n >= ENQUEUE_DEPTH) - break; - } - } -} - -static void schedule_ptds(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv; - struct isp1760_qh *qh, *qh_next; - struct list_head *ep_queue; - LIST_HEAD(urb_list); - struct urb_listitem *urb_listitem, *urb_listitem_next; - int i; - - if (!hcd) { - WARN_ON(1); - return; - } - - priv = hcd_to_priv(hcd); - - /* - * check finished/retired xfers, transfer payloads, call urb_done() - */ - for (i = 0; i < QH_END; i++) { - ep_queue = &priv->qh_list[i]; - list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) { - collect_qtds(hcd, qh, &urb_list); - if (list_empty(&qh->qtd_list)) - list_del(&qh->qh_list); - } - } - - list_for_each_entry_safe(urb_listitem, urb_listitem_next, &urb_list, - urb_list) { - isp1760_urb_done(hcd, urb_listitem->urb); - kmem_cache_free(urb_listitem_cachep, urb_listitem); - } - - /* - * Schedule packets for transfer. - * - * According to USB2.0 specification: - * - * 1st prio: interrupt xfers, up to 80 % of bandwidth - * 2nd prio: control xfers - * 3rd prio: bulk xfers - * - * ... but let's use a simpler scheme here (mostly because ISP1761 doc - * is very unclear on how to prioritize traffic): - * - * 1) Enqueue any queued control transfers, as long as payload chip mem - * and PTD ATL slots are available. - * 2) Enqueue any queued INT transfers, as long as payload chip mem - * and PTD INT slots are available. - * 3) Enqueue any queued bulk transfers, as long as payload chip mem - * and PTD ATL slots are available. - * - * Use double buffering (ENQUEUE_DEPTH==2) as a compromise between - * conservation of chip mem and performance. - * - * I'm sure this scheme could be improved upon! - */ - for (i = 0; i < QH_END; i++) { - ep_queue = &priv->qh_list[i]; - list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) - enqueue_qtds(hcd, qh); - } -} - -#define PTD_STATE_QTD_DONE 1 -#define PTD_STATE_QTD_RELOAD 2 -#define PTD_STATE_URB_RETIRE 3 - -static int check_int_transfer(struct usb_hcd *hcd, struct ptd *ptd, - struct urb *urb) -{ - __dw dw4; - int i; - - dw4 = ptd->dw4; - dw4 >>= 8; - - /* FIXME: ISP1761 datasheet does not say what to do with these. Do we - need to handle these errors? Is it done in hardware? */ - - if (ptd->dw3 & DW3_HALT_BIT) { - - urb->status = -EPROTO; /* Default unknown error */ - - for (i = 0; i < 8; i++) { - switch (dw4 & 0x7) { - case INT_UNDERRUN: - dev_dbg(hcd->self.controller, "%s: underrun " - "during uFrame %d\n", - __func__, i); - urb->status = -ECOMM; /* Could not write data */ - break; - case INT_EXACT: - dev_dbg(hcd->self.controller, "%s: transaction " - "error during uFrame %d\n", - __func__, i); - urb->status = -EPROTO; /* timeout, bad CRC, PID - error etc. */ - break; - case INT_BABBLE: - dev_dbg(hcd->self.controller, "%s: babble " - "error during uFrame %d\n", - __func__, i); - urb->status = -EOVERFLOW; - break; - } - dw4 >>= 3; - } - - return PTD_STATE_URB_RETIRE; - } - - return PTD_STATE_QTD_DONE; -} - -static int check_atl_transfer(struct usb_hcd *hcd, struct ptd *ptd, - struct urb *urb) -{ - WARN_ON(!ptd); - if (ptd->dw3 & DW3_HALT_BIT) { - if (ptd->dw3 & DW3_BABBLE_BIT) - urb->status = -EOVERFLOW; - else if (FROM_DW3_CERR(ptd->dw3)) - urb->status = -EPIPE; /* Stall */ - else if (ptd->dw3 & DW3_ERROR_BIT) - urb->status = -EPROTO; /* XactErr */ - else - urb->status = -EPROTO; /* Unknown */ -/* - dev_dbg(hcd->self.controller, "%s: ptd error:\n" - " dw0: %08x dw1: %08x dw2: %08x dw3: %08x\n" - " dw4: %08x dw5: %08x dw6: %08x dw7: %08x\n", - __func__, - ptd->dw0, ptd->dw1, ptd->dw2, ptd->dw3, - ptd->dw4, ptd->dw5, ptd->dw6, ptd->dw7); -*/ - return PTD_STATE_URB_RETIRE; - } - - if ((ptd->dw3 & DW3_ERROR_BIT) && (ptd->dw3 & DW3_ACTIVE_BIT)) { - /* Transfer Error, *but* active and no HALT -> reload */ - dev_dbg(hcd->self.controller, "PID error; reloading ptd\n"); - return PTD_STATE_QTD_RELOAD; - } - - if (!FROM_DW3_NAKCOUNT(ptd->dw3) && (ptd->dw3 & DW3_ACTIVE_BIT)) { - /* - * NAKs are handled in HW by the chip. Usually if the - * device is not able to send data fast enough. - * This happens mostly on slower hardware. - */ - return PTD_STATE_QTD_RELOAD; - } - - return PTD_STATE_QTD_DONE; -} - -static void handle_done_ptds(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - struct ptd ptd; - struct isp1760_qh *qh; - int slot; - int state; - struct isp1760_slotinfo *slots; - u32 ptd_offset; - struct isp1760_qtd *qtd; - int modified; - int skip_map; - - skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); - priv->int_done_map &= ~skip_map; - skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); - priv->atl_done_map &= ~skip_map; - - modified = priv->int_done_map || priv->atl_done_map; - - while (priv->int_done_map || priv->atl_done_map) { - if (priv->int_done_map) { - /* INT ptd */ - slot = __ffs(priv->int_done_map); - priv->int_done_map &= ~(1 << slot); - slots = priv->int_slots; - /* This should not trigger, and could be removed if - noone have any problems with it triggering: */ - if (!slots[slot].qh) { - WARN_ON(1); - continue; - } - ptd_offset = INT_PTD_OFFSET; - ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd); - state = check_int_transfer(hcd, &ptd, - slots[slot].qtd->urb); - } else { - /* ATL ptd */ - slot = __ffs(priv->atl_done_map); - priv->atl_done_map &= ~(1 << slot); - slots = priv->atl_slots; - /* This should not trigger, and could be removed if - noone have any problems with it triggering: */ - if (!slots[slot].qh) { - WARN_ON(1); - continue; - } - ptd_offset = ATL_PTD_OFFSET; - ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); - state = check_atl_transfer(hcd, &ptd, - slots[slot].qtd->urb); - } - - qtd = slots[slot].qtd; - slots[slot].qtd = NULL; - qh = slots[slot].qh; - slots[slot].qh = NULL; - qh->slot = -1; - - WARN_ON(qtd->status != QTD_XFER_STARTED); - - switch (state) { - case PTD_STATE_QTD_DONE: - if ((usb_pipeint(qtd->urb->pipe)) && - (qtd->urb->dev->speed != USB_SPEED_HIGH)) - qtd->actual_length = - FROM_DW3_SCS_NRBYTESTRANSFERRED(ptd.dw3); - else - qtd->actual_length = - FROM_DW3_NRBYTESTRANSFERRED(ptd.dw3); - - qtd->status = QTD_XFER_COMPLETE; - if (list_is_last(&qtd->qtd_list, &qh->qtd_list) || - is_short_bulk(qtd)) - qtd = NULL; - else - qtd = list_entry(qtd->qtd_list.next, - typeof(*qtd), qtd_list); - - qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); - qh->ping = FROM_DW3_PING(ptd.dw3); - break; - - case PTD_STATE_QTD_RELOAD: /* QTD_RETRY, for atls only */ - qtd->status = QTD_PAYLOAD_ALLOC; - ptd.dw0 |= DW0_VALID_BIT; - /* RL counter = ERR counter */ - ptd.dw3 &= ~TO_DW3_NAKCOUNT(0xf); - ptd.dw3 |= TO_DW3_NAKCOUNT(FROM_DW2_RL(ptd.dw2)); - ptd.dw3 &= ~TO_DW3_CERR(3); - ptd.dw3 |= TO_DW3_CERR(ERR_COUNTER); - qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); - qh->ping = FROM_DW3_PING(ptd.dw3); - break; - - case PTD_STATE_URB_RETIRE: - qtd->status = QTD_RETIRE; - if ((qtd->urb->dev->speed != USB_SPEED_HIGH) && - (qtd->urb->status != -EPIPE) && - (qtd->urb->status != -EREMOTEIO)) { - qh->tt_buffer_dirty = 1; - if (usb_hub_clear_tt_buffer(qtd->urb)) - /* Clear failed; let's hope things work - anyway */ - qh->tt_buffer_dirty = 0; - } - qtd = NULL; - qh->toggle = 0; - qh->ping = 0; - break; - - default: - WARN_ON(1); - continue; - } - - if (qtd && (qtd->status == QTD_PAYLOAD_ALLOC)) { - if (slots == priv->int_slots) { - if (state == PTD_STATE_QTD_RELOAD) - dev_err(hcd->self.controller, - "%s: PTD_STATE_QTD_RELOAD on " - "interrupt packet\n", __func__); - if (state != PTD_STATE_QTD_RELOAD) - create_ptd_int(qh, qtd, &ptd); - } else { - if (state != PTD_STATE_QTD_RELOAD) - create_ptd_atl(qh, qtd, &ptd); - } - - start_bus_transfer(hcd, ptd_offset, slot, slots, qtd, - qh, &ptd); - } - } - - if (modified) - schedule_ptds(hcd); -} - -static irqreturn_t isp1760_irq(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 imask; - irqreturn_t irqret = IRQ_NONE; - - spin_lock(&priv->lock); - - if (!(hcd->state & HC_STATE_RUNNING)) - goto leave; - - imask = reg_read32(hcd->regs, HC_INTERRUPT_REG); - if (unlikely(!imask)) - goto leave; - reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */ - - priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); - priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); - - handle_done_ptds(hcd); - - irqret = IRQ_HANDLED; -leave: - spin_unlock(&priv->lock); - - return irqret; -} - -/* - * Workaround for problem described in chip errata 2: - * - * Sometimes interrupts are not generated when ATL (not INT?) completion occurs. - * One solution suggested in the errata is to use SOF interrupts _instead_of_ - * ATL done interrupts (the "instead of" might be important since it seems - * enabling ATL interrupts also causes the chip to sometimes - rarely - "forget" - * to set the PTD's done bit in addition to not generating an interrupt!). - * - * So if we use SOF + ATL interrupts, we sometimes get stale PTDs since their - * done bit is not being set. This is bad - it blocks the endpoint until reboot. - * - * If we use SOF interrupts only, we get latency between ptd completion and the - * actual handling. This is very noticeable in testusb runs which takes several - * minutes longer without ATL interrupts. - * - * A better solution is to run the code below every SLOT_CHECK_PERIOD ms. If it - * finds active ATL slots which are older than SLOT_TIMEOUT ms, it checks the - * slot's ACTIVE and VALID bits. If these are not set, the ptd is considered - * completed and its done map bit is set. - * - * The values of SLOT_TIMEOUT and SLOT_CHECK_PERIOD have been arbitrarily chosen - * not to cause too much lag when this HW bug occurs, while still hopefully - * ensuring that the check does not falsely trigger. - */ -#define SLOT_TIMEOUT 300 -#define SLOT_CHECK_PERIOD 200 -static struct timer_list errata2_timer; - -static void errata2_function(unsigned long data) -{ - struct usb_hcd *hcd = (struct usb_hcd *) data; - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int slot; - struct ptd ptd; - unsigned long spinflags; - - spin_lock_irqsave(&priv->lock, spinflags); - - for (slot = 0; slot < 32; slot++) - if (priv->atl_slots[slot].qh && time_after(jiffies, - priv->atl_slots[slot].timestamp + - SLOT_TIMEOUT * HZ / 1000)) { - ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); - if (!FROM_DW0_VALID(ptd.dw0) && - !FROM_DW3_ACTIVE(ptd.dw3)) - priv->atl_done_map |= 1 << slot; - } - - if (priv->atl_done_map) - handle_done_ptds(hcd); - - spin_unlock_irqrestore(&priv->lock, spinflags); - - errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; - add_timer(&errata2_timer); -} - -static int isp1760_run(struct usb_hcd *hcd) -{ - int retval; - u32 temp; - u32 command; - u32 chipid; - - hcd->uses_new_polling = 1; - - hcd->state = HC_STATE_RUNNING; - - /* Set PTD interrupt AND & OR maps */ - reg_write32(hcd->regs, HC_ATL_IRQ_MASK_AND_REG, 0); - reg_write32(hcd->regs, HC_ATL_IRQ_MASK_OR_REG, 0xffffffff); - reg_write32(hcd->regs, HC_INT_IRQ_MASK_AND_REG, 0); - reg_write32(hcd->regs, HC_INT_IRQ_MASK_OR_REG, 0xffffffff); - reg_write32(hcd->regs, HC_ISO_IRQ_MASK_AND_REG, 0); - reg_write32(hcd->regs, HC_ISO_IRQ_MASK_OR_REG, 0xffffffff); - /* step 23 passed */ - - temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp | HW_GLOBAL_INTR_EN); - - command = reg_read32(hcd->regs, HC_USBCMD); - command &= ~(CMD_LRESET|CMD_RESET); - command |= CMD_RUN; - reg_write32(hcd->regs, HC_USBCMD, command); - - retval = handshake(hcd, HC_USBCMD, CMD_RUN, CMD_RUN, 250 * 1000); - if (retval) - return retval; - - /* - * XXX - * Spec says to write FLAG_CF as last config action, priv code grabs - * the semaphore while doing so. - */ - down_write(&ehci_cf_port_reset_rwsem); - reg_write32(hcd->regs, HC_CONFIGFLAG, FLAG_CF); - - retval = handshake(hcd, HC_CONFIGFLAG, FLAG_CF, FLAG_CF, 250 * 1000); - up_write(&ehci_cf_port_reset_rwsem); - if (retval) - return retval; - - init_timer(&errata2_timer); - errata2_timer.function = errata2_function; - errata2_timer.data = (unsigned long) hcd; - errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; - add_timer(&errata2_timer); - - chipid = reg_read32(hcd->regs, HC_CHIP_ID_REG); - dev_info(hcd->self.controller, "USB ISP %04x HW rev. %d started\n", - chipid & 0xffff, chipid >> 16); - - /* PTD Register Init Part 2, Step 28 */ - - /* Setup registers controlling PTD checking */ - reg_write32(hcd->regs, HC_ATL_PTD_LASTPTD_REG, 0x80000000); - reg_write32(hcd->regs, HC_INT_PTD_LASTPTD_REG, 0x80000000); - reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0xffffffff); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0xffffffff); - reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0xffffffff); - reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, - ATL_BUF_FILL | INT_BUF_FILL); - - /* GRR this is run-once init(), being done every time the HC starts. - * So long as they're part of class devices, we can't do it init() - * since the class device isn't created that early. - */ - return 0; -} - -static int qtd_fill(struct isp1760_qtd *qtd, void *databuffer, size_t len) -{ - qtd->data_buffer = databuffer; - - if (len > MAX_PAYLOAD_SIZE) - len = MAX_PAYLOAD_SIZE; - qtd->length = len; - - return qtd->length; -} - -static void qtd_list_free(struct list_head *qtd_list) -{ - struct isp1760_qtd *qtd, *qtd_next; - - list_for_each_entry_safe(qtd, qtd_next, qtd_list, qtd_list) { - list_del(&qtd->qtd_list); - qtd_free(qtd); - } -} - -/* - * Packetize urb->transfer_buffer into list of packets of size wMaxPacketSize. - * Also calculate the PID type (SETUP/IN/OUT) for each packet. - */ -#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) -static void packetize_urb(struct usb_hcd *hcd, - struct urb *urb, struct list_head *head, gfp_t flags) -{ - struct isp1760_qtd *qtd; - void *buf; - int len, maxpacketsize; - u8 packet_type; - - /* - * URBs map to sequences of QTDs: one logical transaction - */ - - if (!urb->transfer_buffer && urb->transfer_buffer_length) { - /* XXX This looks like usb storage / SCSI bug */ - dev_err(hcd->self.controller, - "buf is null, dma is %08lx len is %d\n", - (long unsigned)urb->transfer_dma, - urb->transfer_buffer_length); - WARN_ON(1); - } - - if (usb_pipein(urb->pipe)) - packet_type = IN_PID; - else - packet_type = OUT_PID; - - if (usb_pipecontrol(urb->pipe)) { - qtd = qtd_alloc(flags, urb, SETUP_PID); - if (!qtd) - goto cleanup; - qtd_fill(qtd, urb->setup_packet, sizeof(struct usb_ctrlrequest)); - list_add_tail(&qtd->qtd_list, head); - - /* for zero length DATA stages, STATUS is always IN */ - if (urb->transfer_buffer_length == 0) - packet_type = IN_PID; - } - - maxpacketsize = max_packet(usb_maxpacket(urb->dev, urb->pipe, - usb_pipeout(urb->pipe))); - - /* - * buffer gets wrapped in one or more qtds; - * last one may be "short" (including zero len) - * and may serve as a control status ack - */ - buf = urb->transfer_buffer; - len = urb->transfer_buffer_length; - - for (;;) { - int this_qtd_len; - - qtd = qtd_alloc(flags, urb, packet_type); - if (!qtd) - goto cleanup; - this_qtd_len = qtd_fill(qtd, buf, len); - list_add_tail(&qtd->qtd_list, head); - - len -= this_qtd_len; - buf += this_qtd_len; - - if (len <= 0) - break; - } - - /* - * control requests may need a terminating data "status" ack; - * bulk ones may need a terminating short packet (zero length). - */ - if (urb->transfer_buffer_length != 0) { - int one_more = 0; - - if (usb_pipecontrol(urb->pipe)) { - one_more = 1; - if (packet_type == IN_PID) - packet_type = OUT_PID; - else - packet_type = IN_PID; - } else if (usb_pipebulk(urb->pipe) - && (urb->transfer_flags & URB_ZERO_PACKET) - && !(urb->transfer_buffer_length % - maxpacketsize)) { - one_more = 1; - } - if (one_more) { - qtd = qtd_alloc(flags, urb, packet_type); - if (!qtd) - goto cleanup; - - /* never any data in such packets */ - qtd_fill(qtd, NULL, 0); - list_add_tail(&qtd->qtd_list, head); - } - } - - return; - -cleanup: - qtd_list_free(head); -} - -static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - struct list_head *ep_queue; - struct isp1760_qh *qh, *qhit; - unsigned long spinflags; - LIST_HEAD(new_qtds); - int retval; - int qh_in_queue; - - switch (usb_pipetype(urb->pipe)) { - case PIPE_CONTROL: - ep_queue = &priv->qh_list[QH_CONTROL]; - break; - case PIPE_BULK: - ep_queue = &priv->qh_list[QH_BULK]; - break; - case PIPE_INTERRUPT: - if (urb->interval < 0) - return -EINVAL; - /* FIXME: Check bandwidth */ - ep_queue = &priv->qh_list[QH_INTERRUPT]; - break; - case PIPE_ISOCHRONOUS: - dev_err(hcd->self.controller, "%s: isochronous USB packets " - "not yet supported\n", - __func__); - return -EPIPE; - default: - dev_err(hcd->self.controller, "%s: unknown pipe type\n", - __func__); - return -EPIPE; - } - - if (usb_pipein(urb->pipe)) - urb->actual_length = 0; - - packetize_urb(hcd, urb, &new_qtds, mem_flags); - if (list_empty(&new_qtds)) - return -ENOMEM; - - retval = 0; - spin_lock_irqsave(&priv->lock, spinflags); - - if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { - retval = -ESHUTDOWN; - qtd_list_free(&new_qtds); - goto out; - } - retval = usb_hcd_link_urb_to_ep(hcd, urb); - if (retval) { - qtd_list_free(&new_qtds); - goto out; - } - - qh = urb->ep->hcpriv; - if (qh) { - qh_in_queue = 0; - list_for_each_entry(qhit, ep_queue, qh_list) { - if (qhit == qh) { - qh_in_queue = 1; - break; - } - } - if (!qh_in_queue) - list_add_tail(&qh->qh_list, ep_queue); - } else { - qh = qh_alloc(GFP_ATOMIC); - if (!qh) { - retval = -ENOMEM; - usb_hcd_unlink_urb_from_ep(hcd, urb); - qtd_list_free(&new_qtds); - goto out; - } - list_add_tail(&qh->qh_list, ep_queue); - urb->ep->hcpriv = qh; - } - - list_splice_tail(&new_qtds, &qh->qtd_list); - schedule_ptds(hcd); - -out: - spin_unlock_irqrestore(&priv->lock, spinflags); - return retval; -} - -static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, - struct isp1760_qh *qh) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int skip_map; - - WARN_ON(qh->slot == -1); - - /* We need to forcefully reclaim the slot since some transfers never - return, e.g. interrupt transfers and NAKed bulk transfers. */ - if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe)) { - skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); - skip_map |= (1 << qh->slot); - reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); - priv->atl_slots[qh->slot].qh = NULL; - priv->atl_slots[qh->slot].qtd = NULL; - } else { - skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); - skip_map |= (1 << qh->slot); - reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); - priv->int_slots[qh->slot].qh = NULL; - priv->int_slots[qh->slot].qtd = NULL; - } - - qh->slot = -1; -} - -/* - * Retire the qtds beginning at 'qtd' and belonging all to the same urb, killing - * any active transfer belonging to the urb in the process. - */ -static void dequeue_urb_from_qtd(struct usb_hcd *hcd, struct isp1760_qh *qh, - struct isp1760_qtd *qtd) -{ - struct urb *urb; - int urb_was_running; - - urb = qtd->urb; - urb_was_running = 0; - list_for_each_entry_from(qtd, &qh->qtd_list, qtd_list) { - if (qtd->urb != urb) - break; - - if (qtd->status >= QTD_XFER_STARTED) - urb_was_running = 1; - if (last_qtd_of_urb(qtd, qh) && - (qtd->status >= QTD_XFER_COMPLETE)) - urb_was_running = 0; - - if (qtd->status == QTD_XFER_STARTED) - kill_transfer(hcd, urb, qh); - qtd->status = QTD_RETIRE; - } - - if ((urb->dev->speed != USB_SPEED_HIGH) && urb_was_running) { - qh->tt_buffer_dirty = 1; - if (usb_hub_clear_tt_buffer(urb)) - /* Clear failed; let's hope things work anyway */ - qh->tt_buffer_dirty = 0; - } -} - -static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, - int status) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - unsigned long spinflags; - struct isp1760_qh *qh; - struct isp1760_qtd *qtd; - int retval = 0; - - spin_lock_irqsave(&priv->lock, spinflags); - retval = usb_hcd_check_unlink_urb(hcd, urb, status); - if (retval) - goto out; - - qh = urb->ep->hcpriv; - if (!qh) { - retval = -EINVAL; - goto out; - } - - list_for_each_entry(qtd, &qh->qtd_list, qtd_list) - if (qtd->urb == urb) { - dequeue_urb_from_qtd(hcd, qh, qtd); - list_move(&qtd->qtd_list, &qh->qtd_list); - break; - } - - urb->status = status; - schedule_ptds(hcd); - -out: - spin_unlock_irqrestore(&priv->lock, spinflags); - return retval; -} - -static void isp1760_endpoint_disable(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - unsigned long spinflags; - struct isp1760_qh *qh, *qh_iter; - int i; - - spin_lock_irqsave(&priv->lock, spinflags); - - qh = ep->hcpriv; - if (!qh) - goto out; - - WARN_ON(!list_empty(&qh->qtd_list)); - - for (i = 0; i < QH_END; i++) - list_for_each_entry(qh_iter, &priv->qh_list[i], qh_list) - if (qh_iter == qh) { - list_del(&qh_iter->qh_list); - i = QH_END; - break; - } - qh_free(qh); - ep->hcpriv = NULL; - - schedule_ptds(hcd); - -out: - spin_unlock_irqrestore(&priv->lock, spinflags); -} - -static int isp1760_hub_status_data(struct usb_hcd *hcd, char *buf) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 temp, status = 0; - u32 mask; - int retval = 1; - unsigned long flags; - - /* if !PM, root hub timers won't get shut down ... */ - if (!HC_IS_RUNNING(hcd->state)) - return 0; - - /* init status to no-changes */ - buf[0] = 0; - mask = PORT_CSC; - - spin_lock_irqsave(&priv->lock, flags); - temp = reg_read32(hcd->regs, HC_PORTSC1); - - if (temp & PORT_OWNER) { - if (temp & PORT_CSC) { - temp &= ~PORT_CSC; - reg_write32(hcd->regs, HC_PORTSC1, temp); - goto done; - } - } - - /* - * Return status information even for ports with OWNER set. - * Otherwise hub_wq wouldn't see the disconnect event when a - * high-speed device is switched over to the companion - * controller by the user. - */ - - if ((temp & mask) != 0 - || ((temp & PORT_RESUME) != 0 - && time_after_eq(jiffies, - priv->reset_done))) { - buf [0] |= 1 << (0 + 1); - status = STS_PCD; - } - /* FIXME autosuspend idle root hubs */ -done: - spin_unlock_irqrestore(&priv->lock, flags); - return status ? retval : 0; -} - -static void isp1760_hub_descriptor(struct isp1760_hcd *priv, - struct usb_hub_descriptor *desc) -{ - int ports = HCS_N_PORTS(priv->hcs_params); - u16 temp; - - desc->bDescriptorType = 0x29; - /* priv 1.0, 2.3.9 says 20ms max */ - desc->bPwrOn2PwrGood = 10; - desc->bHubContrCurrent = 0; - - desc->bNbrPorts = ports; - temp = 1 + (ports / 8); - desc->bDescLength = 7 + 2 * temp; - - /* ports removable, and usb 1.0 legacy PortPwrCtrlMask */ - memset(&desc->u.hs.DeviceRemovable[0], 0, temp); - memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); - - /* per-port overcurrent reporting */ - temp = 0x0008; - if (HCS_PPC(priv->hcs_params)) - /* per-port power control */ - temp |= 0x0001; - else - /* no power switching */ - temp |= 0x0002; - desc->wHubCharacteristics = cpu_to_le16(temp); -} - -#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) - -static int check_reset_complete(struct usb_hcd *hcd, int index, - int port_status) -{ - if (!(port_status & PORT_CONNECT)) - return port_status; - - /* if reset finished and it's still not enabled -- handoff */ - if (!(port_status & PORT_PE)) { - - dev_info(hcd->self.controller, - "port %d full speed --> companion\n", - index + 1); - - port_status |= PORT_OWNER; - port_status &= ~PORT_RWC_BITS; - reg_write32(hcd->regs, HC_PORTSC1, port_status); - - } else - dev_info(hcd->self.controller, "port %d high speed\n", - index + 1); - - return port_status; -} - -static int isp1760_hub_control(struct usb_hcd *hcd, u16 typeReq, - u16 wValue, u16 wIndex, char *buf, u16 wLength) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - int ports = HCS_N_PORTS(priv->hcs_params); - u32 temp, status; - unsigned long flags; - int retval = 0; - unsigned selector; - - /* - * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. - * HCS_INDICATOR may say we can change LEDs to off/amber/green. - * (track current state ourselves) ... blink for diagnostics, - * power, "this is the one", etc. EHCI spec supports this. - */ - - spin_lock_irqsave(&priv->lock, flags); - switch (typeReq) { - case ClearHubFeature: - switch (wValue) { - case C_HUB_LOCAL_POWER: - case C_HUB_OVER_CURRENT: - /* no hub-wide feature/status flags */ - break; - default: - goto error; - } - break; - case ClearPortFeature: - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - temp = reg_read32(hcd->regs, HC_PORTSC1); - - /* - * Even if OWNER is set, so the port is owned by the - * companion controller, hub_wq needs to be able to clear - * the port-change status bits (especially - * USB_PORT_STAT_C_CONNECTION). - */ - - switch (wValue) { - case USB_PORT_FEAT_ENABLE: - reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_PE); - break; - case USB_PORT_FEAT_C_ENABLE: - /* XXX error? */ - break; - case USB_PORT_FEAT_SUSPEND: - if (temp & PORT_RESET) - goto error; - - if (temp & PORT_SUSPEND) { - if ((temp & PORT_PE) == 0) - goto error; - /* resume signaling for 20 msec */ - temp &= ~(PORT_RWC_BITS); - reg_write32(hcd->regs, HC_PORTSC1, - temp | PORT_RESUME); - priv->reset_done = jiffies + - msecs_to_jiffies(20); - } - break; - case USB_PORT_FEAT_C_SUSPEND: - /* we auto-clear this feature */ - break; - case USB_PORT_FEAT_POWER: - if (HCS_PPC(priv->hcs_params)) - reg_write32(hcd->regs, HC_PORTSC1, - temp & ~PORT_POWER); - break; - case USB_PORT_FEAT_C_CONNECTION: - reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_CSC); - break; - case USB_PORT_FEAT_C_OVER_CURRENT: - /* XXX error ?*/ - break; - case USB_PORT_FEAT_C_RESET: - /* GetPortStatus clears reset */ - break; - default: - goto error; - } - reg_read32(hcd->regs, HC_USBCMD); - break; - case GetHubDescriptor: - isp1760_hub_descriptor(priv, (struct usb_hub_descriptor *) - buf); - break; - case GetHubStatus: - /* no hub-wide feature/status flags */ - memset(buf, 0, 4); - break; - case GetPortStatus: - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - status = 0; - temp = reg_read32(hcd->regs, HC_PORTSC1); - - /* wPortChange bits */ - if (temp & PORT_CSC) - status |= USB_PORT_STAT_C_CONNECTION << 16; - - - /* whoever resumes must GetPortStatus to complete it!! */ - if (temp & PORT_RESUME) { - dev_err(hcd->self.controller, "Port resume should be skipped.\n"); - - /* Remote Wakeup received? */ - if (!priv->reset_done) { - /* resume signaling for 20 msec */ - priv->reset_done = jiffies - + msecs_to_jiffies(20); - /* check the port again */ - mod_timer(&hcd->rh_timer, priv->reset_done); - } - - /* resume completed? */ - else if (time_after_eq(jiffies, - priv->reset_done)) { - status |= USB_PORT_STAT_C_SUSPEND << 16; - priv->reset_done = 0; - - /* stop resume signaling */ - temp = reg_read32(hcd->regs, HC_PORTSC1); - reg_write32(hcd->regs, HC_PORTSC1, - temp & ~(PORT_RWC_BITS | PORT_RESUME)); - retval = handshake(hcd, HC_PORTSC1, - PORT_RESUME, 0, 2000 /* 2msec */); - if (retval != 0) { - dev_err(hcd->self.controller, - "port %d resume error %d\n", - wIndex + 1, retval); - goto error; - } - temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); - } - } - - /* whoever resets must GetPortStatus to complete it!! */ - if ((temp & PORT_RESET) - && time_after_eq(jiffies, - priv->reset_done)) { - status |= USB_PORT_STAT_C_RESET << 16; - priv->reset_done = 0; - - /* force reset to complete */ - reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_RESET); - /* REVISIT: some hardware needs 550+ usec to clear - * this bit; seems too long to spin routinely... - */ - retval = handshake(hcd, HC_PORTSC1, - PORT_RESET, 0, 750); - if (retval != 0) { - dev_err(hcd->self.controller, "port %d reset error %d\n", - wIndex + 1, retval); - goto error; - } - - /* see what we found out */ - temp = check_reset_complete(hcd, wIndex, - reg_read32(hcd->regs, HC_PORTSC1)); - } - /* - * Even if OWNER is set, there's no harm letting hub_wq - * see the wPortStatus values (they should all be 0 except - * for PORT_POWER anyway). - */ - - if (temp & PORT_OWNER) - dev_err(hcd->self.controller, "PORT_OWNER is set\n"); - - if (temp & PORT_CONNECT) { - status |= USB_PORT_STAT_CONNECTION; - /* status may be from integrated TT */ - status |= USB_PORT_STAT_HIGH_SPEED; - } - if (temp & PORT_PE) - status |= USB_PORT_STAT_ENABLE; - if (temp & (PORT_SUSPEND|PORT_RESUME)) - status |= USB_PORT_STAT_SUSPEND; - if (temp & PORT_RESET) - status |= USB_PORT_STAT_RESET; - if (temp & PORT_POWER) - status |= USB_PORT_STAT_POWER; - - put_unaligned(cpu_to_le32(status), (__le32 *) buf); - break; - case SetHubFeature: - switch (wValue) { - case C_HUB_LOCAL_POWER: - case C_HUB_OVER_CURRENT: - /* no hub-wide feature/status flags */ - break; - default: - goto error; - } - break; - case SetPortFeature: - selector = wIndex >> 8; - wIndex &= 0xff; - if (!wIndex || wIndex > ports) - goto error; - wIndex--; - temp = reg_read32(hcd->regs, HC_PORTSC1); - if (temp & PORT_OWNER) - break; - -/* temp &= ~PORT_RWC_BITS; */ - switch (wValue) { - case USB_PORT_FEAT_ENABLE: - reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_PE); - break; - - case USB_PORT_FEAT_SUSPEND: - if ((temp & PORT_PE) == 0 - || (temp & PORT_RESET) != 0) - goto error; - - reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_SUSPEND); - break; - case USB_PORT_FEAT_POWER: - if (HCS_PPC(priv->hcs_params)) - reg_write32(hcd->regs, HC_PORTSC1, - temp | PORT_POWER); - break; - case USB_PORT_FEAT_RESET: - if (temp & PORT_RESUME) - goto error; - /* line status bits may report this as low speed, - * which can be fine if this root hub has a - * transaction translator built in. - */ - if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT - && PORT_USB11(temp)) { - temp |= PORT_OWNER; - } else { - temp |= PORT_RESET; - temp &= ~PORT_PE; - - /* - * caller must wait, then call GetPortStatus - * usb 2.0 spec says 50 ms resets on root - */ - priv->reset_done = jiffies + - msecs_to_jiffies(50); - } - reg_write32(hcd->regs, HC_PORTSC1, temp); - break; - default: - goto error; - } - reg_read32(hcd->regs, HC_USBCMD); - break; - - default: -error: - /* "stall" on error */ - retval = -EPIPE; - } - spin_unlock_irqrestore(&priv->lock, flags); - return retval; -} - -static int isp1760_get_frame(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 fr; - - fr = reg_read32(hcd->regs, HC_FRINDEX); - return (fr >> 3) % priv->periodic_size; -} - -static void isp1760_stop(struct usb_hcd *hcd) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - u32 temp; - - del_timer(&errata2_timer); - - isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1, - NULL, 0); - mdelay(20); - - spin_lock_irq(&priv->lock); - ehci_reset(hcd); - /* Disable IRQ */ - temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); - spin_unlock_irq(&priv->lock); - - reg_write32(hcd->regs, HC_CONFIGFLAG, 0); -} - -static void isp1760_shutdown(struct usb_hcd *hcd) -{ - u32 command, temp; - - isp1760_stop(hcd); - temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); - reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); - - command = reg_read32(hcd->regs, HC_USBCMD); - command &= ~CMD_RUN; - reg_write32(hcd->regs, HC_USBCMD, command); -} - -static void isp1760_clear_tt_buffer_complete(struct usb_hcd *hcd, - struct usb_host_endpoint *ep) -{ - struct isp1760_hcd *priv = hcd_to_priv(hcd); - struct isp1760_qh *qh = ep->hcpriv; - unsigned long spinflags; - - if (!qh) - return; - - spin_lock_irqsave(&priv->lock, spinflags); - qh->tt_buffer_dirty = 0; - schedule_ptds(hcd); - spin_unlock_irqrestore(&priv->lock, spinflags); -} - - -static const struct hc_driver isp1760_hc_driver = { - .description = "isp1760-hcd", - .product_desc = "NXP ISP1760 USB Host Controller", - .hcd_priv_size = sizeof(struct isp1760_hcd *), - .irq = isp1760_irq, - .flags = HCD_MEMORY | HCD_USB2, - .reset = isp1760_hc_setup, - .start = isp1760_run, - .stop = isp1760_stop, - .shutdown = isp1760_shutdown, - .urb_enqueue = isp1760_urb_enqueue, - .urb_dequeue = isp1760_urb_dequeue, - .endpoint_disable = isp1760_endpoint_disable, - .get_frame_number = isp1760_get_frame, - .hub_status_data = isp1760_hub_status_data, - .hub_control = isp1760_hub_control, - .clear_tt_buffer_complete = isp1760_clear_tt_buffer_complete, -}; - -int __init isp1760_init_kmem_once(void) -{ - urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", - sizeof(struct urb_listitem), 0, SLAB_TEMPORARY | - SLAB_MEM_SPREAD, NULL); - - if (!urb_listitem_cachep) - return -ENOMEM; - - qtd_cachep = kmem_cache_create("isp1760_qtd", - sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY | - SLAB_MEM_SPREAD, NULL); - - if (!qtd_cachep) - return -ENOMEM; - - qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh), - 0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL); - - if (!qh_cachep) { - kmem_cache_destroy(qtd_cachep); - return -ENOMEM; - } - - return 0; -} - -void isp1760_deinit_kmem_cache(void) -{ - kmem_cache_destroy(qtd_cachep); - kmem_cache_destroy(qh_cachep); - kmem_cache_destroy(urb_listitem_cachep); -} - -int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, - struct resource *mem, int irq, unsigned long irqflags, - struct device *dev) -{ - struct usb_hcd *hcd; - int ret; - - hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); - if (!hcd) - return -ENOMEM; - - *(struct isp1760_hcd **)hcd->hcd_priv = priv; - - priv->hcd = hcd; - - init_memory(priv); - - hcd->irq = irq; - hcd->regs = regs; - hcd->rsrc_start = mem->start; - hcd->rsrc_len = resource_size(mem); - - ret = usb_add_hcd(hcd, irq, irqflags); - if (ret) - goto error; - - device_wakeup_enable(hcd->self.controller); - - return 0; - -error: - usb_put_hcd(hcd); - return ret; -} - -void isp1760_hcd_unregister(struct isp1760_hcd *priv) -{ - usb_remove_hcd(priv->hcd); - usb_put_hcd(priv->hcd); -} diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h deleted file mode 100644 index df7ea36..0000000 --- a/drivers/usb/host/isp1760-hcd.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef _ISP1760_HCD_H_ -#define _ISP1760_HCD_H_ - -#include - -struct isp1760_qh; -struct isp1760_qtd; -struct resource; -struct usb_hcd; - -/* - * 60kb divided in: - * - 32 blocks @ 256 bytes - * - 20 blocks @ 1024 bytes - * - 4 blocks @ 8192 bytes - */ - -#define BLOCK_1_NUM 32 -#define BLOCK_2_NUM 20 -#define BLOCK_3_NUM 4 - -#define BLOCK_1_SIZE 256 -#define BLOCK_2_SIZE 1024 -#define BLOCK_3_SIZE 8192 -#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) -#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE -#define PAYLOAD_AREA_SIZE 0xf000 - -struct isp1760_slotinfo { - struct isp1760_qh *qh; - struct isp1760_qtd *qtd; - unsigned long timestamp; -}; - -/* chip memory management */ -struct isp1760_memory_chunk { - unsigned int start; - unsigned int size; - unsigned int free; -}; - -enum isp1760_queue_head_types { - QH_CONTROL, - QH_BULK, - QH_INTERRUPT, - QH_END -}; - -struct isp1760_hcd { - struct usb_hcd *hcd; - - u32 hcs_params; - spinlock_t lock; - struct isp1760_slotinfo atl_slots[32]; - int atl_done_map; - struct isp1760_slotinfo int_slots[32]; - int int_done_map; - struct isp1760_memory_chunk memory_pool[BLOCKS]; - struct list_head qh_list[QH_END]; - - /* periodic schedule support */ -#define DEFAULT_I_TDPS 1024 - unsigned periodic_size; - unsigned i_thresh; - unsigned long reset_done; - unsigned long next_statechange; -}; - -int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, - struct resource *mem, int irq, unsigned long irqflags, - struct device *dev); -void isp1760_hcd_unregister(struct isp1760_hcd *priv); - -int isp1760_init_kmem_once(void); -void isp1760_deinit_kmem_cache(void); - -#endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c deleted file mode 100644 index c2a94c9..0000000 --- a/drivers/usb/host/isp1760-if.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Glue code for the ISP1760 driver and bus - * Currently there is support for - * - OpenFirmware - * - PCI - * - PDEV (generic platform device centralized driver model) - * - * (c) 2007 Sebastian Siewior - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "isp1760-core.h" -#include "isp1760-regs.h" - -#ifdef CONFIG_PCI -#include -#endif - -#ifdef CONFIG_PCI -static int isp1761_pci_init(struct pci_dev *dev) -{ - resource_size_t mem_start; - resource_size_t mem_length; - u8 __iomem *iobase; - u8 latency, limit; - int retry_count; - u32 reg_data; - - /* Grab the PLX PCI shared memory of the ISP 1761 we need */ - mem_start = pci_resource_start(dev, 3); - mem_length = pci_resource_len(dev, 3); - if (mem_length < 0xffff) { - printk(KERN_ERR "memory length for this resource is wrong\n"); - return -ENOMEM; - } - - if (!request_mem_region(mem_start, mem_length, "ISP-PCI")) { - printk(KERN_ERR "host controller already in use\n"); - return -EBUSY; - } - - /* map available memory */ - iobase = ioremap_nocache(mem_start, mem_length); - if (!iobase) { - printk(KERN_ERR "Error ioremap failed\n"); - release_mem_region(mem_start, mem_length); - return -ENOMEM; - } - - /* bad pci latencies can contribute to overruns */ - pci_read_config_byte(dev, PCI_LATENCY_TIMER, &latency); - if (latency) { - pci_read_config_byte(dev, PCI_MAX_LAT, &limit); - if (limit && limit < latency) - pci_write_config_byte(dev, PCI_LATENCY_TIMER, limit); - } - - /* Try to check whether we can access Scratch Register of - * Host Controller or not. The initial PCI access is retried until - * local init for the PCI bridge is completed - */ - retry_count = 20; - reg_data = 0; - while ((reg_data != 0xFACE) && retry_count) { - /*by default host is in 16bit mode, so - * io operations at this stage must be 16 bit - * */ - writel(0xface, iobase + HC_SCRATCH_REG); - udelay(100); - reg_data = readl(iobase + HC_SCRATCH_REG) & 0x0000ffff; - retry_count--; - } - - iounmap(iobase); - release_mem_region(mem_start, mem_length); - - /* Host Controller presence is detected by writing to scratch register - * and reading back and checking the contents are same or not - */ - if (reg_data != 0xFACE) { - dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); - return -ENOMEM; - } - - /* Grab the PLX PCI mem maped port start address we need */ - mem_start = pci_resource_start(dev, 0); - mem_length = pci_resource_len(dev, 0); - - if (!request_mem_region(mem_start, mem_length, "ISP1761 IO MEM")) { - printk(KERN_ERR "request region #1\n"); - return -EBUSY; - } - - iobase = ioremap_nocache(mem_start, mem_length); - if (!iobase) { - printk(KERN_ERR "ioremap #1\n"); - release_mem_region(mem_start, mem_length); - return -ENOMEM; - } - - /* configure PLX PCI chip to pass interrupts */ -#define PLX_INT_CSR_REG 0x68 - reg_data = readl(iobase + PLX_INT_CSR_REG); - reg_data |= 0x900; - writel(reg_data, iobase + PLX_INT_CSR_REG); - - /* done with PLX IO access */ - iounmap(iobase); - release_mem_region(mem_start, mem_length); - - return 0; -} - -static int isp1761_pci_probe(struct pci_dev *dev, - const struct pci_device_id *id) -{ - unsigned int devflags = 0; - int ret; - - if (usb_disabled()) - return -ENODEV; - - if (!dev->irq) - return -ENODEV; - - if (pci_enable_device(dev) < 0) - return -ENODEV; - - ret = isp1761_pci_init(dev); - if (ret < 0) - goto error; - - pci_set_master(dev); - - dev->dev.dma_mask = NULL; - ret = isp1760_register(&dev->resource[3], dev->irq, 0, &dev->dev, - devflags); - if (ret < 0) - goto error; - - return 0; - -error: - pci_disable_device(dev); - return ret; -} - -static void isp1761_pci_remove(struct pci_dev *dev) -{ - isp1760_unregister(&dev->dev); - - pci_disable_device(dev); -} - -static void isp1761_pci_shutdown(struct pci_dev *dev) -{ - printk(KERN_ERR "ips1761_pci_shutdown\n"); -} - -static const struct pci_device_id isp1760_plx [] = { - { - .class = PCI_CLASS_BRIDGE_OTHER << 8, - .class_mask = ~0, - .vendor = PCI_VENDOR_ID_PLX, - .device = 0x5406, - .subvendor = PCI_VENDOR_ID_PLX, - .subdevice = 0x9054, - }, - { } -}; -MODULE_DEVICE_TABLE(pci, isp1760_plx); - -static struct pci_driver isp1761_pci_driver = { - .name = "isp1760", - .id_table = isp1760_plx, - .probe = isp1761_pci_probe, - .remove = isp1761_pci_remove, - .shutdown = isp1761_pci_shutdown, -}; -#endif - -static int isp1760_plat_probe(struct platform_device *pdev) -{ - unsigned long irqflags; - unsigned int devflags = 0; - struct resource *mem_res; - struct resource *irq_res; - int ret; - - mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!irq_res) { - pr_warning("isp1760: IRQ resource not available\n"); - return -ENODEV; - } - irqflags = irq_res->flags & IRQF_TRIGGER_MASK; - - if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { - struct device_node *dp = pdev->dev.of_node; - u32 bus_width = 0; - - if (of_device_is_compatible(dp, "nxp,usb-isp1761")) - devflags |= ISP1760_FLAG_ISP1761; - - /* Some systems wire up only 16 of the 32 data lines */ - of_property_read_u32(dp, "bus-width", &bus_width); - if (bus_width == 16) - devflags |= ISP1760_FLAG_BUS_WIDTH_16; - - if (of_property_read_bool(dp, "port1-otg")) - devflags |= ISP1760_FLAG_OTG_EN; - - if (of_property_read_bool(dp, "analog-oc")) - devflags |= ISP1760_FLAG_ANALOG_OC; - - if (of_property_read_bool(dp, "dack-polarity")) - devflags |= ISP1760_FLAG_DACK_POL_HIGH; - - if (of_property_read_bool(dp, "dreq-polarity")) - devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - } else if (dev_get_platdata(&pdev->dev)) { - struct isp1760_platform_data *pdata = - dev_get_platdata(&pdev->dev); - - if (pdata->is_isp1761) - devflags |= ISP1760_FLAG_ISP1761; - if (pdata->bus_width_16) - devflags |= ISP1760_FLAG_BUS_WIDTH_16; - if (pdata->port1_otg) - devflags |= ISP1760_FLAG_OTG_EN; - if (pdata->analog_oc) - devflags |= ISP1760_FLAG_ANALOG_OC; - if (pdata->dack_polarity_high) - devflags |= ISP1760_FLAG_DACK_POL_HIGH; - if (pdata->dreq_polarity_high) - devflags |= ISP1760_FLAG_DREQ_POL_HIGH; - } - - ret = isp1760_register(mem_res, irq_res->start, irqflags, &pdev->dev, - devflags); - if (ret < 0) - return ret; - - pr_info("ISP1760 USB device initialised\n"); - return 0; -} - -static int isp1760_plat_remove(struct platform_device *pdev) -{ - isp1760_unregister(&pdev->dev); - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id isp1760_of_match[] = { - { .compatible = "nxp,usb-isp1760", }, - { .compatible = "nxp,usb-isp1761", }, - { }, -}; -MODULE_DEVICE_TABLE(of, isp1760_of_match); -#endif - -static struct platform_driver isp1760_plat_driver = { - .probe = isp1760_plat_probe, - .remove = isp1760_plat_remove, - .driver = { - .name = "isp1760", - .of_match_table = of_match_ptr(isp1760_of_match), - }, -}; - -static int __init isp1760_init(void) -{ - int ret, any_ret = -ENODEV; - - isp1760_init_kmem_once(); - - ret = platform_driver_register(&isp1760_plat_driver); - if (!ret) - any_ret = 0; -#ifdef CONFIG_PCI - ret = pci_register_driver(&isp1761_pci_driver); - if (!ret) - any_ret = 0; -#endif - - if (any_ret) - isp1760_deinit_kmem_cache(); - return any_ret; -} -module_init(isp1760_init); - -static void __exit isp1760_exit(void) -{ - platform_driver_unregister(&isp1760_plat_driver); -#ifdef CONFIG_PCI - pci_unregister_driver(&isp1761_pci_driver); -#endif - isp1760_deinit_kmem_cache(); -} -module_exit(isp1760_exit); diff --git a/drivers/usb/host/isp1760-regs.h b/drivers/usb/host/isp1760-regs.h deleted file mode 100644 index b67095c..0000000 --- a/drivers/usb/host/isp1760-regs.h +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Driver for the NXP ISP1760 chip - * - * Copyright 2014 Laurent Pinchart - * Copyright 2007 Sebastian Siewior - * - * Contacts: - * Sebastian Siewior - * Laurent Pinchart - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - */ - -#ifndef _ISP1760_REGS_H_ -#define _ISP1760_REGS_H_ - -/* ----------------------------------------------------------------------------- - * Host Controller - */ - -/* EHCI capability registers */ -#define HC_CAPLENGTH 0x000 -#define HC_LENGTH(p) (((p) >> 00) & 0x00ff) /* bits 7:0 */ -#define HC_VERSION(p) (((p) >> 16) & 0xffff) /* bits 31:16 */ - -#define HC_HCSPARAMS 0x004 -#define HCS_INDICATOR(p) ((p) & (1 << 16)) /* true: has port indicators */ -#define HCS_PPC(p) ((p) & (1 << 4)) /* true: port power control */ -#define HCS_N_PORTS(p) (((p) >> 0) & 0xf) /* bits 3:0, ports on HC */ - -#define HC_HCCPARAMS 0x008 -#define HCC_ISOC_CACHE(p) ((p) & (1 << 7)) /* true: can cache isoc frame */ -#define HCC_ISOC_THRES(p) (((p) >> 4) & 0x7) /* bits 6:4, uframes cached */ - -/* EHCI operational registers */ -#define HC_USBCMD 0x020 -#define CMD_LRESET (1 << 7) /* partial reset (no ports, etc) */ -#define CMD_RESET (1 << 1) /* reset HC not bus */ -#define CMD_RUN (1 << 0) /* start/stop HC */ - -#define HC_USBSTS 0x024 -#define STS_PCD (1 << 2) /* port change detect */ - -#define HC_FRINDEX 0x02c - -#define HC_CONFIGFLAG 0x060 -#define FLAG_CF (1 << 0) /* true: we'll support "high speed" */ - -#define HC_PORTSC1 0x064 -#define PORT_OWNER (1 << 13) /* true: companion hc owns this port */ -#define PORT_POWER (1 << 12) /* true: has power (see PPC) */ -#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ -#define PORT_RESET (1 << 8) /* reset port */ -#define PORT_SUSPEND (1 << 7) /* suspend port */ -#define PORT_RESUME (1 << 6) /* resume it */ -#define PORT_PE (1 << 2) /* port enable */ -#define PORT_CSC (1 << 1) /* connect status change */ -#define PORT_CONNECT (1 << 0) /* device connected */ -#define PORT_RWC_BITS (PORT_CSC) - -#define HC_ISO_PTD_DONEMAP_REG 0x130 -#define HC_ISO_PTD_SKIPMAP_REG 0x134 -#define HC_ISO_PTD_LASTPTD_REG 0x138 -#define HC_INT_PTD_DONEMAP_REG 0x140 -#define HC_INT_PTD_SKIPMAP_REG 0x144 -#define HC_INT_PTD_LASTPTD_REG 0x148 -#define HC_ATL_PTD_DONEMAP_REG 0x150 -#define HC_ATL_PTD_SKIPMAP_REG 0x154 -#define HC_ATL_PTD_LASTPTD_REG 0x158 - -/* Configuration Register */ -#define HC_HW_MODE_CTRL 0x300 -#define ALL_ATX_RESET (1 << 31) -#define HW_ANA_DIGI_OC (1 << 15) -#define HW_DEV_DMA (1 << 11) -#define HW_COMN_IRQ (1 << 10) -#define HW_COMN_DMA (1 << 9) -#define HW_DATA_BUS_32BIT (1 << 8) -#define HW_DACK_POL_HIGH (1 << 6) -#define HW_DREQ_POL_HIGH (1 << 5) -#define HW_INTR_HIGH_ACT (1 << 2) -#define HW_INTR_EDGE_TRIG (1 << 1) -#define HW_GLOBAL_INTR_EN (1 << 0) - -#define HC_CHIP_ID_REG 0x304 -#define HC_SCRATCH_REG 0x308 - -#define HC_RESET_REG 0x30c -#define SW_RESET_RESET_HC (1 << 1) -#define SW_RESET_RESET_ALL (1 << 0) - -#define HC_BUFFER_STATUS_REG 0x334 -#define ISO_BUF_FILL (1 << 2) -#define INT_BUF_FILL (1 << 1) -#define ATL_BUF_FILL (1 << 0) - -#define HC_MEMORY_REG 0x33c -#define ISP_BANK(x) ((x) << 16) - -#define HC_PORT1_CTRL 0x374 -#define PORT1_POWER (3 << 3) -#define PORT1_INIT1 (1 << 7) -#define PORT1_INIT2 (1 << 23) -#define HW_OTG_CTRL_SET 0x374 -#define HW_OTG_CTRL_CLR 0x376 -#define HW_OTG_DISABLE (1 << 10) -#define HW_OTG_SE0_EN (1 << 9) -#define HW_BDIS_ACON_EN (1 << 8) -#define HW_SW_SEL_HC_DC (1 << 7) -#define HW_VBUS_CHRG (1 << 6) -#define HW_VBUS_DISCHRG (1 << 5) -#define HW_VBUS_DRV (1 << 4) -#define HW_SEL_CP_EXT (1 << 3) -#define HW_DM_PULLDOWN (1 << 2) -#define HW_DP_PULLDOWN (1 << 1) -#define HW_DP_PULLUP (1 << 0) - -/* Interrupt Register */ -#define HC_INTERRUPT_REG 0x310 - -#define HC_INTERRUPT_ENABLE 0x314 -#define HC_ISO_INT (1 << 9) -#define HC_ATL_INT (1 << 8) -#define HC_INTL_INT (1 << 7) -#define HC_EOT_INT (1 << 3) -#define HC_SOT_INT (1 << 1) -#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) - -#define HC_ISO_IRQ_MASK_OR_REG 0x318 -#define HC_INT_IRQ_MASK_OR_REG 0x31c -#define HC_ATL_IRQ_MASK_OR_REG 0x320 -#define HC_ISO_IRQ_MASK_AND_REG 0x324 -#define HC_INT_IRQ_MASK_AND_REG 0x328 -#define HC_ATL_IRQ_MASK_AND_REG 0x32c - -/* ----------------------------------------------------------------------------- - * Peripheral Controller - */ - -/* Initialization Registers */ -#define DC_ADDRESS 0x0200 -#define DC_DEVEN (1 << 7) - -#define DC_MODE 0x020c -#define DC_DMACLKON (1 << 9) -#define DC_VBUSSTAT (1 << 8) -#define DC_CLKAON (1 << 7) -#define DC_SNDRSU (1 << 6) -#define DC_GOSUSP (1 << 5) -#define DC_SFRESET (1 << 4) -#define DC_GLINTENA (1 << 3) -#define DC_WKUPCS (1 << 2) - -#define DC_INTCONF 0x0210 -#define DC_CDBGMOD_ACK_NAK (0 << 6) -#define DC_CDBGMOD_ACK (1 << 6) -#define DC_CDBGMOD_ACK_1NAK (2 << 6) -#define DC_DDBGMODIN_ACK_NAK (0 << 4) -#define DC_DDBGMODIN_ACK (1 << 4) -#define DC_DDBGMODIN_ACK_1NAK (2 << 4) -#define DC_DDBGMODOUT_ACK_NYET_NAK (0 << 2) -#define DC_DDBGMODOUT_ACK_NYET (1 << 2) -#define DC_DDBGMODOUT_ACK_NYET_1NAK (2 << 2) -#define DC_INTLVL (1 << 1) -#define DC_INTPOL (1 << 0) - -#define DC_DEBUG 0x0212 -#define DC_INTENABLE 0x0214 -#define DC_IEPTX(n) (1 << (11 + 2 * (n))) -#define DC_IEPRX(n) (1 << (10 + 2 * (n))) -#define DC_IEPRXTX(n) (3 << (10 + 2 * (n))) -#define DC_IEP0SETUP (1 << 8) -#define DC_IEVBUS (1 << 7) -#define DC_IEDMA (1 << 6) -#define DC_IEHS_STA (1 << 5) -#define DC_IERESM (1 << 4) -#define DC_IESUSP (1 << 3) -#define DC_IEPSOF (1 << 2) -#define DC_IESOF (1 << 1) -#define DC_IEBRST (1 << 0) - -/* Data Flow Registers */ -#define DC_EPINDEX 0x022c -#define DC_EP0SETUP (1 << 5) -#define DC_ENDPIDX(n) ((n) << 1) -#define DC_EPDIR (1 << 0) - -#define DC_CTRLFUNC 0x0228 -#define DC_CLBUF (1 << 4) -#define DC_VENDP (1 << 3) -#define DC_DSEN (1 << 2) -#define DC_STATUS (1 << 1) -#define DC_STALL (1 << 0) - -#define DC_DATAPORT 0x0220 -#define DC_BUFLEN 0x021c -#define DC_DATACOUNT_MASK 0xffff -#define DC_BUFSTAT 0x021e -#define DC_EPMAXPKTSZ 0x0204 - -#define DC_EPTYPE 0x0208 -#define DC_NOEMPKT (1 << 4) -#define DC_EPENABLE (1 << 3) -#define DC_DBLBUF (1 << 2) -#define DC_ENDPTYP_ISOC (1 << 0) -#define DC_ENDPTYP_BULK (2 << 0) -#define DC_ENDPTYP_INTERRUPT (3 << 0) - -/* DMA Registers */ -#define DC_DMACMD 0x0230 -#define DC_DMATXCOUNT 0x0234 -#define DC_DMACONF 0x0238 -#define DC_DMAHW 0x023c -#define DC_DMAINTREASON 0x0250 -#define DC_DMAINTEN 0x0254 -#define DC_DMAEP 0x0258 -#define DC_DMABURSTCOUNT 0x0264 - -/* General Registers */ -#define DC_INTERRUPT 0x0218 -#define DC_CHIPID 0x0270 -#define DC_FRAMENUM 0x0274 -#define DC_SCRATCH 0x0278 -#define DC_UNLOCKDEV 0x027c -#define DC_INTPULSEWIDTH 0x0280 -#define DC_TESTMODE 0x0284 - -#endif diff --git a/drivers/usb/host/isp1760-udc.c b/drivers/usb/host/isp1760-udc.c deleted file mode 100644 index 6bfda30..0000000 --- a/drivers/usb/host/isp1760-udc.c +++ /dev/null @@ -1,1495 +0,0 @@ -/* - * Driver for the NXP ISP1761 device controller - * - * Copyright 2014 Ideas on Board Oy - * - * Contacts: - * Laurent Pinchart - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "isp1760-core.h" -#include "isp1760-regs.h" -#include "isp1760-udc.h" - -#define ISP1760_VBUS_POLL_INTERVAL msecs_to_jiffies(500) - -struct isp1760_request { - struct usb_request req; - struct list_head queue; - struct isp1760_ep *ep; - unsigned int packet_size; -}; - -static inline struct isp1760_udc *gadget_to_udc(struct usb_gadget *gadget) -{ - return container_of(gadget, struct isp1760_udc, gadget); -} - -static inline struct isp1760_ep *ep_to_udc_ep(struct usb_ep *ep) -{ - return container_of(ep, struct isp1760_ep, ep); -} - -static inline struct isp1760_request *req_to_udc_req(struct usb_request *req) -{ - return container_of(req, struct isp1760_request, req); -} - -static inline u32 isp1760_udc_read(struct isp1760_udc *udc, u16 reg) -{ - return isp1760_read32(udc->regs, reg); -} - -static inline void isp1760_udc_write(struct isp1760_udc *udc, u16 reg, u32 val) -{ - isp1760_write32(udc->regs, reg, val); -} - -/* ----------------------------------------------------------------------------- - * Endpoint Management - */ - -static struct isp1760_ep *isp1760_udc_find_ep(struct isp1760_udc *udc, - u16 index) -{ - unsigned int i; - - if (index == 0) - return &udc->ep[0]; - - for (i = 1; i < ARRAY_SIZE(udc->ep); ++i) { - if (udc->ep[i].addr == index) - return udc->ep[i].desc ? &udc->ep[i] : NULL; - } - - return NULL; -} - -static void __isp1760_udc_select_ep(struct isp1760_ep *ep, int dir) -{ - isp1760_udc_write(ep->udc, DC_EPINDEX, - DC_ENDPIDX(ep->addr & USB_ENDPOINT_NUMBER_MASK) | - (dir == USB_DIR_IN ? DC_EPDIR : 0)); -} - -/** - * isp1760_udc_select_ep - Select an endpoint for register access - * @ep: The endpoint - * - * The ISP1761 endpoint registers are banked. This function selects the target - * endpoint for banked register access. The selection remains valid until the - * next call to this function, the next direct access to the EPINDEX register - * or the next reset, whichever comes first. - * - * Called with the UDC spinlock held. - */ -static void isp1760_udc_select_ep(struct isp1760_ep *ep) -{ - __isp1760_udc_select_ep(ep, ep->addr & USB_ENDPOINT_DIR_MASK); -} - -/* Called with the UDC spinlock held. */ -static void isp1760_udc_ctrl_send_status(struct isp1760_ep *ep, int dir) -{ - struct isp1760_udc *udc = ep->udc; - - /* - * Proceed to the status stage. The status stage data packet flows in - * the direction opposite to the data stage data packets, we thus need - * to select the OUT/IN endpoint for IN/OUT transfers. - */ - isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | - (dir == USB_DIR_IN ? 0 : DC_EPDIR)); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_STATUS); - - /* - * The hardware will terminate the request automatically and go back to - * the setup stage without notifying us. - */ - udc->ep0_state = ISP1760_CTRL_SETUP; -} - -/* Called without the UDC spinlock held. */ -static void isp1760_udc_request_complete(struct isp1760_ep *ep, - struct isp1760_request *req, - int status) -{ - struct isp1760_udc *udc = ep->udc; - unsigned long flags; - - dev_dbg(ep->udc->isp->dev, "completing request %p with status %d\n", - req, status); - - req->ep = NULL; - req->req.status = status; - req->req.complete(&ep->ep, &req->req); - - spin_lock_irqsave(&udc->lock, flags); - - /* - * When completing control OUT requests, move to the status stage after - * calling the request complete callback. This gives the gadget an - * opportunity to stall the control transfer if needed. - */ - if (status == 0 && ep->addr == 0 && udc->ep0_dir == USB_DIR_OUT) - isp1760_udc_ctrl_send_status(ep, USB_DIR_OUT); - - spin_unlock_irqrestore(&udc->lock, flags); -} - -static void isp1760_udc_ctrl_send_stall(struct isp1760_ep *ep) -{ - struct isp1760_udc *udc = ep->udc; - unsigned long flags; - - dev_dbg(ep->udc->isp->dev, "%s(ep%02x)\n", __func__, ep->addr); - - spin_lock_irqsave(&udc->lock, flags); - - /* Stall both the IN and OUT endpoints. */ - __isp1760_udc_select_ep(ep, USB_DIR_OUT); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); - __isp1760_udc_select_ep(ep, USB_DIR_IN); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); - - /* A protocol stall completes the control transaction. */ - udc->ep0_state = ISP1760_CTRL_SETUP; - - spin_unlock_irqrestore(&udc->lock, flags); -} - -/* ----------------------------------------------------------------------------- - * Data Endpoints - */ - -/* Called with the UDC spinlock held. */ -static bool isp1760_udc_receive(struct isp1760_ep *ep, - struct isp1760_request *req) -{ - struct isp1760_udc *udc = ep->udc; - unsigned int len; - u32 *buf; - int i; - - isp1760_udc_select_ep(ep); - len = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; - - dev_dbg(udc->isp->dev, "%s: received %u bytes (%u/%u done)\n", - __func__, len, req->req.actual, req->req.length); - - len = min(len, req->req.length - req->req.actual); - - if (!len) { - /* - * There's no data to be read from the FIFO, acknowledge the RX - * interrupt by clearing the buffer. - * - * TODO: What if another packet arrives in the meantime ? The - * datasheet doesn't clearly document how this should be - * handled. - */ - isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); - return false; - } - - buf = req->req.buf + req->req.actual; - - /* - * Make sure not to read more than one extra byte, otherwise data from - * the next packet might be removed from the FIFO. - */ - for (i = len; i > 2; i -= 4, ++buf) - *buf = le32_to_cpu(isp1760_udc_read(udc, DC_DATAPORT)); - if (i > 0) - *(u16 *)buf = le16_to_cpu(readw(udc->regs + DC_DATAPORT)); - - req->req.actual += len; - - /* - * TODO: The short_not_ok flag isn't supported yet, but isn't used by - * any gadget driver either. - */ - - dev_dbg(udc->isp->dev, - "%s: req %p actual/length %u/%u maxpacket %u packet size %u\n", - __func__, req, req->req.actual, req->req.length, ep->maxpacket, - len); - - ep->rx_pending = false; - - /* - * Complete the request if all data has been received or if a short - * packet has been received. - */ - if (req->req.actual == req->req.length || len < ep->maxpacket) { - list_del(&req->queue); - return true; - } - - return false; -} - -static void isp1760_udc_transmit(struct isp1760_ep *ep, - struct isp1760_request *req) -{ - struct isp1760_udc *udc = ep->udc; - u32 *buf = req->req.buf + req->req.actual; - int i; - - req->packet_size = min(req->req.length - req->req.actual, - ep->maxpacket); - - dev_dbg(udc->isp->dev, "%s: transferring %u bytes (%u/%u done)\n", - __func__, req->packet_size, req->req.actual, - req->req.length); - - __isp1760_udc_select_ep(ep, USB_DIR_IN); - - if (req->packet_size) - isp1760_udc_write(udc, DC_BUFLEN, req->packet_size); - - /* - * Make sure not to write more than one extra byte, otherwise extra data - * will stay in the FIFO and will be transmitted during the next control - * request. The endpoint control CLBUF bit is supposed to allow flushing - * the FIFO for this kind of conditions, but doesn't seem to work. - */ - for (i = req->packet_size; i > 2; i -= 4, ++buf) - isp1760_udc_write(udc, DC_DATAPORT, cpu_to_le32(*buf)); - if (i > 0) - writew(cpu_to_le16(*(u16 *)buf), udc->regs + DC_DATAPORT); - - if (ep->addr == 0) - isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); - if (!req->packet_size) - isp1760_udc_write(udc, DC_CTRLFUNC, DC_VENDP); -} - -static void isp1760_ep_rx_ready(struct isp1760_ep *ep) -{ - struct isp1760_udc *udc = ep->udc; - struct isp1760_request *req; - bool complete; - - spin_lock(&udc->lock); - - if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_OUT) { - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "%s: invalid ep0 state %u\n", __func__, - udc->ep0_state); - return; - } - - if (ep->addr != 0 && !ep->desc) { - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, - ep->addr); - return; - } - - if (list_empty(&ep->queue)) { - ep->rx_pending = true; - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "%s: ep%02x (%p) has no request queued\n", - __func__, ep->addr, ep); - return; - } - - req = list_first_entry(&ep->queue, struct isp1760_request, - queue); - complete = isp1760_udc_receive(ep, req); - - spin_unlock(&udc->lock); - - if (complete) - isp1760_udc_request_complete(ep, req, 0); -} - -static void isp1760_ep_tx_complete(struct isp1760_ep *ep) -{ - struct isp1760_udc *udc = ep->udc; - struct isp1760_request *complete = NULL; - struct isp1760_request *req; - bool need_zlp; - - spin_lock(&udc->lock); - - if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_IN) { - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "TX IRQ: invalid endpoint state %u\n", - udc->ep0_state); - return; - } - - if (list_empty(&ep->queue)) { - /* - * This can happen for the control endpoint when the reply to - * the GET_STATUS IN control request is sent directly by the - * setup IRQ handler. Just proceed to the status stage. - */ - if (ep->addr == 0) { - isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); - spin_unlock(&udc->lock); - return; - } - - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "%s: ep%02x has no request queued\n", - __func__, ep->addr); - return; - } - - req = list_first_entry(&ep->queue, struct isp1760_request, - queue); - req->req.actual += req->packet_size; - - need_zlp = req->req.actual == req->req.length && - !(req->req.length % ep->maxpacket) && - req->packet_size && req->req.zero; - - dev_dbg(udc->isp->dev, - "TX IRQ: req %p actual/length %u/%u maxpacket %u packet size %u zero %u need zlp %u\n", - req, req->req.actual, req->req.length, ep->maxpacket, - req->packet_size, req->req.zero, need_zlp); - - /* - * Complete the request if all data has been sent and we don't need to - * transmit a zero length packet. - */ - if (req->req.actual == req->req.length && !need_zlp) { - complete = req; - list_del(&req->queue); - - if (ep->addr == 0) - isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); - - if (!list_empty(&ep->queue)) - req = list_first_entry(&ep->queue, - struct isp1760_request, queue); - else - req = NULL; - } - - /* - * Transmit the next packet or start the next request, if any. - * - * TODO: If the endpoint is stalled the next request shouldn't be - * started, but what about the next packet ? - */ - if (req) - isp1760_udc_transmit(ep, req); - - spin_unlock(&udc->lock); - - if (complete) - isp1760_udc_request_complete(ep, complete, 0); -} - -static int __isp1760_udc_set_halt(struct isp1760_ep *ep, bool halt) -{ - struct isp1760_udc *udc = ep->udc; - - dev_dbg(udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, - halt ? "set" : "clear", ep->addr); - - if (ep->desc && usb_endpoint_xfer_isoc(ep->desc)) { - dev_dbg(udc->isp->dev, "%s: ep%02x is isochronous\n", __func__, - ep->addr); - return -EINVAL; - } - - isp1760_udc_select_ep(ep); - isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); - - if (ep->addr == 0) { - /* When halting the control endpoint, stall both IN and OUT. */ - __isp1760_udc_select_ep(ep, USB_DIR_IN); - isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); - } else if (!halt) { - /* Reset the data PID by cycling the endpoint enable bit. */ - u16 eptype = isp1760_udc_read(udc, DC_EPTYPE); - - isp1760_udc_write(udc, DC_EPTYPE, eptype & ~DC_EPENABLE); - isp1760_udc_write(udc, DC_EPTYPE, eptype); - - /* - * Disabling the endpoint emptied the transmit FIFO, fill it - * again if a request is pending. - * - * TODO: Does the gadget framework require synchronizatino with - * the TX IRQ handler ? - */ - if ((ep->addr & USB_DIR_IN) && !list_empty(&ep->queue)) { - struct isp1760_request *req; - - req = list_first_entry(&ep->queue, - struct isp1760_request, queue); - isp1760_udc_transmit(ep, req); - } - } - - ep->halted = halt; - - return 0; -} - -/* ----------------------------------------------------------------------------- - * Control Endpoint - */ - -static int isp1760_udc_get_status(struct isp1760_udc *udc, - const struct usb_ctrlrequest *req) -{ - struct isp1760_ep *ep; - u16 status; - - if (req->wLength != cpu_to_le16(2) || req->wValue != cpu_to_le16(0)) - return -EINVAL; - - switch (req->bRequestType) { - case USB_DIR_IN | USB_RECIP_DEVICE: - status = udc->devstatus; - break; - - case USB_DIR_IN | USB_RECIP_INTERFACE: - status = 0; - break; - - case USB_DIR_IN | USB_RECIP_ENDPOINT: - ep = isp1760_udc_find_ep(udc, le16_to_cpu(req->wIndex)); - if (!ep) - return -EINVAL; - - status = 0; - if (ep->halted) - status |= 1 << USB_ENDPOINT_HALT; - break; - - default: - return -EINVAL; - } - - isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | DC_EPDIR); - isp1760_udc_write(udc, DC_BUFLEN, 2); - - writew(cpu_to_le16(status), udc->regs + DC_DATAPORT); - - isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); - - dev_dbg(udc->isp->dev, "%s: status 0x%04x\n", __func__, status); - - return 0; -} - -static int isp1760_udc_set_address(struct isp1760_udc *udc, u16 addr) -{ - if (addr > 127) { - dev_dbg(udc->isp->dev, "invalid device address %u\n", addr); - return -EINVAL; - } - - if (udc->gadget.state != USB_STATE_DEFAULT && - udc->gadget.state != USB_STATE_ADDRESS) { - dev_dbg(udc->isp->dev, "can't set address in state %u\n", - udc->gadget.state); - return -EINVAL; - } - - usb_gadget_set_state(&udc->gadget, addr ? USB_STATE_ADDRESS : - USB_STATE_DEFAULT); - - isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN | addr); - - spin_lock(&udc->lock); - isp1760_udc_ctrl_send_status(&udc->ep[0], USB_DIR_OUT); - spin_unlock(&udc->lock); - - return 0; -} - -static bool isp1760_ep0_setup_standard(struct isp1760_udc *udc, - struct usb_ctrlrequest *req) -{ - bool stall; - - switch (req->bRequest) { - case USB_REQ_GET_STATUS: - return isp1760_udc_get_status(udc, req); - - case USB_REQ_CLEAR_FEATURE: - switch (req->bRequestType) { - case USB_DIR_OUT | USB_RECIP_DEVICE: { - /* TODO: Handle remote wakeup feature. */ - return true; - } - - case USB_DIR_OUT | USB_RECIP_ENDPOINT: { - u16 index = le16_to_cpu(req->wIndex); - struct isp1760_ep *ep; - - if (req->wLength != cpu_to_le16(0) || - req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) - return true; - - ep = isp1760_udc_find_ep(udc, index); - if (!ep) - return true; - - spin_lock(&udc->lock); - - /* - * If the endpoint is wedged only the gadget can clear - * the halt feature. Pretend success in that case, but - * keep the endpoint halted. - */ - if (!ep->wedged) - stall = __isp1760_udc_set_halt(ep, false); - else - stall = false; - - if (!stall) - isp1760_udc_ctrl_send_status(&udc->ep[0], - USB_DIR_OUT); - - spin_unlock(&udc->lock); - return stall; - } - - default: - return true; - } - break; - - case USB_REQ_SET_FEATURE: - switch (req->bRequestType) { - case USB_DIR_OUT | USB_RECIP_DEVICE: { - /* TODO: Handle remote wakeup and test mode features */ - return true; - } - - case USB_DIR_OUT | USB_RECIP_ENDPOINT: { - u16 index = le16_to_cpu(req->wIndex); - struct isp1760_ep *ep; - - if (req->wLength != cpu_to_le16(0) || - req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) - return true; - - ep = isp1760_udc_find_ep(udc, index); - if (!ep) - return true; - - spin_lock(&udc->lock); - - stall = __isp1760_udc_set_halt(ep, true); - if (!stall) - isp1760_udc_ctrl_send_status(&udc->ep[0], - USB_DIR_OUT); - - spin_unlock(&udc->lock); - return stall; - } - - default: - return true; - } - break; - - case USB_REQ_SET_ADDRESS: - if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) - return true; - - return isp1760_udc_set_address(udc, le16_to_cpu(req->wValue)); - - case USB_REQ_SET_CONFIGURATION: - if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) - return true; - - if (udc->gadget.state != USB_STATE_ADDRESS && - udc->gadget.state != USB_STATE_CONFIGURED) - return true; - - stall = udc->driver->setup(&udc->gadget, req) < 0; - if (stall) - return true; - - usb_gadget_set_state(&udc->gadget, req->wValue ? - USB_STATE_CONFIGURED : USB_STATE_ADDRESS); - - /* - * SET_CONFIGURATION (and SET_INTERFACE) must reset the halt - * feature on all endpoints. There is however no need to do so - * explicitly here as the gadget driver will disable and - * reenable endpoints, clearing the halt feature. - */ - return false; - - default: - return udc->driver->setup(&udc->gadget, req) < 0; - } -} - -static void isp1760_ep0_setup(struct isp1760_udc *udc) -{ - union { - struct usb_ctrlrequest r; - u32 data[2]; - } req; - unsigned int count; - bool stall = false; - - spin_lock(&udc->lock); - - isp1760_udc_write(udc, DC_EPINDEX, DC_EP0SETUP); - - count = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; - if (count != sizeof(req)) { - spin_unlock(&udc->lock); - - dev_err(udc->isp->dev, "invalid length %u for setup packet\n", - count); - - isp1760_udc_ctrl_send_stall(&udc->ep[0]); - return; - } - - req.data[0] = isp1760_udc_read(udc, DC_DATAPORT); - req.data[1] = isp1760_udc_read(udc, DC_DATAPORT); - - if (udc->ep0_state != ISP1760_CTRL_SETUP) { - spin_unlock(&udc->lock); - dev_dbg(udc->isp->dev, "unexpected SETUP packet\n"); - return; - } - - /* Move to the data stage. */ - if (!req.r.wLength) - udc->ep0_state = ISP1760_CTRL_STATUS; - else if (req.r.bRequestType & USB_DIR_IN) - udc->ep0_state = ISP1760_CTRL_DATA_IN; - else - udc->ep0_state = ISP1760_CTRL_DATA_OUT; - - udc->ep0_dir = req.r.bRequestType & USB_DIR_IN; - udc->ep0_length = le16_to_cpu(req.r.wLength); - - spin_unlock(&udc->lock); - - dev_dbg(udc->isp->dev, - "%s: bRequestType 0x%02x bRequest 0x%02x wValue 0x%04x wIndex 0x%04x wLength 0x%04x\n", - __func__, req.r.bRequestType, req.r.bRequest, - le16_to_cpu(req.r.wValue), le16_to_cpu(req.r.wIndex), - le16_to_cpu(req.r.wLength)); - - if ((req.r.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) - stall = isp1760_ep0_setup_standard(udc, &req.r); - else - stall = udc->driver->setup(&udc->gadget, &req.r) < 0; - - if (stall) - isp1760_udc_ctrl_send_stall(&udc->ep[0]); -} - -/* ----------------------------------------------------------------------------- - * Gadget Endpoint Operations - */ - -static int isp1760_ep_enable(struct usb_ep *ep, - const struct usb_endpoint_descriptor *desc) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - unsigned long flags; - unsigned int type; - - dev_dbg(uep->udc->isp->dev, "%s\n", __func__); - - /* - * Validate the descriptor. The control endpoint can't be enabled - * manually. - */ - if (desc->bDescriptorType != USB_DT_ENDPOINT || - desc->bEndpointAddress == 0 || - desc->bEndpointAddress != uep->addr || - le16_to_cpu(desc->wMaxPacketSize) > ep->maxpacket) { - dev_dbg(udc->isp->dev, - "%s: invalid descriptor type %u addr %02x ep addr %02x max packet size %u/%u\n", - __func__, desc->bDescriptorType, - desc->bEndpointAddress, uep->addr, - le16_to_cpu(desc->wMaxPacketSize), ep->maxpacket); - return -EINVAL; - } - - switch (usb_endpoint_type(desc)) { - case USB_ENDPOINT_XFER_ISOC: - type = DC_ENDPTYP_ISOC; - break; - case USB_ENDPOINT_XFER_BULK: - type = DC_ENDPTYP_BULK; - break; - case USB_ENDPOINT_XFER_INT: - type = DC_ENDPTYP_INTERRUPT; - break; - case USB_ENDPOINT_XFER_CONTROL: - default: - dev_dbg(udc->isp->dev, "%s: control endpoints unsupported\n", - __func__); - return -EINVAL; - } - - spin_lock_irqsave(&udc->lock, flags); - - uep->desc = desc; - uep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); - uep->rx_pending = false; - uep->halted = false; - uep->wedged = false; - - isp1760_udc_select_ep(uep); - isp1760_udc_write(udc, DC_EPMAXPKTSZ, uep->maxpacket); - isp1760_udc_write(udc, DC_BUFLEN, uep->maxpacket); - isp1760_udc_write(udc, DC_EPTYPE, DC_EPENABLE | type); - - spin_unlock_irqrestore(&udc->lock, flags); - - return 0; -} - -static int isp1760_ep_disable(struct usb_ep *ep) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - struct isp1760_request *req, *nreq; - LIST_HEAD(req_list); - unsigned long flags; - - dev_dbg(udc->isp->dev, "%s\n", __func__); - - spin_lock_irqsave(&udc->lock, flags); - - if (!uep->desc) { - dev_dbg(udc->isp->dev, "%s: endpoint not enabled\n", __func__); - spin_unlock_irqrestore(&udc->lock, flags); - return -EINVAL; - } - - uep->desc = NULL; - uep->maxpacket = 0; - - isp1760_udc_select_ep(uep); - isp1760_udc_write(udc, DC_EPTYPE, 0); - - /* TODO Synchronize with the IRQ handler */ - - list_splice_init(&uep->queue, &req_list); - - spin_unlock_irqrestore(&udc->lock, flags); - - list_for_each_entry_safe(req, nreq, &req_list, queue) { - list_del(&req->queue); - isp1760_udc_request_complete(uep, req, -ESHUTDOWN); - } - - return 0; -} - -static struct usb_request *isp1760_ep_alloc_request(struct usb_ep *ep, - gfp_t gfp_flags) -{ - struct isp1760_request *req; - - req = kzalloc(sizeof(*req), gfp_flags); - - return &req->req; -} - -static void isp1760_ep_free_request(struct usb_ep *ep, struct usb_request *_req) -{ - struct isp1760_request *req = req_to_udc_req(_req); - - kfree(req); -} - -static int isp1760_ep_queue(struct usb_ep *ep, struct usb_request *_req, - gfp_t gfp_flags) -{ - struct isp1760_request *req = req_to_udc_req(_req); - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - bool complete = false; - unsigned long flags; - int ret = 0; - - _req->status = -EINPROGRESS; - _req->actual = 0; - - spin_lock_irqsave(&udc->lock, flags); - - dev_dbg(udc->isp->dev, - "%s: req %p (%u bytes%s) ep %p(0x%02x)\n", __func__, _req, - _req->length, _req->zero ? " (zlp)" : "", uep, uep->addr); - - req->ep = uep; - - if (uep->addr == 0) { - if (_req->length != udc->ep0_length && - udc->ep0_state != ISP1760_CTRL_DATA_IN) { - dev_dbg(udc->isp->dev, - "%s: invalid length %u for req %p\n", - __func__, _req->length, req); - ret = -EINVAL; - goto done; - } - - switch (udc->ep0_state) { - case ISP1760_CTRL_DATA_IN: - dev_dbg(udc->isp->dev, "%s: transmitting req %p\n", - __func__, req); - - list_add_tail(&req->queue, &uep->queue); - isp1760_udc_transmit(uep, req); - break; - - case ISP1760_CTRL_DATA_OUT: - list_add_tail(&req->queue, &uep->queue); - __isp1760_udc_select_ep(uep, USB_DIR_OUT); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); - break; - - case ISP1760_CTRL_STATUS: - complete = true; - break; - - default: - dev_dbg(udc->isp->dev, "%s: invalid ep0 state\n", - __func__); - ret = -EINVAL; - break; - } - } else if (uep->desc) { - bool empty = list_empty(&uep->queue); - - list_add_tail(&req->queue, &uep->queue); - if ((uep->addr & USB_DIR_IN) && !uep->halted && empty) - isp1760_udc_transmit(uep, req); - else if (!(uep->addr & USB_DIR_IN) && uep->rx_pending) - complete = isp1760_udc_receive(uep, req); - } else { - dev_dbg(udc->isp->dev, - "%s: can't queue request to disabled ep%02x\n", - __func__, uep->addr); - ret = -ESHUTDOWN; - } - -done: - if (ret < 0) - req->ep = NULL; - - spin_unlock_irqrestore(&udc->lock, flags); - - if (complete) - isp1760_udc_request_complete(uep, req, 0); - - return ret; -} - -static int isp1760_ep_dequeue(struct usb_ep *ep, struct usb_request *_req) -{ - struct isp1760_request *req = req_to_udc_req(_req); - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - unsigned long flags; - - dev_dbg(uep->udc->isp->dev, "%s(ep%02x)\n", __func__, uep->addr); - - spin_lock_irqsave(&udc->lock, flags); - - if (req->ep != uep) - req = NULL; - else - list_del(&req->queue); - - spin_unlock_irqrestore(&udc->lock, flags); - - if (!req) - return -EINVAL; - - isp1760_udc_request_complete(uep, req, -ECONNRESET); - return 0; -} - -static int __isp1760_ep_set_halt(struct isp1760_ep *uep, bool stall, bool wedge) -{ - struct isp1760_udc *udc = uep->udc; - int ret; - - if (!uep->addr) { - /* - * Halting the control endpoint is only valid as a delayed error - * response to a SETUP packet. Make sure EP0 is in the right - * stage and that the gadget isn't trying to clear the halt - * condition. - */ - if (WARN_ON(udc->ep0_state == ISP1760_CTRL_SETUP || !stall || - wedge)) { - return -EINVAL; - } - } - - if (uep->addr && !uep->desc) { - dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, - uep->addr); - return -EINVAL; - } - - if (uep->addr & USB_DIR_IN) { - /* Refuse to halt IN endpoints with active transfers. */ - if (!list_empty(&uep->queue)) { - dev_dbg(udc->isp->dev, - "%s: ep%02x has request pending\n", __func__, - uep->addr); - return -EAGAIN; - } - } - - ret = __isp1760_udc_set_halt(uep, stall); - if (ret < 0) - return ret; - - if (!uep->addr) { - /* - * Stalling EP0 completes the control transaction, move back to - * the SETUP state. - */ - udc->ep0_state = ISP1760_CTRL_SETUP; - return 0; - } - - if (wedge) - uep->wedged = true; - else if (!stall) - uep->wedged = false; - - return 0; -} - -static int isp1760_ep_set_halt(struct usb_ep *ep, int value) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - unsigned long flags; - int ret; - - dev_dbg(uep->udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, - value ? "set" : "clear", uep->addr); - - spin_lock_irqsave(&uep->udc->lock, flags); - ret = __isp1760_ep_set_halt(uep, value, false); - spin_unlock_irqrestore(&uep->udc->lock, flags); - - return ret; -} - -static int isp1760_ep_set_wedge(struct usb_ep *ep) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - unsigned long flags; - int ret; - - dev_dbg(uep->udc->isp->dev, "%s: set wedge on ep%02x)\n", __func__, - uep->addr); - - spin_lock_irqsave(&uep->udc->lock, flags); - ret = __isp1760_ep_set_halt(uep, true, true); - spin_unlock_irqrestore(&uep->udc->lock, flags); - - return ret; -} - -static void isp1760_ep_fifo_flush(struct usb_ep *ep) -{ - struct isp1760_ep *uep = ep_to_udc_ep(ep); - struct isp1760_udc *udc = uep->udc; - unsigned long flags; - - spin_lock_irqsave(&udc->lock, flags); - - isp1760_udc_select_ep(uep); - - /* - * Set the CLBUF bit twice to flush both buffers in case double - * buffering is enabled. - */ - isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); - isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); - - spin_unlock_irqrestore(&udc->lock, flags); -} - -static const struct usb_ep_ops isp1760_ep_ops = { - .enable = isp1760_ep_enable, - .disable = isp1760_ep_disable, - .alloc_request = isp1760_ep_alloc_request, - .free_request = isp1760_ep_free_request, - .queue = isp1760_ep_queue, - .dequeue = isp1760_ep_dequeue, - .set_halt = isp1760_ep_set_halt, - .set_wedge = isp1760_ep_set_wedge, - .fifo_flush = isp1760_ep_fifo_flush, -}; - -/* ----------------------------------------------------------------------------- - * Device States - */ - -/* Called with the UDC spinlock held. */ -static void isp1760_udc_connect(struct isp1760_udc *udc) -{ - usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); - mod_timer(&udc->vbus_timer, jiffies + ISP1760_VBUS_POLL_INTERVAL); -} - -/* Called with the UDC spinlock held. */ -static void isp1760_udc_disconnect(struct isp1760_udc *udc) -{ - if (udc->gadget.state < USB_STATE_POWERED) - return; - - dev_dbg(udc->isp->dev, "Device disconnected in state %u\n", - udc->gadget.state); - - udc->gadget.speed = USB_SPEED_UNKNOWN; - usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); - - if (udc->driver->disconnect) - udc->driver->disconnect(&udc->gadget); - - del_timer(&udc->vbus_timer); - - /* TODO Reset all endpoints ? */ -} - -static void isp1760_udc_init_hw(struct isp1760_udc *udc) -{ - /* - * The device controller currently shares its interrupt with the host - * controller, the DC_IRQ polarity and signaling mode are ignored. Set - * the to active-low level-triggered. - * - * Configure the control, in and out pipes to generate interrupts on - * ACK tokens only (and NYET for the out pipe). The default - * configuration also generates an interrupt on the first NACK token. - */ - isp1760_udc_write(udc, DC_INTCONF, DC_CDBGMOD_ACK | DC_DDBGMODIN_ACK | - DC_DDBGMODOUT_ACK_NYET); - - isp1760_udc_write(udc, DC_INTENABLE, DC_IEPRXTX(7) | DC_IEPRXTX(6) | - DC_IEPRXTX(5) | DC_IEPRXTX(4) | DC_IEPRXTX(3) | - DC_IEPRXTX(2) | DC_IEPRXTX(1) | DC_IEPRXTX(0) | - DC_IEP0SETUP | DC_IEVBUS | DC_IERESM | DC_IESUSP | - DC_IEHS_STA | DC_IEBRST); - - if (udc->connected) - isp1760_set_pullup(udc->isp, true); - - isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN); -} - -static void isp1760_udc_reset(struct isp1760_udc *udc) -{ - unsigned long flags; - - spin_lock_irqsave(&udc->lock, flags); - - /* - * The bus reset has reset most registers to their default value, - * reinitialize the UDC hardware. - */ - isp1760_udc_init_hw(udc); - - udc->ep0_state = ISP1760_CTRL_SETUP; - udc->gadget.speed = USB_SPEED_FULL; - - usb_gadget_udc_reset(&udc->gadget, udc->driver); - - spin_unlock_irqrestore(&udc->lock, flags); -} - -static void isp1760_udc_suspend(struct isp1760_udc *udc) -{ - if (udc->gadget.state < USB_STATE_DEFAULT) - return; - - if (udc->driver->suspend) - udc->driver->suspend(&udc->gadget); -} - -static void isp1760_udc_resume(struct isp1760_udc *udc) -{ - if (udc->gadget.state < USB_STATE_DEFAULT) - return; - - if (udc->driver->resume) - udc->driver->resume(&udc->gadget); -} - -/* ----------------------------------------------------------------------------- - * Gadget Operations - */ - -static int isp1760_udc_get_frame(struct usb_gadget *gadget) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - return isp1760_udc_read(udc, DC_FRAMENUM) & ((1 << 11) - 1); -} - -static int isp1760_udc_wakeup(struct usb_gadget *gadget) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - dev_dbg(udc->isp->dev, "%s\n", __func__); - return -ENOTSUPP; -} - -static int isp1760_udc_set_selfpowered(struct usb_gadget *gadget, - int is_selfpowered) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - if (is_selfpowered) - udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; - else - udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); - - return 0; -} - -static int isp1760_udc_pullup(struct usb_gadget *gadget, int is_on) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - isp1760_set_pullup(udc->isp, is_on); - udc->connected = is_on; - - return 0; -} - -static int isp1760_udc_start(struct usb_gadget *gadget, - struct usb_gadget_driver *driver) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - /* The hardware doesn't support low speed. */ - if (driver->max_speed < USB_SPEED_FULL) { - dev_err(udc->isp->dev, "Invalid gadget driver\n"); - return -EINVAL; - } - - spin_lock(&udc->lock); - - if (udc->driver) { - dev_err(udc->isp->dev, "UDC already has a gadget driver\n"); - spin_unlock(&udc->lock); - return -EBUSY; - } - - udc->driver = driver; - - spin_unlock(&udc->lock); - - dev_dbg(udc->isp->dev, "starting UDC with driver %s\n", - driver->function); - - udc->devstatus = 0; - udc->connected = true; - - usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); - - /* DMA isn't supported yet, don't enable the DMA clock. */ - isp1760_udc_write(udc, DC_MODE, DC_GLINTENA); - - isp1760_udc_init_hw(udc); - - dev_dbg(udc->isp->dev, "UDC started with driver %s\n", - driver->function); - - return 0; -} - -static int isp1760_udc_stop(struct usb_gadget *gadget) -{ - struct isp1760_udc *udc = gadget_to_udc(gadget); - - dev_dbg(udc->isp->dev, "%s\n", __func__); - - del_timer_sync(&udc->vbus_timer); - - isp1760_udc_write(udc, DC_MODE, 0); - - spin_lock(&udc->lock); - udc->driver = NULL; - spin_unlock(&udc->lock); - - return 0; -} - -static struct usb_gadget_ops isp1760_udc_ops = { - .get_frame = isp1760_udc_get_frame, - .wakeup = isp1760_udc_wakeup, - .set_selfpowered = isp1760_udc_set_selfpowered, - .pullup = isp1760_udc_pullup, - .udc_start = isp1760_udc_start, - .udc_stop = isp1760_udc_stop, -}; - -/* ----------------------------------------------------------------------------- - * Interrupt Handling - */ - -static irqreturn_t isp1760_udc_irq(int irq, void *dev) -{ - struct isp1760_udc *udc = dev; - unsigned int i; - u32 status; - - status = isp1760_udc_read(udc, DC_INTERRUPT) - & isp1760_udc_read(udc, DC_INTENABLE); - isp1760_udc_write(udc, DC_INTERRUPT, status); - - if (status & DC_IEVBUS) { - dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__); - /* The VBUS interrupt is only triggered when VBUS appears. */ - spin_lock(&udc->lock); - isp1760_udc_connect(udc); - spin_unlock(&udc->lock); - } - - if (status & DC_IEBRST) { - dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__); - - isp1760_udc_reset(udc); - } - - for (i = 0; i <= 7; ++i) { - struct isp1760_ep *ep = &udc->ep[i*2]; - - if (status & DC_IEPTX(i)) { - dev_dbg(udc->isp->dev, "%s(EPTX%u)\n", __func__, i); - isp1760_ep_tx_complete(ep); - } - - if (status & DC_IEPRX(i)) { - dev_dbg(udc->isp->dev, "%s(EPRX%u)\n", __func__, i); - isp1760_ep_rx_ready(i ? ep - 1 : ep); - } - } - - if (status & DC_IEP0SETUP) { - dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__); - - isp1760_ep0_setup(udc); - } - - if (status & DC_IERESM) { - dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__); - isp1760_udc_resume(udc); - } - - if (status & DC_IESUSP) { - dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__); - - spin_lock(&udc->lock); - if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) - isp1760_udc_disconnect(udc); - else - isp1760_udc_suspend(udc); - spin_unlock(&udc->lock); - } - - if (status & DC_IEHS_STA) { - dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__); - udc->gadget.speed = USB_SPEED_HIGH; - } - - return status ? IRQ_HANDLED : IRQ_NONE; -} - -static void isp1760_udc_vbus_poll(unsigned long data) -{ - struct isp1760_udc *udc = (struct isp1760_udc *)data; - unsigned long flags; - - spin_lock_irqsave(&udc->lock, flags); - - if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) - isp1760_udc_disconnect(udc); - else if (udc->gadget.state >= USB_STATE_POWERED) - mod_timer(&udc->vbus_timer, - jiffies + ISP1760_VBUS_POLL_INTERVAL); - - spin_unlock_irqrestore(&udc->lock, flags); -} - -/* ----------------------------------------------------------------------------- - * Registration - */ - -static void isp1760_udc_init_eps(struct isp1760_udc *udc) -{ - unsigned int i; - - INIT_LIST_HEAD(&udc->gadget.ep_list); - - for (i = 0; i < ARRAY_SIZE(udc->ep); ++i) { - struct isp1760_ep *ep = &udc->ep[i]; - unsigned int ep_num = (i + 1) / 2; - bool is_in = !(i & 1); - - ep->udc = udc; - - INIT_LIST_HEAD(&ep->queue); - - ep->addr = (ep_num && is_in ? USB_DIR_IN : USB_DIR_OUT) - | ep_num; - ep->desc = NULL; - - sprintf(ep->name, "ep%u%s", ep_num, - ep_num ? (is_in ? "in" : "out") : ""); - - ep->ep.ops = &isp1760_ep_ops; - ep->ep.name = ep->name; - - /* - * Hardcode the maximum packet sizes for now, to 64 bytes for - * the control endpoint and 512 bytes for all other endpoints. - * This fits in the 8kB FIFO without double-buffering. - */ - if (ep_num == 0) { - ep->ep.maxpacket = 64; - ep->maxpacket = 64; - udc->gadget.ep0 = &ep->ep; - } else { - ep->ep.maxpacket = 512; - ep->maxpacket = 0; - list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); - } - } -} - -static int isp1760_udc_init(struct isp1760_udc *udc) -{ - u16 scratch; - u32 chipid; - - /* - * Check that the controller is present by writing to the scratch - * register, modifying the bus pattern by reading from the chip ID - * register, and reading the scratch register value back. The chip ID - * and scratch register contents must match the expected values. - */ - isp1760_udc_write(udc, DC_SCRATCH, 0xbabe); - chipid = isp1760_udc_read(udc, DC_CHIPID); - scratch = isp1760_udc_read(udc, DC_SCRATCH); - - if (scratch != 0xbabe) { - dev_err(udc->isp->dev, - "udc: scratch test failed (0x%04x/0x%08x)\n", - scratch, chipid); - return -ENODEV; - } - - if (chipid != 0x00011582) { - dev_err(udc->isp->dev, "udc: invalid chip ID 0x%08x\n", chipid); - return -ENODEV; - } - - /* Reset the device controller. */ - isp1760_udc_write(udc, DC_MODE, DC_SFRESET); - usleep_range(10000, 11000); - isp1760_udc_write(udc, DC_MODE, 0); - usleep_range(10000, 11000); - - return 0; -} - -int isp1760_udc_register(struct isp1760_device *isp, int irq, - unsigned long irqflags) -{ - struct isp1760_udc *udc = &isp->udc; - const char *devname; - int ret; - - udc->irq = -1; - udc->isp = isp; - udc->regs = isp->regs; - - spin_lock_init(&udc->lock); - setup_timer(&udc->vbus_timer, isp1760_udc_vbus_poll, - (unsigned long)udc); - - ret = isp1760_udc_init(udc); - if (ret < 0) - return ret; - - devname = dev_name(isp->dev); - udc->irqname = kmalloc(strlen(devname) + 7, GFP_KERNEL); - if (!udc->irqname) - return -ENOMEM; - - sprintf(udc->irqname, "%s (udc)", devname); - - ret = request_irq(irq, isp1760_udc_irq, IRQF_SHARED | IRQF_DISABLED | - irqflags, udc->irqname, udc); - if (ret < 0) - goto error; - - udc->irq = irq; - - /* - * Initialize the gadget static fields and register its device. Gadget - * fields that vary during the life time of the gadget are initialized - * by the UDC core. - */ - udc->gadget.ops = &isp1760_udc_ops; - udc->gadget.speed = USB_SPEED_UNKNOWN; - udc->gadget.max_speed = USB_SPEED_HIGH; - udc->gadget.name = "isp1761_udc"; - - isp1760_udc_init_eps(udc); - - ret = usb_add_gadget_udc(isp->dev, &udc->gadget); - if (ret < 0) - goto error; - - return 0; - -error: - if (udc->irq >= 0) - free_irq(udc->irq, udc); - kfree(udc->irqname); - - return ret; -} - -void isp1760_udc_unregister(struct isp1760_device *isp) -{ - struct isp1760_udc *udc = &isp->udc; - - usb_del_gadget_udc(&udc->gadget); - - free_irq(udc->irq, udc); - kfree(udc->irqname); -} diff --git a/drivers/usb/host/isp1760-udc.h b/drivers/usb/host/isp1760-udc.h deleted file mode 100644 index 4af6ba6..0000000 --- a/drivers/usb/host/isp1760-udc.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Driver for the NXP ISP1761 device controller - * - * Copyright 2014 Ideas on Board Oy - * - * Contacts: - * Laurent Pinchart - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - */ - -#ifndef _ISP1760_UDC_H_ -#define _ISP1760_UDC_H_ - -#include -#include -#include -#include -#include - -struct isp1760_device; -struct isp1760_udc; - -enum isp1760_ctrl_state { - ISP1760_CTRL_SETUP, /* Waiting for a SETUP transaction */ - ISP1760_CTRL_DATA_IN, /* Setup received, data IN stage */ - ISP1760_CTRL_DATA_OUT, /* Setup received, data OUT stage */ - ISP1760_CTRL_STATUS, /* 0-length request in status stage */ -}; - -struct isp1760_ep { - struct isp1760_udc *udc; - struct usb_ep ep; - - struct list_head queue; - - unsigned int addr; - unsigned int maxpacket; - char name[7]; - - const struct usb_endpoint_descriptor *desc; - - bool rx_pending; - bool halted; - bool wedged; -}; - -/** - * struct isp1760_udc - UDC state information - * irq: IRQ number - * irqname: IRQ name (as passed to request_irq) - * regs: Base address of the UDC registers - * driver: Gadget driver - * gadget: Gadget device - * lock: Protects driver, vbus_timer, ep, ep0_*, DC_EPINDEX register - * ep: Array of endpoints - * ep0_state: Control request state for endpoint 0 - * ep0_dir: Direction of the current control request - * ep0_length: Length of the current control request - * connected: Tracks gadget driver bus connection state - */ -struct isp1760_udc { -#if CONFIG_USB_ISP1761_UDC - struct isp1760_device *isp; - - int irq; - char *irqname; - void __iomem *regs; - - struct usb_gadget_driver *driver; - struct usb_gadget gadget; - - spinlock_t lock; - struct timer_list vbus_timer; - - struct isp1760_ep ep[15]; - - enum isp1760_ctrl_state ep0_state; - u8 ep0_dir; - u16 ep0_length; - - bool connected; - - unsigned int devstatus; -#endif -}; - -#if CONFIG_USB_ISP1761_UDC -int isp1760_udc_register(struct isp1760_device *isp, int irq, - unsigned long irqflags); -void isp1760_udc_unregister(struct isp1760_device *isp); -#else -static inline int isp1760_udc_register(struct isp1760_device *isp, int irq, - unsigned long irqflags) -{ - return 0; -} - -static inline void isp1760_udc_unregister(struct isp1760_device *isp) -{ -} -#endif - -#endif diff --git a/drivers/usb/isp1760/Kconfig b/drivers/usb/isp1760/Kconfig new file mode 100644 index 0000000..c09ab8f --- /dev/null +++ b/drivers/usb/isp1760/Kconfig @@ -0,0 +1,22 @@ +config USB_ISP1760 + tristate "NXP ISP 1760/1761 support" + depends on USB + help + Say Y or M here if your system as an ISP1760 USB host controller + or an ISP1761 USB dual-role controller. + + This driver does not support isochronous transfers or OTG. + This USB controller is usually attached to a non-DMA-Master + capable bus. NXP's eval kit brings this chip on PCI card + where the chip itself is behind a PLB to simulate such + a bus. + + To compile this driver as a module, choose M here: the + module will be called isp1760. + +config USB_ISP1761_UDC + boolean "NXP ISP1761 USB Device Controller" + depends on USB_ISP1760 && USB_GADGET + help + The NXP ISP1761 is a dual-role high-speed USB host and device + controller. diff --git a/drivers/usb/isp1760/Makefile b/drivers/usb/isp1760/Makefile new file mode 100644 index 0000000..698ccb0 --- /dev/null +++ b/drivers/usb/isp1760/Makefile @@ -0,0 +1,4 @@ +isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o +isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o + +obj-$(CONFIG_USB_ISP1760) += isp1760.o diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c new file mode 100644 index 0000000..727e90a --- /dev/null +++ b/drivers/usb/isp1760/isp1760-core.c @@ -0,0 +1,171 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-hcd.h" +#include "isp1760-regs.h" +#include "isp1760-udc.h" + +static void isp1760_init_core(struct isp1760_device *isp) +{ + u32 otgctrl; + u32 hwmode; + + /* Low-level chip reset */ + if (isp->rst_gpio) { + gpiod_set_value_cansleep(isp->rst_gpio, 1); + mdelay(50); + gpiod_set_value_cansleep(isp->rst_gpio, 0); + } + + /* + * Reset the host controller, including the CPU interface + * configuration. + */ + isp1760_write32(isp->regs, HC_RESET_REG, SW_RESET_RESET_ALL); + msleep(100); + + /* Setup HW Mode Control: This assumes a level active-low interrupt */ + hwmode = HW_DATA_BUS_32BIT; + + if (isp->devflags & ISP1760_FLAG_BUS_WIDTH_16) + hwmode &= ~HW_DATA_BUS_32BIT; + if (isp->devflags & ISP1760_FLAG_ANALOG_OC) + hwmode |= HW_ANA_DIGI_OC; + if (isp->devflags & ISP1760_FLAG_DACK_POL_HIGH) + hwmode |= HW_DACK_POL_HIGH; + if (isp->devflags & ISP1760_FLAG_DREQ_POL_HIGH) + hwmode |= HW_DREQ_POL_HIGH; + if (isp->devflags & ISP1760_FLAG_INTR_POL_HIGH) + hwmode |= HW_INTR_HIGH_ACT; + if (isp->devflags & ISP1760_FLAG_INTR_EDGE_TRIG) + hwmode |= HW_INTR_EDGE_TRIG; + + /* + * The ISP1761 has a dedicated DC IRQ line but supports sharing the HC + * IRQ line for both the host and device controllers. Hardcode IRQ + * sharing for now and disable the DC interrupts globally to avoid + * spurious interrupts during HCD registration. + */ + if (isp->devflags & ISP1760_FLAG_ISP1761) { + isp1760_write32(isp->regs, DC_MODE, 0); + hwmode |= HW_COMN_IRQ; + } + + /* + * We have to set this first in case we're in 16-bit mode. + * Write it twice to ensure correct upper bits if switching + * to 16-bit mode. + */ + isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + isp1760_write32(isp->regs, HC_HW_MODE_CTRL, hwmode); + + /* + * PORT 1 Control register of the ISP1760 is the OTG control register + * on ISP1761. + * + * TODO: Really support OTG. For now we configure port 1 in device mode + * when OTG is requested. + */ + if ((isp->devflags & ISP1760_FLAG_ISP1761) && + (isp->devflags & ISP1760_FLAG_OTG_EN)) + otgctrl = ((HW_DM_PULLDOWN | HW_DP_PULLDOWN) << 16) + | HW_OTG_DISABLE; + else + otgctrl = (HW_SW_SEL_HC_DC << 16) + | (HW_VBUS_DRV | HW_SEL_CP_EXT); + + isp1760_write32(isp->regs, HC_PORT1_CTRL, otgctrl); + + dev_info(isp->dev, "bus width: %u, oc: %s\n", + isp->devflags & ISP1760_FLAG_BUS_WIDTH_16 ? 16 : 32, + isp->devflags & ISP1760_FLAG_ANALOG_OC ? "analog" : "digital"); +} + +void isp1760_set_pullup(struct isp1760_device *isp, bool enable) +{ + isp1760_write32(isp->regs, HW_OTG_CTRL_SET, + enable ? HW_DP_PULLUP : HW_DP_PULLUP << 16); +} + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags) +{ + struct isp1760_device *isp; + int ret; + + if (usb_disabled()) + return -ENODEV; + + /* prevent usb-core allocating DMA pages */ + dev->dma_mask = NULL; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->dev = dev; + isp->devflags = devflags; + + isp->rst_gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); + if (IS_ERR(isp->rst_gpio)) + return PTR_ERR(isp->rst_gpio); + + isp->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(isp->regs)) + return PTR_ERR(isp->regs); + + isp1760_init_core(isp); + + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, + irqflags | IRQF_SHARED, dev); + if (ret < 0) + return ret; + + if (devflags & ISP1760_FLAG_ISP1761) { + ret = isp1760_udc_register(isp, irq, irqflags | IRQF_SHARED | + IRQF_DISABLED); + if (ret < 0) { + isp1760_hcd_unregister(&isp->hcd); + return ret; + } + } + + dev_set_drvdata(dev, isp); + + return 0; +} + +void isp1760_unregister(struct device *dev) +{ + struct isp1760_device *isp = dev_get_drvdata(dev); + + if (isp->devflags & ISP1760_FLAG_ISP1761) + isp1760_udc_unregister(isp); + + isp1760_hcd_unregister(&isp->hcd); +} + +MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP"); +MODULE_AUTHOR("Sebastian Siewior "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/isp1760/isp1760-core.h b/drivers/usb/isp1760/isp1760-core.h new file mode 100644 index 0000000..c70f836 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-core.h @@ -0,0 +1,68 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef _ISP1760_CORE_H_ +#define _ISP1760_CORE_H_ + +#include + +#include "isp1760-hcd.h" +#include "isp1760-udc.h" + +struct device; +struct gpio_desc; + +/* + * Device flags that can vary from board to board. All of these + * indicate the most "atypical" case, so that a devflags of 0 is + * a sane default configuration. + */ +#define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ +#define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ +#define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ +#define ISP1760_FLAG_DACK_POL_HIGH 0x00000010 /* DACK active high */ +#define ISP1760_FLAG_DREQ_POL_HIGH 0x00000020 /* DREQ active high */ +#define ISP1760_FLAG_ISP1761 0x00000040 /* Chip is ISP1761 */ +#define ISP1760_FLAG_INTR_POL_HIGH 0x00000080 /* Interrupt polarity active high */ +#define ISP1760_FLAG_INTR_EDGE_TRIG 0x00000100 /* Interrupt edge triggered */ + +struct isp1760_device { + struct device *dev; + + void __iomem *regs; + unsigned int devflags; + struct gpio_desc *rst_gpio; + + struct isp1760_hcd hcd; + struct isp1760_udc udc; +}; + +int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, + struct device *dev, unsigned int devflags); +void isp1760_unregister(struct device *dev); + +void isp1760_set_pullup(struct isp1760_device *isp, bool enable); + +static inline u32 isp1760_read32(void __iomem *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void isp1760_write32(void __iomem *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +#endif diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c new file mode 100644 index 0000000..568446c --- /dev/null +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -0,0 +1,2231 @@ +/* + * Driver for the NXP ISP1760 chip + * + * However, the code might contain some bugs. What doesn't work for sure is: + * - ISO + * - OTG + e The interrupt line is configured as active low, level. + * + * (c) 2007 Sebastian Siewior + * + * (c) 2011 Arvid Brodin + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-hcd.h" +#include "isp1760-regs.h" + +static struct kmem_cache *qtd_cachep; +static struct kmem_cache *qh_cachep; +static struct kmem_cache *urb_listitem_cachep; + +typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd); + +static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd) +{ + return *(struct isp1760_hcd **)hcd->hcd_priv; +} + +/* urb state*/ +#define DELETE_URB (0x0008) +#define NO_TRANSFER_ACTIVE (0xffffffff) + +/* Philips Proprietary Transfer Descriptor (PTD) */ +typedef __u32 __bitwise __dw; +struct ptd { + __dw dw0; + __dw dw1; + __dw dw2; + __dw dw3; + __dw dw4; + __dw dw5; + __dw dw6; + __dw dw7; +}; +#define PTD_OFFSET 0x0400 +#define ISO_PTD_OFFSET 0x0400 +#define INT_PTD_OFFSET 0x0800 +#define ATL_PTD_OFFSET 0x0c00 +#define PAYLOAD_OFFSET 0x1000 + + +/* ATL */ +/* DW0 */ +#define DW0_VALID_BIT 1 +#define FROM_DW0_VALID(x) ((x) & 0x01) +#define TO_DW0_LENGTH(x) (((u32) x) << 3) +#define TO_DW0_MAXPACKET(x) (((u32) x) << 18) +#define TO_DW0_MULTI(x) (((u32) x) << 29) +#define TO_DW0_ENDPOINT(x) (((u32) x) << 31) +/* DW1 */ +#define TO_DW1_DEVICE_ADDR(x) (((u32) x) << 3) +#define TO_DW1_PID_TOKEN(x) (((u32) x) << 10) +#define DW1_TRANS_BULK ((u32) 2 << 12) +#define DW1_TRANS_INT ((u32) 3 << 12) +#define DW1_TRANS_SPLIT ((u32) 1 << 14) +#define DW1_SE_USB_LOSPEED ((u32) 2 << 16) +#define TO_DW1_PORT_NUM(x) (((u32) x) << 18) +#define TO_DW1_HUB_NUM(x) (((u32) x) << 25) +/* DW2 */ +#define TO_DW2_DATA_START_ADDR(x) (((u32) x) << 8) +#define TO_DW2_RL(x) ((x) << 25) +#define FROM_DW2_RL(x) (((x) >> 25) & 0xf) +/* DW3 */ +#define FROM_DW3_NRBYTESTRANSFERRED(x) ((x) & 0x7fff) +#define FROM_DW3_SCS_NRBYTESTRANSFERRED(x) ((x) & 0x07ff) +#define TO_DW3_NAKCOUNT(x) ((x) << 19) +#define FROM_DW3_NAKCOUNT(x) (((x) >> 19) & 0xf) +#define TO_DW3_CERR(x) ((x) << 23) +#define FROM_DW3_CERR(x) (((x) >> 23) & 0x3) +#define TO_DW3_DATA_TOGGLE(x) ((x) << 25) +#define FROM_DW3_DATA_TOGGLE(x) (((x) >> 25) & 0x1) +#define TO_DW3_PING(x) ((x) << 26) +#define FROM_DW3_PING(x) (((x) >> 26) & 0x1) +#define DW3_ERROR_BIT (1 << 28) +#define DW3_BABBLE_BIT (1 << 29) +#define DW3_HALT_BIT (1 << 30) +#define DW3_ACTIVE_BIT (1 << 31) +#define FROM_DW3_ACTIVE(x) (((x) >> 31) & 0x01) + +#define INT_UNDERRUN (1 << 2) +#define INT_BABBLE (1 << 1) +#define INT_EXACT (1 << 0) + +#define SETUP_PID (2) +#define IN_PID (1) +#define OUT_PID (0) + +/* Errata 1 */ +#define RL_COUNTER (0) +#define NAK_COUNTER (0) +#define ERR_COUNTER (2) + +struct isp1760_qtd { + u8 packet_type; + void *data_buffer; + u32 payload_addr; + + /* the rest is HCD-private */ + struct list_head qtd_list; + struct urb *urb; + size_t length; + size_t actual_length; + + /* QTD_ENQUEUED: waiting for transfer (inactive) */ + /* QTD_PAYLOAD_ALLOC: chip mem has been allocated for payload */ + /* QTD_XFER_STARTED: valid ptd has been written to isp176x - only + interrupt handler may touch this qtd! */ + /* QTD_XFER_COMPLETE: payload has been transferred successfully */ + /* QTD_RETIRE: transfer error/abort qtd */ +#define QTD_ENQUEUED 0 +#define QTD_PAYLOAD_ALLOC 1 +#define QTD_XFER_STARTED 2 +#define QTD_XFER_COMPLETE 3 +#define QTD_RETIRE 4 + u32 status; +}; + +/* Queue head, one for each active endpoint */ +struct isp1760_qh { + struct list_head qh_list; + struct list_head qtd_list; + u32 toggle; + u32 ping; + int slot; + int tt_buffer_dirty; /* See USB2.0 spec section 11.17.5 */ +}; + +struct urb_listitem { + struct list_head urb_list; + struct urb *urb; +}; + +/* + * Access functions for isp176x registers (addresses 0..0x03FF). + */ +static u32 reg_read32(void __iomem *base, u32 reg) +{ + return isp1760_read32(base, reg); +} + +static void reg_write32(void __iomem *base, u32 reg, u32 val) +{ + isp1760_write32(base, reg, val); +} + +/* + * Access functions for isp176x memory (offset >= 0x0400). + * + * bank_reads8() reads memory locations prefetched by an earlier write to + * HC_MEMORY_REG (see isp176x datasheet). Unless you want to do fancy multi- + * bank optimizations, you should use the more generic mem_reads8() below. + * + * For access to ptd memory, use the specialized ptd_read() and ptd_write() + * below. + * + * These functions copy via MMIO data to/from the device. memcpy_{to|from}io() + * doesn't quite work because some people have to enforce 32-bit access + */ +static void bank_reads8(void __iomem *src_base, u32 src_offset, u32 bank_addr, + __u32 *dst, u32 bytes) +{ + __u32 __iomem *src; + u32 val; + __u8 *src_byteptr; + __u8 *dst_byteptr; + + src = src_base + (bank_addr | src_offset); + + if (src_offset < PAYLOAD_OFFSET) { + while (bytes >= 4) { + *dst = le32_to_cpu(__raw_readl(src)); + bytes -= 4; + src++; + dst++; + } + } else { + while (bytes >= 4) { + *dst = __raw_readl(src); + bytes -= 4; + src++; + dst++; + } + } + + if (!bytes) + return; + + /* in case we have 3, 2 or 1 by left. The dst buffer may not be fully + * allocated. + */ + if (src_offset < PAYLOAD_OFFSET) + val = le32_to_cpu(__raw_readl(src)); + else + val = __raw_readl(src); + + dst_byteptr = (void *) dst; + src_byteptr = (void *) &val; + while (bytes > 0) { + *dst_byteptr = *src_byteptr; + dst_byteptr++; + src_byteptr++; + bytes--; + } +} + +static void mem_reads8(void __iomem *src_base, u32 src_offset, void *dst, + u32 bytes) +{ + reg_write32(src_base, HC_MEMORY_REG, src_offset + ISP_BANK(0)); + ndelay(90); + bank_reads8(src_base, src_offset, ISP_BANK(0), dst, bytes); +} + +static void mem_writes8(void __iomem *dst_base, u32 dst_offset, + __u32 const *src, u32 bytes) +{ + __u32 __iomem *dst; + + dst = dst_base + dst_offset; + + if (dst_offset < PAYLOAD_OFFSET) { + while (bytes >= 4) { + __raw_writel(cpu_to_le32(*src), dst); + bytes -= 4; + src++; + dst++; + } + } else { + while (bytes >= 4) { + __raw_writel(*src, dst); + bytes -= 4; + src++; + dst++; + } + } + + if (!bytes) + return; + /* in case we have 3, 2 or 1 bytes left. The buffer is allocated and the + * extra bytes should not be read by the HW. + */ + + if (dst_offset < PAYLOAD_OFFSET) + __raw_writel(cpu_to_le32(*src), dst); + else + __raw_writel(*src, dst); +} + +/* + * Read and write ptds. 'ptd_offset' should be one of ISO_PTD_OFFSET, + * INT_PTD_OFFSET, and ATL_PTD_OFFSET. 'slot' should be less than 32. + */ +static void ptd_read(void __iomem *base, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + reg_write32(base, HC_MEMORY_REG, + ISP_BANK(0) + ptd_offset + slot*sizeof(*ptd)); + ndelay(90); + bank_reads8(base, ptd_offset + slot*sizeof(*ptd), ISP_BANK(0), + (void *) ptd, sizeof(*ptd)); +} + +static void ptd_write(void __iomem *base, u32 ptd_offset, u32 slot, + struct ptd *ptd) +{ + mem_writes8(base, ptd_offset + slot*sizeof(*ptd) + sizeof(ptd->dw0), + &ptd->dw1, 7*sizeof(ptd->dw1)); + /* Make sure dw0 gets written last (after other dw's and after payload) + since it contains the enable bit */ + wmb(); + mem_writes8(base, ptd_offset + slot*sizeof(*ptd), &ptd->dw0, + sizeof(ptd->dw0)); +} + + +/* memory management of the 60kb on the chip from 0x1000 to 0xffff */ +static void init_memory(struct isp1760_hcd *priv) +{ + int i, curr; + u32 payload_addr; + + payload_addr = PAYLOAD_OFFSET; + for (i = 0; i < BLOCK_1_NUM; i++) { + priv->memory_pool[i].start = payload_addr; + priv->memory_pool[i].size = BLOCK_1_SIZE; + priv->memory_pool[i].free = 1; + payload_addr += priv->memory_pool[i].size; + } + + curr = i; + for (i = 0; i < BLOCK_2_NUM; i++) { + priv->memory_pool[curr + i].start = payload_addr; + priv->memory_pool[curr + i].size = BLOCK_2_SIZE; + priv->memory_pool[curr + i].free = 1; + payload_addr += priv->memory_pool[curr + i].size; + } + + curr = i; + for (i = 0; i < BLOCK_3_NUM; i++) { + priv->memory_pool[curr + i].start = payload_addr; + priv->memory_pool[curr + i].size = BLOCK_3_SIZE; + priv->memory_pool[curr + i].free = 1; + payload_addr += priv->memory_pool[curr + i].size; + } + + WARN_ON(payload_addr - priv->memory_pool[0].start > PAYLOAD_AREA_SIZE); +} + +static void alloc_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int i; + + WARN_ON(qtd->payload_addr); + + if (!qtd->length) + return; + + for (i = 0; i < BLOCKS; i++) { + if (priv->memory_pool[i].size >= qtd->length && + priv->memory_pool[i].free) { + priv->memory_pool[i].free = 0; + qtd->payload_addr = priv->memory_pool[i].start; + return; + } + } +} + +static void free_mem(struct usb_hcd *hcd, struct isp1760_qtd *qtd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int i; + + if (!qtd->payload_addr) + return; + + for (i = 0; i < BLOCKS; i++) { + if (priv->memory_pool[i].start == qtd->payload_addr) { + WARN_ON(priv->memory_pool[i].free); + priv->memory_pool[i].free = 1; + qtd->payload_addr = 0; + return; + } + } + + dev_err(hcd->self.controller, "%s: Invalid pointer: %08x\n", + __func__, qtd->payload_addr); + WARN_ON(1); + qtd->payload_addr = 0; +} + +static int handshake(struct usb_hcd *hcd, u32 reg, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = reg_read32(hcd->regs, reg); + if (result == ~0) + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct usb_hcd *hcd) +{ + int retval; + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + u32 command = reg_read32(hcd->regs, HC_USBCMD); + + command |= CMD_RESET; + reg_write32(hcd->regs, HC_USBCMD, command); + hcd->state = HC_STATE_HALT; + priv->next_statechange = jiffies; + retval = handshake(hcd, HC_USBCMD, + CMD_RESET, 0, 250 * 1000); + return retval; +} + +static struct isp1760_qh *qh_alloc(gfp_t flags) +{ + struct isp1760_qh *qh; + + qh = kmem_cache_zalloc(qh_cachep, flags); + if (!qh) + return NULL; + + INIT_LIST_HEAD(&qh->qh_list); + INIT_LIST_HEAD(&qh->qtd_list); + qh->slot = -1; + + return qh; +} + +static void qh_free(struct isp1760_qh *qh) +{ + WARN_ON(!list_empty(&qh->qtd_list)); + WARN_ON(qh->slot > -1); + kmem_cache_free(qh_cachep, qh); +} + +/* one-time init, only for memory state */ +static int priv_init(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 hcc_params; + int i; + + spin_lock_init(&priv->lock); + + for (i = 0; i < QH_END; i++) + INIT_LIST_HEAD(&priv->qh_list[i]); + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + priv->periodic_size = DEFAULT_I_TDPS; + + /* controllers may cache some of the periodic schedule ... */ + hcc_params = reg_read32(hcd->regs, HC_HCCPARAMS); + /* full frame cache */ + if (HCC_ISOC_CACHE(hcc_params)) + priv->i_thresh = 8; + else /* N microframes cached */ + priv->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + return 0; +} + +static int isp1760_hc_setup(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int result; + u32 scratch, hwmode; + + reg_write32(hcd->regs, HC_SCRATCH_REG, 0xdeadbabe); + /* Change bus pattern */ + scratch = reg_read32(hcd->regs, HC_CHIP_ID_REG); + scratch = reg_read32(hcd->regs, HC_SCRATCH_REG); + if (scratch != 0xdeadbabe) { + dev_err(hcd->self.controller, "Scratch test failed.\n"); + return -ENODEV; + } + + /* + * The RESET_HC bit in the SW_RESET register is supposed to reset the + * host controller without touching the CPU interface registers, but at + * least on the ISP1761 it seems to behave as the RESET_ALL bit and + * reset the whole device. We thus can't use it here, so let's reset + * the host controller through the EHCI USB Command register. The device + * has been reset in core code anyway, so this shouldn't matter. + */ + reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, 0); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, NO_TRANSFER_ACTIVE); + + result = ehci_reset(hcd); + if (result) + return result; + + /* Step 11 passed */ + + /* ATL reset */ + hwmode = reg_read32(hcd->regs, HC_HW_MODE_CTRL) & ~ALL_ATX_RESET; + reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode | ALL_ATX_RESET); + mdelay(10); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, hwmode); + + reg_write32(hcd->regs, HC_INTERRUPT_ENABLE, INTERRUPT_ENABLE_MASK); + + priv->hcs_params = reg_read32(hcd->regs, HC_HCSPARAMS); + + return priv_init(hcd); +} + +static u32 base_to_chip(u32 base) +{ + return ((base - 0x400) >> 3); +} + +static int last_qtd_of_urb(struct isp1760_qtd *qtd, struct isp1760_qh *qh) +{ + struct urb *urb; + + if (list_is_last(&qtd->qtd_list, &qh->qtd_list)) + return 1; + + urb = qtd->urb; + qtd = list_entry(qtd->qtd_list.next, typeof(*qtd), qtd_list); + return (qtd->urb != urb); +} + +/* magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ + +static void create_ptd_atl(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + u32 maxpacket; + u32 multi; + u32 rl = RL_COUNTER; + u32 nak = NAK_COUNTER; + + memset(ptd, 0, sizeof(*ptd)); + + /* according to 3.6.2, max packet len can not be > 0x400 */ + maxpacket = usb_maxpacket(qtd->urb->dev, qtd->urb->pipe, + usb_pipeout(qtd->urb->pipe)); + multi = 1 + ((maxpacket >> 11) & 0x3); + maxpacket &= 0x7ff; + + /* DW0 */ + ptd->dw0 = DW0_VALID_BIT; + ptd->dw0 |= TO_DW0_LENGTH(qtd->length); + ptd->dw0 |= TO_DW0_MAXPACKET(maxpacket); + ptd->dw0 |= TO_DW0_ENDPOINT(usb_pipeendpoint(qtd->urb->pipe)); + + /* DW1 */ + ptd->dw1 = usb_pipeendpoint(qtd->urb->pipe) >> 1; + ptd->dw1 |= TO_DW1_DEVICE_ADDR(usb_pipedevice(qtd->urb->pipe)); + ptd->dw1 |= TO_DW1_PID_TOKEN(qtd->packet_type); + + if (usb_pipebulk(qtd->urb->pipe)) + ptd->dw1 |= DW1_TRANS_BULK; + else if (usb_pipeint(qtd->urb->pipe)) + ptd->dw1 |= DW1_TRANS_INT; + + if (qtd->urb->dev->speed != USB_SPEED_HIGH) { + /* split transaction */ + + ptd->dw1 |= DW1_TRANS_SPLIT; + if (qtd->urb->dev->speed == USB_SPEED_LOW) + ptd->dw1 |= DW1_SE_USB_LOSPEED; + + ptd->dw1 |= TO_DW1_PORT_NUM(qtd->urb->dev->ttport); + ptd->dw1 |= TO_DW1_HUB_NUM(qtd->urb->dev->tt->hub->devnum); + + /* SE bit for Split INT transfers */ + if (usb_pipeint(qtd->urb->pipe) && + (qtd->urb->dev->speed == USB_SPEED_LOW)) + ptd->dw1 |= 2 << 16; + + rl = 0; + nak = 0; + } else { + ptd->dw0 |= TO_DW0_MULTI(multi); + if (usb_pipecontrol(qtd->urb->pipe) || + usb_pipebulk(qtd->urb->pipe)) + ptd->dw3 |= TO_DW3_PING(qh->ping); + } + /* DW2 */ + ptd->dw2 = 0; + ptd->dw2 |= TO_DW2_DATA_START_ADDR(base_to_chip(qtd->payload_addr)); + ptd->dw2 |= TO_DW2_RL(rl); + + /* DW3 */ + ptd->dw3 |= TO_DW3_NAKCOUNT(nak); + ptd->dw3 |= TO_DW3_DATA_TOGGLE(qh->toggle); + if (usb_pipecontrol(qtd->urb->pipe)) { + if (qtd->data_buffer == qtd->urb->setup_packet) + ptd->dw3 &= ~TO_DW3_DATA_TOGGLE(1); + else if (last_qtd_of_urb(qtd, qh)) + ptd->dw3 |= TO_DW3_DATA_TOGGLE(1); + } + + ptd->dw3 |= DW3_ACTIVE_BIT; + /* Cerr */ + ptd->dw3 |= TO_DW3_CERR(ERR_COUNTER); +} + +static void transform_add_int(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + u32 usof; + u32 period; + + /* + * Most of this is guessing. ISP1761 datasheet is quite unclear, and + * the algorithm from the original Philips driver code, which was + * pretty much used in this driver before as well, is quite horrendous + * and, i believe, incorrect. The code below follows the datasheet and + * USB2.0 spec as far as I can tell, and plug/unplug seems to be much + * more reliable this way (fingers crossed...). + */ + + if (qtd->urb->dev->speed == USB_SPEED_HIGH) { + /* urb->interval is in units of microframes (1/8 ms) */ + period = qtd->urb->interval >> 3; + + if (qtd->urb->interval > 4) + usof = 0x01; /* One bit set => + interval 1 ms * uFrame-match */ + else if (qtd->urb->interval > 2) + usof = 0x22; /* Two bits set => interval 1/2 ms */ + else if (qtd->urb->interval > 1) + usof = 0x55; /* Four bits set => interval 1/4 ms */ + else + usof = 0xff; /* All bits set => interval 1/8 ms */ + } else { + /* urb->interval is in units of frames (1 ms) */ + period = qtd->urb->interval; + usof = 0x0f; /* Execute Start Split on any of the + four first uFrames */ + + /* + * First 8 bits in dw5 is uSCS and "specifies which uSOF the + * complete split needs to be sent. Valid only for IN." Also, + * "All bits can be set to one for every transfer." (p 82, + * ISP1761 data sheet.) 0x1c is from Philips driver. Where did + * that number come from? 0xff seems to work fine... + */ + /* ptd->dw5 = 0x1c; */ + ptd->dw5 = 0xff; /* Execute Complete Split on any uFrame */ + } + + period = period >> 1;/* Ensure equal or shorter period than requested */ + period &= 0xf8; /* Mask off too large values and lowest unused 3 bits */ + + ptd->dw2 |= period; + ptd->dw4 = usof; +} + +static void create_ptd_int(struct isp1760_qh *qh, + struct isp1760_qtd *qtd, struct ptd *ptd) +{ + create_ptd_atl(qh, qtd, ptd); + transform_add_int(qh, qtd, ptd); +} + +static void isp1760_urb_done(struct usb_hcd *hcd, struct urb *urb) +__releases(priv->lock) +__acquires(priv->lock) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + + if (!urb->unlinked) { + if (urb->status == -EINPROGRESS) + urb->status = 0; + } + + if (usb_pipein(urb->pipe) && usb_pipetype(urb->pipe) != PIPE_CONTROL) { + void *ptr; + for (ptr = urb->transfer_buffer; + ptr < urb->transfer_buffer + urb->transfer_buffer_length; + ptr += PAGE_SIZE) + flush_dcache_page(virt_to_page(ptr)); + } + + /* complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock(&priv->lock); + usb_hcd_giveback_urb(hcd, urb, urb->status); + spin_lock(&priv->lock); +} + +static struct isp1760_qtd *qtd_alloc(gfp_t flags, struct urb *urb, + u8 packet_type) +{ + struct isp1760_qtd *qtd; + + qtd = kmem_cache_zalloc(qtd_cachep, flags); + if (!qtd) + return NULL; + + INIT_LIST_HEAD(&qtd->qtd_list); + qtd->urb = urb; + qtd->packet_type = packet_type; + qtd->status = QTD_ENQUEUED; + qtd->actual_length = 0; + + return qtd; +} + +static void qtd_free(struct isp1760_qtd *qtd) +{ + WARN_ON(qtd->payload_addr); + kmem_cache_free(qtd_cachep, qtd); +} + +static void start_bus_transfer(struct usb_hcd *hcd, u32 ptd_offset, int slot, + struct isp1760_slotinfo *slots, + struct isp1760_qtd *qtd, struct isp1760_qh *qh, + struct ptd *ptd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int skip_map; + + WARN_ON((slot < 0) || (slot > 31)); + WARN_ON(qtd->length && !qtd->payload_addr); + WARN_ON(slots[slot].qtd); + WARN_ON(slots[slot].qh); + WARN_ON(qtd->status != QTD_PAYLOAD_ALLOC); + + /* Make sure done map has not triggered from some unlinked transfer */ + if (ptd_offset == ATL_PTD_OFFSET) { + priv->atl_done_map |= reg_read32(hcd->regs, + HC_ATL_PTD_DONEMAP_REG); + priv->atl_done_map &= ~(1 << slot); + } else { + priv->int_done_map |= reg_read32(hcd->regs, + HC_INT_PTD_DONEMAP_REG); + priv->int_done_map &= ~(1 << slot); + } + + qh->slot = slot; + qtd->status = QTD_XFER_STARTED; + slots[slot].timestamp = jiffies; + slots[slot].qtd = qtd; + slots[slot].qh = qh; + ptd_write(hcd->regs, ptd_offset, slot, ptd); + + if (ptd_offset == ATL_PTD_OFFSET) { + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + skip_map &= ~(1 << qh->slot); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); + } else { + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + skip_map &= ~(1 << qh->slot); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); + } +} + +static int is_short_bulk(struct isp1760_qtd *qtd) +{ + return (usb_pipebulk(qtd->urb->pipe) && + (qtd->actual_length < qtd->length)); +} + +static void collect_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct list_head *urb_list) +{ + int last_qtd; + struct isp1760_qtd *qtd, *qtd_next; + struct urb_listitem *urb_listitem; + + list_for_each_entry_safe(qtd, qtd_next, &qh->qtd_list, qtd_list) { + if (qtd->status < QTD_XFER_COMPLETE) + break; + + last_qtd = last_qtd_of_urb(qtd, qh); + + if ((!last_qtd) && (qtd->status == QTD_RETIRE)) + qtd_next->status = QTD_RETIRE; + + if (qtd->status == QTD_XFER_COMPLETE) { + if (qtd->actual_length) { + switch (qtd->packet_type) { + case IN_PID: + mem_reads8(hcd->regs, qtd->payload_addr, + qtd->data_buffer, + qtd->actual_length); + /* Fall through (?) */ + case OUT_PID: + qtd->urb->actual_length += + qtd->actual_length; + /* Fall through ... */ + case SETUP_PID: + break; + } + } + + if (is_short_bulk(qtd)) { + if (qtd->urb->transfer_flags & URB_SHORT_NOT_OK) + qtd->urb->status = -EREMOTEIO; + if (!last_qtd) + qtd_next->status = QTD_RETIRE; + } + } + + if (qtd->payload_addr) + free_mem(hcd, qtd); + + if (last_qtd) { + if ((qtd->status == QTD_RETIRE) && + (qtd->urb->status == -EINPROGRESS)) + qtd->urb->status = -EPIPE; + /* Defer calling of urb_done() since it releases lock */ + urb_listitem = kmem_cache_zalloc(urb_listitem_cachep, + GFP_ATOMIC); + if (unlikely(!urb_listitem)) + break; /* Try again on next call */ + urb_listitem->urb = qtd->urb; + list_add_tail(&urb_listitem->urb_list, urb_list); + } + + list_del(&qtd->qtd_list); + qtd_free(qtd); + } +} + +#define ENQUEUE_DEPTH 2 +static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int ptd_offset; + struct isp1760_slotinfo *slots; + int curr_slot, free_slot; + int n; + struct ptd ptd; + struct isp1760_qtd *qtd; + + if (unlikely(list_empty(&qh->qtd_list))) { + WARN_ON(1); + return; + } + + /* Make sure this endpoint's TT buffer is clean before queueing ptds */ + if (qh->tt_buffer_dirty) + return; + + if (usb_pipeint(list_entry(qh->qtd_list.next, struct isp1760_qtd, + qtd_list)->urb->pipe)) { + ptd_offset = INT_PTD_OFFSET; + slots = priv->int_slots; + } else { + ptd_offset = ATL_PTD_OFFSET; + slots = priv->atl_slots; + } + + free_slot = -1; + for (curr_slot = 0; curr_slot < 32; curr_slot++) { + if ((free_slot == -1) && (slots[curr_slot].qtd == NULL)) + free_slot = curr_slot; + if (slots[curr_slot].qh == qh) + break; + } + + n = 0; + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { + if (qtd->status == QTD_ENQUEUED) { + WARN_ON(qtd->payload_addr); + alloc_mem(hcd, qtd); + if ((qtd->length) && (!qtd->payload_addr)) + break; + + if ((qtd->length) && + ((qtd->packet_type == SETUP_PID) || + (qtd->packet_type == OUT_PID))) { + mem_writes8(hcd->regs, qtd->payload_addr, + qtd->data_buffer, qtd->length); + } + + qtd->status = QTD_PAYLOAD_ALLOC; + } + + if (qtd->status == QTD_PAYLOAD_ALLOC) { +/* + if ((curr_slot > 31) && (free_slot == -1)) + dev_dbg(hcd->self.controller, "%s: No slot " + "available for transfer\n", __func__); +*/ + /* Start xfer for this endpoint if not already done */ + if ((curr_slot > 31) && (free_slot > -1)) { + if (usb_pipeint(qtd->urb->pipe)) + create_ptd_int(qh, qtd, &ptd); + else + create_ptd_atl(qh, qtd, &ptd); + + start_bus_transfer(hcd, ptd_offset, free_slot, + slots, qtd, qh, &ptd); + curr_slot = free_slot; + } + + n++; + if (n >= ENQUEUE_DEPTH) + break; + } + } +} + +static void schedule_ptds(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv; + struct isp1760_qh *qh, *qh_next; + struct list_head *ep_queue; + LIST_HEAD(urb_list); + struct urb_listitem *urb_listitem, *urb_listitem_next; + int i; + + if (!hcd) { + WARN_ON(1); + return; + } + + priv = hcd_to_priv(hcd); + + /* + * check finished/retired xfers, transfer payloads, call urb_done() + */ + for (i = 0; i < QH_END; i++) { + ep_queue = &priv->qh_list[i]; + list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) { + collect_qtds(hcd, qh, &urb_list); + if (list_empty(&qh->qtd_list)) + list_del(&qh->qh_list); + } + } + + list_for_each_entry_safe(urb_listitem, urb_listitem_next, &urb_list, + urb_list) { + isp1760_urb_done(hcd, urb_listitem->urb); + kmem_cache_free(urb_listitem_cachep, urb_listitem); + } + + /* + * Schedule packets for transfer. + * + * According to USB2.0 specification: + * + * 1st prio: interrupt xfers, up to 80 % of bandwidth + * 2nd prio: control xfers + * 3rd prio: bulk xfers + * + * ... but let's use a simpler scheme here (mostly because ISP1761 doc + * is very unclear on how to prioritize traffic): + * + * 1) Enqueue any queued control transfers, as long as payload chip mem + * and PTD ATL slots are available. + * 2) Enqueue any queued INT transfers, as long as payload chip mem + * and PTD INT slots are available. + * 3) Enqueue any queued bulk transfers, as long as payload chip mem + * and PTD ATL slots are available. + * + * Use double buffering (ENQUEUE_DEPTH==2) as a compromise between + * conservation of chip mem and performance. + * + * I'm sure this scheme could be improved upon! + */ + for (i = 0; i < QH_END; i++) { + ep_queue = &priv->qh_list[i]; + list_for_each_entry_safe(qh, qh_next, ep_queue, qh_list) + enqueue_qtds(hcd, qh); + } +} + +#define PTD_STATE_QTD_DONE 1 +#define PTD_STATE_QTD_RELOAD 2 +#define PTD_STATE_URB_RETIRE 3 + +static int check_int_transfer(struct usb_hcd *hcd, struct ptd *ptd, + struct urb *urb) +{ + __dw dw4; + int i; + + dw4 = ptd->dw4; + dw4 >>= 8; + + /* FIXME: ISP1761 datasheet does not say what to do with these. Do we + need to handle these errors? Is it done in hardware? */ + + if (ptd->dw3 & DW3_HALT_BIT) { + + urb->status = -EPROTO; /* Default unknown error */ + + for (i = 0; i < 8; i++) { + switch (dw4 & 0x7) { + case INT_UNDERRUN: + dev_dbg(hcd->self.controller, "%s: underrun " + "during uFrame %d\n", + __func__, i); + urb->status = -ECOMM; /* Could not write data */ + break; + case INT_EXACT: + dev_dbg(hcd->self.controller, "%s: transaction " + "error during uFrame %d\n", + __func__, i); + urb->status = -EPROTO; /* timeout, bad CRC, PID + error etc. */ + break; + case INT_BABBLE: + dev_dbg(hcd->self.controller, "%s: babble " + "error during uFrame %d\n", + __func__, i); + urb->status = -EOVERFLOW; + break; + } + dw4 >>= 3; + } + + return PTD_STATE_URB_RETIRE; + } + + return PTD_STATE_QTD_DONE; +} + +static int check_atl_transfer(struct usb_hcd *hcd, struct ptd *ptd, + struct urb *urb) +{ + WARN_ON(!ptd); + if (ptd->dw3 & DW3_HALT_BIT) { + if (ptd->dw3 & DW3_BABBLE_BIT) + urb->status = -EOVERFLOW; + else if (FROM_DW3_CERR(ptd->dw3)) + urb->status = -EPIPE; /* Stall */ + else if (ptd->dw3 & DW3_ERROR_BIT) + urb->status = -EPROTO; /* XactErr */ + else + urb->status = -EPROTO; /* Unknown */ +/* + dev_dbg(hcd->self.controller, "%s: ptd error:\n" + " dw0: %08x dw1: %08x dw2: %08x dw3: %08x\n" + " dw4: %08x dw5: %08x dw6: %08x dw7: %08x\n", + __func__, + ptd->dw0, ptd->dw1, ptd->dw2, ptd->dw3, + ptd->dw4, ptd->dw5, ptd->dw6, ptd->dw7); +*/ + return PTD_STATE_URB_RETIRE; + } + + if ((ptd->dw3 & DW3_ERROR_BIT) && (ptd->dw3 & DW3_ACTIVE_BIT)) { + /* Transfer Error, *but* active and no HALT -> reload */ + dev_dbg(hcd->self.controller, "PID error; reloading ptd\n"); + return PTD_STATE_QTD_RELOAD; + } + + if (!FROM_DW3_NAKCOUNT(ptd->dw3) && (ptd->dw3 & DW3_ACTIVE_BIT)) { + /* + * NAKs are handled in HW by the chip. Usually if the + * device is not able to send data fast enough. + * This happens mostly on slower hardware. + */ + return PTD_STATE_QTD_RELOAD; + } + + return PTD_STATE_QTD_DONE; +} + +static void handle_done_ptds(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct ptd ptd; + struct isp1760_qh *qh; + int slot; + int state; + struct isp1760_slotinfo *slots; + u32 ptd_offset; + struct isp1760_qtd *qtd; + int modified; + int skip_map; + + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + priv->int_done_map &= ~skip_map; + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + priv->atl_done_map &= ~skip_map; + + modified = priv->int_done_map || priv->atl_done_map; + + while (priv->int_done_map || priv->atl_done_map) { + if (priv->int_done_map) { + /* INT ptd */ + slot = __ffs(priv->int_done_map); + priv->int_done_map &= ~(1 << slot); + slots = priv->int_slots; + /* This should not trigger, and could be removed if + noone have any problems with it triggering: */ + if (!slots[slot].qh) { + WARN_ON(1); + continue; + } + ptd_offset = INT_PTD_OFFSET; + ptd_read(hcd->regs, INT_PTD_OFFSET, slot, &ptd); + state = check_int_transfer(hcd, &ptd, + slots[slot].qtd->urb); + } else { + /* ATL ptd */ + slot = __ffs(priv->atl_done_map); + priv->atl_done_map &= ~(1 << slot); + slots = priv->atl_slots; + /* This should not trigger, and could be removed if + noone have any problems with it triggering: */ + if (!slots[slot].qh) { + WARN_ON(1); + continue; + } + ptd_offset = ATL_PTD_OFFSET; + ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); + state = check_atl_transfer(hcd, &ptd, + slots[slot].qtd->urb); + } + + qtd = slots[slot].qtd; + slots[slot].qtd = NULL; + qh = slots[slot].qh; + slots[slot].qh = NULL; + qh->slot = -1; + + WARN_ON(qtd->status != QTD_XFER_STARTED); + + switch (state) { + case PTD_STATE_QTD_DONE: + if ((usb_pipeint(qtd->urb->pipe)) && + (qtd->urb->dev->speed != USB_SPEED_HIGH)) + qtd->actual_length = + FROM_DW3_SCS_NRBYTESTRANSFERRED(ptd.dw3); + else + qtd->actual_length = + FROM_DW3_NRBYTESTRANSFERRED(ptd.dw3); + + qtd->status = QTD_XFER_COMPLETE; + if (list_is_last(&qtd->qtd_list, &qh->qtd_list) || + is_short_bulk(qtd)) + qtd = NULL; + else + qtd = list_entry(qtd->qtd_list.next, + typeof(*qtd), qtd_list); + + qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); + qh->ping = FROM_DW3_PING(ptd.dw3); + break; + + case PTD_STATE_QTD_RELOAD: /* QTD_RETRY, for atls only */ + qtd->status = QTD_PAYLOAD_ALLOC; + ptd.dw0 |= DW0_VALID_BIT; + /* RL counter = ERR counter */ + ptd.dw3 &= ~TO_DW3_NAKCOUNT(0xf); + ptd.dw3 |= TO_DW3_NAKCOUNT(FROM_DW2_RL(ptd.dw2)); + ptd.dw3 &= ~TO_DW3_CERR(3); + ptd.dw3 |= TO_DW3_CERR(ERR_COUNTER); + qh->toggle = FROM_DW3_DATA_TOGGLE(ptd.dw3); + qh->ping = FROM_DW3_PING(ptd.dw3); + break; + + case PTD_STATE_URB_RETIRE: + qtd->status = QTD_RETIRE; + if ((qtd->urb->dev->speed != USB_SPEED_HIGH) && + (qtd->urb->status != -EPIPE) && + (qtd->urb->status != -EREMOTEIO)) { + qh->tt_buffer_dirty = 1; + if (usb_hub_clear_tt_buffer(qtd->urb)) + /* Clear failed; let's hope things work + anyway */ + qh->tt_buffer_dirty = 0; + } + qtd = NULL; + qh->toggle = 0; + qh->ping = 0; + break; + + default: + WARN_ON(1); + continue; + } + + if (qtd && (qtd->status == QTD_PAYLOAD_ALLOC)) { + if (slots == priv->int_slots) { + if (state == PTD_STATE_QTD_RELOAD) + dev_err(hcd->self.controller, + "%s: PTD_STATE_QTD_RELOAD on " + "interrupt packet\n", __func__); + if (state != PTD_STATE_QTD_RELOAD) + create_ptd_int(qh, qtd, &ptd); + } else { + if (state != PTD_STATE_QTD_RELOAD) + create_ptd_atl(qh, qtd, &ptd); + } + + start_bus_transfer(hcd, ptd_offset, slot, slots, qtd, + qh, &ptd); + } + } + + if (modified) + schedule_ptds(hcd); +} + +static irqreturn_t isp1760_irq(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 imask; + irqreturn_t irqret = IRQ_NONE; + + spin_lock(&priv->lock); + + if (!(hcd->state & HC_STATE_RUNNING)) + goto leave; + + imask = reg_read32(hcd->regs, HC_INTERRUPT_REG); + if (unlikely(!imask)) + goto leave; + reg_write32(hcd->regs, HC_INTERRUPT_REG, imask); /* Clear */ + + priv->int_done_map |= reg_read32(hcd->regs, HC_INT_PTD_DONEMAP_REG); + priv->atl_done_map |= reg_read32(hcd->regs, HC_ATL_PTD_DONEMAP_REG); + + handle_done_ptds(hcd); + + irqret = IRQ_HANDLED; +leave: + spin_unlock(&priv->lock); + + return irqret; +} + +/* + * Workaround for problem described in chip errata 2: + * + * Sometimes interrupts are not generated when ATL (not INT?) completion occurs. + * One solution suggested in the errata is to use SOF interrupts _instead_of_ + * ATL done interrupts (the "instead of" might be important since it seems + * enabling ATL interrupts also causes the chip to sometimes - rarely - "forget" + * to set the PTD's done bit in addition to not generating an interrupt!). + * + * So if we use SOF + ATL interrupts, we sometimes get stale PTDs since their + * done bit is not being set. This is bad - it blocks the endpoint until reboot. + * + * If we use SOF interrupts only, we get latency between ptd completion and the + * actual handling. This is very noticeable in testusb runs which takes several + * minutes longer without ATL interrupts. + * + * A better solution is to run the code below every SLOT_CHECK_PERIOD ms. If it + * finds active ATL slots which are older than SLOT_TIMEOUT ms, it checks the + * slot's ACTIVE and VALID bits. If these are not set, the ptd is considered + * completed and its done map bit is set. + * + * The values of SLOT_TIMEOUT and SLOT_CHECK_PERIOD have been arbitrarily chosen + * not to cause too much lag when this HW bug occurs, while still hopefully + * ensuring that the check does not falsely trigger. + */ +#define SLOT_TIMEOUT 300 +#define SLOT_CHECK_PERIOD 200 +static struct timer_list errata2_timer; + +static void errata2_function(unsigned long data) +{ + struct usb_hcd *hcd = (struct usb_hcd *) data; + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int slot; + struct ptd ptd; + unsigned long spinflags; + + spin_lock_irqsave(&priv->lock, spinflags); + + for (slot = 0; slot < 32; slot++) + if (priv->atl_slots[slot].qh && time_after(jiffies, + priv->atl_slots[slot].timestamp + + SLOT_TIMEOUT * HZ / 1000)) { + ptd_read(hcd->regs, ATL_PTD_OFFSET, slot, &ptd); + if (!FROM_DW0_VALID(ptd.dw0) && + !FROM_DW3_ACTIVE(ptd.dw3)) + priv->atl_done_map |= 1 << slot; + } + + if (priv->atl_done_map) + handle_done_ptds(hcd); + + spin_unlock_irqrestore(&priv->lock, spinflags); + + errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; + add_timer(&errata2_timer); +} + +static int isp1760_run(struct usb_hcd *hcd) +{ + int retval; + u32 temp; + u32 command; + u32 chipid; + + hcd->uses_new_polling = 1; + + hcd->state = HC_STATE_RUNNING; + + /* Set PTD interrupt AND & OR maps */ + reg_write32(hcd->regs, HC_ATL_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_ATL_IRQ_MASK_OR_REG, 0xffffffff); + reg_write32(hcd->regs, HC_INT_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_INT_IRQ_MASK_OR_REG, 0xffffffff); + reg_write32(hcd->regs, HC_ISO_IRQ_MASK_AND_REG, 0); + reg_write32(hcd->regs, HC_ISO_IRQ_MASK_OR_REG, 0xffffffff); + /* step 23 passed */ + + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp | HW_GLOBAL_INTR_EN); + + command = reg_read32(hcd->regs, HC_USBCMD); + command &= ~(CMD_LRESET|CMD_RESET); + command |= CMD_RUN; + reg_write32(hcd->regs, HC_USBCMD, command); + + retval = handshake(hcd, HC_USBCMD, CMD_RUN, CMD_RUN, 250 * 1000); + if (retval) + return retval; + + /* + * XXX + * Spec says to write FLAG_CF as last config action, priv code grabs + * the semaphore while doing so. + */ + down_write(&ehci_cf_port_reset_rwsem); + reg_write32(hcd->regs, HC_CONFIGFLAG, FLAG_CF); + + retval = handshake(hcd, HC_CONFIGFLAG, FLAG_CF, FLAG_CF, 250 * 1000); + up_write(&ehci_cf_port_reset_rwsem); + if (retval) + return retval; + + init_timer(&errata2_timer); + errata2_timer.function = errata2_function; + errata2_timer.data = (unsigned long) hcd; + errata2_timer.expires = jiffies + SLOT_CHECK_PERIOD * HZ / 1000; + add_timer(&errata2_timer); + + chipid = reg_read32(hcd->regs, HC_CHIP_ID_REG); + dev_info(hcd->self.controller, "USB ISP %04x HW rev. %d started\n", + chipid & 0xffff, chipid >> 16); + + /* PTD Register Init Part 2, Step 28 */ + + /* Setup registers controlling PTD checking */ + reg_write32(hcd->regs, HC_ATL_PTD_LASTPTD_REG, 0x80000000); + reg_write32(hcd->regs, HC_INT_PTD_LASTPTD_REG, 0x80000000); + reg_write32(hcd->regs, HC_ISO_PTD_LASTPTD_REG, 0x00000001); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_ISO_PTD_SKIPMAP_REG, 0xffffffff); + reg_write32(hcd->regs, HC_BUFFER_STATUS_REG, + ATL_BUF_FILL | INT_BUF_FILL); + + /* GRR this is run-once init(), being done every time the HC starts. + * So long as they're part of class devices, we can't do it init() + * since the class device isn't created that early. + */ + return 0; +} + +static int qtd_fill(struct isp1760_qtd *qtd, void *databuffer, size_t len) +{ + qtd->data_buffer = databuffer; + + if (len > MAX_PAYLOAD_SIZE) + len = MAX_PAYLOAD_SIZE; + qtd->length = len; + + return qtd->length; +} + +static void qtd_list_free(struct list_head *qtd_list) +{ + struct isp1760_qtd *qtd, *qtd_next; + + list_for_each_entry_safe(qtd, qtd_next, qtd_list, qtd_list) { + list_del(&qtd->qtd_list); + qtd_free(qtd); + } +} + +/* + * Packetize urb->transfer_buffer into list of packets of size wMaxPacketSize. + * Also calculate the PID type (SETUP/IN/OUT) for each packet. + */ +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) +static void packetize_urb(struct usb_hcd *hcd, + struct urb *urb, struct list_head *head, gfp_t flags) +{ + struct isp1760_qtd *qtd; + void *buf; + int len, maxpacketsize; + u8 packet_type; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + + if (!urb->transfer_buffer && urb->transfer_buffer_length) { + /* XXX This looks like usb storage / SCSI bug */ + dev_err(hcd->self.controller, + "buf is null, dma is %08lx len is %d\n", + (long unsigned)urb->transfer_dma, + urb->transfer_buffer_length); + WARN_ON(1); + } + + if (usb_pipein(urb->pipe)) + packet_type = IN_PID; + else + packet_type = OUT_PID; + + if (usb_pipecontrol(urb->pipe)) { + qtd = qtd_alloc(flags, urb, SETUP_PID); + if (!qtd) + goto cleanup; + qtd_fill(qtd, urb->setup_packet, sizeof(struct usb_ctrlrequest)); + list_add_tail(&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (urb->transfer_buffer_length == 0) + packet_type = IN_PID; + } + + maxpacketsize = max_packet(usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe))); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + buf = urb->transfer_buffer; + len = urb->transfer_buffer_length; + + for (;;) { + int this_qtd_len; + + qtd = qtd_alloc(flags, urb, packet_type); + if (!qtd) + goto cleanup; + this_qtd_len = qtd_fill(qtd, buf, len); + list_add_tail(&qtd->qtd_list, head); + + len -= this_qtd_len; + buf += this_qtd_len; + + if (len <= 0) + break; + } + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (urb->transfer_buffer_length != 0) { + int one_more = 0; + + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + if (packet_type == IN_PID) + packet_type = OUT_PID; + else + packet_type = IN_PID; + } else if (usb_pipebulk(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % + maxpacketsize)) { + one_more = 1; + } + if (one_more) { + qtd = qtd_alloc(flags, urb, packet_type); + if (!qtd) + goto cleanup; + + /* never any data in such packets */ + qtd_fill(qtd, NULL, 0); + list_add_tail(&qtd->qtd_list, head); + } + } + + return; + +cleanup: + qtd_list_free(head); +} + +static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct list_head *ep_queue; + struct isp1760_qh *qh, *qhit; + unsigned long spinflags; + LIST_HEAD(new_qtds); + int retval; + int qh_in_queue; + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ep_queue = &priv->qh_list[QH_CONTROL]; + break; + case PIPE_BULK: + ep_queue = &priv->qh_list[QH_BULK]; + break; + case PIPE_INTERRUPT: + if (urb->interval < 0) + return -EINVAL; + /* FIXME: Check bandwidth */ + ep_queue = &priv->qh_list[QH_INTERRUPT]; + break; + case PIPE_ISOCHRONOUS: + dev_err(hcd->self.controller, "%s: isochronous USB packets " + "not yet supported\n", + __func__); + return -EPIPE; + default: + dev_err(hcd->self.controller, "%s: unknown pipe type\n", + __func__); + return -EPIPE; + } + + if (usb_pipein(urb->pipe)) + urb->actual_length = 0; + + packetize_urb(hcd, urb, &new_qtds, mem_flags); + if (list_empty(&new_qtds)) + return -ENOMEM; + + retval = 0; + spin_lock_irqsave(&priv->lock, spinflags); + + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + retval = -ESHUTDOWN; + qtd_list_free(&new_qtds); + goto out; + } + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (retval) { + qtd_list_free(&new_qtds); + goto out; + } + + qh = urb->ep->hcpriv; + if (qh) { + qh_in_queue = 0; + list_for_each_entry(qhit, ep_queue, qh_list) { + if (qhit == qh) { + qh_in_queue = 1; + break; + } + } + if (!qh_in_queue) + list_add_tail(&qh->qh_list, ep_queue); + } else { + qh = qh_alloc(GFP_ATOMIC); + if (!qh) { + retval = -ENOMEM; + usb_hcd_unlink_urb_from_ep(hcd, urb); + qtd_list_free(&new_qtds); + goto out; + } + list_add_tail(&qh->qh_list, ep_queue); + urb->ep->hcpriv = qh; + } + + list_splice_tail(&new_qtds, &qh->qtd_list); + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); + return retval; +} + +static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, + struct isp1760_qh *qh) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int skip_map; + + WARN_ON(qh->slot == -1); + + /* We need to forcefully reclaim the slot since some transfers never + return, e.g. interrupt transfers and NAKed bulk transfers. */ + if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe)) { + skip_map = reg_read32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG); + skip_map |= (1 << qh->slot); + reg_write32(hcd->regs, HC_ATL_PTD_SKIPMAP_REG, skip_map); + priv->atl_slots[qh->slot].qh = NULL; + priv->atl_slots[qh->slot].qtd = NULL; + } else { + skip_map = reg_read32(hcd->regs, HC_INT_PTD_SKIPMAP_REG); + skip_map |= (1 << qh->slot); + reg_write32(hcd->regs, HC_INT_PTD_SKIPMAP_REG, skip_map); + priv->int_slots[qh->slot].qh = NULL; + priv->int_slots[qh->slot].qtd = NULL; + } + + qh->slot = -1; +} + +/* + * Retire the qtds beginning at 'qtd' and belonging all to the same urb, killing + * any active transfer belonging to the urb in the process. + */ +static void dequeue_urb_from_qtd(struct usb_hcd *hcd, struct isp1760_qh *qh, + struct isp1760_qtd *qtd) +{ + struct urb *urb; + int urb_was_running; + + urb = qtd->urb; + urb_was_running = 0; + list_for_each_entry_from(qtd, &qh->qtd_list, qtd_list) { + if (qtd->urb != urb) + break; + + if (qtd->status >= QTD_XFER_STARTED) + urb_was_running = 1; + if (last_qtd_of_urb(qtd, qh) && + (qtd->status >= QTD_XFER_COMPLETE)) + urb_was_running = 0; + + if (qtd->status == QTD_XFER_STARTED) + kill_transfer(hcd, urb, qh); + qtd->status = QTD_RETIRE; + } + + if ((urb->dev->speed != USB_SPEED_HIGH) && urb_was_running) { + qh->tt_buffer_dirty = 1; + if (usb_hub_clear_tt_buffer(urb)) + /* Clear failed; let's hope things work anyway */ + qh->tt_buffer_dirty = 0; + } +} + +static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + unsigned long spinflags; + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + int retval = 0; + + spin_lock_irqsave(&priv->lock, spinflags); + retval = usb_hcd_check_unlink_urb(hcd, urb, status); + if (retval) + goto out; + + qh = urb->ep->hcpriv; + if (!qh) { + retval = -EINVAL; + goto out; + } + + list_for_each_entry(qtd, &qh->qtd_list, qtd_list) + if (qtd->urb == urb) { + dequeue_urb_from_qtd(hcd, qh, qtd); + list_move(&qtd->qtd_list, &qh->qtd_list); + break; + } + + urb->status = status; + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); + return retval; +} + +static void isp1760_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + unsigned long spinflags; + struct isp1760_qh *qh, *qh_iter; + int i; + + spin_lock_irqsave(&priv->lock, spinflags); + + qh = ep->hcpriv; + if (!qh) + goto out; + + WARN_ON(!list_empty(&qh->qtd_list)); + + for (i = 0; i < QH_END; i++) + list_for_each_entry(qh_iter, &priv->qh_list[i], qh_list) + if (qh_iter == qh) { + list_del(&qh_iter->qh_list); + i = QH_END; + break; + } + qh_free(qh); + ep->hcpriv = NULL; + + schedule_ptds(hcd); + +out: + spin_unlock_irqrestore(&priv->lock, spinflags); +} + +static int isp1760_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 temp, status = 0; + u32 mask; + int retval = 1; + unsigned long flags; + + /* if !PM, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(hcd->state)) + return 0; + + /* init status to no-changes */ + buf[0] = 0; + mask = PORT_CSC; + + spin_lock_irqsave(&priv->lock, flags); + temp = reg_read32(hcd->regs, HC_PORTSC1); + + if (temp & PORT_OWNER) { + if (temp & PORT_CSC) { + temp &= ~PORT_CSC; + reg_write32(hcd->regs, HC_PORTSC1, temp); + goto done; + } + } + + /* + * Return status information even for ports with OWNER set. + * Otherwise hub_wq wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if ((temp & mask) != 0 + || ((temp & PORT_RESUME) != 0 + && time_after_eq(jiffies, + priv->reset_done))) { + buf [0] |= 1 << (0 + 1); + status = STS_PCD; + } + /* FIXME autosuspend idle root hubs */ +done: + spin_unlock_irqrestore(&priv->lock, flags); + return status ? retval : 0; +} + +static void isp1760_hub_descriptor(struct isp1760_hcd *priv, + struct usb_hub_descriptor *desc) +{ + int ports = HCS_N_PORTS(priv->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + /* priv 1.0, 2.3.9 says 20ms max */ + desc->bPwrOn2PwrGood = 10; + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + /* per-port overcurrent reporting */ + temp = 0x0008; + if (HCS_PPC(priv->hcs_params)) + /* per-port power control */ + temp |= 0x0001; + else + /* no power switching */ + temp |= 0x0002; + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) + +static int check_reset_complete(struct usb_hcd *hcd, int index, + int port_status) +{ + if (!(port_status & PORT_CONNECT)) + return port_status; + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + + dev_info(hcd->self.controller, + "port %d full speed --> companion\n", + index + 1); + + port_status |= PORT_OWNER; + port_status &= ~PORT_RWC_BITS; + reg_write32(hcd->regs, HC_PORTSC1, port_status); + + } else + dev_info(hcd->self.controller, "port %d high speed\n", + index + 1); + + return port_status; +} + +static int isp1760_hub_control(struct usb_hcd *hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + int ports = HCS_N_PORTS(priv->hcs_params); + u32 temp, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave(&priv->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = reg_read32(hcd->regs, HC_PORTSC1); + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, hub_wq needs to be able to clear + * the port-change status bits (especially + * USB_PORT_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_PE); + break; + case USB_PORT_FEAT_C_ENABLE: + /* XXX error? */ + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + + if (temp & PORT_SUSPEND) { + if ((temp & PORT_PE) == 0) + goto error; + /* resume signaling for 20 msec */ + temp &= ~(PORT_RWC_BITS); + reg_write32(hcd->regs, HC_PORTSC1, + temp | PORT_RESUME); + priv->reset_done = jiffies + + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + /* we auto-clear this feature */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(priv->hcs_params)) + reg_write32(hcd->regs, HC_PORTSC1, + temp & ~PORT_POWER); + break; + case USB_PORT_FEAT_C_CONNECTION: + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_CSC); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + /* XXX error ?*/ + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + reg_read32(hcd->regs, HC_USBCMD); + break; + case GetHubDescriptor: + isp1760_hub_descriptor(priv, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset(buf, 0, 4); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = reg_read32(hcd->regs, HC_PORTSC1); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + dev_err(hcd->self.controller, "Port resume should be skipped.\n"); + + /* Remote Wakeup received? */ + if (!priv->reset_done) { + /* resume signaling for 20 msec */ + priv->reset_done = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&hcd->rh_timer, priv->reset_done); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + priv->reset_done)) { + status |= USB_PORT_STAT_C_SUSPEND << 16; + priv->reset_done = 0; + + /* stop resume signaling */ + temp = reg_read32(hcd->regs, HC_PORTSC1); + reg_write32(hcd->regs, HC_PORTSC1, + temp & ~(PORT_RWC_BITS | PORT_RESUME)); + retval = handshake(hcd, HC_PORTSC1, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + dev_err(hcd->self.controller, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && time_after_eq(jiffies, + priv->reset_done)) { + status |= USB_PORT_STAT_C_RESET << 16; + priv->reset_done = 0; + + /* force reset to complete */ + reg_write32(hcd->regs, HC_PORTSC1, temp & ~PORT_RESET); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(hcd, HC_PORTSC1, + PORT_RESET, 0, 750); + if (retval != 0) { + dev_err(hcd->self.controller, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete(hcd, wIndex, + reg_read32(hcd->regs, HC_PORTSC1)); + } + /* + * Even if OWNER is set, there's no harm letting hub_wq + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_OWNER) + dev_err(hcd->self.controller, "PORT_OWNER is set\n"); + + if (temp & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + /* status may be from integrated TT */ + status |= USB_PORT_STAT_HIGH_SPEED; + } + if (temp & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + if (temp & (PORT_SUSPEND|PORT_RESUME)) + status |= USB_PORT_STAT_SUSPEND; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (temp & PORT_POWER) + status |= USB_PORT_STAT_POWER; + + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = reg_read32(hcd->regs, HC_PORTSC1); + if (temp & PORT_OWNER) + break; + +/* temp &= ~PORT_RWC_BITS; */ + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_PE); + break; + + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + + reg_write32(hcd->regs, HC_PORTSC1, temp | PORT_SUSPEND); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(priv->hcs_params)) + reg_write32(hcd->regs, HC_PORTSC1, + temp | PORT_POWER); + break; + case USB_PORT_FEAT_RESET: + if (temp & PORT_RESUME) + goto error; + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT + && PORT_USB11(temp)) { + temp |= PORT_OWNER; + } else { + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + priv->reset_done = jiffies + + msecs_to_jiffies(50); + } + reg_write32(hcd->regs, HC_PORTSC1, temp); + break; + default: + goto error; + } + reg_read32(hcd->regs, HC_USBCMD); + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&priv->lock, flags); + return retval; +} + +static int isp1760_get_frame(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 fr; + + fr = reg_read32(hcd->regs, HC_FRINDEX); + return (fr >> 3) % priv->periodic_size; +} + +static void isp1760_stop(struct usb_hcd *hcd) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + u32 temp; + + del_timer(&errata2_timer); + + isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1, + NULL, 0); + mdelay(20); + + spin_lock_irq(&priv->lock); + ehci_reset(hcd); + /* Disable IRQ */ + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); + spin_unlock_irq(&priv->lock); + + reg_write32(hcd->regs, HC_CONFIGFLAG, 0); +} + +static void isp1760_shutdown(struct usb_hcd *hcd) +{ + u32 command, temp; + + isp1760_stop(hcd); + temp = reg_read32(hcd->regs, HC_HW_MODE_CTRL); + reg_write32(hcd->regs, HC_HW_MODE_CTRL, temp &= ~HW_GLOBAL_INTR_EN); + + command = reg_read32(hcd->regs, HC_USBCMD); + command &= ~CMD_RUN; + reg_write32(hcd->regs, HC_USBCMD, command); +} + +static void isp1760_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct isp1760_hcd *priv = hcd_to_priv(hcd); + struct isp1760_qh *qh = ep->hcpriv; + unsigned long spinflags; + + if (!qh) + return; + + spin_lock_irqsave(&priv->lock, spinflags); + qh->tt_buffer_dirty = 0; + schedule_ptds(hcd); + spin_unlock_irqrestore(&priv->lock, spinflags); +} + + +static const struct hc_driver isp1760_hc_driver = { + .description = "isp1760-hcd", + .product_desc = "NXP ISP1760 USB Host Controller", + .hcd_priv_size = sizeof(struct isp1760_hcd *), + .irq = isp1760_irq, + .flags = HCD_MEMORY | HCD_USB2, + .reset = isp1760_hc_setup, + .start = isp1760_run, + .stop = isp1760_stop, + .shutdown = isp1760_shutdown, + .urb_enqueue = isp1760_urb_enqueue, + .urb_dequeue = isp1760_urb_dequeue, + .endpoint_disable = isp1760_endpoint_disable, + .get_frame_number = isp1760_get_frame, + .hub_status_data = isp1760_hub_status_data, + .hub_control = isp1760_hub_control, + .clear_tt_buffer_complete = isp1760_clear_tt_buffer_complete, +}; + +int __init isp1760_init_kmem_once(void) +{ + urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", + sizeof(struct urb_listitem), 0, SLAB_TEMPORARY | + SLAB_MEM_SPREAD, NULL); + + if (!urb_listitem_cachep) + return -ENOMEM; + + qtd_cachep = kmem_cache_create("isp1760_qtd", + sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY | + SLAB_MEM_SPREAD, NULL); + + if (!qtd_cachep) + return -ENOMEM; + + qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh), + 0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL); + + if (!qh_cachep) { + kmem_cache_destroy(qtd_cachep); + return -ENOMEM; + } + + return 0; +} + +void isp1760_deinit_kmem_cache(void) +{ + kmem_cache_destroy(qtd_cachep); + kmem_cache_destroy(qh_cachep); + kmem_cache_destroy(urb_listitem_cachep); +} + +int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, + struct resource *mem, int irq, unsigned long irqflags, + struct device *dev) +{ + struct usb_hcd *hcd; + int ret; + + hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev_name(dev)); + if (!hcd) + return -ENOMEM; + + *(struct isp1760_hcd **)hcd->hcd_priv = priv; + + priv->hcd = hcd; + + init_memory(priv); + + hcd->irq = irq; + hcd->regs = regs; + hcd->rsrc_start = mem->start; + hcd->rsrc_len = resource_size(mem); + + ret = usb_add_hcd(hcd, irq, irqflags); + if (ret) + goto error; + + device_wakeup_enable(hcd->self.controller); + + return 0; + +error: + usb_put_hcd(hcd); + return ret; +} + +void isp1760_hcd_unregister(struct isp1760_hcd *priv) +{ + usb_remove_hcd(priv->hcd); + usb_put_hcd(priv->hcd); +} diff --git a/drivers/usb/isp1760/isp1760-hcd.h b/drivers/usb/isp1760/isp1760-hcd.h new file mode 100644 index 0000000..df7ea36 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-hcd.h @@ -0,0 +1,77 @@ +#ifndef _ISP1760_HCD_H_ +#define _ISP1760_HCD_H_ + +#include + +struct isp1760_qh; +struct isp1760_qtd; +struct resource; +struct usb_hcd; + +/* + * 60kb divided in: + * - 32 blocks @ 256 bytes + * - 20 blocks @ 1024 bytes + * - 4 blocks @ 8192 bytes + */ + +#define BLOCK_1_NUM 32 +#define BLOCK_2_NUM 20 +#define BLOCK_3_NUM 4 + +#define BLOCK_1_SIZE 256 +#define BLOCK_2_SIZE 1024 +#define BLOCK_3_SIZE 8192 +#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM) +#define MAX_PAYLOAD_SIZE BLOCK_3_SIZE +#define PAYLOAD_AREA_SIZE 0xf000 + +struct isp1760_slotinfo { + struct isp1760_qh *qh; + struct isp1760_qtd *qtd; + unsigned long timestamp; +}; + +/* chip memory management */ +struct isp1760_memory_chunk { + unsigned int start; + unsigned int size; + unsigned int free; +}; + +enum isp1760_queue_head_types { + QH_CONTROL, + QH_BULK, + QH_INTERRUPT, + QH_END +}; + +struct isp1760_hcd { + struct usb_hcd *hcd; + + u32 hcs_params; + spinlock_t lock; + struct isp1760_slotinfo atl_slots[32]; + int atl_done_map; + struct isp1760_slotinfo int_slots[32]; + int int_done_map; + struct isp1760_memory_chunk memory_pool[BLOCKS]; + struct list_head qh_list[QH_END]; + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 + unsigned periodic_size; + unsigned i_thresh; + unsigned long reset_done; + unsigned long next_statechange; +}; + +int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, + struct resource *mem, int irq, unsigned long irqflags, + struct device *dev); +void isp1760_hcd_unregister(struct isp1760_hcd *priv); + +int isp1760_init_kmem_once(void); +void isp1760_deinit_kmem_cache(void); + +#endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/isp1760/isp1760-if.c b/drivers/usb/isp1760/isp1760-if.c new file mode 100644 index 0000000..c2a94c9 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-if.c @@ -0,0 +1,312 @@ +/* + * Glue code for the ISP1760 driver and bus + * Currently there is support for + * - OpenFirmware + * - PCI + * - PDEV (generic platform device centralized driver model) + * + * (c) 2007 Sebastian Siewior + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-regs.h" + +#ifdef CONFIG_PCI +#include +#endif + +#ifdef CONFIG_PCI +static int isp1761_pci_init(struct pci_dev *dev) +{ + resource_size_t mem_start; + resource_size_t mem_length; + u8 __iomem *iobase; + u8 latency, limit; + int retry_count; + u32 reg_data; + + /* Grab the PLX PCI shared memory of the ISP 1761 we need */ + mem_start = pci_resource_start(dev, 3); + mem_length = pci_resource_len(dev, 3); + if (mem_length < 0xffff) { + printk(KERN_ERR "memory length for this resource is wrong\n"); + return -ENOMEM; + } + + if (!request_mem_region(mem_start, mem_length, "ISP-PCI")) { + printk(KERN_ERR "host controller already in use\n"); + return -EBUSY; + } + + /* map available memory */ + iobase = ioremap_nocache(mem_start, mem_length); + if (!iobase) { + printk(KERN_ERR "Error ioremap failed\n"); + release_mem_region(mem_start, mem_length); + return -ENOMEM; + } + + /* bad pci latencies can contribute to overruns */ + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &latency); + if (latency) { + pci_read_config_byte(dev, PCI_MAX_LAT, &limit); + if (limit && limit < latency) + pci_write_config_byte(dev, PCI_LATENCY_TIMER, limit); + } + + /* Try to check whether we can access Scratch Register of + * Host Controller or not. The initial PCI access is retried until + * local init for the PCI bridge is completed + */ + retry_count = 20; + reg_data = 0; + while ((reg_data != 0xFACE) && retry_count) { + /*by default host is in 16bit mode, so + * io operations at this stage must be 16 bit + * */ + writel(0xface, iobase + HC_SCRATCH_REG); + udelay(100); + reg_data = readl(iobase + HC_SCRATCH_REG) & 0x0000ffff; + retry_count--; + } + + iounmap(iobase); + release_mem_region(mem_start, mem_length); + + /* Host Controller presence is detected by writing to scratch register + * and reading back and checking the contents are same or not + */ + if (reg_data != 0xFACE) { + dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); + return -ENOMEM; + } + + /* Grab the PLX PCI mem maped port start address we need */ + mem_start = pci_resource_start(dev, 0); + mem_length = pci_resource_len(dev, 0); + + if (!request_mem_region(mem_start, mem_length, "ISP1761 IO MEM")) { + printk(KERN_ERR "request region #1\n"); + return -EBUSY; + } + + iobase = ioremap_nocache(mem_start, mem_length); + if (!iobase) { + printk(KERN_ERR "ioremap #1\n"); + release_mem_region(mem_start, mem_length); + return -ENOMEM; + } + + /* configure PLX PCI chip to pass interrupts */ +#define PLX_INT_CSR_REG 0x68 + reg_data = readl(iobase + PLX_INT_CSR_REG); + reg_data |= 0x900; + writel(reg_data, iobase + PLX_INT_CSR_REG); + + /* done with PLX IO access */ + iounmap(iobase); + release_mem_region(mem_start, mem_length); + + return 0; +} + +static int isp1761_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + unsigned int devflags = 0; + int ret; + + if (usb_disabled()) + return -ENODEV; + + if (!dev->irq) + return -ENODEV; + + if (pci_enable_device(dev) < 0) + return -ENODEV; + + ret = isp1761_pci_init(dev); + if (ret < 0) + goto error; + + pci_set_master(dev); + + dev->dev.dma_mask = NULL; + ret = isp1760_register(&dev->resource[3], dev->irq, 0, &dev->dev, + devflags); + if (ret < 0) + goto error; + + return 0; + +error: + pci_disable_device(dev); + return ret; +} + +static void isp1761_pci_remove(struct pci_dev *dev) +{ + isp1760_unregister(&dev->dev); + + pci_disable_device(dev); +} + +static void isp1761_pci_shutdown(struct pci_dev *dev) +{ + printk(KERN_ERR "ips1761_pci_shutdown\n"); +} + +static const struct pci_device_id isp1760_plx [] = { + { + .class = PCI_CLASS_BRIDGE_OTHER << 8, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x5406, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = 0x9054, + }, + { } +}; +MODULE_DEVICE_TABLE(pci, isp1760_plx); + +static struct pci_driver isp1761_pci_driver = { + .name = "isp1760", + .id_table = isp1760_plx, + .probe = isp1761_pci_probe, + .remove = isp1761_pci_remove, + .shutdown = isp1761_pci_shutdown, +}; +#endif + +static int isp1760_plat_probe(struct platform_device *pdev) +{ + unsigned long irqflags; + unsigned int devflags = 0; + struct resource *mem_res; + struct resource *irq_res; + int ret; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq_res) { + pr_warning("isp1760: IRQ resource not available\n"); + return -ENODEV; + } + irqflags = irq_res->flags & IRQF_TRIGGER_MASK; + + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + struct device_node *dp = pdev->dev.of_node; + u32 bus_width = 0; + + if (of_device_is_compatible(dp, "nxp,usb-isp1761")) + devflags |= ISP1760_FLAG_ISP1761; + + /* Some systems wire up only 16 of the 32 data lines */ + of_property_read_u32(dp, "bus-width", &bus_width); + if (bus_width == 16) + devflags |= ISP1760_FLAG_BUS_WIDTH_16; + + if (of_property_read_bool(dp, "port1-otg")) + devflags |= ISP1760_FLAG_OTG_EN; + + if (of_property_read_bool(dp, "analog-oc")) + devflags |= ISP1760_FLAG_ANALOG_OC; + + if (of_property_read_bool(dp, "dack-polarity")) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + + if (of_property_read_bool(dp, "dreq-polarity")) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + } else if (dev_get_platdata(&pdev->dev)) { + struct isp1760_platform_data *pdata = + dev_get_platdata(&pdev->dev); + + if (pdata->is_isp1761) + devflags |= ISP1760_FLAG_ISP1761; + if (pdata->bus_width_16) + devflags |= ISP1760_FLAG_BUS_WIDTH_16; + if (pdata->port1_otg) + devflags |= ISP1760_FLAG_OTG_EN; + if (pdata->analog_oc) + devflags |= ISP1760_FLAG_ANALOG_OC; + if (pdata->dack_polarity_high) + devflags |= ISP1760_FLAG_DACK_POL_HIGH; + if (pdata->dreq_polarity_high) + devflags |= ISP1760_FLAG_DREQ_POL_HIGH; + } + + ret = isp1760_register(mem_res, irq_res->start, irqflags, &pdev->dev, + devflags); + if (ret < 0) + return ret; + + pr_info("ISP1760 USB device initialised\n"); + return 0; +} + +static int isp1760_plat_remove(struct platform_device *pdev) +{ + isp1760_unregister(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id isp1760_of_match[] = { + { .compatible = "nxp,usb-isp1760", }, + { .compatible = "nxp,usb-isp1761", }, + { }, +}; +MODULE_DEVICE_TABLE(of, isp1760_of_match); +#endif + +static struct platform_driver isp1760_plat_driver = { + .probe = isp1760_plat_probe, + .remove = isp1760_plat_remove, + .driver = { + .name = "isp1760", + .of_match_table = of_match_ptr(isp1760_of_match), + }, +}; + +static int __init isp1760_init(void) +{ + int ret, any_ret = -ENODEV; + + isp1760_init_kmem_once(); + + ret = platform_driver_register(&isp1760_plat_driver); + if (!ret) + any_ret = 0; +#ifdef CONFIG_PCI + ret = pci_register_driver(&isp1761_pci_driver); + if (!ret) + any_ret = 0; +#endif + + if (any_ret) + isp1760_deinit_kmem_cache(); + return any_ret; +} +module_init(isp1760_init); + +static void __exit isp1760_exit(void) +{ + platform_driver_unregister(&isp1760_plat_driver); +#ifdef CONFIG_PCI + pci_unregister_driver(&isp1761_pci_driver); +#endif + isp1760_deinit_kmem_cache(); +} +module_exit(isp1760_exit); diff --git a/drivers/usb/isp1760/isp1760-regs.h b/drivers/usb/isp1760/isp1760-regs.h new file mode 100644 index 0000000..b67095c --- /dev/null +++ b/drivers/usb/isp1760/isp1760-regs.h @@ -0,0 +1,230 @@ +/* + * Driver for the NXP ISP1760 chip + * + * Copyright 2014 Laurent Pinchart + * Copyright 2007 Sebastian Siewior + * + * Contacts: + * Sebastian Siewior + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef _ISP1760_REGS_H_ +#define _ISP1760_REGS_H_ + +/* ----------------------------------------------------------------------------- + * Host Controller + */ + +/* EHCI capability registers */ +#define HC_CAPLENGTH 0x000 +#define HC_LENGTH(p) (((p) >> 00) & 0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) /* bits 31:16 */ + +#define HC_HCSPARAMS 0x004 +#define HCS_INDICATOR(p) ((p) & (1 << 16)) /* true: has port indicators */ +#define HCS_PPC(p) ((p) & (1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p) >> 0) & 0xf) /* bits 3:0, ports on HC */ + +#define HC_HCCPARAMS 0x008 +#define HCC_ISOC_CACHE(p) ((p) & (1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p) >> 4) & 0x7) /* bits 6:4, uframes cached */ + +/* EHCI operational registers */ +#define HC_USBCMD 0x020 +#define CMD_LRESET (1 << 7) /* partial reset (no ports, etc) */ +#define CMD_RESET (1 << 1) /* reset HC not bus */ +#define CMD_RUN (1 << 0) /* start/stop HC */ + +#define HC_USBSTS 0x024 +#define STS_PCD (1 << 2) /* port change detect */ + +#define HC_FRINDEX 0x02c + +#define HC_CONFIGFLAG 0x060 +#define FLAG_CF (1 << 0) /* true: we'll support "high speed" */ + +#define HC_PORTSC1 0x064 +#define PORT_OWNER (1 << 13) /* true: companion hc owns this port */ +#define PORT_POWER (1 << 12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */ +#define PORT_RESET (1 << 8) /* reset port */ +#define PORT_SUSPEND (1 << 7) /* suspend port */ +#define PORT_RESUME (1 << 6) /* resume it */ +#define PORT_PE (1 << 2) /* port enable */ +#define PORT_CSC (1 << 1) /* connect status change */ +#define PORT_CONNECT (1 << 0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC) + +#define HC_ISO_PTD_DONEMAP_REG 0x130 +#define HC_ISO_PTD_SKIPMAP_REG 0x134 +#define HC_ISO_PTD_LASTPTD_REG 0x138 +#define HC_INT_PTD_DONEMAP_REG 0x140 +#define HC_INT_PTD_SKIPMAP_REG 0x144 +#define HC_INT_PTD_LASTPTD_REG 0x148 +#define HC_ATL_PTD_DONEMAP_REG 0x150 +#define HC_ATL_PTD_SKIPMAP_REG 0x154 +#define HC_ATL_PTD_LASTPTD_REG 0x158 + +/* Configuration Register */ +#define HC_HW_MODE_CTRL 0x300 +#define ALL_ATX_RESET (1 << 31) +#define HW_ANA_DIGI_OC (1 << 15) +#define HW_DEV_DMA (1 << 11) +#define HW_COMN_IRQ (1 << 10) +#define HW_COMN_DMA (1 << 9) +#define HW_DATA_BUS_32BIT (1 << 8) +#define HW_DACK_POL_HIGH (1 << 6) +#define HW_DREQ_POL_HIGH (1 << 5) +#define HW_INTR_HIGH_ACT (1 << 2) +#define HW_INTR_EDGE_TRIG (1 << 1) +#define HW_GLOBAL_INTR_EN (1 << 0) + +#define HC_CHIP_ID_REG 0x304 +#define HC_SCRATCH_REG 0x308 + +#define HC_RESET_REG 0x30c +#define SW_RESET_RESET_HC (1 << 1) +#define SW_RESET_RESET_ALL (1 << 0) + +#define HC_BUFFER_STATUS_REG 0x334 +#define ISO_BUF_FILL (1 << 2) +#define INT_BUF_FILL (1 << 1) +#define ATL_BUF_FILL (1 << 0) + +#define HC_MEMORY_REG 0x33c +#define ISP_BANK(x) ((x) << 16) + +#define HC_PORT1_CTRL 0x374 +#define PORT1_POWER (3 << 3) +#define PORT1_INIT1 (1 << 7) +#define PORT1_INIT2 (1 << 23) +#define HW_OTG_CTRL_SET 0x374 +#define HW_OTG_CTRL_CLR 0x376 +#define HW_OTG_DISABLE (1 << 10) +#define HW_OTG_SE0_EN (1 << 9) +#define HW_BDIS_ACON_EN (1 << 8) +#define HW_SW_SEL_HC_DC (1 << 7) +#define HW_VBUS_CHRG (1 << 6) +#define HW_VBUS_DISCHRG (1 << 5) +#define HW_VBUS_DRV (1 << 4) +#define HW_SEL_CP_EXT (1 << 3) +#define HW_DM_PULLDOWN (1 << 2) +#define HW_DP_PULLDOWN (1 << 1) +#define HW_DP_PULLUP (1 << 0) + +/* Interrupt Register */ +#define HC_INTERRUPT_REG 0x310 + +#define HC_INTERRUPT_ENABLE 0x314 +#define HC_ISO_INT (1 << 9) +#define HC_ATL_INT (1 << 8) +#define HC_INTL_INT (1 << 7) +#define HC_EOT_INT (1 << 3) +#define HC_SOT_INT (1 << 1) +#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT) + +#define HC_ISO_IRQ_MASK_OR_REG 0x318 +#define HC_INT_IRQ_MASK_OR_REG 0x31c +#define HC_ATL_IRQ_MASK_OR_REG 0x320 +#define HC_ISO_IRQ_MASK_AND_REG 0x324 +#define HC_INT_IRQ_MASK_AND_REG 0x328 +#define HC_ATL_IRQ_MASK_AND_REG 0x32c + +/* ----------------------------------------------------------------------------- + * Peripheral Controller + */ + +/* Initialization Registers */ +#define DC_ADDRESS 0x0200 +#define DC_DEVEN (1 << 7) + +#define DC_MODE 0x020c +#define DC_DMACLKON (1 << 9) +#define DC_VBUSSTAT (1 << 8) +#define DC_CLKAON (1 << 7) +#define DC_SNDRSU (1 << 6) +#define DC_GOSUSP (1 << 5) +#define DC_SFRESET (1 << 4) +#define DC_GLINTENA (1 << 3) +#define DC_WKUPCS (1 << 2) + +#define DC_INTCONF 0x0210 +#define DC_CDBGMOD_ACK_NAK (0 << 6) +#define DC_CDBGMOD_ACK (1 << 6) +#define DC_CDBGMOD_ACK_1NAK (2 << 6) +#define DC_DDBGMODIN_ACK_NAK (0 << 4) +#define DC_DDBGMODIN_ACK (1 << 4) +#define DC_DDBGMODIN_ACK_1NAK (2 << 4) +#define DC_DDBGMODOUT_ACK_NYET_NAK (0 << 2) +#define DC_DDBGMODOUT_ACK_NYET (1 << 2) +#define DC_DDBGMODOUT_ACK_NYET_1NAK (2 << 2) +#define DC_INTLVL (1 << 1) +#define DC_INTPOL (1 << 0) + +#define DC_DEBUG 0x0212 +#define DC_INTENABLE 0x0214 +#define DC_IEPTX(n) (1 << (11 + 2 * (n))) +#define DC_IEPRX(n) (1 << (10 + 2 * (n))) +#define DC_IEPRXTX(n) (3 << (10 + 2 * (n))) +#define DC_IEP0SETUP (1 << 8) +#define DC_IEVBUS (1 << 7) +#define DC_IEDMA (1 << 6) +#define DC_IEHS_STA (1 << 5) +#define DC_IERESM (1 << 4) +#define DC_IESUSP (1 << 3) +#define DC_IEPSOF (1 << 2) +#define DC_IESOF (1 << 1) +#define DC_IEBRST (1 << 0) + +/* Data Flow Registers */ +#define DC_EPINDEX 0x022c +#define DC_EP0SETUP (1 << 5) +#define DC_ENDPIDX(n) ((n) << 1) +#define DC_EPDIR (1 << 0) + +#define DC_CTRLFUNC 0x0228 +#define DC_CLBUF (1 << 4) +#define DC_VENDP (1 << 3) +#define DC_DSEN (1 << 2) +#define DC_STATUS (1 << 1) +#define DC_STALL (1 << 0) + +#define DC_DATAPORT 0x0220 +#define DC_BUFLEN 0x021c +#define DC_DATACOUNT_MASK 0xffff +#define DC_BUFSTAT 0x021e +#define DC_EPMAXPKTSZ 0x0204 + +#define DC_EPTYPE 0x0208 +#define DC_NOEMPKT (1 << 4) +#define DC_EPENABLE (1 << 3) +#define DC_DBLBUF (1 << 2) +#define DC_ENDPTYP_ISOC (1 << 0) +#define DC_ENDPTYP_BULK (2 << 0) +#define DC_ENDPTYP_INTERRUPT (3 << 0) + +/* DMA Registers */ +#define DC_DMACMD 0x0230 +#define DC_DMATXCOUNT 0x0234 +#define DC_DMACONF 0x0238 +#define DC_DMAHW 0x023c +#define DC_DMAINTREASON 0x0250 +#define DC_DMAINTEN 0x0254 +#define DC_DMAEP 0x0258 +#define DC_DMABURSTCOUNT 0x0264 + +/* General Registers */ +#define DC_INTERRUPT 0x0218 +#define DC_CHIPID 0x0270 +#define DC_FRAMENUM 0x0274 +#define DC_SCRATCH 0x0278 +#define DC_UNLOCKDEV 0x027c +#define DC_INTPULSEWIDTH 0x0280 +#define DC_TESTMODE 0x0284 + +#endif diff --git a/drivers/usb/isp1760/isp1760-udc.c b/drivers/usb/isp1760/isp1760-udc.c new file mode 100644 index 0000000..6bfda30 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-udc.c @@ -0,0 +1,1495 @@ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isp1760-core.h" +#include "isp1760-regs.h" +#include "isp1760-udc.h" + +#define ISP1760_VBUS_POLL_INTERVAL msecs_to_jiffies(500) + +struct isp1760_request { + struct usb_request req; + struct list_head queue; + struct isp1760_ep *ep; + unsigned int packet_size; +}; + +static inline struct isp1760_udc *gadget_to_udc(struct usb_gadget *gadget) +{ + return container_of(gadget, struct isp1760_udc, gadget); +} + +static inline struct isp1760_ep *ep_to_udc_ep(struct usb_ep *ep) +{ + return container_of(ep, struct isp1760_ep, ep); +} + +static inline struct isp1760_request *req_to_udc_req(struct usb_request *req) +{ + return container_of(req, struct isp1760_request, req); +} + +static inline u32 isp1760_udc_read(struct isp1760_udc *udc, u16 reg) +{ + return isp1760_read32(udc->regs, reg); +} + +static inline void isp1760_udc_write(struct isp1760_udc *udc, u16 reg, u32 val) +{ + isp1760_write32(udc->regs, reg, val); +} + +/* ----------------------------------------------------------------------------- + * Endpoint Management + */ + +static struct isp1760_ep *isp1760_udc_find_ep(struct isp1760_udc *udc, + u16 index) +{ + unsigned int i; + + if (index == 0) + return &udc->ep[0]; + + for (i = 1; i < ARRAY_SIZE(udc->ep); ++i) { + if (udc->ep[i].addr == index) + return udc->ep[i].desc ? &udc->ep[i] : NULL; + } + + return NULL; +} + +static void __isp1760_udc_select_ep(struct isp1760_ep *ep, int dir) +{ + isp1760_udc_write(ep->udc, DC_EPINDEX, + DC_ENDPIDX(ep->addr & USB_ENDPOINT_NUMBER_MASK) | + (dir == USB_DIR_IN ? DC_EPDIR : 0)); +} + +/** + * isp1760_udc_select_ep - Select an endpoint for register access + * @ep: The endpoint + * + * The ISP1761 endpoint registers are banked. This function selects the target + * endpoint for banked register access. The selection remains valid until the + * next call to this function, the next direct access to the EPINDEX register + * or the next reset, whichever comes first. + * + * Called with the UDC spinlock held. + */ +static void isp1760_udc_select_ep(struct isp1760_ep *ep) +{ + __isp1760_udc_select_ep(ep, ep->addr & USB_ENDPOINT_DIR_MASK); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_ctrl_send_status(struct isp1760_ep *ep, int dir) +{ + struct isp1760_udc *udc = ep->udc; + + /* + * Proceed to the status stage. The status stage data packet flows in + * the direction opposite to the data stage data packets, we thus need + * to select the OUT/IN endpoint for IN/OUT transfers. + */ + isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | + (dir == USB_DIR_IN ? 0 : DC_EPDIR)); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STATUS); + + /* + * The hardware will terminate the request automatically and go back to + * the setup stage without notifying us. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; +} + +/* Called without the UDC spinlock held. */ +static void isp1760_udc_request_complete(struct isp1760_ep *ep, + struct isp1760_request *req, + int status) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "completing request %p with status %d\n", + req, status); + + req->ep = NULL; + req->req.status = status; + req->req.complete(&ep->ep, &req->req); + + spin_lock_irqsave(&udc->lock, flags); + + /* + * When completing control OUT requests, move to the status stage after + * calling the request complete callback. This gives the gadget an + * opportunity to stall the control transfer if needed. + */ + if (status == 0 && ep->addr == 0 && udc->ep0_dir == USB_DIR_OUT) + isp1760_udc_ctrl_send_status(ep, USB_DIR_OUT); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_ctrl_send_stall(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + unsigned long flags; + + dev_dbg(ep->udc->isp->dev, "%s(ep%02x)\n", __func__, ep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + /* Stall both the IN and OUT endpoints. */ + __isp1760_udc_select_ep(ep, USB_DIR_OUT); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); + __isp1760_udc_select_ep(ep, USB_DIR_IN); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_STALL); + + /* A protocol stall completes the control transaction. */ + udc->ep0_state = ISP1760_CTRL_SETUP; + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Data Endpoints + */ + +/* Called with the UDC spinlock held. */ +static bool isp1760_udc_receive(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + unsigned int len; + u32 *buf; + int i; + + isp1760_udc_select_ep(ep); + len = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; + + dev_dbg(udc->isp->dev, "%s: received %u bytes (%u/%u done)\n", + __func__, len, req->req.actual, req->req.length); + + len = min(len, req->req.length - req->req.actual); + + if (!len) { + /* + * There's no data to be read from the FIFO, acknowledge the RX + * interrupt by clearing the buffer. + * + * TODO: What if another packet arrives in the meantime ? The + * datasheet doesn't clearly document how this should be + * handled. + */ + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + return false; + } + + buf = req->req.buf + req->req.actual; + + /* + * Make sure not to read more than one extra byte, otherwise data from + * the next packet might be removed from the FIFO. + */ + for (i = len; i > 2; i -= 4, ++buf) + *buf = le32_to_cpu(isp1760_udc_read(udc, DC_DATAPORT)); + if (i > 0) + *(u16 *)buf = le16_to_cpu(readw(udc->regs + DC_DATAPORT)); + + req->req.actual += len; + + /* + * TODO: The short_not_ok flag isn't supported yet, but isn't used by + * any gadget driver either. + */ + + dev_dbg(udc->isp->dev, + "%s: req %p actual/length %u/%u maxpacket %u packet size %u\n", + __func__, req, req->req.actual, req->req.length, ep->maxpacket, + len); + + ep->rx_pending = false; + + /* + * Complete the request if all data has been received or if a short + * packet has been received. + */ + if (req->req.actual == req->req.length || len < ep->maxpacket) { + list_del(&req->queue); + return true; + } + + return false; +} + +static void isp1760_udc_transmit(struct isp1760_ep *ep, + struct isp1760_request *req) +{ + struct isp1760_udc *udc = ep->udc; + u32 *buf = req->req.buf + req->req.actual; + int i; + + req->packet_size = min(req->req.length - req->req.actual, + ep->maxpacket); + + dev_dbg(udc->isp->dev, "%s: transferring %u bytes (%u/%u done)\n", + __func__, req->packet_size, req->req.actual, + req->req.length); + + __isp1760_udc_select_ep(ep, USB_DIR_IN); + + if (req->packet_size) + isp1760_udc_write(udc, DC_BUFLEN, req->packet_size); + + /* + * Make sure not to write more than one extra byte, otherwise extra data + * will stay in the FIFO and will be transmitted during the next control + * request. The endpoint control CLBUF bit is supposed to allow flushing + * the FIFO for this kind of conditions, but doesn't seem to work. + */ + for (i = req->packet_size; i > 2; i -= 4, ++buf) + isp1760_udc_write(udc, DC_DATAPORT, cpu_to_le32(*buf)); + if (i > 0) + writew(cpu_to_le16(*(u16 *)buf), udc->regs + DC_DATAPORT); + + if (ep->addr == 0) + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + if (!req->packet_size) + isp1760_udc_write(udc, DC_CTRLFUNC, DC_VENDP); +} + +static void isp1760_ep_rx_ready(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *req; + bool complete; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_OUT) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: invalid ep0 state %u\n", __func__, + udc->ep0_state); + return; + } + + if (ep->addr != 0 && !ep->desc) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + ep->addr); + return; + } + + if (list_empty(&ep->queue)) { + ep->rx_pending = true; + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x (%p) has no request queued\n", + __func__, ep->addr, ep); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + complete = isp1760_udc_receive(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, req, 0); +} + +static void isp1760_ep_tx_complete(struct isp1760_ep *ep) +{ + struct isp1760_udc *udc = ep->udc; + struct isp1760_request *complete = NULL; + struct isp1760_request *req; + bool need_zlp; + + spin_lock(&udc->lock); + + if (ep->addr == 0 && udc->ep0_state != ISP1760_CTRL_DATA_IN) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "TX IRQ: invalid endpoint state %u\n", + udc->ep0_state); + return; + } + + if (list_empty(&ep->queue)) { + /* + * This can happen for the control endpoint when the reply to + * the GET_STATUS IN control request is sent directly by the + * setup IRQ handler. Just proceed to the status stage. + */ + if (ep->addr == 0) { + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + spin_unlock(&udc->lock); + return; + } + + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "%s: ep%02x has no request queued\n", + __func__, ep->addr); + return; + } + + req = list_first_entry(&ep->queue, struct isp1760_request, + queue); + req->req.actual += req->packet_size; + + need_zlp = req->req.actual == req->req.length && + !(req->req.length % ep->maxpacket) && + req->packet_size && req->req.zero; + + dev_dbg(udc->isp->dev, + "TX IRQ: req %p actual/length %u/%u maxpacket %u packet size %u zero %u need zlp %u\n", + req, req->req.actual, req->req.length, ep->maxpacket, + req->packet_size, req->req.zero, need_zlp); + + /* + * Complete the request if all data has been sent and we don't need to + * transmit a zero length packet. + */ + if (req->req.actual == req->req.length && !need_zlp) { + complete = req; + list_del(&req->queue); + + if (ep->addr == 0) + isp1760_udc_ctrl_send_status(ep, USB_DIR_IN); + + if (!list_empty(&ep->queue)) + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + else + req = NULL; + } + + /* + * Transmit the next packet or start the next request, if any. + * + * TODO: If the endpoint is stalled the next request shouldn't be + * started, but what about the next packet ? + */ + if (req) + isp1760_udc_transmit(ep, req); + + spin_unlock(&udc->lock); + + if (complete) + isp1760_udc_request_complete(ep, complete, 0); +} + +static int __isp1760_udc_set_halt(struct isp1760_ep *ep, bool halt) +{ + struct isp1760_udc *udc = ep->udc; + + dev_dbg(udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + halt ? "set" : "clear", ep->addr); + + if (ep->desc && usb_endpoint_xfer_isoc(ep->desc)) { + dev_dbg(udc->isp->dev, "%s: ep%02x is isochronous\n", __func__, + ep->addr); + return -EINVAL; + } + + isp1760_udc_select_ep(ep); + isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); + + if (ep->addr == 0) { + /* When halting the control endpoint, stall both IN and OUT. */ + __isp1760_udc_select_ep(ep, USB_DIR_IN); + isp1760_udc_write(udc, DC_CTRLFUNC, halt ? DC_STALL : 0); + } else if (!halt) { + /* Reset the data PID by cycling the endpoint enable bit. */ + u16 eptype = isp1760_udc_read(udc, DC_EPTYPE); + + isp1760_udc_write(udc, DC_EPTYPE, eptype & ~DC_EPENABLE); + isp1760_udc_write(udc, DC_EPTYPE, eptype); + + /* + * Disabling the endpoint emptied the transmit FIFO, fill it + * again if a request is pending. + * + * TODO: Does the gadget framework require synchronizatino with + * the TX IRQ handler ? + */ + if ((ep->addr & USB_DIR_IN) && !list_empty(&ep->queue)) { + struct isp1760_request *req; + + req = list_first_entry(&ep->queue, + struct isp1760_request, queue); + isp1760_udc_transmit(ep, req); + } + } + + ep->halted = halt; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Control Endpoint + */ + +static int isp1760_udc_get_status(struct isp1760_udc *udc, + const struct usb_ctrlrequest *req) +{ + struct isp1760_ep *ep; + u16 status; + + if (req->wLength != cpu_to_le16(2) || req->wValue != cpu_to_le16(0)) + return -EINVAL; + + switch (req->bRequestType) { + case USB_DIR_IN | USB_RECIP_DEVICE: + status = udc->devstatus; + break; + + case USB_DIR_IN | USB_RECIP_INTERFACE: + status = 0; + break; + + case USB_DIR_IN | USB_RECIP_ENDPOINT: + ep = isp1760_udc_find_ep(udc, le16_to_cpu(req->wIndex)); + if (!ep) + return -EINVAL; + + status = 0; + if (ep->halted) + status |= 1 << USB_ENDPOINT_HALT; + break; + + default: + return -EINVAL; + } + + isp1760_udc_write(udc, DC_EPINDEX, DC_ENDPIDX(0) | DC_EPDIR); + isp1760_udc_write(udc, DC_BUFLEN, 2); + + writew(cpu_to_le16(status), udc->regs + DC_DATAPORT); + + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + + dev_dbg(udc->isp->dev, "%s: status 0x%04x\n", __func__, status); + + return 0; +} + +static int isp1760_udc_set_address(struct isp1760_udc *udc, u16 addr) +{ + if (addr > 127) { + dev_dbg(udc->isp->dev, "invalid device address %u\n", addr); + return -EINVAL; + } + + if (udc->gadget.state != USB_STATE_DEFAULT && + udc->gadget.state != USB_STATE_ADDRESS) { + dev_dbg(udc->isp->dev, "can't set address in state %u\n", + udc->gadget.state); + return -EINVAL; + } + + usb_gadget_set_state(&udc->gadget, addr ? USB_STATE_ADDRESS : + USB_STATE_DEFAULT); + + isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN | addr); + + spin_lock(&udc->lock); + isp1760_udc_ctrl_send_status(&udc->ep[0], USB_DIR_OUT); + spin_unlock(&udc->lock); + + return 0; +} + +static bool isp1760_ep0_setup_standard(struct isp1760_udc *udc, + struct usb_ctrlrequest *req) +{ + bool stall; + + switch (req->bRequest) { + case USB_REQ_GET_STATUS: + return isp1760_udc_get_status(udc, req); + + case USB_REQ_CLEAR_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup feature. */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + /* + * If the endpoint is wedged only the gadget can clear + * the halt feature. Pretend success in that case, but + * keep the endpoint halted. + */ + if (!ep->wedged) + stall = __isp1760_udc_set_halt(ep, false); + else + stall = false; + + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_FEATURE: + switch (req->bRequestType) { + case USB_DIR_OUT | USB_RECIP_DEVICE: { + /* TODO: Handle remote wakeup and test mode features */ + return true; + } + + case USB_DIR_OUT | USB_RECIP_ENDPOINT: { + u16 index = le16_to_cpu(req->wIndex); + struct isp1760_ep *ep; + + if (req->wLength != cpu_to_le16(0) || + req->wValue != cpu_to_le16(USB_ENDPOINT_HALT)) + return true; + + ep = isp1760_udc_find_ep(udc, index); + if (!ep) + return true; + + spin_lock(&udc->lock); + + stall = __isp1760_udc_set_halt(ep, true); + if (!stall) + isp1760_udc_ctrl_send_status(&udc->ep[0], + USB_DIR_OUT); + + spin_unlock(&udc->lock); + return stall; + } + + default: + return true; + } + break; + + case USB_REQ_SET_ADDRESS: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + return isp1760_udc_set_address(udc, le16_to_cpu(req->wValue)); + + case USB_REQ_SET_CONFIGURATION: + if (req->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE)) + return true; + + if (udc->gadget.state != USB_STATE_ADDRESS && + udc->gadget.state != USB_STATE_CONFIGURED) + return true; + + stall = udc->driver->setup(&udc->gadget, req) < 0; + if (stall) + return true; + + usb_gadget_set_state(&udc->gadget, req->wValue ? + USB_STATE_CONFIGURED : USB_STATE_ADDRESS); + + /* + * SET_CONFIGURATION (and SET_INTERFACE) must reset the halt + * feature on all endpoints. There is however no need to do so + * explicitly here as the gadget driver will disable and + * reenable endpoints, clearing the halt feature. + */ + return false; + + default: + return udc->driver->setup(&udc->gadget, req) < 0; + } +} + +static void isp1760_ep0_setup(struct isp1760_udc *udc) +{ + union { + struct usb_ctrlrequest r; + u32 data[2]; + } req; + unsigned int count; + bool stall = false; + + spin_lock(&udc->lock); + + isp1760_udc_write(udc, DC_EPINDEX, DC_EP0SETUP); + + count = isp1760_udc_read(udc, DC_BUFLEN) & DC_DATACOUNT_MASK; + if (count != sizeof(req)) { + spin_unlock(&udc->lock); + + dev_err(udc->isp->dev, "invalid length %u for setup packet\n", + count); + + isp1760_udc_ctrl_send_stall(&udc->ep[0]); + return; + } + + req.data[0] = isp1760_udc_read(udc, DC_DATAPORT); + req.data[1] = isp1760_udc_read(udc, DC_DATAPORT); + + if (udc->ep0_state != ISP1760_CTRL_SETUP) { + spin_unlock(&udc->lock); + dev_dbg(udc->isp->dev, "unexpected SETUP packet\n"); + return; + } + + /* Move to the data stage. */ + if (!req.r.wLength) + udc->ep0_state = ISP1760_CTRL_STATUS; + else if (req.r.bRequestType & USB_DIR_IN) + udc->ep0_state = ISP1760_CTRL_DATA_IN; + else + udc->ep0_state = ISP1760_CTRL_DATA_OUT; + + udc->ep0_dir = req.r.bRequestType & USB_DIR_IN; + udc->ep0_length = le16_to_cpu(req.r.wLength); + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, + "%s: bRequestType 0x%02x bRequest 0x%02x wValue 0x%04x wIndex 0x%04x wLength 0x%04x\n", + __func__, req.r.bRequestType, req.r.bRequest, + le16_to_cpu(req.r.wValue), le16_to_cpu(req.r.wIndex), + le16_to_cpu(req.r.wLength)); + + if ((req.r.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + stall = isp1760_ep0_setup_standard(udc, &req.r); + else + stall = udc->driver->setup(&udc->gadget, &req.r) < 0; + + if (stall) + isp1760_udc_ctrl_send_stall(&udc->ep[0]); +} + +/* ----------------------------------------------------------------------------- + * Gadget Endpoint Operations + */ + +static int isp1760_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + unsigned int type; + + dev_dbg(uep->udc->isp->dev, "%s\n", __func__); + + /* + * Validate the descriptor. The control endpoint can't be enabled + * manually. + */ + if (desc->bDescriptorType != USB_DT_ENDPOINT || + desc->bEndpointAddress == 0 || + desc->bEndpointAddress != uep->addr || + le16_to_cpu(desc->wMaxPacketSize) > ep->maxpacket) { + dev_dbg(udc->isp->dev, + "%s: invalid descriptor type %u addr %02x ep addr %02x max packet size %u/%u\n", + __func__, desc->bDescriptorType, + desc->bEndpointAddress, uep->addr, + le16_to_cpu(desc->wMaxPacketSize), ep->maxpacket); + return -EINVAL; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_ISOC: + type = DC_ENDPTYP_ISOC; + break; + case USB_ENDPOINT_XFER_BULK: + type = DC_ENDPTYP_BULK; + break; + case USB_ENDPOINT_XFER_INT: + type = DC_ENDPTYP_INTERRUPT; + break; + case USB_ENDPOINT_XFER_CONTROL: + default: + dev_dbg(udc->isp->dev, "%s: control endpoints unsupported\n", + __func__); + return -EINVAL; + } + + spin_lock_irqsave(&udc->lock, flags); + + uep->desc = desc; + uep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); + uep->rx_pending = false; + uep->halted = false; + uep->wedged = false; + + isp1760_udc_select_ep(uep); + isp1760_udc_write(udc, DC_EPMAXPKTSZ, uep->maxpacket); + isp1760_udc_write(udc, DC_BUFLEN, uep->maxpacket); + isp1760_udc_write(udc, DC_EPTYPE, DC_EPENABLE | type); + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +static int isp1760_ep_disable(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + struct isp1760_request *req, *nreq; + LIST_HEAD(req_list); + unsigned long flags; + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + spin_lock_irqsave(&udc->lock, flags); + + if (!uep->desc) { + dev_dbg(udc->isp->dev, "%s: endpoint not enabled\n", __func__); + spin_unlock_irqrestore(&udc->lock, flags); + return -EINVAL; + } + + uep->desc = NULL; + uep->maxpacket = 0; + + isp1760_udc_select_ep(uep); + isp1760_udc_write(udc, DC_EPTYPE, 0); + + /* TODO Synchronize with the IRQ handler */ + + list_splice_init(&uep->queue, &req_list); + + spin_unlock_irqrestore(&udc->lock, flags); + + list_for_each_entry_safe(req, nreq, &req_list, queue) { + list_del(&req->queue); + isp1760_udc_request_complete(uep, req, -ESHUTDOWN); + } + + return 0; +} + +static struct usb_request *isp1760_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct isp1760_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + + return &req->req; +} + +static void isp1760_ep_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + + kfree(req); +} + +static int isp1760_ep_queue(struct usb_ep *ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + bool complete = false; + unsigned long flags; + int ret = 0; + + _req->status = -EINPROGRESS; + _req->actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + dev_dbg(udc->isp->dev, + "%s: req %p (%u bytes%s) ep %p(0x%02x)\n", __func__, _req, + _req->length, _req->zero ? " (zlp)" : "", uep, uep->addr); + + req->ep = uep; + + if (uep->addr == 0) { + if (_req->length != udc->ep0_length && + udc->ep0_state != ISP1760_CTRL_DATA_IN) { + dev_dbg(udc->isp->dev, + "%s: invalid length %u for req %p\n", + __func__, _req->length, req); + ret = -EINVAL; + goto done; + } + + switch (udc->ep0_state) { + case ISP1760_CTRL_DATA_IN: + dev_dbg(udc->isp->dev, "%s: transmitting req %p\n", + __func__, req); + + list_add_tail(&req->queue, &uep->queue); + isp1760_udc_transmit(uep, req); + break; + + case ISP1760_CTRL_DATA_OUT: + list_add_tail(&req->queue, &uep->queue); + __isp1760_udc_select_ep(uep, USB_DIR_OUT); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_DSEN); + break; + + case ISP1760_CTRL_STATUS: + complete = true; + break; + + default: + dev_dbg(udc->isp->dev, "%s: invalid ep0 state\n", + __func__); + ret = -EINVAL; + break; + } + } else if (uep->desc) { + bool empty = list_empty(&uep->queue); + + list_add_tail(&req->queue, &uep->queue); + if ((uep->addr & USB_DIR_IN) && !uep->halted && empty) + isp1760_udc_transmit(uep, req); + else if (!(uep->addr & USB_DIR_IN) && uep->rx_pending) + complete = isp1760_udc_receive(uep, req); + } else { + dev_dbg(udc->isp->dev, + "%s: can't queue request to disabled ep%02x\n", + __func__, uep->addr); + ret = -ESHUTDOWN; + } + +done: + if (ret < 0) + req->ep = NULL; + + spin_unlock_irqrestore(&udc->lock, flags); + + if (complete) + isp1760_udc_request_complete(uep, req, 0); + + return ret; +} + +static int isp1760_ep_dequeue(struct usb_ep *ep, struct usb_request *_req) +{ + struct isp1760_request *req = req_to_udc_req(_req); + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + dev_dbg(uep->udc->isp->dev, "%s(ep%02x)\n", __func__, uep->addr); + + spin_lock_irqsave(&udc->lock, flags); + + if (req->ep != uep) + req = NULL; + else + list_del(&req->queue); + + spin_unlock_irqrestore(&udc->lock, flags); + + if (!req) + return -EINVAL; + + isp1760_udc_request_complete(uep, req, -ECONNRESET); + return 0; +} + +static int __isp1760_ep_set_halt(struct isp1760_ep *uep, bool stall, bool wedge) +{ + struct isp1760_udc *udc = uep->udc; + int ret; + + if (!uep->addr) { + /* + * Halting the control endpoint is only valid as a delayed error + * response to a SETUP packet. Make sure EP0 is in the right + * stage and that the gadget isn't trying to clear the halt + * condition. + */ + if (WARN_ON(udc->ep0_state == ISP1760_CTRL_SETUP || !stall || + wedge)) { + return -EINVAL; + } + } + + if (uep->addr && !uep->desc) { + dev_dbg(udc->isp->dev, "%s: ep%02x is disabled\n", __func__, + uep->addr); + return -EINVAL; + } + + if (uep->addr & USB_DIR_IN) { + /* Refuse to halt IN endpoints with active transfers. */ + if (!list_empty(&uep->queue)) { + dev_dbg(udc->isp->dev, + "%s: ep%02x has request pending\n", __func__, + uep->addr); + return -EAGAIN; + } + } + + ret = __isp1760_udc_set_halt(uep, stall); + if (ret < 0) + return ret; + + if (!uep->addr) { + /* + * Stalling EP0 completes the control transaction, move back to + * the SETUP state. + */ + udc->ep0_state = ISP1760_CTRL_SETUP; + return 0; + } + + if (wedge) + uep->wedged = true; + else if (!stall) + uep->wedged = false; + + return 0; +} + +static int isp1760_ep_set_halt(struct usb_ep *ep, int value) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: %s halt on ep%02x\n", __func__, + value ? "set" : "clear", uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, value, false); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static int isp1760_ep_set_wedge(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + unsigned long flags; + int ret; + + dev_dbg(uep->udc->isp->dev, "%s: set wedge on ep%02x)\n", __func__, + uep->addr); + + spin_lock_irqsave(&uep->udc->lock, flags); + ret = __isp1760_ep_set_halt(uep, true, true); + spin_unlock_irqrestore(&uep->udc->lock, flags); + + return ret; +} + +static void isp1760_ep_fifo_flush(struct usb_ep *ep) +{ + struct isp1760_ep *uep = ep_to_udc_ep(ep); + struct isp1760_udc *udc = uep->udc; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + isp1760_udc_select_ep(uep); + + /* + * Set the CLBUF bit twice to flush both buffers in case double + * buffering is enabled. + */ + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + isp1760_udc_write(udc, DC_CTRLFUNC, DC_CLBUF); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static const struct usb_ep_ops isp1760_ep_ops = { + .enable = isp1760_ep_enable, + .disable = isp1760_ep_disable, + .alloc_request = isp1760_ep_alloc_request, + .free_request = isp1760_ep_free_request, + .queue = isp1760_ep_queue, + .dequeue = isp1760_ep_dequeue, + .set_halt = isp1760_ep_set_halt, + .set_wedge = isp1760_ep_set_wedge, + .fifo_flush = isp1760_ep_fifo_flush, +}; + +/* ----------------------------------------------------------------------------- + * Device States + */ + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_connect(struct isp1760_udc *udc) +{ + usb_gadget_set_state(&udc->gadget, USB_STATE_POWERED); + mod_timer(&udc->vbus_timer, jiffies + ISP1760_VBUS_POLL_INTERVAL); +} + +/* Called with the UDC spinlock held. */ +static void isp1760_udc_disconnect(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_POWERED) + return; + + dev_dbg(udc->isp->dev, "Device disconnected in state %u\n", + udc->gadget.state); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + if (udc->driver->disconnect) + udc->driver->disconnect(&udc->gadget); + + del_timer(&udc->vbus_timer); + + /* TODO Reset all endpoints ? */ +} + +static void isp1760_udc_init_hw(struct isp1760_udc *udc) +{ + /* + * The device controller currently shares its interrupt with the host + * controller, the DC_IRQ polarity and signaling mode are ignored. Set + * the to active-low level-triggered. + * + * Configure the control, in and out pipes to generate interrupts on + * ACK tokens only (and NYET for the out pipe). The default + * configuration also generates an interrupt on the first NACK token. + */ + isp1760_udc_write(udc, DC_INTCONF, DC_CDBGMOD_ACK | DC_DDBGMODIN_ACK | + DC_DDBGMODOUT_ACK_NYET); + + isp1760_udc_write(udc, DC_INTENABLE, DC_IEPRXTX(7) | DC_IEPRXTX(6) | + DC_IEPRXTX(5) | DC_IEPRXTX(4) | DC_IEPRXTX(3) | + DC_IEPRXTX(2) | DC_IEPRXTX(1) | DC_IEPRXTX(0) | + DC_IEP0SETUP | DC_IEVBUS | DC_IERESM | DC_IESUSP | + DC_IEHS_STA | DC_IEBRST); + + if (udc->connected) + isp1760_set_pullup(udc->isp, true); + + isp1760_udc_write(udc, DC_ADDRESS, DC_DEVEN); +} + +static void isp1760_udc_reset(struct isp1760_udc *udc) +{ + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + /* + * The bus reset has reset most registers to their default value, + * reinitialize the UDC hardware. + */ + isp1760_udc_init_hw(udc); + + udc->ep0_state = ISP1760_CTRL_SETUP; + udc->gadget.speed = USB_SPEED_FULL; + + usb_gadget_udc_reset(&udc->gadget, udc->driver); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +static void isp1760_udc_suspend(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void isp1760_udc_resume(struct isp1760_udc *udc) +{ + if (udc->gadget.state < USB_STATE_DEFAULT) + return; + + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); +} + +/* ----------------------------------------------------------------------------- + * Gadget Operations + */ + +static int isp1760_udc_get_frame(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + return isp1760_udc_read(udc, DC_FRAMENUM) & ((1 << 11) - 1); +} + +static int isp1760_udc_wakeup(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + return -ENOTSUPP; +} + +static int isp1760_udc_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + if (is_selfpowered) + udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; + else + udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); + + return 0; +} + +static int isp1760_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + isp1760_set_pullup(udc->isp, is_on); + udc->connected = is_on; + + return 0; +} + +static int isp1760_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + /* The hardware doesn't support low speed. */ + if (driver->max_speed < USB_SPEED_FULL) { + dev_err(udc->isp->dev, "Invalid gadget driver\n"); + return -EINVAL; + } + + spin_lock(&udc->lock); + + if (udc->driver) { + dev_err(udc->isp->dev, "UDC already has a gadget driver\n"); + spin_unlock(&udc->lock); + return -EBUSY; + } + + udc->driver = driver; + + spin_unlock(&udc->lock); + + dev_dbg(udc->isp->dev, "starting UDC with driver %s\n", + driver->function); + + udc->devstatus = 0; + udc->connected = true; + + usb_gadget_set_state(&udc->gadget, USB_STATE_ATTACHED); + + /* DMA isn't supported yet, don't enable the DMA clock. */ + isp1760_udc_write(udc, DC_MODE, DC_GLINTENA); + + isp1760_udc_init_hw(udc); + + dev_dbg(udc->isp->dev, "UDC started with driver %s\n", + driver->function); + + return 0; +} + +static int isp1760_udc_stop(struct usb_gadget *gadget) +{ + struct isp1760_udc *udc = gadget_to_udc(gadget); + + dev_dbg(udc->isp->dev, "%s\n", __func__); + + del_timer_sync(&udc->vbus_timer); + + isp1760_udc_write(udc, DC_MODE, 0); + + spin_lock(&udc->lock); + udc->driver = NULL; + spin_unlock(&udc->lock); + + return 0; +} + +static struct usb_gadget_ops isp1760_udc_ops = { + .get_frame = isp1760_udc_get_frame, + .wakeup = isp1760_udc_wakeup, + .set_selfpowered = isp1760_udc_set_selfpowered, + .pullup = isp1760_udc_pullup, + .udc_start = isp1760_udc_start, + .udc_stop = isp1760_udc_stop, +}; + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t isp1760_udc_irq(int irq, void *dev) +{ + struct isp1760_udc *udc = dev; + unsigned int i; + u32 status; + + status = isp1760_udc_read(udc, DC_INTERRUPT) + & isp1760_udc_read(udc, DC_INTENABLE); + isp1760_udc_write(udc, DC_INTERRUPT, status); + + if (status & DC_IEVBUS) { + dev_dbg(udc->isp->dev, "%s(VBUS)\n", __func__); + /* The VBUS interrupt is only triggered when VBUS appears. */ + spin_lock(&udc->lock); + isp1760_udc_connect(udc); + spin_unlock(&udc->lock); + } + + if (status & DC_IEBRST) { + dev_dbg(udc->isp->dev, "%s(BRST)\n", __func__); + + isp1760_udc_reset(udc); + } + + for (i = 0; i <= 7; ++i) { + struct isp1760_ep *ep = &udc->ep[i*2]; + + if (status & DC_IEPTX(i)) { + dev_dbg(udc->isp->dev, "%s(EPTX%u)\n", __func__, i); + isp1760_ep_tx_complete(ep); + } + + if (status & DC_IEPRX(i)) { + dev_dbg(udc->isp->dev, "%s(EPRX%u)\n", __func__, i); + isp1760_ep_rx_ready(i ? ep - 1 : ep); + } + } + + if (status & DC_IEP0SETUP) { + dev_dbg(udc->isp->dev, "%s(EP0SETUP)\n", __func__); + + isp1760_ep0_setup(udc); + } + + if (status & DC_IERESM) { + dev_dbg(udc->isp->dev, "%s(RESM)\n", __func__); + isp1760_udc_resume(udc); + } + + if (status & DC_IESUSP) { + dev_dbg(udc->isp->dev, "%s(SUSP)\n", __func__); + + spin_lock(&udc->lock); + if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else + isp1760_udc_suspend(udc); + spin_unlock(&udc->lock); + } + + if (status & DC_IEHS_STA) { + dev_dbg(udc->isp->dev, "%s(HS_STA)\n", __func__); + udc->gadget.speed = USB_SPEED_HIGH; + } + + return status ? IRQ_HANDLED : IRQ_NONE; +} + +static void isp1760_udc_vbus_poll(unsigned long data) +{ + struct isp1760_udc *udc = (struct isp1760_udc *)data; + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + if (!(isp1760_udc_read(udc, DC_MODE) & DC_VBUSSTAT)) + isp1760_udc_disconnect(udc); + else if (udc->gadget.state >= USB_STATE_POWERED) + mod_timer(&udc->vbus_timer, + jiffies + ISP1760_VBUS_POLL_INTERVAL); + + spin_unlock_irqrestore(&udc->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Registration + */ + +static void isp1760_udc_init_eps(struct isp1760_udc *udc) +{ + unsigned int i; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + for (i = 0; i < ARRAY_SIZE(udc->ep); ++i) { + struct isp1760_ep *ep = &udc->ep[i]; + unsigned int ep_num = (i + 1) / 2; + bool is_in = !(i & 1); + + ep->udc = udc; + + INIT_LIST_HEAD(&ep->queue); + + ep->addr = (ep_num && is_in ? USB_DIR_IN : USB_DIR_OUT) + | ep_num; + ep->desc = NULL; + + sprintf(ep->name, "ep%u%s", ep_num, + ep_num ? (is_in ? "in" : "out") : ""); + + ep->ep.ops = &isp1760_ep_ops; + ep->ep.name = ep->name; + + /* + * Hardcode the maximum packet sizes for now, to 64 bytes for + * the control endpoint and 512 bytes for all other endpoints. + * This fits in the 8kB FIFO without double-buffering. + */ + if (ep_num == 0) { + ep->ep.maxpacket = 64; + ep->maxpacket = 64; + udc->gadget.ep0 = &ep->ep; + } else { + ep->ep.maxpacket = 512; + ep->maxpacket = 0; + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + } + } +} + +static int isp1760_udc_init(struct isp1760_udc *udc) +{ + u16 scratch; + u32 chipid; + + /* + * Check that the controller is present by writing to the scratch + * register, modifying the bus pattern by reading from the chip ID + * register, and reading the scratch register value back. The chip ID + * and scratch register contents must match the expected values. + */ + isp1760_udc_write(udc, DC_SCRATCH, 0xbabe); + chipid = isp1760_udc_read(udc, DC_CHIPID); + scratch = isp1760_udc_read(udc, DC_SCRATCH); + + if (scratch != 0xbabe) { + dev_err(udc->isp->dev, + "udc: scratch test failed (0x%04x/0x%08x)\n", + scratch, chipid); + return -ENODEV; + } + + if (chipid != 0x00011582) { + dev_err(udc->isp->dev, "udc: invalid chip ID 0x%08x\n", chipid); + return -ENODEV; + } + + /* Reset the device controller. */ + isp1760_udc_write(udc, DC_MODE, DC_SFRESET); + usleep_range(10000, 11000); + isp1760_udc_write(udc, DC_MODE, 0); + usleep_range(10000, 11000); + + return 0; +} + +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + struct isp1760_udc *udc = &isp->udc; + const char *devname; + int ret; + + udc->irq = -1; + udc->isp = isp; + udc->regs = isp->regs; + + spin_lock_init(&udc->lock); + setup_timer(&udc->vbus_timer, isp1760_udc_vbus_poll, + (unsigned long)udc); + + ret = isp1760_udc_init(udc); + if (ret < 0) + return ret; + + devname = dev_name(isp->dev); + udc->irqname = kmalloc(strlen(devname) + 7, GFP_KERNEL); + if (!udc->irqname) + return -ENOMEM; + + sprintf(udc->irqname, "%s (udc)", devname); + + ret = request_irq(irq, isp1760_udc_irq, IRQF_SHARED | IRQF_DISABLED | + irqflags, udc->irqname, udc); + if (ret < 0) + goto error; + + udc->irq = irq; + + /* + * Initialize the gadget static fields and register its device. Gadget + * fields that vary during the life time of the gadget are initialized + * by the UDC core. + */ + udc->gadget.ops = &isp1760_udc_ops; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.name = "isp1761_udc"; + + isp1760_udc_init_eps(udc); + + ret = usb_add_gadget_udc(isp->dev, &udc->gadget); + if (ret < 0) + goto error; + + return 0; + +error: + if (udc->irq >= 0) + free_irq(udc->irq, udc); + kfree(udc->irqname); + + return ret; +} + +void isp1760_udc_unregister(struct isp1760_device *isp) +{ + struct isp1760_udc *udc = &isp->udc; + + usb_del_gadget_udc(&udc->gadget); + + free_irq(udc->irq, udc); + kfree(udc->irqname); +} diff --git a/drivers/usb/isp1760/isp1760-udc.h b/drivers/usb/isp1760/isp1760-udc.h new file mode 100644 index 0000000..4af6ba6 --- /dev/null +++ b/drivers/usb/isp1760/isp1760-udc.h @@ -0,0 +1,106 @@ +/* + * Driver for the NXP ISP1761 device controller + * + * Copyright 2014 Ideas on Board Oy + * + * Contacts: + * Laurent Pinchart + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef _ISP1760_UDC_H_ +#define _ISP1760_UDC_H_ + +#include +#include +#include +#include +#include + +struct isp1760_device; +struct isp1760_udc; + +enum isp1760_ctrl_state { + ISP1760_CTRL_SETUP, /* Waiting for a SETUP transaction */ + ISP1760_CTRL_DATA_IN, /* Setup received, data IN stage */ + ISP1760_CTRL_DATA_OUT, /* Setup received, data OUT stage */ + ISP1760_CTRL_STATUS, /* 0-length request in status stage */ +}; + +struct isp1760_ep { + struct isp1760_udc *udc; + struct usb_ep ep; + + struct list_head queue; + + unsigned int addr; + unsigned int maxpacket; + char name[7]; + + const struct usb_endpoint_descriptor *desc; + + bool rx_pending; + bool halted; + bool wedged; +}; + +/** + * struct isp1760_udc - UDC state information + * irq: IRQ number + * irqname: IRQ name (as passed to request_irq) + * regs: Base address of the UDC registers + * driver: Gadget driver + * gadget: Gadget device + * lock: Protects driver, vbus_timer, ep, ep0_*, DC_EPINDEX register + * ep: Array of endpoints + * ep0_state: Control request state for endpoint 0 + * ep0_dir: Direction of the current control request + * ep0_length: Length of the current control request + * connected: Tracks gadget driver bus connection state + */ +struct isp1760_udc { +#if CONFIG_USB_ISP1761_UDC + struct isp1760_device *isp; + + int irq; + char *irqname; + void __iomem *regs; + + struct usb_gadget_driver *driver; + struct usb_gadget gadget; + + spinlock_t lock; + struct timer_list vbus_timer; + + struct isp1760_ep ep[15]; + + enum isp1760_ctrl_state ep0_state; + u8 ep0_dir; + u16 ep0_length; + + bool connected; + + unsigned int devstatus; +#endif +}; + +#if CONFIG_USB_ISP1761_UDC +int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags); +void isp1760_udc_unregister(struct isp1760_device *isp); +#else +static inline int isp1760_udc_register(struct isp1760_device *isp, int irq, + unsigned long irqflags) +{ + return 0; +} + +static inline void isp1760_udc_unregister(struct isp1760_device *isp) +{ +} +#endif + +#endif -- cgit v0.10.2 From 100832abf065bc186ae48165c16546784b90a4be Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:03 +0200 Subject: usb: isp1760: Make HCD support optional Enable compilation of the isp1760 driver in pure host mode, pure device mode, or dual-role mode. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/isp1760/Kconfig b/drivers/usb/isp1760/Kconfig index c09ab8f..c94b7d9 100644 --- a/drivers/usb/isp1760/Kconfig +++ b/drivers/usb/isp1760/Kconfig @@ -1,6 +1,6 @@ config USB_ISP1760 tristate "NXP ISP 1760/1761 support" - depends on USB + depends on USB || USB_GADGET help Say Y or M here if your system as an ISP1760 USB host controller or an ISP1761 USB dual-role controller. @@ -14,9 +14,46 @@ config USB_ISP1760 To compile this driver as a module, choose M here: the module will be called isp1760. +config USB_ISP1760_HCD + bool + config USB_ISP1761_UDC - boolean "NXP ISP1761 USB Device Controller" - depends on USB_ISP1760 && USB_GADGET + bool + +if USB_ISP1760 + +choice + bool "ISP1760 Mode Selection" + default USB_ISP1760_DUAL_ROLE if (USB && USB_GADGET) + default USB_ISP1760_HOST_ROLE if (USB && !USB_GADGET) + default USB_ISP1760_GADGET_ROLE if (!USB && USB_GADGET) + +config USB_ISP1760_HOST_ROLE + bool "Host only mode" + depends on USB=y || USB=USB_ISP1760 + select USB_ISP1760_HCD + help + Select this if you want to use the ISP1760 in host mode only. The + gadget function will be disabled. + +config USB_ISP1760_GADGET_ROLE + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_ISP1760 + select USB_ISP1761_UDC + help + Select this if you want to use the ISP1760 in peripheral mode only. + The host function will be disabled. + +config USB_ISP1760_DUAL_ROLE + bool "Dual Role mode" + depends on USB=y || USB=USB_ISP1760 + depends on USB_GADGET=y || USB_GADGET=USB_ISP1760 + select USB_ISP1760_HCD + select USB_ISP1761_UDC help - The NXP ISP1761 is a dual-role high-speed USB host and device - controller. + Select this if you want to use the ISP1760 in both host and + peripheral modes. + +endchoice + +endif diff --git a/drivers/usb/isp1760/Makefile b/drivers/usb/isp1760/Makefile index 698ccb0..2b74107 100644 --- a/drivers/usb/isp1760/Makefile +++ b/drivers/usb/isp1760/Makefile @@ -1,4 +1,5 @@ -isp1760-y := isp1760-core.o isp1760-hcd.o isp1760-if.o +isp1760-y := isp1760-core.o isp1760-if.o +isp1760-$(CONFIG_USB_ISP1760_HCD) += isp1760-hcd.o isp1760-$(CONFIG_USB_ISP1761_UDC) += isp1760-udc.o obj-$(CONFIG_USB_ISP1760) += isp1760.o diff --git a/drivers/usb/isp1760/isp1760-hcd.h b/drivers/usb/isp1760/isp1760-hcd.h index df7ea36..0c1c98d 100644 --- a/drivers/usb/isp1760/isp1760-hcd.h +++ b/drivers/usb/isp1760/isp1760-hcd.h @@ -47,6 +47,7 @@ enum isp1760_queue_head_types { }; struct isp1760_hcd { +#ifdef CONFIG_USB_ISP1760_HCD struct usb_hcd *hcd; u32 hcs_params; @@ -64,8 +65,10 @@ struct isp1760_hcd { unsigned i_thresh; unsigned long reset_done; unsigned long next_statechange; +#endif }; +#ifdef CONFIG_USB_ISP1760_HCD int isp1760_hcd_register(struct isp1760_hcd *priv, void __iomem *regs, struct resource *mem, int irq, unsigned long irqflags, struct device *dev); @@ -73,5 +76,27 @@ void isp1760_hcd_unregister(struct isp1760_hcd *priv); int isp1760_init_kmem_once(void); void isp1760_deinit_kmem_cache(void); +#else +static inline int isp1760_hcd_register(struct isp1760_hcd *priv, + void __iomem *regs, struct resource *mem, + int irq, unsigned long irqflags, + struct device *dev) +{ + return 0; +} + +static inline void isp1760_hcd_unregister(struct isp1760_hcd *priv) +{ +} + +static inline int isp1760_init_kmem_once(void) +{ + return 0; +} + +static inline void isp1760_deinit_kmem_cache(void) +{ +} +#endif #endif /* _ISP1760_HCD_H_ */ diff --git a/drivers/usb/isp1760/isp1760-udc.h b/drivers/usb/isp1760/isp1760-udc.h index 4af6ba6..26899ed 100644 --- a/drivers/usb/isp1760/isp1760-udc.h +++ b/drivers/usb/isp1760/isp1760-udc.h @@ -62,7 +62,7 @@ struct isp1760_ep { * connected: Tracks gadget driver bus connection state */ struct isp1760_udc { -#if CONFIG_USB_ISP1761_UDC +#ifdef CONFIG_USB_ISP1761_UDC struct isp1760_device *isp; int irq; @@ -87,7 +87,7 @@ struct isp1760_udc { #endif }; -#if CONFIG_USB_ISP1761_UDC +#ifdef CONFIG_USB_ISP1761_UDC int isp1760_udc_register(struct isp1760_device *isp, int irq, unsigned long irqflags); void isp1760_udc_unregister(struct isp1760_device *isp); -- cgit v0.10.2 From 1f8d9b9b503070e5450f49e0a341ac3b43d9164d Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:04 +0200 Subject: usb: isp1760: Remove duplicate usb_disabled() check Both isp1760_register() and isp1761_pci_probe() check whether USB is disabled by calling usb_disabled(), and bail out with an error if it is. One check is enough, remove the PCI-specific check. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/isp1760/isp1760-if.c b/drivers/usb/isp1760/isp1760-if.c index c2a94c9..264be4d 100644 --- a/drivers/usb/isp1760/isp1760-if.c +++ b/drivers/usb/isp1760/isp1760-if.c @@ -126,9 +126,6 @@ static int isp1761_pci_probe(struct pci_dev *dev, unsigned int devflags = 0; int ret; - if (usb_disabled()) - return -ENODEV; - if (!dev->irq) return -ENODEV; -- cgit v0.10.2 From d21daf1e90514cee8e3fb11c8e28acee3fb87edf Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Wed, 21 Jan 2015 00:56:05 +0200 Subject: usb: isp1760: Fix USB disabled check The isp1760 driver registration function returns an error if USB is disabled. This made sense when the driver only supported host controllers, but now that it supports peripheral controllers host support isn't mandatory anymore. Fix this by returning an error only when both the HCD and UDC functions are disabled, either through the kernel configuration or at runtime. Signed-off-by: Laurent Pinchart Signed-off-by: Felipe Balbi diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c index 727e90a..b982755 100644 --- a/drivers/usb/isp1760/isp1760-core.c +++ b/drivers/usb/isp1760/isp1760-core.c @@ -112,9 +112,15 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, struct device *dev, unsigned int devflags) { struct isp1760_device *isp; + bool udc_disabled = !(devflags & ISP1760_FLAG_ISP1761); int ret; - if (usb_disabled()) + /* + * If neither the HCD not the UDC is enabled return an error, as no + * device would be registered. + */ + if ((!IS_ENABLED(CONFIG_USB_ISP1760_HCD) || usb_disabled()) && + (!IS_ENABLED(CONFIG_USB_ISP1761_UDC) || udc_disabled)) return -ENODEV; /* prevent usb-core allocating DMA pages */ @@ -137,12 +143,14 @@ int isp1760_register(struct resource *mem, int irq, unsigned long irqflags, isp1760_init_core(isp); - ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, - irqflags | IRQF_SHARED, dev); - if (ret < 0) - return ret; + if (IS_ENABLED(CONFIG_USB_ISP1760_HCD) && !usb_disabled()) { + ret = isp1760_hcd_register(&isp->hcd, isp->regs, mem, irq, + irqflags | IRQF_SHARED, dev); + if (ret < 0) + return ret; + } - if (devflags & ISP1760_FLAG_ISP1761) { + if (IS_ENABLED(CONFIG_USB_ISP1761_UDC) && !udc_disabled) { ret = isp1760_udc_register(isp, irq, irqflags | IRQF_SHARED | IRQF_DISABLED); if (ret < 0) { @@ -160,9 +168,7 @@ void isp1760_unregister(struct device *dev) { struct isp1760_device *isp = dev_get_drvdata(dev); - if (isp->devflags & ISP1760_FLAG_ISP1761) - isp1760_udc_unregister(isp); - + isp1760_udc_unregister(isp); isp1760_hcd_unregister(&isp->hcd); } diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 568446c..996b2c1 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -2226,6 +2226,9 @@ error: void isp1760_hcd_unregister(struct isp1760_hcd *priv) { + if (!priv->hcd) + return; + usb_remove_hcd(priv->hcd); usb_put_hcd(priv->hcd); } diff --git a/drivers/usb/isp1760/isp1760-udc.c b/drivers/usb/isp1760/isp1760-udc.c index 6bfda30..9612d79 100644 --- a/drivers/usb/isp1760/isp1760-udc.c +++ b/drivers/usb/isp1760/isp1760-udc.c @@ -1488,6 +1488,9 @@ void isp1760_udc_unregister(struct isp1760_device *isp) { struct isp1760_udc *udc = &isp->udc; + if (!udc->isp) + return; + usb_del_gadget_udc(&udc->gadget); free_irq(udc->irq, udc); -- cgit v0.10.2 From dd811ba7427f7e6b7e521f3738a5313d00319675 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 16 Jan 2015 18:28:58 +0800 Subject: usb: phy: mxs: don't need IP fix for imx6sx The RLT code has already done it, so no software operation is needed. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 58cae78..fcadbd2 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -131,8 +131,7 @@ static const struct mxs_phy_data vf610_phy_data = { }; static const struct mxs_phy_data imx6sx_phy_data = { - .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | - MXS_PHY_NEED_IP_FIX, + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, }; static const struct of_device_id mxs_phy_dt_ids[] = { -- cgit v0.10.2 From 7b09e67639d6857439bae0d6276d443a7c2c9c40 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Fri, 16 Jan 2015 18:28:59 +0800 Subject: usb: phy: mxs: refine mxs_phy_disconnect_line For non-otg mode, we keep the usage of disconnect line between phy analog and digital unchanging; for otg mode, at peripheral role, we keep the usage unchanging too, at host role, the digital part needs to know dp/dm change to respond device's data pulse when it is at low power mode. Signed-off-by: Peter Chen Signed-off-by: Li Jun Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index fcadbd2..eb74605 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -40,6 +40,7 @@ #define BM_USBPHY_CTRL_SFTRST BIT(31) #define BM_USBPHY_CTRL_CLKGATE BIT(30) +#define BM_USBPHY_CTRL_OTG_ID_VALUE BIT(27) #define BM_USBPHY_CTRL_ENAUTOSET_USBCLKS BIT(26) #define BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE BIT(25) #define BM_USBPHY_CTRL_ENVBUSCHG_WKUP BIT(23) @@ -255,6 +256,18 @@ static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect) usleep_range(500, 1000); } +static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy) +{ + void __iomem *base = mxs_phy->phy.io_priv; + u32 phyctrl = readl(base + HW_USBPHY_CTRL); + + if (IS_ENABLED(CONFIG_USB_OTG) && + !(phyctrl & BM_USBPHY_CTRL_OTG_ID_VALUE)) + return true; + + return false; +} + static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) { bool vbus_is_on = false; @@ -269,7 +282,7 @@ static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); - if (on && !vbus_is_on) + if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy)) __mxs_phy_disconnect_line(mxs_phy, true); else __mxs_phy_disconnect_line(mxs_phy, false); -- cgit v0.10.2 From efdbd3a5d6e6108f1565ab4dc4c53e77aba6fe0a Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 16 Jan 2015 18:29:00 +0800 Subject: usb: phy: mxs: do not set PWD.RXPWD1PT1 for low speed connection At very rare cases, the SoF will not send out after resume with low speed connection. The workaround is do not power down PWD.RXPWD1PT1 bit during the suspend. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index eb74605..c3177a1 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -70,6 +70,9 @@ #define ANADIG_USB2_LOOPBACK_SET 0x244 #define ANADIG_USB2_LOOPBACK_CLR 0x248 +#define ANADIG_USB1_MISC 0x1f0 +#define ANADIG_USB2_MISC 0x250 + #define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG BIT(12) #define BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL BIT(11) @@ -81,6 +84,11 @@ #define BM_ANADIG_USB2_LOOPBACK_UTMI_DIG_TST1 BIT(2) #define BM_ANADIG_USB2_LOOPBACK_TSTI_TX_EN BIT(5) +#define BM_ANADIG_USB1_MISC_RX_VPIN_FS BIT(29) +#define BM_ANADIG_USB1_MISC_RX_VMIN_FS BIT(28) +#define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29) +#define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -323,13 +331,50 @@ static void mxs_phy_shutdown(struct usb_phy *phy) clk_disable_unprepare(mxs_phy->clk); } +static bool mxs_phy_is_low_speed_connection(struct mxs_phy *mxs_phy) +{ + unsigned int line_state; + /* bit definition is the same for all controllers */ + unsigned int dp_bit = BM_ANADIG_USB1_MISC_RX_VPIN_FS, + dm_bit = BM_ANADIG_USB1_MISC_RX_VMIN_FS; + unsigned int reg = ANADIG_USB1_MISC; + + /* If the SoCs don't have anatop, quit */ + if (!mxs_phy->regmap_anatop) + return false; + + if (mxs_phy->port_id == 0) + reg = ANADIG_USB1_MISC; + else if (mxs_phy->port_id == 1) + reg = ANADIG_USB2_MISC; + + regmap_read(mxs_phy->regmap_anatop, reg, &line_state); + + if ((line_state & (dp_bit | dm_bit)) == dm_bit) + return true; + else + return false; +} + static int mxs_phy_suspend(struct usb_phy *x, int suspend) { int ret; struct mxs_phy *mxs_phy = to_mxs_phy(x); + bool low_speed_connection, vbus_is_on; + + low_speed_connection = mxs_phy_is_low_speed_connection(mxs_phy); + vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); if (suspend) { - writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); + /* + * FIXME: Do not power down RXPWD1PT1 bit for low speed + * connect. The low speed connection will have problem at + * very rare cases during usb suspend and resume process. + */ + if (low_speed_connection & vbus_is_on) + writel(0xfffbffff, x->io_priv + HW_USBPHY_PWD); + else + writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_SET); clk_disable_unprepare(mxs_phy->clk); -- cgit v0.10.2 From e235f7b86f33beea7e096b46db1802dbf5d7d22e Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 16 Jan 2015 18:29:01 +0800 Subject: usb: phy: mxs: add delay before set phyctrl.clkgate There is a request from IC engineer that if we doesn't set phypwd as 0xffffffff, we need to delay about five 32Khz cycles before set phy's pwd register, otherwise, the wakeup signal may can't wake up controller. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index c3177a1..8f7cb06 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -371,10 +371,16 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) * connect. The low speed connection will have problem at * very rare cases during usb suspend and resume process. */ - if (low_speed_connection & vbus_is_on) - writel(0xfffbffff, x->io_priv + HW_USBPHY_PWD); - else + if (low_speed_connection & vbus_is_on) { + /* + * If value to be set as pwd value is not 0xffffffff, + * several 32Khz cycles are needed. + */ + mxs_phy_clock_switch_delay(); + writel(0xffbfffff, x->io_priv + HW_USBPHY_PWD); + } else { writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); + } writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_SET); clk_disable_unprepare(mxs_phy->clk); -- cgit v0.10.2 From 727968357eb84497ac8e2514cf06be3d8779d24f Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 19 Jan 2015 13:52:57 +0100 Subject: usb: gadget: uvc: use explicit type instead of void * The first parameter of __uvcg_iter_strm_cls() is always used in the context of struct uvcg_streaming_header, so change the function prototype accordingly. Acked-by: Laurent Pinchart Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 49f25e8..51d8e9e 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1918,10 +1918,10 @@ enum uvcg_strm_type { UVCG_FRAME }; -static int __uvcg_iter_strm_cls(void *priv1, void *priv2, void *priv3, +static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, + void *priv2, void *priv3, int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type)) { - struct uvcg_streaming_header *h = priv1; struct uvcg_format_ptr *f; struct config_group *grp; struct config_item *item; -- cgit v0.10.2 From 578d0b6b6171e7a7afa49e74f87c683297e9cae9 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Mon, 19 Jan 2015 13:52:58 +0100 Subject: usb: gadget: uvc: comments for iterating over streaming hierarchy The purpose of the functions and their parametrs might not be obvious to the reader, so explain it. Signed-off-by: Andrzej Pietrasiewicz Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 51d8e9e..3c0467b 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1918,6 +1918,25 @@ enum uvcg_strm_type { UVCG_FRAME }; +/* + * Iterate over a hierarchy of streaming descriptors' config items. + * The items are created by the user with configfs. + * + * It "processes" the header pointed to by @priv1, then for each format + * that follows the header "processes" the format itself and then for + * each frame inside a format "processes" the frame. + * + * As a "processing" function the @fun is used. + * + * __uvcg_iter_strm_cls() is used in two context: first, to calculate + * the amount of memory needed for an array of streaming descriptors + * and second, to actually fill the array. + * + * @h: streaming header pointer + * @priv2: an "inout" parameter (the caller might want to see the changes to it) + * @priv3: an "inout" parameter (the caller might want to see the changes to it) + * @fun: callback function for processing each level of the hierarchy + */ static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, void *priv2, void *priv3, int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type)) @@ -1951,6 +1970,14 @@ static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h, return ret; } +/* + * Count how many bytes are needed for an array of streaming descriptors. + * + * @priv1: pointer to a header, format or frame + * @priv2: inout parameter, accumulated size of the array + * @priv3: inout parameter, accumulated number of the array elements + * @n: unused, this function's prototype must match @fun in __uvcg_iter_strm_cls + */ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, enum uvcg_strm_type type) { @@ -2000,6 +2027,13 @@ static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n, return 0; } +/* + * Fill an array of streaming descriptors. + * + * @priv1: pointer to a header, format or frame + * @priv2: inout parameter, pointer into a block of memory + * @priv3: inout parameter, pointer to a 2-dimensional array + */ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, enum uvcg_strm_type type) { -- cgit v0.10.2 From 4cd8f6d05599be8bd2052df28c805f7c2f8ff479 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 27 Jan 2015 13:24:26 -0600 Subject: usb: dwc3: gadget: avoid variable shadowing We already have both ret and dwc defined in this same function. Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 6c5e344..944036d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1110,15 +1110,10 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * handled. */ if (dep->stream_capable) { - int ret; - ret = __dwc3_gadget_kick_transfer(dep, 0, true); - if (ret && ret != -EBUSY) { - struct dwc3 *dwc = dep->dwc; - + if (ret && ret != -EBUSY) dev_dbg(dwc->dev, "%s: failed to kick transfers\n", dep->name); - } } return 0; -- cgit v0.10.2 From e9f2aa871c732acfe29c3ba5e6b541b2aa786778 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 27 Jan 2015 13:49:28 -0600 Subject: usb: dwc3: gadget: WARN() in case of unknown IRQ if an unknown IRQ event is triggered, that means the HW is really misbehaving. Instead of printing a debug message, let's WARN() so users report when that happens. Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 944036d..c6eec26 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2518,7 +2518,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, dev_vdbg(dwc->dev, "Overflow\n"); break; default: - dev_dbg(dwc->dev, "UNKNOWN IRQ %d\n", event->type); + dev_WARN(dwc->dev, "UNKNOWN IRQ %d\n", event->type); } } -- cgit v0.10.2 From 6bac4ff0a52262faea4649dc8142962767f88b82 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 27 Jan 2015 13:47:02 -0600 Subject: usb: dwc3: trace: add trace logs for core and gadget Sometimes we want to just print a formatted string without passing any extra data. The following will be used for removing reliance on dev_vdbg() from dwc3. Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index 9fc20b3..9c10669 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -47,6 +47,16 @@ DEFINE_EVENT(dwc3_log_msg, dwc3_writel, TP_ARGS(vaf) ); +DEFINE_EVENT(dwc3_log_msg, dwc3_gadget, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(dwc3_log_msg, dwc3_core, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + DEFINE_EVENT(dwc3_log_msg, dwc3_ep0, TP_PROTO(struct va_format *vaf), TP_ARGS(vaf) -- cgit v0.10.2 From 73815280a5af8d95e818eb25f4091e86497dfa4b Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 27 Jan 2015 13:48:14 -0600 Subject: usb: dwc3: remove reliance on dev_vdbg() By moving all dev_vdbg() to tracepoints, we can finally get rid of dev_vdbg() usage from dwc3. Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 58b5b2c..edbf9c8 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -104,12 +104,6 @@ config USB_DWC3_DEBUG help Say Y here to enable debugging messages on DWC3 Driver. -config USB_DWC3_VERBOSE - bool "Enable Verbose Debugging Messages" - depends on USB_DWC3_DEBUG - help - Say Y here to enable verbose debugging messages on DWC3 Driver. - config DWC3_HOST_USB3_LPM_ENABLE bool "Enable USB3 LPM Capability" depends on USB_DWC3_HOST=y || USB_DWC3_DUAL_ROLE=y diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index bb34fbc..46172f4 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -2,7 +2,6 @@ CFLAGS_trace.o := -I$(src) ccflags-$(CONFIG_USB_DWC3_DEBUG) := -DDEBUG -ccflags-$(CONFIG_USB_DWC3_VERBOSE) += -DVERBOSE_DEBUG obj-$(CONFIG_USB_DWC3) += dwc3.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 25ddc39..9f0e209 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -345,7 +345,7 @@ static void dwc3_core_num_eps(struct dwc3 *dwc) dwc->num_in_eps = DWC3_NUM_IN_EPS(parms); dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps; - dev_vdbg(dwc->dev, "found %d IN and %d OUT endpoints\n", + dwc3_trace(trace_dwc3_core, "found %d IN and %d OUT endpoints", dwc->num_in_eps, dwc->num_out_eps); } diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index c6eec26..7cce00e 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -139,7 +139,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state) udelay(5); } - dev_vdbg(dwc->dev, "link state change request timed out\n"); + dwc3_trace(trace_dwc3_gadget, + "link state change request timed out"); return -ETIMEDOUT; } @@ -219,7 +220,7 @@ int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc) fifo_size |= (last_fifo_depth << 16); - dev_vdbg(dwc->dev, "%s: Fifo Addr %04x Size %d\n", + dwc3_trace(trace_dwc3_gadget, "%s: Fifo Addr %04x Size %d", dep->name, last_fifo_depth, fifo_size & 0xffff); dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num), fifo_size); @@ -287,7 +288,8 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) do { reg = dwc3_readl(dwc->regs, DWC3_DGCMD); if (!(reg & DWC3_DGCMD_CMDACT)) { - dev_vdbg(dwc->dev, "Command Complete --> %d\n", + dwc3_trace(trace_dwc3_gadget, + "Command Complete --> %d", DWC3_DGCMD_STATUS(reg)); return 0; } @@ -297,8 +299,11 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) * interrupt context. */ timeout--; - if (!timeout) + if (!timeout) { + dwc3_trace(trace_dwc3_gadget, + "Command Timed Out"); return -ETIMEDOUT; + } udelay(1); } while (1); } @@ -320,7 +325,8 @@ int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, do { reg = dwc3_readl(dwc->regs, DWC3_DEPCMD(ep)); if (!(reg & DWC3_DEPCMD_CMDACT)) { - dev_vdbg(dwc->dev, "Command Complete --> %d\n", + dwc3_trace(trace_dwc3_gadget, + "Command Complete --> %d", DWC3_DEPCMD_STATUS(reg)); return 0; } @@ -330,8 +336,11 @@ int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, * interrupt context. */ timeout--; - if (!timeout) + if (!timeout) { + dwc3_trace(trace_dwc3_gadget, + "Command Timed Out"); return -ETIMEDOUT; + } udelay(1); } while (1); @@ -489,7 +498,7 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, u32 reg; int ret; - dev_vdbg(dwc->dev, "Enabling %s\n", dep->name); + dwc3_trace(trace_dwc3_gadget, "Enabling %s", dep->name); if (!(dep->flags & DWC3_EP_ENABLED)) { ret = dwc3_gadget_start_config(dwc, dep); @@ -726,10 +735,9 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_request *req, dma_addr_t dma, unsigned length, unsigned last, unsigned chain, unsigned node) { - struct dwc3 *dwc = dep->dwc; struct dwc3_trb *trb; - dev_vdbg(dwc->dev, "%s: req %p dma %08llx length %d%s%s\n", + dwc3_trace(trace_dwc3_gadget, "%s: req %p dma %08llx length %d%s%s", dep->name, req, (unsigned long long) dma, length, last ? " last" : "", chain ? " chain" : ""); @@ -931,7 +939,7 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, u32 cmd; if (start_new && (dep->flags & DWC3_EP_BUSY)) { - dev_vdbg(dwc->dev, "%s: endpoint busy\n", dep->name); + dwc3_trace(trace_dwc3_gadget, "%s: endpoint busy", dep->name); return -EBUSY; } dep->flags &= ~DWC3_EP_PENDING_REQUEST; @@ -1002,8 +1010,9 @@ static void __dwc3_gadget_start_isoc(struct dwc3 *dwc, u32 uf; if (list_empty(&dep->request_list)) { - dev_vdbg(dwc->dev, "ISOC ep %s run out for requests.\n", - dep->name); + dwc3_trace(trace_dwc3_gadget, + "ISOC ep %s run out for requests", + dep->name); dep->flags |= DWC3_EP_PENDING_REQUEST; return; } @@ -1144,8 +1153,6 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, goto out; } - dev_vdbg(dwc->dev, "queing request %p to %s length %d\n", - request, ep->name, request->length); trace_dwc3_ep_queue(req); ret = __dwc3_gadget_ep_queue(dep, req); @@ -1460,7 +1467,7 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) udelay(1); } while (1); - dev_vdbg(dwc->dev, "gadget %s data soft-%s\n", + dwc3_trace(trace_dwc3_gadget, "gadget %s data soft-%s", dwc->gadget_driver ? dwc->gadget_driver->function : "no-function", is_on ? "connect" : "disconnect"); @@ -1680,7 +1687,7 @@ static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, dep->endpoint.name = dep->name; - dev_vdbg(dwc->dev, "initializing %s\n", dep->name); + dwc3_trace(trace_dwc3_gadget, "initializing %s", dep->name); if (epnum == 0 || epnum == 1) { usb_ep_set_maxpacket_limit(&dep->endpoint, 512); @@ -1717,13 +1724,15 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_out_eps, 0); if (ret < 0) { - dev_vdbg(dwc->dev, "failed to allocate OUT endpoints\n"); + dwc3_trace(trace_dwc3_gadget, + "failed to allocate OUT endpoints"); return ret; } ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_in_eps, 1); if (ret < 0) { - dev_vdbg(dwc->dev, "failed to allocate IN endpoints\n"); + dwc3_trace(trace_dwc3_gadget, + "failed to allocate IN endpoints"); return ret; } @@ -1969,7 +1978,7 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, } else { int ret; - dev_vdbg(dwc->dev, "%s: reason %s\n", + dwc3_trace(trace_dwc3_gadget, "%s: reason %s", dep->name, event->status & DEPEVT_STATUS_TRANSFER_ACTIVE ? "Transfer Active" @@ -1993,7 +2002,8 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, switch (event->status) { case DEPEVT_STREAMEVT_FOUND: - dev_vdbg(dwc->dev, "Stream %d found and started\n", + dwc3_trace(trace_dwc3_gadget, + "Stream %d found and started", event->parameters); break; @@ -2007,7 +2017,7 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, dev_dbg(dwc->dev, "%s FIFO Overrun\n", dep->name); break; case DWC3_DEPEVT_EPCMDCMPLT: - dev_vdbg(dwc->dev, "Endpoint Command Complete\n"); + dwc3_trace(trace_dwc3_gadget, "Endpoint Command Complete"); break; } } @@ -2381,7 +2391,8 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, (pwropt != DWC3_GHWPARAMS1_EN_PWROPT_HIB)) { if ((dwc->link_state == DWC3_LINK_STATE_U3) && (next == DWC3_LINK_STATE_RESUME)) { - dev_vdbg(dwc->dev, "ignoring transition U3 -> Resume\n"); + dwc3_trace(trace_dwc3_gadget, + "ignoring transition U3 -> Resume"); return; } } @@ -2503,19 +2514,19 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, dwc3_gadget_linksts_change_interrupt(dwc, event->event_info); break; case DWC3_DEVICE_EVENT_EOPF: - dev_vdbg(dwc->dev, "End of Periodic Frame\n"); + dwc3_trace(trace_dwc3_gadget, "End of Periodic Frame"); break; case DWC3_DEVICE_EVENT_SOF: - dev_vdbg(dwc->dev, "Start of Periodic Frame\n"); + dwc3_trace(trace_dwc3_gadget, "Start of Periodic Frame"); break; case DWC3_DEVICE_EVENT_ERRATIC_ERROR: - dev_vdbg(dwc->dev, "Erratic Error\n"); + dwc3_trace(trace_dwc3_gadget, "Erratic Error"); break; case DWC3_DEVICE_EVENT_CMD_CMPL: - dev_vdbg(dwc->dev, "Command Complete\n"); + dwc3_trace(trace_dwc3_gadget, "Command Complete"); break; case DWC3_DEVICE_EVENT_OVERFLOW: - dev_vdbg(dwc->dev, "Overflow\n"); + dwc3_trace(trace_dwc3_gadget, "Overflow"); break; default: dev_WARN(dwc->dev, "UNKNOWN IRQ %d\n", event->type); -- cgit v0.10.2 From 5c7b3b02de766a8634708953e805399e3544a290 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 29 Jan 2015 10:29:18 -0600 Subject: usb: dwc3: gadget: add missing spin_lock() commit 8e74475b0e0a (usb: dwc3: gadget: use udc-core's reset notifier) added support for the new UDC core's reset notifier to dwc3 but while at it, it removed a spin_lock() from dwc3_reset_gadget() which might cause an unbalanced spin_unlock() further down the line Fixes: 8e74475b0e0a (usb: dwc3: gadget: use udc-core's reset notifier) Cc: # v3.19 Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 7cce00e..eccd29b 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2045,6 +2045,7 @@ static void dwc3_resume_gadget(struct dwc3 *dwc) if (dwc->gadget_driver && dwc->gadget_driver->resume) { spin_unlock(&dwc->lock); dwc->gadget_driver->resume(&dwc->gadget); + spin_lock(&dwc->lock); } } -- cgit v0.10.2 From 80b2502cea34a965a6b3390691854e753945ca5f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:24 +0800 Subject: usb: gadget: introduce is_selfpowered for usb_gadget Whether the gadget is selfpowerwed or not can be determined by composite core, so we can use a common entry to indicate if the self-powered is supported by gadget, and the related private variable at individual udc driver can be deleted. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 70ddb39..e2f00fd 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -523,6 +523,7 @@ struct usb_gadget_ops { * enabled HNP support. * @quirk_ep_out_aligned_size: epout requires buffer size to be aligned to * MaxPacketSize. + * @is_selfpowered: if the gadget is self-powered. * * Gadgets have a mostly-portable "gadget driver" implementing device * functions, handling all usb configurations and interfaces. Gadget @@ -563,6 +564,7 @@ struct usb_gadget { unsigned a_hnp_support:1; unsigned a_alt_hnp_support:1; unsigned quirk_ep_out_aligned_size:1; + unsigned is_selfpowered:1; }; #define work_to_gadget(w) (container_of((w), struct usb_gadget, work)) -- cgit v0.10.2 From 1009f9a36d30f87676ede4c69f97a4eb45001f15 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:25 +0800 Subject: usb: chipidea: udc: add set_selfpowered gaget ops The gadget power property will be used at get_status request. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 4fe18ce..ff45104 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -819,8 +819,8 @@ __acquires(hwep->lock) } if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - /* Assume that device is bus powered for now. */ - *(u16 *)req->buf = ci->remote_wakeup << 1; + *(u16 *)req->buf = (ci->remote_wakeup << 1) | + ci->gadget.is_selfpowered; } else if ((setup->bRequestType & USB_RECIP_MASK) \ == USB_RECIP_ENDPOINT) { dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? @@ -1520,6 +1520,19 @@ static int ci_udc_vbus_draw(struct usb_gadget *_gadget, unsigned ma) return -ENOTSUPP; } +static int ci_udc_selfpowered(struct usb_gadget *_gadget, int is_on) +{ + struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); + struct ci_hw_ep *hwep = ci->ep0in; + unsigned long flags; + + spin_lock_irqsave(hwep->lock, flags); + _gadget->is_selfpowered = (is_on != 0); + spin_unlock_irqrestore(hwep->lock, flags); + + return 0; +} + /* Change Data+ pullup status * this func is used by usb_gadget_connect/disconnet */ @@ -1549,6 +1562,7 @@ static int ci_udc_stop(struct usb_gadget *gadget); static const struct usb_gadget_ops usb_gadget_ops = { .vbus_session = ci_udc_vbus_session, .wakeup = ci_udc_wakeup, + .set_selfpowered = ci_udc_selfpowered, .pullup = ci_udc_pullup, .vbus_draw = ci_udc_vbus_draw, .udc_start = ci_udc_start, -- cgit v0.10.2 From 7301971f50d08250ecddc1a2298dde5466d6e315 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:26 +0800 Subject: usb: gadget: at91_udc: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/at91_udc.c b/drivers/usb/gadget/udc/at91_udc.c index c862656..c0ec5f7 100644 --- a/drivers/usb/gadget/udc/at91_udc.c +++ b/drivers/usb/gadget/udc/at91_udc.c @@ -176,7 +176,7 @@ static int proc_udc_show(struct seq_file *s, void *unused) udc->enabled ? (udc->vbus ? "active" : "enabled") : "disabled", - udc->selfpowered ? "self" : "VBUS", + udc->gadget.is_selfpowered ? "self" : "VBUS", udc->suspended ? ", suspended" : "", udc->driver ? udc->driver->driver.name : "(none)"); @@ -1000,7 +1000,7 @@ static int at91_set_selfpowered(struct usb_gadget *gadget, int is_on) unsigned long flags; spin_lock_irqsave(&udc->lock, flags); - udc->selfpowered = (is_on != 0); + gadget->is_selfpowered = (is_on != 0); spin_unlock_irqrestore(&udc->lock, flags); return 0; } @@ -1149,7 +1149,7 @@ static void handle_setup(struct at91_udc *udc, struct at91_ep *ep, u32 csr) */ case ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE) << 8) | USB_REQ_GET_STATUS: - tmp = (udc->selfpowered << USB_DEVICE_SELF_POWERED); + tmp = (udc->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED); if (at91_udp_read(udc, AT91_UDP_GLB_STAT) & AT91_UDP_ESR) tmp |= (1 << USB_DEVICE_REMOTE_WAKEUP); PACKET("get device status\n"); @@ -1653,7 +1653,7 @@ static int at91_start(struct usb_gadget *gadget, udc->driver = driver; udc->gadget.dev.of_node = udc->pdev->dev.of_node; udc->enabled = 1; - udc->selfpowered = 1; + udc->gadget.is_selfpowered = 1; return 0; } diff --git a/drivers/usb/gadget/udc/at91_udc.h b/drivers/usb/gadget/udc/at91_udc.h index 0175246..467dcfb 100644 --- a/drivers/usb/gadget/udc/at91_udc.h +++ b/drivers/usb/gadget/udc/at91_udc.h @@ -122,7 +122,6 @@ struct at91_udc { unsigned req_pending:1; unsigned wait_for_addr_ack:1; unsigned wait_for_config_ack:1; - unsigned selfpowered:1; unsigned active_suspend:1; u8 addr; struct at91_udc_data board; -- cgit v0.10.2 From 9239d88fc5e58a2a72bc949362f999aac9bffb29 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:27 +0800 Subject: staging: emxx_udc: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/staging/emxx_udc/emxx_udc.c b/drivers/staging/emxx_udc/emxx_udc.c index eb178fc..1d3135a 100644 --- a/drivers/staging/emxx_udc/emxx_udc.c +++ b/drivers/staging/emxx_udc/emxx_udc.c @@ -1608,7 +1608,7 @@ static int std_req_get_status(struct nbu2ss_udc *udc) switch (recipient) { case USB_RECIP_DEVICE: if (udc->ctrl.wIndex == 0x0000) { - if (udc->self_powered) + if (udc->gadget.is_selfpowered) status_data |= (1 << USB_DEVICE_SELF_POWERED); if (udc->remote_wakeup) @@ -3117,7 +3117,6 @@ static int nbu2ss_gad_wakeup(struct usb_gadget *pgadget) static int nbu2ss_gad_set_selfpowered(struct usb_gadget *pgadget, int is_selfpowered) { - struct nbu2ss_udc *udc; unsigned long flags; /* INFO("=== %s()\n", __func__); */ @@ -3127,10 +3126,8 @@ static int nbu2ss_gad_set_selfpowered(struct usb_gadget *pgadget, return -EINVAL; } - udc = container_of(pgadget, struct nbu2ss_udc, gadget); - spin_lock_irqsave(&udc->lock, flags); - udc->self_powered = (is_selfpowered != 0); + pgadget->is_selfpowered = (is_selfpowered != 0); spin_unlock_irqrestore(&udc->lock, flags); return 0; @@ -3308,7 +3305,7 @@ static int __init nbu2ss_drv_contest_init( spin_lock_init(&udc->lock); udc->dev = &pdev->dev; - udc->self_powered = 1; + udc->gadget.is_selfpowered = 1; udc->devstate = USB_STATE_NOTATTACHED; udc->pdev = pdev; udc->mA = 0; diff --git a/drivers/staging/emxx_udc/emxx_udc.h b/drivers/staging/emxx_udc/emxx_udc.h index ee1b80d..202e2dc 100644 --- a/drivers/staging/emxx_udc/emxx_udc.h +++ b/drivers/staging/emxx_udc/emxx_udc.h @@ -624,7 +624,6 @@ struct nbu2ss_udc { unsigned linux_suspended:1; unsigned linux_resume:1; unsigned usb_suspended:1; - unsigned self_powered:1; unsigned remote_wakeup:1; unsigned udc_enabled:1; -- cgit v0.10.2 From a17fd41206a6d3efc565d8319405692325874884 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:28 +0800 Subject: usb: renesas_usbhs: gadget: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c index 8697e6e..e0384af 100644 --- a/drivers/usb/renesas_usbhs/mod_gadget.c +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -926,6 +926,8 @@ static int usbhsg_set_selfpowered(struct usb_gadget *gadget, int is_self) else usbhsg_status_clr(gpriv, USBHSG_STATUS_SELF_POWERED); + gadget->is_selfpowered = (is_self != 0); + return 0; } -- cgit v0.10.2 From 4605437e335bf1cf3b30c1261a6a146dbd830188 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:29 +0800 Subject: usb: gadget: bdc_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/bdc/bdc_udc.c b/drivers/usb/gadget/udc/bdc/bdc_udc.c index 3700ce7..7f77db5 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_udc.c +++ b/drivers/usb/gadget/udc/bdc/bdc_udc.c @@ -454,6 +454,7 @@ static int bdc_udc_set_selfpowered(struct usb_gadget *gadget, unsigned long flags; dev_dbg(bdc->dev, "%s()\n", __func__); + gadget->is_selfpowered = (is_self != 0); spin_lock_irqsave(&bdc->lock, flags); if (!is_self) bdc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; -- cgit v0.10.2 From 5d9cb6afc496b20b2d2ef71e4dd3adf50841f8c8 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:30 +0800 Subject: usb: gadget: dummy_hcd: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index 270c1ec..8dda484 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -802,6 +802,7 @@ static int dummy_set_selfpowered(struct usb_gadget *_gadget, int value) { struct dummy *dum; + _gadget->is_selfpowered = (value != 0); dum = gadget_to_dummy_hcd(_gadget)->dum; if (value) dum->devstatus |= (1 << USB_DEVICE_SELF_POWERED); -- cgit v0.10.2 From d60d939221dc16dfc0589f4fbb730aab1aa4daeb Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:31 +0800 Subject: usb: gadget: lpc32xx_udc: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/lpc32xx_udc.c b/drivers/usb/gadget/udc/lpc32xx_udc.c index 34d9b7b..27fd413 100644 --- a/drivers/usb/gadget/udc/lpc32xx_udc.c +++ b/drivers/usb/gadget/udc/lpc32xx_udc.c @@ -191,7 +191,6 @@ struct lpc32xx_udc { bool enabled; bool clocked; bool suspended; - bool selfpowered; int ep0state; atomic_t enabled_ep_cnt; wait_queue_head_t ep_disable_wait_queue; @@ -547,7 +546,7 @@ static int proc_udc_show(struct seq_file *s, void *unused) udc->vbus ? "present" : "off", udc->enabled ? (udc->vbus ? "active" : "enabled") : "disabled", - udc->selfpowered ? "self" : "VBUS", + udc->gadget.is_selfpowered ? "self" : "VBUS", udc->suspended ? ", suspended" : "", udc->driver ? udc->driver->driver.name : "(none)"); @@ -2212,7 +2211,7 @@ static int udc_get_status(struct lpc32xx_udc *udc, u16 reqtype, u16 wIndex) break; /* Not supported */ case USB_RECIP_DEVICE: - ep0buff = (udc->selfpowered << USB_DEVICE_SELF_POWERED); + ep0buff = udc->gadget.is_selfpowered; if (udc->dev_status & (1 << USB_DEVICE_REMOTE_WAKEUP)) ep0buff |= (1 << USB_DEVICE_REMOTE_WAKEUP); break; @@ -2498,10 +2497,7 @@ static int lpc32xx_wakeup(struct usb_gadget *gadget) static int lpc32xx_set_selfpowered(struct usb_gadget *gadget, int is_on) { - struct lpc32xx_udc *udc = to_udc(gadget); - - /* Always self-powered */ - udc->selfpowered = (is_on != 0); + gadget->is_selfpowered = (is_on != 0); return 0; } @@ -2946,7 +2942,7 @@ static int lpc32xx_start(struct usb_gadget *gadget, udc->driver = driver; udc->gadget.dev.of_node = udc->dev->of_node; udc->enabled = 1; - udc->selfpowered = 1; + udc->gadget.is_selfpowered = 1; udc->vbus = 0; /* Force VBUS process once to check for cable insertion */ -- cgit v0.10.2 From 4651fcf3294eaa018c63dfdae0b1fb65e432f727 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:32 +0800 Subject: usb: gadget: fsl_udc_core: set value for common is_selfpowered fsl udc core assumes itself always self powered, so set is_selfpowered is 1. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index c3830ad..55fcb93 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -1337,7 +1337,7 @@ static void ch9getstatus(struct fsl_udc *udc, u8 request_type, u16 value, if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) { /* Get device status */ - tmp = 1 << USB_DEVICE_SELF_POWERED; + tmp = udc->gadget.is_selfpowered; tmp |= udc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP; } else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) { /* Get interface status */ @@ -1948,6 +1948,7 @@ static int fsl_udc_start(struct usb_gadget *g, /* hook up the driver */ udc_controller->driver = driver; spin_unlock_irqrestore(&udc_controller->lock, flags); + g->is_selfpowered = 1; if (!IS_ERR_OR_NULL(udc_controller->transceiver)) { /* Suspend the controller until OTG enable it */ -- cgit v0.10.2 From 58ae8e0b1455ab2adc7c6733ad2dde9e0317cc36 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:33 +0800 Subject: usb: gadget: omap_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/omap_udc.c b/drivers/usb/gadget/udc/omap_udc.c index 288c087..e2fcdb8 100644 --- a/drivers/usb/gadget/udc/omap_udc.c +++ b/drivers/usb/gadget/udc/omap_udc.c @@ -1171,6 +1171,7 @@ omap_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) unsigned long flags; u16 syscon1; + gadget->is_selfpowered = (is_selfpowered != 0); udc = container_of(gadget, struct omap_udc, gadget); spin_lock_irqsave(&udc->lock, flags); syscon1 = omap_readw(UDC_SYSCON1); -- cgit v0.10.2 From 0bcff9eaa83228f36b8ae42cedc588e9337b7e71 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:34 +0800 Subject: usb: gadget: r8a66597-udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/r8a66597-udc.c b/drivers/usb/gadget/udc/r8a66597-udc.c index 06870da..2495fe9 100644 --- a/drivers/usb/gadget/udc/r8a66597-udc.c +++ b/drivers/usb/gadget/udc/r8a66597-udc.c @@ -1803,6 +1803,7 @@ static int r8a66597_set_selfpowered(struct usb_gadget *gadget, int is_self) { struct r8a66597 *r8a66597 = gadget_to_r8a66597(gadget); + gadget->is_selfpowered = (is_self != 0); if (is_self) r8a66597->device_status |= 1 << USB_DEVICE_SELF_POWERED; else -- cgit v0.10.2 From c8678d9f288e7df66f50ab7be1eff1beb6bc7d70 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:35 +0800 Subject: usb: gadget: net2280: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 6411ed8..d2c0bf6 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1365,10 +1365,10 @@ static int net2280_set_selfpowered(struct usb_gadget *_gadget, int value) tmp = readl(&dev->usb->usbctl); if (value) { tmp |= BIT(SELF_POWERED_STATUS); - dev->selfpowered = 1; + _gadget->is_selfpowered = 1; } else { tmp &= ~BIT(SELF_POWERED_STATUS); - dev->selfpowered = 0; + _gadget->is_selfpowered = 0; } writel(tmp, &dev->usb->usbctl); spin_unlock_irqrestore(&dev->lock, flags); @@ -2611,7 +2611,7 @@ static void handle_stat0_irqs_superspeed(struct net2280 *dev, switch (r.bRequestType) { case (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE): status = dev->wakeup_enable ? 0x02 : 0x00; - if (dev->selfpowered) + if (dev->gadget.is_selfpowered) status |= BIT(0); status |= (dev->u1_enable << 2 | dev->u2_enable << 3 | dev->ltm_enable << 4); diff --git a/drivers/usb/gadget/udc/net2280.h b/drivers/usb/gadget/udc/net2280.h index a307dce..ac8d5a2 100644 --- a/drivers/usb/gadget/udc/net2280.h +++ b/drivers/usb/gadget/udc/net2280.h @@ -164,7 +164,6 @@ struct net2280 { u2_enable:1, ltm_enable:1, wakeup_enable:1, - selfpowered:1, addressed_state:1, bug7734_patched:1; u16 chiprev; -- cgit v0.10.2 From b3764dd1a318a227099984a4406f47f861b1d6eb Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:36 +0800 Subject: usb: gadget: s3c2410_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/s3c2410_udc.c b/drivers/usb/gadget/udc/s3c2410_udc.c index 256a67b..b808951 100644 --- a/drivers/usb/gadget/udc/s3c2410_udc.c +++ b/drivers/usb/gadget/udc/s3c2410_udc.c @@ -1434,6 +1434,7 @@ static int s3c2410_udc_set_selfpowered(struct usb_gadget *gadget, int value) dprintk(DEBUG_NORMAL, "%s()\n", __func__); + gadget->is_selfpowered = (value != 0); if (value) udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED); else -- cgit v0.10.2 From d4a1c47946a2796520a95da6bed8e5642ce9fb19 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:37 +0800 Subject: usb: gadget: net2272: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/net2272.c b/drivers/usb/gadget/udc/net2272.c index d20de1f..195baf3 100644 --- a/drivers/usb/gadget/udc/net2272.c +++ b/drivers/usb/gadget/udc/net2272.c @@ -1132,13 +1132,10 @@ net2272_wakeup(struct usb_gadget *_gadget) static int net2272_set_selfpowered(struct usb_gadget *_gadget, int value) { - struct net2272 *dev; - if (!_gadget) return -ENODEV; - dev = container_of(_gadget, struct net2272, gadget); - dev->is_selfpowered = value; + _gadget->is_selfpowered = (value != 0); return 0; } @@ -1844,7 +1841,7 @@ net2272_handle_stat0_irqs(struct net2272 *dev, u8 stat) case USB_RECIP_DEVICE: if (u.r.wLength > 2) goto do_stall; - if (dev->is_selfpowered) + if (dev->gadget.is_selfpowered) status = (1 << USB_DEVICE_SELF_POWERED); /* don't bother with a request object! */ diff --git a/drivers/usb/gadget/udc/net2272.h b/drivers/usb/gadget/udc/net2272.h index e595057..127ab03 100644 --- a/drivers/usb/gadget/udc/net2272.h +++ b/drivers/usb/gadget/udc/net2272.h @@ -458,7 +458,6 @@ struct net2272 { struct usb_gadget_driver *driver; unsigned protocol_stall:1, softconnect:1, - is_selfpowered:1, wakeup:1, dma_eot_polarity:1, dma_dack_polarity:1, -- cgit v0.10.2 From d618c368ef98631da3adf802e500d5c61103804f Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:38 +0800 Subject: usb: gadget: atmel_usba_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 4b5b7fa..c041086 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -989,6 +989,7 @@ usba_udc_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) struct usba_udc *udc = to_usba_udc(gadget); unsigned long flags; + gadget->is_selfpowered = (is_selfpowered != 0); spin_lock_irqsave(&udc->lock, flags); if (is_selfpowered) udc->devstatus |= 1 << USB_DEVICE_SELF_POWERED; -- cgit v0.10.2 From 716013b0ab2b13fe6a176d869b9fcb9bf0f8e112 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:39 +0800 Subject: usb: gadget: pch_udc: set value for common is_selfpowered Set value for common is_selfpowered. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c index 1c7379a..613547f 100644 --- a/drivers/usb/gadget/udc/pch_udc.c +++ b/drivers/usb/gadget/udc/pch_udc.c @@ -1161,6 +1161,7 @@ static int pch_udc_pcd_selfpowered(struct usb_gadget *gadget, int value) if (!gadget) return -EINVAL; + gadget->is_selfpowered = (value != 0); dev = container_of(gadget, struct pch_udc_dev, gadget); if (value) pch_udc_set_selfpowered(dev); -- cgit v0.10.2 From bcdea50312b291ee3920bad43a13e42d7102f900 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:40 +0800 Subject: usb: dwc3: gadget: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 0842aa8..d201910 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -682,7 +682,6 @@ struct dwc3_scratchpad_array { * @is_utmi_l1_suspend: the core asserts output signal * 0 - utmi_sleep_n * 1 - utmi_l1_suspend_n - * @is_selfpowered: true when we are selfpowered * @is_fpga: true when we are using the FPGA board * @needs_fifo_resize: not all users might want fifo resizing, flag it * @pullups_connected: true when Run/Stop bit is set @@ -806,7 +805,6 @@ struct dwc3 { unsigned has_hibernation:1; unsigned has_lpm_erratum:1; unsigned is_utmi_l1_suspend:1; - unsigned is_selfpowered:1; unsigned is_fpga:1; unsigned needs_fifo_resize:1; unsigned pullups_connected:1; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 1bc77a3..2ef3c8d 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -344,7 +344,7 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc, /* * LTM will be set once we know how to set this in HW. */ - usb_status |= dwc->is_selfpowered << USB_DEVICE_SELF_POWERED; + usb_status |= dwc->gadget.is_selfpowered; if (dwc->speed == DWC3_DSTS_SUPERSPEED) { reg = dwc3_readl(dwc->regs, DWC3_DCTL); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index eccd29b..a03a485 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1415,7 +1415,7 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, unsigned long flags; spin_lock_irqsave(&dwc->lock, flags); - dwc->is_selfpowered = !!is_selfpowered; + g->is_selfpowered = !!is_selfpowered; spin_unlock_irqrestore(&dwc->lock, flags); return 0; -- cgit v0.10.2 From dadac9861f466635c3ac0851f2a39be68fa10a71 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:41 +0800 Subject: usb: musb: gadget: use common is_selfpowered Delete private selfpowered variable, and use common one. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 49b04cb..b2d9040 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1612,9 +1612,7 @@ done: static int musb_gadget_set_self_powered(struct usb_gadget *gadget, int is_selfpowered) { - struct musb *musb = gadget_to_musb(gadget); - - musb->is_self_powered = !!is_selfpowered; + gadget->is_selfpowered = !!is_selfpowered; return 0; } diff --git a/drivers/usb/musb/musb_gadget_ep0.c b/drivers/usb/musb/musb_gadget_ep0.c index 2af45a0..10d30af 100644 --- a/drivers/usb/musb/musb_gadget_ep0.c +++ b/drivers/usb/musb/musb_gadget_ep0.c @@ -85,7 +85,7 @@ static int service_tx_status_request( switch (recip) { case USB_RECIP_DEVICE: - result[0] = musb->is_self_powered << USB_DEVICE_SELF_POWERED; + result[0] = musb->g.is_selfpowered << USB_DEVICE_SELF_POWERED; result[0] |= musb->may_wakeup << USB_DEVICE_REMOTE_WAKEUP; if (musb->g.is_otg) { result[0] |= musb->g.b_hnp_enable -- cgit v0.10.2 From 3f6dd4feda71760c41336963c17b594872346654 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 28 Jan 2015 16:32:42 +0800 Subject: usb: udc-core: add is_selfpowered sys entry The user can read it through sys entry. Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/udc/udc-core.c b/drivers/usb/gadget/udc/udc-core.c index e31d574..5a81cb0 100644 --- a/drivers/usb/gadget/udc/udc-core.c +++ b/drivers/usb/gadget/udc/udc-core.c @@ -564,6 +564,7 @@ static USB_UDC_ATTR(is_a_peripheral); static USB_UDC_ATTR(b_hnp_enable); static USB_UDC_ATTR(a_hnp_support); static USB_UDC_ATTR(a_alt_hnp_support); +static USB_UDC_ATTR(is_selfpowered); static struct attribute *usb_udc_attrs[] = { &dev_attr_srp.attr, @@ -577,6 +578,7 @@ static struct attribute *usb_udc_attrs[] = { &dev_attr_b_hnp_enable.attr, &dev_attr_a_hnp_support.attr, &dev_attr_a_alt_hnp_support.attr, + &dev_attr_is_selfpowered.attr, NULL, }; -- cgit v0.10.2 From a0cb12e2ed61f4c1bb586268ca43b27dd8ce959e Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 28 Jan 2015 22:50:04 +0100 Subject: usb: musb: add generic usb phy dependencies Multiple musb glue drivers depend on the generic usb phy support, but fail to list it as a dependency in Kconfig. This results in build erros like: drivers/built-in.o: In function `am35x_remove': :(.text+0xadacc): undefined reference to `usb_phy_generic_unregister' drivers/built-in.o: In function `am35x_probe': :(.text+0xae1c8): undefined reference to `usb_phy_generic_register' :(.text+0xae244): undefined reference to `usb_phy_generic_unregister' drivers/built-in.o: In function `jz4740_remove': :(.text+0xaf648): undefined reference to `usb_phy_generic_unregister' drivers/built-in.o: In function `jz4740_musb_init': :(.text+0xaf694): undefined reference to `usb_phy_generic_register' This adds the ones that are missing. Signed-off-by: Arnd Bergmann Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index b005010..26c835f 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -63,11 +63,13 @@ comment "Platform Glue Layer" config USB_MUSB_DAVINCI tristate "DaVinci" depends on ARCH_DAVINCI_DMx + depends on NOP_USB_XCEIV depends on BROKEN config USB_MUSB_DA8XX tristate "DA8xx/OMAP-L1x" depends on ARCH_DAVINCI_DA8XX + depends on NOP_USB_XCEIV depends on BROKEN config USB_MUSB_TUSB6010 @@ -83,6 +85,7 @@ config USB_MUSB_OMAP2PLUS config USB_MUSB_AM35X tristate "AM35x" depends on ARCH_OMAP + depends on NOP_USB_XCEIV config USB_MUSB_DSPS tristate "TI DSPS platforms" @@ -93,6 +96,7 @@ config USB_MUSB_DSPS config USB_MUSB_BLACKFIN tristate "Blackfin" depends on (BF54x && !BF544) || (BF52x && ! BF522 && !BF523) + depends on NOP_USB_XCEIV config USB_MUSB_UX500 tristate "Ux500 platforms" @@ -100,6 +104,7 @@ config USB_MUSB_UX500 config USB_MUSB_JZ4740 tristate "JZ4740" + depends on NOP_USB_XCEIV depends on MACH_JZ4740 || COMPILE_TEST depends on USB_MUSB_GADGET depends on USB_OTG_BLACKLIST_HUB -- cgit v0.10.2 From fbba7db3990cb707ff91cd6507d53a0a730afe97 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 28 Jan 2015 22:51:04 +0100 Subject: usb: musb: add omap-control dependency The omap musb front-end calls into the phy driver directly instead of using a generic phy interface, which causes a link error when the specific driver is not built-in: drivers/built-in.o: In function `omap2430_musb_disable': usb/musb/omap2430.c:480: undefined reference to `omap_control_usb_set_mode' drivers/built-in.o: In function `omap2430_musb_enable': usb/musb/omap2430.c:466: undefined reference to `omap_control_usb_set_mode' usb/musb/omap2430.c:447: undefined reference to `omap_control_usb_set_mode' drivers/built-in.o: In function `omap_musb_set_mailbox': usb/musb/omap2430.c:273: undefined reference to `omap_control_usb_set_mode' usb/musb/omap2430.c:304: undefined reference to `omap_control_usb_set_mode' drivers/built-in.o:(.debug_addr+0xbd9e0): more undefined references to `omap_control_usb_set_mode' follow This adds an explicit dependency. Signed-off-by: Arnd Bergmann Fixes: ca784be36cc725 ("usb: start using the control module driver") Cc: # v3.9+ Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 26c835f..14e1628 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -79,7 +79,7 @@ config USB_MUSB_TUSB6010 config USB_MUSB_OMAP2PLUS tristate "OMAP2430 and onwards" - depends on ARCH_OMAP2PLUS && USB + depends on ARCH_OMAP2PLUS && USB && OMAP_CONTROL_PHY select GENERIC_PHY config USB_MUSB_AM35X -- cgit v0.10.2 From b46146d59fdac6a6f559d5e6618f128abf0c2912 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:26 +0100 Subject: usb: dwc2: host: resume root hub on remote wakeup When a remote wakeup happens during bus_suspend, hcd needs to resume its root hub. Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index c3e66f0..4a9bcdd 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -316,10 +316,12 @@ void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) */ static void dwc2_hcd_rem_wakeup(struct dwc2_hsotg *hsotg) { - if (hsotg->lx_state == DWC2_L2) + if (hsotg->lx_state == DWC2_L2) { hsotg->flags.b.port_suspend_change = 1; - else + usb_hcd_resume_root_hub(hsotg->priv); + } else { hsotg->flags.b.port_l1_change = 1; + } } /** -- cgit v0.10.2 From c00dd4a6ec20a58ed38be85f8e47efda40625f17 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:27 +0100 Subject: usb: dwc2: gadget: fix clear halt feature handling When clearing HALT on an endpoint, req->complete of in progress requests must be called with locks off. New request should only be started if there is not already a pending request on the endpoint. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 50ae096..02d0e9a 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1007,16 +1007,22 @@ static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, hs_req = ep->req; ep->req = NULL; list_del_init(&hs_req->queue); - usb_gadget_giveback_request(&ep->ep, - &hs_req->req); + if (hs_req->req.complete) { + spin_unlock(&hsotg->lock); + usb_gadget_giveback_request( + &ep->ep, &hs_req->req); + spin_lock(&hsotg->lock); + } } /* If we have pending request, then start it */ - restart = !list_empty(&ep->queue); - if (restart) { - hs_req = get_ep_head(ep); - s3c_hsotg_start_req(hsotg, ep, - hs_req, false); + if (!ep->req) { + restart = !list_empty(&ep->queue); + if (restart) { + hs_req = get_ep_head(ep); + s3c_hsotg_start_req(hsotg, ep, + hs_req, false); + } } } -- cgit v0.10.2 From 9e14d0a566f48d33a9253096963b3b8199e4df57 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:28 +0100 Subject: usb: dwc2: gadget: add TEST_MODE feature support Handle SET_FEATURE TEST_MODE request sent by the host. Slightly rework FEATURE request handling to allow parsing other request types than Endpoint. Also add a debugfs to change test mode value from user space. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index f09b3de..c750fd3 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -567,12 +567,14 @@ struct dwc2_hw_params { * @num_of_eps: Number of available EPs (excluding EP0) * @debug_root: Root directrory for debugfs. * @debug_file: Main status file for debugfs. + * @debug_testmode: Testmode status file for debugfs. * @debug_fifo: FIFO status file for debugfs. * @ep0_reply: Request used for ep0 reply. * @ep0_buff: Buffer for EP0 reply data, if needed. * @ctrl_buff: Buffer for EP0 control requests. * @ctrl_req: Request for EP0 control packets. * @ep0_state: EP0 control transfers state + * @test_mode: USB test mode requested by the host * @last_rst: Time of last reset * @eps: The endpoints being supplied to the gadget framework * @g_using_dma: Indicate if dma usage is enabled @@ -610,6 +612,7 @@ struct dwc2_hsotg { struct dentry *debug_root; struct dentry *debug_file; + struct dentry *debug_testmode; struct dentry *debug_fifo; /* DWC OTG HW Release versions */ @@ -706,6 +709,7 @@ struct dwc2_hsotg { void *ep0_buff; void *ctrl_buff; enum dwc2_ep0_state ep0_state; + u8 test_mode; struct usb_gadget gadget; unsigned int enabled:1; diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 02d0e9a..67ea258 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "core.h" #include "hw.h" @@ -835,6 +836,32 @@ static struct s3c_hsotg_ep *ep_from_windex(struct dwc2_hsotg *hsotg, } /** + * s3c_hsotg_set_test_mode - Enable usb Test Modes + * @hsotg: The driver state. + * @testmode: requested usb test mode + * Enable usb Test Mode requested by the Host. + */ +static int s3c_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode) +{ + int dctl = readl(hsotg->regs + DCTL); + + dctl &= ~DCTL_TSTCTL_MASK; + switch (testmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + dctl |= testmode << DCTL_TSTCTL_SHIFT; + break; + default: + return -EINVAL; + } + writel(dctl, hsotg->regs + DCTL); + return 0; +} + +/** * s3c_hsotg_send_reply - send reply to control request * @hsotg: The device state * @ep: Endpoint 0 @@ -968,19 +995,48 @@ static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, struct s3c_hsotg_ep *ep; int ret; bool halted; + u32 recip; + u32 wValue; + u32 wIndex; dev_dbg(hsotg->dev, "%s: %s_FEATURE\n", __func__, set ? "SET" : "CLEAR"); - if (ctrl->bRequestType == USB_RECIP_ENDPOINT) { - ep = ep_from_windex(hsotg, le16_to_cpu(ctrl->wIndex)); + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (wValue) { + case USB_DEVICE_TEST_MODE: + if ((wIndex & 0xff) != 0) + return -EINVAL; + if (!set) + return -EINVAL; + + hsotg->test_mode = wIndex >> 8; + ret = s3c_hsotg_send_reply(hsotg, ep0, NULL, 0); + if (ret) { + dev_err(hsotg->dev, + "%s: failed to send reply\n", __func__); + return ret; + } + break; + default: + return -ENOENT; + } + break; + + case USB_RECIP_ENDPOINT: + ep = ep_from_windex(hsotg, wIndex); if (!ep) { dev_dbg(hsotg->dev, "%s: no endpoint for 0x%04x\n", - __func__, le16_to_cpu(ctrl->wIndex)); + __func__, wIndex); return -ENOENT; } - switch (le16_to_cpu(ctrl->wValue)) { + switch (wValue) { case USB_ENDPOINT_HALT: halted = ep->halted; @@ -1031,9 +1087,10 @@ static int s3c_hsotg_process_req_feature(struct dwc2_hsotg *hsotg, default: return -ENOENT; } - } else - return -ENOENT; /* currently only deal with endpoint */ - + break; + default: + return -ENOENT; + } return 1; } @@ -1734,6 +1791,17 @@ static void s3c_hsotg_complete_in(struct dwc2_hsotg *hsotg, if (hs_ep->index == 0 && hsotg->ep0_state == DWC2_EP0_STATUS_IN) { dev_dbg(hsotg->dev, "zlp packet sent\n"); s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); + if (hsotg->test_mode) { + int ret; + + ret = s3c_hsotg_set_test_mode(hsotg, hsotg->test_mode); + if (ret < 0) { + dev_dbg(hsotg->dev, "Invalid Test #%d\n", + hsotg->test_mode); + s3c_hsotg_stall_ep0(hsotg); + return; + } + } s3c_hsotg_enqueue_setup(hsotg); return; } @@ -2045,6 +2113,7 @@ void s3c_hsotg_disconnect(struct dwc2_hsotg *hsotg) return; hsotg->connected = 0; + hsotg->test_mode = 0; for (ep = 0; ep < hsotg->num_of_eps; ep++) { if (hsotg->eps_in[ep]) @@ -3257,6 +3326,103 @@ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg) } /** + * testmode_write - debugfs: change usb test mode + * @seq: The seq file to write to. + * @v: Unused parameter. + * + * This debugfs entry modify the current usb test mode. + */ +static ssize_t testmode_write(struct file *file, const char __user *ubuf, size_t + count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct dwc2_hsotg *hsotg = s->private; + unsigned long flags; + u32 testmode = 0; + char buf[32]; + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "test_j", 6)) + testmode = TEST_J; + else if (!strncmp(buf, "test_k", 6)) + testmode = TEST_K; + else if (!strncmp(buf, "test_se0_nak", 12)) + testmode = TEST_SE0_NAK; + else if (!strncmp(buf, "test_packet", 11)) + testmode = TEST_PACKET; + else if (!strncmp(buf, "test_force_enable", 17)) + testmode = TEST_FORCE_EN; + else + testmode = 0; + + spin_lock_irqsave(&hsotg->lock, flags); + s3c_hsotg_set_test_mode(hsotg, testmode); + spin_unlock_irqrestore(&hsotg->lock, flags); + return count; +} + +/** + * testmode_show - debugfs: show usb test mode state + * @seq: The seq file to write to. + * @v: Unused parameter. + * + * This debugfs entry shows which usb test mode is currently enabled. + */ +static int testmode_show(struct seq_file *s, void *unused) +{ + struct dwc2_hsotg *hsotg = s->private; + unsigned long flags; + int dctl; + + spin_lock_irqsave(&hsotg->lock, flags); + dctl = readl(hsotg->regs + DCTL); + dctl &= DCTL_TSTCTL_MASK; + dctl >>= DCTL_TSTCTL_SHIFT; + spin_unlock_irqrestore(&hsotg->lock, flags); + + switch (dctl) { + case 0: + seq_puts(s, "no test\n"); + break; + case TEST_J: + seq_puts(s, "test_j\n"); + break; + case TEST_K: + seq_puts(s, "test_k\n"); + break; + case TEST_SE0_NAK: + seq_puts(s, "test_se0_nak\n"); + break; + case TEST_PACKET: + seq_puts(s, "test_packet\n"); + break; + case TEST_FORCE_EN: + seq_puts(s, "test_force_enable\n"); + break; + default: + seq_printf(s, "UNKNOWN %d\n", dctl); + } + + return 0; +} + +static int testmode_open(struct inode *inode, struct file *file) +{ + return single_open(file, testmode_show, inode->i_private); +} + +static const struct file_operations testmode_fops = { + .owner = THIS_MODULE, + .open = testmode_open, + .write = testmode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** * state_show - debugfs: show overall driver and device state. * @seq: The seq file to write to. * @v: Unused parameter. @@ -3490,6 +3656,14 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) if (IS_ERR(hsotg->debug_file)) dev_err(hsotg->dev, "%s: failed to create state\n", __func__); + hsotg->debug_testmode = debugfs_create_file("testmode", + S_IRUGO | S_IWUSR, root, + hsotg, &testmode_fops); + + if (IS_ERR(hsotg->debug_testmode)) + dev_err(hsotg->dev, "%s: failed to create testmode\n", + __func__); + hsotg->debug_fifo = debugfs_create_file("fifo", 0444, root, hsotg, &fifo_fops); @@ -3544,6 +3718,7 @@ static void s3c_hsotg_delete_debug(struct dwc2_hsotg *hsotg) } debugfs_remove(hsotg->debug_file); + debugfs_remove(hsotg->debug_testmode); debugfs_remove(hsotg->debug_fifo); debugfs_remove(hsotg->debug_root); } -- cgit v0.10.2 From 58f7c43e06d0560ace01137d158108df0a08dc08 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:29 +0100 Subject: usb: dwc2: gadget: fix a typo in comment s3c_hsotg_process_req_feature comments was not correct Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 67ea258..aa5c0ba 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -981,7 +981,7 @@ static struct s3c_hsotg_req *get_ep_head(struct s3c_hsotg_ep *hs_ep) } /** - * s3c_hsotg_process_req_featire - process request {SET,CLEAR}_FEATURE + * s3c_hsotg_process_req_feature - process request {SET,CLEAR}_FEATURE * @hsotg: The device state * @ctrl: USB control request */ -- cgit v0.10.2 From d7c747c590a98d2d75e4c2c97c875f0dcd5743ee Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:30 +0100 Subject: usb: dwc2: gadget: remove hardcoded if (0) and if (1) checks Remove dead code as well. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index aa5c0ba..fc9462a 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -566,11 +566,6 @@ static void s3c_hsotg_start_req(struct dwc2_hsotg *hsotg, length = ureq->length - ureq->actual; dev_dbg(hsotg->dev, "ureq->length:%d ureq->actual:%d\n", ureq->length, ureq->actual); - if (0) - dev_dbg(hsotg->dev, - "REQ buf %p len %d dma %pad noi=%d zp=%d snok=%d\n", - ureq->buf, length, &ureq->dma, - ureq->no_interrupt, ureq->zero, ureq->short_not_ok); maxreq = get_ep_limit(hs_ep); if (length > maxreq) { @@ -1566,8 +1561,7 @@ static void s3c_hsotg_handle_rx(struct dwc2_hsotg *hsotg) size = grxstsr & GRXSTS_BYTECNT_MASK; size >>= GRXSTS_BYTECNT_SHIFT; - if (1) - dev_dbg(hsotg->dev, "%s: GRXSTSP=0x%08x (%d@%d)\n", + dev_dbg(hsotg->dev, "%s: GRXSTSP=0x%08x (%d@%d)\n", __func__, grxstsr, size, epnum); switch ((status & GRXSTS_PKTSTS_MASK) >> GRXSTS_PKTSTS_SHIFT) { @@ -2926,12 +2920,6 @@ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) /* Be in disconnected state until gadget is registered */ __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); - if (0) { - /* post global nak until we're ready */ - writel(DCTL_SGNPINNAK | DCTL_SGOUTNAK, - hsotg->regs + DCTL); - } - /* setup fifos */ dev_dbg(hsotg->dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", -- cgit v0.10.2 From 7d24c1b5a77c71d885fca047d1f25721f6b366e7 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:31 +0100 Subject: usb: dwc2: gadget: add unaligned buffers support When using DMA, dwc2 requires buffers to be 4 bytes aligned. Use bounce buffers if they are not. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index c750fd3..485b195 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -158,10 +158,12 @@ struct s3c_hsotg_ep { * struct s3c_hsotg_req - data transfer request * @req: The USB gadget request * @queue: The list of requests for the endpoint this is queued for. + * @saved_req_buf: variable to save req.buf when bounce buffers are used. */ struct s3c_hsotg_req { struct usb_request req; struct list_head queue; + void *saved_req_buf; }; #if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index fc9462a..5e91cf15 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -726,6 +726,59 @@ dma_error: return -EIO; } +static int s3c_hsotg_handle_unaligned_buf_start(struct dwc2_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, struct s3c_hsotg_req *hs_req) +{ + void *req_buf = hs_req->req.buf; + + /* If dma is not being used or buffer is aligned */ + if (!using_dma(hsotg) || !((long)req_buf & 3)) + return 0; + + WARN_ON(hs_req->saved_req_buf); + + dev_dbg(hsotg->dev, "%s: %s: buf=%p length=%d\n", __func__, + hs_ep->ep.name, req_buf, hs_req->req.length); + + hs_req->req.buf = kmalloc(hs_req->req.length, GFP_ATOMIC); + if (!hs_req->req.buf) { + hs_req->req.buf = req_buf; + dev_err(hsotg->dev, + "%s: unable to allocate memory for bounce buffer\n", + __func__); + return -ENOMEM; + } + + /* Save actual buffer */ + hs_req->saved_req_buf = req_buf; + + if (hs_ep->dir_in) + memcpy(hs_req->req.buf, req_buf, hs_req->req.length); + return 0; +} + +static void s3c_hsotg_handle_unaligned_buf_complete(struct dwc2_hsotg *hsotg, + struct s3c_hsotg_ep *hs_ep, struct s3c_hsotg_req *hs_req) +{ + /* If dma is not being used or buffer was aligned */ + if (!using_dma(hsotg) || !hs_req->saved_req_buf) + return; + + dev_dbg(hsotg->dev, "%s: %s: status=%d actual-length=%d\n", __func__, + hs_ep->ep.name, hs_req->req.status, hs_req->req.actual); + + /* Copy data from bounce buffer on successful out transfer */ + if (!hs_ep->dir_in && !hs_req->req.status) + memcpy(hs_req->saved_req_buf, hs_req->req.buf, + hs_req->req.actual); + + /* Free bounce buffer */ + kfree(hs_req->req.buf); + + hs_req->req.buf = hs_req->saved_req_buf; + hs_req->saved_req_buf = NULL; +} + static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags) { @@ -733,6 +786,7 @@ static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, struct s3c_hsotg_ep *hs_ep = our_ep(ep); struct dwc2_hsotg *hs = hs_ep->parent; bool first; + int ret; dev_dbg(hs->dev, "%s: req %p: %d@%p, noi=%d, zero=%d, snok=%d\n", ep->name, req, req->length, req->buf, req->no_interrupt, @@ -743,9 +797,13 @@ static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, req->actual = 0; req->status = -EINPROGRESS; + ret = s3c_hsotg_handle_unaligned_buf_start(hs, hs_ep, hs_req); + if (ret) + return ret; + /* if we're using DMA, sync the buffers as necessary */ if (using_dma(hs)) { - int ret = s3c_hsotg_map_dma(hs, hs_ep, req); + ret = s3c_hsotg_map_dma(hs, hs_ep, req); if (ret) return ret; } @@ -1326,6 +1384,8 @@ static void s3c_hsotg_complete_request(struct dwc2_hsotg *hsotg, if (hs_req->req.status == -EINPROGRESS) hs_req->req.status = result; + s3c_hsotg_handle_unaligned_buf_complete(hsotg, hs_ep, hs_req); + hs_ep->req = NULL; list_del_init(&hs_req->queue); -- cgit v0.10.2 From 643cc4dee1ee804b46f8d288babca046cca7e099 Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:32 +0100 Subject: usb: dwc2: gadget: add reset flag in init function Add a flag to request physical reset of the controller when s3c_hsotg_core_init_disconnected is called. During the usb reset, controller must not be fully reconfigured and resetted. Else this leads to shorter chirp-k duration during enumeration. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 485b195..f74304b 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -999,7 +999,8 @@ extern int s3c_hsotg_remove(struct dwc2_hsotg *hsotg); extern int s3c_hsotg_suspend(struct dwc2_hsotg *dwc2); extern int s3c_hsotg_resume(struct dwc2_hsotg *dwc2); extern int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq); -extern void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2); +extern void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, + bool reset); extern void s3c_hsotg_core_connect(struct dwc2_hsotg *hsotg); extern void s3c_hsotg_disconnect(struct dwc2_hsotg *dwc2); #else @@ -1011,7 +1012,8 @@ static inline int s3c_hsotg_resume(struct dwc2_hsotg *dwc2) { return 0; } static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq) { return 0; } -static inline void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2) {} +static inline void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2, + bool reset) {} static inline void s3c_hsotg_core_connect(struct dwc2_hsotg *hsotg) {} static inline void s3c_hsotg_disconnect(struct dwc2_hsotg *dwc2) {} #endif diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5e91cf15..3cf9c6a 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2271,9 +2271,13 @@ static int s3c_hsotg_corereset(struct dwc2_hsotg *hsotg) * * Issue a soft reset to the core, and await the core finishing it. */ -void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) +void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, + bool is_usb_reset) { - s3c_hsotg_corereset(hsotg); + u32 val; + + if (!is_usb_reset) + s3c_hsotg_corereset(hsotg); /* * we must now enable ep0 ready for host detection and then @@ -2286,7 +2290,8 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) s3c_hsotg_init_fifo(hsotg); - __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); + if (!is_usb_reset) + __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); writel(1 << 18 | DCFG_DEVSPD_HS, hsotg->regs + DCFG); @@ -2357,9 +2362,11 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) s3c_hsotg_ctrl_epint(hsotg, 0, 0, 1); s3c_hsotg_ctrl_epint(hsotg, 0, 1, 1); - __orr32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); - udelay(10); /* see openiboot */ - __bic32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + if (!is_usb_reset) { + __orr32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + udelay(10); /* see openiboot */ + __bic32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + } dev_dbg(hsotg->dev, "DCTL=0x%08x\n", readl(hsotg->regs + DCTL)); @@ -2388,8 +2395,10 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg) readl(hsotg->regs + DOEPCTL0)); /* clear global NAKs */ - writel(DCTL_CGOUTNAK | DCTL_CGNPINNAK | DCTL_SFTDISCON, - hsotg->regs + DCTL); + val = DCTL_CGOUTNAK | DCTL_CGNPINNAK; + if (!is_usb_reset) + val |= DCTL_SFTDISCON; + __orr32(hsotg->regs + DCTL, val); /* must be at-least 3ms to allow bus to see disconnect */ mdelay(3); @@ -2482,7 +2491,7 @@ irq_retry: kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, true); s3c_hsotg_core_connect(hsotg); } } @@ -3052,7 +3061,7 @@ static int s3c_hsotg_udc_start(struct usb_gadget *gadget, spin_lock_irqsave(&hsotg->lock, flags); s3c_hsotg_init(hsotg); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, false); hsotg->enabled = 0; spin_unlock_irqrestore(&hsotg->lock, flags); @@ -3171,7 +3180,7 @@ static int s3c_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) if (is_active) { /* Kill any ep0 requests as controller will be reinitialized */ kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, false); if (hsotg->enabled) s3c_hsotg_core_connect(hsotg); } else { @@ -4097,7 +4106,7 @@ int s3c_hsotg_resume(struct dwc2_hsotg *hsotg) s3c_hsotg_phy_enable(hsotg); spin_lock_irqsave(&hsotg->lock, flags); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, false); if (hsotg->enabled) s3c_hsotg_core_connect(hsotg); spin_unlock_irqrestore(&hsotg->lock, flags); diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 4a9bcdd..c78c874 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1373,7 +1373,7 @@ static void dwc2_conn_id_status_change(struct work_struct *work) hsotg->op_state = OTG_STATE_B_PERIPHERAL; dwc2_core_init(hsotg, false, -1); dwc2_enable_global_interrupts(hsotg); - s3c_hsotg_core_init_disconnected(hsotg); + s3c_hsotg_core_init_disconnected(hsotg, false); s3c_hsotg_core_connect(hsotg); } else { /* A-Device connector (Host Mode) */ -- cgit v0.10.2 From 86e37bf9f5d7f1e634ba5ca6bbc02aa85d004d2f Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:33 +0100 Subject: usb: dwc2: gadget: don't modify pullup status during reset Pullup doesn't need to be enabled during usb reset since it is already enabled. This leads to shorter chirp-k duration if done during usb reset. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 3cf9c6a..5090300 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2492,7 +2492,6 @@ irq_retry: -ECONNRESET); s3c_hsotg_core_init_disconnected(hsotg, true); - s3c_hsotg_core_connect(hsotg); } } } -- cgit v0.10.2 From ccb34a9101b340885b1ca3ec766e8544a6a433a7 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:34 +0100 Subject: usb: dwc2: gadget: fix debug message for zlp Print debug message according to zlp direction. Always saying "Sending" is misleading. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 5090300..38ba022 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1335,7 +1335,12 @@ static void s3c_hsotg_program_zlp(struct dwc2_hsotg *hsotg, u32 epctl_reg = hs_ep->dir_in ? DIEPCTL(index) : DOEPCTL(index); u32 epsiz_reg = hs_ep->dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index); - dev_dbg(hsotg->dev, "Sending zero-length packet on ep%d\n", index); + if (hs_ep->dir_in) + dev_dbg(hsotg->dev, "Sending zero-length packet on ep%d\n", + index); + else + dev_dbg(hsotg->dev, "Receiving zero-length packet on ep%d\n", + index); writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | DXEPTSIZ_XFERSIZE(0), hsotg->regs + -- cgit v0.10.2 From fa4a8d722bc2f0b1887ea929874a0f6754eda8b4 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:35 +0100 Subject: usb: dwc2: gadget: fix phy interface configuration hsotg->phyif is set in dwc2_gadget_init according to phy interface width. Use it for configuration instead of hardcoded value. Moreover, set USB turnaround time according to phy width. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 38ba022..8c3f8f8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2290,8 +2290,9 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, */ /* set the PLL on, remove the HNP/SRP and set the PHY */ + val = (hsotg->phyif == GUSBCFG_PHYIF8) ? 9 : 5; writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | - (0x5 << 10), hsotg->regs + GUSBCFG); + (val << 10), hsotg->regs + GUSBCFG); s3c_hsotg_init_fifo(hsotg); @@ -2978,6 +2979,7 @@ static void s3c_hsotg_phy_disable(struct dwc2_hsotg *hsotg) */ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) { + u32 trdtim; /* unmask subset of endpoint interrupts */ writel(DIEPMSK_TIMEOUTMSK | DIEPMSK_AHBERRMSK | @@ -3002,8 +3004,10 @@ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) s3c_hsotg_init_fifo(hsotg); /* set the PLL on, remove the HNP/SRP and set the PHY */ - writel(GUSBCFG_PHYIF16 | GUSBCFG_TOUTCAL(7) | (0x5 << 10), - hsotg->regs + GUSBCFG); + trdtim = (hsotg->phyif == GUSBCFG_PHYIF8) ? 9 : 5; + writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | + (trdtim << 10), + hsotg->regs + GUSBCFG); if (using_dma(hsotg)) __orr32(hsotg->regs + GAHBCFG, GAHBCFG_DMA_EN); -- cgit v0.10.2 From f889f23d1c15fa73edaecc2cbce35a441eb52581 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Fri, 30 Jan 2015 09:09:36 +0100 Subject: usb: dwc2: gadget: replace constants with defines Defines are more readable and searchable than constants. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 8c3f8f8..f201f6d 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2292,14 +2292,14 @@ void s3c_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, /* set the PLL on, remove the HNP/SRP and set the PHY */ val = (hsotg->phyif == GUSBCFG_PHYIF8) ? 9 : 5; writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | - (val << 10), hsotg->regs + GUSBCFG); + (val << GUSBCFG_USBTRDTIM_SHIFT), hsotg->regs + GUSBCFG); s3c_hsotg_init_fifo(hsotg); if (!is_usb_reset) __orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); - writel(1 << 18 | DCFG_DEVSPD_HS, hsotg->regs + DCFG); + writel(DCFG_EPMISCNT(1) | DCFG_DEVSPD_HS, hsotg->regs + DCFG); /* Clear any pending OTG interrupts */ writel(0xffffffff, hsotg->regs + GOTGINT); @@ -3006,7 +3006,7 @@ static void s3c_hsotg_init(struct dwc2_hsotg *hsotg) /* set the PLL on, remove the HNP/SRP and set the PHY */ trdtim = (hsotg->phyif == GUSBCFG_PHYIF8) ? 9 : 5; writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | - (trdtim << 10), + (trdtim << GUSBCFG_USBTRDTIM_SHIFT), hsotg->regs + GUSBCFG); if (using_dma(hsotg)) @@ -3295,7 +3295,7 @@ static int s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) /* check hardware configuration */ cfg = readl(hsotg->regs + GHWCFG2); - hsotg->num_of_eps = (cfg >> 10) & 0xF; + hsotg->num_of_eps = (cfg >> GHWCFG2_NUM_DEV_EP_SHIFT) & 0xF; /* Add ep0 */ hsotg->num_of_eps++; @@ -3326,10 +3326,10 @@ static int s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) } cfg = readl(hsotg->regs + GHWCFG3); - hsotg->fifo_mem = (cfg >> 16); + hsotg->fifo_mem = (cfg >> GHWCFG3_DFIFO_DEPTH_SHIFT); cfg = readl(hsotg->regs + GHWCFG4); - hsotg->dedicated_fifos = (cfg >> 25) & 1; + hsotg->dedicated_fifos = (cfg >> GHWCFG4_DED_FIFO_SHIFT) & 1; dev_info(hsotg->dev, "EPs: %d, %s fifos, %d entries in SPRAM\n", hsotg->num_of_eps, @@ -3354,8 +3354,8 @@ static void s3c_hsotg_dump(struct dwc2_hsotg *hsotg) readl(regs + DCFG), readl(regs + DCTL), readl(regs + DIEPMSK)); - dev_info(dev, "GAHBCFG=0x%08x, 0x44=0x%08x\n", - readl(regs + GAHBCFG), readl(regs + 0x44)); + dev_info(dev, "GAHBCFG=0x%08x, GHWCFG1=0x%08x\n", + readl(regs + GAHBCFG), readl(regs + GHWCFG1)); dev_info(dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", readl(regs + GRXFSIZ), readl(regs + GNPTXFSIZ)); @@ -3715,7 +3715,7 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) /* create general state file */ - hsotg->debug_file = debugfs_create_file("state", 0444, root, + hsotg->debug_file = debugfs_create_file("state", S_IRUGO, root, hsotg, &state_fops); if (IS_ERR(hsotg->debug_file)) @@ -3729,7 +3729,7 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) dev_err(hsotg->dev, "%s: failed to create testmode\n", __func__); - hsotg->debug_fifo = debugfs_create_file("fifo", 0444, root, + hsotg->debug_fifo = debugfs_create_file("fifo", S_IRUGO, root, hsotg, &fifo_fops); if (IS_ERR(hsotg->debug_fifo)) @@ -3741,7 +3741,7 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) ep = hsotg->eps_out[epidx]; if (ep) { - ep->debugfs = debugfs_create_file(ep->name, 0444, + ep->debugfs = debugfs_create_file(ep->name, S_IRUGO, root, ep, &ep_fops); if (IS_ERR(ep->debugfs)) @@ -3755,7 +3755,7 @@ static void s3c_hsotg_create_debug(struct dwc2_hsotg *hsotg) ep = hsotg->eps_in[epidx]; if (ep) { - ep->debugfs = debugfs_create_file(ep->name, 0444, + ep->debugfs = debugfs_create_file(ep->name, S_IRUGO, root, ep, &ep_fops); if (IS_ERR(ep->debugfs)) diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index d018ebe..d0a5ed8 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -294,6 +294,7 @@ #define GHWCFG4_NUM_IN_EPS_MASK (0xf << 26) #define GHWCFG4_NUM_IN_EPS_SHIFT 26 #define GHWCFG4_DED_FIFO_EN (1 << 25) +#define GHWCFG4_DED_FIFO_SHIFT 25 #define GHWCFG4_SESSION_END_FILT_EN (1 << 24) #define GHWCFG4_B_VALID_FILT_EN (1 << 23) #define GHWCFG4_A_VALID_FILT_EN (1 << 22) -- cgit v0.10.2 From b4c2378df6419b81eefcb9cd56726321c29a70ca Mon Sep 17 00:00:00 2001 From: Gregory Herrero Date: Fri, 30 Jan 2015 09:09:37 +0100 Subject: usb: dwc2: gadget: initialize controller in pullup callback USB reset interrupt is no more used to reset the controller. Thus, reset the controller in pullup callback as described by Synopsys programming guide. Otherwise enumeration sometimes fails when usb configuration is switched without physical disconnection. Tested-by: Robert Baldyga Acked-by: John Youn Signed-off-by: Gregory Herrero Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index f201f6d..15aa578 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3162,6 +3162,7 @@ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) if (is_on) { clk_enable(hsotg->clk); hsotg->enabled = 1; + s3c_hsotg_core_init_disconnected(hsotg, false); s3c_hsotg_core_connect(hsotg); } else { s3c_hsotg_core_disconnect(hsotg); -- cgit v0.10.2 From 9eb0797722895f4309b46d122e24d87ad17f473b Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Fri, 30 Jan 2015 17:22:45 +0100 Subject: usb: phy: generic: fix the gpios to be optional All the gpios, ie. reset-gpios and vbus-detect-gpio, should be optional and not prevent the driver from working. Fix the regression in the behavior introduced by commit "usb: phy: generic: migrate to gpio_desc". Signed-off-by: Robert Jarzmik Tested-by: Fabio Estevam Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index 9a826ff..bdb4cb3 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -218,12 +218,12 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, clk_rate = 0; needs_vcc = of_property_read_bool(node, "vcc-supply"); - nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset-gpios"); - err = PTR_ERR(nop->gpiod_reset); + nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset"); + err = PTR_ERR_OR_ZERO(nop->gpiod_reset); if (!err) { nop->gpiod_vbus = devm_gpiod_get_optional(dev, - "vbus-detect-gpio"); - err = PTR_ERR(nop->gpiod_vbus); + "vbus-detect"); + err = PTR_ERR_OR_ZERO(nop->gpiod_vbus); } } else if (pdata) { type = pdata->type; @@ -242,9 +242,11 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop, if (err == -EPROBE_DEFER) return -EPROBE_DEFER; if (err) { - dev_err(dev, "Error requesting RESET GPIO\n"); + dev_err(dev, "Error requesting RESET or VBUS GPIO\n"); return err; } + if (nop->gpiod_reset) + gpiod_direction_output(nop->gpiod_reset, 1); nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg), GFP_KERNEL); -- cgit v0.10.2 From 0f4ff5f1f95796ff5e32766136f3d3dfce1ec2d7 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Fri, 30 Jan 2015 17:22:46 +0100 Subject: usb: phy: generic: fix the vbus interrupt request Declare the interrupt as "one shot" so that it is masked until the end of the threaded handler. This prevents the irq core from spitting out an error : "Threaded irq requested with handler=NULL and !ONESHOT for irq 63" This was introduced by commit "usb: phy: generic: add vbus support". Signed-off-by: Robert Jarzmik Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index bdb4cb3..48af068 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -41,7 +41,8 @@ #include "phy-generic.h" #define VBUS_IRQ_FLAGS \ - (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING) + (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | \ + IRQF_ONESHOT) struct platform_device *usb_phy_generic_register(void) { -- cgit v0.10.2 From ffb9da65755815611010e07ad1c937858f226cb2 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Fri, 30 Jan 2015 15:58:23 +0900 Subject: usb: renesas_usbhs: fix NULL pointer dereference in dma_release_channel() This patch fixes an issue that the following commit causes NULL pointer dereference in dma_release_channel(). "usb: renesas_usbhs: add support for requesting DT DMA" (commit id abd2dbf6bb1b5f3a03a8c76b1a8879da1dd30caa) The usbhsf_dma_init_dt() should set fifo->{t,r}x_chan to NULL if dma_request_slave_channel_reason() returns IS_ERR value. Otherwise, usbhsf_dma_quit() will call dma_release_channel(), and then NULL pointer dereference happens. Acked-by: Geert Uytterhoeven Tested-by: Simon Horman Reported-by: Simon Horman Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi diff --git a/drivers/usb/renesas_usbhs/fifo.c b/drivers/usb/renesas_usbhs/fifo.c index 4c086b1..d891bff 100644 --- a/drivers/usb/renesas_usbhs/fifo.c +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -1072,7 +1072,11 @@ static void usbhsf_dma_init_pdev(struct usbhs_fifo *fifo) static void usbhsf_dma_init_dt(struct device *dev, struct usbhs_fifo *fifo) { fifo->tx_chan = dma_request_slave_channel_reason(dev, "tx"); + if (IS_ERR(fifo->tx_chan)) + fifo->tx_chan = NULL; fifo->rx_chan = dma_request_slave_channel_reason(dev, "rx"); + if (IS_ERR(fifo->rx_chan)) + fifo->rx_chan = NULL; } static void usbhsf_dma_init(struct usbhs_priv *priv, -- cgit v0.10.2 From b7974de821bb65a7efe1961a600c850b5029ed6d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 28 Jan 2015 22:49:00 +0100 Subject: usb: dwc2: fix USB core dependencies It is currently possible to configure the dwc2 driver as built-in when host mode or dual-role is enabled, but the USB core is a loadable module. This leads to a link failure: drivers/built-in.o: In function `_dwc2_hcd_start': :(.text+0x84538): undefined reference to `usb_hcd_resume_root_hub' drivers/built-in.o: In function `_dwc2_hcd_urb_dequeue': :(.text+0x84aa0): undefined reference to `usb_hcd_check_unlink_urb' :(.text+0x84e4c): undefined reference to `usb_hcd_unlink_urb_from_ep' :(.text+0x84e74): undefined reference to `usb_hcd_giveback_urb' drivers/built-in.o: In function `dwc2_assign_and_init_hc': :(.text+0x86b98): undefined reference to `usb_hcd_unmap_urb_for_dma' drivers/built-in.o: In function `_dwc2_hcd_urb_enqueue': :(.text+0x8717c): undefined reference to `usb_hcd_link_urb_to_ep' :(.text+0x872f4): undefined reference to `usb_hcd_unlink_urb_from_ep' drivers/built-in.o: In function `dwc2_host_complete': :(.text+0x875d4): undefined reference to `usb_hcd_unlink_urb_from_ep' :(.text+0x87600): undefined reference to `usb_hcd_giveback_urb' drivers/built-in.o: In function `dwc2_hcd_init': :(.text+0x87ba8): undefined reference to `usb_disabled' :(.text+0x87d38): undefined reference to `usb_create_hcd' :(.text+0x88094): undefined reference to `usb_add_hcd' :(.text+0x880dc): undefined reference to `usb_put_hcd' drivers/built-in.o: In function `dwc2_hcd_remove': :(.text+0x8821c): undefined reference to `usb_remove_hcd' :(.text+0x8823c): undefined reference to `usb_put_hcd' drivers/built-in.o: In function `dwc2_hc_handle_tt_clear.isra.10': :(.text+0x88e2c): undefined reference to `usb_hub_clear_tt_buffer' drivers/built-in.o: In function `dwc2_hcd_qtd_add': :(.text+0x8b554): undefined reference to `usb_calc_bus_time' To fix the problem, this patch changes the dependencies so that dwc2 host mode can only be enabled if either the USB core is built-in or both USB and dwc2 are modules. Acked-by: John Youn Signed-off-by: Arnd Bergmann Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig index b323c4c..76b9ba4 100644 --- a/drivers/usb/dwc2/Kconfig +++ b/drivers/usb/dwc2/Kconfig @@ -23,7 +23,7 @@ choice config USB_DWC2_HOST bool "Host only mode" - depends on USB + depends on USB=y || (USB_DWC2=m && USB) help The Designware USB2.0 high-speed host controller integrated into many SoCs. Select this option if you want the @@ -42,7 +42,7 @@ config USB_DWC2_PERIPHERAL config USB_DWC2_DUAL_ROLE bool "Dual Role mode" - depends on (USB=y || USB=USB_DWC2) && (USB_GADGET=y || USB_GADGET=USB_DWC2) + depends on (USB=y && USB_GADGET=y) || (USB_DWC2=m && USB && USB_GADGET) help Select this option if you want the driver to work in a dual-role mode. In this mode both host and gadget features are enabled, and -- cgit v0.10.2 From 74379991f6eb760c8767a39c60b4a918af30f80b Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Wed, 28 Jan 2015 13:19:17 -0200 Subject: usb: phy: phy-generic: Fix USB PHY gpio reset Since commit e9f2cefb0cdc2ae ("usb: phy: generic: migrate to gpio_desc") a kernel hang is observed on imx51-babbage board: [ 1.392824] ci_hdrc ci_hdrc.1: doesn't support gadget [ 1.397975] ci_hdrc ci_hdrc.1: EHCI Host Controller [ 1.403205] ci_hdrc ci_hdrc.1: new USB bus registered, assigned bus number 1 [ 1.422335] ci_hdrc ci_hdrc.1: USB 2.0 started, EHCI 1.00 [ 1.432962] hub 1-0:1.0: USB hub found [ 1.437119] hub 1-0:1.0: 1 port detected This hang happens because the reset GPIO stays at logic level 0. The USB PHY reset gpio is defined in the dts file as: reset-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; , which means it is active low, so what the gpio reset pin needs to do in this case is the following: - Go to logic level 0 to reset the USB PHY - Stay at 0 for a bit - Go back to logic level 1 When switching to gpiod API we need to following according to Documentation/gpio/consumer.txt: "The first thing a driver must do with a GPIO is setting its direction. If no direction-setting flags have been given to gpiod_get*(), this is done by invoking one of the gpiod_direction_*() functions: int gpiod_direction_input(struct gpio_desc *desc) int gpiod_direction_output(struct gpio_desc *desc, int value)" Since no direction-setting flags have been given to devm_gpiod_get_optional() in our case, we need to use gpiod_direction_output to comply with the gpiod API. With this change the USB PHY reset performs a proper reset, the kernel boots fine and USB host is functional. Signed-off-by: Fabio Estevam Signed-off-by: Felipe Balbi diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index 48af068..70be50b 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -64,11 +64,12 @@ static int nop_set_suspend(struct usb_phy *x, int suspend) static void nop_reset_set(struct usb_phy_generic *nop, int asserted) { - if (nop->gpiod_reset) - gpiod_set_value(nop->gpiod_reset, asserted); + if (!nop->gpiod_reset) + return; - if (!asserted) - usleep_range(10000, 20000); + gpiod_direction_output(nop->gpiod_reset, !asserted); + usleep_range(10000, 20000); + gpiod_set_value(nop->gpiod_reset, asserted); } /* interface to regulator framework */ -- cgit v0.10.2 From d9b2b19fc45265a2423fa68e22318882b42a111c Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 31 Jan 2015 21:13:59 -0800 Subject: usb: musb: blackfin: remove incorrect __exit_p() bfin_remove() is not (nor should it be) marked as __exit, so we should not be using __exit_p() wrapper with it, otherwise unbinding through sysfs does not work properly. Signed-off-by: Dmitry Torokhov Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/blackfin.c b/drivers/usb/musb/blackfin.c index 1782501..6123b74 100644 --- a/drivers/usb/musb/blackfin.c +++ b/drivers/usb/musb/blackfin.c @@ -608,7 +608,7 @@ static SIMPLE_DEV_PM_OPS(bfin_pm_ops, bfin_suspend, bfin_resume); static struct platform_driver bfin_driver = { .probe = bfin_probe, - .remove = __exit_p(bfin_remove), + .remove = bfin_remove, .driver = { .name = "musb-blackfin", .pm = &bfin_pm_ops, -- cgit v0.10.2 From 8333d3cd06f89bdddea61c64ae123b2265ffab9d Mon Sep 17 00:00:00 2001 From: Christoph Jaeger Date: Mon, 2 Feb 2015 10:00:03 -0500 Subject: usb: gadget: Kconfig: use bool instead of boolean Keyword 'boolean' for type definition attributes is considered deprecated and, therefore, should not be used anymore. See http://lkml.kernel.org/r/cover.1418003065.git.cj@linux.com See http://lkml.kernel.org/r/1419108071-11607-1-git-send-email-cj@linux.com Signed-off-by: Christoph Jaeger Signed-off-by: Felipe Balbi diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 65f7f12..9653903 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -424,7 +424,7 @@ config USB_CONFIGFS_F_HID For more information, see Documentation/usb/gadget_hid.txt. config USB_CONFIGFS_F_UVC - boolean "USB Webcam function" + bool "USB Webcam function" depends on USB_CONFIGFS depends on VIDEO_DEV select VIDEOBUF2_VMALLOC -- cgit v0.10.2 From 1fa2df0c70dadba1139c9dd52c9070d00fe23c98 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 3 Feb 2015 10:37:06 +0800 Subject: staging: emxx_udc: fix the build error Fix below build error: reproduce: wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross git checkout 9239d88fc5e58a2a72bc949362f999aac9bffb29 # save the attached .config to linux build tree make.cross ARCH=arm All error/warnings: In file included from include/linux/seqlock.h:35:0, from include/linux/time.h:5, from include/linux/stat.h:18, from include/linux/module.h:10, from drivers/staging/emxx_udc/emxx_udc.c:22: drivers/staging/emxx_udc/emxx_udc.c: In function 'nbu2ss_gad_set_selfpowered': >> drivers/staging/emxx_udc/emxx_udc.c:3129:21: error: 'udc' undeclared (first use in this function) spin_lock_irqsave(&udc->lock, flags); ^ include/linux/spinlock.h:215:34: note: in definition of macro 'raw_spin_lock_irqsave' flags = _raw_spin_lock_irqsave(lock); \ ^ >> drivers/staging/emxx_udc/emxx_udc.c:3129:2: note: in expansion of macro 'spin_lock_irqsave' spin_lock_irqsave(&udc->lock, flags); ^ drivers/staging/emxx_udc/emxx_udc.c:3129:21: note: each undeclared identifier is reported only once for each function it appears in spin_lock_irqsave(&udc->lock, flags); ^ include/linux/spinlock.h:215:34: note: in definition of macro 'raw_spin_lock_irqsave' flags = _raw_spin_lock_irqsave(lock); \ ^ >> drivers/staging/emxx_udc/emxx_udc.c:3129:2: note: in expansion of macro 'spin_lock_irqsave' spin_lock_irqsave(&udc->lock, flags); ^ vim +/udc +3129 drivers/staging/emxx_udc/emxx_udc.c 33aa8d45 Magnus Damm 2014-06-06 3123 33aa8d45 Magnus Damm 2014-06-06 3124 if (pgadget == NULL) { 33aa8d45 Magnus Damm 2014-06-06 3125 ERR("%s, bad param\n", __func__); 33aa8d45 Magnus Damm 2014-06-06 3126 return -EINVAL; 33aa8d45 Magnus Damm 2014-06-06 3127 } 33aa8d45 Magnus Damm 2014-06-06 3128 33aa8d45 Magnus Damm 2014-06-06 @3129 spin_lock_irqsave(&udc->lock, flags); 9239d88f Peter Chen 2015-01-28 3130 pgadget->is_selfpowered = (is_selfpowered != 0); 33aa8d45 Magnus Damm 2014-06-06 3131 spin_unlock_irqrestore(&udc->lock, flags); 33aa8d45 Magnus Damm 2014-06-06 3132 Signed-off-by: Peter Chen Signed-off-by: Felipe Balbi diff --git a/drivers/staging/emxx_udc/emxx_udc.c b/drivers/staging/emxx_udc/emxx_udc.c index 1d3135a..bd70ea0 100644 --- a/drivers/staging/emxx_udc/emxx_udc.c +++ b/drivers/staging/emxx_udc/emxx_udc.c @@ -3117,6 +3117,7 @@ static int nbu2ss_gad_wakeup(struct usb_gadget *pgadget) static int nbu2ss_gad_set_selfpowered(struct usb_gadget *pgadget, int is_selfpowered) { + struct nbu2ss_udc *udc; unsigned long flags; /* INFO("=== %s()\n", __func__); */ @@ -3126,6 +3127,8 @@ static int nbu2ss_gad_set_selfpowered(struct usb_gadget *pgadget, return -EINVAL; } + udc = container_of(pgadget, struct nbu2ss_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); pgadget->is_selfpowered = (is_selfpowered != 0); spin_unlock_irqrestore(&udc->lock, flags); -- cgit v0.10.2 From 251a17f5aff990ab117590df2a13ef032464c0d3 Mon Sep 17 00:00:00 2001 From: Roshan Pius Date: Mon, 2 Feb 2015 14:55:38 -0800 Subject: usb: dwc2: Fix a bug in reading the endpoint directions from reg. According to the DWC2 datasheet, the HWCFG1 register stores the configured endpoint directions for endpoints 0-15 in bit positions 0-31. ========================== Endpoint Direction (EpDir) This 32-bit field uses two bits per endpoint to determine the endpoint direction. Endpoint Bits [31:30]: Endpoint 15 direction Bits [29:28]: Endpoint 14 direction .... Bits [3:2]: Endpoint 1 direction Bits[1:0]: Endpoint 0 direction (always BIDIR) ========================== The DWC2 driver is currently interpreting the contents of the register as directions for endpoints 1-15 which leads to an error in determining the configured endpoint directions in the core because the first 2 bits determine the direction of endpoint 0 and not 1. This is based on testing/next branch in Felipe's git. Signed-off-by: Roshan Pius Acked-by: John Youn Signed-off-by: Felipe Balbi diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 15aa578..6a30887 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3308,7 +3308,7 @@ static int s3c_hsotg_hw_cfg(struct dwc2_hsotg *hsotg) hsotg->eps_out[0] = hsotg->eps_in[0]; cfg = readl(hsotg->regs + GHWCFG1); - for (i = 1; i < hsotg->num_of_eps; i++, cfg >>= 2) { + for (i = 1, cfg >>= 2; i < hsotg->num_of_eps; i++, cfg >>= 2) { ep_type = cfg & 3; /* Direction in or both */ if (!(ep_type & 2)) { -- cgit v0.10.2 From 9298b4aad37e8c6962edcdbd0b62620adb207d03 Mon Sep 17 00:00:00 2001 From: Bin Liu Date: Tue, 3 Feb 2015 11:02:10 -0600 Subject: usb: musb: fix device hotplug behind hub The commit 889ad3b "usb: musb: try a race-free wakeup" breaks device hotplug enumeraitonn when the device is connected behind a hub while usb autosuspend is enabled. Adding finish_resume_work into runtime resume callback fixes the issue. Also resume root hub is required to resume the bus from runtime suspend, so move musb_host_resume_root_hub() back to its original location, where handles RESUME interrupt. Signed-off-by: Bin Liu Tested-by: Tony Lindgren Signed-off-by: Felipe Balbi diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 34cce3e..e6f4cbf 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -567,6 +567,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, musb->xceiv->otg->state = OTG_STATE_A_HOST; musb->is_active = 1; + musb_host_resume_root_hub(musb); break; case OTG_STATE_B_WAIT_ACON: musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL; @@ -2500,6 +2501,12 @@ static int musb_runtime_resume(struct device *dev) musb_restore_context(musb); first = 0; + if (musb->need_finish_resume) { + musb->need_finish_resume = 0; + schedule_delayed_work(&musb->finish_resume_work, + msecs_to_jiffies(20)); + } + return 0; } diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index 662de58..294e159 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -72,7 +72,6 @@ void musb_host_finish_resume(struct work_struct *work) musb->xceiv->otg->state = OTG_STATE_A_HOST; spin_unlock_irqrestore(&musb->lock, flags); - musb_host_resume_root_hub(musb); } void musb_port_suspend(struct musb *musb, bool do_suspend) -- cgit v0.10.2