diff options
199 files changed, 13846 insertions, 2159 deletions
diff --git a/Documentation/ABI/testing/configfs-usb-gadget-eem b/Documentation/ABI/testing/configfs-usb-gadget-eem new file mode 100644 index 0000000..10e87d6 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-eem @@ -0,0 +1,14 @@ +What: /config/usb-gadget/gadget/functions/eem.name +Date: May 2013 +KenelVersion: 3.11 +Description: + The attributes: + + 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 diff --git a/Documentation/ABI/testing/configfs-usb-gadget-phonet b/Documentation/ABI/testing/configfs-usb-gadget-phonet new file mode 100644 index 0000000..19b67d3 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-phonet @@ -0,0 +1,8 @@ +What: /config/usb-gadget/gadget/functions/phonet.name +Date: May 2013 +KenelVersion: 3.11 +Description: + + This item contains just one readonly attribute: ifname. + It contains the network interface name assigned during + network device registration. diff --git a/Documentation/ABI/testing/configfs-usb-gadget-rndis b/Documentation/ABI/testing/configfs-usb-gadget-rndis new file mode 100644 index 0000000..ff127dd --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-rndis @@ -0,0 +1,14 @@ +What: /config/usb-gadget/gadget/functions/rndis.name +Date: May 2013 +KenelVersion: 3.11 +Description: + The attributes: + + 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 diff --git a/Documentation/ABI/testing/configfs-usb-gadget-subset b/Documentation/ABI/testing/configfs-usb-gadget-subset new file mode 100644 index 0000000..f47170a --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-subset @@ -0,0 +1,14 @@ +What: /config/usb-gadget/gadget/functions/geth.name +Date: May 2013 +KenelVersion: 3.11 +Description: + The attributes: + + 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 diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index f093e59..9759b8c 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -236,3 +236,30 @@ Description: This attribute is to expose these information to user space. The file will read "hotplug", "wired" and "not used" if the information is available, and "unknown" otherwise. + +What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout +Date: May 2013 +Contact: Mathias Nyman <mathias.nyman@linux.intel.com> +Description: + USB 2.0 devices may support hardware link power management (LPM) + L1 sleep state. The usb2_lpm_l1_timeout attribute allows + tuning the timeout for L1 inactivity timer (LPM timer), e.g. + needed inactivity time before host requests the device to go to L1 sleep. + Useful for power management tuning. + Supported values are 0 - 65535 microseconds. + +What: /sys/bus/usb/devices/.../power/usb2_lpm_besl +Date: May 2013 +Contact: Mathias Nyman <mathias.nyman@linux.intel.com> +Description: + USB 2.0 devices that support hardware link power management (LPM) + L1 sleep state now use a best effort service latency value (BESL) to + indicate the best effort to resumption of service to the device after the + initiation of the resume event. + If the device does not have a preferred besl value then the host can select + one instead. This usb2_lpm_besl attribute allows to tune the host selected besl + value in order to tune power saving and service latency. + + Supported values are 0 - 15. + More information on how besl values map to microseconds can be found in + USB 2.0 ECN Errata for Link Power Management, section 4.10) diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra20-ehci.txt b/Documentation/devicetree/bindings/usb/nvidia,tegra20-ehci.txt index 34c9528..df09330 100644 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra20-ehci.txt +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra20-ehci.txt @@ -6,27 +6,10 @@ Practice : Universal Serial Bus" with the following modifications and additions : Required properties : - - compatible : Should be "nvidia,tegra20-ehci" for USB controllers - used in host mode. - - phy_type : Should be one of "ulpi" or "utmi". - - nvidia,vbus-gpio : If present, specifies a gpio that needs to be - activated for the bus to be powered. - - nvidia,phy : phandle of the PHY instance, the controller is connected to. - -Required properties for phy_type == ulpi: - - nvidia,phy-reset-gpio : The GPIO used to reset the PHY. + - compatible : Should be "nvidia,tegra20-ehci". + - nvidia,phy : phandle of the PHY that the controller is connected to. + - clocks : Contains a single entry which defines the USB controller's clock. Optional properties: - - dr_mode : dual role mode. Indicates the working mode for - nvidia,tegra20-ehci compatible controllers. Can be "host", "peripheral", - or "otg". Default to "host" if not defined for backward compatibility. - host means this is a host controller - peripheral means it is device controller - otg means it can operate as either ("on the go") - - nvidia,has-legacy-mode : boolean indicates whether this controller can - operate in legacy mode (as APX 2500 / 2600). In legacy mode some - registers are accessed through the APB_MISC base address instead of - the USB controller. Since this is a legacy issue it probably does not - warrant a compatible string of its own. - - nvidia,needs-double-reset : boolean is to be set for some of the Tegra2 - USB ports, which need reset twice due to hardware issues. + - nvidia,needs-double-reset : boolean is to be set for some of the Tegra20 + USB ports, which need reset twice due to hardware issues. diff --git a/Documentation/devicetree/bindings/usb/nvidia,tegra20-usb-phy.txt b/Documentation/devicetree/bindings/usb/nvidia,tegra20-usb-phy.txt index 6bdaba2..c4c9e9e 100644 --- a/Documentation/devicetree/bindings/usb/nvidia,tegra20-usb-phy.txt +++ b/Documentation/devicetree/bindings/usb/nvidia,tegra20-usb-phy.txt @@ -4,14 +4,49 @@ The device node for Tegra SOC USB PHY: Required properties : - compatible : Should be "nvidia,tegra20-usb-phy". - - reg : Address and length of the register set for the USB PHY interface. - - phy_type : Should be one of "ulpi" or "utmi". + - reg : Defines the following set of registers, in the order listed: + - The PHY's own register set. + Always present. + - The register set of the PHY containing the UTMI pad control registers. + Present if-and-only-if phy_type == utmi. + - phy_type : Should be one of "utmi", "ulpi" or "hsic". + - clocks : Defines the clocks listed in the clock-names property. + - clock-names : The following clock names must be present: + - reg: The clock needed to access the PHY's own registers. This is the + associated EHCI controller's clock. Always present. + - pll_u: PLL_U. Always present. + - timer: The timeout clock (clk_m). Present if phy_type == utmi. + - utmi-pads: The clock needed to access the UTMI pad control registers. + Present if phy_type == utmi. + - ulpi-link: The clock Tegra provides to the ULPI PHY (cdev2). + Present if phy_type == ulpi, and ULPI link mode is in use. Required properties for phy_type == ulpi: - nvidia,phy-reset-gpio : The GPIO used to reset the PHY. +Required PHY timing params for utmi phy: + - nvidia,hssync-start-delay : Number of 480 Mhz clock cycles to wait before + start of sync launches RxActive + - nvidia,elastic-limit : Variable FIFO Depth of elastic input store + - nvidia,idle-wait-delay : Number of 480 Mhz clock cycles of idle to wait + before declare IDLE. + - nvidia,term-range-adj : Range adjusment on terminations + - nvidia,xcvr-setup : HS driver output control + - nvidia,xcvr-lsfslew : LS falling slew rate control. + - nvidia,xcvr-lsrslew : LS rising slew rate control. + Optional properties: - nvidia,has-legacy-mode : boolean indicates whether this controller can operate in legacy mode (as APX 2500 / 2600). In legacy mode some registers are accessed through the APB_MISC base address instead of - the USB controller.
\ No newline at end of file + the USB controller. + - nvidia,is-wired : boolean. Indicates whether we can do certain kind of power + optimizations for the devices that are always connected. e.g. modem. + - dr_mode : dual role mode. Indicates the working mode for the PHY. Can be + "host", "peripheral", or "otg". Defaults to "host" if not defined. + host means this is a host controller + peripheral means it is device controller + otg means it can operate as either ("on the go") + +Required properties for dr_mode == otg: + - vbus-supply: regulator for VBUS diff --git a/Documentation/devicetree/bindings/usb/usb3503.txt b/Documentation/devicetree/bindings/usb/usb3503.txt index 6813a71..8c5be48 100644 --- a/Documentation/devicetree/bindings/usb/usb3503.txt +++ b/Documentation/devicetree/bindings/usb/usb3503.txt @@ -4,6 +4,10 @@ Required properties: - compatible: Should be "smsc,usb3503". - reg: Specifies the i2c slave address, it should be 0x08. - connect-gpios: Should specify GPIO for connect. +- disabled-ports: Should specify the ports unused. + '1' or '2' or '3' are availe for this property to describe the port + number. 1~3 property values are possible to be desribed. + Do not describe this property if all ports have to be enabled. - intn-gpios: Should specify GPIO for interrupt. - reset-gpios: Should specify GPIO for reset. - initial-mode: Should specify initial mode. @@ -14,6 +18,7 @@ Examples: compatible = "smsc,usb3503"; reg = <0x08>; connect-gpios = <&gpx3 0 1>; + disabled-ports = <2 3>; intn-gpios = <&gpx3 4 1>; reset-gpios = <&gpx3 5 1>; initial-mode = <1>; diff --git a/arch/arm/boot/dts/tegra20-colibri-512.dtsi b/arch/arm/boot/dts/tegra20-colibri-512.dtsi index a573b94..c12af78 100644 --- a/arch/arm/boot/dts/tegra20-colibri-512.dtsi +++ b/arch/arm/boot/dts/tegra20-colibri-512.dtsi @@ -449,7 +449,11 @@ usb@c5004000 { status = "okay"; - nvidia,phy-reset-gpio = <&gpio 169 0>; /* gpio PV1 */ + nvidia,phy-reset-gpio = <&gpio 169 1>; /* gpio PV1, active low */ + }; + + usb-phy@c5004000 { + nvidia,phy-reset-gpio = <&gpio 169 1>; /* gpio PV1, active low */ }; sdhci@c8000600 { diff --git a/arch/arm/boot/dts/tegra20-harmony.dts b/arch/arm/boot/dts/tegra20-harmony.dts index e7d5de4..ec52937 100644 --- a/arch/arm/boot/dts/tegra20-harmony.dts +++ b/arch/arm/boot/dts/tegra20-harmony.dts @@ -428,17 +428,26 @@ status = "okay"; }; + usb-phy@c5000000 { + status = "okay"; + }; + usb@c5004000 { status = "okay"; - nvidia,phy-reset-gpio = <&gpio 169 0>; /* gpio PV1 */ + nvidia,phy-reset-gpio = <&gpio 169 1>; /* gpio PV1, active low */ + }; + + usb-phy@c5004000 { + status = "okay"; + nvidia,phy-reset-gpio = <&gpio 169 1>; /* gpio PV1, active low */ }; usb@c5008000 { status = "okay"; }; - usb-phy@c5004400 { - nvidia,phy-reset-gpio = <&gpio 169 0>; /* gpio PV1 */ + usb-phy@c5008000 { + status = "okay"; }; sdhci@c8000200 { diff --git a/arch/arm/boot/dts/tegra20-iris-512.dts b/arch/arm/boot/dts/tegra20-iris-512.dts index 52f1103..9f64f70 100644 --- a/arch/arm/boot/dts/tegra20-iris-512.dts +++ b/arch/arm/boot/dts/tegra20-iris-512.dts @@ -38,13 +38,20 @@ usb@c5000000 { status = "okay"; - dr_mode = "otg"; + }; + + usb-phy@c5000000 { + status = "okay"; }; usb@c5008000 { status = "okay"; }; + usb-phy@c5008000 { + status = "okay"; + }; + serial@70006000 { status = "okay"; }; diff --git a/arch/arm/boot/dts/tegra20-paz00.dts b/arch/arm/boot/dts/tegra20-paz00.dts index e3e0c99..1c17ffa 100644 --- a/arch/arm/boot/dts/tegra20-paz00.dts +++ b/arch/arm/boot/dts/tegra20-paz00.dts @@ -427,17 +427,26 @@ status = "okay"; }; + usb-phy@c5000000 { + status = "okay"; + }; + usb@c5004000 { status = "okay"; - nvidia,phy-reset-gpio = <&gpio 168 0>; /* gpio PV0 */ + nvidia,phy-reset-gpio = <&gpio 168 1>; /* gpio PV0, active low */ + }; + + usb-phy@c5004000 { + status = "okay"; + nvidia,phy-reset-gpio = <&gpio 168 1>; /* gpio PV0, active low */ }; usb@c5008000 { status = "okay"; }; - usb-phy@c5004400 { - nvidia,phy-reset-gpio = <&gpio 168 0>; /* gpio PV0 */ + usb-phy@c5008000 { + status = "okay"; }; sdhci@c8000000 { diff --git a/arch/arm/boot/dts/tegra20-seaboard.dts b/arch/arm/boot/dts/tegra20-seaboard.dts index cee4c34..009dafe 100644 --- a/arch/arm/boot/dts/tegra20-seaboard.dts +++ b/arch/arm/boot/dts/tegra20-seaboard.dts @@ -569,17 +569,28 @@ dr_mode = "otg"; }; + usb-phy@c5000000 { + status = "okay"; + vbus-supply = <&vbus_reg>; + dr_mode = "otg"; + }; + usb@c5004000 { status = "okay"; - nvidia,phy-reset-gpio = <&gpio 169 0>; /* gpio PV1 */ + nvidia,phy-reset-gpio = <&gpio 169 1>; /* gpio PV1, active low */ + }; + + usb-phy@c5004000 { + status = "okay"; + nvidia,phy-reset-gpio = <&gpio 169 1>; /* gpio PV1, active low */ }; usb@c5008000 { status = "okay"; }; - usb-phy@c5004400 { - nvidia,phy-reset-gpio = <&gpio 169 0>; /* gpio PV1 */ + usb-phy@c5008000 { + status = "okay"; }; sdhci@c8000000 { @@ -807,6 +818,15 @@ gpio = <&pmic 1 0>; enable-active-high; }; + + vbus_reg: regulator@3 { + compatible = "regulator-fixed"; + reg = <3>; + regulator-name = "vdd_vbus_wup1"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio 24 0>; /* PD0 */ + }; }; sound { diff --git a/arch/arm/boot/dts/tegra20-tamonten.dtsi b/arch/arm/boot/dts/tegra20-tamonten.dtsi index 50b3ec1..fc2f7d6 100644 --- a/arch/arm/boot/dts/tegra20-tamonten.dtsi +++ b/arch/arm/boot/dts/tegra20-tamonten.dtsi @@ -470,6 +470,10 @@ status = "okay"; }; + usb-phy@c5008000 { + status = "okay"; + }; + sdhci@c8000600 { cd-gpios = <&gpio 58 1>; /* gpio PH2 */ wp-gpios = <&gpio 59 0>; /* gpio PH3 */ diff --git a/arch/arm/boot/dts/tegra20-trimslice.dts b/arch/arm/boot/dts/tegra20-trimslice.dts index 9cc78a1..0e65c00 100644 --- a/arch/arm/boot/dts/tegra20-trimslice.dts +++ b/arch/arm/boot/dts/tegra20-trimslice.dts @@ -314,17 +314,27 @@ nvidia,vbus-gpio = <&gpio 170 0>; /* gpio PV2 */ }; + usb-phy@c5000000 { + status = "okay"; + vbus-supply = <&vbus_reg>; + }; + usb@c5004000 { status = "okay"; - nvidia,phy-reset-gpio = <&gpio 168 0>; /* gpio PV0 */ + nvidia,phy-reset-gpio = <&gpio 168 1>; /* gpio PV0, active low */ + }; + + usb-phy@c5004000 { + status = "okay"; + nvidia,phy-reset-gpio = <&gpio 168 1>; /* gpio PV0, active low */ }; usb@c5008000 { status = "okay"; }; - usb-phy@c5004400 { - nvidia,phy-reset-gpio = <&gpio 168 0>; /* gpio PV0 */ + usb-phy@c5008000 { + status = "okay"; }; sdhci@c8000000 { @@ -390,6 +400,15 @@ regulator-max-microvolt = <1800000>; regulator-always-on; }; + + vbus_reg: regulator@2 { + compatible = "regulator-fixed"; + reg = <2>; + regulator-name = "usb1_vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio 170 0>; /* PV2 */ + }; }; sound { diff --git a/arch/arm/boot/dts/tegra20-ventana.dts b/arch/arm/boot/dts/tegra20-ventana.dts index dd38f1f..e00f89e 100644 --- a/arch/arm/boot/dts/tegra20-ventana.dts +++ b/arch/arm/boot/dts/tegra20-ventana.dts @@ -505,17 +505,26 @@ status = "okay"; }; + usb-phy@c5000000 { + status = "okay"; + }; + usb@c5004000 { status = "okay"; - nvidia,phy-reset-gpio = <&gpio 169 0>; /* gpio PV1 */ + nvidia,phy-reset-gpio = <&gpio 169 1>; /* gpio PV1, active low */ + }; + + usb-phy@c5004000 { + status = "okay"; + nvidia,phy-reset-gpio = <&gpio 169 1>; /* gpio PV1, active low */ }; usb@c5008000 { status = "okay"; }; - usb-phy@c5004400 { - nvidia,phy-reset-gpio = <&gpio 169 0>; /* gpio PV1 */ + usb-phy@c5008000 { + status = "okay"; }; sdhci@c8000000 { diff --git a/arch/arm/boot/dts/tegra20-whistler.dts b/arch/arm/boot/dts/tegra20-whistler.dts index d2567f8..3c24c9b 100644 --- a/arch/arm/boot/dts/tegra20-whistler.dts +++ b/arch/arm/boot/dts/tegra20-whistler.dts @@ -511,11 +511,21 @@ nvidia,vbus-gpio = <&tca6416 0 0>; /* GPIO_PMU0 */ }; + usb-phy@c5000000 { + status = "okay"; + vbus-supply = <&vbus1_reg>; + }; + usb@c5008000 { status = "okay"; nvidia,vbus-gpio = <&tca6416 1 0>; /* GPIO_PMU1 */ }; + usb-phy@c5008000 { + status = "okay"; + vbus-supply = <&vbus3_reg>; + }; + sdhci@c8000400 { status = "okay"; cd-gpios = <&gpio 69 1>; /* gpio PI5 */ @@ -568,6 +578,24 @@ regulator-max-microvolt = <5000000>; regulator-always-on; }; + + vbus1_reg: regulator@2 { + compatible = "regulator-fixed"; + reg = <2>; + regulator-name = "vbus1"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&tca6416 0 0>; /* GPIO_PMU0 */ + }; + + vbus3_reg: regulator@3 { + compatible = "regulator-fixed"; + reg = <3>; + regulator-name = "vbus3"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&tca6416 1 0>; /* GPIO_PMU1 */ + }; }; sound { diff --git a/arch/arm/boot/dts/tegra20.dtsi b/arch/arm/boot/dts/tegra20.dtsi index 56a9110..96d6d8a 100644 --- a/arch/arm/boot/dts/tegra20.dtsi +++ b/arch/arm/boot/dts/tegra20.dtsi @@ -455,13 +455,24 @@ status = "disabled"; }; - phy1: usb-phy@c5000400 { + phy1: usb-phy@c5000000 { compatible = "nvidia,tegra20-usb-phy"; - reg = <0xc5000400 0x3c00>; + reg = <0xc5000000 0x4000 0xc5000000 0x4000>; phy_type = "utmi"; + clocks = <&tegra_car 22>, + <&tegra_car 127>, + <&tegra_car 106>, + <&tegra_car 22>; + clock-names = "reg", "pll_u", "timer", "utmi-pads"; nvidia,has-legacy-mode; - clocks = <&tegra_car 22>, <&tegra_car 127>; - clock-names = "phy", "pll_u"; + hssync_start_delay = <9>; + idle_wait_delay = <17>; + elastic_limit = <16>; + term_range_adj = <6>; + xcvr_setup = <9>; + xcvr_lsfslew = <1>; + xcvr_lsrslew = <1>; + status = "disabled"; }; usb@c5004000 { @@ -474,12 +485,15 @@ status = "disabled"; }; - phy2: usb-phy@c5004400 { + phy2: usb-phy@c5004000 { compatible = "nvidia,tegra20-usb-phy"; - reg = <0xc5004400 0x3c00>; + reg = <0xc5004000 0x4000>; phy_type = "ulpi"; - clocks = <&tegra_car 93>, <&tegra_car 127>; - clock-names = "phy", "pll_u"; + clocks = <&tegra_car 58>, + <&tegra_car 127>, + <&tegra_car 93>; + clock-names = "reg", "pll_u", "ulpi-link"; + status = "disabled"; }; usb@c5008000 { @@ -492,12 +506,23 @@ status = "disabled"; }; - phy3: usb-phy@c5008400 { + phy3: usb-phy@c5008000 { compatible = "nvidia,tegra20-usb-phy"; - reg = <0xc5008400 0x3c00>; + reg = <0xc5008000 0x4000 0xc5000000 0x4000>; phy_type = "utmi"; - clocks = <&tegra_car 22>, <&tegra_car 127>; - clock-names = "phy", "pll_u"; + clocks = <&tegra_car 59>, + <&tegra_car 127>, + <&tegra_car 106>, + <&tegra_car 22>; + clock-names = "reg", "pll_u", "timer", "utmi-pads"; + hssync_start_delay = <9>; + idle_wait_delay = <17>; + elastic_limit = <16>; + term_range_adj = <6>; + xcvr_setup = <9>; + xcvr_lsfslew = <2>; + xcvr_lsrslew = <2>; + status = "disabled"; }; sdhci@c8000000 { diff --git a/drivers/staging/serqt_usb2/serqt_usb2.c b/drivers/staging/serqt_usb2/serqt_usb2.c index 8a6e5ea..4f891db 100644 --- a/drivers/staging/serqt_usb2/serqt_usb2.c +++ b/drivers/staging/serqt_usb2/serqt_usb2.c @@ -873,7 +873,7 @@ static int qt_open(struct tty_struct *tty, result = qt_get_device(serial, &port0->DeviceData); /* Port specific setups */ - result = qt_open_channel(serial, port->number, &ChannelData); + result = qt_open_channel(serial, port->port_number, &ChannelData); if (result < 0) { dev_dbg(&port->dev, "qt_open_channel failed\n"); return result; @@ -888,7 +888,7 @@ static int qt_open(struct tty_struct *tty, (SERIAL_MSR_CTS | SERIAL_MSR_DSR | SERIAL_MSR_RI | SERIAL_MSR_CD); /* Set Baud rate to default and turn off (default)flow control here */ - result = qt_setuart(serial, port->number, DEFAULT_DIVISOR, DEFAULT_LCR); + result = qt_setuart(serial, port->port_number, DEFAULT_DIVISOR, DEFAULT_LCR); if (result < 0) { dev_dbg(&port->dev, "qt_setuart failed\n"); return result; @@ -906,7 +906,6 @@ static int qt_open(struct tty_struct *tty, qt_submit_urb_from_open(serial, port); } - dev_dbg(&port->dev, "port number is %d\n", port->number); dev_dbg(&port->dev, "serial number is %d\n", port->serial->minor); dev_dbg(&port->dev, "Bulkin endpoint is %d\n", port->bulk_in_endpointAddress); @@ -1022,14 +1021,11 @@ static void qt_close(struct usb_serial_port *port) /* Close uart channel */ status = qt_close_channel(serial, index); if (status < 0) - dev_dbg(&port->dev, - "%s - port %d qt_close_channel failed.\n", - __func__, port->number); + dev_dbg(&port->dev, "%s - qt_close_channel failed.\n", __func__); port0->open_ports--; - dev_dbg(&port->dev, "qt_num_open_ports in close%d:in port%d\n", - port0->open_ports, port->number); + dev_dbg(&port->dev, "qt_num_open_ports in close%d\n", port0->open_ports); if (port0->open_ports == 0) { if (serial->port[0]->interrupt_in_urb) { @@ -1169,8 +1165,7 @@ static int qt_ioctl(struct tty_struct *tty, return 0; } - dev_dbg(&port->dev, "%s -No ioctl for that one. port = %d\n", - __func__, port->number); + dev_dbg(&port->dev, "%s -No ioctl for that one.\n", __func__); return -ENOIOCTLCMD; } @@ -1245,8 +1240,7 @@ static void qt_set_termios(struct tty_struct *tty, /* Now determine flow control */ if (cflag & CRTSCTS) { - dev_dbg(&port->dev, "%s - Enabling HW flow control port %d\n", - __func__, port->number); + dev_dbg(&port->dev, "%s - Enabling HW flow control\n", __func__); /* Enable RTS/CTS flow control */ status = BoxSetHW_FlowCtrl(port->serial, index, 1); @@ -1258,8 +1252,7 @@ static void qt_set_termios(struct tty_struct *tty, } else { /* Disable RTS/CTS flow control */ dev_dbg(&port->dev, - "%s - disabling HW flow control port %d\n", - __func__, port->number); + "%s - disabling HW flow control\n", __func__); status = BoxSetHW_FlowCtrl(port->serial, index, 0); if (status < 0) { diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 92e1dc9..73f62ca 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -2,59 +2,15 @@ # USB device configuration # -# many non-PCI SOC chips embed OHCI +# These are unused now, remove them once they are no longer selected config USB_ARCH_HAS_OHCI - boolean - # ARM: - default y if SA1111 - default y if ARCH_OMAP - default y if ARCH_S3C24XX - default y if PXA27x - default y if PXA3xx - default y if ARCH_EP93XX - default y if ARCH_AT91 - default y if MFD_TC6393XB - default y if ARCH_W90X900 - default y if ARCH_DAVINCI_DA8XX - default y if ARCH_CNS3XXX - default y if PLAT_SPEAR - default y if ARCH_EXYNOS - # PPC: - default y if STB03xxx - default y if PPC_MPC52xx - # MIPS: - default y if MIPS_ALCHEMY - default y if MACH_JZ4740 - # more: - default PCI - -# some non-PCI hcds implement EHCI + bool + config USB_ARCH_HAS_EHCI - boolean - default y if FSL_SOC - default y if PPC_MPC512x - default y if ARCH_IXP4XX - default y if ARCH_W90X900 - default y if ARCH_AT91 - default y if ARCH_MXC - default y if ARCH_MXS - default y if ARCH_OMAP3 - default y if ARCH_CNS3XXX - default y if ARCH_VT8500 - default y if PLAT_SPEAR - default y if PLAT_S5P - default y if ARCH_MSM - default y if MICROBLAZE - default y if SPARC_LEON - default y if ARCH_MMP - default y if MACH_LOONGSON1 - default y if PLAT_ORION - default PCI - -# some non-PCI HCDs implement xHCI + bool + config USB_ARCH_HAS_XHCI - boolean - default PCI + bool menuconfig USB_SUPPORT bool "USB support" @@ -71,19 +27,8 @@ config USB_COMMON default y depends on USB || USB_GADGET -# Host-side USB depends on having a host controller -# NOTE: dummy_hcd is always an option, but it's ignored here ... -# NOTE: SL-811 option should be board-specific ... config USB_ARCH_HAS_HCD - boolean - default y if USB_ARCH_HAS_OHCI - default y if USB_ARCH_HAS_EHCI - default y if USB_ARCH_HAS_XHCI - default y if PCMCIA && !M32R # sl811_cs - default y if ARM # SL-811 - default y if BLACKFIN # SL-811 - default y if SUPERH # r8a66597-hcd - default PCI + def_bool y # ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface. config USB diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index c41feba..238c5d4 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -25,6 +25,7 @@ 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/ obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ diff --git a/drivers/usb/chipidea/ci13xxx_imx.c b/drivers/usb/chipidea/ci13xxx_imx.c index 73f9d5f..585099a 100644 --- a/drivers/usb/chipidea/ci13xxx_imx.c +++ b/drivers/usb/chipidea/ci13xxx_imx.c @@ -244,8 +244,6 @@ static int ci13xxx_imx_remove(struct platform_device *pdev) clk_disable_unprepare(data->clk); - platform_set_drvdata(pdev, NULL); - return 0; } diff --git a/drivers/usb/chipidea/ci13xxx_pci.c b/drivers/usb/chipidea/ci13xxx_pci.c index 4e1fc61..a2a6ac8 100644 --- a/drivers/usb/chipidea/ci13xxx_pci.c +++ b/drivers/usb/chipidea/ci13xxx_pci.c @@ -71,7 +71,6 @@ static int ci13xxx_pci_probe(struct pci_dev *pdev, goto disable_device; } - pci_set_power_state(pdev, PCI_D0); pci_set_master(pdev); pci_try_set_mwi(pdev); diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 4c5506a..609dbc2 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -31,6 +31,8 @@ #include <linux/usb/tmc.h> +#define RIGOL 1 +#define USBTMC_HEADER_SIZE 12 #define USBTMC_MINOR_BASE 176 /* @@ -84,6 +86,8 @@ struct usbtmc_device_data { u8 bTag_last_write; /* needed for abort */ u8 bTag_last_read; /* needed for abort */ + u8 rigol_quirk; + /* attributes from the USB TMC spec for this device */ u8 TermChar; bool TermCharEnabled; @@ -97,6 +101,16 @@ struct usbtmc_device_data { }; #define to_usbtmc_data(d) container_of(d, struct usbtmc_device_data, kref) +struct usbtmc_ID_rigol_quirk { + __u16 idVendor; + __u16 idProduct; +}; + +static const struct usbtmc_ID_rigol_quirk usbtmc_id_quirk[] = { + { 0x1ab1, 0x0588 }, + { 0, 0 } +}; + /* Forward declarations */ static struct usb_driver usbtmc_driver; @@ -361,6 +375,59 @@ exit: return rv; } +/* + * Sends a REQUEST_DEV_DEP_MSG_IN message on the Bulk-IN endpoint. + * @transfer_size: number of bytes to request from the device. + * + * See the USBTMC specification, Table 4. + * + * Also updates bTag_last_write. + */ +static int send_request_dev_dep_msg_in(struct usbtmc_device_data *data, size_t transfer_size) +{ + int retval; + u8 buffer[USBTMC_HEADER_SIZE]; + int actual; + + /* Setup IO buffer for REQUEST_DEV_DEP_MSG_IN message + * Refer to class specs for details + */ + buffer[0] = 2; + buffer[1] = data->bTag; + buffer[2] = ~(data->bTag); + buffer[3] = 0; /* Reserved */ + buffer[4] = (transfer_size) & 255; + buffer[5] = ((transfer_size) >> 8) & 255; + buffer[6] = ((transfer_size) >> 16) & 255; + buffer[7] = ((transfer_size) >> 24) & 255; + buffer[8] = data->TermCharEnabled * 2; + /* Use term character? */ + buffer[9] = data->TermChar; + buffer[10] = 0; /* Reserved */ + buffer[11] = 0; /* Reserved */ + + /* Send bulk URB */ + retval = usb_bulk_msg(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, + data->bulk_out), + buffer, USBTMC_HEADER_SIZE, &actual, USBTMC_TIMEOUT); + + /* Store bTag (in case we need to abort) */ + data->bTag_last_write = data->bTag; + + /* Increment bTag -- and increment again if zero */ + data->bTag++; + if (!data->bTag) + (data->bTag)++; + + if (retval < 0) { + dev_err(&data->intf->dev, "usb_bulk_msg in send_request_dev_dep_msg_in() returned %d\n", retval); + return retval; + } + + return 0; +} + static ssize_t usbtmc_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { @@ -388,51 +455,39 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, goto exit; } - remaining = count; - done = 0; + if (data->rigol_quirk) { + dev_dbg(dev, "usb_bulk_msg_in: count(%zu)\n", count); - while (remaining > 0) { - if (remaining > USBTMC_SIZE_IOBUFFER - 12 - 3) - this_part = USBTMC_SIZE_IOBUFFER - 12 - 3; - else - this_part = remaining; + retval = send_request_dev_dep_msg_in(data, count); - /* Setup IO buffer for DEV_DEP_MSG_IN message - * Refer to class specs for details - */ - buffer[0] = 2; - buffer[1] = data->bTag; - buffer[2] = ~(data->bTag); - buffer[3] = 0; /* Reserved */ - buffer[4] = (this_part) & 255; - buffer[5] = ((this_part) >> 8) & 255; - buffer[6] = ((this_part) >> 16) & 255; - buffer[7] = ((this_part) >> 24) & 255; - buffer[8] = data->TermCharEnabled * 2; - /* Use term character? */ - buffer[9] = data->TermChar; - buffer[10] = 0; /* Reserved */ - buffer[11] = 0; /* Reserved */ + if (retval < 0) { + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } + } - /* Send bulk URB */ - retval = usb_bulk_msg(data->usb_dev, - usb_sndbulkpipe(data->usb_dev, - data->bulk_out), - buffer, 12, &actual, USBTMC_TIMEOUT); + /* Loop until we have fetched everything we requested */ + remaining = count; + this_part = remaining; + done = 0; - /* Store bTag (in case we need to abort) */ - data->bTag_last_write = data->bTag; + while (remaining > 0) { + if (!(data->rigol_quirk)) { + dev_dbg(dev, "usb_bulk_msg_in: remaining(%zu), count(%zu)\n", remaining, count); - /* Increment bTag -- and increment again if zero */ - data->bTag++; - if (!data->bTag) - (data->bTag)++; + if (remaining > USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE - 3) + this_part = USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE - 3; + else + this_part = remaining; - if (retval < 0) { + retval = send_request_dev_dep_msg_in(data, this_part); + if (retval < 0) { dev_err(dev, "usb_bulk_msg returned %d\n", retval); - if (data->auto_abort) - usbtmc_ioctl_abort_bulk_out(data); - goto exit; + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } } /* Send bulk URB */ @@ -442,51 +497,109 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, buffer, USBTMC_SIZE_IOBUFFER, &actual, USBTMC_TIMEOUT); + dev_dbg(dev, "usb_bulk_msg: retval(%u), done(%zu), remaining(%zu), actual(%d)\n", retval, done, remaining, actual); + /* Store bTag (in case we need to abort) */ data->bTag_last_read = data->bTag; if (retval < 0) { - dev_err(dev, "Unable to read data, error %d\n", retval); + dev_dbg(dev, "Unable to read data, error %d\n", retval); if (data->auto_abort) usbtmc_ioctl_abort_bulk_in(data); goto exit; } - /* How many characters did the instrument send? */ - n_characters = buffer[4] + - (buffer[5] << 8) + - (buffer[6] << 16) + - (buffer[7] << 24); + /* Parse header in first packet */ + if ((done == 0) || (!(data->rigol_quirk))) { + /* Sanity checks for the header */ + if (actual < USBTMC_HEADER_SIZE) { + dev_err(dev, "Device sent too small first packet: %u < %u\n", actual, USBTMC_HEADER_SIZE); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } - /* Ensure the instrument doesn't lie about it */ - if(n_characters > actual - 12) { - dev_err(dev, "Device lies about message size: %u > %d\n", n_characters, actual - 12); - n_characters = actual - 12; - } + if (buffer[0] != 2) { + dev_err(dev, "Device sent reply with wrong MsgID: %u != 2\n", buffer[0]); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } - /* Ensure the instrument doesn't send more back than requested */ - if(n_characters > this_part) { - dev_err(dev, "Device returns more than requested: %zu > %zu\n", done + n_characters, done + this_part); - n_characters = this_part; - } + if (buffer[1] != data->bTag_last_write) { + dev_err(dev, "Device sent reply with wrong bTag: %u != %u\n", buffer[1], data->bTag_last_write); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } - /* Bound amount of data received by amount of data requested */ - if (n_characters > this_part) - n_characters = this_part; + /* How many characters did the instrument send? */ + n_characters = buffer[4] + + (buffer[5] << 8) + + (buffer[6] << 16) + + (buffer[7] << 24); - /* Copy buffer to user space */ - if (copy_to_user(buf + done, &buffer[12], n_characters)) { - /* There must have been an addressing problem */ - retval = -EFAULT; - goto exit; + if (n_characters > this_part) { + dev_err(dev, "Device wants to return more data than requested: %u > %zu\n", n_characters, count); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + /* Remove the USBTMC header */ + actual -= USBTMC_HEADER_SIZE; + + /* Check if the message is smaller than requested */ + if (data->rigol_quirk) { + if (remaining > n_characters) + remaining = n_characters; + /* Remove padding if it exists */ + if (actual > remaining) + actual = remaining; + } + else { + if (this_part > n_characters) + this_part = n_characters; + /* Remove padding if it exists */ + if (actual > this_part) + actual = this_part; + } + + dev_dbg(dev, "Bulk-IN header: N_characters(%u), bTransAttr(%u)\n", n_characters, buffer[8]); + + remaining -= actual; + + /* Terminate if end-of-message bit received from device */ + if ((buffer[8] & 0x01) && (actual >= n_characters)) + remaining = 0; + + dev_dbg(dev, "Bulk-IN header: remaining(%zu), buf(%p), buffer(%p) done(%zu)\n", remaining,buf,buffer,done); + + + /* Copy buffer to user space */ + if (copy_to_user(buf + done, &buffer[USBTMC_HEADER_SIZE], actual)) { + /* There must have been an addressing problem */ + retval = -EFAULT; + goto exit; + } + done += actual; } + else { + if (actual > remaining) + actual = remaining; + + remaining -= actual; + + dev_dbg(dev, "Bulk-IN header cont: actual(%u), done(%zu), remaining(%zu), buf(%p), buffer(%p)\n", actual, done, remaining,buf,buffer); - done += n_characters; - /* Terminate if end-of-message bit received from device */ - if ((buffer[8] & 0x01) && (actual >= n_characters + 12)) - remaining = 0; - else - remaining -= n_characters; + /* Copy buffer to user space */ + if (copy_to_user(buf + done, buffer, actual)) { + /* There must have been an addressing problem */ + retval = -EFAULT; + goto exit; + } + done += actual; + } } /* Update file position value */ @@ -527,8 +640,8 @@ static ssize_t usbtmc_write(struct file *filp, const char __user *buf, done = 0; while (remaining > 0) { - if (remaining > USBTMC_SIZE_IOBUFFER - 12) { - this_part = USBTMC_SIZE_IOBUFFER - 12; + if (remaining > USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE) { + this_part = USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE; buffer[8] = 0; } else { this_part = remaining; @@ -549,13 +662,13 @@ static ssize_t usbtmc_write(struct file *filp, const char __user *buf, buffer[10] = 0; /* Reserved */ buffer[11] = 0; /* Reserved */ - if (copy_from_user(&buffer[12], buf + done, this_part)) { + if (copy_from_user(&buffer[USBTMC_HEADER_SIZE], buf + done, this_part)) { retval = -EFAULT; goto exit; } - n_bytes = roundup(12 + this_part, 4); - memset(buffer + 12 + this_part, 0, n_bytes - (12 + this_part)); + n_bytes = roundup(USBTMC_HEADER_SIZE + this_part, 4); + memset(buffer + USBTMC_HEADER_SIZE + this_part, 0, n_bytes - (USBTMC_HEADER_SIZE + this_part)); do { retval = usb_bulk_msg(data->usb_dev, @@ -1003,6 +1116,20 @@ static int usbtmc_probe(struct usb_interface *intf, mutex_init(&data->io_mutex); data->zombie = 0; + /* Determine if it is a Rigol or not */ + data->rigol_quirk = 0; + dev_dbg(&intf->dev, "Trying to find if device Vendor 0x%04X Product 0x%04X has the RIGOL quirk\n", + data->usb_dev->descriptor.idVendor, + data->usb_dev->descriptor.idProduct); + for(n = 0; usbtmc_id_quirk[n].idVendor > 0; n++) { + if ((usbtmc_id_quirk[n].idVendor == data->usb_dev->descriptor.idVendor) && + (usbtmc_id_quirk[n].idProduct == data->usb_dev->descriptor.idProduct)) { + dev_dbg(&intf->dev, "Setting this device as having the RIGOL quirk\n"); + data->rigol_quirk = 1; + break; + } + } + /* Initialize USBTMC bTag and other fields */ data->bTag = 1; data->TermCharEnabled = 0; diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index c88c4fb..0598650 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -49,14 +49,14 @@ #include <linux/security.h> #include <linux/user_namespace.h> #include <linux/scatterlist.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> #include <asm/byteorder.h> #include <linux/moduleparam.h> #include "usb.h" #define USB_MAXBUS 64 -#define USB_DEVICE_MAX USB_MAXBUS * 128 +#define USB_DEVICE_MAX (USB_MAXBUS * 128) #define USB_SG_SIZE 16384 /* split-size for large txs */ /* Mutual exclusion for removal, open, and release */ @@ -1804,7 +1804,8 @@ static int proc_ioctl(struct dev_state *ps, struct usbdevfs_ioctl *ctl) /* alloc buffer */ if ((size = _IOC_SIZE(ctl->ioctl_code)) > 0) { - if ((buf = kmalloc(size, GFP_KERNEL)) == NULL) + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) return -ENOMEM; if ((_IOC_DIR(ctl->ioctl_code) & _IOC_WRITE)) { if (copy_from_user(buf, ctl->data, size)) { diff --git a/drivers/usb/core/file.c b/drivers/usb/core/file.c index e5387a4..6a4c407 100644 --- a/drivers/usb/core/file.c +++ b/drivers/usb/core/file.c @@ -94,7 +94,7 @@ static int init_usb_class(void) kref_init(&usb_class->kref); usb_class->class = class_create(THIS_MODULE, "usbmisc"); if (IS_ERR(usb_class->class)) { - result = IS_ERR(usb_class->class); + result = PTR_ERR(usb_class->class); printk(KERN_ERR "class_create failed for usb devices\n"); kfree(usb_class); usb_class = NULL; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index d53547d..014dc99 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -149,6 +149,27 @@ static const u8 usb3_rh_dev_descriptor[18] = { 0x01 /* __u8 bNumConfigurations; */ }; +/* usb 2.5 (wireless USB 1.0) root hub device descriptor */ +static const u8 usb25_rh_dev_descriptor[18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x50, 0x02, /* __le16 bcdUSB; v2.5 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x00, /* __u8 bDeviceProtocol; [ usb 2.0 no TT ] */ + 0xFF, /* __u8 bMaxPacketSize0; always 0xFF (WUSB Spec 7.4.1). */ + + 0x6b, 0x1d, /* __le16 idVendor; Linux Foundation 0x1d6b */ + 0x02, 0x00, /* __le16 idProduct; device 0x0002 */ + KERNEL_VER, KERNEL_REL, /* __le16 bcdDevice */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + /* usb 2.0 root hub device descriptor */ static const u8 usb2_rh_dev_descriptor [18] = { 0x12, /* __u8 bLength; */ @@ -527,6 +548,9 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) case HCD_USB3: bufp = usb3_rh_dev_descriptor; break; + case HCD_USB25: + bufp = usb25_rh_dev_descriptor; + break; case HCD_USB2: bufp = usb2_rh_dev_descriptor; break; @@ -546,6 +570,7 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) bufp = ss_rh_config_descriptor; len = sizeof ss_rh_config_descriptor; break; + case HCD_USB25: case HCD_USB2: bufp = hs_rh_config_descriptor; len = sizeof hs_rh_config_descriptor; @@ -2511,6 +2536,9 @@ int usb_add_hcd(struct usb_hcd *hcd, case HCD_USB2: rhdev->speed = USB_SPEED_HIGH; break; + case HCD_USB25: + rhdev->speed = USB_SPEED_WIRELESS; + break; case HCD_USB3: rhdev->speed = USB_SPEED_SUPER; break; diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 444d30e..e7ee1e4 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -252,7 +252,7 @@ static void sg_clean(struct usb_sg_request *io) { if (io->urbs) { while (io->entries--) - usb_free_urb(io->urbs [io->entries]); + usb_free_urb(io->urbs[io->entries]); kfree(io->urbs); io->urbs = NULL; } @@ -300,10 +300,10 @@ static void sg_complete(struct urb *urb) */ spin_unlock(&io->lock); for (i = 0, found = 0; i < io->entries; i++) { - if (!io->urbs [i] || !io->urbs [i]->dev) + if (!io->urbs[i] || !io->urbs[i]->dev) continue; if (found) { - retval = usb_unlink_urb(io->urbs [i]); + retval = usb_unlink_urb(io->urbs[i]); if (retval != -EINPROGRESS && retval != -ENODEV && retval != -EBUSY && @@ -311,7 +311,7 @@ static void sg_complete(struct urb *urb) dev_err(&io->dev->dev, "%s, unlink --> %d\n", __func__, retval); - } else if (urb == io->urbs [i]) + } else if (urb == io->urbs[i]) found = 1; } spin_lock(&io->lock); @@ -379,7 +379,7 @@ int usb_sg_init(struct usb_sg_request *io, struct usb_device *dev, } /* initialize all the urbs we'll use */ - io->urbs = kmalloc(io->entries * sizeof *io->urbs, mem_flags); + io->urbs = kmalloc(io->entries * sizeof(*io->urbs), mem_flags); if (!io->urbs) goto nomem; @@ -511,7 +511,7 @@ void usb_sg_wait(struct usb_sg_request *io) int retval; io->urbs[i]->dev = io->dev; - retval = usb_submit_urb(io->urbs [i], GFP_ATOMIC); + retval = usb_submit_urb(io->urbs[i], GFP_ATOMIC); /* after we submit, let completions or cancelations fire; * we handshake using io->status. @@ -586,9 +586,9 @@ void usb_sg_cancel(struct usb_sg_request *io) for (i = 0; i < io->entries; i++) { int retval; - if (!io->urbs [i]->dev) + if (!io->urbs[i]->dev) continue; - retval = usb_unlink_urb(io->urbs [i]); + retval = usb_unlink_urb(io->urbs[i]); if (retval != -EINPROGRESS && retval != -ENODEV && retval != -EBUSY diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index aa38db4..d9284b9 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -497,8 +497,62 @@ set_usb2_hardware_lpm(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(usb2_hardware_lpm, S_IRUGO | S_IWUSR, show_usb2_hardware_lpm, set_usb2_hardware_lpm); +static ssize_t +show_usb2_lpm_l1_timeout(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->l1_params.timeout); +} + +static ssize_t +set_usb2_lpm_l1_timeout(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + u16 timeout; + + if (kstrtou16(buf, 0, &timeout)) + return -EINVAL; + + udev->l1_params.timeout = timeout; + + return count; +} + +static DEVICE_ATTR(usb2_lpm_l1_timeout, S_IRUGO | S_IWUSR, + show_usb2_lpm_l1_timeout, set_usb2_lpm_l1_timeout); + +static ssize_t +show_usb2_lpm_besl(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->l1_params.besl); +} + +static ssize_t +set_usb2_lpm_besl(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + u8 besl; + + if (kstrtou8(buf, 0, &besl) || besl > 15) + return -EINVAL; + + udev->l1_params.besl = besl; + + return count; +} + +static DEVICE_ATTR(usb2_lpm_besl, S_IRUGO | S_IWUSR, + show_usb2_lpm_besl, set_usb2_lpm_besl); + static struct attribute *usb2_hardware_lpm_attr[] = { &dev_attr_usb2_hardware_lpm.attr, + &dev_attr_usb2_lpm_l1_timeout.attr, + &dev_attr_usb2_lpm_besl.attr, NULL, }; static struct attribute_group usb2_hardware_lpm_attr_group = { diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index b10da72..7dad603 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -209,6 +209,39 @@ struct usb_interface *usb_find_interface(struct usb_driver *drv, int minor) } EXPORT_SYMBOL_GPL(usb_find_interface); +struct each_dev_arg { + void *data; + int (*fn)(struct usb_device *, void *); +}; + +static int __each_dev(struct device *dev, void *data) +{ + struct each_dev_arg *arg = (struct each_dev_arg *)data; + + /* There are struct usb_interface on the same bus, filter them out */ + if (!is_usb_device(dev)) + return 0; + + return arg->fn(container_of(dev, struct usb_device, dev), arg->data); +} + +/** + * usb_for_each_dev - iterate over all USB devices in the system + * @data: data pointer that will be handed to the callback function + * @fn: callback function to be called for each USB device + * + * Iterate over all USB devices and call @fn for each, passing it @data. If it + * returns anything other than 0, we break the iteration prematurely and return + * that value. + */ +int usb_for_each_dev(void *data, int (*fn)(struct usb_device *, void *)) +{ + struct each_dev_arg arg = {data, fn}; + + return bus_for_each_dev(&usb_bus_type, NULL, &arg, __each_dev); +} +EXPORT_SYMBOL_GPL(usb_for_each_dev); + /** * usb_release_dev - free a usb device structure when all users of it are finished. * @dev: device that's been disconnected diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c index 34638b9..077f110b 100644 --- a/drivers/usb/dwc3/dwc3-omap.c +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -61,21 +61,46 @@ #define USBOTGSS_REVISION 0x0000 #define USBOTGSS_SYSCONFIG 0x0010 #define USBOTGSS_IRQ_EOI 0x0020 +#define USBOTGSS_EOI_OFFSET 0x0008 #define USBOTGSS_IRQSTATUS_RAW_0 0x0024 #define USBOTGSS_IRQSTATUS_0 0x0028 #define USBOTGSS_IRQENABLE_SET_0 0x002c #define USBOTGSS_IRQENABLE_CLR_0 0x0030 -#define USBOTGSS_IRQSTATUS_RAW_1 0x0034 -#define USBOTGSS_IRQSTATUS_1 0x0038 -#define USBOTGSS_IRQENABLE_SET_1 0x003c -#define USBOTGSS_IRQENABLE_CLR_1 0x0040 +#define USBOTGSS_IRQ0_OFFSET 0x0004 +#define USBOTGSS_IRQSTATUS_RAW_1 0x0030 +#define USBOTGSS_IRQSTATUS_1 0x0034 +#define USBOTGSS_IRQENABLE_SET_1 0x0038 +#define USBOTGSS_IRQENABLE_CLR_1 0x003c +#define USBOTGSS_IRQSTATUS_RAW_2 0x0040 +#define USBOTGSS_IRQSTATUS_2 0x0044 +#define USBOTGSS_IRQENABLE_SET_2 0x0048 +#define USBOTGSS_IRQENABLE_CLR_2 0x004c +#define USBOTGSS_IRQSTATUS_RAW_3 0x0050 +#define USBOTGSS_IRQSTATUS_3 0x0054 +#define USBOTGSS_IRQENABLE_SET_3 0x0058 +#define USBOTGSS_IRQENABLE_CLR_3 0x005c +#define USBOTGSS_IRQSTATUS_EOI_MISC 0x0030 +#define USBOTGSS_IRQSTATUS_RAW_MISC 0x0034 +#define USBOTGSS_IRQSTATUS_MISC 0x0038 +#define USBOTGSS_IRQENABLE_SET_MISC 0x003c +#define USBOTGSS_IRQENABLE_CLR_MISC 0x0040 +#define USBOTGSS_IRQMISC_OFFSET 0x03fc #define USBOTGSS_UTMI_OTG_CTRL 0x0080 #define USBOTGSS_UTMI_OTG_STATUS 0x0084 +#define USBOTGSS_UTMI_OTG_OFFSET 0x0480 +#define USBOTGSS_TXFIFO_DEPTH 0x0508 +#define USBOTGSS_RXFIFO_DEPTH 0x050c #define USBOTGSS_MMRAM_OFFSET 0x0100 #define USBOTGSS_FLADJ 0x0104 #define USBOTGSS_DEBUG_CFG 0x0108 #define USBOTGSS_DEBUG_DATA 0x010c +#define USBOTGSS_DEV_EBC_EN 0x0110 +#define USBOTGSS_DEBUG_OFFSET 0x0600 +/* REVISION REGISTER */ +#define USBOTGSS_REVISION_XMAJOR(reg) ((reg >> 8) & 0x7) +#define USBOTGSS_REVISION_XMAJOR1 1 +#define USBOTGSS_REVISION_XMAJOR2 2 /* SYSCONFIG REGISTER */ #define USBOTGSS_SYSCONFIG_DMADISABLE (1 << 16) @@ -85,17 +110,17 @@ /* IRQS0 BITS */ #define USBOTGSS_IRQO_COREIRQ_ST (1 << 0) -/* IRQ1 BITS */ -#define USBOTGSS_IRQ1_DMADISABLECLR (1 << 17) -#define USBOTGSS_IRQ1_OEVT (1 << 16) -#define USBOTGSS_IRQ1_DRVVBUS_RISE (1 << 13) -#define USBOTGSS_IRQ1_CHRGVBUS_RISE (1 << 12) -#define USBOTGSS_IRQ1_DISCHRGVBUS_RISE (1 << 11) -#define USBOTGSS_IRQ1_IDPULLUP_RISE (1 << 8) -#define USBOTGSS_IRQ1_DRVVBUS_FALL (1 << 5) -#define USBOTGSS_IRQ1_CHRGVBUS_FALL (1 << 4) -#define USBOTGSS_IRQ1_DISCHRGVBUS_FALL (1 << 3) -#define USBOTGSS_IRQ1_IDPULLUP_FALL (1 << 0) +/* IRQMISC BITS */ +#define USBOTGSS_IRQMISC_DMADISABLECLR (1 << 17) +#define USBOTGSS_IRQMISC_OEVT (1 << 16) +#define USBOTGSS_IRQMISC_DRVVBUS_RISE (1 << 13) +#define USBOTGSS_IRQMISC_CHRGVBUS_RISE (1 << 12) +#define USBOTGSS_IRQMISC_DISCHRGVBUS_RISE (1 << 11) +#define USBOTGSS_IRQMISC_IDPULLUP_RISE (1 << 8) +#define USBOTGSS_IRQMISC_DRVVBUS_FALL (1 << 5) +#define USBOTGSS_IRQMISC_CHRGVBUS_FALL (1 << 4) +#define USBOTGSS_IRQMISC_DISCHRGVBUS_FALL (1 << 3) +#define USBOTGSS_IRQMISC_IDPULLUP_FALL (1 << 0) /* UTMI_OTG_CTRL REGISTER */ #define USBOTGSS_UTMI_OTG_CTRL_DRVVBUS (1 << 5) @@ -122,6 +147,12 @@ struct dwc3_omap { void __iomem *base; u32 utmi_otg_status; + u32 utmi_otg_offset; + u32 irqmisc_offset; + u32 irq_eoi_offset; + u32 debug_offset; + u32 irq0_offset; + u32 revision; u32 dma_status:1; }; @@ -138,6 +169,58 @@ static inline void dwc3_omap_writel(void __iomem *base, u32 offset, u32 value) writel(value, base + offset); } +static u32 dwc3_omap_read_utmi_status(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS + + omap->utmi_otg_offset); +} + +static void dwc3_omap_write_utmi_status(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS + + omap->utmi_otg_offset, value); + +} + +static u32 dwc3_omap_read_irq0_status(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_0 - + omap->irq0_offset); +} + +static void dwc3_omap_write_irq0_status(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_0 - + omap->irq0_offset, value); + +} + +static u32 dwc3_omap_read_irqmisc_status(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_MISC + + omap->irqmisc_offset); +} + +static void dwc3_omap_write_irqmisc_status(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_MISC + + omap->irqmisc_offset, value); + +} + +static void dwc3_omap_write_irqmisc_set(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_MISC + + omap->irqmisc_offset, value); + +} + +static void dwc3_omap_write_irq0_set(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_0 - + omap->irq0_offset, value); +} + int dwc3_omap_mailbox(enum omap_dwc3_vbus_id_status status) { u32 val; @@ -150,38 +233,38 @@ int dwc3_omap_mailbox(enum omap_dwc3_vbus_id_status status) case OMAP_DWC3_ID_GROUND: dev_dbg(omap->dev, "ID GND\n"); - val = dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS); + val = dwc3_omap_read_utmi_status(omap); val &= ~(USBOTGSS_UTMI_OTG_STATUS_IDDIG | USBOTGSS_UTMI_OTG_STATUS_VBUSVALID | USBOTGSS_UTMI_OTG_STATUS_SESSEND); val |= USBOTGSS_UTMI_OTG_STATUS_SESSVALID | USBOTGSS_UTMI_OTG_STATUS_POWERPRESENT; - dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, val); + dwc3_omap_write_utmi_status(omap, val); break; case OMAP_DWC3_VBUS_VALID: dev_dbg(omap->dev, "VBUS Connect\n"); - val = dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS); + val = dwc3_omap_read_utmi_status(omap); val &= ~USBOTGSS_UTMI_OTG_STATUS_SESSEND; val |= USBOTGSS_UTMI_OTG_STATUS_IDDIG | USBOTGSS_UTMI_OTG_STATUS_VBUSVALID | USBOTGSS_UTMI_OTG_STATUS_SESSVALID | USBOTGSS_UTMI_OTG_STATUS_POWERPRESENT; - dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, val); + dwc3_omap_write_utmi_status(omap, val); break; case OMAP_DWC3_ID_FLOAT: case OMAP_DWC3_VBUS_OFF: dev_dbg(omap->dev, "VBUS Disconnect\n"); - val = dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS); + val = dwc3_omap_read_utmi_status(omap); val &= ~(USBOTGSS_UTMI_OTG_STATUS_SESSVALID | USBOTGSS_UTMI_OTG_STATUS_VBUSVALID | USBOTGSS_UTMI_OTG_STATUS_POWERPRESENT); val |= USBOTGSS_UTMI_OTG_STATUS_SESSEND | USBOTGSS_UTMI_OTG_STATUS_IDDIG; - dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, val); + dwc3_omap_write_utmi_status(omap, val); break; default: @@ -199,44 +282,45 @@ static irqreturn_t dwc3_omap_interrupt(int irq, void *_omap) spin_lock(&omap->lock); - reg = dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_1); + reg = dwc3_omap_read_irqmisc_status(omap); - if (reg & USBOTGSS_IRQ1_DMADISABLECLR) { + if (reg & USBOTGSS_IRQMISC_DMADISABLECLR) { dev_dbg(omap->dev, "DMA Disable was Cleared\n"); omap->dma_status = false; } - if (reg & USBOTGSS_IRQ1_OEVT) + if (reg & USBOTGSS_IRQMISC_OEVT) dev_dbg(omap->dev, "OTG Event\n"); - if (reg & USBOTGSS_IRQ1_DRVVBUS_RISE) + if (reg & USBOTGSS_IRQMISC_DRVVBUS_RISE) dev_dbg(omap->dev, "DRVVBUS Rise\n"); - if (reg & USBOTGSS_IRQ1_CHRGVBUS_RISE) + if (reg & USBOTGSS_IRQMISC_CHRGVBUS_RISE) dev_dbg(omap->dev, "CHRGVBUS Rise\n"); - if (reg & USBOTGSS_IRQ1_DISCHRGVBUS_RISE) + if (reg & USBOTGSS_IRQMISC_DISCHRGVBUS_RISE) dev_dbg(omap->dev, "DISCHRGVBUS Rise\n"); - if (reg & USBOTGSS_IRQ1_IDPULLUP_RISE) + if (reg & USBOTGSS_IRQMISC_IDPULLUP_RISE) dev_dbg(omap->dev, "IDPULLUP Rise\n"); - if (reg & USBOTGSS_IRQ1_DRVVBUS_FALL) + if (reg & USBOTGSS_IRQMISC_DRVVBUS_FALL) dev_dbg(omap->dev, "DRVVBUS Fall\n"); - if (reg & USBOTGSS_IRQ1_CHRGVBUS_FALL) + if (reg & USBOTGSS_IRQMISC_CHRGVBUS_FALL) dev_dbg(omap->dev, "CHRGVBUS Fall\n"); - if (reg & USBOTGSS_IRQ1_DISCHRGVBUS_FALL) + if (reg & USBOTGSS_IRQMISC_DISCHRGVBUS_FALL) dev_dbg(omap->dev, "DISCHRGVBUS Fall\n"); - if (reg & USBOTGSS_IRQ1_IDPULLUP_FALL) + if (reg & USBOTGSS_IRQMISC_IDPULLUP_FALL) dev_dbg(omap->dev, "IDPULLUP Fall\n"); - dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_1, reg); + dwc3_omap_write_irqmisc_status(omap, reg); + + reg = dwc3_omap_read_irq0_status(omap); - reg = dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_0); - dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_0, reg); + dwc3_omap_write_irq0_status(omap, reg); spin_unlock(&omap->lock); @@ -258,26 +342,26 @@ static void dwc3_omap_enable_irqs(struct dwc3_omap *omap) /* enable all IRQs */ reg = USBOTGSS_IRQO_COREIRQ_ST; - dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_0, reg); - - reg = (USBOTGSS_IRQ1_OEVT | - USBOTGSS_IRQ1_DRVVBUS_RISE | - USBOTGSS_IRQ1_CHRGVBUS_RISE | - USBOTGSS_IRQ1_DISCHRGVBUS_RISE | - USBOTGSS_IRQ1_IDPULLUP_RISE | - USBOTGSS_IRQ1_DRVVBUS_FALL | - USBOTGSS_IRQ1_CHRGVBUS_FALL | - USBOTGSS_IRQ1_DISCHRGVBUS_FALL | - USBOTGSS_IRQ1_IDPULLUP_FALL); - - dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_1, reg); + dwc3_omap_write_irq0_set(omap, reg); + + reg = (USBOTGSS_IRQMISC_OEVT | + USBOTGSS_IRQMISC_DRVVBUS_RISE | + USBOTGSS_IRQMISC_CHRGVBUS_RISE | + USBOTGSS_IRQMISC_DISCHRGVBUS_RISE | + USBOTGSS_IRQMISC_IDPULLUP_RISE | + USBOTGSS_IRQMISC_DRVVBUS_FALL | + USBOTGSS_IRQMISC_CHRGVBUS_FALL | + USBOTGSS_IRQMISC_DISCHRGVBUS_FALL | + USBOTGSS_IRQMISC_IDPULLUP_FALL); + + dwc3_omap_write_irqmisc_set(omap, reg); } static void dwc3_omap_disable_irqs(struct dwc3_omap *omap) { /* disable all IRQs */ - dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_1, 0x00); - dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_0, 0x00); + dwc3_omap_write_irqmisc_set(omap, 0x00); + dwc3_omap_write_irq0_set(omap, 0x00); } static u64 dwc3_omap_dma_mask = DMA_BIT_MASK(32); @@ -294,6 +378,7 @@ static int dwc3_omap_probe(struct platform_device *pdev) int irq; int utmi_mode = 0; + int x_major; u32 reg; @@ -347,10 +432,46 @@ static int dwc3_omap_probe(struct platform_device *pdev) ret = pm_runtime_get_sync(dev); if (ret < 0) { dev_err(dev, "get_sync failed with err %d\n", ret); - return ret; + goto err0; } - reg = dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_STATUS); + reg = dwc3_omap_readl(omap->base, USBOTGSS_REVISION); + omap->revision = reg; + x_major = USBOTGSS_REVISION_XMAJOR(reg); + + /* Differentiate between OMAP5 and AM437x */ + switch (x_major) { + case USBOTGSS_REVISION_XMAJOR1: + case USBOTGSS_REVISION_XMAJOR2: + omap->irq_eoi_offset = 0; + omap->irq0_offset = 0; + omap->irqmisc_offset = 0; + omap->utmi_otg_offset = 0; + omap->debug_offset = 0; + break; + default: + /* Default to the latest revision */ + omap->irq_eoi_offset = USBOTGSS_EOI_OFFSET; + omap->irq0_offset = USBOTGSS_IRQ0_OFFSET; + omap->irqmisc_offset = USBOTGSS_IRQMISC_OFFSET; + omap->utmi_otg_offset = USBOTGSS_UTMI_OTG_OFFSET; + omap->debug_offset = USBOTGSS_DEBUG_OFFSET; + break; + } + + /* For OMAP5(ES2.0) and AM437x x_major is 2 even though there are + * changes in wrapper registers, Using dt compatible for aegis + */ + + if (of_device_is_compatible(node, "ti,am437x-dwc3")) { + omap->irq_eoi_offset = USBOTGSS_EOI_OFFSET; + omap->irq0_offset = USBOTGSS_IRQ0_OFFSET; + omap->irqmisc_offset = USBOTGSS_IRQMISC_OFFSET; + omap->utmi_otg_offset = USBOTGSS_UTMI_OTG_OFFSET; + omap->debug_offset = USBOTGSS_DEBUG_OFFSET; + } + + reg = dwc3_omap_read_utmi_status(omap); of_property_read_u32(node, "utmi-mode", &utmi_mode); @@ -365,7 +486,7 @@ static int dwc3_omap_probe(struct platform_device *pdev) dev_dbg(dev, "UNKNOWN utmi mode %d\n", utmi_mode); } - dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, reg); + dwc3_omap_write_utmi_status(omap, reg); /* check the DMA Status */ reg = dwc3_omap_readl(omap->base, USBOTGSS_SYSCONFIG); @@ -376,7 +497,7 @@ static int dwc3_omap_probe(struct platform_device *pdev) if (ret) { dev_err(dev, "failed to request IRQ #%d --> %d\n", omap->irq, ret); - return ret; + goto err1; } dwc3_omap_enable_irqs(omap); @@ -384,10 +505,21 @@ static int dwc3_omap_probe(struct platform_device *pdev) ret = of_platform_populate(node, NULL, NULL, dev); if (ret) { dev_err(&pdev->dev, "failed to create dwc3 core\n"); - return ret; + goto err2; } return 0; + +err2: + dwc3_omap_disable_irqs(omap); + +err1: + pm_runtime_put_sync(dev); + +err0: + pm_runtime_disable(dev); + + return ret; } static int dwc3_omap_remove(struct platform_device *pdev) @@ -406,6 +538,9 @@ static const struct of_device_id of_dwc3_match[] = { { .compatible = "ti,dwc3" }, + { + .compatible = "ti,am437x-dwc3" + }, { }, }; MODULE_DEVICE_TABLE(of, of_dwc3_match); @@ -431,8 +566,7 @@ static int dwc3_omap_suspend(struct device *dev) { struct dwc3_omap *omap = dev_get_drvdata(dev); - omap->utmi_otg_status = dwc3_omap_readl(omap->base, - USBOTGSS_UTMI_OTG_STATUS); + omap->utmi_otg_status = dwc3_omap_read_utmi_status(omap); return 0; } @@ -441,8 +575,7 @@ static int dwc3_omap_resume(struct device *dev) { struct dwc3_omap *omap = dev_get_drvdata(dev); - dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_STATUS, - omap->utmi_otg_status); + dwc3_omap_write_utmi_status(omap, omap->utmi_otg_status); pm_runtime_disable(dev); pm_runtime_set_active(dev); diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index eba9e2b..ed07ec0 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -133,7 +133,6 @@ static int dwc3_pci_probe(struct pci_dev *pci, return -ENODEV; } - pci_set_power_state(pci, PCI_D0); pci_set_master(pci); ret = dwc3_pci_register_phys(glue); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index f41aa0d..2b2a11c 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -192,6 +192,16 @@ config USB_FUSB300 help Faraday usb device controller FUSB300 driver +config USB_FOTG210_UDC + tristate "Faraday FOTG210 USB Peripheral Controller" + help + Faraday USB2.0 OTG controller which can be configured as + high speed or full speed USB device. This driver supppors + Bulk Transfer so far. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "fotg210_udc". + config USB_OMAP tristate "OMAP USB Device Controller" depends on ARCH_OMAP1 @@ -334,14 +344,6 @@ config USB_MV_U3D # Controllers available in both integrated and discrete versions # -# musb builds in ../musb along with host support -config USB_GADGET_MUSB_HDRC - tristate "Inventra HDRC USB Peripheral (TI, ADI, ...)" - depends on USB_MUSB_HDRC - help - This OTG-capable silicon IP is used in dual designs including - the TI DaVinci, OMAP 243x, OMAP 343x, TUSB 6010, and ADI Blackfin - config USB_M66592 tristate "Renesas M66592 USB Peripheral Controller" help @@ -507,12 +509,36 @@ config USB_F_SS_LB config USB_U_SERIAL tristate +config USB_U_ETHER + tristate + +config USB_U_RNDIS + tristate + config USB_F_SERIAL tristate config USB_F_OBEX tristate +config USB_F_NCM + tristate + +config USB_F_ECM + tristate + +config USB_F_PHONET + tristate + +config USB_F_EEM + tristate + +config USB_F_SUBSET + tristate + +config USB_F_RNDIS + tristate + choice tristate "USB Gadget Drivers" default USB_ETH @@ -534,6 +560,57 @@ choice # this first set of drivers all depend on bulk-capable hardware. +config USB_CONFIGFS_ECM_SUBSET + boolean "Ethernet Control Model (CDC ECM) subset" + depends on USB_CONFIGFS + depends on NET + select USB_U_ETHER + select USB_F_SUBSET + help + On hardware that can't implement the full protocol, + a simple CDC subset is used, placing fewer demands on USB. + +config USB_CONFIGFS_RNDIS + bool "RNDIS" + depends on USB_CONFIGFS + depends on NET + select USB_U_ETHER + select USB_F_RNDIS + help + Microsoft Windows XP bundles the "Remote NDIS" (RNDIS) protocol, + and Microsoft provides redistributable binary RNDIS drivers for + older versions of Windows. + + To make MS-Windows work with this, use Documentation/usb/linux.inf + as the "driver info file". For versions of MS-Windows older than + XP, you'll need to download drivers from Microsoft's website; a URL + is given in comments found in that info file. + +config USB_CONFIGFS_EEM + bool "Ethernet Emulation Model (EEM)" + depends on USB_CONFIGFS + depends on NET + select USB_U_ETHER + select USB_F_EEM + help + CDC EEM is a newer USB standard that is somewhat simpler than CDC ECM + and therefore can be supported by more hardware. Technically ECM and + EEM are designed for different applications. The ECM model extends + the network interface to the target (e.g. a USB cable modem), and the + EEM model is for mobile devices to communicate with hosts using + ethernet over USB. For Linux gadgets, however, the interface with + the host is the same (a usbX device), so the differences are minimal. + +config USB_CONFIGFS_PHONET + boolean "Phonet protocol" + depends on USB_CONFIGFS + depends on NET + depends on PHONET + select USB_U_ETHER + select USB_F_PHONET + help + The Phonet protocol implementation for USB device. + config USB_ZERO tristate "Gadget Zero (DEVELOPMENT)" select USB_LIBCOMPOSITE @@ -603,6 +680,10 @@ config USB_ETH tristate "Ethernet Gadget (with CDC Ethernet support)" depends on NET select USB_LIBCOMPOSITE + select USB_U_ETHER + select USB_U_RNDIS + select USB_F_ECM + select USB_F_SUBSET select CRC32 help This driver implements Ethernet style communication, in one of @@ -639,6 +720,7 @@ config USB_ETH_RNDIS bool "RNDIS support" depends on USB_ETH select USB_LIBCOMPOSITE + select USB_F_RNDIS default y help Microsoft Windows XP bundles the "Remote NDIS" (RNDIS) protocol, @@ -658,6 +740,7 @@ config USB_ETH_EEM bool "Ethernet Emulation Model (EEM) support" depends on USB_ETH select USB_LIBCOMPOSITE + select USB_F_EEM default n help CDC EEM is a newer USB standard that is somewhat simpler than CDC ECM @@ -675,6 +758,8 @@ config USB_G_NCM tristate "Network Control Model (NCM) support" depends on NET select USB_LIBCOMPOSITE + select USB_U_ETHER + select USB_F_NCM select CRC32 help This driver implements USB CDC NCM subclass standard. NCM is @@ -718,6 +803,7 @@ config USB_FUNCTIONFS config USB_FUNCTIONFS_ETH bool "Include configuration with CDC ECM (Ethernet)" depends on USB_FUNCTIONFS && NET + select USB_U_ETHER help Include a configuration with CDC ECM function (Ethernet) and the Function Filesystem. @@ -725,6 +811,8 @@ config USB_FUNCTIONFS_ETH config USB_FUNCTIONFS_RNDIS bool "Include configuration with RNDIS (Ethernet)" depends on USB_FUNCTIONFS && NET + select USB_U_ETHER + select USB_U_RNDIS help Include a configuration with RNDIS function (Ethernet) and the Filesystem. @@ -825,7 +913,9 @@ config USB_CDC_COMPOSITE depends on NET select USB_LIBCOMPOSITE select USB_U_SERIAL + select USB_U_ETHER select USB_F_ACM + select USB_F_ECM help This driver provides two functions in one configuration: a CDC Ethernet (ECM) link, and a CDC ACM (serial port) link. @@ -842,7 +932,11 @@ config USB_G_NOKIA depends on PHONET select USB_LIBCOMPOSITE select USB_U_SERIAL + select USB_U_ETHER select USB_F_ACM + select USB_F_OBEX + select USB_F_PHONET + select USB_F_ECM help The Nokia composite gadget provides support for acm, obex and phonet in only one composite gadget driver. @@ -869,6 +963,8 @@ config USB_G_MULTI select USB_G_MULTI_CDC if !USB_G_MULTI_RNDIS select USB_LIBCOMPOSITE select USB_U_SERIAL + select USB_U_ETHER + select USB_U_RNDIS select USB_F_ACM help The Multifunction Composite Gadget provides Ethernet (RNDIS diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 6afd166..bad08e6 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_USB_EG20T) += pch_udc.o obj-$(CONFIG_USB_MV_UDC) += mv_udc.o mv_udc-y := mv_udc_core.o obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o +obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o # USB Functions @@ -45,6 +46,21 @@ usb_f_serial-y := f_serial.o obj-$(CONFIG_USB_F_SERIAL) += usb_f_serial.o usb_f_obex-y := f_obex.o obj-$(CONFIG_USB_F_OBEX) += usb_f_obex.o +obj-$(CONFIG_USB_U_ETHER) += u_ether.o +u_rndis-y := rndis.o +obj-$(CONFIG_USB_U_RNDIS) += u_rndis.o +usb_f_ncm-y := f_ncm.o +obj-$(CONFIG_USB_F_NCM) += usb_f_ncm.o +usb_f_ecm-y := f_ecm.o +obj-$(CONFIG_USB_F_ECM) += usb_f_ecm.o +usb_f_phonet-y := f_phonet.o +obj-$(CONFIG_USB_F_PHONET) += usb_f_phonet.o +usb_f_eem-y := f_eem.o +obj-$(CONFIG_USB_F_EEM) += usb_f_eem.o +usb_f_ecm_subset-y := f_subset.o +obj-$(CONFIG_USB_F_SUBSET) += usb_f_ecm_subset.o +usb_f_rndis-y := f_rndis.o +obj-$(CONFIG_USB_F_RNDIS) += usb_f_rndis.o # # USB gadget drivers diff --git a/drivers/usb/gadget/cdc2.c b/drivers/usb/gadget/cdc2.c index 2c52551..5a5acf2 100644 --- a/drivers/usb/gadget/cdc2.c +++ b/drivers/usb/gadget/cdc2.c @@ -15,6 +15,7 @@ #include "u_ether.h" #include "u_serial.h" +#include "u_ecm.h" #define DRIVER_DESC "CDC Composite Gadget" @@ -32,18 +33,9 @@ #define CDC_VENDOR_NUM 0x0525 /* NetChip */ #define CDC_PRODUCT_NUM 0xa4aa /* CDC Composite: ECM + ACM */ -/*-------------------------------------------------------------------------*/ USB_GADGET_COMPOSITE_OPTIONS(); -/* - * Kbuild is not very cooperative with respect to linking separately - * compiled library objects into one module. So for now we won't use - * separate compilation ... ensuring init/exit sections work to shrink - * the runtime footprint, and giving us at least some parts of what - * a "gcc --combine ... part1.c part2.c part3.c ... " build would. - */ -#include "f_ecm.c" -#include "u_ether.c" +USB_ETHERNET_MODULE_PARAMETERS(); /*-------------------------------------------------------------------------*/ @@ -102,12 +94,13 @@ static struct usb_gadget_strings *dev_strings[] = { NULL, }; -static u8 hostaddr[ETH_ALEN]; -static struct eth_dev *the_dev; /*-------------------------------------------------------------------------*/ static struct usb_function *f_acm; static struct usb_function_instance *fi_serial; +static struct usb_function *f_ecm; +static struct usb_function_instance *fi_ecm; + /* * We _always_ have both CDC ECM and CDC ACM functions. */ @@ -120,13 +113,27 @@ static int __init cdc_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } - status = ecm_bind_config(c, hostaddr, the_dev); - if (status < 0) - return status; + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) { + status = PTR_ERR(fi_ecm); + goto err_func_ecm; + } + + f_ecm = usb_get_function(fi_ecm); + if (IS_ERR(f_ecm)) { + status = PTR_ERR(f_ecm); + goto err_get_ecm; + } + + status = usb_add_function(c, f_ecm); + if (status) + goto err_add_ecm; fi_serial = usb_get_function_instance("acm"); - if (IS_ERR(fi_serial)) - return PTR_ERR(fi_serial); + if (IS_ERR(fi_serial)) { + status = PTR_ERR(fi_serial); + goto err_get_acm; + } f_acm = usb_get_function(fi_serial); if (IS_ERR(f_acm)) { @@ -136,12 +143,21 @@ static int __init cdc_do_config(struct usb_configuration *c) status = usb_add_function(c, f_acm); if (status) - goto err_conf; + goto err_add_acm; + return 0; -err_conf: + +err_add_acm: usb_put_function(f_acm); err_func_acm: usb_put_function_instance(fi_serial); +err_get_acm: + usb_remove_function(c, f_ecm); +err_add_ecm: + usb_put_function(f_ecm); +err_get_ecm: + usb_put_function_instance(fi_ecm); +err_func_ecm: return status; } @@ -157,6 +173,7 @@ static struct usb_configuration cdc_config_driver = { static int __init cdc_bind(struct usb_composite_dev *cdev) { struct usb_gadget *gadget = cdev->gadget; + struct f_ecm_opts *ecm_opts; int status; if (!can_support_ecm(cdev->gadget)) { @@ -165,10 +182,23 @@ static int __init cdc_bind(struct usb_composite_dev *cdev) return -EINVAL; } - /* set up network link layer */ - the_dev = gether_setup(cdev->gadget, hostaddr); - if (IS_ERR(the_dev)) - return PTR_ERR(the_dev); + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) + return PTR_ERR(fi_ecm); + + ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); + + gether_set_qmult(ecm_opts->net, qmult); + if (!gether_set_host_addr(ecm_opts->net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(ecm_opts->net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); + + fi_serial = usb_get_function_instance("acm"); + if (IS_ERR(fi_serial)) { + status = PTR_ERR(fi_serial); + goto fail; + } /* Allocate string descriptor numbers ... note that string * contents can be overridden by the composite_dev glue. @@ -192,7 +222,9 @@ static int __init cdc_bind(struct usb_composite_dev *cdev) return 0; fail1: - gether_cleanup(the_dev); + usb_put_function_instance(fi_serial); +fail: + usb_put_function_instance(fi_ecm); return status; } @@ -200,7 +232,10 @@ static int __exit cdc_unbind(struct usb_composite_dev *cdev) { usb_put_function(f_acm); usb_put_function_instance(fi_serial); - gether_cleanup(the_dev); + if (!IS_ERR_OR_NULL(f_ecm)) + usb_put_function(f_ecm); + if (!IS_ERR_OR_NULL(fi_ecm)) + usb_put_function_instance(fi_ecm); return 0; } diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 56c8eca..f48712f 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -14,6 +14,7 @@ /* #define VERBOSE_DEBUG */ #include <linux/kernel.h> +#include <linux/netdevice.h> #if defined USB_ETH_RNDIS # undef USB_ETH_RNDIS @@ -91,27 +92,23 @@ static inline bool has_rndis(void) #endif } -/*-------------------------------------------------------------------------*/ +#include <linux/module.h> -/* - * Kbuild is not very cooperative with respect to linking separately - * compiled library objects into one module. So for now we won't use - * separate compilation ... ensuring init/exit sections work to shrink - * the runtime footprint, and giving us at least some parts of what - * a "gcc --combine ... part1.c part2.c part3.c ... " build would. - */ -#include "f_ecm.c" -#include "f_subset.c" +#include "u_ecm.h" +#include "u_gether.h" #ifdef USB_ETH_RNDIS -#include "f_rndis.c" -#include "rndis.c" +#include "u_rndis.h" +#include "rndis.h" +#else +#define rndis_borrow_net(...) do {} while (0) #endif -#include "f_eem.c" -#include "u_ether.c" +#include "u_eem.h" /*-------------------------------------------------------------------------*/ USB_GADGET_COMPOSITE_OPTIONS(); +USB_ETHERNET_MODULE_PARAMETERS(); + /* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! * Instead: allocate your own, using normal USB-IF procedures. */ @@ -206,8 +203,18 @@ static struct usb_gadget_strings *dev_strings[] = { NULL, }; -static u8 hostaddr[ETH_ALEN]; -static struct eth_dev *the_dev; +static struct usb_function_instance *fi_ecm; +static struct usb_function *f_ecm; + +static struct usb_function_instance *fi_eem; +static struct usb_function *f_eem; + +static struct usb_function_instance *fi_geth; +static struct usb_function *f_geth; + +static struct usb_function_instance *fi_rndis; +static struct usb_function *f_rndis; + /*-------------------------------------------------------------------------*/ /* @@ -217,6 +224,8 @@ static struct eth_dev *the_dev; */ static int __init rndis_do_config(struct usb_configuration *c) { + int status; + /* FIXME alloc iConfiguration string, set it in c->strings */ if (gadget_is_otg(c->cdev->gadget)) { @@ -224,7 +233,15 @@ static int __init rndis_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } - return rndis_bind_config(c, hostaddr, the_dev); + f_rndis = usb_get_function(fi_rndis); + if (IS_ERR(f_rndis)) + return PTR_ERR(f_rndis); + + status = usb_add_function(c, f_rndis); + if (status < 0) + usb_put_function(f_rndis); + + return status; } static struct usb_configuration rndis_config_driver = { @@ -249,6 +266,8 @@ MODULE_PARM_DESC(use_eem, "use CDC EEM mode"); */ static int __init eth_do_config(struct usb_configuration *c) { + int status = 0; + /* FIXME alloc iConfiguration string, set it in c->strings */ if (gadget_is_otg(c->cdev->gadget)) { @@ -256,12 +275,38 @@ static int __init eth_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } - if (use_eem) - return eem_bind_config(c, the_dev); - else if (can_support_ecm(c->cdev->gadget)) - return ecm_bind_config(c, hostaddr, the_dev); - else - return geth_bind_config(c, hostaddr, the_dev); + if (use_eem) { + f_eem = usb_get_function(fi_eem); + if (IS_ERR(f_eem)) + return PTR_ERR(f_eem); + + status = usb_add_function(c, f_eem); + if (status < 0) + usb_put_function(f_eem); + + return status; + } else if (can_support_ecm(c->cdev->gadget)) { + f_ecm = usb_get_function(fi_ecm); + if (IS_ERR(f_ecm)) + return PTR_ERR(f_ecm); + + status = usb_add_function(c, f_ecm); + if (status < 0) + usb_put_function(f_ecm); + + return status; + } else { + f_geth = usb_get_function(fi_geth); + if (IS_ERR(f_geth)) + return PTR_ERR(f_geth); + + status = usb_add_function(c, f_geth); + if (status < 0) + usb_put_function(f_geth); + + return status; + } + } static struct usb_configuration eth_config_driver = { @@ -276,24 +321,50 @@ static struct usb_configuration eth_config_driver = { static int __init eth_bind(struct usb_composite_dev *cdev) { struct usb_gadget *gadget = cdev->gadget; + struct f_eem_opts *eem_opts = NULL; + struct f_ecm_opts *ecm_opts = NULL; + struct f_gether_opts *geth_opts = NULL; + struct net_device *net; int status; - /* set up network link layer */ - the_dev = gether_setup(cdev->gadget, hostaddr); - if (IS_ERR(the_dev)) - return PTR_ERR(the_dev); - /* set up main config label and device descriptor */ if (use_eem) { /* EEM */ + fi_eem = usb_get_function_instance("eem"); + if (IS_ERR(fi_eem)) + return PTR_ERR(fi_eem); + + eem_opts = container_of(fi_eem, struct f_eem_opts, func_inst); + + net = eem_opts->net; + eth_config_driver.label = "CDC Ethernet (EEM)"; device_desc.idVendor = cpu_to_le16(EEM_VENDOR_NUM); device_desc.idProduct = cpu_to_le16(EEM_PRODUCT_NUM); - } else if (can_support_ecm(cdev->gadget)) { + } else if (can_support_ecm(gadget)) { /* ECM */ + + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) + return PTR_ERR(fi_ecm); + + ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); + + net = ecm_opts->net; + eth_config_driver.label = "CDC Ethernet (ECM)"; } else { /* CDC Subset */ + + fi_geth = usb_get_function_instance("geth"); + if (IS_ERR(fi_geth)) + return PTR_ERR(fi_geth); + + geth_opts = container_of(fi_geth, struct f_gether_opts, + func_inst); + + net = geth_opts->net; + eth_config_driver.label = "CDC Subset/SAFE"; device_desc.idVendor = cpu_to_le16(SIMPLE_VENDOR_NUM); @@ -302,8 +373,34 @@ static int __init eth_bind(struct usb_composite_dev *cdev) device_desc.bDeviceClass = USB_CLASS_VENDOR_SPEC; } + gether_set_qmult(net, qmult); + if (!gether_set_host_addr(net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); + if (has_rndis()) { /* RNDIS plus ECM-or-Subset */ + gether_set_gadget(net, cdev->gadget); + status = gether_register_netdev(net); + if (status) + goto fail; + + if (use_eem) + eem_opts->bound = true; + else if (can_support_ecm(gadget)) + ecm_opts->bound = true; + else + geth_opts->bound = true; + + fi_rndis = usb_get_function_instance("rndis"); + if (IS_ERR(fi_rndis)) { + status = PTR_ERR(fi_rndis); + goto fail; + } + + rndis_borrow_net(fi_rndis, net); + device_desc.idVendor = cpu_to_le16(RNDIS_VENDOR_NUM); device_desc.idProduct = cpu_to_le16(RNDIS_PRODUCT_NUM); device_desc.bNumConfigurations = 2; @@ -315,7 +412,7 @@ static int __init eth_bind(struct usb_composite_dev *cdev) status = usb_string_ids_tab(cdev, strings_dev); if (status < 0) - goto fail; + goto fail1; device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; @@ -324,12 +421,12 @@ static int __init eth_bind(struct usb_composite_dev *cdev) status = usb_add_config(cdev, &rndis_config_driver, rndis_do_config); if (status < 0) - goto fail; + goto fail1; } status = usb_add_config(cdev, ð_config_driver, eth_do_config); if (status < 0) - goto fail; + goto fail1; usb_composite_overwrite_options(cdev, &coverwrite); dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n", @@ -337,14 +434,29 @@ static int __init eth_bind(struct usb_composite_dev *cdev) return 0; +fail1: + if (has_rndis()) + usb_put_function_instance(fi_rndis); fail: - gether_cleanup(the_dev); + if (use_eem) + usb_put_function_instance(fi_eem); + else if (can_support_ecm(gadget)) + usb_put_function_instance(fi_ecm); + else + usb_put_function_instance(fi_geth); return status; } static int __exit eth_unbind(struct usb_composite_dev *cdev) { - gether_cleanup(the_dev); + if (has_rndis()) + usb_put_function_instance(fi_rndis); + if (use_eem) + usb_put_function_instance(fi_eem); + else if (can_support_ecm(cdev->gadget)) + usb_put_function_instance(fi_ecm); + else + usb_put_function_instance(fi_geth); return 0; } diff --git a/drivers/usb/gadget/f_ecm.c b/drivers/usb/gadget/f_ecm.c index abf8a31..fcafe1a 100644 --- a/drivers/usb/gadget/f_ecm.c +++ b/drivers/usb/gadget/f_ecm.c @@ -14,10 +14,13 @@ #include <linux/slab.h> #include <linux/kernel.h> +#include <linux/module.h> #include <linux/device.h> #include <linux/etherdevice.h> #include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_ecm.h" /* @@ -684,9 +687,44 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_ecm *ecm = func_to_ecm(f); + struct usb_string *us; int status; struct usb_ep *ep; +#ifndef USBF_ECM_INCLUDED + struct f_ecm_opts *ecm_opts; + + if (!can_support_ecm(cdev->gadget)) + return -EINVAL; + + ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst); + + /* + * in drivers/usb/gadget/configfs.c:configfs_composite_bind() + * configurations are bound in sequence with list_for_each_entry, + * in each configuration its functions are bound in sequence + * with list_for_each_entry, so we assume no race condition + * with regard to ecm_opts->bound access + */ + if (!ecm_opts->bound) { + mutex_lock(&ecm_opts->lock); + gether_set_gadget(ecm_opts->net, cdev->gadget); + status = gether_register_netdev(ecm_opts->net); + mutex_unlock(&ecm_opts->lock); + if (status) + return status; + ecm_opts->bound = true; + } +#endif + us = usb_gstrings_attach(cdev, ecm_strings, + ARRAY_SIZE(ecm_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + ecm_control_intf.iInterface = us[0].id; + ecm_data_intf.iInterface = us[2].id; + ecm_desc.iMACAddress = us[1].id; + ecm_iad_descriptor.iFunction = us[3].id; + /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) @@ -796,14 +834,15 @@ fail: return status; } +#ifdef USBF_ECM_INCLUDED + static void -ecm_unbind(struct usb_configuration *c, struct usb_function *f) +ecm_old_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_ecm *ecm = func_to_ecm(f); DBG(c->cdev, "ecm unbind\n"); - ecm_string_defs[0].id = 0; usb_free_all_descriptors(f); kfree(ecm->notify_req->buf); @@ -834,17 +873,6 @@ ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], if (!can_support_ecm(c->cdev->gadget) || !ethaddr) return -EINVAL; - if (ecm_string_defs[0].id == 0) { - status = usb_string_ids_tab(c->cdev, ecm_string_defs); - if (status) - return status; - - ecm_control_intf.iInterface = ecm_string_defs[0].id; - ecm_data_intf.iInterface = ecm_string_defs[2].id; - ecm_desc.iMACAddress = ecm_string_defs[1].id; - ecm_iad_descriptor.iFunction = ecm_string_defs[3].id; - } - /* allocate and initialize one new instance */ ecm = kzalloc(sizeof *ecm, GFP_KERNEL); if (!ecm) @@ -858,10 +886,9 @@ ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], ecm->port.cdc_filter = DEFAULT_FILTER; ecm->port.func.name = "cdc_ethernet"; - ecm->port.func.strings = ecm_strings; /* descriptors are per-instance copies */ ecm->port.func.bind = ecm_bind; - ecm->port.func.unbind = ecm_unbind; + ecm->port.func.unbind = ecm_old_unbind; ecm->port.func.set_alt = ecm_set_alt; ecm->port.func.get_alt = ecm_get_alt; ecm->port.func.setup = ecm_setup; @@ -872,3 +899,142 @@ ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], kfree(ecm); return status; } + +#else + +static inline struct f_ecm_opts *to_f_ecm_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_ecm_opts, + func_inst.group); +} + +/* f_ecm_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(ecm); + +/* f_ecm_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ecm); + +/* f_ecm_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ecm); + +/* f_ecm_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ecm); + +/* f_ecm_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ecm); + +static struct configfs_attribute *ecm_attrs[] = { + &f_ecm_opts_dev_addr.attr, + &f_ecm_opts_host_addr.attr, + &f_ecm_opts_qmult.attr, + &f_ecm_opts_ifname.attr, + NULL, +}; + +static struct config_item_type ecm_func_type = { + .ct_item_ops = &ecm_item_ops, + .ct_attrs = ecm_attrs, + .ct_owner = THIS_MODULE, +}; + +static void ecm_free_inst(struct usb_function_instance *f) +{ + struct f_ecm_opts *opts; + + opts = container_of(f, struct f_ecm_opts, func_inst); + if (opts->bound) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *ecm_alloc_inst(void) +{ + struct f_ecm_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = ecm_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) + return ERR_PTR(PTR_ERR(opts->net)); + + config_group_init_type_name(&opts->func_inst.group, "", &ecm_func_type); + + return &opts->func_inst; +} + +static void ecm_free(struct usb_function *f) +{ + struct f_ecm *ecm; + struct f_ecm_opts *opts; + + ecm = func_to_ecm(f); + opts = container_of(f->fi, struct f_ecm_opts, func_inst); + kfree(ecm); + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); +} + +static void ecm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ecm *ecm = func_to_ecm(f); + + DBG(c->cdev, "ecm unbind\n"); + + usb_free_all_descriptors(f); + + kfree(ecm->notify_req->buf); + usb_ep_free_request(ecm->notify, ecm->notify_req); +} + +struct usb_function *ecm_alloc(struct usb_function_instance *fi) +{ + struct f_ecm *ecm; + struct f_ecm_opts *opts; + int status; + + /* allocate and initialize one new instance */ + ecm = kzalloc(sizeof(*ecm), GFP_KERNEL); + if (!ecm) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_ecm_opts, func_inst); + mutex_lock(&opts->lock); + opts->refcnt++; + + /* export host's Ethernet address in CDC format */ + status = gether_get_host_addr_cdc(opts->net, ecm->ethaddr, + sizeof(ecm->ethaddr)); + if (status < 12) { + kfree(ecm); + return ERR_PTR(-EINVAL); + } + ecm_string_defs[1].s = ecm->ethaddr; + + ecm->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); + ecm->port.cdc_filter = DEFAULT_FILTER; + + ecm->port.func.name = "cdc_ethernet"; + /* descriptors are per-instance copies */ + ecm->port.func.bind = ecm_bind; + ecm->port.func.unbind = ecm_unbind; + ecm->port.func.set_alt = ecm_set_alt; + ecm->port.func.get_alt = ecm_get_alt; + ecm->port.func.setup = ecm_setup; + ecm->port.func.disable = ecm_disable; + ecm->port.func.free_func = ecm_free; + + return &ecm->port.func; +} + +DECLARE_USB_FUNCTION_INIT(ecm, ecm_alloc_inst, ecm_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); + +#endif diff --git a/drivers/usb/gadget/f_eem.c b/drivers/usb/gadget/f_eem.c index f4e0bbe..90ee802 100644 --- a/drivers/usb/gadget/f_eem.c +++ b/drivers/usb/gadget/f_eem.c @@ -12,12 +12,15 @@ */ #include <linux/kernel.h> +#include <linux/module.h> #include <linux/device.h> #include <linux/etherdevice.h> #include <linux/crc32.h> #include <linux/slab.h> #include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_eem.h" #define EEM_HLEN 2 @@ -40,7 +43,7 @@ static inline struct f_eem *func_to_eem(struct usb_function *f) /* interface descriptor: */ -static struct usb_interface_descriptor eem_intf __initdata = { +static struct usb_interface_descriptor eem_intf = { .bLength = sizeof eem_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -54,7 +57,7 @@ static struct usb_interface_descriptor eem_intf __initdata = { /* full speed support: */ -static struct usb_endpoint_descriptor eem_fs_in_desc __initdata = { +static struct usb_endpoint_descriptor eem_fs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -62,7 +65,7 @@ static struct usb_endpoint_descriptor eem_fs_in_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_endpoint_descriptor eem_fs_out_desc __initdata = { +static struct usb_endpoint_descriptor eem_fs_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -70,7 +73,7 @@ static struct usb_endpoint_descriptor eem_fs_out_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_descriptor_header *eem_fs_function[] __initdata = { +static struct usb_descriptor_header *eem_fs_function[] = { /* CDC EEM control descriptors */ (struct usb_descriptor_header *) &eem_intf, (struct usb_descriptor_header *) &eem_fs_in_desc, @@ -80,7 +83,7 @@ static struct usb_descriptor_header *eem_fs_function[] __initdata = { /* high speed support: */ -static struct usb_endpoint_descriptor eem_hs_in_desc __initdata = { +static struct usb_endpoint_descriptor eem_hs_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -89,7 +92,7 @@ static struct usb_endpoint_descriptor eem_hs_in_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_endpoint_descriptor eem_hs_out_desc __initdata = { +static struct usb_endpoint_descriptor eem_hs_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -98,7 +101,7 @@ static struct usb_endpoint_descriptor eem_hs_out_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_descriptor_header *eem_hs_function[] __initdata = { +static struct usb_descriptor_header *eem_hs_function[] = { /* CDC EEM control descriptors */ (struct usb_descriptor_header *) &eem_intf, (struct usb_descriptor_header *) &eem_hs_in_desc, @@ -108,7 +111,7 @@ static struct usb_descriptor_header *eem_hs_function[] __initdata = { /* super speed support: */ -static struct usb_endpoint_descriptor eem_ss_in_desc __initdata = { +static struct usb_endpoint_descriptor eem_ss_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -117,7 +120,7 @@ static struct usb_endpoint_descriptor eem_ss_in_desc __initdata = { .wMaxPacketSize = cpu_to_le16(1024), }; -static struct usb_endpoint_descriptor eem_ss_out_desc __initdata = { +static struct usb_endpoint_descriptor eem_ss_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -126,7 +129,7 @@ static struct usb_endpoint_descriptor eem_ss_out_desc __initdata = { .wMaxPacketSize = cpu_to_le16(1024), }; -static struct usb_ss_ep_comp_descriptor eem_ss_bulk_comp_desc __initdata = { +static struct usb_ss_ep_comp_descriptor eem_ss_bulk_comp_desc = { .bLength = sizeof eem_ss_bulk_comp_desc, .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, @@ -135,7 +138,7 @@ static struct usb_ss_ep_comp_descriptor eem_ss_bulk_comp_desc __initdata = { /* .bmAttributes = 0, */ }; -static struct usb_descriptor_header *eem_ss_function[] __initdata = { +static struct usb_descriptor_header *eem_ss_function[] = { /* CDC EEM control descriptors */ (struct usb_descriptor_header *) &eem_intf, (struct usb_descriptor_header *) &eem_ss_in_desc, @@ -242,14 +245,40 @@ static void eem_disable(struct usb_function *f) /* EEM function driver setup/binding */ -static int __init -eem_bind(struct usb_configuration *c, struct usb_function *f) +static int eem_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_eem *eem = func_to_eem(f); + struct usb_string *us; int status; struct usb_ep *ep; + struct f_eem_opts *eem_opts; + + eem_opts = container_of(f->fi, struct f_eem_opts, func_inst); + /* + * in drivers/usb/gadget/configfs.c:configfs_composite_bind() + * configurations are bound in sequence with list_for_each_entry, + * in each configuration its functions are bound in sequence + * with list_for_each_entry, so we assume no race condition + * with regard to eem_opts->bound access + */ + if (!eem_opts->bound) { + mutex_lock(&eem_opts->lock); + gether_set_gadget(eem_opts->net, cdev->gadget); + status = gether_register_netdev(eem_opts->net); + mutex_unlock(&eem_opts->lock); + if (status) + return status; + eem_opts->bound = true; + } + + us = usb_gstrings_attach(cdev, eem_strings, + ARRAY_SIZE(eem_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + eem_intf.iInterface = us[0].id; + /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) @@ -307,17 +336,6 @@ fail: return status; } -static void -eem_unbind(struct usb_configuration *c, struct usb_function *f) -{ - struct f_eem *eem = func_to_eem(f); - - DBG(c->cdev, "eem unbind\n"); - - usb_free_all_descriptors(f); - kfree(eem); -} - static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req) { struct sk_buff *skb = (struct sk_buff *)req->context; @@ -518,55 +536,124 @@ error: return status; } -/** - * eem_bind_config - add CDC Ethernet (EEM) network link to a configuration - * @c: the configuration to support the network link - * Context: single threaded during gadget setup - * - * Returns zero on success, else negative errno. - * - * Caller must have called @gether_setup(). Caller is also responsible - * for calling @gether_cleanup() before module unload. - */ -int __init eem_bind_config(struct usb_configuration *c, struct eth_dev *dev) +static inline struct f_eem_opts *to_f_eem_opts(struct config_item *item) { - struct f_eem *eem; - int status; + return container_of(to_config_group(item), struct f_eem_opts, + func_inst.group); +} - /* maybe allocate device-global string IDs */ - if (eem_string_defs[0].id == 0) { +/* f_eem_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(eem); - /* control interface label */ - status = usb_string_id(c->cdev); - if (status < 0) - return status; - eem_string_defs[0].id = status; - eem_intf.iInterface = status; - } +/* f_eem_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(eem); + +/* f_eem_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(eem); + +/* f_eem_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(eem); + +/* f_eem_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(eem); + +static struct configfs_attribute *eem_attrs[] = { + &f_eem_opts_dev_addr.attr, + &f_eem_opts_host_addr.attr, + &f_eem_opts_qmult.attr, + &f_eem_opts_ifname.attr, + NULL, +}; + +static struct config_item_type eem_func_type = { + .ct_item_ops = &eem_item_ops, + .ct_attrs = eem_attrs, + .ct_owner = THIS_MODULE, +}; + +static void eem_free_inst(struct usb_function_instance *f) +{ + struct f_eem_opts *opts; + + opts = container_of(f, struct f_eem_opts, func_inst); + if (opts->bound) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *eem_alloc_inst(void) +{ + struct f_eem_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = eem_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) + return ERR_CAST(opts->net); + + config_group_init_type_name(&opts->func_inst.group, "", &eem_func_type); + + return &opts->func_inst; +} + +static void eem_free(struct usb_function *f) +{ + struct f_eem *eem; + struct f_eem_opts *opts; + + eem = func_to_eem(f); + opts = container_of(f->fi, struct f_eem_opts, func_inst); + kfree(eem); + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); +} + +static void eem_unbind(struct usb_configuration *c, struct usb_function *f) +{ + DBG(c->cdev, "eem unbind\n"); + + usb_free_all_descriptors(f); +} + +struct usb_function *eem_alloc(struct usb_function_instance *fi) +{ + struct f_eem *eem; + struct f_eem_opts *opts; /* allocate and initialize one new instance */ - eem = kzalloc(sizeof *eem, GFP_KERNEL); + eem = kzalloc(sizeof(*eem), GFP_KERNEL); if (!eem) - return -ENOMEM; + return ERR_PTR(-ENOMEM); - eem->port.ioport = dev; + opts = container_of(fi, struct f_eem_opts, func_inst); + mutex_lock(&opts->lock); + opts->refcnt++; + + eem->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); eem->port.cdc_filter = DEFAULT_FILTER; eem->port.func.name = "cdc_eem"; - eem->port.func.strings = eem_strings; /* descriptors are per-instance copies */ eem->port.func.bind = eem_bind; eem->port.func.unbind = eem_unbind; eem->port.func.set_alt = eem_set_alt; eem->port.func.setup = eem_setup; eem->port.func.disable = eem_disable; + eem->port.func.free_func = eem_free; eem->port.wrap = eem_wrap; eem->port.unwrap = eem_unwrap; eem->port.header_len = EEM_HLEN; - status = usb_add_function(c, &eem->port.func); - if (status) - kfree(eem); - return status; + return &eem->port.func; } +DECLARE_USB_FUNCTION_INIT(eem, eem_alloc_inst, eem_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 97666e8..56f1fd1 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -413,6 +413,7 @@ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) /* Caller must hold fsg->lock */ static void wakeup_thread(struct fsg_common *common) { + smp_wmb(); /* ensure the write of bh->state is complete */ /* Tell the main thread that something has happened */ common->thread_wakeup_needed = 1; if (common->thread_task) @@ -632,6 +633,7 @@ static int sleep_thread(struct fsg_common *common) } __set_current_state(TASK_RUNNING); common->thread_wakeup_needed = 0; + smp_rmb(); /* ensure the latest bh->state is visible */ return rc; } @@ -2745,8 +2747,8 @@ buffhds_first_it: "%-8s%-16s%04x", cfg->vendor_name ?: "Linux", /* Assume product name dependent on the first LUN */ cfg->product_name ?: (common->luns->cdrom - ? "File-Stor Gadget" - : "File-CD Gadget"), + ? "File-CD Gadget" + : "File-Stor Gadget"), i); /* diff --git a/drivers/usb/gadget/f_ncm.c b/drivers/usb/gadget/f_ncm.c index ee19bc8..47a5724 100644 --- a/drivers/usb/gadget/f_ncm.c +++ b/drivers/usb/gadget/f_ncm.c @@ -16,6 +16,7 @@ */ #include <linux/kernel.h> +#include <linux/module.h> #include <linux/device.h> #include <linux/etherdevice.h> #include <linux/crc32.h> @@ -23,6 +24,8 @@ #include <linux/usb/cdc.h> #include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_ncm.h" /* * This function is a "CDC Network Control Model" (CDC NCM) Ethernet link. @@ -125,7 +128,7 @@ static struct usb_cdc_ncm_ntb_parameters ntb_parameters = { #define NCM_STATUS_INTERVAL_MS 32 #define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ -static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = { +static struct usb_interface_assoc_descriptor ncm_iad_desc = { .bLength = sizeof ncm_iad_desc, .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, @@ -139,7 +142,7 @@ static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = { /* interface descriptor: */ -static struct usb_interface_descriptor ncm_control_intf __initdata = { +static struct usb_interface_descriptor ncm_control_intf = { .bLength = sizeof ncm_control_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -151,7 +154,7 @@ static struct usb_interface_descriptor ncm_control_intf __initdata = { /* .iInterface = DYNAMIC */ }; -static struct usb_cdc_header_desc ncm_header_desc __initdata = { +static struct usb_cdc_header_desc ncm_header_desc = { .bLength = sizeof ncm_header_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_HEADER_TYPE, @@ -159,7 +162,7 @@ static struct usb_cdc_header_desc ncm_header_desc __initdata = { .bcdCDC = cpu_to_le16(0x0110), }; -static struct usb_cdc_union_desc ncm_union_desc __initdata = { +static struct usb_cdc_union_desc ncm_union_desc = { .bLength = sizeof(ncm_union_desc), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_UNION_TYPE, @@ -167,7 +170,7 @@ static struct usb_cdc_union_desc ncm_union_desc __initdata = { /* .bSlaveInterface0 = DYNAMIC */ }; -static struct usb_cdc_ether_desc ecm_desc __initdata = { +static struct usb_cdc_ether_desc ecm_desc = { .bLength = sizeof ecm_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, @@ -182,7 +185,7 @@ static struct usb_cdc_ether_desc ecm_desc __initdata = { #define NCAPS (USB_CDC_NCM_NCAP_ETH_FILTER | USB_CDC_NCM_NCAP_CRC_MODE) -static struct usb_cdc_ncm_desc ncm_desc __initdata = { +static struct usb_cdc_ncm_desc ncm_desc = { .bLength = sizeof ncm_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_NCM_TYPE, @@ -194,7 +197,7 @@ static struct usb_cdc_ncm_desc ncm_desc __initdata = { /* the default data interface has no endpoints ... */ -static struct usb_interface_descriptor ncm_data_nop_intf __initdata = { +static struct usb_interface_descriptor ncm_data_nop_intf = { .bLength = sizeof ncm_data_nop_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -209,7 +212,7 @@ static struct usb_interface_descriptor ncm_data_nop_intf __initdata = { /* ... but the "real" data interface has two bulk endpoints */ -static struct usb_interface_descriptor ncm_data_intf __initdata = { +static struct usb_interface_descriptor ncm_data_intf = { .bLength = sizeof ncm_data_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -224,7 +227,7 @@ static struct usb_interface_descriptor ncm_data_intf __initdata = { /* full speed support: */ -static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = { +static struct usb_endpoint_descriptor fs_ncm_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -234,7 +237,7 @@ static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = { .bInterval = NCM_STATUS_INTERVAL_MS, }; -static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = { +static struct usb_endpoint_descriptor fs_ncm_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -242,7 +245,7 @@ static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = { +static struct usb_endpoint_descriptor fs_ncm_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -250,7 +253,7 @@ static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_descriptor_header *ncm_fs_function[] __initdata = { +static struct usb_descriptor_header *ncm_fs_function[] = { (struct usb_descriptor_header *) &ncm_iad_desc, /* CDC NCM control descriptors */ (struct usb_descriptor_header *) &ncm_control_intf, @@ -269,7 +272,7 @@ static struct usb_descriptor_header *ncm_fs_function[] __initdata = { /* high speed support: */ -static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = { +static struct usb_endpoint_descriptor hs_ncm_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -278,7 +281,7 @@ static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = { .wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT), .bInterval = USB_MS_TO_HS_INTERVAL(NCM_STATUS_INTERVAL_MS), }; -static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = { +static struct usb_endpoint_descriptor hs_ncm_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -287,7 +290,7 @@ static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_endpoint_descriptor hs_ncm_out_desc __initdata = { +static struct usb_endpoint_descriptor hs_ncm_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -296,7 +299,7 @@ static struct usb_endpoint_descriptor hs_ncm_out_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_descriptor_header *ncm_hs_function[] __initdata = { +static struct usb_descriptor_header *ncm_hs_function[] = { (struct usb_descriptor_header *) &ncm_iad_desc, /* CDC NCM control descriptors */ (struct usb_descriptor_header *) &ncm_control_intf, @@ -1152,13 +1155,44 @@ static void ncm_close(struct gether *geth) /* ethernet function driver setup/binding */ -static int __init -ncm_bind(struct usb_configuration *c, struct usb_function *f) +static int ncm_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_ncm *ncm = func_to_ncm(f); + struct usb_string *us; int status; struct usb_ep *ep; + struct f_ncm_opts *ncm_opts; + + if (!can_support_ecm(cdev->gadget)) + return -EINVAL; + + ncm_opts = container_of(f->fi, struct f_ncm_opts, func_inst); + /* + * in drivers/usb/gadget/configfs.c:configfs_composite_bind() + * configurations are bound in sequence with list_for_each_entry, + * in each configuration its functions are bound in sequence + * with list_for_each_entry, so we assume no race condition + * with regard to ncm_opts->bound access + */ + if (!ncm_opts->bound) { + mutex_lock(&ncm_opts->lock); + gether_set_gadget(ncm_opts->net, cdev->gadget); + status = gether_register_netdev(ncm_opts->net); + mutex_unlock(&ncm_opts->lock); + if (status) + return status; + ncm_opts->bound = true; + } + us = usb_gstrings_attach(cdev, ncm_strings, + ARRAY_SIZE(ncm_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + ncm_control_intf.iInterface = us[STRING_CTRL_IDX].id; + ncm_data_nop_intf.iInterface = us[STRING_DATA_IDX].id; + ncm_data_intf.iInterface = us[STRING_DATA_IDX].id; + ecm_desc.iMACAddress = us[STRING_MAC_IDX].id; + ncm_iad_desc.iFunction = us[STRING_IAD_IDX].id; /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); @@ -1259,74 +1293,127 @@ fail: return status; } -static void -ncm_unbind(struct usb_configuration *c, struct usb_function *f) +static inline struct f_ncm_opts *to_f_ncm_opts(struct config_item *item) { - struct f_ncm *ncm = func_to_ncm(f); + return container_of(to_config_group(item), struct f_ncm_opts, + func_inst.group); +} - DBG(c->cdev, "ncm unbind\n"); +/* f_ncm_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(ncm); - ncm_string_defs[0].id = 0; - usb_free_all_descriptors(f); +/* f_ncm_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(ncm); - kfree(ncm->notify_req->buf); - usb_ep_free_request(ncm->notify, ncm->notify_req); +/* f_ncm_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(ncm); +/* f_ncm_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ncm); + +/* f_ncm_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ncm); + +static struct configfs_attribute *ncm_attrs[] = { + &f_ncm_opts_dev_addr.attr, + &f_ncm_opts_host_addr.attr, + &f_ncm_opts_qmult.attr, + &f_ncm_opts_ifname.attr, + NULL, +}; + +static struct config_item_type ncm_func_type = { + .ct_item_ops = &ncm_item_ops, + .ct_attrs = ncm_attrs, + .ct_owner = THIS_MODULE, +}; + +static void ncm_free_inst(struct usb_function_instance *f) +{ + struct f_ncm_opts *opts; + + opts = container_of(f, struct f_ncm_opts, func_inst); + if (opts->bound) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *ncm_alloc_inst(void) +{ + struct f_ncm_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = ncm_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) + return ERR_PTR(PTR_ERR(opts->net)); + + config_group_init_type_name(&opts->func_inst.group, "", &ncm_func_type); + + return &opts->func_inst; +} + +static void ncm_free(struct usb_function *f) +{ + struct f_ncm *ncm; + struct f_ncm_opts *opts; + + ncm = func_to_ncm(f); + opts = container_of(f->fi, struct f_ncm_opts, func_inst); kfree(ncm); + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); } -/** - * ncm_bind_config - add CDC Network link to a configuration - * @c: the configuration to support the network link - * @ethaddr: a buffer in which the ethernet address of the host side - * side of the link was recorded - * Context: single threaded during gadget setup - * - * Returns zero on success, else negative errno. - * - * Caller must have called @gether_setup(). Caller is also responsible - * for calling @gether_cleanup() before module unload. - */ -int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], - struct eth_dev *dev) +static void ncm_unbind(struct usb_configuration *c, struct usb_function *f) { - struct f_ncm *ncm; - int status; + struct f_ncm *ncm = func_to_ncm(f); - if (!can_support_ecm(c->cdev->gadget) || !ethaddr) - return -EINVAL; + DBG(c->cdev, "ncm unbind\n"); - if (ncm_string_defs[0].id == 0) { - status = usb_string_ids_tab(c->cdev, ncm_string_defs); - if (status < 0) - return status; - ncm_control_intf.iInterface = - ncm_string_defs[STRING_CTRL_IDX].id; + usb_free_all_descriptors(f); - status = ncm_string_defs[STRING_DATA_IDX].id; - ncm_data_nop_intf.iInterface = status; - ncm_data_intf.iInterface = status; + kfree(ncm->notify_req->buf); + usb_ep_free_request(ncm->notify, ncm->notify_req); +} - ecm_desc.iMACAddress = ncm_string_defs[STRING_MAC_IDX].id; - ncm_iad_desc.iFunction = ncm_string_defs[STRING_IAD_IDX].id; - } +struct usb_function *ncm_alloc(struct usb_function_instance *fi) +{ + struct f_ncm *ncm; + struct f_ncm_opts *opts; + int status; /* allocate and initialize one new instance */ - ncm = kzalloc(sizeof *ncm, GFP_KERNEL); + ncm = kzalloc(sizeof(*ncm), GFP_KERNEL); if (!ncm) - return -ENOMEM; + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_ncm_opts, func_inst); + mutex_lock(&opts->lock); + opts->refcnt++; /* export host's Ethernet address in CDC format */ - snprintf(ncm->ethaddr, sizeof ncm->ethaddr, "%pm", ethaddr); + status = gether_get_host_addr_cdc(opts->net, ncm->ethaddr, + sizeof(ncm->ethaddr)); + if (status < 12) { /* strlen("01234567890a") */ + kfree(ncm); + return ERR_PTR(-EINVAL); + } ncm_string_defs[STRING_MAC_IDX].s = ncm->ethaddr; spin_lock_init(&ncm->lock); ncm_reset_values(ncm); - ncm->port.ioport = dev; + ncm->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); ncm->port.is_fixed = true; ncm->port.func.name = "cdc_network"; - ncm->port.func.strings = ncm_strings; /* descriptors are per-instance copies */ ncm->port.func.bind = ncm_bind; ncm->port.func.unbind = ncm_unbind; @@ -1334,12 +1421,14 @@ int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], ncm->port.func.get_alt = ncm_get_alt; ncm->port.func.setup = ncm_setup; ncm->port.func.disable = ncm_disable; + ncm->port.func.free_func = ncm_free; ncm->port.wrap = ncm_wrap_ntb; ncm->port.unwrap = ncm_unwrap_ntb; - status = usb_add_function(c, &ncm->port.func); - if (status) - kfree(ncm); - return status; + return &ncm->port.func; } + +DECLARE_USB_FUNCTION_INIT(ncm, ncm_alloc_inst, ncm_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yauheni Kaliuta"); diff --git a/drivers/usb/gadget/f_obex.c b/drivers/usb/gadget/f_obex.c index 8aa2be5..ad39f1d 100644 --- a/drivers/usb/gadget/f_obex.c +++ b/drivers/usb/gadget/f_obex.c @@ -309,23 +309,20 @@ static int obex_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_obex *obex = func_to_obex(f); + struct usb_string *us; int status; struct usb_ep *ep; if (!can_support_obex(c)) return -EINVAL; - if (obex_string_defs[OBEX_CTRL_IDX].id == 0) { - status = usb_string_ids_tab(c->cdev, obex_string_defs); - if (status < 0) - return status; - obex_control_intf.iInterface = - obex_string_defs[OBEX_CTRL_IDX].id; - - status = obex_string_defs[OBEX_DATA_IDX].id; - obex_data_nop_intf.iInterface = status; - obex_data_intf.iInterface = status; - } + us = usb_gstrings_attach(cdev, obex_strings, + ARRAY_SIZE(obex_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + obex_control_intf.iInterface = us[OBEX_CTRL_IDX].id; + obex_data_nop_intf.iInterface = us[OBEX_DATA_IDX].id; + obex_data_intf.iInterface = us[OBEX_DATA_IDX].id; /* allocate instance-specific interface IDs, and patch descriptors */ @@ -406,57 +403,6 @@ fail: return status; } -#ifdef USBF_OBEX_INCLUDED - -static void -obex_old_unbind(struct usb_configuration *c, struct usb_function *f) -{ - obex_string_defs[OBEX_CTRL_IDX].id = 0; - usb_free_all_descriptors(f); - kfree(func_to_obex(f)); -} - -/** - * obex_bind_config - add a CDC OBEX function to a configuration - * @c: the configuration to support the CDC OBEX instance - * @port_num: /dev/ttyGS* port this interface will use - * Context: single threaded during gadget setup - * - * Returns zero on success, else negative errno. - */ -int __init obex_bind_config(struct usb_configuration *c, u8 port_num) -{ - struct f_obex *obex; - int status; - - /* allocate and initialize one new instance */ - obex = kzalloc(sizeof *obex, GFP_KERNEL); - if (!obex) - return -ENOMEM; - - obex->port_num = port_num; - - obex->port.connect = obex_connect; - obex->port.disconnect = obex_disconnect; - - obex->port.func.name = "obex"; - obex->port.func.strings = obex_strings; - /* descriptors are per-instance copies */ - obex->port.func.bind = obex_bind; - obex->port.func.unbind = obex_old_unbind; - obex->port.func.set_alt = obex_set_alt; - obex->port.func.get_alt = obex_get_alt; - obex->port.func.disable = obex_disable; - - status = usb_add_function(c, &obex->port.func); - if (status) - kfree(obex); - - return status; -} - -#else - static inline struct f_serial_opts *to_f_serial_opts(struct config_item *item) { return container_of(to_config_group(item), struct f_serial_opts, @@ -550,7 +496,6 @@ static void obex_free(struct usb_function *f) static void obex_unbind(struct usb_configuration *c, struct usb_function *f) { - obex_string_defs[OBEX_CTRL_IDX].id = 0; usb_free_all_descriptors(f); } @@ -572,7 +517,6 @@ struct usb_function *obex_alloc(struct usb_function_instance *fi) obex->port.disconnect = obex_disconnect; obex->port.func.name = "obex"; - obex->port.func.strings = obex_strings; /* descriptors are per-instance copies */ obex->port.func.bind = obex_bind; obex->port.func.unbind = obex_unbind; @@ -585,8 +529,5 @@ struct usb_function *obex_alloc(struct usb_function_instance *fi) } DECLARE_USB_FUNCTION_INIT(obex, obex_alloc_inst, obex_alloc); - -#endif - MODULE_AUTHOR("Felipe Balbi"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_phonet.c b/drivers/usb/gadget/f_phonet.c index b21ab55..7944fb0 100644 --- a/drivers/usb/gadget/f_phonet.c +++ b/drivers/usb/gadget/f_phonet.c @@ -13,6 +13,7 @@ #include <linux/mm.h> #include <linux/slab.h> #include <linux/kernel.h> +#include <linux/module.h> #include <linux/device.h> #include <linux/netdevice.h> @@ -25,6 +26,7 @@ #include <linux/usb/composite.h> #include "u_phonet.h" +#include "u_ether.h" #define PN_MEDIA_USB 0x1B #define MAXPACKET 512 @@ -478,8 +480,7 @@ static void pn_disconnect(struct usb_function *f) /*-------------------------------------------------------------------------*/ -static __init -int pn_bind(struct usb_configuration *c, struct usb_function *f) +static int pn_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct usb_gadget *gadget = cdev->gadget; @@ -487,6 +488,27 @@ int pn_bind(struct usb_configuration *c, struct usb_function *f) struct usb_ep *ep; int status, i; +#ifndef USBF_PHONET_INCLUDED + struct f_phonet_opts *phonet_opts; + + phonet_opts = container_of(f->fi, struct f_phonet_opts, func_inst); + + /* + * in drivers/usb/gadget/configfs.c:configfs_composite_bind() + * configurations are bound in sequence with list_for_each_entry, + * in each configuration its functions are bound in sequence + * with list_for_each_entry, so we assume no race condition + * with regard to phonet_opts->bound access + */ + if (!phonet_opts->bound) { + gphonet_set_gadget(phonet_opts->net, gadget); + status = gphonet_register_netdev(phonet_opts->net); + if (status) + return status; + phonet_opts->bound = true; + } +#endif + /* Reserve interface IDs */ status = usb_interface_id(c, f); if (status < 0) @@ -560,8 +582,98 @@ err: return status; } -static void -pn_unbind(struct usb_configuration *c, struct usb_function *f) +static inline struct f_phonet_opts *to_f_phonet_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_phonet_opts, + func_inst.group); +} + +CONFIGFS_ATTR_STRUCT(f_phonet_opts); +static ssize_t f_phonet_attr_show(struct config_item *item, + struct configfs_attribute *attr, + char *page) +{ + struct f_phonet_opts *opts = to_f_phonet_opts(item); + struct f_phonet_opts_attribute *f_phonet_opts_attr = + container_of(attr, struct f_phonet_opts_attribute, attr); + ssize_t ret = 0; + + if (f_phonet_opts_attr->show) + ret = f_phonet_opts_attr->show(opts, page); + return ret; +} + +static void phonet_attr_release(struct config_item *item) +{ + struct f_phonet_opts *opts = to_f_phonet_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations phonet_item_ops = { + .release = phonet_attr_release, + .show_attribute = f_phonet_attr_show, +}; + +static ssize_t f_phonet_ifname_show(struct f_phonet_opts *opts, char *page) +{ + return gether_get_ifname(opts->net, page, PAGE_SIZE); +} + +static struct f_phonet_opts_attribute f_phonet_ifname = + __CONFIGFS_ATTR_RO(ifname, f_phonet_ifname_show); + +static struct configfs_attribute *phonet_attrs[] = { + &f_phonet_ifname.attr, + NULL, +}; + +static struct config_item_type phonet_func_type = { + .ct_item_ops = &phonet_item_ops, + .ct_attrs = phonet_attrs, + .ct_owner = THIS_MODULE, +}; + +static void phonet_free_inst(struct usb_function_instance *f) +{ + struct f_phonet_opts *opts; + + opts = container_of(f, struct f_phonet_opts, func_inst); + if (opts->bound) + gphonet_cleanup(opts->net); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *phonet_alloc_inst(void) +{ + struct f_phonet_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + opts->func_inst.free_func_inst = phonet_free_inst; + opts->net = gphonet_setup_default(); + if (IS_ERR(opts->net)) + return ERR_PTR(PTR_ERR(opts->net)); + + config_group_init_type_name(&opts->func_inst.group, "", + &phonet_func_type); + + return &opts->func_inst; +} + +static void phonet_free(struct usb_function *f) +{ + struct f_phonet *phonet; + + phonet = func_to_pn(f); + kfree(phonet); +} + +static void pn_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_phonet *fp = func_to_pn(f); int i; @@ -574,61 +686,72 @@ pn_unbind(struct usb_configuration *c, struct usb_function *f) usb_ep_free_request(fp->out_ep, fp->out_reqv[i]); usb_free_all_descriptors(f); - kfree(fp); } -/*-------------------------------------------------------------------------*/ - -static struct net_device *dev; - -int __init phonet_bind_config(struct usb_configuration *c) +struct usb_function *phonet_alloc(struct usb_function_instance *fi) { struct f_phonet *fp; - int err, size; + struct f_phonet_opts *opts; + int size; size = sizeof(*fp) + (phonet_rxq_size * sizeof(struct usb_request *)); fp = kzalloc(size, GFP_KERNEL); if (!fp) - return -ENOMEM; + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_phonet_opts, func_inst); - fp->dev = dev; + fp->dev = opts->net; fp->function.name = "phonet"; fp->function.bind = pn_bind; fp->function.unbind = pn_unbind; fp->function.set_alt = pn_set_alt; fp->function.get_alt = pn_get_alt; fp->function.disable = pn_disconnect; + fp->function.free_func = phonet_free; spin_lock_init(&fp->rx.lock); - err = usb_add_function(c, &fp->function); - if (err) - kfree(fp); - return err; + return &fp->function; } -int __init gphonet_setup(struct usb_gadget *gadget) +struct net_device *gphonet_setup_default(void) { + struct net_device *dev; struct phonet_port *port; - int err; /* Create net device */ - BUG_ON(dev); dev = alloc_netdev(sizeof(*port), "upnlink%d", pn_net_setup); if (!dev) - return -ENOMEM; + return ERR_PTR(-ENOMEM); port = netdev_priv(dev); spin_lock_init(&port->lock); netif_carrier_off(dev); - SET_NETDEV_DEV(dev, &gadget->dev); - err = register_netdev(dev); - if (err) - free_netdev(dev); - return err; + return dev; +} + +void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g) +{ + SET_NETDEV_DEV(net, &g->dev); +} + +int gphonet_register_netdev(struct net_device *net) +{ + int status; + + status = register_netdev(net); + if (status) + free_netdev(net); + + return status; } -void gphonet_cleanup(void) +void gphonet_cleanup(struct net_device *dev) { unregister_netdev(dev); } + +DECLARE_USB_FUNCTION_INIT(phonet, phonet_alloc_inst, phonet_alloc); +MODULE_AUTHOR("Rémi Denis-Courmont"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index 36e8c44..191df35 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -17,15 +17,17 @@ #include <linux/slab.h> #include <linux/kernel.h> +#include <linux/module.h> #include <linux/device.h> #include <linux/etherdevice.h> #include <linux/atomic.h> #include "u_ether.h" +#include "u_ether_configfs.h" +#include "u_rndis.h" #include "rndis.h" - /* * This function is an RNDIS Ethernet port -- a Microsoft protocol that's * been promoted instead of the standard CDC Ethernet. The published RNDIS @@ -655,6 +657,13 @@ static void rndis_close(struct gether *geth) /*-------------------------------------------------------------------------*/ +/* Some controllers can't support RNDIS ... */ +static inline bool can_support_rndis(struct usb_configuration *c) +{ + /* everything else is *presumably* fine */ + return true; +} + /* ethernet function driver setup/binding */ static int @@ -662,9 +671,41 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_rndis *rndis = func_to_rndis(f); + struct usb_string *us; int status; struct usb_ep *ep; +#ifndef USB_FRNDIS_INCLUDED + struct f_rndis_opts *rndis_opts; + + if (!can_support_rndis(c)) + return -EINVAL; + + rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst); + + /* + * in drivers/usb/gadget/configfs.c:configfs_composite_bind() + * configurations are bound in sequence with list_for_each_entry, + * in each configuration its functions are bound in sequence + * with list_for_each_entry, so we assume no race condition + * with regard to rndis_opts->bound access + */ + if (!rndis_opts->bound) { + gether_set_gadget(rndis_opts->net, cdev->gadget); + status = gether_register_netdev(rndis_opts->net); + if (status) + return status; + rndis_opts->bound = true; + } +#endif + us = usb_gstrings_attach(cdev, rndis_strings, + ARRAY_SIZE(rndis_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + rndis_control_intf.iInterface = us[0].id; + rndis_data_intf.iInterface = us[1].id; + rndis_iad_descriptor.iFunction = us[2].id; + /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) @@ -741,10 +782,12 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis->port.open = rndis_open; rndis->port.close = rndis_close; +#ifdef USB_FRNDIS_INCLUDED status = rndis_register(rndis_response_available, rndis); if (status < 0) goto fail; rndis->config = status; +#endif rndis_set_param_medium(rndis->config, RNDIS_MEDIUM_802_3, 0); rndis_set_host_mac(rndis->config, rndis->ethaddr); @@ -787,15 +830,15 @@ fail: return status; } +#ifdef USB_FRNDIS_INCLUDED + static void -rndis_unbind(struct usb_configuration *c, struct usb_function *f) +rndis_old_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_rndis *rndis = func_to_rndis(f); rndis_deregister(rndis->config); - rndis_exit(); - rndis_string_defs[0].id = 0; usb_free_all_descriptors(f); kfree(rndis->notify_req->buf); @@ -804,13 +847,6 @@ rndis_unbind(struct usb_configuration *c, struct usb_function *f) kfree(rndis); } -/* Some controllers can't support RNDIS ... */ -static inline bool can_support_rndis(struct usb_configuration *c) -{ - /* everything else is *presumably* fine */ - return true; -} - int rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], u32 vendorID, const char *manufacturer, struct eth_dev *dev) @@ -818,24 +854,6 @@ rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], struct f_rndis *rndis; int status; - if (!can_support_rndis(c) || !ethaddr) - return -EINVAL; - - if (rndis_string_defs[0].id == 0) { - /* ... and setup RNDIS itself */ - status = rndis_init(); - if (status < 0) - return status; - - status = usb_string_ids_tab(c->cdev, rndis_string_defs); - if (status) - return status; - - rndis_control_intf.iInterface = rndis_string_defs[0].id; - rndis_data_intf.iInterface = rndis_string_defs[1].id; - rndis_iad_descriptor.iFunction = rndis_string_defs[2].id; - } - /* allocate and initialize one new instance */ status = -ENOMEM; rndis = kzalloc(sizeof *rndis, GFP_KERNEL); @@ -856,19 +874,178 @@ rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], rndis->port.unwrap = rndis_rm_hdr; rndis->port.func.name = "rndis"; - rndis->port.func.strings = rndis_strings; /* descriptors are per-instance copies */ rndis->port.func.bind = rndis_bind; - rndis->port.func.unbind = rndis_unbind; + rndis->port.func.unbind = rndis_old_unbind; rndis->port.func.set_alt = rndis_set_alt; rndis->port.func.setup = rndis_setup; rndis->port.func.disable = rndis_disable; status = usb_add_function(c, &rndis->port.func); - if (status) { + if (status) kfree(rndis); fail: - rndis_exit(); - } return status; } + +#else + +void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net) +{ + struct f_rndis_opts *opts; + + opts = container_of(f, struct f_rndis_opts, func_inst); + if (opts->bound) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + opts->borrowed_net = opts->bound = true; + opts->net = net; +} +EXPORT_SYMBOL(rndis_borrow_net); + +static inline struct f_rndis_opts *to_f_rndis_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_rndis_opts, + func_inst.group); +} + +/* f_rndis_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(rndis); + +/* f_rndis_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(rndis); + +/* f_rndis_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(rndis); + +/* f_rndis_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(rndis); + +/* f_rndis_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(rndis); + +static struct configfs_attribute *rndis_attrs[] = { + &f_rndis_opts_dev_addr.attr, + &f_rndis_opts_host_addr.attr, + &f_rndis_opts_qmult.attr, + &f_rndis_opts_ifname.attr, + NULL, +}; + +static struct config_item_type rndis_func_type = { + .ct_item_ops = &rndis_item_ops, + .ct_attrs = rndis_attrs, + .ct_owner = THIS_MODULE, +}; + +static void rndis_free_inst(struct usb_function_instance *f) +{ + struct f_rndis_opts *opts; + + opts = container_of(f, struct f_rndis_opts, func_inst); + if (!opts->borrowed_net) { + if (opts->bound) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + } + kfree(opts); +} + +static struct usb_function_instance *rndis_alloc_inst(void) +{ + struct f_rndis_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = rndis_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) + return ERR_CAST(opts->net); + + config_group_init_type_name(&opts->func_inst.group, "", + &rndis_func_type); + + return &opts->func_inst; +} + +static void rndis_free(struct usb_function *f) +{ + struct f_rndis *rndis; + struct f_rndis_opts *opts; + + rndis = func_to_rndis(f); + rndis_deregister(rndis->config); + opts = container_of(f->fi, struct f_rndis_opts, func_inst); + kfree(rndis); + mutex_lock(&opts->lock); + opts->refcnt--; + mutex_unlock(&opts->lock); +} + +static void rndis_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rndis *rndis = func_to_rndis(f); + + usb_free_all_descriptors(f); + + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); +} + +static struct usb_function *rndis_alloc(struct usb_function_instance *fi) +{ + struct f_rndis *rndis; + struct f_rndis_opts *opts; + int status; + + /* allocate and initialize one new instance */ + rndis = kzalloc(sizeof(*rndis), GFP_KERNEL); + if (!rndis) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_rndis_opts, func_inst); + mutex_lock(&opts->lock); + opts->refcnt++; + + gether_get_host_addr_u8(opts->net, rndis->ethaddr); + rndis->vendorID = opts->vendor_id; + rndis->manufacturer = opts->manufacturer; + + rndis->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); + /* RNDIS activates when the host changes this filter */ + rndis->port.cdc_filter = 0; + + /* RNDIS has special (and complex) framing */ + rndis->port.header_len = sizeof(struct rndis_packet_msg_type); + rndis->port.wrap = rndis_add_header; + rndis->port.unwrap = rndis_rm_hdr; + + rndis->port.func.name = "rndis"; + /* descriptors are per-instance copies */ + rndis->port.func.bind = rndis_bind; + rndis->port.func.unbind = rndis_unbind; + rndis->port.func.set_alt = rndis_set_alt; + rndis->port.func.setup = rndis_setup; + rndis->port.func.disable = rndis_disable; + rndis->port.func.free_func = rndis_free; + + status = rndis_register(rndis_response_available, rndis); + if (status < 0) { + kfree(rndis); + return ERR_PTR(status); + } + rndis->config = status; + + return &rndis->port.func; +} + +DECLARE_USB_FUNCTION_INIT(rndis, rndis_alloc_inst, rndis_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); + +#endif diff --git a/drivers/usb/gadget/f_subset.c b/drivers/usb/gadget/f_subset.c index 7be04b3..fbc7a24 100644 --- a/drivers/usb/gadget/f_subset.c +++ b/drivers/usb/gadget/f_subset.c @@ -12,11 +12,13 @@ #include <linux/slab.h> #include <linux/kernel.h> +#include <linux/module.h> #include <linux/device.h> #include <linux/etherdevice.h> #include "u_ether.h" - +#include "u_ether_configfs.h" +#include "u_gether.h" /* * This function packages a simple "CDC Subset" Ethernet port with no real @@ -295,9 +297,40 @@ geth_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_gether *geth = func_to_geth(f); + struct usb_string *us; int status; struct usb_ep *ep; +#ifndef USB_FSUBSET_INCLUDED + struct f_gether_opts *gether_opts; + + gether_opts = container_of(f->fi, struct f_gether_opts, func_inst); + + /* + * in drivers/usb/gadget/configfs.c:configfs_composite_bind() + * configurations are bound in sequence with list_for_each_entry, + * in each configuration its functions are bound in sequence + * with list_for_each_entry, so we assume no race condition + * with regard to gether_opts->bound access + */ + if (!gether_opts->bound) { + mutex_lock(&gether_opts->lock); + gether_set_gadget(gether_opts->net, cdev->gadget); + status = gether_register_netdev(gether_opts->net); + mutex_unlock(&gether_opts->lock); + if (status) + return status; + gether_opts->bound = true; + } +#endif + us = usb_gstrings_attach(cdev, geth_strings, + ARRAY_SIZE(geth_string_defs)); + if (IS_ERR(us)) + return PTR_ERR(us); + + subset_data_intf.iInterface = us[0].id; + ether_desc.iMACAddress = us[1].id; + /* allocate instance-specific interface IDs */ status = usb_interface_id(c, f); if (status < 0) @@ -360,8 +393,10 @@ fail: return status; } +#ifdef USB_FSUBSET_INCLUDED + static void -geth_unbind(struct usb_configuration *c, struct usb_function *f) +geth_old_unbind(struct usb_configuration *c, struct usb_function *f) { geth_string_defs[0].id = 0; usb_free_all_descriptors(f); @@ -387,18 +422,6 @@ int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], struct f_gether *geth; int status; - if (!ethaddr) - return -EINVAL; - - /* maybe allocate device-global string IDs */ - if (geth_string_defs[0].id == 0) { - status = usb_string_ids_tab(c->cdev, geth_string_defs); - if (status < 0) - return status; - subset_data_intf.iInterface = geth_string_defs[0].id; - ether_desc.iMACAddress = geth_string_defs[1].id; - } - /* allocate and initialize one new instance */ geth = kzalloc(sizeof *geth, GFP_KERNEL); if (!geth) @@ -412,9 +435,8 @@ int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], geth->port.cdc_filter = DEFAULT_FILTER; geth->port.func.name = "cdc_subset"; - geth->port.func.strings = geth_strings; geth->port.func.bind = geth_bind; - geth->port.func.unbind = geth_unbind; + geth->port.func.unbind = geth_old_unbind; geth->port.func.set_alt = geth_set_alt; geth->port.func.disable = geth_disable; @@ -423,3 +445,129 @@ int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], kfree(geth); return status; } + +#else + +static inline struct f_gether_opts *to_f_gether_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_gether_opts, + func_inst.group); +} + +/* f_gether_item_ops */ +USB_ETHERNET_CONFIGFS_ITEM(gether); + +/* f_gether_opts_dev_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(gether); + +/* f_gether_opts_host_addr */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(gether); + +/* f_gether_opts_qmult */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(gether); + +/* f_gether_opts_ifname */ +USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(gether); + +static struct configfs_attribute *gether_attrs[] = { + &f_gether_opts_dev_addr.attr, + &f_gether_opts_host_addr.attr, + &f_gether_opts_qmult.attr, + &f_gether_opts_ifname.attr, + NULL, +}; + +static struct config_item_type gether_func_type = { + .ct_item_ops = &gether_item_ops, + .ct_attrs = gether_attrs, + .ct_owner = THIS_MODULE, +}; + +static void geth_free_inst(struct usb_function_instance *f) +{ + struct f_gether_opts *opts; + + opts = container_of(f, struct f_gether_opts, func_inst); + if (opts->bound) + gether_cleanup(netdev_priv(opts->net)); + else + free_netdev(opts->net); + kfree(opts); +} + +static struct usb_function_instance *geth_alloc_inst(void) +{ + struct f_gether_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = geth_free_inst; + opts->net = gether_setup_default(); + if (IS_ERR(opts->net)) + return ERR_CAST(opts->net); + + config_group_init_type_name(&opts->func_inst.group, "", + &gether_func_type); + + return &opts->func_inst; +} + +static void geth_free(struct usb_function *f) +{ + struct f_gether *eth; + + eth = func_to_geth(f); + kfree(eth); +} + +static void geth_unbind(struct usb_configuration *c, struct usb_function *f) +{ + geth_string_defs[0].id = 0; + usb_free_all_descriptors(f); +} + +static struct usb_function *geth_alloc(struct usb_function_instance *fi) +{ + struct f_gether *geth; + struct f_gether_opts *opts; + int status; + + /* allocate and initialize one new instance */ + geth = kzalloc(sizeof(*geth), GFP_KERNEL); + if (!geth) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_gether_opts, func_inst); + + mutex_lock(&opts->lock); + opts->refcnt++; + /* export host's Ethernet address in CDC format */ + status = gether_get_host_addr_cdc(opts->net, geth->ethaddr, + sizeof(geth->ethaddr)); + if (status < 12) { + kfree(geth); + return ERR_PTR(-EINVAL); + } + geth_string_defs[1].s = geth->ethaddr; + + geth->port.ioport = netdev_priv(opts->net); + mutex_unlock(&opts->lock); + geth->port.cdc_filter = DEFAULT_FILTER; + + geth->port.func.name = "cdc_subset"; + geth->port.func.bind = geth_bind; + geth->port.func.unbind = geth_unbind; + geth->port.func.set_alt = geth_set_alt; + geth->port.func.disable = geth_disable; + geth->port.func.free_func = geth_free; + + return &geth->port.func; +} + +DECLARE_USB_FUNCTION_INIT(geth, geth_alloc_inst, geth_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); + +#endif diff --git a/drivers/usb/gadget/f_uac2.c b/drivers/usb/gadget/f_uac2.c index 03c1fb6..2f23566 100644 --- a/drivers/usb/gadget/f_uac2.c +++ b/drivers/usb/gadget/f_uac2.c @@ -90,6 +90,7 @@ struct uac2_req { }; struct uac2_rtd_params { + struct snd_uac2_chip *uac2; /* parent chip */ bool ep_enabled; /* if the ep is enabled */ /* Size of the ring buffer */ size_t dma_bytes; @@ -169,18 +170,6 @@ struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p) } static inline -struct snd_uac2_chip *prm_to_uac2(struct uac2_rtd_params *r) -{ - struct snd_uac2_chip *uac2 = container_of(r, - struct snd_uac2_chip, c_prm); - - if (&uac2->c_prm != r) - uac2 = container_of(r, struct snd_uac2_chip, p_prm); - - return uac2; -} - -static inline uint num_channels(uint chanmask) { uint num = 0; @@ -204,7 +193,7 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req) struct uac2_req *ur = req->context; struct snd_pcm_substream *substream; struct uac2_rtd_params *prm = ur->pp; - struct snd_uac2_chip *uac2 = prm_to_uac2(prm); + struct snd_uac2_chip *uac2 = prm->uac2; /* i/f shutting down */ if (!prm->ep_enabled) @@ -894,7 +883,7 @@ struct cntrl_range_lay3 { static inline void free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep) { - struct snd_uac2_chip *uac2 = prm_to_uac2(prm); + struct snd_uac2_chip *uac2 = prm->uac2; int i; prm->ep_enabled = false; @@ -970,6 +959,9 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) } agdev->in_ep->driver_data = agdev; + uac2->p_prm.uac2 = uac2; + uac2->c_prm.uac2 = uac2; + hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; hs_epout_desc.wMaxPacketSize = fs_epout_desc.wMaxPacketSize; hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; diff --git a/drivers/usb/gadget/f_uvc.c b/drivers/usb/gadget/f_uvc.c index 38dcedd..5f91c7a 100644 --- a/drivers/usb/gadget/f_uvc.c +++ b/drivers/usb/gadget/f_uvc.c @@ -156,8 +156,6 @@ static struct usb_endpoint_descriptor uvc_fs_streaming_ep __initdata = { /* The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ - .wMaxPacketSize = 0, - .bInterval = 0, }; static struct usb_endpoint_descriptor uvc_hs_streaming_ep __initdata = { @@ -169,8 +167,6 @@ static struct usb_endpoint_descriptor uvc_hs_streaming_ep __initdata = { /* The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ - .wMaxPacketSize = 0, - .bInterval = 0, }; static struct usb_endpoint_descriptor uvc_ss_streaming_ep __initdata = { @@ -183,17 +179,14 @@ static struct usb_endpoint_descriptor uvc_ss_streaming_ep __initdata = { /* The wMaxPacketSize and bInterval values will be initialized from * module parameters. */ - .wMaxPacketSize = 0, - .bInterval = 0, }; static struct usb_ss_ep_comp_descriptor uvc_ss_streaming_comp __initdata = { .bLength = sizeof(uvc_ss_streaming_comp), .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - /* The following 3 values can be tweaked if necessary. */ - .bMaxBurst = 0, - .bmAttributes = 0, - .wBytesPerInterval = cpu_to_le16(1024), + /* The bMaxBurst, bmAttributes and wBytesPerInterval values will be + * initialized from module parameters. + */ }; static const struct usb_descriptor_header * const uvc_fs_streaming[] = { diff --git a/drivers/usb/gadget/fotg210-udc.c b/drivers/usb/gadget/fotg210-udc.c new file mode 100644 index 0000000..cce5535 --- /dev/null +++ b/drivers/usb/gadget/fotg210-udc.c @@ -0,0 +1,1219 @@ +/* + * FOTG210 UDC Driver supports Bulk transfer so far + * + * Copyright (C) 2013 Faraday Technology Corporation + * + * Author : Yuan-Hsin Chen <yhchen@faraday-tech.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include "fotg210.h" + +#define DRIVER_DESC "FOTG210 USB Device Controller Driver" +#define DRIVER_VERSION "30-April-2013" + +static const char udc_name[] = "fotg210_udc"; +static const char * const fotg210_ep_name[] = { + "ep0", "ep1", "ep2", "ep3", "ep4"}; + +static void fotg210_disable_fifo_int(struct fotg210_ep *ep) +{ + u32 value = ioread32(ep->fotg210->reg + FOTG210_DMISGR1); + + if (ep->dir_in) + value |= DMISGR1_MF_IN_INT(ep->epnum - 1); + else + value |= DMISGR1_MF_OUTSPK_INT(ep->epnum - 1); + iowrite32(value, ep->fotg210->reg + FOTG210_DMISGR1); +} + +static void fotg210_enable_fifo_int(struct fotg210_ep *ep) +{ + u32 value = ioread32(ep->fotg210->reg + FOTG210_DMISGR1); + + if (ep->dir_in) + value &= ~DMISGR1_MF_IN_INT(ep->epnum - 1); + else + value &= ~DMISGR1_MF_OUTSPK_INT(ep->epnum - 1); + iowrite32(value, ep->fotg210->reg + FOTG210_DMISGR1); +} + +static void fotg210_set_cxdone(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DCFESR); + + value |= DCFESR_CX_DONE; + iowrite32(value, fotg210->reg + FOTG210_DCFESR); +} + +static void fotg210_done(struct fotg210_ep *ep, struct fotg210_request *req, + int status) +{ + list_del_init(&req->queue); + + /* don't modify queue heads during completion callback */ + if (ep->fotg210->gadget.speed == USB_SPEED_UNKNOWN) + req->req.status = -ESHUTDOWN; + else + req->req.status = status; + + spin_unlock(&ep->fotg210->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->fotg210->lock); + + if (ep->epnum) { + if (list_empty(&ep->queue)) + fotg210_disable_fifo_int(ep); + } else { + fotg210_set_cxdone(ep->fotg210); + } +} + +static void fotg210_fifo_ep_mapping(struct fotg210_ep *ep, u32 epnum, + u32 dir_in) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 val; + + /* Driver should map an ep to a fifo and then map the fifo + * to the ep. What a brain-damaged design! + */ + + /* map a fifo to an ep */ + val = ioread32(fotg210->reg + FOTG210_EPMAP); + val &= ~EPMAP_FIFONOMSK(epnum, dir_in); + val |= EPMAP_FIFONO(epnum, dir_in); + iowrite32(val, fotg210->reg + FOTG210_EPMAP); + + /* map the ep to the fifo */ + val = ioread32(fotg210->reg + FOTG210_FIFOMAP); + val &= ~FIFOMAP_EPNOMSK(epnum); + val |= FIFOMAP_EPNO(epnum); + iowrite32(val, fotg210->reg + FOTG210_FIFOMAP); + + /* enable fifo */ + val = ioread32(fotg210->reg + FOTG210_FIFOCF); + val |= FIFOCF_FIFO_EN(epnum - 1); + iowrite32(val, fotg210->reg + FOTG210_FIFOCF); +} + +static void fotg210_set_fifo_dir(struct fotg210_ep *ep, u32 epnum, u32 dir_in) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 val; + + val = ioread32(fotg210->reg + FOTG210_FIFOMAP); + val |= (dir_in ? FIFOMAP_DIRIN(epnum - 1) : FIFOMAP_DIROUT(epnum - 1)); + iowrite32(val, fotg210->reg + FOTG210_FIFOMAP); +} + +static void fotg210_set_tfrtype(struct fotg210_ep *ep, u32 epnum, u32 type) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 val; + + val = ioread32(fotg210->reg + FOTG210_FIFOCF); + val |= FIFOCF_TYPE(type, epnum - 1); + iowrite32(val, fotg210->reg + FOTG210_FIFOCF); +} + +static void fotg210_set_mps(struct fotg210_ep *ep, u32 epnum, u32 mps, + u32 dir_in) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 val; + u32 offset = dir_in ? FOTG210_INEPMPSR(epnum) : + FOTG210_OUTEPMPSR(epnum); + + val = ioread32(fotg210->reg + offset); + val |= INOUTEPMPSR_MPS(mps); + iowrite32(val, fotg210->reg + offset); +} + +static int fotg210_config_ep(struct fotg210_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + + fotg210_set_fifo_dir(ep, ep->epnum, ep->dir_in); + fotg210_set_tfrtype(ep, ep->epnum, ep->type); + fotg210_set_mps(ep, ep->epnum, ep->ep.maxpacket, ep->dir_in); + fotg210_fifo_ep_mapping(ep, ep->epnum, ep->dir_in); + + fotg210->ep[ep->epnum] = ep; + + return 0; +} + +static int fotg210_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct fotg210_ep *ep; + + ep = container_of(_ep, struct fotg210_ep, ep); + + ep->desc = desc; + ep->epnum = usb_endpoint_num(desc); + ep->type = usb_endpoint_type(desc); + ep->dir_in = usb_endpoint_dir_in(desc); + ep->ep.maxpacket = usb_endpoint_maxp(desc); + + return fotg210_config_ep(ep, desc); +} + +static void fotg210_reset_tseq(struct fotg210_udc *fotg210, u8 epnum) +{ + struct fotg210_ep *ep = fotg210->ep[epnum]; + u32 value; + void __iomem *reg; + + reg = (ep->dir_in) ? + fotg210->reg + FOTG210_INEPMPSR(epnum) : + fotg210->reg + FOTG210_OUTEPMPSR(epnum); + + /* Note: Driver needs to set and clear INOUTEPMPSR_RESET_TSEQ + * bit. Controller wouldn't clear this bit. WTF!!! + */ + + value = ioread32(reg); + value |= INOUTEPMPSR_RESET_TSEQ; + iowrite32(value, reg); + + value = ioread32(reg); + value &= ~INOUTEPMPSR_RESET_TSEQ; + iowrite32(value, reg); +} + +static int fotg210_ep_release(struct fotg210_ep *ep) +{ + if (!ep->epnum) + return 0; + ep->epnum = 0; + ep->stall = 0; + ep->wedged = 0; + + fotg210_reset_tseq(ep->fotg210, ep->epnum); + + return 0; +} + +static int fotg210_ep_disable(struct usb_ep *_ep) +{ + struct fotg210_ep *ep; + struct fotg210_request *req; + unsigned long flags; + + BUG_ON(!_ep); + + ep = container_of(_ep, struct fotg210_ep, ep); + + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct fotg210_request, queue); + spin_lock_irqsave(&ep->fotg210->lock, flags); + fotg210_done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->fotg210->lock, flags); + } + + return fotg210_ep_release(ep); +} + +static struct usb_request *fotg210_ep_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct fotg210_request *req; + + req = kzalloc(sizeof(struct fotg210_request), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void fotg210_ep_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct fotg210_request *req; + + req = container_of(_req, struct fotg210_request, req); + kfree(req); +} + +static void fotg210_enable_dma(struct fotg210_ep *ep, + dma_addr_t d, u32 len) +{ + u32 value; + struct fotg210_udc *fotg210 = ep->fotg210; + + /* set transfer length and direction */ + value = ioread32(fotg210->reg + FOTG210_DMACPSR1); + value &= ~(DMACPSR1_DMA_LEN(0xFFFF) | DMACPSR1_DMA_TYPE(1)); + value |= DMACPSR1_DMA_LEN(len) | DMACPSR1_DMA_TYPE(ep->dir_in); + iowrite32(value, fotg210->reg + FOTG210_DMACPSR1); + + /* set device DMA target FIFO number */ + value = ioread32(fotg210->reg + FOTG210_DMATFNR); + if (ep->epnum) + value |= DMATFNR_ACC_FN(ep->epnum - 1); + else + value |= DMATFNR_ACC_CXF; + iowrite32(value, fotg210->reg + FOTG210_DMATFNR); + + /* set DMA memory address */ + iowrite32(d, fotg210->reg + FOTG210_DMACPSR2); + + /* enable MDMA_EROR and MDMA_CMPLT interrupt */ + value = ioread32(fotg210->reg + FOTG210_DMISGR2); + value &= ~(DMISGR2_MDMA_CMPLT | DMISGR2_MDMA_ERROR); + iowrite32(value, fotg210->reg + FOTG210_DMISGR2); + + /* start DMA */ + value = ioread32(fotg210->reg + FOTG210_DMACPSR1); + value |= DMACPSR1_DMA_START; + iowrite32(value, fotg210->reg + FOTG210_DMACPSR1); +} + +static void fotg210_disable_dma(struct fotg210_ep *ep) +{ + iowrite32(DMATFNR_DISDMA, ep->fotg210->reg + FOTG210_DMATFNR); +} + +static void fotg210_wait_dma_done(struct fotg210_ep *ep) +{ + u32 value; + + do { + value = ioread32(ep->fotg210->reg + FOTG210_DISGR2); + if ((value & DISGR2_USBRST_INT) || + (value & DISGR2_DMA_ERROR)) + goto dma_reset; + } while (!(value & DISGR2_DMA_CMPLT)); + + value &= ~DISGR2_DMA_CMPLT; + iowrite32(value, ep->fotg210->reg + FOTG210_DISGR2); + return; + +dma_reset: + value = ioread32(ep->fotg210->reg + FOTG210_DMACPSR1); + value |= DMACPSR1_DMA_ABORT; + iowrite32(value, ep->fotg210->reg + FOTG210_DMACPSR1); + + /* reset fifo */ + if (ep->epnum) { + value = ioread32(ep->fotg210->reg + + FOTG210_FIBCR(ep->epnum - 1)); + value |= FIBCR_FFRST; + iowrite32(value, ep->fotg210->reg + + FOTG210_FIBCR(ep->epnum - 1)); + } else { + value = ioread32(ep->fotg210->reg + FOTG210_DCFESR); + value |= DCFESR_CX_CLR; + iowrite32(value, ep->fotg210->reg + FOTG210_DCFESR); + } +} + +static void fotg210_start_dma(struct fotg210_ep *ep, + struct fotg210_request *req) +{ + dma_addr_t d; + u8 *buffer; + u32 length; + + if (ep->epnum) { + if (ep->dir_in) { + buffer = req->req.buf; + length = req->req.length; + } else { + buffer = req->req.buf + req->req.actual; + length = ioread32(ep->fotg210->reg + + FOTG210_FIBCR(ep->epnum - 1)); + length &= FIBCR_BCFX; + } + } else { + buffer = req->req.buf + req->req.actual; + if (req->req.length - req->req.actual > ep->ep.maxpacket) + length = ep->ep.maxpacket; + else + length = req->req.length; + } + + d = dma_map_single(NULL, buffer, length, + ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (dma_mapping_error(NULL, d)) { + pr_err("dma_mapping_error\n"); + return; + } + + dma_sync_single_for_device(NULL, d, length, + ep->dir_in ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + + fotg210_enable_dma(ep, d, length); + + /* check if dma is done */ + fotg210_wait_dma_done(ep); + + fotg210_disable_dma(ep); + + /* update actual transfer length */ + req->req.actual += length; + + dma_unmap_single(NULL, d, length, DMA_TO_DEVICE); +} + +static void fotg210_ep0_queue(struct fotg210_ep *ep, + struct fotg210_request *req) +{ + if (!req->req.length) { + fotg210_done(ep, req, 0); + return; + } + if (ep->dir_in) { /* if IN */ + if (req->req.length) { + fotg210_start_dma(ep, req); + } else { + pr_err("%s : req->req.length = 0x%x\n", + __func__, req->req.length); + } + if ((req->req.length == req->req.actual) || + (req->req.actual < ep->ep.maxpacket)) + fotg210_done(ep, req, 0); + } else { /* OUT */ + if (!req->req.length) { + fotg210_done(ep, req, 0); + } else { + u32 value = ioread32(ep->fotg210->reg + + FOTG210_DMISGR0); + + value &= ~DMISGR0_MCX_OUT_INT; + iowrite32(value, ep->fotg210->reg + FOTG210_DMISGR0); + } + } +} + +static int fotg210_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct fotg210_ep *ep; + struct fotg210_request *req; + unsigned long flags; + int request = 0; + + ep = container_of(_ep, struct fotg210_ep, ep); + req = container_of(_req, struct fotg210_request, req); + + if (ep->fotg210->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&ep->fotg210->lock, flags); + + if (list_empty(&ep->queue)) + request = 1; + + list_add_tail(&req->queue, &ep->queue); + + req->req.actual = 0; + req->req.status = -EINPROGRESS; + + if (!ep->epnum) /* ep0 */ + fotg210_ep0_queue(ep, req); + else if (request && !ep->stall) + fotg210_enable_fifo_int(ep); + + spin_unlock_irqrestore(&ep->fotg210->lock, flags); + + return 0; +} + +static int fotg210_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct fotg210_ep *ep; + struct fotg210_request *req; + unsigned long flags; + + ep = container_of(_ep, struct fotg210_ep, ep); + req = container_of(_req, struct fotg210_request, req); + + spin_lock_irqsave(&ep->fotg210->lock, flags); + if (!list_empty(&ep->queue)) + fotg210_done(ep, req, -ECONNRESET); + spin_unlock_irqrestore(&ep->fotg210->lock, flags); + + return 0; +} + +static void fotg210_set_epnstall(struct fotg210_ep *ep) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 value; + void __iomem *reg; + + /* check if IN FIFO is empty before stall */ + if (ep->dir_in) { + do { + value = ioread32(fotg210->reg + FOTG210_DCFESR); + } while (!(value & DCFESR_FIFO_EMPTY(ep->epnum - 1))); + } + + reg = (ep->dir_in) ? + fotg210->reg + FOTG210_INEPMPSR(ep->epnum) : + fotg210->reg + FOTG210_OUTEPMPSR(ep->epnum); + value = ioread32(reg); + value |= INOUTEPMPSR_STL_EP; + iowrite32(value, reg); +} + +static void fotg210_clear_epnstall(struct fotg210_ep *ep) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 value; + void __iomem *reg; + + reg = (ep->dir_in) ? + fotg210->reg + FOTG210_INEPMPSR(ep->epnum) : + fotg210->reg + FOTG210_OUTEPMPSR(ep->epnum); + value = ioread32(reg); + value &= ~INOUTEPMPSR_STL_EP; + iowrite32(value, reg); +} + +static int fotg210_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedge) +{ + struct fotg210_ep *ep; + struct fotg210_udc *fotg210; + unsigned long flags; + int ret = 0; + + ep = container_of(_ep, struct fotg210_ep, ep); + + fotg210 = ep->fotg210; + + spin_lock_irqsave(&ep->fotg210->lock, flags); + + if (value) { + fotg210_set_epnstall(ep); + ep->stall = 1; + if (wedge) + ep->wedged = 1; + } else { + fotg210_reset_tseq(fotg210, ep->epnum); + fotg210_clear_epnstall(ep); + ep->stall = 0; + ep->wedged = 0; + if (!list_empty(&ep->queue)) + fotg210_enable_fifo_int(ep); + } + + spin_unlock_irqrestore(&ep->fotg210->lock, flags); + return ret; +} + +static int fotg210_ep_set_halt(struct usb_ep *_ep, int value) +{ + return fotg210_set_halt_and_wedge(_ep, value, 0); +} + +static int fotg210_ep_set_wedge(struct usb_ep *_ep) +{ + return fotg210_set_halt_and_wedge(_ep, 1, 1); +} + +static void fotg210_ep_fifo_flush(struct usb_ep *_ep) +{ +} + +static struct usb_ep_ops fotg210_ep_ops = { + .enable = fotg210_ep_enable, + .disable = fotg210_ep_disable, + + .alloc_request = fotg210_ep_alloc_request, + .free_request = fotg210_ep_free_request, + + .queue = fotg210_ep_queue, + .dequeue = fotg210_ep_dequeue, + + .set_halt = fotg210_ep_set_halt, + .fifo_flush = fotg210_ep_fifo_flush, + .set_wedge = fotg210_ep_set_wedge, +}; + +static void fotg210_clear_tx0byte(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_TX0BYTE); + + value &= ~(TX0BYTE_EP1 | TX0BYTE_EP2 | TX0BYTE_EP3 + | TX0BYTE_EP4); + iowrite32(value, fotg210->reg + FOTG210_TX0BYTE); +} + +static void fotg210_clear_rx0byte(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_RX0BYTE); + + value &= ~(RX0BYTE_EP1 | RX0BYTE_EP2 | RX0BYTE_EP3 + | RX0BYTE_EP4); + iowrite32(value, fotg210->reg + FOTG210_RX0BYTE); +} + +/* read 8-byte setup packet only */ +static void fotg210_rdsetupp(struct fotg210_udc *fotg210, + u8 *buffer) +{ + int i = 0; + u8 *tmp = buffer; + u32 data; + u32 length = 8; + + iowrite32(DMATFNR_ACC_CXF, fotg210->reg + FOTG210_DMATFNR); + + for (i = (length >> 2); i > 0; i--) { + data = ioread32(fotg210->reg + FOTG210_CXPORT); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + *(tmp + 2) = (data >> 16) & 0xFF; + *(tmp + 3) = (data >> 24) & 0xFF; + tmp = tmp + 4; + } + + switch (length % 4) { + case 1: + data = ioread32(fotg210->reg + FOTG210_CXPORT); + *tmp = data & 0xFF; + break; + case 2: + data = ioread32(fotg210->reg + FOTG210_CXPORT); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + break; + case 3: + data = ioread32(fotg210->reg + FOTG210_CXPORT); + *tmp = data & 0xFF; + *(tmp + 1) = (data >> 8) & 0xFF; + *(tmp + 2) = (data >> 16) & 0xFF; + break; + default: + break; + } + + iowrite32(DMATFNR_DISDMA, fotg210->reg + FOTG210_DMATFNR); +} + +static void fotg210_set_configuration(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DAR); + + value |= DAR_AFT_CONF; + iowrite32(value, fotg210->reg + FOTG210_DAR); +} + +static void fotg210_set_dev_addr(struct fotg210_udc *fotg210, u32 addr) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DAR); + + value |= (addr & 0x7F); + iowrite32(value, fotg210->reg + FOTG210_DAR); +} + +static void fotg210_set_cxstall(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DCFESR); + + value |= DCFESR_CX_STL; + iowrite32(value, fotg210->reg + FOTG210_DCFESR); +} + +static void fotg210_request_error(struct fotg210_udc *fotg210) +{ + fotg210_set_cxstall(fotg210); + pr_err("request error!!\n"); +} + +static void fotg210_set_address(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + if (ctrl->wValue >= 0x0100) { + fotg210_request_error(fotg210); + } else { + fotg210_set_dev_addr(fotg210, ctrl->wValue); + fotg210_set_cxdone(fotg210); + } +} + +static void fotg210_set_feature(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + fotg210_set_cxdone(fotg210); + break; + case USB_RECIP_INTERFACE: + fotg210_set_cxdone(fotg210); + break; + case USB_RECIP_ENDPOINT: { + u8 epnum; + epnum = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + if (epnum) + fotg210_set_epnstall(fotg210->ep[epnum]); + else + fotg210_set_cxstall(fotg210); + fotg210_set_cxdone(fotg210); + } + break; + default: + fotg210_request_error(fotg210); + break; + } +} + +static void fotg210_clear_feature(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + struct fotg210_ep *ep = + fotg210->ep[ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK]; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + fotg210_set_cxdone(fotg210); + break; + case USB_RECIP_INTERFACE: + fotg210_set_cxdone(fotg210); + break; + case USB_RECIP_ENDPOINT: + if (ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK) { + if (ep->wedged) { + fotg210_set_cxdone(fotg210); + break; + } + if (ep->stall) + fotg210_set_halt_and_wedge(&ep->ep, 0, 0); + } + fotg210_set_cxdone(fotg210); + break; + default: + fotg210_request_error(fotg210); + break; + } +} + +static int fotg210_is_epnstall(struct fotg210_ep *ep) +{ + struct fotg210_udc *fotg210 = ep->fotg210; + u32 value; + void __iomem *reg; + + reg = (ep->dir_in) ? + fotg210->reg + FOTG210_INEPMPSR(ep->epnum) : + fotg210->reg + FOTG210_OUTEPMPSR(ep->epnum); + value = ioread32(reg); + return value & INOUTEPMPSR_STL_EP ? 1 : 0; +} + +static void fotg210_get_status(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + u8 epnum; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + fotg210->ep0_data = 1 << USB_DEVICE_SELF_POWERED; + break; + case USB_RECIP_INTERFACE: + fotg210->ep0_data = 0; + break; + case USB_RECIP_ENDPOINT: + epnum = ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK; + if (epnum) + fotg210->ep0_data = + fotg210_is_epnstall(fotg210->ep[epnum]) + << USB_ENDPOINT_HALT; + else + fotg210_request_error(fotg210); + break; + + default: + fotg210_request_error(fotg210); + return; /* exit */ + } + + fotg210->ep0_req->buf = &fotg210->ep0_data; + fotg210->ep0_req->length = 2; + + spin_unlock(&fotg210->lock); + fotg210_ep_queue(fotg210->gadget.ep0, fotg210->ep0_req, GFP_KERNEL); + spin_lock(&fotg210->lock); +} + +static int fotg210_setup_packet(struct fotg210_udc *fotg210, + struct usb_ctrlrequest *ctrl) +{ + u8 *p = (u8 *)ctrl; + u8 ret = 0; + + fotg210_rdsetupp(fotg210, p); + + fotg210->ep[0]->dir_in = ctrl->bRequestType & USB_DIR_IN; + + if (fotg210->gadget.speed == USB_SPEED_UNKNOWN) { + u32 value = ioread32(fotg210->reg + FOTG210_DMCR); + fotg210->gadget.speed = value & DMCR_HS_EN ? + USB_SPEED_HIGH : USB_SPEED_FULL; + } + + /* check request */ + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + fotg210_get_status(fotg210, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + fotg210_clear_feature(fotg210, ctrl); + break; + case USB_REQ_SET_FEATURE: + fotg210_set_feature(fotg210, ctrl); + break; + case USB_REQ_SET_ADDRESS: + fotg210_set_address(fotg210, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + fotg210_set_configuration(fotg210); + ret = 1; + break; + default: + ret = 1; + break; + } + } else { + ret = 1; + } + + return ret; +} + +static void fotg210_ep0out(struct fotg210_udc *fotg210) +{ + struct fotg210_ep *ep = fotg210->ep[0]; + + if (!list_empty(&ep->queue) && !ep->dir_in) { + struct fotg210_request *req; + + req = list_first_entry(&ep->queue, + struct fotg210_request, queue); + + if (req->req.length) + fotg210_start_dma(ep, req); + + if ((req->req.length - req->req.actual) < ep->ep.maxpacket) + fotg210_done(ep, req, 0); + } else { + pr_err("%s : empty queue\n", __func__); + } +} + +static void fotg210_ep0in(struct fotg210_udc *fotg210) +{ + struct fotg210_ep *ep = fotg210->ep[0]; + + if ((!list_empty(&ep->queue)) && (ep->dir_in)) { + struct fotg210_request *req; + + req = list_entry(ep->queue.next, + struct fotg210_request, queue); + + if (req->req.length) + fotg210_start_dma(ep, req); + + if ((req->req.length - req->req.actual) < ep->ep.maxpacket) + fotg210_done(ep, req, 0); + } else { + fotg210_set_cxdone(fotg210); + } +} + +static void fotg210_clear_comabt_int(struct fotg210_udc *fotg210) +{ + u32 value = ioread32(fotg210->reg + FOTG210_DISGR0); + + value &= ~DISGR0_CX_COMABT_INT; + iowrite32(value, fotg210->reg + FOTG210_DISGR0); +} + +static void fotg210_in_fifo_handler(struct fotg210_ep *ep) +{ + struct fotg210_request *req = list_entry(ep->queue.next, + struct fotg210_request, queue); + + if (req->req.length) + fotg210_start_dma(ep, req); + fotg210_done(ep, req, 0); +} + +static void fotg210_out_fifo_handler(struct fotg210_ep *ep) +{ + struct fotg210_request *req = list_entry(ep->queue.next, + struct fotg210_request, queue); + + fotg210_start_dma(ep, req); + + /* finish out transfer */ + if (req->req.length == req->req.actual || + req->req.actual < ep->ep.maxpacket) + fotg210_done(ep, req, 0); +} + +static irqreturn_t fotg210_irq(int irq, void *_fotg210) +{ + struct fotg210_udc *fotg210 = _fotg210; + u32 int_grp = ioread32(fotg210->reg + FOTG210_DIGR); + u32 int_msk = ioread32(fotg210->reg + FOTG210_DMIGR); + + int_grp &= ~int_msk; + + spin_lock(&fotg210->lock); + + if (int_grp & DIGR_INT_G2) { + void __iomem *reg = fotg210->reg + FOTG210_DISGR2; + u32 int_grp2 = ioread32(reg); + u32 int_msk2 = ioread32(fotg210->reg + FOTG210_DMISGR2); + u32 value; + + int_grp2 &= ~int_msk2; + + if (int_grp2 & DISGR2_USBRST_INT) { + value = ioread32(reg); + value &= ~DISGR2_USBRST_INT; + iowrite32(value, reg); + pr_info("fotg210 udc reset\n"); + } + if (int_grp2 & DISGR2_SUSP_INT) { + value = ioread32(reg); + value &= ~DISGR2_SUSP_INT; + iowrite32(value, reg); + pr_info("fotg210 udc suspend\n"); + } + if (int_grp2 & DISGR2_RESM_INT) { + value = ioread32(reg); + value &= ~DISGR2_RESM_INT; + iowrite32(value, reg); + pr_info("fotg210 udc resume\n"); + } + if (int_grp2 & DISGR2_ISO_SEQ_ERR_INT) { + value = ioread32(reg); + value &= ~DISGR2_ISO_SEQ_ERR_INT; + iowrite32(value, reg); + pr_info("fotg210 iso sequence error\n"); + } + if (int_grp2 & DISGR2_ISO_SEQ_ABORT_INT) { + value = ioread32(reg); + value &= ~DISGR2_ISO_SEQ_ABORT_INT; + iowrite32(value, reg); + pr_info("fotg210 iso sequence abort\n"); + } + if (int_grp2 & DISGR2_TX0BYTE_INT) { + fotg210_clear_tx0byte(fotg210); + value = ioread32(reg); + value &= ~DISGR2_TX0BYTE_INT; + iowrite32(value, reg); + pr_info("fotg210 transferred 0 byte\n"); + } + if (int_grp2 & DISGR2_RX0BYTE_INT) { + fotg210_clear_rx0byte(fotg210); + value = ioread32(reg); + value &= ~DISGR2_RX0BYTE_INT; + iowrite32(value, reg); + pr_info("fotg210 received 0 byte\n"); + } + if (int_grp2 & DISGR2_DMA_ERROR) { + value = ioread32(reg); + value &= ~DISGR2_DMA_ERROR; + iowrite32(value, reg); + } + } + + if (int_grp & DIGR_INT_G0) { + void __iomem *reg = fotg210->reg + FOTG210_DISGR0; + u32 int_grp0 = ioread32(reg); + u32 int_msk0 = ioread32(fotg210->reg + FOTG210_DMISGR0); + struct usb_ctrlrequest ctrl; + + int_grp0 &= ~int_msk0; + + /* the highest priority in this source register */ + if (int_grp0 & DISGR0_CX_COMABT_INT) { + fotg210_clear_comabt_int(fotg210); + pr_info("fotg210 CX command abort\n"); + } + + if (int_grp0 & DISGR0_CX_SETUP_INT) { + if (fotg210_setup_packet(fotg210, &ctrl)) { + spin_unlock(&fotg210->lock); + if (fotg210->driver->setup(&fotg210->gadget, + &ctrl) < 0) + fotg210_set_cxstall(fotg210); + spin_lock(&fotg210->lock); + } + } + if (int_grp0 & DISGR0_CX_COMEND_INT) + pr_info("fotg210 cmd end\n"); + + if (int_grp0 & DISGR0_CX_IN_INT) + fotg210_ep0in(fotg210); + + if (int_grp0 & DISGR0_CX_OUT_INT) + fotg210_ep0out(fotg210); + + if (int_grp0 & DISGR0_CX_COMFAIL_INT) { + fotg210_set_cxstall(fotg210); + pr_info("fotg210 ep0 fail\n"); + } + } + + if (int_grp & DIGR_INT_G1) { + void __iomem *reg = fotg210->reg + FOTG210_DISGR1; + u32 int_grp1 = ioread32(reg); + u32 int_msk1 = ioread32(fotg210->reg + FOTG210_DMISGR1); + int fifo; + + int_grp1 &= ~int_msk1; + + for (fifo = 0; fifo < FOTG210_MAX_FIFO_NUM; fifo++) { + if (int_grp1 & DISGR1_IN_INT(fifo)) + fotg210_in_fifo_handler(fotg210->ep[fifo + 1]); + + if ((int_grp1 & DISGR1_OUT_INT(fifo)) || + (int_grp1 & DISGR1_SPK_INT(fifo))) + fotg210_out_fifo_handler(fotg210->ep[fifo + 1]); + } + } + + spin_unlock(&fotg210->lock); + + return IRQ_HANDLED; +} + +static void fotg210_disable_unplug(struct fotg210_udc *fotg210) +{ + u32 reg = ioread32(fotg210->reg + FOTG210_PHYTMSR); + + reg &= ~PHYTMSR_UNPLUG; + iowrite32(reg, fotg210->reg + FOTG210_PHYTMSR); +} + +static int fotg210_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct fotg210_udc *fotg210 = gadget_to_fotg210(g); + u32 value; + + /* hook up the driver */ + driver->driver.bus = NULL; + fotg210->driver = driver; + + /* enable device global interrupt */ + value = ioread32(fotg210->reg + FOTG210_DMCR); + value |= DMCR_GLINT_EN; + iowrite32(value, fotg210->reg + FOTG210_DMCR); + + return 0; +} + +static void fotg210_init(struct fotg210_udc *fotg210) +{ + u32 value; + + /* disable global interrupt and set int polarity to active high */ + iowrite32(GMIR_MHC_INT | GMIR_MOTG_INT | GMIR_INT_POLARITY, + fotg210->reg + FOTG210_GMIR); + + /* disable device global interrupt */ + value = ioread32(fotg210->reg + FOTG210_DMCR); + value &= ~DMCR_GLINT_EN; + iowrite32(value, fotg210->reg + FOTG210_DMCR); + + /* disable all fifo interrupt */ + iowrite32(~(u32)0, fotg210->reg + FOTG210_DMISGR1); + + /* disable cmd end */ + value = ioread32(fotg210->reg + FOTG210_DMISGR0); + value |= DMISGR0_MCX_COMEND; + iowrite32(value, fotg210->reg + FOTG210_DMISGR0); +} + +static int fotg210_udc_stop(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct fotg210_udc *fotg210 = gadget_to_fotg210(g); + unsigned long flags; + + spin_lock_irqsave(&fotg210->lock, flags); + + fotg210_init(fotg210); + fotg210->driver = NULL; + + spin_unlock_irqrestore(&fotg210->lock, flags); + + return 0; +} + +static struct usb_gadget_ops fotg210_gadget_ops = { + .udc_start = fotg210_udc_start, + .udc_stop = fotg210_udc_stop, +}; + +static int __exit fotg210_udc_remove(struct platform_device *pdev) +{ + struct fotg210_udc *fotg210 = dev_get_drvdata(&pdev->dev); + + usb_del_gadget_udc(&fotg210->gadget); + iounmap(fotg210->reg); + free_irq(platform_get_irq(pdev, 0), fotg210); + + fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req); + kfree(fotg210); + + return 0; +} + +static int __init fotg210_udc_probe(struct platform_device *pdev) +{ + struct resource *res, *ires; + struct fotg210_udc *fotg210 = NULL; + struct fotg210_ep *_ep[FOTG210_MAX_NUM_EP]; + int ret = 0; + int i; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + pr_err("platform_get_resource error.\n"); + return -ENODEV; + } + + ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!ires) { + pr_err("platform_get_resource IORESOURCE_IRQ error.\n"); + return -ENODEV; + } + + ret = -ENOMEM; + + /* initialize udc */ + fotg210 = kzalloc(sizeof(struct fotg210_udc), GFP_KERNEL); + if (fotg210 == NULL) { + pr_err("kzalloc error\n"); + goto err_alloc; + } + + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { + _ep[i] = kzalloc(sizeof(struct fotg210_ep), GFP_KERNEL); + if (_ep[i] == NULL) { + pr_err("_ep kzalloc error\n"); + goto err_alloc; + } + fotg210->ep[i] = _ep[i]; + } + + fotg210->reg = ioremap(res->start, resource_size(res)); + if (fotg210->reg == NULL) { + pr_err("ioremap error.\n"); + goto err_map; + } + + spin_lock_init(&fotg210->lock); + + dev_set_drvdata(&pdev->dev, fotg210); + + fotg210->gadget.ops = &fotg210_gadget_ops; + + fotg210->gadget.max_speed = USB_SPEED_HIGH; + fotg210->gadget.dev.parent = &pdev->dev; + fotg210->gadget.dev.dma_mask = pdev->dev.dma_mask; + fotg210->gadget.name = udc_name; + + INIT_LIST_HEAD(&fotg210->gadget.ep_list); + + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { + struct fotg210_ep *ep = fotg210->ep[i]; + + if (i) { + INIT_LIST_HEAD(&fotg210->ep[i]->ep.ep_list); + list_add_tail(&fotg210->ep[i]->ep.ep_list, + &fotg210->gadget.ep_list); + } + ep->fotg210 = fotg210; + INIT_LIST_HEAD(&ep->queue); + ep->ep.name = fotg210_ep_name[i]; + ep->ep.ops = &fotg210_ep_ops; + } + fotg210->ep[0]->ep.maxpacket = 0x40; + fotg210->gadget.ep0 = &fotg210->ep[0]->ep; + INIT_LIST_HEAD(&fotg210->gadget.ep0->ep_list); + + fotg210->ep0_req = fotg210_ep_alloc_request(&fotg210->ep[0]->ep, + GFP_KERNEL); + if (fotg210->ep0_req == NULL) + goto err_req; + + fotg210_init(fotg210); + + fotg210_disable_unplug(fotg210); + + ret = request_irq(ires->start, fotg210_irq, IRQF_SHARED, + udc_name, fotg210); + if (ret < 0) { + pr_err("request_irq error (%d)\n", ret); + goto err_irq; + } + + ret = usb_add_gadget_udc(&pdev->dev, &fotg210->gadget); + if (ret) + goto err_add_udc; + + dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION); + + return 0; + +err_add_udc: +err_irq: + free_irq(ires->start, fotg210); + +err_req: + fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req); + +err_map: + if (fotg210->reg) + iounmap(fotg210->reg); + +err_alloc: + kfree(fotg210); + + return ret; +} + +static struct platform_driver fotg210_driver = { + .driver = { + .name = (char *)udc_name, + .owner = THIS_MODULE, + }, + .probe = fotg210_udc_probe, + .remove = fotg210_udc_remove, +}; + +module_platform_driver(fotg210_driver); + +MODULE_AUTHOR("Yuan-Hsin Chen <yhchen@faraday-tech.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/usb/gadget/fotg210.h b/drivers/usb/gadget/fotg210.h new file mode 100644 index 0000000..bbf991b --- /dev/null +++ b/drivers/usb/gadget/fotg210.h @@ -0,0 +1,253 @@ +/* + * Faraday FOTG210 USB OTG controller + * + * Copyright (C) 2013 Faraday Technology Corporation + * Author: Yuan-Hsin Chen <yhchen@faraday-tech.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/kernel.h> + +#define FOTG210_MAX_NUM_EP 5 /* ep0...ep4 */ +#define FOTG210_MAX_FIFO_NUM 4 /* fifo0...fifo4 */ + +/* Global Mask of HC/OTG/DEV interrupt Register(0xC4) */ +#define FOTG210_GMIR 0xC4 +#define GMIR_INT_POLARITY 0x8 /*Active High*/ +#define GMIR_MHC_INT 0x4 +#define GMIR_MOTG_INT 0x2 +#define GMIR_MDEV_INT 0x1 + +/* Device Main Control Register(0x100) */ +#define FOTG210_DMCR 0x100 +#define DMCR_HS_EN (1 << 6) +#define DMCR_CHIP_EN (1 << 5) +#define DMCR_SFRST (1 << 4) +#define DMCR_GOSUSP (1 << 3) +#define DMCR_GLINT_EN (1 << 2) +#define DMCR_HALF_SPEED (1 << 1) +#define DMCR_CAP_RMWAKUP (1 << 0) + +/* Device Address Register(0x104) */ +#define FOTG210_DAR 0x104 +#define DAR_AFT_CONF (1 << 7) + +/* Device Test Register(0x108) */ +#define FOTG210_DTR 0x108 +#define DTR_TST_CLRFF (1 << 0) + +/* PHY Test Mode Selector register(0x114) */ +#define FOTG210_PHYTMSR 0x114 +#define PHYTMSR_TST_PKT (1 << 4) +#define PHYTMSR_TST_SE0NAK (1 << 3) +#define PHYTMSR_TST_KSTA (1 << 2) +#define PHYTMSR_TST_JSTA (1 << 1) +#define PHYTMSR_UNPLUG (1 << 0) + +/* Cx configuration and FIFO Empty Status register(0x120) */ +#define FOTG210_DCFESR 0x120 +#define DCFESR_FIFO_EMPTY(fifo) (1 << 8 << (fifo)) +#define DCFESR_CX_EMP (1 << 5) +#define DCFESR_CX_CLR (1 << 3) +#define DCFESR_CX_STL (1 << 2) +#define DCFESR_TST_PKDONE (1 << 1) +#define DCFESR_CX_DONE (1 << 0) + +/* Device IDLE Counter Register(0x124) */ +#define FOTG210_DICR 0x124 + +/* Device Mask of Interrupt Group Register (0x130) */ +#define FOTG210_DMIGR 0x130 +#define DMIGR_MINT_G0 (1 << 0) + +/* Device Mask of Interrupt Source Group 0(0x134) */ +#define FOTG210_DMISGR0 0x134 +#define DMISGR0_MCX_COMEND (1 << 3) +#define DMISGR0_MCX_OUT_INT (1 << 2) +#define DMISGR0_MCX_IN_INT (1 << 1) +#define DMISGR0_MCX_SETUP_INT (1 << 0) + +/* Device Mask of Interrupt Source Group 1 Register(0x138)*/ +#define FOTG210_DMISGR1 0x138 +#define DMISGR1_MF3_IN_INT (1 << 19) +#define DMISGR1_MF2_IN_INT (1 << 18) +#define DMISGR1_MF1_IN_INT (1 << 17) +#define DMISGR1_MF0_IN_INT (1 << 16) +#define DMISGR1_MF_IN_INT(fifo) (1 << (16 + (fifo))) +#define DMISGR1_MF3_SPK_INT (1 << 7) +#define DMISGR1_MF3_OUT_INT (1 << 6) +#define DMISGR1_MF2_SPK_INT (1 << 5) +#define DMISGR1_MF2_OUT_INT (1 << 4) +#define DMISGR1_MF1_SPK_INT (1 << 3) +#define DMISGR1_MF1_OUT_INT (1 << 2) +#define DMISGR1_MF0_SPK_INT (1 << 1) +#define DMISGR1_MF0_OUT_INT (1 << 0) +#define DMISGR1_MF_OUTSPK_INT(fifo) (0x3 << (fifo) * 2) + +/* Device Mask of Interrupt Source Group 2 Register (0x13C) */ +#define FOTG210_DMISGR2 0x13C +#define DMISGR2_MDMA_ERROR (1 << 8) +#define DMISGR2_MDMA_CMPLT (1 << 7) + +/* Device Interrupt group Register (0x140) */ +#define FOTG210_DIGR 0x140 +#define DIGR_INT_G2 (1 << 2) +#define DIGR_INT_G1 (1 << 1) +#define DIGR_INT_G0 (1 << 0) + +/* Device Interrupt Source Group 0 Register (0x144) */ +#define FOTG210_DISGR0 0x144 +#define DISGR0_CX_COMABT_INT (1 << 5) +#define DISGR0_CX_COMFAIL_INT (1 << 4) +#define DISGR0_CX_COMEND_INT (1 << 3) +#define DISGR0_CX_OUT_INT (1 << 2) +#define DISGR0_CX_IN_INT (1 << 1) +#define DISGR0_CX_SETUP_INT (1 << 0) + +/* Device Interrupt Source Group 1 Register (0x148) */ +#define FOTG210_DISGR1 0x148 +#define DISGR1_OUT_INT(fifo) (1 << ((fifo) * 2)) +#define DISGR1_SPK_INT(fifo) (1 << 1 << ((fifo) * 2)) +#define DISGR1_IN_INT(fifo) (1 << 16 << (fifo)) + +/* Device Interrupt Source Group 2 Register (0x14C) */ +#define FOTG210_DISGR2 0x14C +#define DISGR2_DMA_ERROR (1 << 8) +#define DISGR2_DMA_CMPLT (1 << 7) +#define DISGR2_RX0BYTE_INT (1 << 6) +#define DISGR2_TX0BYTE_INT (1 << 5) +#define DISGR2_ISO_SEQ_ABORT_INT (1 << 4) +#define DISGR2_ISO_SEQ_ERR_INT (1 << 3) +#define DISGR2_RESM_INT (1 << 2) +#define DISGR2_SUSP_INT (1 << 1) +#define DISGR2_USBRST_INT (1 << 0) + +/* Device Receive Zero-Length Data Packet Register (0x150)*/ +#define FOTG210_RX0BYTE 0x150 +#define RX0BYTE_EP8 (1 << 7) +#define RX0BYTE_EP7 (1 << 6) +#define RX0BYTE_EP6 (1 << 5) +#define RX0BYTE_EP5 (1 << 4) +#define RX0BYTE_EP4 (1 << 3) +#define RX0BYTE_EP3 (1 << 2) +#define RX0BYTE_EP2 (1 << 1) +#define RX0BYTE_EP1 (1 << 0) + +/* Device Transfer Zero-Length Data Packet Register (0x154)*/ +#define FOTG210_TX0BYTE 0x154 +#define TX0BYTE_EP8 (1 << 7) +#define TX0BYTE_EP7 (1 << 6) +#define TX0BYTE_EP6 (1 << 5) +#define TX0BYTE_EP5 (1 << 4) +#define TX0BYTE_EP4 (1 << 3) +#define TX0BYTE_EP3 (1 << 2) +#define TX0BYTE_EP2 (1 << 1) +#define TX0BYTE_EP1 (1 << 0) + +/* Device IN Endpoint x MaxPacketSize Register(0x160+4*(x-1)) */ +#define FOTG210_INEPMPSR(ep) (0x160 + 4 * ((ep) - 1)) +#define INOUTEPMPSR_MPS(mps) ((mps) & 0x2FF) +#define INOUTEPMPSR_STL_EP (1 << 11) +#define INOUTEPMPSR_RESET_TSEQ (1 << 12) + +/* Device OUT Endpoint x MaxPacketSize Register(0x180+4*(x-1)) */ +#define FOTG210_OUTEPMPSR(ep) (0x180 + 4 * ((ep) - 1)) + +/* Device Endpoint 1~4 Map Register (0x1A0) */ +#define FOTG210_EPMAP 0x1A0 +#define EPMAP_FIFONO(ep, dir) \ + ((((ep) - 1) << ((ep) - 1) * 8) << ((dir) ? 0 : 4)) +#define EPMAP_FIFONOMSK(ep, dir) \ + ((3 << ((ep) - 1) * 8) << ((dir) ? 0 : 4)) + +/* Device FIFO Map Register (0x1A8) */ +#define FOTG210_FIFOMAP 0x1A8 +#define FIFOMAP_DIROUT(fifo) (0x0 << 4 << (fifo) * 8) +#define FIFOMAP_DIRIN(fifo) (0x1 << 4 << (fifo) * 8) +#define FIFOMAP_BIDIR(fifo) (0x2 << 4 << (fifo) * 8) +#define FIFOMAP_NA(fifo) (0x3 << 4 << (fifo) * 8) +#define FIFOMAP_EPNO(ep) ((ep) << ((ep) - 1) * 8) +#define FIFOMAP_EPNOMSK(ep) (0xF << ((ep) - 1) * 8) + +/* Device FIFO Confuguration Register (0x1AC) */ +#define FOTG210_FIFOCF 0x1AC +#define FIFOCF_TYPE(type, fifo) ((type) << (fifo) * 8) +#define FIFOCF_BLK_SIN(fifo) (0x0 << (fifo) * 8 << 2) +#define FIFOCF_BLK_DUB(fifo) (0x1 << (fifo) * 8 << 2) +#define FIFOCF_BLK_TRI(fifo) (0x2 << (fifo) * 8 << 2) +#define FIFOCF_BLKSZ_512(fifo) (0x0 << (fifo) * 8 << 4) +#define FIFOCF_BLKSZ_1024(fifo) (0x1 << (fifo) * 8 << 4) +#define FIFOCF_FIFO_EN(fifo) (0x1 << (fifo) * 8 << 5) + +/* Device FIFO n Instruction and Byte Count Register (0x1B0+4*n) */ +#define FOTG210_FIBCR(fifo) (0x1B0 + (fifo) * 4) +#define FIBCR_BCFX 0x7FF +#define FIBCR_FFRST (1 << 12) + +/* Device DMA Target FIFO Number Register (0x1C0) */ +#define FOTG210_DMATFNR 0x1C0 +#define DMATFNR_ACC_CXF (1 << 4) +#define DMATFNR_ACC_F3 (1 << 3) +#define DMATFNR_ACC_F2 (1 << 2) +#define DMATFNR_ACC_F1 (1 << 1) +#define DMATFNR_ACC_F0 (1 << 0) +#define DMATFNR_ACC_FN(fifo) (1 << (fifo)) +#define DMATFNR_DISDMA 0 + +/* Device DMA Controller Parameter setting 1 Register (0x1C8) */ +#define FOTG210_DMACPSR1 0x1C8 +#define DMACPSR1_DMA_LEN(len) (((len) & 0xFFFF) << 8) +#define DMACPSR1_DMA_ABORT (1 << 3) +#define DMACPSR1_DMA_TYPE(dir_in) (((dir_in) ? 1 : 0) << 1) +#define DMACPSR1_DMA_START (1 << 0) + +/* Device DMA Controller Parameter setting 2 Register (0x1CC) */ +#define FOTG210_DMACPSR2 0x1CC + +/* Device DMA Controller Parameter setting 3 Register (0x1CC) */ +#define FOTG210_CXPORT 0x1D0 + +struct fotg210_request { + struct usb_request req; + struct list_head queue; +}; + +struct fotg210_ep { + struct usb_ep ep; + struct fotg210_udc *fotg210; + + struct list_head queue; + unsigned stall:1; + unsigned wedged:1; + unsigned use_dma:1; + + unsigned char epnum; + unsigned char type; + unsigned char dir_in; + unsigned int maxp; + const struct usb_endpoint_descriptor *desc; +}; + +struct fotg210_udc { + spinlock_t lock; /* protect the struct */ + void __iomem *reg; + + unsigned long irq_trigger; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + + struct fotg210_ep *ep[FOTG210_MAX_NUM_EP]; + + struct usb_request *ep0_req; /* for internal request */ + __le16 ep0_data; + u8 ep0_dir; /* 0/0x80 out/in */ + + u8 reenum; /* if re-enumeration */ +}; + +#define gadget_to_fotg210(g) container_of((g), struct fotg210_udc, gadget) diff --git a/drivers/usb/gadget/fsl_qe_udc.c b/drivers/usb/gadget/fsl_qe_udc.c index 9a7ee33..f3bb363 100644 --- a/drivers/usb/gadget/fsl_qe_udc.c +++ b/drivers/usb/gadget/fsl_qe_udc.c @@ -2589,7 +2589,7 @@ static int qe_udc_probe(struct platform_device *ofdev) if (ret) goto err6; - dev_set_drvdata(&ofdev->dev, udc); + platform_set_drvdata(ofdev, udc); dev_info(udc->dev, "%s USB controller initialized as device\n", (udc->soc_type == PORT_QE) ? "QE" : "CPM"); @@ -2640,7 +2640,7 @@ static int qe_udc_resume(struct platform_device *dev) static int qe_udc_remove(struct platform_device *ofdev) { - struct qe_udc *udc = dev_get_drvdata(&ofdev->dev); + struct qe_udc *udc = platform_get_drvdata(ofdev); struct qe_ep *ep; unsigned int size; DECLARE_COMPLETION(done); diff --git a/drivers/usb/gadget/fusb300_udc.c b/drivers/usb/gadget/fusb300_udc.c index b8632d4..c83f3e1 100644 --- a/drivers/usb/gadget/fusb300_udc.c +++ b/drivers/usb/gadget/fusb300_udc.c @@ -1347,7 +1347,7 @@ static const struct usb_gadget_ops fusb300_gadget_ops = { static int __exit fusb300_remove(struct platform_device *pdev) { - struct fusb300 *fusb300 = dev_get_drvdata(&pdev->dev); + struct fusb300 *fusb300 = platform_get_drvdata(pdev); usb_del_gadget_udc(&fusb300->gadget); iounmap(fusb300->reg); @@ -1416,7 +1416,7 @@ static int __init fusb300_probe(struct platform_device *pdev) spin_lock_init(&fusb300->lock); - dev_set_drvdata(&pdev->dev, fusb300); + platform_set_drvdata(pdev, fusb300); fusb300->gadget.ops = &fusb300_gadget_ops; diff --git a/drivers/usb/gadget/g_ffs.c b/drivers/usb/gadget/g_ffs.c index 787a78e..5327c82 100644 --- a/drivers/usb/gadget/g_ffs.c +++ b/drivers/usb/gadget/g_ffs.c @@ -28,15 +28,18 @@ # define USB_ETH_RNDIS y # endif +#define USBF_ECM_INCLUDED # include "f_ecm.c" +#define USB_FSUBSET_INCLUDED # include "f_subset.c" # ifdef USB_ETH_RNDIS +# define USB_FRNDIS_INCLUDED # include "f_rndis.c" -# include "rndis.c" +# include "rndis.h" # endif -# include "u_ether.c" +# include "u_ether.h" -static u8 gfs_hostaddr[ETH_ALEN]; +static u8 gfs_host_mac[ETH_ALEN]; static struct eth_dev *the_dev; # ifdef CONFIG_USB_FUNCTIONFS_ETH static int eth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], @@ -45,7 +48,7 @@ static int eth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], #else # define the_dev NULL # define gether_cleanup(dev) do { } while (0) -# define gfs_hostaddr NULL +# define gfs_host_mac NULL struct eth_dev; #endif @@ -73,6 +76,8 @@ struct gfs_ffs_obj { USB_GADGET_COMPOSITE_OPTIONS(); +USB_ETHERNET_MODULE_PARAMETERS(); + static struct usb_device_descriptor gfs_dev_desc = { .bLength = sizeof gfs_dev_desc, .bDescriptorType = USB_DT_DEVICE, @@ -350,7 +355,8 @@ static int gfs_bind(struct usb_composite_dev *cdev) if (missing_funcs) return -ENODEV; #if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS - the_dev = gether_setup(cdev->gadget, gfs_hostaddr); + the_dev = gether_setup(cdev->gadget, dev_addr, host_addr, gfs_host_mac, + qmult); #endif if (IS_ERR(the_dev)) { ret = PTR_ERR(the_dev); @@ -446,7 +452,7 @@ static int gfs_do_config(struct usb_configuration *c) } if (gc->eth) { - ret = gc->eth(c, gfs_hostaddr, the_dev); + ret = gc->eth(c, gfs_host_mac, the_dev); if (unlikely(ret < 0)) return ret; } diff --git a/drivers/usb/gadget/m66592-udc.c b/drivers/usb/gadget/m66592-udc.c index 51cfe72..46ba983 100644 --- a/drivers/usb/gadget/m66592-udc.c +++ b/drivers/usb/gadget/m66592-udc.c @@ -1533,7 +1533,7 @@ static const struct usb_gadget_ops m66592_gadget_ops = { static int __exit m66592_remove(struct platform_device *pdev) { - struct m66592 *m66592 = dev_get_drvdata(&pdev->dev); + struct m66592 *m66592 = platform_get_drvdata(pdev); usb_del_gadget_udc(&m66592->gadget); @@ -1602,7 +1602,7 @@ static int __init m66592_probe(struct platform_device *pdev) m66592->irq_trigger = ires->flags & IRQF_TRIGGER_MASK; spin_lock_init(&m66592->lock); - dev_set_drvdata(&pdev->dev, m66592); + platform_set_drvdata(pdev, m66592); m66592->gadget.ops = &m66592_gadget_ops; m66592->gadget.max_speed = USB_SPEED_HIGH; diff --git a/drivers/usb/gadget/multi.c b/drivers/usb/gadget/multi.c index 4a45e80..032b96a 100644 --- a/drivers/usb/gadget/multi.c +++ b/drivers/usb/gadget/multi.c @@ -43,16 +43,19 @@ MODULE_LICENSE("GPL"); */ #include "f_mass_storage.c" +#define USBF_ECM_INCLUDED #include "f_ecm.c" -#include "f_subset.c" #ifdef USB_ETH_RNDIS +# define USB_FRNDIS_INCLUDED # include "f_rndis.c" -# include "rndis.c" +# include "rndis.h" #endif -#include "u_ether.c" +#include "u_ether.h" USB_GADGET_COMPOSITE_OPTIONS(); +USB_ETHERNET_MODULE_PARAMETERS(); + /***************************** Device Descriptor ****************************/ #define MULTI_VENDOR_NUM 0x1d6b /* Linux Foundation */ @@ -133,7 +136,7 @@ FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); static struct fsg_common fsg_common; -static u8 hostaddr[ETH_ALEN]; +static u8 host_mac[ETH_ALEN]; static struct usb_function_instance *fi_acm; static struct eth_dev *the_dev; @@ -152,7 +155,7 @@ static __init int rndis_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } - ret = rndis_bind_config(c, hostaddr, the_dev); + ret = rndis_bind_config(c, host_mac, the_dev); if (ret < 0) return ret; @@ -216,7 +219,7 @@ static __init int cdc_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } - ret = ecm_bind_config(c, hostaddr, the_dev); + ret = ecm_bind_config(c, host_mac, the_dev); if (ret < 0) return ret; @@ -280,7 +283,8 @@ static int __ref multi_bind(struct usb_composite_dev *cdev) } /* set up network link layer */ - the_dev = gether_setup(cdev->gadget, hostaddr); + the_dev = gether_setup(cdev->gadget, dev_addr, host_addr, host_mac, + qmult); if (IS_ERR(the_dev)) return PTR_ERR(the_dev); diff --git a/drivers/usb/gadget/mv_u3d_core.c b/drivers/usb/gadget/mv_u3d_core.c index 58288e9..07fdb3e 100644 --- a/drivers/usb/gadget/mv_u3d_core.c +++ b/drivers/usb/gadget/mv_u3d_core.c @@ -1786,8 +1786,6 @@ static int mv_u3d_remove(struct platform_device *dev) clk_put(u3d->clk); - platform_set_drvdata(dev, NULL); - kfree(u3d); return 0; @@ -1997,7 +1995,6 @@ err_map_cap_regs: err_get_cap_regs: err_get_clk: clk_put(u3d->clk); - platform_set_drvdata(dev, NULL); kfree(u3d); err_alloc_private: err_pdata: @@ -2053,7 +2050,7 @@ static SIMPLE_DEV_PM_OPS(mv_u3d_pm_ops, mv_u3d_suspend, mv_u3d_resume); static void mv_u3d_shutdown(struct platform_device *dev) { - struct mv_u3d *u3d = dev_get_drvdata(&dev->dev); + struct mv_u3d *u3d = platform_get_drvdata(dev); u32 tmp; tmp = ioread32(&u3d->op_regs->usbcmd); diff --git a/drivers/usb/gadget/ncm.c b/drivers/usb/gadget/ncm.c index 3b02fd4..81956fe 100644 --- a/drivers/usb/gadget/ncm.c +++ b/drivers/usb/gadget/ncm.c @@ -24,23 +24,12 @@ #include <linux/usb/composite.h> #include "u_ether.h" +#include "u_ncm.h" #define DRIVER_DESC "NCM Gadget" /*-------------------------------------------------------------------------*/ -/* - * Kbuild is not very cooperative with respect to linking separately - * compiled library objects into one module. So for now we won't use - * separate compilation ... ensuring init/exit sections work to shrink - * the runtime footprint, and giving us at least some parts of what - * a "gcc --combine ... part1.c part2.c part3.c ... " build would. - */ -#include "f_ncm.c" -#include "u_ether.c" - -/*-------------------------------------------------------------------------*/ - /* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! * Instead: allocate your own, using normal USB-IF procedures. */ @@ -54,6 +43,8 @@ /*-------------------------------------------------------------------------*/ USB_GADGET_COMPOSITE_OPTIONS(); +USB_ETHERNET_MODULE_PARAMETERS(); + static struct usb_device_descriptor device_desc = { .bLength = sizeof device_desc, .bDescriptorType = USB_DT_DEVICE, @@ -111,13 +102,15 @@ static struct usb_gadget_strings *dev_strings[] = { NULL, }; -struct eth_dev *the_dev; -static u8 hostaddr[ETH_ALEN]; +static struct usb_function_instance *f_ncm_inst; +static struct usb_function *f_ncm; /*-------------------------------------------------------------------------*/ static int __init ncm_do_config(struct usb_configuration *c) { + int status; + /* FIXME alloc iConfiguration string, set it in c->strings */ if (gadget_is_otg(c->cdev->gadget)) { @@ -125,7 +118,19 @@ static int __init ncm_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } - return ncm_bind_config(c, hostaddr, the_dev); + f_ncm = usb_get_function(f_ncm_inst); + if (IS_ERR(f_ncm)) { + status = PTR_ERR(f_ncm); + return status; + } + + status = usb_add_function(c, f_ncm); + if (status < 0) { + usb_put_function(f_ncm); + return status; + } + + return 0; } static struct usb_configuration ncm_config_driver = { @@ -141,12 +146,20 @@ static struct usb_configuration ncm_config_driver = { static int __init gncm_bind(struct usb_composite_dev *cdev) { struct usb_gadget *gadget = cdev->gadget; + struct f_ncm_opts *ncm_opts; int status; - /* set up network link layer */ - the_dev = gether_setup(cdev->gadget, hostaddr); - if (IS_ERR(the_dev)) - return PTR_ERR(the_dev); + f_ncm_inst = usb_get_function_instance("ncm"); + if (IS_ERR(f_ncm_inst)) + return PTR_ERR(f_ncm_inst); + + ncm_opts = container_of(f_ncm_inst, struct f_ncm_opts, func_inst); + + gether_set_qmult(ncm_opts->net, qmult); + if (!gether_set_host_addr(ncm_opts->net, host_addr)) + pr_info("using host ethernet address: %s", host_addr); + if (!gether_set_dev_addr(ncm_opts->net, dev_addr)) + pr_info("using self ethernet address: %s", dev_addr); /* Allocate string descriptor numbers ... note that string * contents can be overridden by the composite_dev glue. @@ -169,13 +182,16 @@ static int __init gncm_bind(struct usb_composite_dev *cdev) return 0; fail: - gether_cleanup(the_dev); + usb_put_function_instance(f_ncm_inst); return status; } static int __exit gncm_unbind(struct usb_composite_dev *cdev) { - gether_cleanup(the_dev); + if (!IS_ERR_OR_NULL(f_ncm)) + usb_put_function(f_ncm); + if (!IS_ERR_OR_NULL(f_ncm_inst)) + usb_put_function_instance(f_ncm_inst); return 0; } diff --git a/drivers/usb/gadget/nokia.c b/drivers/usb/gadget/nokia.c index 3b344b4..0a8099a 100644 --- a/drivers/usb/gadget/nokia.c +++ b/drivers/usb/gadget/nokia.c @@ -16,11 +16,13 @@ */ #include <linux/kernel.h> +#include <linux/module.h> #include <linux/device.h> #include "u_serial.h" #include "u_ether.h" #include "u_phonet.h" +#include "u_ecm.h" #include "gadget_chips.h" /* Defines */ @@ -28,24 +30,10 @@ #define NOKIA_VERSION_NUM 0x0211 #define NOKIA_LONG_NAME "N900 (PC-Suite Mode)" -/*-------------------------------------------------------------------------*/ - -/* - * Kbuild is not very cooperative with respect to linking separately - * compiled library objects into one module. So for now we won't use - * separate compilation ... ensuring init/exit sections work to shrink - * the runtime footprint, and giving us at least some parts of what - * a "gcc --combine ... part1.c part2.c part3.c ... " build would. - */ -#define USBF_OBEX_INCLUDED -#include "f_ecm.c" -#include "f_obex.c" -#include "f_phonet.c" -#include "u_ether.c" - -/*-------------------------------------------------------------------------*/ USB_GADGET_COMPOSITE_OPTIONS(); +USB_ETHERNET_MODULE_PARAMETERS(); + #define NOKIA_VENDOR_ID 0x0421 /* Nokia */ #define NOKIA_PRODUCT_ID 0x01c8 /* Nokia Gadget */ @@ -98,16 +86,15 @@ MODULE_LICENSE("GPL"); /*-------------------------------------------------------------------------*/ static struct usb_function *f_acm_cfg1; static struct usb_function *f_acm_cfg2; -static u8 hostaddr[ETH_ALEN]; -static struct eth_dev *the_dev; - -enum { - TTY_PORT_OBEX0, - TTY_PORT_OBEX1, - TTY_PORTS_MAX, -}; +static struct usb_function *f_ecm_cfg1; +static struct usb_function *f_ecm_cfg2; +static struct usb_function *f_obex1_cfg1; +static struct usb_function *f_obex2_cfg1; +static struct usb_function *f_obex1_cfg2; +static struct usb_function *f_obex2_cfg2; +static struct usb_function *f_phonet_cfg1; +static struct usb_function *f_phonet_cfg2; -static unsigned char tty_lines[TTY_PORTS_MAX]; static struct usb_configuration nokia_config_500ma_driver = { .label = "Bus Powered", @@ -126,47 +113,114 @@ static struct usb_configuration nokia_config_100ma_driver = { }; static struct usb_function_instance *fi_acm; +static struct usb_function_instance *fi_ecm; +static struct usb_function_instance *fi_obex1; +static struct usb_function_instance *fi_obex2; +static struct usb_function_instance *fi_phonet; static int __init nokia_bind_config(struct usb_configuration *c) { struct usb_function *f_acm; + struct usb_function *f_phonet = NULL; + struct usb_function *f_obex1 = NULL; + struct usb_function *f_ecm; + struct usb_function *f_obex2 = NULL; int status = 0; + int obex1_stat = 0; + int obex2_stat = 0; + int phonet_stat = 0; + + if (!IS_ERR(fi_phonet)) { + f_phonet = usb_get_function(fi_phonet); + if (IS_ERR(f_phonet)) + pr_debug("could not get phonet function\n"); + } - status = phonet_bind_config(c); - if (status) - printk(KERN_DEBUG "could not bind phonet config\n"); - - status = obex_bind_config(c, tty_lines[TTY_PORT_OBEX0]); - if (status) - printk(KERN_DEBUG "could not bind obex config %d\n", 0); + if (!IS_ERR(fi_obex1)) { + f_obex1 = usb_get_function(fi_obex1); + if (IS_ERR(f_obex1)) + pr_debug("could not get obex function 0\n"); + } - status = obex_bind_config(c, tty_lines[TTY_PORT_OBEX1]); - if (status) - printk(KERN_DEBUG "could not bind obex config %d\n", 0); + if (!IS_ERR(fi_obex2)) { + f_obex2 = usb_get_function(fi_obex2); + if (IS_ERR(f_obex2)) + pr_debug("could not get obex function 1\n"); + } f_acm = usb_get_function(fi_acm); - if (IS_ERR(f_acm)) - return PTR_ERR(f_acm); + if (IS_ERR(f_acm)) { + status = PTR_ERR(f_acm); + goto err_get_acm; + } + + f_ecm = usb_get_function(fi_ecm); + if (IS_ERR(f_ecm)) { + status = PTR_ERR(f_ecm); + goto err_get_ecm; + } + + if (!IS_ERR_OR_NULL(f_phonet)) { + phonet_stat = usb_add_function(c, f_phonet); + if (phonet_stat) + pr_debug("could not add phonet function\n"); + } + + if (!IS_ERR_OR_NULL(f_obex1)) { + obex1_stat = usb_add_function(c, f_obex1); + if (obex1_stat) + pr_debug("could not add obex function 0\n"); + } + + if (!IS_ERR_OR_NULL(f_obex2)) { + obex2_stat = usb_add_function(c, f_obex2); + if (obex2_stat) + pr_debug("could not add obex function 1\n"); + } status = usb_add_function(c, f_acm); if (status) goto err_conf; - status = ecm_bind_config(c, hostaddr, the_dev); + status = usb_add_function(c, f_ecm); if (status) { pr_debug("could not bind ecm config %d\n", status); goto err_ecm; } - if (c == &nokia_config_500ma_driver) + if (c == &nokia_config_500ma_driver) { f_acm_cfg1 = f_acm; - else + f_ecm_cfg1 = f_ecm; + f_phonet_cfg1 = f_phonet; + f_obex1_cfg1 = f_obex1; + f_obex2_cfg1 = f_obex2; + } else { f_acm_cfg2 = f_acm; + f_ecm_cfg2 = f_ecm; + f_phonet_cfg2 = f_phonet; + f_obex1_cfg2 = f_obex1; + f_obex2_cfg2 = f_obex2; + } return status; err_ecm: usb_remove_function(c, f_acm); err_conf: + if (!obex2_stat) + usb_remove_function(c, f_obex2); + if (!obex1_stat) + usb_remove_function(c, f_obex1); + if (!phonet_stat) + usb_remove_function(c, f_phonet); + usb_put_function(f_ecm); +err_get_ecm: usb_put_function(f_acm); +err_get_acm: + if (!IS_ERR_OR_NULL(f_obex2)) + usb_put_function(f_obex2); + if (!IS_ERR_OR_NULL(f_obex1)) + usb_put_function(f_obex1); + if (!IS_ERR_OR_NULL(f_phonet)) + usb_put_function(f_phonet); return status; } @@ -174,23 +228,6 @@ static int __init nokia_bind(struct usb_composite_dev *cdev) { struct usb_gadget *gadget = cdev->gadget; int status; - int cur_line; - - status = gphonet_setup(cdev->gadget); - if (status < 0) - goto err_phonet; - - for (cur_line = 0; cur_line < TTY_PORTS_MAX; cur_line++) { - status = gserial_alloc_line(&tty_lines[cur_line]); - if (status) - goto err_ether; - } - - the_dev = gether_setup(cdev->gadget, hostaddr); - if (IS_ERR(the_dev)) { - status = PTR_ERR(the_dev); - goto err_ether; - } status = usb_string_ids_tab(cdev, strings_dev); if (status < 0) @@ -201,18 +238,40 @@ static int __init nokia_bind(struct usb_composite_dev *cdev) nokia_config_500ma_driver.iConfiguration = status; nokia_config_100ma_driver.iConfiguration = status; - if (!gadget_supports_altsettings(gadget)) + if (!gadget_supports_altsettings(gadget)) { + status = -ENODEV; goto err_usb; + } + + fi_phonet = usb_get_function_instance("phonet"); + if (IS_ERR(fi_phonet)) + pr_debug("could not find phonet function\n"); + + fi_obex1 = usb_get_function_instance("obex"); + if (IS_ERR(fi_obex1)) + pr_debug("could not find obex function 1\n"); + + fi_obex2 = usb_get_function_instance("obex"); + if (IS_ERR(fi_obex2)) + pr_debug("could not find obex function 2\n"); fi_acm = usb_get_function_instance("acm"); - if (IS_ERR(fi_acm)) - goto err_usb; + if (IS_ERR(fi_acm)) { + status = PTR_ERR(fi_acm); + goto err_obex2_inst; + } + + fi_ecm = usb_get_function_instance("ecm"); + if (IS_ERR(fi_ecm)) { + status = PTR_ERR(fi_ecm); + goto err_acm_inst; + } /* finally register the configuration */ status = usb_add_config(cdev, &nokia_config_500ma_driver, nokia_bind_config); if (status < 0) - goto err_acm_inst; + goto err_ecm_inst; status = usb_add_config(cdev, &nokia_config_100ma_driver, nokia_bind_config); @@ -226,33 +285,55 @@ static int __init nokia_bind(struct usb_composite_dev *cdev) err_put_cfg1: usb_put_function(f_acm_cfg1); + if (!IS_ERR_OR_NULL(f_obex1_cfg1)) + usb_put_function(f_obex1_cfg1); + if (!IS_ERR_OR_NULL(f_obex2_cfg1)) + usb_put_function(f_obex2_cfg1); + if (!IS_ERR_OR_NULL(f_phonet_cfg1)) + usb_put_function(f_phonet_cfg1); + usb_put_function(f_ecm_cfg1); +err_ecm_inst: + usb_put_function_instance(fi_ecm); err_acm_inst: usb_put_function_instance(fi_acm); +err_obex2_inst: + if (!IS_ERR(fi_obex2)) + usb_put_function_instance(fi_obex2); + if (!IS_ERR(fi_obex1)) + usb_put_function_instance(fi_obex1); + if (!IS_ERR(fi_phonet)) + usb_put_function_instance(fi_phonet); err_usb: - gether_cleanup(the_dev); -err_ether: - cur_line--; - while (cur_line >= 0) - gserial_free_line(tty_lines[cur_line--]); - - gphonet_cleanup(); -err_phonet: return status; } static int __exit nokia_unbind(struct usb_composite_dev *cdev) { - int i; - + if (!IS_ERR_OR_NULL(f_obex1_cfg2)) + usb_put_function(f_obex1_cfg2); + if (!IS_ERR_OR_NULL(f_obex2_cfg2)) + usb_put_function(f_obex2_cfg2); + if (!IS_ERR_OR_NULL(f_obex1_cfg1)) + usb_put_function(f_obex1_cfg1); + if (!IS_ERR_OR_NULL(f_obex2_cfg1)) + usb_put_function(f_obex2_cfg1); + if (!IS_ERR_OR_NULL(f_phonet_cfg1)) + usb_put_function(f_phonet_cfg1); + if (!IS_ERR_OR_NULL(f_phonet_cfg2)) + usb_put_function(f_phonet_cfg2); usb_put_function(f_acm_cfg1); usb_put_function(f_acm_cfg2); + usb_put_function(f_ecm_cfg1); + usb_put_function(f_ecm_cfg2); + + usb_put_function_instance(fi_ecm); + if (!IS_ERR(fi_obex2)) + usb_put_function_instance(fi_obex2); + if (!IS_ERR(fi_obex1)) + usb_put_function_instance(fi_obex1); + if (!IS_ERR(fi_phonet)) + usb_put_function_instance(fi_phonet); usb_put_function_instance(fi_acm); - gphonet_cleanup(); - - for (i = 0; i < TTY_PORTS_MAX; i++) - gserial_free_line(tty_lines[i]); - - gether_cleanup(the_dev); return 0; } diff --git a/drivers/usb/gadget/pxa27x_udc.c b/drivers/usb/gadget/pxa27x_udc.c index 6b4c7d9..41cea95 100644 --- a/drivers/usb/gadget/pxa27x_udc.c +++ b/drivers/usb/gadget/pxa27x_udc.c @@ -2505,7 +2505,6 @@ static int pxa_udc_remove(struct platform_device *_dev) usb_put_phy(udc->transceiver); udc->transceiver = NULL; - platform_set_drvdata(_dev, NULL); the_controller = NULL; clk_put(udc->clk); iounmap(udc->regs); diff --git a/drivers/usb/gadget/r8a66597-udc.c b/drivers/usb/gadget/r8a66597-udc.c index 7ff7d9c..c6af649 100644 --- a/drivers/usb/gadget/r8a66597-udc.c +++ b/drivers/usb/gadget/r8a66597-udc.c @@ -1469,11 +1469,11 @@ static irqreturn_t r8a66597_irq(int irq, void *_r8a66597) u16 savepipe; u16 mask0; + spin_lock(&r8a66597->lock); + if (r8a66597_is_sudmac(r8a66597)) r8a66597_sudmac_irq(r8a66597); - spin_lock(&r8a66597->lock); - intsts0 = r8a66597_read(r8a66597, INTSTS0); intenb0 = r8a66597_read(r8a66597, INTENB0); @@ -1822,7 +1822,7 @@ static const struct usb_gadget_ops r8a66597_gadget_ops = { static int __exit r8a66597_remove(struct platform_device *pdev) { - struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev); + struct r8a66597 *r8a66597 = platform_get_drvdata(pdev); usb_del_gadget_udc(&r8a66597->gadget); del_timer_sync(&r8a66597->timer); @@ -1909,7 +1909,7 @@ static int __init r8a66597_probe(struct platform_device *pdev) } spin_lock_init(&r8a66597->lock); - dev_set_drvdata(&pdev->dev, r8a66597); + platform_set_drvdata(pdev, r8a66597); r8a66597->pdata = pdev->dev.platform_data; r8a66597->irq_sense_low = irq_trigger == IRQF_TRIGGER_LOW; diff --git a/drivers/usb/gadget/rndis.c b/drivers/usb/gadget/rndis.c index 1e4cfb0..3e3ea72 100644 --- a/drivers/usb/gadget/rndis.c +++ b/drivers/usb/gadget/rndis.c @@ -761,6 +761,7 @@ int rndis_signal_connect(int configNr) return rndis_indicate_status_msg(configNr, RNDIS_STATUS_MEDIA_CONNECT); } +EXPORT_SYMBOL(rndis_signal_connect); int rndis_signal_disconnect(int configNr) { @@ -769,6 +770,7 @@ int rndis_signal_disconnect(int configNr) return rndis_indicate_status_msg(configNr, RNDIS_STATUS_MEDIA_DISCONNECT); } +EXPORT_SYMBOL(rndis_signal_disconnect); void rndis_uninit(int configNr) { @@ -783,11 +785,13 @@ void rndis_uninit(int configNr) while ((buf = rndis_get_next_response(configNr, &length))) rndis_free_response(configNr, buf); } +EXPORT_SYMBOL(rndis_uninit); void rndis_set_host_mac(int configNr, const u8 *addr) { rndis_per_dev_params[configNr].host_mac = addr; } +EXPORT_SYMBOL(rndis_set_host_mac); /* * Message Parser @@ -870,6 +874,7 @@ int rndis_msg_parser(u8 configNr, u8 *buf) return -ENOTSUPP; } +EXPORT_SYMBOL(rndis_msg_parser); int rndis_register(void (*resp_avail)(void *v), void *v) { @@ -891,6 +896,7 @@ int rndis_register(void (*resp_avail)(void *v), void *v) return -ENODEV; } +EXPORT_SYMBOL(rndis_register); void rndis_deregister(int configNr) { @@ -899,6 +905,7 @@ void rndis_deregister(int configNr) if (configNr >= RNDIS_MAX_CONFIGS) return; rndis_per_dev_params[configNr].used = 0; } +EXPORT_SYMBOL(rndis_deregister); int rndis_set_param_dev(u8 configNr, struct net_device *dev, u16 *cdc_filter) { @@ -912,6 +919,7 @@ int rndis_set_param_dev(u8 configNr, struct net_device *dev, u16 *cdc_filter) return 0; } +EXPORT_SYMBOL(rndis_set_param_dev); int rndis_set_param_vendor(u8 configNr, u32 vendorID, const char *vendorDescr) { @@ -924,6 +932,7 @@ int rndis_set_param_vendor(u8 configNr, u32 vendorID, const char *vendorDescr) return 0; } +EXPORT_SYMBOL(rndis_set_param_vendor); int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed) { @@ -935,6 +944,7 @@ int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed) return 0; } +EXPORT_SYMBOL(rndis_set_param_medium); void rndis_add_hdr(struct sk_buff *skb) { @@ -949,6 +959,7 @@ void rndis_add_hdr(struct sk_buff *skb) header->DataOffset = cpu_to_le32(36); header->DataLength = cpu_to_le32(skb->len - sizeof(*header)); } +EXPORT_SYMBOL(rndis_add_hdr); void rndis_free_response(int configNr, u8 *buf) { @@ -965,6 +976,7 @@ void rndis_free_response(int configNr, u8 *buf) } } } +EXPORT_SYMBOL(rndis_free_response); u8 *rndis_get_next_response(int configNr, u32 *length) { @@ -986,6 +998,7 @@ u8 *rndis_get_next_response(int configNr, u32 *length) return NULL; } +EXPORT_SYMBOL(rndis_get_next_response); static rndis_resp_t *rndis_add_response(int configNr, u32 length) { @@ -1029,6 +1042,7 @@ int rndis_rm_hdr(struct gether *port, skb_queue_tail(list, skb); return 0; } +EXPORT_SYMBOL(rndis_rm_hdr); #ifdef CONFIG_USB_GADGET_DEBUG_FILES @@ -1160,6 +1174,7 @@ int rndis_init(void) return 0; } +module_init(rndis_init); void rndis_exit(void) { @@ -1173,3 +1188,6 @@ void rndis_exit(void) } #endif } +module_exit(rndis_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/rndis.h b/drivers/usb/gadget/rndis.h index 0647f2f..0f4abb4 100644 --- a/drivers/usb/gadget/rndis.h +++ b/drivers/usb/gadget/rndis.h @@ -16,6 +16,7 @@ #define _LINUX_RNDIS_H #include <linux/rndis.h> +#include "u_ether.h" #include "ndis.h" #define RNDIS_MAXIMUM_FRAME_SIZE 1518 @@ -216,7 +217,4 @@ int rndis_signal_disconnect (int configNr); int rndis_state (int configNr); extern void rndis_set_host_mac (int configNr, const u8 *addr); -int rndis_init(void); -void rndis_exit (void); - #endif /* _LINUX_RNDIS_H */ diff --git a/drivers/usb/gadget/u_ecm.h b/drivers/usb/gadget/u_ecm.h new file mode 100644 index 0000000..262cc03 --- /dev/null +++ b/drivers/usb/gadget/u_ecm.h @@ -0,0 +1,36 @@ +/* + * u_ecm.h + * + * Utility definitions for the ecm function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * 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 U_ECM_H +#define U_ECM_H + +#include <linux/usb/composite.h> + +struct f_ecm_opts { + struct usb_function_instance func_inst; + struct net_device *net; + bool bound; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This is to protect the data from concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +#endif /* U_ECM_H */ diff --git a/drivers/usb/gadget/u_eem.h b/drivers/usb/gadget/u_eem.h new file mode 100644 index 0000000..e3ae978 --- /dev/null +++ b/drivers/usb/gadget/u_eem.h @@ -0,0 +1,36 @@ +/* + * u_eem.h + * + * Utility definitions for the eem function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * 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 U_EEM_H +#define U_EEM_H + +#include <linux/usb/composite.h> + +struct f_eem_opts { + struct usb_function_instance func_inst; + struct net_device *net; + bool bound; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This is to protect the data from concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +#endif /* U_EEM_H */ diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index 4b76124..2aae0d6 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -63,6 +63,8 @@ struct eth_dev { struct sk_buff_head rx_frames; + unsigned qmult; + unsigned header_len; struct sk_buff *(*wrap)(struct gether *, struct sk_buff *skb); int (*unwrap)(struct gether *, @@ -76,6 +78,7 @@ struct eth_dev { bool zlp; u8 host_mac[ETH_ALEN]; + u8 dev_mac[ETH_ALEN]; }; /*-------------------------------------------------------------------------*/ @@ -84,12 +87,8 @@ struct eth_dev { #define DEFAULT_QLEN 2 /* double buffering by default */ -static unsigned qmult = 5; -module_param(qmult, uint, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(qmult, "queue length multiplier at high/super speed"); - /* for dual-speed hardware, use deeper queues at high/super speed */ -static inline int qlen(struct usb_gadget *gadget) +static inline int qlen(struct usb_gadget *gadget, unsigned qmult) { if (gadget_is_dualspeed(gadget) && (gadget->speed == USB_SPEED_HIGH || gadget->speed == USB_SPEED_SUPER)) @@ -588,7 +587,7 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, if (gadget_is_dualspeed(dev->gadget)) req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH || dev->gadget->speed == USB_SPEED_SUPER) - ? ((atomic_read(&dev->tx_qlen) % qmult) != 0) + ? ((atomic_read(&dev->tx_qlen) % dev->qmult) != 0) : 0; retval = usb_ep_queue(in, req, GFP_ATOMIC); @@ -697,16 +696,6 @@ static int eth_stop(struct net_device *net) /*-------------------------------------------------------------------------*/ -/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */ -static char *dev_addr; -module_param(dev_addr, charp, S_IRUGO); -MODULE_PARM_DESC(dev_addr, "Device Ethernet Address"); - -/* this address is invisible to ifconfig */ -static char *host_addr; -module_param(host_addr, charp, S_IRUGO); -MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); - static int get_ether_addr(const char *str, u8 *dev_addr) { if (str) { @@ -728,6 +717,17 @@ static int get_ether_addr(const char *str, u8 *dev_addr) return 1; } +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]); + return 18; +} + static const struct net_device_ops eth_netdev_ops = { .ndo_open = eth_open, .ndo_stop = eth_stop, @@ -755,8 +755,9 @@ static struct device_type gadget_type = { * * Returns negative errno, or zero on success */ -struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], - const char *netname) +struct eth_dev *gether_setup_name(struct usb_gadget *g, + const char *dev_addr, const char *host_addr, + u8 ethaddr[ETH_ALEN], unsigned qmult, const char *netname) { struct eth_dev *dev; struct net_device *net; @@ -777,6 +778,7 @@ struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], /* network device setup */ dev->net = net; + dev->qmult = qmult; snprintf(net->name, sizeof(net->name), "%s%%d", netname); if (get_ether_addr(dev_addr, net->dev_addr)) @@ -806,7 +808,8 @@ struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], INFO(dev, "MAC %pM\n", net->dev_addr); INFO(dev, "HOST MAC %pM\n", dev->host_mac); - /* two kinds of host-initiated state changes: + /* + * two kinds of host-initiated state changes: * - iff DATA transfer is active, carrier is "on" * - tx queueing enabled if open *and* carrier is "on" */ @@ -815,6 +818,186 @@ struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], return dev; } +EXPORT_SYMBOL(gether_setup_name); + +struct net_device *gether_setup_name_default(const char *netname) +{ + struct net_device *net; + struct eth_dev *dev; + + net = alloc_etherdev(sizeof(*dev)); + if (!net) + return ERR_PTR(-ENOMEM); + + dev = netdev_priv(net); + spin_lock_init(&dev->lock); + spin_lock_init(&dev->req_lock); + INIT_WORK(&dev->work, eth_work); + INIT_LIST_HEAD(&dev->tx_reqs); + INIT_LIST_HEAD(&dev->rx_reqs); + + skb_queue_head_init(&dev->rx_frames); + + /* network device setup */ + dev->net = net; + dev->qmult = QMULT_DEFAULT; + snprintf(net->name, sizeof(net->name), "%s%%d", netname); + + eth_random_addr(dev->dev_mac); + pr_warn("using random %s ethernet address\n", "self"); + eth_random_addr(dev->host_mac); + pr_warn("using random %s ethernet address\n", "host"); + + net->netdev_ops = ð_netdev_ops; + + SET_ETHTOOL_OPS(net, &ops); + SET_NETDEV_DEVTYPE(net, &gadget_type); + + return net; +} +EXPORT_SYMBOL(gether_setup_name_default); + +int gether_register_netdev(struct net_device *net) +{ + struct eth_dev *dev; + struct usb_gadget *g; + struct sockaddr sa; + int status; + + if (!net->dev.parent) + return -EINVAL; + dev = netdev_priv(net); + g = dev->gadget; + status = register_netdev(net); + if (status < 0) { + dev_dbg(&g->dev, "register_netdev failed, %d\n", status); + return status; + } else { + INFO(dev, "HOST MAC %pM\n", dev->host_mac); + + /* two kinds of host-initiated state changes: + * - iff DATA transfer is active, carrier is "on" + * - tx queueing enabled if open *and* carrier is "on" + */ + netif_carrier_off(net); + } + sa.sa_family = net->type; + memcpy(sa.sa_data, dev->dev_mac, ETH_ALEN); + rtnl_lock(); + status = dev_set_mac_address(net, &sa); + rtnl_unlock(); + if (status) + pr_warn("cannot set self ethernet address: %d\n", status); + else + INFO(dev, "MAC %pM\n", dev->dev_mac); + + return status; +} +EXPORT_SYMBOL(gether_register_netdev); + +void gether_set_gadget(struct net_device *net, struct usb_gadget *g) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + dev->gadget = g; + SET_NETDEV_DEV(net, &g->dev); +} +EXPORT_SYMBOL(gether_set_gadget); + +int gether_set_dev_addr(struct net_device *net, const char *dev_addr) +{ + struct eth_dev *dev; + u8 new_addr[ETH_ALEN]; + + dev = netdev_priv(net); + if (get_ether_addr(dev_addr, new_addr)) + return -EINVAL; + memcpy(dev->dev_mac, new_addr, ETH_ALEN); + return 0; +} +EXPORT_SYMBOL(gether_set_dev_addr); + +int gether_get_dev_addr(struct net_device *net, char *dev_addr, int len) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + return get_ether_addr_str(dev->dev_mac, dev_addr, len); +} +EXPORT_SYMBOL(gether_get_dev_addr); + +int gether_set_host_addr(struct net_device *net, const char *host_addr) +{ + struct eth_dev *dev; + u8 new_addr[ETH_ALEN]; + + dev = netdev_priv(net); + if (get_ether_addr(host_addr, new_addr)) + return -EINVAL; + memcpy(dev->host_mac, new_addr, ETH_ALEN); + return 0; +} +EXPORT_SYMBOL(gether_set_host_addr); + +int gether_get_host_addr(struct net_device *net, char *host_addr, int len) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + return get_ether_addr_str(dev->host_mac, host_addr, len); +} +EXPORT_SYMBOL(gether_get_host_addr); + +int gether_get_host_addr_cdc(struct net_device *net, char *host_addr, int len) +{ + struct eth_dev *dev; + + if (len < 13) + return -EINVAL; + + dev = netdev_priv(net); + snprintf(host_addr, len, "%pm", dev->host_mac); + + return strlen(host_addr); +} +EXPORT_SYMBOL(gether_get_host_addr_cdc); + +void gether_get_host_addr_u8(struct net_device *net, u8 host_mac[ETH_ALEN]) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + memcpy(host_mac, dev->host_mac, ETH_ALEN); +} +EXPORT_SYMBOL(gether_get_host_addr_u8); + +void gether_set_qmult(struct net_device *net, unsigned qmult) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + dev->qmult = qmult; +} +EXPORT_SYMBOL(gether_set_qmult); + +unsigned gether_get_qmult(struct net_device *net) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + return dev->qmult; +} +EXPORT_SYMBOL(gether_get_qmult); + +int gether_get_ifname(struct net_device *net, char *name, int len) +{ + rtnl_lock(); + strlcpy(name, netdev_name(net), len); + rtnl_unlock(); + return strlen(name); +} +EXPORT_SYMBOL(gether_get_ifname); /** * gether_cleanup - remove Ethernet-over-USB device @@ -831,6 +1014,7 @@ void gether_cleanup(struct eth_dev *dev) flush_work(&dev->work); free_netdev(dev->net); } +EXPORT_SYMBOL(gether_cleanup); /** * gether_connect - notify network layer that USB link is active @@ -873,11 +1057,12 @@ struct net_device *gether_connect(struct gether *link) } if (result == 0) - result = alloc_requests(dev, link, qlen(dev->gadget)); + result = alloc_requests(dev, link, qlen(dev->gadget, + dev->qmult)); if (result == 0) { dev->zlp = link->is_zlp_ok; - DBG(dev, "qlen %d\n", qlen(dev->gadget)); + DBG(dev, "qlen %d\n", qlen(dev->gadget, dev->qmult)); dev->header_len = link->header_len; dev->unwrap = link->unwrap; @@ -910,6 +1095,7 @@ fail0: return ERR_PTR(result); return dev->net; } +EXPORT_SYMBOL(gether_connect); /** * gether_disconnect - notify network layer that USB link is inactive @@ -980,3 +1166,7 @@ void gether_disconnect(struct gether *link) dev->port_usb = NULL; spin_unlock(&dev->lock); } +EXPORT_SYMBOL(gether_disconnect); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Brownell"); diff --git a/drivers/usb/gadget/u_ether.h b/drivers/usb/gadget/u_ether.h index 0252233..fb23d1f 100644 --- a/drivers/usb/gadget/u_ether.h +++ b/drivers/usb/gadget/u_ether.h @@ -21,6 +21,26 @@ #include "gadget_chips.h" +#define QMULT_DEFAULT 5 + +/* + * dev_addr: initial value + * changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" + * host_addr: this address is invisible to ifconfig + */ +#define USB_ETHERNET_MODULE_PARAMETERS() \ + static unsigned qmult = QMULT_DEFAULT; \ + module_param(qmult, uint, S_IRUGO|S_IWUSR); \ + MODULE_PARM_DESC(qmult, "queue length multiplier at high/super speed");\ + \ + static char *dev_addr; \ + module_param(dev_addr, charp, S_IRUGO); \ + MODULE_PARM_DESC(dev_addr, "Device Ethernet Address"); \ + \ + static char *host_addr; \ + module_param(host_addr, charp, S_IRUGO); \ + MODULE_PARM_DESC(host_addr, "Host Ethernet Address") + struct eth_dev; /* @@ -71,8 +91,9 @@ struct gether { |USB_CDC_PACKET_TYPE_DIRECTED) /* variant of gether_setup that allows customizing network device name */ -struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], - const char *netname); +struct eth_dev *gether_setup_name(struct usb_gadget *g, + const char *dev_addr, const char *host_addr, + u8 ethaddr[ETH_ALEN], unsigned qmult, const char *netname); /* netdev setup/teardown as directed by the gadget driver */ /* gether_setup - initialize one ethernet-over-usb link @@ -88,11 +109,145 @@ struct eth_dev *gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], * Returns negative errno, or zero on success */ static inline struct eth_dev *gether_setup(struct usb_gadget *g, - u8 ethaddr[ETH_ALEN]) + const char *dev_addr, const char *host_addr, + u8 ethaddr[ETH_ALEN], unsigned qmult) { - return gether_setup_name(g, ethaddr, "usb"); + return gether_setup_name(g, dev_addr, host_addr, ethaddr, qmult, "usb"); } +/* + * variant of gether_setup_default that allows customizing + * network device name + */ +struct net_device *gether_setup_name_default(const char *netname); + +/* + * gether_register_netdev - register the net device + * @net: net device to register + * + * Registers the net device associated with this ethernet-over-usb link + * + */ +int gether_register_netdev(struct net_device *net); + +/* gether_setup_default - initialize one ethernet-over-usb link + * Context: may sleep + * + * This sets up the single network link that may be exported by a + * gadget driver using this framework. The link layer addresses + * are set to random values. + * + * Returns negative errno, or zero on success + */ +static inline struct net_device *gether_setup_default(void) +{ + return gether_setup_name_default("usb"); +} + +/** + * gether_set_gadget - initialize one ethernet-over-usb link with a gadget + * @net: device representing this link + * @g: the gadget to initialize with + * + * This associates one ethernet-over-usb link with a gadget. + */ +void gether_set_gadget(struct net_device *net, struct usb_gadget *g); + +/** + * gether_set_dev_addr - initialize an ethernet-over-usb link with eth address + * @net: device representing this link + * @dev_addr: eth address of this device + * + * This sets the device-side Ethernet address of this ethernet-over-usb link + * if dev_addr is correct. + * Returns negative errno if the new address is incorrect. + */ +int gether_set_dev_addr(struct net_device *net, const char *dev_addr); + +/** + * gether_get_dev_addr - get an ethernet-over-usb link eth address + * @net: device representing this link + * @dev_addr: place to store device's eth address + * @len: length of the @dev_addr buffer + * + * This gets the device-side Ethernet address of this ethernet-over-usb link. + * Returns zero on success, else negative errno. + */ +int gether_get_dev_addr(struct net_device *net, char *dev_addr, int len); + +/** + * gether_set_host_addr - initialize an ethernet-over-usb link with host address + * @net: device representing this link + * @host_addr: eth address of the host + * + * This sets the host-side Ethernet address of this ethernet-over-usb link + * if host_addr is correct. + * Returns negative errno if the new address is incorrect. + */ +int gether_set_host_addr(struct net_device *net, const char *host_addr); + +/** + * gether_get_host_addr - get an ethernet-over-usb link host address + * @net: device representing this link + * @host_addr: place to store eth address of the host + * @len: length of the @host_addr buffer + * + * This gets the host-side Ethernet address of this ethernet-over-usb link. + * Returns zero on success, else negative errno. + */ +int gether_get_host_addr(struct net_device *net, char *host_addr, int len); + +/** + * gether_get_host_addr_cdc - get an ethernet-over-usb link host address + * @net: device representing this link + * @host_addr: place to store eth address of the host + * @len: length of the @host_addr buffer + * + * This gets the CDC formatted host-side Ethernet address of this + * ethernet-over-usb link. + * Returns zero on success, else negative errno. + */ +int gether_get_host_addr_cdc(struct net_device *net, char *host_addr, int len); + +/** + * gether_get_host_addr_u8 - get an ethernet-over-usb link host address + * @net: device representing this link + * @host_mac: place to store the eth address of the host + * + * This gets the binary formatted host-side Ethernet address of this + * ethernet-over-usb link. + */ +void gether_get_host_addr_u8(struct net_device *net, u8 host_mac[ETH_ALEN]); + +/** + * gether_set_qmult - initialize an ethernet-over-usb link with a multiplier + * @net: device representing this link + * @qmult: queue multiplier + * + * This sets the queue length multiplier of this ethernet-over-usb link. + * For higher speeds use longer queues. + */ +void gether_set_qmult(struct net_device *net, unsigned qmult); + +/** + * gether_get_qmult - get an ethernet-over-usb link multiplier + * @net: device representing this link + * + * This gets the queue length multiplier of this ethernet-over-usb link. + */ +unsigned gether_get_qmult(struct net_device *net); + +/** + * gether_get_ifname - get an ethernet-over-usb link interface name + * @net: device representing this link + * @name: place to store the interface name + * @len: length of the @name buffer + * + * This gets the interface name of this ethernet-over-usb link. + * Returns zero on success, else negative errno. + */ +int gether_get_ifname(struct net_device *net, char *name, int len); + void gether_cleanup(struct eth_dev *dev); /* connect/disconnect is handled by individual functions */ @@ -117,9 +272,6 @@ int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], struct eth_dev *dev); int ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], struct eth_dev *dev); -int ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], - struct eth_dev *dev); -int eem_bind_config(struct usb_configuration *c, struct eth_dev *dev); #ifdef USB_ETH_RNDIS diff --git a/drivers/usb/gadget/u_ether_configfs.h b/drivers/usb/gadget/u_ether_configfs.h new file mode 100644 index 0000000..bcbd301 --- /dev/null +++ b/drivers/usb/gadget/u_ether_configfs.h @@ -0,0 +1,164 @@ +/* + * u_ether_configfs.h + * + * Utility definitions for configfs support in USB Ethernet functions + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * 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 __U_ETHER_CONFIGFS_H +#define __U_ETHER_CONFIGFS_H + +#define USB_ETHERNET_CONFIGFS_ITEM(_f_) \ + CONFIGFS_ATTR_STRUCT(f_##_f_##_opts); \ + CONFIGFS_ATTR_OPS(f_##_f_##_opts); \ + \ + static void _f_##_attr_release(struct config_item *item) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + \ + usb_put_function_instance(&opts->func_inst); \ + } \ + \ + static struct configfs_item_operations _f_##_item_ops = { \ + .release = _f_##_attr_release, \ + .show_attribute = f_##_f_##_opts_attr_show, \ + .store_attribute = f_##_f_##_opts_attr_store, \ + } + +#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_DEV_ADDR(_f_) \ + static ssize_t _f_##_opts_dev_addr_show(struct f_##_f_##_opts *opts, \ + char *page) \ + { \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = gether_get_dev_addr(opts->net, page, PAGE_SIZE); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ + } \ + \ + static ssize_t _f_##_opts_dev_addr_store(struct f_##_f_##_opts *opts, \ + const char *page, size_t len)\ + { \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + mutex_unlock(&opts->lock); \ + return -EBUSY; \ + } \ + \ + ret = gether_set_dev_addr(opts->net, page); \ + mutex_unlock(&opts->lock); \ + if (!ret) \ + ret = len; \ + return ret; \ + } \ + \ + static struct f_##_f_##_opts_attribute f_##_f_##_opts_dev_addr = \ + __CONFIGFS_ATTR(dev_addr, S_IRUGO | S_IWUSR, \ + _f_##_opts_dev_addr_show, \ + _f_##_opts_dev_addr_store) + +#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_HOST_ADDR(_f_) \ + static ssize_t _f_##_opts_host_addr_show(struct f_##_f_##_opts *opts, \ + char *page) \ + { \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = gether_get_host_addr(opts->net, page, PAGE_SIZE); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ + } \ + \ + static ssize_t _f_##_opts_host_addr_store(struct f_##_f_##_opts *opts, \ + const char *page, size_t len)\ + { \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + mutex_unlock(&opts->lock); \ + return -EBUSY; \ + } \ + \ + ret = gether_set_host_addr(opts->net, page); \ + mutex_unlock(&opts->lock); \ + if (!ret) \ + ret = len; \ + return ret; \ + } \ + \ + static struct f_##_f_##_opts_attribute f_##_f_##_opts_host_addr = \ + __CONFIGFS_ATTR(host_addr, S_IRUGO | S_IWUSR, \ + _f_##_opts_host_addr_show, \ + _f_##_opts_host_addr_store) + +#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(_f_) \ + static ssize_t _f_##_opts_qmult_show(struct f_##_f_##_opts *opts, \ + char *page) \ + { \ + unsigned qmult; \ + \ + mutex_lock(&opts->lock); \ + qmult = gether_get_qmult(opts->net); \ + mutex_unlock(&opts->lock); \ + return sprintf(page, "%d", qmult); \ + } \ + \ + static ssize_t _f_##_opts_qmult_store(struct f_##_f_##_opts *opts, \ + const char *page, size_t len)\ + { \ + u8 val; \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto out; \ + } \ + \ + ret = kstrtou8(page, 0, &val); \ + if (ret) \ + goto out; \ + \ + gether_set_qmult(opts->net, val); \ + ret = len; \ +out: \ + mutex_unlock(&opts->lock); \ + return ret; \ + } \ + \ + static struct f_##_f_##_opts_attribute f_##_f_##_opts_qmult = \ + __CONFIGFS_ATTR(qmult, S_IRUGO | S_IWUSR, \ + _f_##_opts_qmult_show, \ + _f_##_opts_qmult_store) + +#define USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(_f_) \ + static ssize_t _f_##_opts_ifname_show(struct f_##_f_##_opts *opts, \ + char *page) \ + { \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + ret = gether_get_ifname(opts->net, page, PAGE_SIZE); \ + mutex_unlock(&opts->lock); \ + \ + return ret; \ + } \ + \ + static struct f_##_f_##_opts_attribute f_##_f_##_opts_ifname = \ + __CONFIGFS_ATTR_RO(ifname, _f_##_opts_ifname_show) + +#endif /* __U_ETHER_CONFIGFS_H */ diff --git a/drivers/usb/gadget/u_gether.h b/drivers/usb/gadget/u_gether.h new file mode 100644 index 0000000..d407842 --- /dev/null +++ b/drivers/usb/gadget/u_gether.h @@ -0,0 +1,36 @@ +/* + * u_gether.h + * + * Utility definitions for the subset function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * 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 U_GETHER_H +#define U_GETHER_H + +#include <linux/usb/composite.h> + +struct f_gether_opts { + struct usb_function_instance func_inst; + struct net_device *net; + bool bound; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This is to protect the data from concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +#endif /* U_GETHER_H */ diff --git a/drivers/usb/gadget/u_ncm.h b/drivers/usb/gadget/u_ncm.h new file mode 100644 index 0000000..ce0f3a7 --- /dev/null +++ b/drivers/usb/gadget/u_ncm.h @@ -0,0 +1,36 @@ +/* + * u_ncm.h + * + * Utility definitions for the ncm function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * 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 U_NCM_H +#define U_NCM_H + +#include <linux/usb/composite.h> + +struct f_ncm_opts { + struct usb_function_instance func_inst; + struct net_device *net; + bool bound; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This is to protect the data from concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +#endif /* U_NCM_H */ diff --git a/drivers/usb/gadget/u_phonet.h b/drivers/usb/gadget/u_phonet.h index 09a7525..98ced18 100644 --- a/drivers/usb/gadget/u_phonet.h +++ b/drivers/usb/gadget/u_phonet.h @@ -14,8 +14,16 @@ #include <linux/usb/composite.h> #include <linux/usb/cdc.h> -int gphonet_setup(struct usb_gadget *gadget); -int phonet_bind_config(struct usb_configuration *c); -void gphonet_cleanup(void); +struct f_phonet_opts { + struct usb_function_instance func_inst; + bool bound; + struct net_device *net; +}; + +struct net_device *gphonet_setup_default(void); +void gphonet_set_gadget(struct net_device *net, struct usb_gadget *g); +int gphonet_register_netdev(struct net_device *net); +int phonet_bind_config(struct usb_configuration *c, struct net_device *dev); +void gphonet_cleanup(struct net_device *dev); #endif /* __U_PHONET_H */ diff --git a/drivers/usb/gadget/u_rndis.h b/drivers/usb/gadget/u_rndis.h new file mode 100644 index 0000000..c62ba82 --- /dev/null +++ b/drivers/usb/gadget/u_rndis.h @@ -0,0 +1,41 @@ +/* + * u_rndis.h + * + * Utility definitions for the subset function + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> + * + * 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 U_RNDIS_H +#define U_RNDIS_H + +#include <linux/usb/composite.h> + +struct f_rndis_opts { + struct usb_function_instance func_inst; + u32 vendor_id; + const char *manufacturer; + struct net_device *net; + bool bound; + bool borrowed_net; + + /* + * Read/write access to configfs attributes is handled by configfs. + * + * This is to protect the data from concurrent access by read/write + * and create symlink/remove symlink. + */ + struct mutex lock; + int refcnt; +}; + +void rndis_borrow_net(struct usb_function_instance *f, struct net_device *net); + +#endif /* U_RNDIS_H */ diff --git a/drivers/usb/gadget/uvc_queue.c b/drivers/usb/gadget/uvc_queue.c index 7ce27e3..e617047 100644 --- a/drivers/usb/gadget/uvc_queue.c +++ b/drivers/usb/gadget/uvc_queue.c @@ -103,10 +103,26 @@ static void uvc_buffer_queue(struct vb2_buffer *vb) spin_unlock_irqrestore(&queue->irqlock, flags); } +static void uvc_wait_prepare(struct vb2_queue *vq) +{ + struct uvc_video_queue *queue = vb2_get_drv_priv(vq); + + mutex_unlock(&queue->mutex); +} + +static void uvc_wait_finish(struct vb2_queue *vq) +{ + struct uvc_video_queue *queue = vb2_get_drv_priv(vq); + + mutex_lock(&queue->mutex); +} + static struct vb2_ops uvc_queue_qops = { .queue_setup = uvc_queue_setup, .buf_prepare = uvc_buffer_prepare, .buf_queue = uvc_buffer_queue, + .wait_prepare = uvc_wait_prepare, + .wait_finish = uvc_wait_finish, }; static int uvc_queue_init(struct uvc_video_queue *queue, diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 344d5e2..881609e 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -17,7 +17,6 @@ config USB_C67X00_HCD config USB_XHCI_HCD tristate "xHCI HCD (USB 3.0) support" - depends on USB_ARCH_HAS_XHCI ---help--- The eXtensible Host Controller Interface (xHCI) is standard for USB 3.0 "SuperSpeed" host controller hardware. @@ -43,7 +42,6 @@ endif # USB_XHCI_HCD config USB_EHCI_HCD tristate "EHCI HCD (USB 2.0) support" - depends on USB_ARCH_HAS_EHCI ---help--- The Enhanced Host Controller Interface (EHCI) is standard for USB 2.0 "high speed" (480 Mbit/sec, 60 Mbyte/sec) host controller hardware. @@ -345,9 +343,19 @@ config USB_ISP1362_HCD To compile this driver as a module, choose M here: the module will be called isp1362-hcd. +config USB_FUSBH200_HCD + tristate "FUSBH200 HCD support" + depends on USB + default N + ---help--- + Faraday FUSBH200 is designed to meet USB2.0 EHCI specification + with minor modification. + + To compile this driver as a module, choose M here: the + module will be called fusbh200-hcd. + config USB_OHCI_HCD - tristate "OHCI HCD support" - depends on USB_ARCH_HAS_OHCI + tristate "OHCI HCD (USB 1.1) support" select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3 depends on USB_ISP1301 || !ARCH_LPC32XX ---help--- @@ -415,8 +423,8 @@ config USB_OHCI_HCD_PPC_OF default USB_OHCI_HCD_PPC_OF_BE || USB_OHCI_HCD_PPC_OF_LE config USB_OHCI_HCD_PCI - bool "OHCI support for PCI-bus USB controllers" - depends on PCI && (STB03xxx || PPC_MPC52xx || USB_OHCI_HCD_PPC_OF) + tristate "OHCI support for PCI-bus USB controllers" + depends on PCI default y select USB_OHCI_LITTLE_ENDIAN ---help--- @@ -470,7 +478,7 @@ config USB_CNS3XXX_OHCI It is needed for low-speed USB 1.0 device support. config USB_OHCI_HCD_PLATFORM - bool "Generic OHCI driver for a platform device" + tristate "Generic OHCI driver for a platform device" default n ---help--- Adds an OHCI host driver for a generic platform device, which diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 4fb73c1..b41fa5f 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -37,7 +37,11 @@ obj-$(CONFIG_USB_EHCI_MSM) += ehci-msm.o obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o obj-$(CONFIG_USB_ISP1362_HCD) += isp1362-hcd.o + obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o +obj-$(CONFIG_USB_OHCI_HCD_PCI) += ohci-pci.o +obj-$(CONFIG_USB_OHCI_HCD_PLATFORM) += ohci-platform.o + obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_FHCI_HCD) += fhci.o obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o @@ -52,3 +56,4 @@ obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o obj-$(CONFIG_USB_OCTEON2_COMMON) += octeon2-common.o obj-$(CONFIG_USB_HCD_BCMA) += bcma-hcd.o obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o +obj-$(CONFIG_USB_FUSBH200_HCD) += fusbh200-hcd.o diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index 3be3df2..bd831ec 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -732,6 +732,7 @@ static struct platform_driver ehci_fsl_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "fsl-ehci", + .owner = THIS_MODULE, .pm = EHCI_FSL_PM_OPS, }, }; diff --git a/drivers/usb/host/ehci-grlib.c b/drivers/usb/host/ehci-grlib.c index 5d75de9..a77bd8d 100644 --- a/drivers/usb/host/ehci-grlib.c +++ b/drivers/usb/host/ehci-grlib.c @@ -153,9 +153,7 @@ err_irq: static int ehci_hcd_grlib_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); - - dev_set_drvdata(&op->dev, NULL); + struct usb_hcd *hcd = platform_get_drvdata(op); dev_dbg(&op->dev, "stopping GRLIB GRUSBHC EHCI USB Controller\n"); @@ -171,7 +169,7 @@ static int ehci_hcd_grlib_remove(struct platform_device *op) static void ehci_hcd_grlib_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 9ab4a4d..b2f6450 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -42,6 +42,12 @@ static int ehci_hub_control( u16 wLength ); +static int persist_enabled_on_companion(struct usb_device *udev, void *unused) +{ + return !udev->maxchild && udev->persist_enabled && + udev->bus->root_hub->speed < USB_SPEED_HIGH; +} + /* After a power loss, ports that were owned by the companion must be * reset so that the companion can still own them. */ @@ -56,6 +62,16 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) if (!ehci->owned_ports) return; + /* + * USB 1.1 devices are mostly HIDs, which don't need to persist across + * suspends. If we ensure that none of our companion's devices have + * persist_enabled (by looking through all USB 1.1 buses in the system), + * we can skip this and avoid slowing resume down. Devices without + * persist will just get reenumerated shortly after resume anyway. + */ + if (!usb_for_each_dev(NULL, persist_enabled_on_companion)) + return; + /* Make sure the ports are powered */ port = HCS_N_PORTS(ehci->hcs_params); while (port--) { diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index 4020629..915c2db 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -166,14 +166,14 @@ static int mv_ehci_probe(struct platform_device *pdev) if (IS_ERR(ehci_mv->clk)) { dev_err(&pdev->dev, "error getting clock\n"); retval = PTR_ERR(ehci_mv->clk); - goto err_clear_drvdata; + goto err_put_hcd; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); if (r == NULL) { dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); retval = -ENODEV; - goto err_clear_drvdata; + goto err_put_hcd; } ehci_mv->phy_regs = devm_ioremap(&pdev->dev, r->start, @@ -181,14 +181,14 @@ static int mv_ehci_probe(struct platform_device *pdev) if (ehci_mv->phy_regs == 0) { dev_err(&pdev->dev, "failed to map phy I/O memory\n"); retval = -EFAULT; - goto err_clear_drvdata; + goto err_put_hcd; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); if (!r) { dev_err(&pdev->dev, "no I/O memory resource defined\n"); retval = -ENODEV; - goto err_clear_drvdata; + goto err_put_hcd; } ehci_mv->cap_regs = devm_ioremap(&pdev->dev, r->start, @@ -196,13 +196,13 @@ static int mv_ehci_probe(struct platform_device *pdev) if (ehci_mv->cap_regs == NULL) { dev_err(&pdev->dev, "failed to map I/O memory\n"); retval = -EFAULT; - goto err_clear_drvdata; + goto err_put_hcd; } retval = mv_ehci_enable(ehci_mv); if (retval) { dev_err(&pdev->dev, "init phy error %d\n", retval); - goto err_clear_drvdata; + goto err_put_hcd; } offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; @@ -274,8 +274,6 @@ err_set_vbus: pdata->set_vbus(0); err_disable_clk: mv_ehci_disable(ehci_mv); -err_clear_drvdata: - platform_set_drvdata(pdev, NULL); err_put_hcd: usb_put_hcd(hcd); @@ -300,8 +298,6 @@ static int mv_ehci_remove(struct platform_device *pdev) mv_ehci_disable(ehci_mv); } - platform_set_drvdata(pdev, NULL); - usb_put_hcd(hcd); return 0; diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c index c369767..e4c34ac 100644 --- a/drivers/usb/host/ehci-mxc.c +++ b/drivers/usb/host/ehci-mxc.c @@ -194,7 +194,6 @@ static int ehci_mxc_drv_remove(struct platform_device *pdev) clk_disable_unprepare(priv->phyclk); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ehci-octeon.c b/drivers/usb/host/ehci-octeon.c index a89750f..45cc001 100644 --- a/drivers/usb/host/ehci-octeon.c +++ b/drivers/usb/host/ehci-octeon.c @@ -182,8 +182,6 @@ static int ehci_octeon_drv_remove(struct platform_device *pdev) release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); - return 0; } diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 16d7150..bafa960 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -294,7 +294,7 @@ static struct platform_driver ehci_hcd_omap_driver = { /*.resume = ehci_hcd_omap_resume, */ .driver = { .name = hcd_name, - .of_match_table = of_match_ptr(omap_ehci_dt_ids), + .of_match_table = omap_ehci_dt_ids, } }; diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index efbc588..1a450aa 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -303,7 +303,7 @@ static struct platform_driver ehci_orion_driver = { .driver = { .name = "orion-ehci", .owner = THIS_MODULE, - .of_match_table = of_match_ptr(ehci_orion_dt_ids), + .of_match_table = ehci_orion_dt_ids, }, }; diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index f47f259..5733f8e 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -146,7 +146,6 @@ static int ehci_platform_remove(struct platform_device *dev) usb_remove_hcd(hcd); usb_put_hcd(hcd); - platform_set_drvdata(dev, NULL); if (pdata->power_off) pdata->power_off(dev); @@ -224,7 +223,7 @@ static struct platform_driver ehci_platform_driver = { .owner = THIS_MODULE, .name = "ehci-platform", .pm = &ehci_platform_pm_ops, - .of_match_table = of_match_ptr(vt8500_ehci_ids), + .of_match_table = vt8500_ehci_ids, } }; diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index 56dc732..86da09c 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -180,14 +180,12 @@ err_irq: static int ehci_hcd_ppc_of_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct device_node *np; struct resource res; - dev_set_drvdata(&op->dev, NULL); - dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); usb_remove_hcd(hcd); @@ -219,7 +217,7 @@ static int ehci_hcd_ppc_of_remove(struct platform_device *op) static void ehci_hcd_ppc_of_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c index 379037f..7cc26e6 100644 --- a/drivers/usb/host/ehci-s5p.c +++ b/drivers/usb/host/ehci-s5p.c @@ -50,6 +50,8 @@ struct s5p_ehci_hcd { struct s5p_ehci_platdata *pdata; }; +static struct s5p_ehci_platdata empty_platdata; + #define to_s5p_ehci(hcd) (struct s5p_ehci_hcd *)(hcd_to_ehci(hcd)->priv) static void s5p_setup_vbus_gpio(struct platform_device *pdev) @@ -101,6 +103,13 @@ static int s5p_ehci_probe(struct platform_device *pdev) return -ENOMEM; } s5p_ehci = to_s5p_ehci(hcd); + + if (of_device_is_compatible(pdev->dev.of_node, + "samsung,exynos5440-ehci")) { + s5p_ehci->pdata = &empty_platdata; + goto skip_phy; + } + phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); if (IS_ERR(phy)) { /* Fallback to pdata */ @@ -116,6 +125,8 @@ static int s5p_ehci_probe(struct platform_device *pdev) s5p_ehci->otg = phy->otg; } +skip_phy: + s5p_ehci->clk = devm_clk_get(&pdev->dev, "usbhost"); if (IS_ERR(s5p_ehci->clk)) { @@ -277,6 +288,7 @@ static const struct dev_pm_ops s5p_ehci_pm_ops = { #ifdef CONFIG_OF static const struct of_device_id exynos_ehci_match[] = { { .compatible = "samsung,exynos4210-ehci" }, + { .compatible = "samsung,exynos5440-ehci" }, {}, }; MODULE_DEVICE_TABLE(of, exynos_ehci_match); diff --git a/drivers/usb/host/ehci-sead3.c b/drivers/usb/host/ehci-sead3.c index f55477c..b2de52d 100644 --- a/drivers/usb/host/ehci-sead3.c +++ b/drivers/usb/host/ehci-sead3.c @@ -140,7 +140,6 @@ static int ehci_hcd_sead3_drv_remove(struct platform_device *pdev) usb_remove_hcd(hcd); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ehci-sh.c b/drivers/usb/host/ehci-sh.c index b44d716..c4c0ee9 100644 --- a/drivers/usb/host/ehci-sh.c +++ b/drivers/usb/host/ehci-sh.c @@ -176,7 +176,6 @@ static int ehci_hcd_sh_remove(struct platform_device *pdev) usb_remove_hcd(hcd); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); clk_disable(priv->fclk); clk_disable(priv->iclk); diff --git a/drivers/usb/host/ehci-spear.c b/drivers/usb/host/ehci-spear.c index bd3e5cb..1cf0adb 100644 --- a/drivers/usb/host/ehci-spear.c +++ b/drivers/usb/host/ehci-spear.c @@ -148,10 +148,6 @@ static int spear_ehci_hcd_drv_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); struct spear_ehci *sehci = to_spear_ehci(hcd); - if (!hcd) - return 0; - if (in_interrupt()) - BUG(); usb_remove_hcd(hcd); if (sehci->clk) @@ -174,7 +170,7 @@ static struct platform_driver spear_ehci_hcd_driver = { .name = "spear-ehci", .bus = &platform_bus_type, .pm = &ehci_spear_pm_ops, - .of_match_table = of_match_ptr(spear_ehci_id_table), + .of_match_table = spear_ehci_id_table, } }; diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 59d111b..8390c87 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -611,7 +611,7 @@ static const struct dev_pm_ops tegra_ehci_pm_ops = { /* Bits of PORTSC1, which will get cleared by writing 1 into them */ #define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) -static void tegra_ehci_set_pts(struct usb_phy *x, u8 pts_val) +void tegra_ehci_set_pts(struct usb_phy *x, u8 pts_val) { unsigned long val; struct usb_hcd *hcd = bus_to_hcd(x->otg->host); @@ -622,8 +622,9 @@ static void tegra_ehci_set_pts(struct usb_phy *x, u8 pts_val) val |= TEGRA_USB_PORTSC1_PTS(pts_val & 3); writel(val, base + TEGRA_USB_PORTSC1); } +EXPORT_SYMBOL_GPL(tegra_ehci_set_pts); -static void tegra_ehci_set_phcd(struct usb_phy *x, bool enable) +void tegra_ehci_set_phcd(struct usb_phy *x, bool enable) { unsigned long val; struct usb_hcd *hcd = bus_to_hcd(x->otg->host); @@ -636,6 +637,7 @@ static void tegra_ehci_set_phcd(struct usb_phy *x, bool enable) val &= ~TEGRA_USB_PORTSC1_PHCD; writel(val, base + TEGRA_USB_PORTSC1); } +EXPORT_SYMBOL_GPL(tegra_ehci_set_phcd); static int tegra_ehci_probe(struct platform_device *pdev) { @@ -645,7 +647,7 @@ static int tegra_ehci_probe(struct platform_device *pdev) struct tegra_ehci_platform_data *pdata; int err = 0; int irq; - int instance = pdev->id; + struct device_node *np_phy; struct usb_phy *u_phy; pdata = pdev->dev.platform_data; @@ -670,38 +672,49 @@ static int tegra_ehci_probe(struct platform_device *pdev) if (!tegra) return -ENOMEM; - hcd = usb_create_hcd(&tegra_ehci_hc_driver, &pdev->dev, - dev_name(&pdev->dev)); - if (!hcd) { - dev_err(&pdev->dev, "Unable to create HCD\n"); - return -ENOMEM; - } - - platform_set_drvdata(pdev, tegra); - tegra->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(tegra->clk)) { dev_err(&pdev->dev, "Can't get ehci clock\n"); - err = PTR_ERR(tegra->clk); - goto fail_clk; + return PTR_ERR(tegra->clk); } err = clk_prepare_enable(tegra->clk); if (err) - goto fail_clk; + return err; tegra_periph_reset_assert(tegra->clk); udelay(1); tegra_periph_reset_deassert(tegra->clk); + np_phy = of_parse_phandle(pdev->dev.of_node, "nvidia,phy", 0); + if (!np_phy) { + err = -ENODEV; + goto cleanup_clk; + } + + u_phy = tegra_usb_get_phy(np_phy); + if (IS_ERR(u_phy)) { + err = PTR_ERR(u_phy); + goto cleanup_clk; + } + tegra->needs_double_reset = of_property_read_bool(pdev->dev.of_node, "nvidia,needs-double-reset"); + hcd = usb_create_hcd(&tegra_ehci_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + err = -ENOMEM; + goto cleanup_clk; + } + hcd->phy = u_phy; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get I/O memory\n"); err = -ENXIO; - goto fail_io; + goto cleanup_hcd_create; } hcd->rsrc_start = res->start; hcd->rsrc_len = resource_size(res); @@ -709,58 +722,28 @@ static int tegra_ehci_probe(struct platform_device *pdev) if (!hcd->regs) { dev_err(&pdev->dev, "Failed to remap I/O memory\n"); err = -ENOMEM; - goto fail_io; - } - - /* This is pretty ugly and needs to be fixed when we do only - * device-tree probing. Old code relies on the platform_device - * numbering that we lack for device-tree-instantiated devices. - */ - if (instance < 0) { - switch (res->start) { - case TEGRA_USB_BASE: - instance = 0; - break; - case TEGRA_USB2_BASE: - instance = 1; - break; - case TEGRA_USB3_BASE: - instance = 2; - break; - default: - err = -ENODEV; - dev_err(&pdev->dev, "unknown usb instance\n"); - goto fail_io; - } + goto cleanup_hcd_create; } - tegra->phy = tegra_usb_phy_open(&pdev->dev, instance, hcd->regs, - pdata->phy_config, - TEGRA_USB_PHY_MODE_HOST, - tegra_ehci_set_pts, - tegra_ehci_set_phcd); - if (IS_ERR(tegra->phy)) { - dev_err(&pdev->dev, "Failed to open USB phy\n"); - err = -ENXIO; - goto fail_io; + err = usb_phy_init(hcd->phy); + if (err) { + dev_err(&pdev->dev, "Failed to initialize phy\n"); + goto cleanup_hcd_create; } - hcd->phy = u_phy = &tegra->phy->u_phy; - usb_phy_init(hcd->phy); - u_phy->otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg), GFP_KERNEL); if (!u_phy->otg) { dev_err(&pdev->dev, "Failed to alloc memory for otg\n"); err = -ENOMEM; - goto fail_io; + goto cleanup_phy; } u_phy->otg->host = hcd_to_bus(hcd); err = usb_phy_set_suspend(hcd->phy, 0); if (err) { dev_err(&pdev->dev, "Failed to power on the phy\n"); - goto fail_phy; + goto cleanup_phy; } tegra->host_resumed = 1; @@ -770,7 +753,7 @@ static int tegra_ehci_probe(struct platform_device *pdev) if (!irq) { dev_err(&pdev->dev, "Failed to get IRQ\n"); err = -ENODEV; - goto fail_phy; + goto cleanup_phy; } if (pdata->operating_mode == TEGRA_USB_OTG) { @@ -782,10 +765,12 @@ static int tegra_ehci_probe(struct platform_device *pdev) tegra->transceiver = ERR_PTR(-ENODEV); } + platform_set_drvdata(pdev, tegra); + err = usb_add_hcd(hcd, irq, IRQF_SHARED); if (err) { dev_err(&pdev->dev, "Failed to add USB HCD\n"); - goto fail; + goto cleanup_phy; } pm_runtime_set_active(&pdev->dev); @@ -798,15 +783,15 @@ static int tegra_ehci_probe(struct platform_device *pdev) pm_runtime_put_sync(&pdev->dev); return err; -fail: +cleanup_phy: if (!IS_ERR(tegra->transceiver)) otg_set_host(tegra->transceiver->otg, NULL); -fail_phy: + usb_phy_shutdown(hcd->phy); -fail_io: - clk_disable_unprepare(tegra->clk); -fail_clk: +cleanup_hcd_create: usb_put_hcd(hcd); +cleanup_clk: + clk_disable_unprepare(tegra->clk); return err; } diff --git a/drivers/usb/host/ehci-tilegx.c b/drivers/usb/host/ehci-tilegx.c index b083a35..d72b292 100644 --- a/drivers/usb/host/ehci-tilegx.c +++ b/drivers/usb/host/ehci-tilegx.c @@ -193,7 +193,6 @@ static int ehci_hcd_tilegx_drv_remove(struct platform_device *pdev) tilegx_stop_ehc(); gxio_usb_host_destroy(&pdata->usb_ctx); destroy_irq(pdata->irq); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c index d845e3b..35c7f90 100644 --- a/drivers/usb/host/ehci-xilinx-of.c +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -209,8 +209,7 @@ err_irq: */ static int ehci_hcd_xilinx_of_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); - dev_set_drvdata(&op->dev, NULL); + struct usb_hcd *hcd = platform_get_drvdata(op); dev_dbg(&op->dev, "stopping XILINX-OF USB Controller\n"); @@ -229,7 +228,7 @@ static int ehci_hcd_xilinx_of_remove(struct platform_device *op) */ static void ehci_hcd_xilinx_of_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); diff --git a/drivers/usb/host/fhci-sched.c b/drivers/usb/host/fhci-sched.c index 8f18538..95ca598 100644 --- a/drivers/usb/host/fhci-sched.c +++ b/drivers/usb/host/fhci-sched.c @@ -739,9 +739,13 @@ void fhci_queue_urb(struct fhci_hcd *fhci, struct urb *urb) } /* for ISO transfer calculate start frame index */ - if (ed->mode == FHCI_TF_ISO && urb->transfer_flags & URB_ISO_ASAP) - urb->start_frame = ed->td_head ? ed->last_iso + 1 : + if (ed->mode == FHCI_TF_ISO) { + /* Ignore the possibility of underruns */ + urb->start_frame = ed->td_head ? ed->next_iso : get_frame_num(fhci); + ed->next_iso = (urb->start_frame + urb->interval * + urb->number_of_packets) & 0x07ff; + } /* * OHCI handles the DATA toggle itself,we just use the USB diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h index 7cc1c32..154e6a0 100644 --- a/drivers/usb/host/fhci.h +++ b/drivers/usb/host/fhci.h @@ -338,7 +338,7 @@ struct ed { /* read only parameters, should be cleared upon initialization */ u8 toggle_carry; /* toggle carry from the last TD submitted */ - u32 last_iso; /* time stamp of last queued ISO transfer */ + u16 next_iso; /* time stamp of next queued ISO transfer */ struct td *td_head; /* a pointer to the current TD handled */ }; diff --git a/drivers/usb/host/fusbh200-hcd.c b/drivers/usb/host/fusbh200-hcd.c new file mode 100644 index 0000000..299253c --- /dev/null +++ b/drivers/usb/host/fusbh200-hcd.c @@ -0,0 +1,5972 @@ +/* + * Faraday FUSBH200 EHCI-like driver + * + * Copyright (c) 2013 Faraday Technology Corporation + * + * Author: Yuan-Hsin Chen <yhchen@faraday-tech.com> + * Feng-Hsin Chiang <john453@faraday-tech.com> + * Po-Yu Chuang <ratbert.chuang@gmail.com> + * + * Most of code borrowed from the Linux-3.7 EHCI driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/dmapool.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/hrtimer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/moduleparam.h> +#include <linux/dma-mapping.h> +#include <linux/debugfs.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/unaligned.h> + +/*-------------------------------------------------------------------------*/ +#define DRIVER_AUTHOR "Yuan-Hsin Chen" +#define DRIVER_DESC "FUSBH200 Host Controller (EHCI) Driver" + +static const char hcd_name [] = "fusbh200_hcd"; + +#undef VERBOSE_DEBUG +#undef FUSBH200_URB_TRACE + +#ifdef DEBUG +#define FUSBH200_STATS +#endif + +/* magic numbers that can affect system performance */ +#define FUSBH200_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define FUSBH200_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define FUSBH200_TUNE_RL_TT 0 +#define FUSBH200_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define FUSBH200_TUNE_MULT_TT 1 +/* + * Some drivers think it's safe to schedule isochronous transfers more than + * 256 ms into the future (partly as a result of an old bug in the scheduling + * code). In an attempt to avoid trouble, we will use a minimum scheduling + * length of 512 frames instead of 256. + */ +#define FUSBH200_TUNE_FLS 1 /* (medium) 512-frame schedule */ + +/* Initial IRQ latency: faster than hw default */ +static int log2_irq_thresh = 0; // 0 to 6 +module_param (log2_irq_thresh, int, S_IRUGO); +MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +/* initial park setting: slower than hw default */ +static unsigned park = 0; +module_param (park, uint, S_IRUGO); +MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets"); + +/* for link power management(LPM) feature */ +static unsigned int hird; +module_param(hird, int, S_IRUGO); +MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us"); + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + +#include "fusbh200.h" + +/*-------------------------------------------------------------------------*/ + +#define fusbh200_dbg(fusbh200, fmt, args...) \ + dev_dbg (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_err(fusbh200, fmt, args...) \ + dev_err (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_info(fusbh200, fmt, args...) \ + dev_info (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_warn(fusbh200, fmt, args...) \ + dev_warn (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) + +#ifdef VERBOSE_DEBUG +# define fusbh200_vdbg fusbh200_dbg +#else + static inline void fusbh200_vdbg(struct fusbh200_hcd *fusbh200, ...) {} +#endif + +#ifdef DEBUG + +/* check the values in the HCSPARAMS register + * (host controller _Structural_ parameters) + * see EHCI spec, Table 2-4 for each value + */ +static void dbg_hcs_params (struct fusbh200_hcd *fusbh200, char *label) +{ + u32 params = fusbh200_readl(fusbh200, &fusbh200->caps->hcs_params); + + fusbh200_dbg (fusbh200, + "%s hcs_params 0x%x ports=%d\n", + label, params, + HCS_N_PORTS (params) + ); +} +#else + +static inline void dbg_hcs_params (struct fusbh200_hcd *fusbh200, char *label) {} + +#endif + +#ifdef DEBUG + +/* check the values in the HCCPARAMS register + * (host controller _Capability_ parameters) + * see EHCI Spec, Table 2-5 for each value + * */ +static void dbg_hcc_params (struct fusbh200_hcd *fusbh200, char *label) +{ + u32 params = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + + fusbh200_dbg (fusbh200, + "%s hcc_params %04x uframes %s%s\n", + label, + params, + HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024", + HCC_CANPARK(params) ? " park" : ""); +} +#else + +static inline void dbg_hcc_params (struct fusbh200_hcd *fusbh200, char *label) {} + +#endif + +#ifdef DEBUG + +static void __maybe_unused +dbg_qtd (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qtd *qtd) +{ + fusbh200_dbg(fusbh200, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + hc32_to_cpup(fusbh200, &qtd->hw_next), + hc32_to_cpup(fusbh200, &qtd->hw_alt_next), + hc32_to_cpup(fusbh200, &qtd->hw_token), + hc32_to_cpup(fusbh200, &qtd->hw_buf [0])); + if (qtd->hw_buf [1]) + fusbh200_dbg(fusbh200, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + hc32_to_cpup(fusbh200, &qtd->hw_buf[1]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[2]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[3]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[4])); +} + +static void __maybe_unused +dbg_qh (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qh_hw *hw = qh->hw; + + fusbh200_dbg (fusbh200, "%s qh %p n%08x info %x %x qtd %x\n", label, + qh, hw->hw_next, hw->hw_info1, hw->hw_info2, hw->hw_current); + dbg_qtd("overlay", fusbh200, (struct fusbh200_qtd *) &hw->hw_qtd_next); +} + +static void __maybe_unused +dbg_itd (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_itd *itd) +{ + fusbh200_dbg (fusbh200, "%s [%d] itd %p, next %08x, urb %p\n", + label, itd->frame, itd, hc32_to_cpu(fusbh200, itd->hw_next), + itd->urb); + fusbh200_dbg (fusbh200, + " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(fusbh200, itd->hw_transaction[0]), + hc32_to_cpu(fusbh200, itd->hw_transaction[1]), + hc32_to_cpu(fusbh200, itd->hw_transaction[2]), + hc32_to_cpu(fusbh200, itd->hw_transaction[3]), + hc32_to_cpu(fusbh200, itd->hw_transaction[4]), + hc32_to_cpu(fusbh200, itd->hw_transaction[5]), + hc32_to_cpu(fusbh200, itd->hw_transaction[6]), + hc32_to_cpu(fusbh200, itd->hw_transaction[7])); + fusbh200_dbg (fusbh200, + " buf: %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(fusbh200, itd->hw_bufp[0]), + hc32_to_cpu(fusbh200, itd->hw_bufp[1]), + hc32_to_cpu(fusbh200, itd->hw_bufp[2]), + hc32_to_cpu(fusbh200, itd->hw_bufp[3]), + hc32_to_cpu(fusbh200, itd->hw_bufp[4]), + hc32_to_cpu(fusbh200, itd->hw_bufp[5]), + hc32_to_cpu(fusbh200, itd->hw_bufp[6])); + fusbh200_dbg (fusbh200, " index: %d %d %d %d %d %d %d %d\n", + itd->index[0], itd->index[1], itd->index[2], + itd->index[3], itd->index[4], itd->index[5], + itd->index[6], itd->index[7]); +} + +static int __maybe_unused +dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) +{ + return scnprintf (buf, len, + "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s", + label, label [0] ? " " : "", status, + (status & STS_ASS) ? " Async" : "", + (status & STS_PSS) ? " Periodic" : "", + (status & STS_RECL) ? " Recl" : "", + (status & STS_HALT) ? " Halt" : "", + (status & STS_IAA) ? " IAA" : "", + (status & STS_FATAL) ? " FATAL" : "", + (status & STS_FLR) ? " FLR" : "", + (status & STS_PCD) ? " PCD" : "", + (status & STS_ERR) ? " ERR" : "", + (status & STS_INT) ? " INT" : "" + ); +} + +static int __maybe_unused +dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable) +{ + return scnprintf (buf, len, + "%s%sintrenable %02x%s%s%s%s%s%s", + label, label [0] ? " " : "", enable, + (enable & STS_IAA) ? " IAA" : "", + (enable & STS_FATAL) ? " FATAL" : "", + (enable & STS_FLR) ? " FLR" : "", + (enable & STS_PCD) ? " PCD" : "", + (enable & STS_ERR) ? " ERR" : "", + (enable & STS_INT) ? " INT" : "" + ); +} + +static const char *const fls_strings [] = + { "1024", "512", "256", "??" }; + +static int +dbg_command_buf (char *buf, unsigned len, const char *label, u32 command) +{ + return scnprintf (buf, len, + "%s%scommand %07x %s=%d ithresh=%d%s%s%s " + "period=%s%s %s", + label, label [0] ? " " : "", command, + (command & CMD_PARK) ? " park" : "(park)", + CMD_PARK_CNT (command), + (command >> 16) & 0x3f, + (command & CMD_IAAD) ? " IAAD" : "", + (command & CMD_ASE) ? " Async" : "", + (command & CMD_PSE) ? " Periodic" : "", + fls_strings [(command >> 2) & 0x3], + (command & CMD_RESET) ? " Reset" : "", + (command & CMD_RUN) ? "RUN" : "HALT" + ); +} + +static int +dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status) +{ + char *sig; + + /* signaling state */ + switch (status & (3 << 10)) { + case 0 << 10: sig = "se0"; break; + case 1 << 10: sig = "k"; break; /* low speed */ + case 2 << 10: sig = "j"; break; + default: sig = "?"; break; + } + + return scnprintf (buf, len, + "%s%sport:%d status %06x %d " + "sig=%s%s%s%s%s%s%s%s", + label, label [0] ? " " : "", port, status, + status>>25,/*device address */ + sig, + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : ""); +} + +#else +static inline void __maybe_unused +dbg_qh (char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{} + +static inline int __maybe_unused +dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) +{ return 0; } + +static inline int __maybe_unused +dbg_command_buf (char *buf, unsigned len, const char *label, u32 command) +{ return 0; } + +static inline int __maybe_unused +dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable) +{ return 0; } + +static inline int __maybe_unused +dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status) +{ return 0; } + +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ +#define dbg_status(fusbh200, label, status) { \ + char _buf [80]; \ + dbg_status_buf (_buf, sizeof _buf, label, status); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +#define dbg_cmd(fusbh200, label, command) { \ + char _buf [80]; \ + dbg_command_buf (_buf, sizeof _buf, label, command); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +#define dbg_port(fusbh200, label, port, status) { \ + char _buf [80]; \ + dbg_port_buf (_buf, sizeof _buf, label, port, status); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +/*-------------------------------------------------------------------------*/ + +#ifdef STUB_DEBUG_FILES + +static inline void create_debug_files (struct fusbh200_hcd *bus) { } +static inline void remove_debug_files (struct fusbh200_hcd *bus) { } + +#else + +/* troubleshooting help: expose state in debugfs */ + +static int debug_async_open(struct inode *, struct file *); +static int debug_periodic_open(struct inode *, struct file *); +static int debug_registers_open(struct inode *, struct file *); +static int debug_async_open(struct inode *, struct file *); + +static ssize_t debug_output(struct file*, char __user*, size_t, loff_t*); +static int debug_close(struct inode *, struct file *); + +static const struct file_operations debug_async_fops = { + .owner = THIS_MODULE, + .open = debug_async_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_periodic_fops = { + .owner = THIS_MODULE, + .open = debug_periodic_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_registers_fops = { + .owner = THIS_MODULE, + .open = debug_registers_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; + +static struct dentry *fusbh200_debug_root; + +struct debug_buffer { + ssize_t (*fill_func)(struct debug_buffer *); /* fill method */ + struct usb_bus *bus; + struct mutex mutex; /* protect filling of buffer */ + size_t count; /* number of characters filled into buffer */ + char *output_buf; + size_t alloc_size; +}; + +#define speed_char(info1) ({ char tmp; \ + switch (info1 & (3 << 12)) { \ + case QH_FULL_SPEED: tmp = 'f'; break; \ + case QH_LOW_SPEED: tmp = 'l'; break; \ + case QH_HIGH_SPEED: tmp = 'h'; break; \ + default: tmp = '?'; break; \ + }; tmp; }) + +static inline char token_mark(struct fusbh200_hcd *fusbh200, __hc32 token) +{ + __u32 v = hc32_to_cpu(fusbh200, token); + + if (v & QTD_STS_ACTIVE) + return '*'; + if (v & QTD_STS_HALT) + return '-'; + if (!IS_SHORT_READ (v)) + return ' '; + /* tries to advance through hw_alt_next */ + return '/'; +} + +static void qh_lines ( + struct fusbh200_hcd *fusbh200, + struct fusbh200_qh *qh, + char **nextp, + unsigned *sizep +) +{ + u32 scratch; + u32 hw_curr; + struct fusbh200_qtd *td; + unsigned temp; + unsigned size = *sizep; + char *next = *nextp; + char mark; + __le32 list_end = FUSBH200_LIST_END(fusbh200); + struct fusbh200_qh_hw *hw = qh->hw; + + if (hw->hw_qtd_next == list_end) /* NEC does this */ + mark = '@'; + else + mark = token_mark(fusbh200, hw->hw_token); + if (mark == '/') { /* qh_alt_next controls qh advance? */ + if ((hw->hw_alt_next & QTD_MASK(fusbh200)) + == fusbh200->async->hw->hw_alt_next) + mark = '#'; /* blocked */ + else if (hw->hw_alt_next == list_end) + mark = '.'; /* use hw_qtd_next */ + /* else alt_next points to some other qtd */ + } + scratch = hc32_to_cpup(fusbh200, &hw->hw_info1); + hw_curr = (mark == '*') ? hc32_to_cpup(fusbh200, &hw->hw_current) : 0; + temp = scnprintf (next, size, + "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)", + qh, scratch & 0x007f, + speed_char (scratch), + (scratch >> 8) & 0x000f, + scratch, hc32_to_cpup(fusbh200, &hw->hw_info2), + hc32_to_cpup(fusbh200, &hw->hw_token), mark, + (cpu_to_hc32(fusbh200, QTD_TOGGLE) & hw->hw_token) + ? "data1" : "data0", + (hc32_to_cpup(fusbh200, &hw->hw_alt_next) >> 1) & 0x0f); + size -= temp; + next += temp; + + /* hc may be modifying the list as we read it ... */ + list_for_each_entry(td, &qh->qtd_list, qtd_list) { + scratch = hc32_to_cpup(fusbh200, &td->hw_token); + mark = ' '; + if (hw_curr == td->qtd_dma) + mark = '*'; + else if (hw->hw_qtd_next == cpu_to_hc32(fusbh200, td->qtd_dma)) + mark = '+'; + else if (QTD_LENGTH (scratch)) { + if (td->hw_alt_next == fusbh200->async->hw->hw_alt_next) + mark = '#'; + else if (td->hw_alt_next != list_end) + mark = '/'; + } + temp = snprintf (next, size, + "\n\t%p%c%s len=%d %08x urb %p", + td, mark, ({ char *tmp; + switch ((scratch>>8)&0x03) { + case 0: tmp = "out"; break; + case 1: tmp = "in"; break; + case 2: tmp = "setup"; break; + default: tmp = "?"; break; + } tmp;}), + (scratch >> 16) & 0x7fff, + scratch, + td->urb); + if (size < temp) + temp = size; + size -= temp; + next += temp; + if (temp == size) + goto done; + } + + temp = snprintf (next, size, "\n"); + if (size < temp) + temp = size; + size -= temp; + next += temp; + +done: + *sizep = size; + *nextp = next; +} + +static ssize_t fill_async_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + unsigned temp, size; + char *next; + struct fusbh200_qh *qh; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + *next = 0; + + /* dumps a snapshot of the async schedule. + * usually empty except for long-term bulk reads, or head. + * one QH per line, and TDs we know about + */ + spin_lock_irqsave (&fusbh200->lock, flags); + for (qh = fusbh200->async->qh_next.qh; size > 0 && qh; qh = qh->qh_next.qh) + qh_lines (fusbh200, qh, &next, &size); + if (fusbh200->async_unlink && size > 0) { + temp = scnprintf(next, size, "\nunlink =\n"); + size -= temp; + next += temp; + + for (qh = fusbh200->async_unlink; size > 0 && qh; + qh = qh->unlink_next) + qh_lines (fusbh200, qh, &next, &size); + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + + return strlen(buf->output_buf); +} + +#define DBG_SCHED_LIMIT 64 +static ssize_t fill_periodic_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + union fusbh200_shadow p, *seen; + unsigned temp, size, seen_count; + char *next; + unsigned i; + __hc32 tag; + + if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, GFP_ATOMIC))) + return 0; + seen_count = 0; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + temp = scnprintf (next, size, "size = %d\n", fusbh200->periodic_size); + size -= temp; + next += temp; + + /* dump a snapshot of the periodic schedule. + * iso changes, interrupt usually doesn't. + */ + spin_lock_irqsave (&fusbh200->lock, flags); + for (i = 0; i < fusbh200->periodic_size; i++) { + p = fusbh200->pshadow [i]; + if (likely (!p.ptr)) + continue; + tag = Q_NEXT_TYPE(fusbh200, fusbh200->periodic [i]); + + temp = scnprintf (next, size, "%4d: ", i); + size -= temp; + next += temp; + + do { + struct fusbh200_qh_hw *hw; + + switch (hc32_to_cpu(fusbh200, tag)) { + case Q_TYPE_QH: + hw = p.qh->hw; + temp = scnprintf (next, size, " qh%d-%04x/%p", + p.qh->period, + hc32_to_cpup(fusbh200, + &hw->hw_info2) + /* uframe masks */ + & (QH_CMASK | QH_SMASK), + p.qh); + size -= temp; + next += temp; + /* don't repeat what follows this qh */ + for (temp = 0; temp < seen_count; temp++) { + if (seen [temp].ptr != p.ptr) + continue; + if (p.qh->qh_next.ptr) { + temp = scnprintf (next, size, + " ..."); + size -= temp; + next += temp; + } + break; + } + /* show more info the first time around */ + if (temp == seen_count) { + u32 scratch = hc32_to_cpup(fusbh200, + &hw->hw_info1); + struct fusbh200_qtd *qtd; + char *type = ""; + + /* count tds, get ep direction */ + temp = 0; + list_for_each_entry (qtd, + &p.qh->qtd_list, + qtd_list) { + temp++; + switch (0x03 & (hc32_to_cpu( + fusbh200, + qtd->hw_token) >> 8)) { + case 0: type = "out"; continue; + case 1: type = "in"; continue; + } + } + + temp = scnprintf (next, size, + " (%c%d ep%d%s " + "[%d/%d] q%d p%d)", + speed_char (scratch), + scratch & 0x007f, + (scratch >> 8) & 0x000f, type, + p.qh->usecs, p.qh->c_usecs, + temp, + 0x7ff & (scratch >> 16)); + + if (seen_count < DBG_SCHED_LIMIT) + seen [seen_count++].qh = p.qh; + } else + temp = 0; + tag = Q_NEXT_TYPE(fusbh200, hw->hw_next); + p = p.qh->qh_next; + break; + case Q_TYPE_FSTN: + temp = scnprintf (next, size, + " fstn-%8x/%p", p.fstn->hw_prev, + p.fstn); + tag = Q_NEXT_TYPE(fusbh200, p.fstn->hw_next); + p = p.fstn->fstn_next; + break; + case Q_TYPE_ITD: + temp = scnprintf (next, size, + " itd/%p", p.itd); + tag = Q_NEXT_TYPE(fusbh200, p.itd->hw_next); + p = p.itd->itd_next; + break; + } + size -= temp; + next += temp; + } while (p.ptr); + + temp = scnprintf (next, size, "\n"); + size -= temp; + next += temp; + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + kfree (seen); + + return buf->alloc_size - size; +} +#undef DBG_SCHED_LIMIT + +static const char *rh_state_string(struct fusbh200_hcd *fusbh200) +{ + switch (fusbh200->rh_state) { + case FUSBH200_RH_HALTED: + return "halted"; + case FUSBH200_RH_SUSPENDED: + return "suspended"; + case FUSBH200_RH_RUNNING: + return "running"; + case FUSBH200_RH_STOPPING: + return "stopping"; + } + return "?"; +} + +static ssize_t fill_registers_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + unsigned temp, size, i; + char *next, scratch [80]; + static char fmt [] = "%*s\n"; + static char label [] = ""; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + spin_lock_irqsave (&fusbh200->lock, flags); + + if (!HCD_HW_ACCESSIBLE(hcd)) { + size = scnprintf (next, size, + "bus %s, device %s\n" + "%s\n" + "SUSPENDED (no register access)\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc); + goto done; + } + + /* Capability Registers */ + i = HC_VERSION(fusbh200, fusbh200_readl(fusbh200, &fusbh200->caps->hc_capbase)); + temp = scnprintf (next, size, + "bus %s, device %s\n" + "%s\n" + "EHCI %x.%02x, rh state %s\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc, + i >> 8, i & 0x0ff, rh_state_string(fusbh200)); + size -= temp; + next += temp; + + // FIXME interpret both types of params + i = fusbh200_readl(fusbh200, &fusbh200->caps->hcs_params); + temp = scnprintf (next, size, "structural params 0x%08x\n", i); + size -= temp; + next += temp; + + i = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + temp = scnprintf (next, size, "capability params 0x%08x\n", i); + size -= temp; + next += temp; + + /* Operational Registers */ + temp = dbg_status_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->status)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_command_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->command)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_intr_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->intr_enable)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "uframe %04x\n", + fusbh200_read_frame_index(fusbh200)); + size -= temp; + next += temp; + + if (fusbh200->async_unlink) { + temp = scnprintf(next, size, "async unlink qh %p\n", + fusbh200->async_unlink); + size -= temp; + next += temp; + } + +#ifdef FUSBH200_STATS + temp = scnprintf (next, size, + "irq normal %ld err %ld iaa %ld (lost %ld)\n", + fusbh200->stats.normal, fusbh200->stats.error, fusbh200->stats.iaa, + fusbh200->stats.lost_iaa); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "complete %ld unlink %ld\n", + fusbh200->stats.complete, fusbh200->stats.unlink); + size -= temp; + next += temp; +#endif + +done: + spin_unlock_irqrestore (&fusbh200->lock, flags); + + return buf->alloc_size - size; +} + +static struct debug_buffer *alloc_buffer(struct usb_bus *bus, + ssize_t (*fill_func)(struct debug_buffer *)) +{ + struct debug_buffer *buf; + + buf = kzalloc(sizeof(struct debug_buffer), GFP_KERNEL); + + if (buf) { + buf->bus = bus; + buf->fill_func = fill_func; + mutex_init(&buf->mutex); + buf->alloc_size = PAGE_SIZE; + } + + return buf; +} + +static int fill_buffer(struct debug_buffer *buf) +{ + int ret = 0; + + if (!buf->output_buf) + buf->output_buf = vmalloc(buf->alloc_size); + + if (!buf->output_buf) { + ret = -ENOMEM; + goto out; + } + + ret = buf->fill_func(buf); + + if (ret >= 0) { + buf->count = ret; + ret = 0; + } + +out: + return ret; +} + +static ssize_t debug_output(struct file *file, char __user *user_buf, + size_t len, loff_t *offset) +{ + struct debug_buffer *buf = file->private_data; + int ret = 0; + + mutex_lock(&buf->mutex); + if (buf->count == 0) { + ret = fill_buffer(buf); + if (ret != 0) { + mutex_unlock(&buf->mutex); + goto out; + } + } + mutex_unlock(&buf->mutex); + + ret = simple_read_from_buffer(user_buf, len, offset, + buf->output_buf, buf->count); + +out: + return ret; + +} + +static int debug_close(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf = file->private_data; + + if (buf) { + vfree(buf->output_buf); + kfree(buf); + } + + return 0; +} +static int debug_async_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static int debug_periodic_open(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf; + buf = alloc_buffer(inode->i_private, fill_periodic_buffer); + if (!buf) + return -ENOMEM; + + buf->alloc_size = (sizeof(void *) == 4 ? 6 : 8)*PAGE_SIZE; + file->private_data = buf; + return 0; +} + +static int debug_registers_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_registers_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static inline void create_debug_files (struct fusbh200_hcd *fusbh200) +{ + struct usb_bus *bus = &fusbh200_to_hcd(fusbh200)->self; + + fusbh200->debug_dir = debugfs_create_dir(bus->bus_name, fusbh200_debug_root); + if (!fusbh200->debug_dir) + return; + + if (!debugfs_create_file("async", S_IRUGO, fusbh200->debug_dir, bus, + &debug_async_fops)) + goto file_error; + + if (!debugfs_create_file("periodic", S_IRUGO, fusbh200->debug_dir, bus, + &debug_periodic_fops)) + goto file_error; + + if (!debugfs_create_file("registers", S_IRUGO, fusbh200->debug_dir, bus, + &debug_registers_fops)) + goto file_error; + + return; + +file_error: + debugfs_remove_recursive(fusbh200->debug_dir); +} + +static inline void remove_debug_files (struct fusbh200_hcd *fusbh200) +{ + debugfs_remove_recursive(fusbh200->debug_dir); +} + +#endif /* STUB_DEBUG_FILES */ +/*-------------------------------------------------------------------------*/ + +/* + * handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + * + * That last failure should_only happen in cases like physical cardbus eject + * before driver shutdown. But it also seems to be caused by bugs in cardbus + * bridge shutdown: shutting down the bridge before the devices using it. + */ +static int handshake (struct fusbh200_hcd *fusbh200, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = fusbh200_readl(fusbh200, ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay (1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* + * Force HC to halt state from unknown (EHCI spec section 2.3). + * Must be called with interrupts enabled and the lock not held. + */ +static int fusbh200_halt (struct fusbh200_hcd *fusbh200) +{ + u32 temp; + + spin_lock_irq(&fusbh200->lock); + + /* disable any irqs left enabled by previous code */ + fusbh200_writel(fusbh200, 0, &fusbh200->regs->intr_enable); + + /* + * This routine gets called during probe before fusbh200->command + * has been initialized, so we can't rely on its value. + */ + fusbh200->command &= ~CMD_RUN; + temp = fusbh200_readl(fusbh200, &fusbh200->regs->command); + temp &= ~(CMD_RUN | CMD_IAAD); + fusbh200_writel(fusbh200, temp, &fusbh200->regs->command); + + spin_unlock_irq(&fusbh200->lock); + synchronize_irq(fusbh200_to_hcd(fusbh200)->irq); + + return handshake(fusbh200, &fusbh200->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* + * Reset a non-running (STS_HALT == 1) controller. + * Must be called with interrupts enabled and the lock not held. + */ +static int fusbh200_reset (struct fusbh200_hcd *fusbh200) +{ + int retval; + u32 command = fusbh200_readl(fusbh200, &fusbh200->regs->command); + + /* If the EHCI debug controller is active, special care must be + * taken before and after a host controller reset */ + if (fusbh200->debug && !dbgp_reset_prep(fusbh200_to_hcd(fusbh200))) + fusbh200->debug = NULL; + + command |= CMD_RESET; + dbg_cmd (fusbh200, "reset", command); + fusbh200_writel(fusbh200, command, &fusbh200->regs->command); + fusbh200->rh_state = FUSBH200_RH_HALTED; + fusbh200->next_statechange = jiffies; + retval = handshake (fusbh200, &fusbh200->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (retval) + return retval; + + if (fusbh200->debug) + dbgp_external_startup(fusbh200_to_hcd(fusbh200)); + + fusbh200->port_c_suspend = fusbh200->suspended_ports = + fusbh200->resuming_ports = 0; + return retval; +} + +/* + * Idle the controller (turn off the schedules). + * Must be called with interrupts enabled and the lock not held. + */ +static void fusbh200_quiesce (struct fusbh200_hcd *fusbh200) +{ + u32 temp; + + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + /* wait for any schedule enables/disables to take effect */ + temp = (fusbh200->command << 10) & (STS_ASS | STS_PSS); + handshake(fusbh200, &fusbh200->regs->status, STS_ASS | STS_PSS, temp, 16 * 125); + + /* then disable anything that's still active */ + spin_lock_irq(&fusbh200->lock); + fusbh200->command &= ~(CMD_ASE | CMD_PSE); + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + spin_unlock_irq(&fusbh200->lock); + + /* hardware can take 16 microframes to turn off ... */ + handshake(fusbh200, &fusbh200->regs->status, STS_ASS | STS_PSS, 0, 16 * 125); +} + +/*-------------------------------------------------------------------------*/ + +static void end_unlink_async(struct fusbh200_hcd *fusbh200); +static void unlink_empty_async(struct fusbh200_hcd *fusbh200); +static void fusbh200_work(struct fusbh200_hcd *fusbh200); +static void start_unlink_intr(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh); +static void end_unlink_intr(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh); + +/*-------------------------------------------------------------------------*/ + +/* Set a bit in the USBCMD register */ +static void fusbh200_set_command_bit(struct fusbh200_hcd *fusbh200, u32 bit) +{ + fusbh200->command |= bit; + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + + /* unblock posted write */ + fusbh200_readl(fusbh200, &fusbh200->regs->command); +} + +/* Clear a bit in the USBCMD register */ +static void fusbh200_clear_command_bit(struct fusbh200_hcd *fusbh200, u32 bit) +{ + fusbh200->command &= ~bit; + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + + /* unblock posted write */ + fusbh200_readl(fusbh200, &fusbh200->regs->command); +} + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI timer support... Now using hrtimers. + * + * Lots of different events are triggered from fusbh200->hrtimer. Whenever + * the timer routine runs, it checks each possible event; events that are + * currently enabled and whose expiration time has passed get handled. + * The set of enabled events is stored as a collection of bitflags in + * fusbh200->enabled_hrtimer_events, and they are numbered in order of + * increasing delay values (ranging between 1 ms and 100 ms). + * + * Rather than implementing a sorted list or tree of all pending events, + * we keep track only of the lowest-numbered pending event, in + * fusbh200->next_hrtimer_event. Whenever fusbh200->hrtimer gets restarted, its + * expiration time is set to the timeout value for this event. + * + * As a result, events might not get handled right away; the actual delay + * could be anywhere up to twice the requested delay. This doesn't + * matter, because none of the events are especially time-critical. The + * ones that matter most all have a delay of 1 ms, so they will be + * handled after 2 ms at most, which is okay. In addition to this, we + * allow for an expiration range of 1 ms. + */ + +/* + * Delay lengths for the hrtimer event types. + * Keep this list sorted by delay length, in the same order as + * the event types indexed by enum fusbh200_hrtimer_event in fusbh200.h. + */ +static unsigned event_delays_ns[] = { + 1 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_POLL_ASS */ + 1 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_POLL_PSS */ + 1 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_POLL_DEAD */ + 1125 * NSEC_PER_USEC, /* FUSBH200_HRTIMER_UNLINK_INTR */ + 2 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_FREE_ITDS */ + 6 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_ASYNC_UNLINKS */ + 10 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_IAA_WATCHDOG */ + 10 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_DISABLE_PERIODIC */ + 15 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_DISABLE_ASYNC */ + 100 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_IO_WATCHDOG */ +}; + +/* Enable a pending hrtimer event */ +static void fusbh200_enable_event(struct fusbh200_hcd *fusbh200, unsigned event, + bool resched) +{ + ktime_t *timeout = &fusbh200->hr_timeouts[event]; + + if (resched) + *timeout = ktime_add(ktime_get(), + ktime_set(0, event_delays_ns[event])); + fusbh200->enabled_hrtimer_events |= (1 << event); + + /* Track only the lowest-numbered pending event */ + if (event < fusbh200->next_hrtimer_event) { + fusbh200->next_hrtimer_event = event; + hrtimer_start_range_ns(&fusbh200->hrtimer, *timeout, + NSEC_PER_MSEC, HRTIMER_MODE_ABS); + } +} + + +/* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */ +static void fusbh200_poll_ASS(struct fusbh200_hcd *fusbh200) +{ + unsigned actual, want; + + /* Don't enable anything if the controller isn't running (e.g., died) */ + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + want = (fusbh200->command & CMD_ASE) ? STS_ASS : 0; + actual = fusbh200_readl(fusbh200, &fusbh200->regs->status) & STS_ASS; + + if (want != actual) { + + /* Poll again later, but give up after about 20 ms */ + if (fusbh200->ASS_poll_count++ < 20) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_POLL_ASS, true); + return; + } + fusbh200_dbg(fusbh200, "Waited too long for the async schedule status (%x/%x), giving up\n", + want, actual); + } + fusbh200->ASS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (fusbh200->async_count > 0) + fusbh200_set_command_bit(fusbh200, CMD_ASE); + + } else { /* Running */ + if (fusbh200->async_count == 0) { + + /* Turn off the schedule after a while */ + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_DISABLE_ASYNC, + true); + } + } +} + +/* Turn off the async schedule after a brief delay */ +static void fusbh200_disable_ASE(struct fusbh200_hcd *fusbh200) +{ + fusbh200_clear_command_bit(fusbh200, CMD_ASE); +} + + +/* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ +static void fusbh200_poll_PSS(struct fusbh200_hcd *fusbh200) +{ + unsigned actual, want; + + /* Don't do anything if the controller isn't running (e.g., died) */ + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + want = (fusbh200->command & CMD_PSE) ? STS_PSS : 0; + actual = fusbh200_readl(fusbh200, &fusbh200->regs->status) & STS_PSS; + + if (want != actual) { + + /* Poll again later, but give up after about 20 ms */ + if (fusbh200->PSS_poll_count++ < 20) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_POLL_PSS, true); + return; + } + fusbh200_dbg(fusbh200, "Waited too long for the periodic schedule status (%x/%x), giving up\n", + want, actual); + } + fusbh200->PSS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (fusbh200->periodic_count > 0) + fusbh200_set_command_bit(fusbh200, CMD_PSE); + + } else { /* Running */ + if (fusbh200->periodic_count == 0) { + + /* Turn off the schedule after a while */ + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_DISABLE_PERIODIC, + true); + } + } +} + +/* Turn off the periodic schedule after a brief delay */ +static void fusbh200_disable_PSE(struct fusbh200_hcd *fusbh200) +{ + fusbh200_clear_command_bit(fusbh200, CMD_PSE); +} + + +/* Poll the STS_HALT status bit; see when a dead controller stops */ +static void fusbh200_handle_controller_death(struct fusbh200_hcd *fusbh200) +{ + if (!(fusbh200_readl(fusbh200, &fusbh200->regs->status) & STS_HALT)) { + + /* Give up after a few milliseconds */ + if (fusbh200->died_poll_count++ < 5) { + /* Try again later */ + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_POLL_DEAD, true); + return; + } + fusbh200_warn(fusbh200, "Waited too long for the controller to stop, giving up\n"); + } + + /* Clean up the mess */ + fusbh200->rh_state = FUSBH200_RH_HALTED; + fusbh200_writel(fusbh200, 0, &fusbh200->regs->intr_enable); + fusbh200_work(fusbh200); + end_unlink_async(fusbh200); + + /* Not in process context, so don't try to reset the controller */ +} + + +/* Handle unlinked interrupt QHs once they are gone from the hardware */ +static void fusbh200_handle_intr_unlinks(struct fusbh200_hcd *fusbh200) +{ + bool stopped = (fusbh200->rh_state < FUSBH200_RH_RUNNING); + + /* + * Process all the QHs on the intr_unlink list that were added + * before the current unlink cycle began. The list is in + * temporal order, so stop when we reach the first entry in the + * current cycle. But if the root hub isn't running then + * process all the QHs on the list. + */ + fusbh200->intr_unlinking = true; + while (fusbh200->intr_unlink) { + struct fusbh200_qh *qh = fusbh200->intr_unlink; + + if (!stopped && qh->unlink_cycle == fusbh200->intr_unlink_cycle) + break; + fusbh200->intr_unlink = qh->unlink_next; + qh->unlink_next = NULL; + end_unlink_intr(fusbh200, qh); + } + + /* Handle remaining entries later */ + if (fusbh200->intr_unlink) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_UNLINK_INTR, true); + ++fusbh200->intr_unlink_cycle; + } + fusbh200->intr_unlinking = false; +} + + +/* Start another free-iTDs/siTDs cycle */ +static void start_free_itds(struct fusbh200_hcd *fusbh200) +{ + if (!(fusbh200->enabled_hrtimer_events & BIT(FUSBH200_HRTIMER_FREE_ITDS))) { + fusbh200->last_itd_to_free = list_entry( + fusbh200->cached_itd_list.prev, + struct fusbh200_itd, itd_list); + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_FREE_ITDS, true); + } +} + +/* Wait for controller to stop using old iTDs and siTDs */ +static void end_free_itds(struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_itd *itd, *n; + + if (fusbh200->rh_state < FUSBH200_RH_RUNNING) { + fusbh200->last_itd_to_free = NULL; + } + + list_for_each_entry_safe(itd, n, &fusbh200->cached_itd_list, itd_list) { + list_del(&itd->itd_list); + dma_pool_free(fusbh200->itd_pool, itd, itd->itd_dma); + if (itd == fusbh200->last_itd_to_free) + break; + } + + if (!list_empty(&fusbh200->cached_itd_list)) + start_free_itds(fusbh200); +} + + +/* Handle lost (or very late) IAA interrupts */ +static void fusbh200_iaa_watchdog(struct fusbh200_hcd *fusbh200) +{ + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + /* + * Lost IAA irqs wedge things badly; seen first with a vt8235. + * So we need this watchdog, but must protect it against both + * (a) SMP races against real IAA firing and retriggering, and + * (b) clean HC shutdown, when IAA watchdog was pending. + */ + if (fusbh200->async_iaa) { + u32 cmd, status; + + /* If we get here, IAA is *REALLY* late. It's barely + * conceivable that the system is so busy that CMD_IAAD + * is still legitimately set, so let's be sure it's + * clear before we read STS_IAA. (The HC should clear + * CMD_IAAD when it sets STS_IAA.) + */ + cmd = fusbh200_readl(fusbh200, &fusbh200->regs->command); + + /* + * If IAA is set here it either legitimately triggered + * after the watchdog timer expired (_way_ late, so we'll + * still count it as lost) ... or a silicon erratum: + * - VIA seems to set IAA without triggering the IRQ; + * - IAAD potentially cleared without setting IAA. + */ + status = fusbh200_readl(fusbh200, &fusbh200->regs->status); + if ((status & STS_IAA) || !(cmd & CMD_IAAD)) { + COUNT(fusbh200->stats.lost_iaa); + fusbh200_writel(fusbh200, STS_IAA, &fusbh200->regs->status); + } + + fusbh200_vdbg(fusbh200, "IAA watchdog: status %x cmd %x\n", + status, cmd); + end_unlink_async(fusbh200); + } +} + + +/* Enable the I/O watchdog, if appropriate */ +static void turn_on_io_watchdog(struct fusbh200_hcd *fusbh200) +{ + /* Not needed if the controller isn't running or it's already enabled */ + if (fusbh200->rh_state != FUSBH200_RH_RUNNING || + (fusbh200->enabled_hrtimer_events & + BIT(FUSBH200_HRTIMER_IO_WATCHDOG))) + return; + + /* + * Isochronous transfers always need the watchdog. + * For other sorts we use it only if the flag is set. + */ + if (fusbh200->isoc_count > 0 || (fusbh200->need_io_watchdog && + fusbh200->async_count + fusbh200->intr_count > 0)) + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_IO_WATCHDOG, true); +} + + +/* + * Handler functions for the hrtimer event types. + * Keep this array in the same order as the event types indexed by + * enum fusbh200_hrtimer_event in fusbh200.h. + */ +static void (*event_handlers[])(struct fusbh200_hcd *) = { + fusbh200_poll_ASS, /* FUSBH200_HRTIMER_POLL_ASS */ + fusbh200_poll_PSS, /* FUSBH200_HRTIMER_POLL_PSS */ + fusbh200_handle_controller_death, /* FUSBH200_HRTIMER_POLL_DEAD */ + fusbh200_handle_intr_unlinks, /* FUSBH200_HRTIMER_UNLINK_INTR */ + end_free_itds, /* FUSBH200_HRTIMER_FREE_ITDS */ + unlink_empty_async, /* FUSBH200_HRTIMER_ASYNC_UNLINKS */ + fusbh200_iaa_watchdog, /* FUSBH200_HRTIMER_IAA_WATCHDOG */ + fusbh200_disable_PSE, /* FUSBH200_HRTIMER_DISABLE_PERIODIC */ + fusbh200_disable_ASE, /* FUSBH200_HRTIMER_DISABLE_ASYNC */ + fusbh200_work, /* FUSBH200_HRTIMER_IO_WATCHDOG */ +}; + +static enum hrtimer_restart fusbh200_hrtimer_func(struct hrtimer *t) +{ + struct fusbh200_hcd *fusbh200 = container_of(t, struct fusbh200_hcd, hrtimer); + ktime_t now; + unsigned long events; + unsigned long flags; + unsigned e; + + spin_lock_irqsave(&fusbh200->lock, flags); + + events = fusbh200->enabled_hrtimer_events; + fusbh200->enabled_hrtimer_events = 0; + fusbh200->next_hrtimer_event = FUSBH200_HRTIMER_NO_EVENT; + + /* + * Check each pending event. If its time has expired, handle + * the event; otherwise re-enable it. + */ + now = ktime_get(); + for_each_set_bit(e, &events, FUSBH200_HRTIMER_NUM_EVENTS) { + if (now.tv64 >= fusbh200->hr_timeouts[e].tv64) + event_handlers[e](fusbh200); + else + fusbh200_enable_event(fusbh200, e, false); + } + + spin_unlock_irqrestore(&fusbh200->lock, flags); + return HRTIMER_NORESTART; +} + +/*-------------------------------------------------------------------------*/ + +#define fusbh200_bus_suspend NULL +#define fusbh200_bus_resume NULL + +/*-------------------------------------------------------------------------*/ + +static int check_reset_complete ( + struct fusbh200_hcd *fusbh200, + int index, + u32 __iomem *status_reg, + 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)) { + /* with integrated TT, there's nobody to hand it to! */ + fusbh200_dbg (fusbh200, + "Failed to enable port %d on root hub TT\n", + index+1); + return port_status; + } else { + fusbh200_dbg(fusbh200, "port %d reset complete, port enabled\n", + index + 1); + } + + return port_status; +} + +/*-------------------------------------------------------------------------*/ + + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int +fusbh200_hub_status_data (struct usb_hcd *hcd, char *buf) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + u32 temp, status; + u32 mask; + int retval = 1; + unsigned long flags; + + /* init status to no-changes */ + buf [0] = 0; + + /* Inform the core about resumes-in-progress by returning + * a non-zero value even if there are no status changes. + */ + status = fusbh200->resuming_ports; + + mask = PORT_CSC | PORT_PEC; + // PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave (&fusbh200->lock, flags); + + temp = fusbh200_readl(fusbh200, &fusbh200->regs->port_status); + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd 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 || test_bit(0, &fusbh200->port_c_suspend) + || (fusbh200->reset_done[0] && time_after_eq( + jiffies, fusbh200->reset_done[0]))) { + buf [0] |= 1 << 1; + status = STS_PCD; + } + /* FIXME autosuspend idle root hubs */ + spin_unlock_irqrestore (&fusbh200->lock, flags); + return status ? retval : 0; +} + +/*-------------------------------------------------------------------------*/ + +static void +fusbh200_hub_descriptor ( + struct fusbh200_hcd *fusbh200, + struct usb_hub_descriptor *desc +) { + int ports = HCS_N_PORTS (fusbh200->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* fusbh200 1.0, 2.3.9 says 20ms max */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + temp |= 0x0002; /* no power switching */ + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +/*-------------------------------------------------------------------------*/ + +static int fusbh200_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + int ports = HCS_N_PORTS (fusbh200->hcs_params); + u32 __iomem *status_reg = &fusbh200->regs->port_status; + u32 temp, temp1, 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 (&fusbh200->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 = fusbh200_readl(fusbh200, status_reg); + temp &= ~PORT_RWC_BITS; + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, khubd needs to be able to clear + * the port-change status bits (especially + * USB_PORT_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fusbh200_writel(fusbh200, temp & ~PORT_PE, status_reg); + break; + case USB_PORT_FEAT_C_ENABLE: + fusbh200_writel(fusbh200, temp | PORT_PEC, status_reg); + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + if (!(temp & PORT_SUSPEND)) + break; + if ((temp & PORT_PE) == 0) + goto error; + + /* resume signaling for 20 msec */ + fusbh200_writel(fusbh200, temp | PORT_RESUME, status_reg); + fusbh200->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + break; + case USB_PORT_FEAT_C_SUSPEND: + clear_bit(wIndex, &fusbh200->port_c_suspend); + break; + case USB_PORT_FEAT_C_CONNECTION: + fusbh200_writel(fusbh200, temp | PORT_CSC, status_reg); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + fusbh200_writel(fusbh200, temp | BMISR_OVC, &fusbh200->regs->bmisr); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + fusbh200_readl(fusbh200, &fusbh200->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + fusbh200_hub_descriptor (fusbh200, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset (buf, 0, 4); + //cpu_to_le32s ((u32 *) buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = fusbh200_readl(fusbh200, status_reg); + + // wPortChange bits + if (temp & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + if (temp & PORT_PEC) + status |= USB_PORT_STAT_C_ENABLE << 16; + + temp1 = fusbh200_readl(fusbh200, &fusbh200->regs->bmisr); + if (temp1 & BMISR_OVC) + status |= USB_PORT_STAT_C_OVERCURRENT << 16; + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + + /* Remote Wakeup received? */ + if (!fusbh200->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + fusbh200->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&fusbh200_to_hcd(fusbh200)->rh_timer, + fusbh200->reset_done[wIndex]); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + fusbh200->reset_done[wIndex])) { + clear_bit(wIndex, &fusbh200->suspended_ports); + set_bit(wIndex, &fusbh200->port_c_suspend); + fusbh200->reset_done[wIndex] = 0; + + /* stop resume signaling */ + temp = fusbh200_readl(fusbh200, status_reg); + fusbh200_writel(fusbh200, + temp & ~(PORT_RWC_BITS | PORT_RESUME), + status_reg); + clear_bit(wIndex, &fusbh200->resuming_ports); + retval = handshake(fusbh200, status_reg, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + fusbh200_err(fusbh200, + "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, + fusbh200->reset_done[wIndex])) { + status |= USB_PORT_STAT_C_RESET << 16; + fusbh200->reset_done [wIndex] = 0; + clear_bit(wIndex, &fusbh200->resuming_ports); + + /* force reset to complete */ + fusbh200_writel(fusbh200, temp & ~(PORT_RWC_BITS | PORT_RESET), + status_reg); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(fusbh200, status_reg, + PORT_RESET, 0, 1000); + if (retval != 0) { + fusbh200_err (fusbh200, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete (fusbh200, wIndex, status_reg, + fusbh200_readl(fusbh200, status_reg)); + } + + if (!(temp & (PORT_RESUME|PORT_RESET))) { + fusbh200->reset_done[wIndex] = 0; + clear_bit(wIndex, &fusbh200->resuming_ports); + } + + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &fusbh200->companion_ports)) { + temp &= ~PORT_RWC_BITS; + fusbh200_writel(fusbh200, temp, status_reg); + fusbh200_dbg(fusbh200, "port %d --> companion\n", wIndex + 1); + temp = fusbh200_readl(fusbh200, status_reg); + } + + /* + * Even if OWNER is set, there's no harm letting khubd + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_CONNECT) { + status |= USB_PORT_STAT_CONNECTION; + status |= fusbh200_port_speed(fusbh200, temp); + } + if (temp & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + + /* maybe the port was unsuspended without our knowledge */ + if (temp & (PORT_SUSPEND|PORT_RESUME)) { + status |= USB_PORT_STAT_SUSPEND; + } else if (test_bit(wIndex, &fusbh200->suspended_ports)) { + clear_bit(wIndex, &fusbh200->suspended_ports); + clear_bit(wIndex, &fusbh200->resuming_ports); + fusbh200->reset_done[wIndex] = 0; + if (temp & PORT_PE) + set_bit(wIndex, &fusbh200->port_c_suspend); + } + + temp1 = fusbh200_readl(fusbh200, &fusbh200->regs->bmisr); + if (temp1 & BMISR_OVC) + status |= USB_PORT_STAT_OVERCURRENT; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (test_bit(wIndex, &fusbh200->port_c_suspend)) + status |= USB_PORT_STAT_C_SUSPEND << 16; + +#ifndef VERBOSE_DEBUG + if (status & ~0xffff) /* only if wPortChange is interesting */ +#endif + dbg_port (fusbh200, "GetStatus", wIndex + 1, temp); + put_unaligned_le32(status, 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 = fusbh200_readl(fusbh200, status_reg); + temp &= ~PORT_RWC_BITS; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + + /* After above check the port must be connected. + * Set appropriate bit thus could put phy into low power + * mode if we have hostpc feature + */ + fusbh200_writel(fusbh200, temp | PORT_SUSPEND, status_reg); + set_bit(wIndex, &fusbh200->suspended_ports); + 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. + */ + fusbh200_vdbg (fusbh200, "port %d reset\n", wIndex + 1); + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + fusbh200->reset_done [wIndex] = jiffies + + msecs_to_jiffies (50); + fusbh200_writel(fusbh200, temp, status_reg); + break; + + /* For downstream facing ports (these): one hub port is put + * into test mode according to USB2 11.24.2.13, then the hub + * must be reset (which for root hub now means rmmod+modprobe, + * or else system reboot). See EHCI 2.3.9 and 4.14 for info + * about the EHCI-specific stuff. + */ + case USB_PORT_FEAT_TEST: + if (!selector || selector > 5) + goto error; + spin_unlock_irqrestore(&fusbh200->lock, flags); + fusbh200_quiesce(fusbh200); + spin_lock_irqsave(&fusbh200->lock, flags); + + /* Put all enabled ports into suspend */ + temp = fusbh200_readl(fusbh200, status_reg) & ~PORT_RWC_BITS; + if (temp & PORT_PE) + fusbh200_writel(fusbh200, temp | PORT_SUSPEND, + status_reg); + + spin_unlock_irqrestore(&fusbh200->lock, flags); + fusbh200_halt(fusbh200); + spin_lock_irqsave(&fusbh200->lock, flags); + + temp = fusbh200_readl(fusbh200, status_reg); + temp |= selector << 16; + fusbh200_writel(fusbh200, temp, status_reg); + break; + + default: + goto error; + } + fusbh200_readl(fusbh200, &fusbh200->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + return retval; +} + +static void __maybe_unused fusbh200_relinquish_port(struct usb_hcd *hcd, + int portnum) +{ + return; +} + +static int __maybe_unused fusbh200_port_handed_over(struct usb_hcd *hcd, + int portnum) +{ + return 0; +} +/*-------------------------------------------------------------------------*/ +/* + * There's basically three types of memory: + * - data used only by the HCD ... kmalloc is fine + * - async and periodic schedules, shared by HC and HCD ... these + * need to use dma_pool or dma_alloc_coherent + * - driver buffers, read/written by HC ... single shot DMA mapped + * + * There's also "register" data (e.g. PCI or SOC), which is memory mapped. + * No memory seen by this driver is pageable. + */ + +/*-------------------------------------------------------------------------*/ + +/* Allocate the key transfer structures from the previously allocated pool */ + +static inline void fusbh200_qtd_init(struct fusbh200_hcd *fusbh200, struct fusbh200_qtd *qtd, + dma_addr_t dma) +{ + memset (qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_token = cpu_to_hc32(fusbh200, QTD_STS_HALT); + qtd->hw_next = FUSBH200_LIST_END(fusbh200); + qtd->hw_alt_next = FUSBH200_LIST_END(fusbh200); + INIT_LIST_HEAD (&qtd->qtd_list); +} + +static struct fusbh200_qtd *fusbh200_qtd_alloc (struct fusbh200_hcd *fusbh200, gfp_t flags) +{ + struct fusbh200_qtd *qtd; + dma_addr_t dma; + + qtd = dma_pool_alloc (fusbh200->qtd_pool, flags, &dma); + if (qtd != NULL) { + fusbh200_qtd_init(fusbh200, qtd, dma); + } + return qtd; +} + +static inline void fusbh200_qtd_free (struct fusbh200_hcd *fusbh200, struct fusbh200_qtd *qtd) +{ + dma_pool_free (fusbh200->qtd_pool, qtd, qtd->qtd_dma); +} + + +static void qh_destroy(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + /* clean qtds first, and know this is not linked */ + if (!list_empty (&qh->qtd_list) || qh->qh_next.ptr) { + fusbh200_dbg (fusbh200, "unused qh not empty!\n"); + BUG (); + } + if (qh->dummy) + fusbh200_qtd_free (fusbh200, qh->dummy); + dma_pool_free(fusbh200->qh_pool, qh->hw, qh->qh_dma); + kfree(qh); +} + +static struct fusbh200_qh *fusbh200_qh_alloc (struct fusbh200_hcd *fusbh200, gfp_t flags) +{ + struct fusbh200_qh *qh; + dma_addr_t dma; + + qh = kzalloc(sizeof *qh, GFP_ATOMIC); + if (!qh) + goto done; + qh->hw = (struct fusbh200_qh_hw *) + dma_pool_alloc(fusbh200->qh_pool, flags, &dma); + if (!qh->hw) + goto fail; + memset(qh->hw, 0, sizeof *qh->hw); + qh->qh_dma = dma; + // INIT_LIST_HEAD (&qh->qh_list); + INIT_LIST_HEAD (&qh->qtd_list); + + /* dummy td enables safe urb queuing */ + qh->dummy = fusbh200_qtd_alloc (fusbh200, flags); + if (qh->dummy == NULL) { + fusbh200_dbg (fusbh200, "no dummy td\n"); + goto fail1; + } +done: + return qh; +fail1: + dma_pool_free(fusbh200->qh_pool, qh->hw, qh->qh_dma); +fail: + kfree(qh); + return NULL; +} + +/*-------------------------------------------------------------------------*/ + +/* The queue heads and transfer descriptors are managed from pools tied + * to each of the "per device" structures. + * This is the initialisation and cleanup code. + */ + +static void fusbh200_mem_cleanup (struct fusbh200_hcd *fusbh200) +{ + if (fusbh200->async) + qh_destroy(fusbh200, fusbh200->async); + fusbh200->async = NULL; + + if (fusbh200->dummy) + qh_destroy(fusbh200, fusbh200->dummy); + fusbh200->dummy = NULL; + + /* DMA consistent memory and pools */ + if (fusbh200->qtd_pool) + dma_pool_destroy (fusbh200->qtd_pool); + fusbh200->qtd_pool = NULL; + + if (fusbh200->qh_pool) { + dma_pool_destroy (fusbh200->qh_pool); + fusbh200->qh_pool = NULL; + } + + if (fusbh200->itd_pool) + dma_pool_destroy (fusbh200->itd_pool); + fusbh200->itd_pool = NULL; + + if (fusbh200->periodic) + dma_free_coherent (fusbh200_to_hcd(fusbh200)->self.controller, + fusbh200->periodic_size * sizeof (u32), + fusbh200->periodic, fusbh200->periodic_dma); + fusbh200->periodic = NULL; + + /* shadow periodic table */ + kfree(fusbh200->pshadow); + fusbh200->pshadow = NULL; +} + +/* remember to add cleanup code (above) if you add anything here */ +static int fusbh200_mem_init (struct fusbh200_hcd *fusbh200, gfp_t flags) +{ + int i; + + /* QTDs for control/bulk/intr transfers */ + fusbh200->qtd_pool = dma_pool_create ("fusbh200_qtd", + fusbh200_to_hcd(fusbh200)->self.controller, + sizeof (struct fusbh200_qtd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fusbh200->qtd_pool) { + goto fail; + } + + /* QHs for control/bulk/intr transfers */ + fusbh200->qh_pool = dma_pool_create ("fusbh200_qh", + fusbh200_to_hcd(fusbh200)->self.controller, + sizeof(struct fusbh200_qh_hw), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fusbh200->qh_pool) { + goto fail; + } + fusbh200->async = fusbh200_qh_alloc (fusbh200, flags); + if (!fusbh200->async) { + goto fail; + } + + /* ITD for high speed ISO transfers */ + fusbh200->itd_pool = dma_pool_create ("fusbh200_itd", + fusbh200_to_hcd(fusbh200)->self.controller, + sizeof (struct fusbh200_itd), + 64 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fusbh200->itd_pool) { + goto fail; + } + + /* Hardware periodic table */ + fusbh200->periodic = (__le32 *) + dma_alloc_coherent (fusbh200_to_hcd(fusbh200)->self.controller, + fusbh200->periodic_size * sizeof(__le32), + &fusbh200->periodic_dma, 0); + if (fusbh200->periodic == NULL) { + goto fail; + } + + for (i = 0; i < fusbh200->periodic_size; i++) + fusbh200->periodic[i] = FUSBH200_LIST_END(fusbh200); + + /* software shadow of hardware table */ + fusbh200->pshadow = kcalloc(fusbh200->periodic_size, sizeof(void *), flags); + if (fusbh200->pshadow != NULL) + return 0; + +fail: + fusbh200_dbg (fusbh200, "couldn't init memory\n"); + fusbh200_mem_cleanup (fusbh200); + return -ENOMEM; +} +/*-------------------------------------------------------------------------*/ +/* + * EHCI hardware queue manipulation ... the core. QH/QTD manipulation. + * + * Control, bulk, and interrupt traffic all use "qh" lists. They list "qtd" + * entries describing USB transactions, max 16-20kB/entry (with 4kB-aligned + * buffers needed for the larger number). We use one QH per endpoint, queue + * multiple urbs (all three types) per endpoint. URBs may need several qtds. + * + * ISO traffic uses "ISO TD" (itd) records, and (along with + * interrupts) needs careful scheduling. Performance improvements can be + * an ongoing challenge. That's in "ehci-sched.c". + * + * USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, + * or otherwise through transaction translators (TTs) in USB 2.0 hubs using + * (b) special fields in qh entries or (c) split iso entries. TTs will + * buffer low/full speed data so the host collects it at high speed. + */ + +/*-------------------------------------------------------------------------*/ + +/* fill a qtd, returning how much of the buffer we were able to queue up */ + +static int +qtd_fill(struct fusbh200_hcd *fusbh200, struct fusbh200_qtd *qtd, dma_addr_t buf, + size_t len, int token, int maxpacket) +{ + int i, count; + u64 addr = buf; + + /* one buffer entry per 4K ... first might be short or unaligned */ + qtd->hw_buf[0] = cpu_to_hc32(fusbh200, (u32)addr); + qtd->hw_buf_hi[0] = cpu_to_hc32(fusbh200, (u32)(addr >> 32)); + count = 0x1000 - (buf & 0x0fff); /* rest of that page */ + if (likely (len < count)) /* ... iff needed */ + count = len; + else { + buf += 0x1000; + buf &= ~0x0fff; + + /* per-qtd limit: from 16K to 20K (best alignment) */ + for (i = 1; count < len && i < 5; i++) { + addr = buf; + qtd->hw_buf[i] = cpu_to_hc32(fusbh200, (u32)addr); + qtd->hw_buf_hi[i] = cpu_to_hc32(fusbh200, + (u32)(addr >> 32)); + buf += 0x1000; + if ((count + 0x1000) < len) + count += 0x1000; + else + count = len; + } + + /* short packets may only terminate transfers */ + if (count != len) + count -= (count % maxpacket); + } + qtd->hw_token = cpu_to_hc32(fusbh200, (count << 16) | token); + qtd->length = count; + + return count; +} + +/*-------------------------------------------------------------------------*/ + +static inline void +qh_update (struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh, struct fusbh200_qtd *qtd) +{ + struct fusbh200_qh_hw *hw = qh->hw; + + /* writes to an active overlay are unsafe */ + BUG_ON(qh->qh_state != QH_STATE_IDLE); + + hw->hw_qtd_next = QTD_NEXT(fusbh200, qtd->qtd_dma); + hw->hw_alt_next = FUSBH200_LIST_END(fusbh200); + + /* Except for control endpoints, we make hardware maintain data + * toggle (like OHCI) ... here (re)initialize the toggle in the QH, + * and set the pseudo-toggle in udev. Only usb_clear_halt() will + * ever clear it. + */ + if (!(hw->hw_info1 & cpu_to_hc32(fusbh200, QH_TOGGLE_CTL))) { + unsigned is_out, epnum; + + is_out = qh->is_out; + epnum = (hc32_to_cpup(fusbh200, &hw->hw_info1) >> 8) & 0x0f; + if (unlikely (!usb_gettoggle (qh->dev, epnum, is_out))) { + hw->hw_token &= ~cpu_to_hc32(fusbh200, QTD_TOGGLE); + usb_settoggle (qh->dev, epnum, is_out, 1); + } + } + + hw->hw_token &= cpu_to_hc32(fusbh200, QTD_TOGGLE | QTD_STS_PING); +} + +/* if it weren't for a common silicon quirk (writing the dummy into the qh + * overlay, so qh->hw_token wrongly becomes inactive/halted), only fault + * recovery (including urb dequeue) would need software changes to a QH... + */ +static void +qh_refresh (struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qtd *qtd; + + if (list_empty (&qh->qtd_list)) + qtd = qh->dummy; + else { + qtd = list_entry (qh->qtd_list.next, + struct fusbh200_qtd, qtd_list); + /* + * first qtd may already be partially processed. + * If we come here during unlink, the QH overlay region + * might have reference to the just unlinked qtd. The + * qtd is updated in qh_completions(). Update the QH + * overlay here. + */ + if (cpu_to_hc32(fusbh200, qtd->qtd_dma) == qh->hw->hw_current) { + qh->hw->hw_qtd_next = qtd->hw_next; + qtd = NULL; + } + } + + if (qtd) + qh_update (fusbh200, qh, qtd); +} + +/*-------------------------------------------------------------------------*/ + +static void qh_link_async(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh); + +static void fusbh200_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + struct fusbh200_qh *qh = ep->hcpriv; + unsigned long flags; + + spin_lock_irqsave(&fusbh200->lock, flags); + qh->clearing_tt = 0; + if (qh->qh_state == QH_STATE_IDLE && !list_empty(&qh->qtd_list) + && fusbh200->rh_state == FUSBH200_RH_RUNNING) + qh_link_async(fusbh200, qh); + spin_unlock_irqrestore(&fusbh200->lock, flags); +} + +static void fusbh200_clear_tt_buffer(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh, + struct urb *urb, u32 token) +{ + + /* If an async split transaction gets an error or is unlinked, + * the TT buffer may be left in an indeterminate state. We + * have to clear the TT buffer. + * + * Note: this routine is never called for Isochronous transfers. + */ + if (urb->dev->tt && !usb_pipeint(urb->pipe) && !qh->clearing_tt) { +#ifdef DEBUG + struct usb_device *tt = urb->dev->tt->hub; + dev_dbg(&tt->dev, + "clear tt buffer port %d, a%d ep%d t%08x\n", + urb->dev->ttport, urb->dev->devnum, + usb_pipeendpoint(urb->pipe), token); +#endif /* DEBUG */ + if (urb->dev->tt->hub != + fusbh200_to_hcd(fusbh200)->self.root_hub) { + if (usb_hub_clear_tt_buffer(urb) == 0) + qh->clearing_tt = 1; + } + } +} + +static int qtd_copy_status ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + size_t length, + u32 token +) +{ + int status = -EINPROGRESS; + + /* count IN/OUT bytes, not SETUP (even short packets) */ + if (likely (QTD_PID (token) != 2)) + urb->actual_length += length - QTD_LENGTH (token); + + /* don't modify error codes */ + if (unlikely(urb->unlinked)) + return status; + + /* force cleanup after short read; not always an error */ + if (unlikely (IS_SHORT_READ (token))) + status = -EREMOTEIO; + + /* serious "can't proceed" faults reported by the hardware */ + if (token & QTD_STS_HALT) { + if (token & QTD_STS_BABBLE) { + /* FIXME "must" disable babbling device's port too */ + status = -EOVERFLOW; + /* CERR nonzero + halt --> stall */ + } else if (QTD_CERR(token)) { + status = -EPIPE; + + /* In theory, more than one of the following bits can be set + * since they are sticky and the transaction is retried. + * Which to test first is rather arbitrary. + */ + } else if (token & QTD_STS_MMF) { + /* fs/ls interrupt xfer missed the complete-split */ + status = -EPROTO; + } else if (token & QTD_STS_DBE) { + status = (QTD_PID (token) == 1) /* IN ? */ + ? -ENOSR /* hc couldn't read data */ + : -ECOMM; /* hc couldn't write data */ + } else if (token & QTD_STS_XACT) { + /* timeout, bad CRC, wrong PID, etc */ + fusbh200_dbg(fusbh200, "devpath %s ep%d%s 3strikes\n", + urb->dev->devpath, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out"); + status = -EPROTO; + } else { /* unknown */ + status = -EPROTO; + } + + fusbh200_vdbg (fusbh200, + "dev%d ep%d%s qtd token %08x --> status %d\n", + usb_pipedevice (urb->pipe), + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + token, status); + } + + return status; +} + +static void +fusbh200_urb_done(struct fusbh200_hcd *fusbh200, struct urb *urb, int status) +__releases(fusbh200->lock) +__acquires(fusbh200->lock) +{ + if (likely (urb->hcpriv != NULL)) { + struct fusbh200_qh *qh = (struct fusbh200_qh *) urb->hcpriv; + + /* S-mask in a QH means it's an interrupt urb */ + if ((qh->hw->hw_info2 & cpu_to_hc32(fusbh200, QH_SMASK)) != 0) { + + /* ... update hc-wide periodic stats (for usbfs) */ + fusbh200_to_hcd(fusbh200)->self.bandwidth_int_reqs--; + } + } + + if (unlikely(urb->unlinked)) { + COUNT(fusbh200->stats.unlink); + } else { + /* report non-error and short read status as zero */ + if (status == -EINPROGRESS || status == -EREMOTEIO) + status = 0; + COUNT(fusbh200->stats.complete); + } + +#ifdef FUSBH200_URB_TRACE + fusbh200_dbg (fusbh200, + "%s %s urb %p ep%d%s status %d len %d/%d\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + status, + urb->actual_length, urb->transfer_buffer_length); +#endif + + /* complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(fusbh200_to_hcd(fusbh200), urb); + spin_unlock (&fusbh200->lock); + usb_hcd_giveback_urb(fusbh200_to_hcd(fusbh200), urb, status); + spin_lock (&fusbh200->lock); +} + +static int qh_schedule (struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh); + +/* + * Process and free completed qtds for a qh, returning URBs to drivers. + * Chases up to qh->hw_current. Returns number of completions called, + * indicating how much "real" work we did. + */ +static unsigned +qh_completions (struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qtd *last, *end = qh->dummy; + struct list_head *entry, *tmp; + int last_status; + int stopped; + unsigned count = 0; + u8 state; + struct fusbh200_qh_hw *hw = qh->hw; + + if (unlikely (list_empty (&qh->qtd_list))) + return count; + + /* completions (or tasks on other cpus) must never clobber HALT + * till we've gone through and cleaned everything up, even when + * they add urbs to this qh's queue or mark them for unlinking. + * + * NOTE: unlinking expects to be done in queue order. + * + * It's a bug for qh->qh_state to be anything other than + * QH_STATE_IDLE, unless our caller is scan_async() or + * scan_intr(). + */ + state = qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + stopped = (state == QH_STATE_IDLE); + + rescan: + last = NULL; + last_status = -EINPROGRESS; + qh->needs_rescan = 0; + + /* remove de-activated QTDs from front of queue. + * after faults (including short reads), cleanup this urb + * then let the queue advance. + * if queue is stopped, handles unlinks. + */ + list_for_each_safe (entry, tmp, &qh->qtd_list) { + struct fusbh200_qtd *qtd; + struct urb *urb; + u32 token = 0; + + qtd = list_entry (entry, struct fusbh200_qtd, qtd_list); + urb = qtd->urb; + + /* clean up any state from previous QTD ...*/ + if (last) { + if (likely (last->urb != urb)) { + fusbh200_urb_done(fusbh200, last->urb, last_status); + count++; + last_status = -EINPROGRESS; + } + fusbh200_qtd_free (fusbh200, last); + last = NULL; + } + + /* ignore urbs submitted during completions we reported */ + if (qtd == end) + break; + + /* hardware copies qtd out of qh overlay */ + rmb (); + token = hc32_to_cpu(fusbh200, qtd->hw_token); + + /* always clean up qtds the hc de-activated */ + retry_xacterr: + if ((token & QTD_STS_ACTIVE) == 0) { + + /* Report Data Buffer Error: non-fatal but useful */ + if (token & QTD_STS_DBE) + fusbh200_dbg(fusbh200, + "detected DataBufferErr for urb %p ep%d%s len %d, qtd %p [qh %p]\n", + urb, + usb_endpoint_num(&urb->ep->desc), + usb_endpoint_dir_in(&urb->ep->desc) ? "in" : "out", + urb->transfer_buffer_length, + qtd, + qh); + + /* on STALL, error, and short reads this urb must + * complete and all its qtds must be recycled. + */ + if ((token & QTD_STS_HALT) != 0) { + + /* retry transaction errors until we + * reach the software xacterr limit + */ + if ((token & QTD_STS_XACT) && + QTD_CERR(token) == 0 && + ++qh->xacterrs < QH_XACTERR_MAX && + !urb->unlinked) { + fusbh200_dbg(fusbh200, + "detected XactErr len %zu/%zu retry %d\n", + qtd->length - QTD_LENGTH(token), qtd->length, qh->xacterrs); + + /* reset the token in the qtd and the + * qh overlay (which still contains + * the qtd) so that we pick up from + * where we left off + */ + token &= ~QTD_STS_HALT; + token |= QTD_STS_ACTIVE | + (FUSBH200_TUNE_CERR << 10); + qtd->hw_token = cpu_to_hc32(fusbh200, + token); + wmb(); + hw->hw_token = cpu_to_hc32(fusbh200, + token); + goto retry_xacterr; + } + stopped = 1; + + /* magic dummy for some short reads; qh won't advance. + * that silicon quirk can kick in with this dummy too. + * + * other short reads won't stop the queue, including + * control transfers (status stage handles that) or + * most other single-qtd reads ... the queue stops if + * URB_SHORT_NOT_OK was set so the driver submitting + * the urbs could clean it up. + */ + } else if (IS_SHORT_READ (token) + && !(qtd->hw_alt_next + & FUSBH200_LIST_END(fusbh200))) { + stopped = 1; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely (!stopped + && fusbh200->rh_state >= FUSBH200_RH_RUNNING)) { + break; + + /* scan the whole queue for unlinks whenever it stops */ + } else { + stopped = 1; + + /* cancel everything if we halt, suspend, etc */ + if (fusbh200->rh_state < FUSBH200_RH_RUNNING) + last_status = -ESHUTDOWN; + + /* this qtd is active; skip it unless a previous qtd + * for its urb faulted, or its urb was canceled. + */ + else if (last_status == -EINPROGRESS && !urb->unlinked) + continue; + + /* qh unlinked; token in overlay may be most current */ + if (state == QH_STATE_IDLE + && cpu_to_hc32(fusbh200, qtd->qtd_dma) + == hw->hw_current) { + token = hc32_to_cpu(fusbh200, hw->hw_token); + + /* An unlink may leave an incomplete + * async transaction in the TT buffer. + * We have to clear it. + */ + fusbh200_clear_tt_buffer(fusbh200, qh, urb, token); + } + } + + /* unless we already know the urb's status, collect qtd status + * and update count of bytes transferred. in common short read + * cases with only one data qtd (including control transfers), + * queue processing won't halt. but with two or more qtds (for + * example, with a 32 KB transfer), when the first qtd gets a + * short read the second must be removed by hand. + */ + if (last_status == -EINPROGRESS) { + last_status = qtd_copy_status(fusbh200, urb, + qtd->length, token); + if (last_status == -EREMOTEIO + && (qtd->hw_alt_next + & FUSBH200_LIST_END(fusbh200))) + last_status = -EINPROGRESS; + + /* As part of low/full-speed endpoint-halt processing + * we must clear the TT buffer (11.17.5). + */ + if (unlikely(last_status != -EINPROGRESS && + last_status != -EREMOTEIO)) { + /* The TT's in some hubs malfunction when they + * receive this request following a STALL (they + * stop sending isochronous packets). Since a + * STALL can't leave the TT buffer in a busy + * state (if you believe Figures 11-48 - 11-51 + * in the USB 2.0 spec), we won't clear the TT + * buffer in this case. Strictly speaking this + * is a violation of the spec. + */ + if (last_status != -EPIPE) + fusbh200_clear_tt_buffer(fusbh200, qh, urb, + token); + } + } + + /* if we're removing something not at the queue head, + * patch the hardware queue pointer. + */ + if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { + last = list_entry (qtd->qtd_list.prev, + struct fusbh200_qtd, qtd_list); + last->hw_next = qtd->hw_next; + } + + /* remove qtd; it's recycled after possible urb completion */ + list_del (&qtd->qtd_list); + last = qtd; + + /* reinit the xacterr counter for the next qtd */ + qh->xacterrs = 0; + } + + /* last urb's completion might still need calling */ + if (likely (last != NULL)) { + fusbh200_urb_done(fusbh200, last->urb, last_status); + count++; + fusbh200_qtd_free (fusbh200, last); + } + + /* Do we need to rescan for URBs dequeued during a giveback? */ + if (unlikely(qh->needs_rescan)) { + /* If the QH is already unlinked, do the rescan now. */ + if (state == QH_STATE_IDLE) + goto rescan; + + /* Otherwise we have to wait until the QH is fully unlinked. + * Our caller will start an unlink if qh->needs_rescan is + * set. But if an unlink has already started, nothing needs + * to be done. + */ + if (state != QH_STATE_LINKED) + qh->needs_rescan = 0; + } + + /* restore original state; caller must unlink or relink */ + qh->qh_state = state; + + /* be sure the hardware's done with the qh before refreshing + * it after fault cleanup, or recovering from silicon wrongly + * overlaying the dummy qtd (which reduces DMA chatter). + */ + if (stopped != 0 || hw->hw_qtd_next == FUSBH200_LIST_END(fusbh200)) { + switch (state) { + case QH_STATE_IDLE: + qh_refresh(fusbh200, qh); + break; + case QH_STATE_LINKED: + /* We won't refresh a QH that's linked (after the HC + * stopped the queue). That avoids a race: + * - HC reads first part of QH; + * - CPU updates that first part and the token; + * - HC reads rest of that QH, including token + * Result: HC gets an inconsistent image, and then + * DMAs to/from the wrong memory (corrupting it). + * + * That should be rare for interrupt transfers, + * except maybe high bandwidth ... + */ + + /* Tell the caller to start an unlink */ + qh->needs_rescan = 1; + break; + /* otherwise, unlink already started */ + } + } + + return count; +} + +/*-------------------------------------------------------------------------*/ + +// high bandwidth multiplier, as encoded in highspeed endpoint descriptors +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) +// ... and packet size, for any kind of endpoint descriptor +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/* + * reverse of qh_urb_transaction: free a list of TDs. + * used for cleanup after errors, before HC sees an URB's TDs. + */ +static void qtd_list_free ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *qtd_list +) { + struct list_head *entry, *temp; + + list_for_each_safe (entry, temp, qtd_list) { + struct fusbh200_qtd *qtd; + + qtd = list_entry (entry, struct fusbh200_qtd, qtd_list); + list_del (&qtd->qtd_list); + fusbh200_qtd_free (fusbh200, qtd); + } +} + +/* + * create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head * +qh_urb_transaction ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *head, + gfp_t flags +) { + struct fusbh200_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, this_sg_len, maxpacket; + int is_input; + u32 token; + int i; + struct scatterlist *sg; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = fusbh200_qtd_alloc (fusbh200, flags); + if (unlikely (!qtd)) + return NULL; + list_add_tail (&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (FUSBH200_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + len = urb->transfer_buffer_length; + is_input = usb_pipein (urb->pipe); + if (usb_pipecontrol (urb->pipe)) { + /* SETUP pid */ + qtd_fill(fusbh200, qtd, urb->setup_dma, + sizeof (struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = fusbh200_qtd_alloc (fusbh200, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fusbh200, qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (len == 0) + token |= (1 /* "in" */ << 8); + } + + /* + * data transfer stage: buffer setup + */ + i = urb->num_mapped_sgs; + if (len > 0 && i > 0) { + sg = urb->sg; + buf = sg_dma_address(sg); + + /* urb->transfer_buffer_length may be smaller than the + * size of the scatterlist (or vice versa) + */ + this_sg_len = min_t(int, sg_dma_len(sg), len); + } else { + sg = NULL; + buf = urb->transfer_dma; + this_sg_len = len; + } + + if (is_input) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input)); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + this_qtd_len = qtd_fill(fusbh200, qtd, buf, this_sg_len, token, + maxpacket); + this_sg_len -= this_qtd_len; + len -= this_qtd_len; + buf += this_qtd_len; + + /* + * short reads advance to a "magic" dummy instead of the next + * qtd ... that forces the queue to stop, for manual cleanup. + * (this will usually be overridden later.) + */ + if (is_input) + qtd->hw_alt_next = fusbh200->async->hw->hw_alt_next; + + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely(this_sg_len <= 0)) { + if (--i <= 0 || len <= 0) + break; + sg = sg_next(sg); + buf = sg_dma_address(sg); + this_sg_len = min_t(int, sg_dma_len(sg), len); + } + + qtd_prev = qtd; + qtd = fusbh200_qtd_alloc (fusbh200, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fusbh200, qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + } + + /* + * unless the caller requires manual cleanup after short reads, + * have the alt_next mechanism keep the queue running after the + * last data qtd (the only one, for control and most other cases). + */ + if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 + || usb_pipecontrol (urb->pipe))) + qtd->hw_alt_next = FUSBH200_LIST_END(fusbh200); + + /* + * control requests may need a terminating data "status" ack; + * other OUT ones may need a terminating short packet + * (zero length). + */ + if (likely (urb->transfer_buffer_length != 0)) { + int one_more = 0; + + if (usb_pipecontrol (urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipeout(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = fusbh200_qtd_alloc (fusbh200, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fusbh200, qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill(fusbh200, qtd, 0, 0, token, 0); + } + } + + /* by default, enable interrupt on urb completion */ + if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT))) + qtd->hw_token |= cpu_to_hc32(fusbh200, QTD_IOC); + return head; + +cleanup: + qtd_list_free (fusbh200, urb, head); + return NULL; +} + +/*-------------------------------------------------------------------------*/ + +// Would be best to create all qh's from config descriptors, +// when each interface/altsetting is established. Unlink +// any previous qh and cancel its urbs first; endpoints are +// implicitly reset then (data toggle too). +// That'd mean updating how usbcore talks to HCDs. (2.7?) + + +/* + * Each QH holds a qtd list; a QH is used for everything except iso. + * + * For interrupt urbs, the scheduler must set the microframe scheduling + * mask(s) each time the QH gets scheduled. For highspeed, that's + * just one microframe in the s-mask. For split interrupt transactions + * there are additional complications: c-mask, maybe FSTNs. + */ +static struct fusbh200_qh * +qh_make ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + gfp_t flags +) { + struct fusbh200_qh *qh = fusbh200_qh_alloc (fusbh200, flags); + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + struct usb_tt *tt = urb->dev->tt; + struct fusbh200_qh_hw *hw; + + if (!qh) + return qh; + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint (urb->pipe) << 8; + info1 |= usb_pipedevice (urb->pipe) << 0; + + is_input = usb_pipein (urb->pipe); + type = usb_pipetype (urb->pipe); + maxp = usb_maxpacket (urb->dev, urb->pipe, !is_input); + + /* 1024 byte maxpacket is a hardware ceiling. High bandwidth + * acts like up to 3KB, but is built from smaller packets. + */ + if (max_packet(maxp) > 1024) { + fusbh200_dbg(fusbh200, "bogus qh maxpacket %d\n", max_packet(maxp)); + goto done; + } + + /* Compute interrupt scheduling parameters just once, and save. + * - allowing for high bandwidth, how many nsec/uframe are used? + * - split transactions need a second CSPLIT uframe; same question + * - splits also need a schedule gap (for full/low speed I/O) + * - qh has a polling interval + * + * For control/bulk requests, the HC or TT handles these. + */ + if (type == PIPE_INTERRUPT) { + qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, + is_input, 0, + hb_mult(maxp) * max_packet(maxp))); + qh->start = NO_FRAME; + + if (urb->dev->speed == USB_SPEED_HIGH) { + qh->c_usecs = 0; + qh->gap_uf = 0; + + qh->period = urb->interval >> 3; + if (qh->period == 0 && urb->interval != 1) { + /* NOTE interval 2 or 4 uframes could work. + * But interval 1 scheduling is simpler, and + * includes high bandwidth. + */ + urb->interval = 1; + } else if (qh->period > fusbh200->periodic_size) { + qh->period = fusbh200->periodic_size; + urb->interval = qh->period << 3; + } + } else { + int think_time; + + /* gap is f(FS/LS transfer times) */ + qh->gap_uf = 1 + usb_calc_bus_time (urb->dev->speed, + is_input, 0, maxp) / (125 * 1000); + + /* FIXME this just approximates SPLIT/CSPLIT times */ + if (is_input) { // SPLIT, gap, CSPLIT+DATA + qh->c_usecs = qh->usecs + HS_USECS (0); + qh->usecs = HS_USECS (1); + } else { // SPLIT+DATA, gap, CSPLIT + qh->usecs += HS_USECS (1); + qh->c_usecs = HS_USECS (0); + } + + think_time = tt ? tt->think_time : 0; + qh->tt_usecs = NS_TO_US (think_time + + usb_calc_bus_time (urb->dev->speed, + is_input, 0, max_packet (maxp))); + qh->period = urb->interval; + if (qh->period > fusbh200->periodic_size) { + qh->period = fusbh200->periodic_size; + urb->interval = qh->period; + } + } + } + + /* support for tt scheduling, and access to toggles */ + qh->dev = urb->dev; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= QH_LOW_SPEED; + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) + info1 |= (FUSBH200_TUNE_RL_TT << 28); + if (type == PIPE_CONTROL) { + info1 |= QH_CONTROL_EP; /* for TT */ + info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ + } + info1 |= maxp << 16; + + info2 |= (FUSBH200_TUNE_MULT_TT << 30); + + /* Some Freescale processors have an erratum in which the + * port number in the queue head was 0..N-1 instead of 1..N. + */ + if (fusbh200_has_fsl_portno_bug(fusbh200)) + info2 |= (urb->dev->ttport-1) << 23; + else + info2 |= urb->dev->ttport << 23; + + /* set the address of the TT; for TDI's integrated + * root hub tt, leave it zeroed. + */ + if (tt && tt->hub != fusbh200_to_hcd(fusbh200)->self.root_hub) + info2 |= tt->hub->devnum << 16; + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ + + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= QH_HIGH_SPEED; + if (type == PIPE_CONTROL) { + info1 |= (FUSBH200_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ + info2 |= (FUSBH200_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (FUSBH200_TUNE_RL_HS << 28); + /* The USB spec says that high speed bulk endpoints + * always use 512 byte maxpacket. But some device + * vendors decided to ignore that, and MSFT is happy + * to help them do so. So now people expect to use + * such nonconformant devices with Linux too; sigh. + */ + info1 |= max_packet(maxp) << 16; + info2 |= (FUSBH200_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= max_packet (maxp) << 16; + info2 |= hb_mult (maxp) << 30; + } + break; + default: + fusbh200_dbg(fusbh200, "bogus dev %p speed %d\n", urb->dev, + urb->dev->speed); +done: + qh_destroy(fusbh200, qh); + return NULL; + } + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ + + /* init as live, toggle clear, advance to dummy */ + qh->qh_state = QH_STATE_IDLE; + hw = qh->hw; + hw->hw_info1 = cpu_to_hc32(fusbh200, info1); + hw->hw_info2 = cpu_to_hc32(fusbh200, info2); + qh->is_out = !is_input; + usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1); + qh_refresh (fusbh200, qh); + return qh; +} + +/*-------------------------------------------------------------------------*/ + +static void enable_async(struct fusbh200_hcd *fusbh200) +{ + if (fusbh200->async_count++) + return; + + /* Stop waiting to turn off the async schedule */ + fusbh200->enabled_hrtimer_events &= ~BIT(FUSBH200_HRTIMER_DISABLE_ASYNC); + + /* Don't start the schedule until ASS is 0 */ + fusbh200_poll_ASS(fusbh200); + turn_on_io_watchdog(fusbh200); +} + +static void disable_async(struct fusbh200_hcd *fusbh200) +{ + if (--fusbh200->async_count) + return; + + /* The async schedule and async_unlink list are supposed to be empty */ + WARN_ON(fusbh200->async->qh_next.qh || fusbh200->async_unlink); + + /* Don't turn off the schedule until ASS is 1 */ + fusbh200_poll_ASS(fusbh200); +} + +/* move qh (and its qtds) onto async queue; maybe enable queue. */ + +static void qh_link_async (struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + __hc32 dma = QH_NEXT(fusbh200, qh->qh_dma); + struct fusbh200_qh *head; + + /* Don't link a QH if there's a Clear-TT-Buffer pending */ + if (unlikely(qh->clearing_tt)) + return; + + WARN_ON(qh->qh_state != QH_STATE_IDLE); + + /* clear halt and/or toggle; and maybe recover from silicon quirk */ + qh_refresh(fusbh200, qh); + + /* splice right after start */ + head = fusbh200->async; + qh->qh_next = head->qh_next; + qh->hw->hw_next = head->hw->hw_next; + wmb (); + + head->qh_next.qh = qh; + head->hw->hw_next = dma; + + qh->xacterrs = 0; + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ + + enable_async(fusbh200); +} + +/*-------------------------------------------------------------------------*/ + +/* + * For control/bulk/interrupt, return QH with these TDs appended. + * Allocates and initializes the QH if necessary. + * Returns null if it can't allocate a QH it needs to. + * If the QH has TDs (urbs) already, that's great. + */ +static struct fusbh200_qh *qh_append_tds ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *qtd_list, + int epnum, + void **ptr +) +{ + struct fusbh200_qh *qh = NULL; + __hc32 qh_addr_mask = cpu_to_hc32(fusbh200, 0x7f); + + qh = (struct fusbh200_qh *) *ptr; + if (unlikely (qh == NULL)) { + /* can't sleep here, we have fusbh200->lock... */ + qh = qh_make (fusbh200, urb, GFP_ATOMIC); + *ptr = qh; + } + if (likely (qh != NULL)) { + struct fusbh200_qtd *qtd; + + if (unlikely (list_empty (qtd_list))) + qtd = NULL; + else + qtd = list_entry (qtd_list->next, struct fusbh200_qtd, + qtd_list); + + /* control qh may need patching ... */ + if (unlikely (epnum == 0)) { + + /* usb_reset_device() briefly reverts to address 0 */ + if (usb_pipedevice (urb->pipe) == 0) + qh->hw->hw_info1 &= ~qh_addr_mask; + } + + /* just one way to queue requests: swap with the dummy qtd. + * only hc or qh_refresh() ever modify the overlay. + */ + if (likely (qtd != NULL)) { + struct fusbh200_qtd *dummy; + dma_addr_t dma; + __hc32 token; + + /* to avoid racing the HC, use the dummy td instead of + * the first td of our list (becomes new dummy). both + * tds stay deactivated until we're done, when the + * HC is allowed to fetch the old dummy (4.10.2). + */ + token = qtd->hw_token; + qtd->hw_token = HALT_BIT(fusbh200); + + dummy = qh->dummy; + + dma = dummy->qtd_dma; + *dummy = *qtd; + dummy->qtd_dma = dma; + + list_del (&qtd->qtd_list); + list_add (&dummy->qtd_list, qtd_list); + list_splice_tail(qtd_list, &qh->qtd_list); + + fusbh200_qtd_init(fusbh200, qtd, qtd->qtd_dma); + qh->dummy = qtd; + + /* hc must see the new dummy at list end */ + dma = qtd->qtd_dma; + qtd = list_entry (qh->qtd_list.prev, + struct fusbh200_qtd, qtd_list); + qtd->hw_next = QTD_NEXT(fusbh200, dma); + + /* let the hc process these next qtds */ + wmb (); + dummy->hw_token = token; + + urb->hcpriv = qh; + } + } + return qh; +} + +/*-------------------------------------------------------------------------*/ + +static int +submit_async ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *qtd_list, + gfp_t mem_flags +) { + int epnum; + unsigned long flags; + struct fusbh200_qh *qh = NULL; + int rc; + + epnum = urb->ep->desc.bEndpointAddress; + +#ifdef FUSBH200_URB_TRACE + { + struct fusbh200_qtd *qtd; + qtd = list_entry(qtd_list->next, struct fusbh200_qtd, qtd_list); + fusbh200_dbg(fusbh200, + "%s %s urb %p ep%d%s len %d, qtd %p [qh %p]\n", + __func__, urb->dev->devpath, urb, + epnum & 0x0f, (epnum & USB_DIR_IN) ? "in" : "out", + urb->transfer_buffer_length, + qtd, urb->ep->hcpriv); + } +#endif + + spin_lock_irqsave (&fusbh200->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(fusbh200_to_hcd(fusbh200)))) { + rc = -ESHUTDOWN; + goto done; + } + rc = usb_hcd_link_urb_to_ep(fusbh200_to_hcd(fusbh200), urb); + if (unlikely(rc)) + goto done; + + qh = qh_append_tds(fusbh200, urb, qtd_list, epnum, &urb->ep->hcpriv); + if (unlikely(qh == NULL)) { + usb_hcd_unlink_urb_from_ep(fusbh200_to_hcd(fusbh200), urb); + rc = -ENOMEM; + goto done; + } + + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + if (likely (qh->qh_state == QH_STATE_IDLE)) + qh_link_async(fusbh200, qh); + done: + spin_unlock_irqrestore (&fusbh200->lock, flags); + if (unlikely (qh == NULL)) + qtd_list_free (fusbh200, urb, qtd_list); + return rc; +} + +/*-------------------------------------------------------------------------*/ + +static void single_unlink_async(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qh *prev; + + /* Add to the end of the list of QHs waiting for the next IAAD */ + qh->qh_state = QH_STATE_UNLINK; + if (fusbh200->async_unlink) + fusbh200->async_unlink_last->unlink_next = qh; + else + fusbh200->async_unlink = qh; + fusbh200->async_unlink_last = qh; + + /* Unlink it from the schedule */ + prev = fusbh200->async; + while (prev->qh_next.qh != qh) + prev = prev->qh_next.qh; + + prev->hw->hw_next = qh->hw->hw_next; + prev->qh_next = qh->qh_next; + if (fusbh200->qh_scan_next == qh) + fusbh200->qh_scan_next = qh->qh_next.qh; +} + +static void start_iaa_cycle(struct fusbh200_hcd *fusbh200, bool nested) +{ + /* + * Do nothing if an IAA cycle is already running or + * if one will be started shortly. + */ + if (fusbh200->async_iaa || fusbh200->async_unlinking) + return; + + /* Do all the waiting QHs at once */ + fusbh200->async_iaa = fusbh200->async_unlink; + fusbh200->async_unlink = NULL; + + /* If the controller isn't running, we don't have to wait for it */ + if (unlikely(fusbh200->rh_state < FUSBH200_RH_RUNNING)) { + if (!nested) /* Avoid recursion */ + end_unlink_async(fusbh200); + + /* Otherwise start a new IAA cycle */ + } else if (likely(fusbh200->rh_state == FUSBH200_RH_RUNNING)) { + /* Make sure the unlinks are all visible to the hardware */ + wmb(); + + fusbh200_writel(fusbh200, fusbh200->command | CMD_IAAD, + &fusbh200->regs->command); + fusbh200_readl(fusbh200, &fusbh200->regs->command); + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_IAA_WATCHDOG, true); + } +} + +/* the async qh for the qtds being unlinked are now gone from the HC */ + +static void end_unlink_async(struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_qh *qh; + + /* Process the idle QHs */ + restart: + fusbh200->async_unlinking = true; + while (fusbh200->async_iaa) { + qh = fusbh200->async_iaa; + fusbh200->async_iaa = qh->unlink_next; + qh->unlink_next = NULL; + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + + qh_completions(fusbh200, qh); + if (!list_empty(&qh->qtd_list) && + fusbh200->rh_state == FUSBH200_RH_RUNNING) + qh_link_async(fusbh200, qh); + disable_async(fusbh200); + } + fusbh200->async_unlinking = false; + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (fusbh200->async_unlink) { + start_iaa_cycle(fusbh200, true); + if (unlikely(fusbh200->rh_state < FUSBH200_RH_RUNNING)) + goto restart; + } +} + +static void unlink_empty_async(struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_qh *qh, *next; + bool stopped = (fusbh200->rh_state < FUSBH200_RH_RUNNING); + bool check_unlinks_later = false; + + /* Unlink all the async QHs that have been empty for a timer cycle */ + next = fusbh200->async->qh_next.qh; + while (next) { + qh = next; + next = qh->qh_next.qh; + + if (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED) { + if (!stopped && qh->unlink_cycle == + fusbh200->async_unlink_cycle) + check_unlinks_later = true; + else + single_unlink_async(fusbh200, qh); + } + } + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (fusbh200->async_unlink) + start_iaa_cycle(fusbh200, false); + + /* QHs that haven't been empty for long enough will be handled later */ + if (check_unlinks_later) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_ASYNC_UNLINKS, true); + ++fusbh200->async_unlink_cycle; + } +} + +/* makes sure the async qh will become idle */ +/* caller must own fusbh200->lock */ + +static void start_unlink_async(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + /* + * If the QH isn't linked then there's nothing we can do + * unless we were called during a giveback, in which case + * qh_completions() has to deal with it. + */ + if (qh->qh_state != QH_STATE_LINKED) { + if (qh->qh_state == QH_STATE_COMPLETING) + qh->needs_rescan = 1; + return; + } + + single_unlink_async(fusbh200, qh); + start_iaa_cycle(fusbh200, false); +} + +/*-------------------------------------------------------------------------*/ + +static void scan_async (struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_qh *qh; + bool check_unlinks_later = false; + + fusbh200->qh_scan_next = fusbh200->async->qh_next.qh; + while (fusbh200->qh_scan_next) { + qh = fusbh200->qh_scan_next; + fusbh200->qh_scan_next = qh->qh_next.qh; + rescan: + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list)) { + int temp; + + /* + * Unlinks could happen here; completion reporting + * drops the lock. That's why fusbh200->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then fusbh200->qh_scan_next is adjusted + * in single_unlink_async(). + */ + temp = qh_completions(fusbh200, qh); + if (qh->needs_rescan) { + start_unlink_async(fusbh200, qh); + } else if (list_empty(&qh->qtd_list) + && qh->qh_state == QH_STATE_LINKED) { + qh->unlink_cycle = fusbh200->async_unlink_cycle; + check_unlinks_later = true; + } else if (temp != 0) + goto rescan; + } + } + + /* + * Unlink empty entries, reducing DMA usage as well + * as HCD schedule-scanning costs. Delay for any qh + * we just scanned, there's a not-unusual case that it + * doesn't stay idle for long. + */ + if (check_unlinks_later && fusbh200->rh_state == FUSBH200_RH_RUNNING && + !(fusbh200->enabled_hrtimer_events & + BIT(FUSBH200_HRTIMER_ASYNC_UNLINKS))) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_ASYNC_UNLINKS, true); + ++fusbh200->async_unlink_cycle; + } +} +/*-------------------------------------------------------------------------*/ +/* + * EHCI scheduled transaction support: interrupt, iso, split iso + * These are called "periodic" transactions in the EHCI spec. + * + * Note that for interrupt transfers, the QH/QTD manipulation is shared + * with the "asynchronous" transaction support (control/bulk transfers). + * The only real difference is in how interrupt transfers are scheduled. + * + * For ISO, we make an "iso_stream" head to serve the same role as a QH. + * It keeps track of every ITD (or SITD) that's linked, and holds enough + * pre-calculated schedule data to make appending to the queue be quick. + */ + +static int fusbh200_get_frame (struct usb_hcd *hcd); + +/*-------------------------------------------------------------------------*/ + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd + * @tag: hardware tag for type of this record + */ +static union fusbh200_shadow * +periodic_next_shadow(struct fusbh200_hcd *fusbh200, union fusbh200_shadow *periodic, + __hc32 tag) +{ + switch (hc32_to_cpu(fusbh200, tag)) { + case Q_TYPE_QH: + return &periodic->qh->qh_next; + case Q_TYPE_FSTN: + return &periodic->fstn->fstn_next; + default: + return &periodic->itd->itd_next; + } +} + +static __hc32 * +shadow_next_periodic(struct fusbh200_hcd *fusbh200, union fusbh200_shadow *periodic, + __hc32 tag) +{ + switch (hc32_to_cpu(fusbh200, tag)) { + /* our fusbh200_shadow.qh is actually software part */ + case Q_TYPE_QH: + return &periodic->qh->hw->hw_next; + /* others are hw parts */ + default: + return periodic->hw_next; + } +} + +/* caller must hold fusbh200->lock */ +static void periodic_unlink (struct fusbh200_hcd *fusbh200, unsigned frame, void *ptr) +{ + union fusbh200_shadow *prev_p = &fusbh200->pshadow[frame]; + __hc32 *hw_p = &fusbh200->periodic[frame]; + union fusbh200_shadow here = *prev_p; + + /* find predecessor of "ptr"; hw and shadow lists are in sync */ + while (here.ptr && here.ptr != ptr) { + prev_p = periodic_next_shadow(fusbh200, prev_p, + Q_NEXT_TYPE(fusbh200, *hw_p)); + hw_p = shadow_next_periodic(fusbh200, &here, + Q_NEXT_TYPE(fusbh200, *hw_p)); + here = *prev_p; + } + /* an interrupt entry (at list end) could have been shared */ + if (!here.ptr) + return; + + /* update shadow and hardware lists ... the old "next" pointers + * from ptr may still be in use, the caller updates them. + */ + *prev_p = *periodic_next_shadow(fusbh200, &here, + Q_NEXT_TYPE(fusbh200, *hw_p)); + + *hw_p = *shadow_next_periodic(fusbh200, &here, + Q_NEXT_TYPE(fusbh200, *hw_p)); +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short +periodic_usecs (struct fusbh200_hcd *fusbh200, unsigned frame, unsigned uframe) +{ + __hc32 *hw_p = &fusbh200->periodic [frame]; + union fusbh200_shadow *q = &fusbh200->pshadow [frame]; + unsigned usecs = 0; + struct fusbh200_qh_hw *hw; + + while (q->ptr) { + switch (hc32_to_cpu(fusbh200, Q_NEXT_TYPE(fusbh200, *hw_p))) { + case Q_TYPE_QH: + hw = q->qh->hw; + /* is it in the S-mask? */ + if (hw->hw_info2 & cpu_to_hc32(fusbh200, 1 << uframe)) + usecs += q->qh->usecs; + /* ... or C-mask? */ + if (hw->hw_info2 & cpu_to_hc32(fusbh200, + 1 << (8 + uframe))) + usecs += q->qh->c_usecs; + hw_p = &hw->hw_next; + q = &q->qh->qh_next; + break; + // case Q_TYPE_FSTN: + default: + /* for "save place" FSTNs, count the relevant INTR + * bandwidth from the previous frame + */ + if (q->fstn->hw_prev != FUSBH200_LIST_END(fusbh200)) { + fusbh200_dbg (fusbh200, "ignoring FSTN cost ...\n"); + } + hw_p = &q->fstn->hw_next; + q = &q->fstn->fstn_next; + break; + case Q_TYPE_ITD: + if (q->itd->hw_transaction[uframe]) + usecs += q->itd->stream->usecs; + hw_p = &q->itd->hw_next; + q = &q->itd->itd_next; + break; + } + } +#ifdef DEBUG + if (usecs > fusbh200->uframe_periodic_max) + fusbh200_err (fusbh200, "uframe %d sched overrun: %d usecs\n", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +/*-------------------------------------------------------------------------*/ + +static int same_tt (struct usb_device *dev1, struct usb_device *dev2) +{ + if (!dev1->tt || !dev2->tt) + return 0; + if (dev1->tt != dev2->tt) + return 0; + if (dev1->tt->multi) + return dev1->ttport == dev2->ttport; + else + return 1; +} + +/* return true iff the device's transaction translator is available + * for a periodic transfer starting at the specified frame, using + * all the uframes in the mask. + */ +static int tt_no_collision ( + struct fusbh200_hcd *fusbh200, + unsigned period, + struct usb_device *dev, + unsigned frame, + u32 uf_mask +) +{ + if (period == 0) /* error */ + return 0; + + /* note bandwidth wastage: split never follows csplit + * (different dev or endpoint) until the next uframe. + * calling convention doesn't make that distinction. + */ + for (; frame < fusbh200->periodic_size; frame += period) { + union fusbh200_shadow here; + __hc32 type; + struct fusbh200_qh_hw *hw; + + here = fusbh200->pshadow [frame]; + type = Q_NEXT_TYPE(fusbh200, fusbh200->periodic [frame]); + while (here.ptr) { + switch (hc32_to_cpu(fusbh200, type)) { + case Q_TYPE_ITD: + type = Q_NEXT_TYPE(fusbh200, here.itd->hw_next); + here = here.itd->itd_next; + continue; + case Q_TYPE_QH: + hw = here.qh->hw; + if (same_tt (dev, here.qh->dev)) { + u32 mask; + + mask = hc32_to_cpu(fusbh200, + hw->hw_info2); + /* "knows" no gap is needed */ + mask |= mask >> 8; + if (mask & uf_mask) + break; + } + type = Q_NEXT_TYPE(fusbh200, hw->hw_next); + here = here.qh->qh_next; + continue; + // case Q_TYPE_FSTN: + default: + fusbh200_dbg (fusbh200, + "periodic frame %d bogus type %d\n", + frame, type); + } + + /* collision or error */ + return 0; + } + } + + /* no collision */ + return 1; +} + +/*-------------------------------------------------------------------------*/ + +static void enable_periodic(struct fusbh200_hcd *fusbh200) +{ + if (fusbh200->periodic_count++) + return; + + /* Stop waiting to turn off the periodic schedule */ + fusbh200->enabled_hrtimer_events &= ~BIT(FUSBH200_HRTIMER_DISABLE_PERIODIC); + + /* Don't start the schedule until PSS is 0 */ + fusbh200_poll_PSS(fusbh200); + turn_on_io_watchdog(fusbh200); +} + +static void disable_periodic(struct fusbh200_hcd *fusbh200) +{ + if (--fusbh200->periodic_count) + return; + + /* Don't turn off the schedule until PSS is 1 */ + fusbh200_poll_PSS(fusbh200); +} + +/*-------------------------------------------------------------------------*/ + +/* periodic schedule slots have iso tds (normal or split) first, then a + * sparse tree for active interrupt transfers. + * + * this just links in a qh; caller guarantees uframe masks are set right. + * no FSTN support (yet; fusbh200 0.96+) + */ +static void qh_link_periodic(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + unsigned i; + unsigned period = qh->period; + + dev_dbg (&qh->dev->dev, + "link qh%d-%04x/%p start %d [%d/%d us]\n", + period, hc32_to_cpup(fusbh200, &qh->hw->hw_info2) + & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* high bandwidth, or otherwise every microframe */ + if (period == 0) + period = 1; + + for (i = qh->start; i < fusbh200->periodic_size; i += period) { + union fusbh200_shadow *prev = &fusbh200->pshadow[i]; + __hc32 *hw_p = &fusbh200->periodic[i]; + union fusbh200_shadow here = *prev; + __hc32 type = 0; + + /* skip the iso nodes at list head */ + while (here.ptr) { + type = Q_NEXT_TYPE(fusbh200, *hw_p); + if (type == cpu_to_hc32(fusbh200, Q_TYPE_QH)) + break; + prev = periodic_next_shadow(fusbh200, prev, type); + hw_p = shadow_next_periodic(fusbh200, &here, type); + here = *prev; + } + + /* sorting each branch by period (slow-->fast) + * enables sharing interior tree nodes + */ + while (here.ptr && qh != here.qh) { + if (qh->period > here.qh->period) + break; + prev = &here.qh->qh_next; + hw_p = &here.qh->hw->hw_next; + here = *prev; + } + /* link in this qh, unless some earlier pass did that */ + if (qh != here.qh) { + qh->qh_next = here; + if (here.qh) + qh->hw->hw_next = *hw_p; + wmb (); + prev->qh = qh; + *hw_p = QH_NEXT (fusbh200, qh->qh_dma); + } + } + qh->qh_state = QH_STATE_LINKED; + qh->xacterrs = 0; + + /* update per-qh bandwidth for usbfs */ + fusbh200_to_hcd(fusbh200)->self.bandwidth_allocated += qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + list_add(&qh->intr_node, &fusbh200->intr_qh_list); + + /* maybe enable periodic schedule processing */ + ++fusbh200->intr_count; + enable_periodic(fusbh200); +} + +static void qh_unlink_periodic(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + unsigned i; + unsigned period; + + /* + * If qh is for a low/full-speed device, simply unlinking it + * could interfere with an ongoing split transaction. To unlink + * it safely would require setting the QH_INACTIVATE bit and + * waiting at least one frame, as described in EHCI 4.12.2.5. + * + * We won't bother with any of this. Instead, we assume that the + * only reason for unlinking an interrupt QH while the current URB + * is still active is to dequeue all the URBs (flush the whole + * endpoint queue). + * + * If rebalancing the periodic schedule is ever implemented, this + * approach will no longer be valid. + */ + + /* high bandwidth, or otherwise part of every microframe */ + if ((period = qh->period) == 0) + period = 1; + + for (i = qh->start; i < fusbh200->periodic_size; i += period) + periodic_unlink (fusbh200, i, qh); + + /* update per-qh bandwidth for usbfs */ + fusbh200_to_hcd(fusbh200)->self.bandwidth_allocated -= qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + dev_dbg (&qh->dev->dev, + "unlink qh%d-%04x/%p start %d [%d/%d us]\n", + qh->period, + hc32_to_cpup(fusbh200, &qh->hw->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* qh->qh_next still "live" to HC */ + qh->qh_state = QH_STATE_UNLINK; + qh->qh_next.ptr = NULL; + + if (fusbh200->qh_scan_next == qh) + fusbh200->qh_scan_next = list_entry(qh->intr_node.next, + struct fusbh200_qh, intr_node); + list_del(&qh->intr_node); +} + +static void start_unlink_intr(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + /* If the QH isn't linked then there's nothing we can do + * unless we were called during a giveback, in which case + * qh_completions() has to deal with it. + */ + if (qh->qh_state != QH_STATE_LINKED) { + if (qh->qh_state == QH_STATE_COMPLETING) + qh->needs_rescan = 1; + return; + } + + qh_unlink_periodic (fusbh200, qh); + + /* Make sure the unlinks are visible before starting the timer */ + wmb(); + + /* + * The EHCI spec doesn't say how long it takes the controller to + * stop accessing an unlinked interrupt QH. The timer delay is + * 9 uframes; presumably that will be long enough. + */ + qh->unlink_cycle = fusbh200->intr_unlink_cycle; + + /* New entries go at the end of the intr_unlink list */ + if (fusbh200->intr_unlink) + fusbh200->intr_unlink_last->unlink_next = qh; + else + fusbh200->intr_unlink = qh; + fusbh200->intr_unlink_last = qh; + + if (fusbh200->intr_unlinking) + ; /* Avoid recursive calls */ + else if (fusbh200->rh_state < FUSBH200_RH_RUNNING) + fusbh200_handle_intr_unlinks(fusbh200); + else if (fusbh200->intr_unlink == qh) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_UNLINK_INTR, true); + ++fusbh200->intr_unlink_cycle; + } +} + +static void end_unlink_intr(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qh_hw *hw = qh->hw; + int rc; + + qh->qh_state = QH_STATE_IDLE; + hw->hw_next = FUSBH200_LIST_END(fusbh200); + + qh_completions(fusbh200, qh); + + /* reschedule QH iff another request is queued */ + if (!list_empty(&qh->qtd_list) && fusbh200->rh_state == FUSBH200_RH_RUNNING) { + rc = qh_schedule(fusbh200, qh); + + /* An error here likely indicates handshake failure + * or no space left in the schedule. Neither fault + * should happen often ... + * + * FIXME kill the now-dysfunctional queued urbs + */ + if (rc != 0) + fusbh200_err(fusbh200, "can't reschedule qh %p, err %d\n", + qh, rc); + } + + /* maybe turn off periodic schedule */ + --fusbh200->intr_count; + disable_periodic(fusbh200); +} + +/*-------------------------------------------------------------------------*/ + +static int check_period ( + struct fusbh200_hcd *fusbh200, + unsigned frame, + unsigned uframe, + unsigned period, + unsigned usecs +) { + int claimed; + + /* complete split running into next frame? + * given FSTN support, we could sometimes check... + */ + if (uframe >= 8) + return 0; + + /* convert "usecs we need" to "max already claimed" */ + usecs = fusbh200->uframe_periodic_max - usecs; + + /* we "know" 2 and 4 uframe intervals were rejected; so + * for period 0, check _every_ microframe in the schedule. + */ + if (unlikely (period == 0)) { + do { + for (uframe = 0; uframe < 7; uframe++) { + claimed = periodic_usecs (fusbh200, frame, uframe); + if (claimed > usecs) + return 0; + } + } while ((frame += 1) < fusbh200->periodic_size); + + /* just check the specified uframe, at that period */ + } else { + do { + claimed = periodic_usecs (fusbh200, frame, uframe); + if (claimed > usecs) + return 0; + } while ((frame += period) < fusbh200->periodic_size); + } + + // success! + return 1; +} + +static int check_intr_schedule ( + struct fusbh200_hcd *fusbh200, + unsigned frame, + unsigned uframe, + const struct fusbh200_qh *qh, + __hc32 *c_maskp +) +{ + int retval = -ENOSPC; + u8 mask = 0; + + if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period (fusbh200, frame, uframe, qh->period, qh->usecs)) + goto done; + if (!qh->c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + + /* Make sure this tt's buffer is also available for CSPLITs. + * We pessimize a bit; probably the typical full speed case + * doesn't need the second CSPLIT. + * + * NOTE: both SPLIT and CSPLIT could be checked in just + * one smart pass... + */ + mask = 0x03 << (uframe + qh->gap_uf); + *c_maskp = cpu_to_hc32(fusbh200, mask << 8); + + mask |= 1 << uframe; + if (tt_no_collision (fusbh200, qh->period, qh->dev, frame, mask)) { + if (!check_period (fusbh200, frame, uframe + qh->gap_uf + 1, + qh->period, qh->c_usecs)) + goto done; + if (!check_period (fusbh200, frame, uframe + qh->gap_uf, + qh->period, qh->c_usecs)) + goto done; + retval = 0; + } +done: + return retval; +} + +/* "first fit" scheduling policy used the first time through, + * or when the previous schedule slot can't be re-used. + */ +static int qh_schedule(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + int status; + unsigned uframe; + __hc32 c_mask; + unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ + struct fusbh200_qh_hw *hw = qh->hw; + + qh_refresh(fusbh200, qh); + hw->hw_next = FUSBH200_LIST_END(fusbh200); + frame = qh->start; + + /* reuse the previous schedule slots, if we can */ + if (frame < qh->period) { + uframe = ffs(hc32_to_cpup(fusbh200, &hw->hw_info2) & QH_SMASK); + status = check_intr_schedule (fusbh200, frame, --uframe, + qh, &c_mask); + } else { + uframe = 0; + c_mask = 0; + status = -ENOSPC; + } + + /* else scan the schedule to find a group of slots such that all + * uframes have enough periodic bandwidth available. + */ + if (status) { + /* "normal" case, uframing flexible except with splits */ + if (qh->period) { + int i; + + for (i = qh->period; status && i > 0; --i) { + frame = ++fusbh200->random_frame % qh->period; + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule (fusbh200, + frame, uframe, qh, + &c_mask); + if (status == 0) + break; + } + } + + /* qh->period == 0 means every uframe */ + } else { + frame = 0; + status = check_intr_schedule (fusbh200, 0, 0, qh, &c_mask); + } + if (status) + goto done; + qh->start = frame; + + /* reset S-frame and (maybe) C-frame masks */ + hw->hw_info2 &= cpu_to_hc32(fusbh200, ~(QH_CMASK | QH_SMASK)); + hw->hw_info2 |= qh->period + ? cpu_to_hc32(fusbh200, 1 << uframe) + : cpu_to_hc32(fusbh200, QH_SMASK); + hw->hw_info2 |= c_mask; + } else + fusbh200_dbg (fusbh200, "reused qh %p schedule\n", qh); + + /* stuff into the periodic schedule */ + qh_link_periodic(fusbh200, qh); +done: + return status; +} + +static int intr_submit ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *qtd_list, + gfp_t mem_flags +) { + unsigned epnum; + unsigned long flags; + struct fusbh200_qh *qh; + int status; + struct list_head empty; + + /* get endpoint and transfer/schedule data */ + epnum = urb->ep->desc.bEndpointAddress; + + spin_lock_irqsave (&fusbh200->lock, flags); + + if (unlikely(!HCD_HW_ACCESSIBLE(fusbh200_to_hcd(fusbh200)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(fusbh200_to_hcd(fusbh200), urb); + if (unlikely(status)) + goto done_not_linked; + + /* get qh and force any scheduling errors */ + INIT_LIST_HEAD (&empty); + qh = qh_append_tds(fusbh200, urb, &empty, epnum, &urb->ep->hcpriv); + if (qh == NULL) { + status = -ENOMEM; + goto done; + } + if (qh->qh_state == QH_STATE_IDLE) { + if ((status = qh_schedule (fusbh200, qh)) != 0) + goto done; + } + + /* then queue the urb's tds to the qh */ + qh = qh_append_tds(fusbh200, urb, qtd_list, epnum, &urb->ep->hcpriv); + BUG_ON (qh == NULL); + + /* ... update usbfs periodic stats */ + fusbh200_to_hcd(fusbh200)->self.bandwidth_int_reqs++; + +done: + if (unlikely(status)) + usb_hcd_unlink_urb_from_ep(fusbh200_to_hcd(fusbh200), urb); +done_not_linked: + spin_unlock_irqrestore (&fusbh200->lock, flags); + if (status) + qtd_list_free (fusbh200, urb, qtd_list); + + return status; +} + +static void scan_intr(struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_qh *qh; + + list_for_each_entry_safe(qh, fusbh200->qh_scan_next, &fusbh200->intr_qh_list, + intr_node) { + rescan: + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list)) { + int temp; + + /* + * Unlinks could happen here; completion reporting + * drops the lock. That's why fusbh200->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then fusbh200->qh_scan_next is adjusted + * in qh_unlink_periodic(). + */ + temp = qh_completions(fusbh200, qh); + if (unlikely(qh->needs_rescan || + (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED))) + start_unlink_intr(fusbh200, qh); + else if (temp != 0) + goto rescan; + } + } +} + +/*-------------------------------------------------------------------------*/ + +/* fusbh200_iso_stream ops work with both ITD and SITD */ + +static struct fusbh200_iso_stream * +iso_stream_alloc (gfp_t mem_flags) +{ + struct fusbh200_iso_stream *stream; + + stream = kzalloc(sizeof *stream, mem_flags); + if (likely (stream != NULL)) { + INIT_LIST_HEAD(&stream->td_list); + INIT_LIST_HEAD(&stream->free_list); + stream->next_uframe = -1; + } + return stream; +} + +static void +iso_stream_init ( + struct fusbh200_hcd *fusbh200, + struct fusbh200_iso_stream *stream, + struct usb_device *dev, + int pipe, + unsigned interval +) +{ + u32 buf1; + unsigned epnum, maxp; + int is_input; + long bandwidth; + unsigned multi; + + /* + * this might be a "high bandwidth" highspeed endpoint, + * as encoded in the ep descriptor's wMaxPacket field + */ + epnum = usb_pipeendpoint (pipe); + is_input = usb_pipein (pipe) ? USB_DIR_IN : 0; + maxp = usb_maxpacket(dev, pipe, !is_input); + if (is_input) { + buf1 = (1 << 11); + } else { + buf1 = 0; + } + + maxp = max_packet(maxp); + multi = hb_mult(maxp); + buf1 |= maxp; + maxp *= multi; + + stream->buf0 = cpu_to_hc32(fusbh200, (epnum << 8) | dev->devnum); + stream->buf1 = cpu_to_hc32(fusbh200, buf1); + stream->buf2 = cpu_to_hc32(fusbh200, multi); + + /* usbfs wants to report the average usecs per frame tied up + * when transfers on this endpoint are scheduled ... + */ + if (dev->speed == USB_SPEED_FULL) { + interval <<= 3; + stream->usecs = NS_TO_US(usb_calc_bus_time(dev->speed, + is_input, 1, maxp)); + stream->usecs /= 8; + } else { + stream->highspeed = 1; + stream->usecs = HS_USECS_ISO (maxp); + } + bandwidth = stream->usecs * 8; + bandwidth /= interval; + + stream->bandwidth = bandwidth; + stream->udev = dev; + stream->bEndpointAddress = is_input | epnum; + stream->interval = interval; + stream->maxp = maxp; +} + +static struct fusbh200_iso_stream * +iso_stream_find (struct fusbh200_hcd *fusbh200, struct urb *urb) +{ + unsigned epnum; + struct fusbh200_iso_stream *stream; + struct usb_host_endpoint *ep; + unsigned long flags; + + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein(urb->pipe)) + ep = urb->dev->ep_in[epnum]; + else + ep = urb->dev->ep_out[epnum]; + + spin_lock_irqsave (&fusbh200->lock, flags); + stream = ep->hcpriv; + + if (unlikely (stream == NULL)) { + stream = iso_stream_alloc(GFP_ATOMIC); + if (likely (stream != NULL)) { + ep->hcpriv = stream; + stream->ep = ep; + iso_stream_init(fusbh200, stream, urb->dev, urb->pipe, + urb->interval); + } + + /* if dev->ep [epnum] is a QH, hw is set */ + } else if (unlikely (stream->hw != NULL)) { + fusbh200_dbg (fusbh200, "dev %s ep%d%s, not iso??\n", + urb->dev->devpath, epnum, + usb_pipein(urb->pipe) ? "in" : "out"); + stream = NULL; + } + + spin_unlock_irqrestore (&fusbh200->lock, flags); + return stream; +} + +/*-------------------------------------------------------------------------*/ + +/* fusbh200_iso_sched ops can be ITD-only or SITD-only */ + +static struct fusbh200_iso_sched * +iso_sched_alloc (unsigned packets, gfp_t mem_flags) +{ + struct fusbh200_iso_sched *iso_sched; + int size = sizeof *iso_sched; + + size += packets * sizeof (struct fusbh200_iso_packet); + iso_sched = kzalloc(size, mem_flags); + if (likely (iso_sched != NULL)) { + INIT_LIST_HEAD (&iso_sched->td_list); + } + return iso_sched; +} + +static inline void +itd_sched_init( + struct fusbh200_hcd *fusbh200, + struct fusbh200_iso_sched *iso_sched, + struct fusbh200_iso_stream *stream, + struct urb *urb +) +{ + unsigned i; + dma_addr_t dma = urb->transfer_dma; + + /* how many uframes are needed for these transfers */ + iso_sched->span = urb->number_of_packets * stream->interval; + + /* figure out per-uframe itd fields that we'll need later + * when we fit new itds into the schedule. + */ + for (i = 0; i < urb->number_of_packets; i++) { + struct fusbh200_iso_packet *uframe = &iso_sched->packet [i]; + unsigned length; + dma_addr_t buf; + u32 trans; + + length = urb->iso_frame_desc [i].length; + buf = dma + urb->iso_frame_desc [i].offset; + + trans = FUSBH200_ISOC_ACTIVE; + trans |= buf & 0x0fff; + if (unlikely (((i + 1) == urb->number_of_packets)) + && !(urb->transfer_flags & URB_NO_INTERRUPT)) + trans |= FUSBH200_ITD_IOC; + trans |= length << 16; + uframe->transaction = cpu_to_hc32(fusbh200, trans); + + /* might need to cross a buffer page within a uframe */ + uframe->bufp = (buf & ~(u64)0x0fff); + buf += length; + if (unlikely ((uframe->bufp != (buf & ~(u64)0x0fff)))) + uframe->cross = 1; + } +} + +static void +iso_sched_free ( + struct fusbh200_iso_stream *stream, + struct fusbh200_iso_sched *iso_sched +) +{ + if (!iso_sched) + return; + // caller must hold fusbh200->lock! + list_splice (&iso_sched->td_list, &stream->free_list); + kfree (iso_sched); +} + +static int +itd_urb_transaction ( + struct fusbh200_iso_stream *stream, + struct fusbh200_hcd *fusbh200, + struct urb *urb, + gfp_t mem_flags +) +{ + struct fusbh200_itd *itd; + dma_addr_t itd_dma; + int i; + unsigned num_itds; + struct fusbh200_iso_sched *sched; + unsigned long flags; + + sched = iso_sched_alloc (urb->number_of_packets, mem_flags); + if (unlikely (sched == NULL)) + return -ENOMEM; + + itd_sched_init(fusbh200, sched, stream, urb); + + if (urb->interval < 8) + num_itds = 1 + (sched->span + 7) / 8; + else + num_itds = urb->number_of_packets; + + /* allocate/init ITDs */ + spin_lock_irqsave (&fusbh200->lock, flags); + for (i = 0; i < num_itds; i++) { + + /* + * Use iTDs from the free list, but not iTDs that may + * still be in use by the hardware. + */ + if (likely(!list_empty(&stream->free_list))) { + itd = list_first_entry(&stream->free_list, + struct fusbh200_itd, itd_list); + if (itd->frame == fusbh200->now_frame) + goto alloc_itd; + list_del (&itd->itd_list); + itd_dma = itd->itd_dma; + } else { + alloc_itd: + spin_unlock_irqrestore (&fusbh200->lock, flags); + itd = dma_pool_alloc (fusbh200->itd_pool, mem_flags, + &itd_dma); + spin_lock_irqsave (&fusbh200->lock, flags); + if (!itd) { + iso_sched_free(stream, sched); + spin_unlock_irqrestore(&fusbh200->lock, flags); + return -ENOMEM; + } + } + + memset (itd, 0, sizeof *itd); + itd->itd_dma = itd_dma; + list_add (&itd->itd_list, &sched->td_list); + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + + /* temporarily store schedule info in hcpriv */ + urb->hcpriv = sched; + urb->error_count = 0; + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static inline int +itd_slot_ok ( + struct fusbh200_hcd *fusbh200, + u32 mod, + u32 uframe, + u8 usecs, + u32 period +) +{ + uframe %= period; + do { + /* can't commit more than uframe_periodic_max usec */ + if (periodic_usecs (fusbh200, uframe >> 3, uframe & 0x7) + > (fusbh200->uframe_periodic_max - usecs)) + return 0; + + /* we know urb->interval is 2^N uframes */ + uframe += period; + } while (uframe < mod); + return 1; +} + +/* + * This scheduler plans almost as far into the future as it has actual + * periodic schedule slots. (Affected by TUNE_FLS, which defaults to + * "as small as possible" to be cache-friendlier.) That limits the size + * transfers you can stream reliably; avoid more than 64 msec per urb. + * Also avoid queue depths of less than fusbh200's worst irq latency (affected + * by the per-urb URB_NO_INTERRUPT hint, the log2_irq_thresh module parameter, + * and other factors); or more than about 230 msec total (for portability, + * given FUSBH200_TUNE_FLS and the slop). Or, write a smarter scheduler! + */ + +#define SCHEDULE_SLOP 80 /* microframes */ + +static int +iso_stream_schedule ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct fusbh200_iso_stream *stream +) +{ + u32 now, next, start, period, span; + int status; + unsigned mod = fusbh200->periodic_size << 3; + struct fusbh200_iso_sched *sched = urb->hcpriv; + + period = urb->interval; + span = sched->span; + + if (span > mod - SCHEDULE_SLOP) { + fusbh200_dbg (fusbh200, "iso request %p too long\n", urb); + status = -EFBIG; + goto fail; + } + + now = fusbh200_read_frame_index(fusbh200) & (mod - 1); + + /* Typical case: reuse current schedule, stream is still active. + * Hopefully there are no gaps from the host falling behind + * (irq delays etc), but if there are we'll take the next + * slot in the schedule, implicitly assuming URB_ISO_ASAP. + */ + if (likely (!list_empty (&stream->td_list))) { + u32 excess; + + /* For high speed devices, allow scheduling within the + * isochronous scheduling threshold. For full speed devices + * and Intel PCI-based controllers, don't (work around for + * Intel ICH9 bug). + */ + if (!stream->highspeed && fusbh200->fs_i_thresh) + next = now + fusbh200->i_thresh; + else + next = now; + + /* Fell behind (by up to twice the slop amount)? + * We decide based on the time of the last currently-scheduled + * slot, not the time of the next available slot. + */ + excess = (stream->next_uframe - period - next) & (mod - 1); + if (excess >= mod - 2 * SCHEDULE_SLOP) + start = next + excess - mod + period * + DIV_ROUND_UP(mod - excess, period); + else + start = next + excess + period; + if (start - now >= mod) { + fusbh200_dbg(fusbh200, "request %p would overflow (%d+%d >= %d)\n", + urb, start - now - period, period, + mod); + status = -EFBIG; + goto fail; + } + } + + /* need to schedule; when's the next (u)frame we could start? + * this is bigger than fusbh200->i_thresh allows; scheduling itself + * isn't free, the slop should handle reasonably slow cpus. it + * can also help high bandwidth if the dma and irq loads don't + * jump until after the queue is primed. + */ + else { + int done = 0; + start = SCHEDULE_SLOP + (now & ~0x07); + + /* NOTE: assumes URB_ISO_ASAP, to limit complexity/bugs */ + + /* find a uframe slot with enough bandwidth. + * Early uframes are more precious because full-speed + * iso IN transfers can't use late uframes, + * and therefore they should be allocated last. + */ + next = start; + start += period; + do { + start--; + /* check schedule: enough space? */ + if (itd_slot_ok(fusbh200, mod, start, + stream->usecs, period)) + done = 1; + } while (start > next && !done); + + /* no room in the schedule */ + if (!done) { + fusbh200_dbg(fusbh200, "iso resched full %p (now %d max %d)\n", + urb, now, now + mod); + status = -ENOSPC; + goto fail; + } + } + + /* Tried to schedule too far into the future? */ + if (unlikely(start - now + span - period + >= mod - 2 * SCHEDULE_SLOP)) { + fusbh200_dbg(fusbh200, "request %p would overflow (%d+%d >= %d)\n", + urb, start - now, span - period, + mod - 2 * SCHEDULE_SLOP); + status = -EFBIG; + goto fail; + } + + stream->next_uframe = start & (mod - 1); + + /* report high speed start in uframes; full speed, in frames */ + urb->start_frame = stream->next_uframe; + if (!stream->highspeed) + urb->start_frame >>= 3; + + /* Make sure scan_isoc() sees these */ + if (fusbh200->isoc_count == 0) + fusbh200->next_frame = now >> 3; + return 0; + + fail: + iso_sched_free(stream, sched); + urb->hcpriv = NULL; + return status; +} + +/*-------------------------------------------------------------------------*/ + +static inline void +itd_init(struct fusbh200_hcd *fusbh200, struct fusbh200_iso_stream *stream, + struct fusbh200_itd *itd) +{ + int i; + + /* it's been recently zeroed */ + itd->hw_next = FUSBH200_LIST_END(fusbh200); + itd->hw_bufp [0] = stream->buf0; + itd->hw_bufp [1] = stream->buf1; + itd->hw_bufp [2] = stream->buf2; + + for (i = 0; i < 8; i++) + itd->index[i] = -1; + + /* All other fields are filled when scheduling */ +} + +static inline void +itd_patch( + struct fusbh200_hcd *fusbh200, + struct fusbh200_itd *itd, + struct fusbh200_iso_sched *iso_sched, + unsigned index, + u16 uframe +) +{ + struct fusbh200_iso_packet *uf = &iso_sched->packet [index]; + unsigned pg = itd->pg; + + // BUG_ON (pg == 6 && uf->cross); + + uframe &= 0x07; + itd->index [uframe] = index; + + itd->hw_transaction[uframe] = uf->transaction; + itd->hw_transaction[uframe] |= cpu_to_hc32(fusbh200, pg << 12); + itd->hw_bufp[pg] |= cpu_to_hc32(fusbh200, uf->bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(fusbh200, (u32)(uf->bufp >> 32)); + + /* iso_frame_desc[].offset must be strictly increasing */ + if (unlikely (uf->cross)) { + u64 bufp = uf->bufp + 4096; + + itd->pg = ++pg; + itd->hw_bufp[pg] |= cpu_to_hc32(fusbh200, bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(fusbh200, (u32)(bufp >> 32)); + } +} + +static inline void +itd_link (struct fusbh200_hcd *fusbh200, unsigned frame, struct fusbh200_itd *itd) +{ + union fusbh200_shadow *prev = &fusbh200->pshadow[frame]; + __hc32 *hw_p = &fusbh200->periodic[frame]; + union fusbh200_shadow here = *prev; + __hc32 type = 0; + + /* skip any iso nodes which might belong to previous microframes */ + while (here.ptr) { + type = Q_NEXT_TYPE(fusbh200, *hw_p); + if (type == cpu_to_hc32(fusbh200, Q_TYPE_QH)) + break; + prev = periodic_next_shadow(fusbh200, prev, type); + hw_p = shadow_next_periodic(fusbh200, &here, type); + here = *prev; + } + + itd->itd_next = here; + itd->hw_next = *hw_p; + prev->itd = itd; + itd->frame = frame; + wmb (); + *hw_p = cpu_to_hc32(fusbh200, itd->itd_dma | Q_TYPE_ITD); +} + +/* fit urb's itds into the selected schedule slot; activate as needed */ +static void itd_link_urb( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + unsigned mod, + struct fusbh200_iso_stream *stream +) +{ + int packet; + unsigned next_uframe, uframe, frame; + struct fusbh200_iso_sched *iso_sched = urb->hcpriv; + struct fusbh200_itd *itd; + + next_uframe = stream->next_uframe & (mod - 1); + + if (unlikely (list_empty(&stream->td_list))) { + fusbh200_to_hcd(fusbh200)->self.bandwidth_allocated + += stream->bandwidth; + fusbh200_vdbg (fusbh200, + "schedule devp %s ep%d%s-iso period %d start %d.%d\n", + urb->dev->devpath, stream->bEndpointAddress & 0x0f, + (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + urb->interval, + next_uframe >> 3, next_uframe & 0x7); + } + + /* fill iTDs uframe by uframe */ + for (packet = 0, itd = NULL; packet < urb->number_of_packets; ) { + if (itd == NULL) { + /* ASSERT: we have all necessary itds */ + // BUG_ON (list_empty (&iso_sched->td_list)); + + /* ASSERT: no itds for this endpoint in this uframe */ + + itd = list_entry (iso_sched->td_list.next, + struct fusbh200_itd, itd_list); + list_move_tail (&itd->itd_list, &stream->td_list); + itd->stream = stream; + itd->urb = urb; + itd_init (fusbh200, stream, itd); + } + + uframe = next_uframe & 0x07; + frame = next_uframe >> 3; + + itd_patch(fusbh200, itd, iso_sched, packet, uframe); + + next_uframe += stream->interval; + next_uframe &= mod - 1; + packet++; + + /* link completed itds into the schedule */ + if (((next_uframe >> 3) != frame) + || packet == urb->number_of_packets) { + itd_link(fusbh200, frame & (fusbh200->periodic_size - 1), itd); + itd = NULL; + } + } + stream->next_uframe = next_uframe; + + /* don't need that schedule data any more */ + iso_sched_free (stream, iso_sched); + urb->hcpriv = NULL; + + ++fusbh200->isoc_count; + enable_periodic(fusbh200); +} + +#define ISO_ERRS (FUSBH200_ISOC_BUF_ERR | FUSBH200_ISOC_BABBLE | FUSBH200_ISOC_XACTERR) + +/* Process and recycle a completed ITD. Return true iff its urb completed, + * and hence its completion callback probably added things to the hardware + * schedule. + * + * Note that we carefully avoid recycling this descriptor until after any + * completion callback runs, so that it won't be reused quickly. That is, + * assuming (a) no more than two urbs per frame on this endpoint, and also + * (b) only this endpoint's completions submit URBs. It seems some silicon + * corrupts things if you reuse completed descriptors very quickly... + */ +static bool itd_complete(struct fusbh200_hcd *fusbh200, struct fusbh200_itd *itd) +{ + struct urb *urb = itd->urb; + struct usb_iso_packet_descriptor *desc; + u32 t; + unsigned uframe; + int urb_index = -1; + struct fusbh200_iso_stream *stream = itd->stream; + struct usb_device *dev; + bool retval = false; + + /* for each uframe with a packet */ + for (uframe = 0; uframe < 8; uframe++) { + if (likely (itd->index[uframe] == -1)) + continue; + urb_index = itd->index[uframe]; + desc = &urb->iso_frame_desc [urb_index]; + + t = hc32_to_cpup(fusbh200, &itd->hw_transaction [uframe]); + itd->hw_transaction [uframe] = 0; + + /* report transfer status */ + if (unlikely (t & ISO_ERRS)) { + urb->error_count++; + if (t & FUSBH200_ISOC_BUF_ERR) + desc->status = usb_pipein (urb->pipe) + ? -ENOSR /* hc couldn't read */ + : -ECOMM; /* hc couldn't write */ + else if (t & FUSBH200_ISOC_BABBLE) + desc->status = -EOVERFLOW; + else /* (t & FUSBH200_ISOC_XACTERR) */ + desc->status = -EPROTO; + + /* HC need not update length with this error */ + if (!(t & FUSBH200_ISOC_BABBLE)) { + desc->actual_length = fusbh200_itdlen(urb, desc, t); + urb->actual_length += desc->actual_length; + } + } else if (likely ((t & FUSBH200_ISOC_ACTIVE) == 0)) { + desc->status = 0; + desc->actual_length = fusbh200_itdlen(urb, desc, t); + urb->actual_length += desc->actual_length; + } else { + /* URB was too late */ + desc->status = -EXDEV; + } + } + + /* handle completion now? */ + if (likely ((urb_index + 1) != urb->number_of_packets)) + goto done; + + /* ASSERT: it's really the last itd for this urb + list_for_each_entry (itd, &stream->td_list, itd_list) + BUG_ON (itd->urb == urb); + */ + + /* give urb back to the driver; completion often (re)submits */ + dev = urb->dev; + fusbh200_urb_done(fusbh200, urb, 0); + retval = true; + urb = NULL; + + --fusbh200->isoc_count; + disable_periodic(fusbh200); + + if (unlikely(list_is_singular(&stream->td_list))) { + fusbh200_to_hcd(fusbh200)->self.bandwidth_allocated + -= stream->bandwidth; + fusbh200_vdbg (fusbh200, + "deschedule devp %s ep%d%s-iso\n", + dev->devpath, stream->bEndpointAddress & 0x0f, + (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out"); + } + +done: + itd->urb = NULL; + + /* Add to the end of the free list for later reuse */ + list_move_tail(&itd->itd_list, &stream->free_list); + + /* Recycle the iTDs when the pipeline is empty (ep no longer in use) */ + if (list_empty(&stream->td_list)) { + list_splice_tail_init(&stream->free_list, + &fusbh200->cached_itd_list); + start_free_itds(fusbh200); + } + + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static int itd_submit (struct fusbh200_hcd *fusbh200, struct urb *urb, + gfp_t mem_flags) +{ + int status = -EINVAL; + unsigned long flags; + struct fusbh200_iso_stream *stream; + + /* Get iso_stream head */ + stream = iso_stream_find (fusbh200, urb); + if (unlikely (stream == NULL)) { + fusbh200_dbg (fusbh200, "can't get iso stream\n"); + return -ENOMEM; + } + if (unlikely (urb->interval != stream->interval && + fusbh200_port_speed(fusbh200, 0) == USB_PORT_STAT_HIGH_SPEED)) { + fusbh200_dbg (fusbh200, "can't change iso interval %d --> %d\n", + stream->interval, urb->interval); + goto done; + } + +#ifdef FUSBH200_URB_TRACE + fusbh200_dbg (fusbh200, + "%s %s urb %p ep%d%s len %d, %d pkts %d uframes [%p]\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + urb->transfer_buffer_length, + urb->number_of_packets, urb->interval, + stream); +#endif + + /* allocate ITDs w/o locking anything */ + status = itd_urb_transaction (stream, fusbh200, urb, mem_flags); + if (unlikely (status < 0)) { + fusbh200_dbg (fusbh200, "can't init itds\n"); + goto done; + } + + /* schedule ... need to lock */ + spin_lock_irqsave (&fusbh200->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(fusbh200_to_hcd(fusbh200)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(fusbh200_to_hcd(fusbh200), urb); + if (unlikely(status)) + goto done_not_linked; + status = iso_stream_schedule(fusbh200, urb, stream); + if (likely (status == 0)) + itd_link_urb (fusbh200, urb, fusbh200->periodic_size << 3, stream); + else + usb_hcd_unlink_urb_from_ep(fusbh200_to_hcd(fusbh200), urb); + done_not_linked: + spin_unlock_irqrestore (&fusbh200->lock, flags); + done: + return status; +} + +/*-------------------------------------------------------------------------*/ + +static void scan_isoc(struct fusbh200_hcd *fusbh200) +{ + unsigned uf, now_frame, frame; + unsigned fmask = fusbh200->periodic_size - 1; + bool modified, live; + + /* + * When running, scan from last scan point up to "now" + * else clean up by scanning everything that's left. + * Touches as few pages as possible: cache-friendly. + */ + if (fusbh200->rh_state >= FUSBH200_RH_RUNNING) { + uf = fusbh200_read_frame_index(fusbh200); + now_frame = (uf >> 3) & fmask; + live = true; + } else { + now_frame = (fusbh200->next_frame - 1) & fmask; + live = false; + } + fusbh200->now_frame = now_frame; + + frame = fusbh200->next_frame; + for (;;) { + union fusbh200_shadow q, *q_p; + __hc32 type, *hw_p; + +restart: + /* scan each element in frame's queue for completions */ + q_p = &fusbh200->pshadow [frame]; + hw_p = &fusbh200->periodic [frame]; + q.ptr = q_p->ptr; + type = Q_NEXT_TYPE(fusbh200, *hw_p); + modified = false; + + while (q.ptr != NULL) { + switch (hc32_to_cpu(fusbh200, type)) { + case Q_TYPE_ITD: + /* If this ITD is still active, leave it for + * later processing ... check the next entry. + * No need to check for activity unless the + * frame is current. + */ + if (frame == now_frame && live) { + rmb(); + for (uf = 0; uf < 8; uf++) { + if (q.itd->hw_transaction[uf] & + ITD_ACTIVE(fusbh200)) + break; + } + if (uf < 8) { + q_p = &q.itd->itd_next; + hw_p = &q.itd->hw_next; + type = Q_NEXT_TYPE(fusbh200, + q.itd->hw_next); + q = *q_p; + break; + } + } + + /* Take finished ITDs out of the schedule + * and process them: recycle, maybe report + * URB completion. HC won't cache the + * pointer for much longer, if at all. + */ + *q_p = q.itd->itd_next; + *hw_p = q.itd->hw_next; + type = Q_NEXT_TYPE(fusbh200, q.itd->hw_next); + wmb(); + modified = itd_complete (fusbh200, q.itd); + q = *q_p; + break; + default: + fusbh200_dbg(fusbh200, "corrupt type %d frame %d shadow %p\n", + type, frame, q.ptr); + // BUG (); + /* FALL THROUGH */ + case Q_TYPE_QH: + case Q_TYPE_FSTN: + /* End of the iTDs and siTDs */ + q.ptr = NULL; + break; + } + + /* assume completion callbacks modify the queue */ + if (unlikely(modified && fusbh200->isoc_count > 0)) + goto restart; + } + + /* Stop when we have reached the current frame */ + if (frame == now_frame) + break; + frame = (frame + 1) & fmask; + } + fusbh200->next_frame = now_frame; +} +/*-------------------------------------------------------------------------*/ +/* + * Display / Set uframe_periodic_max + */ +static ssize_t show_uframe_periodic_max(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fusbh200_hcd *fusbh200; + int n; + + fusbh200 = hcd_to_fusbh200(bus_to_hcd(dev_get_drvdata(dev))); + n = scnprintf(buf, PAGE_SIZE, "%d\n", fusbh200->uframe_periodic_max); + return n; +} + + +static ssize_t store_uframe_periodic_max(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fusbh200_hcd *fusbh200; + unsigned uframe_periodic_max; + unsigned frame, uframe; + unsigned short allocated_max; + unsigned long flags; + ssize_t ret; + + fusbh200 = hcd_to_fusbh200(bus_to_hcd(dev_get_drvdata(dev))); + if (kstrtouint(buf, 0, &uframe_periodic_max) < 0) + return -EINVAL; + + if (uframe_periodic_max < 100 || uframe_periodic_max >= 125) { + fusbh200_info(fusbh200, "rejecting invalid request for " + "uframe_periodic_max=%u\n", uframe_periodic_max); + return -EINVAL; + } + + ret = -EINVAL; + + /* + * lock, so that our checking does not race with possible periodic + * bandwidth allocation through submitting new urbs. + */ + spin_lock_irqsave (&fusbh200->lock, flags); + + /* + * for request to decrease max periodic bandwidth, we have to check + * every microframe in the schedule to see whether the decrease is + * possible. + */ + if (uframe_periodic_max < fusbh200->uframe_periodic_max) { + allocated_max = 0; + + for (frame = 0; frame < fusbh200->periodic_size; ++frame) + for (uframe = 0; uframe < 7; ++uframe) + allocated_max = max(allocated_max, + periodic_usecs (fusbh200, frame, uframe)); + + if (allocated_max > uframe_periodic_max) { + fusbh200_info(fusbh200, + "cannot decrease uframe_periodic_max becase " + "periodic bandwidth is already allocated " + "(%u > %u)\n", + allocated_max, uframe_periodic_max); + goto out_unlock; + } + } + + /* increasing is always ok */ + + fusbh200_info(fusbh200, "setting max periodic bandwidth to %u%% " + "(== %u usec/uframe)\n", + 100*uframe_periodic_max/125, uframe_periodic_max); + + if (uframe_periodic_max != 100) + fusbh200_warn(fusbh200, "max periodic bandwidth set is non-standard\n"); + + fusbh200->uframe_periodic_max = uframe_periodic_max; + ret = count; + +out_unlock: + spin_unlock_irqrestore (&fusbh200->lock, flags); + return ret; +} +static DEVICE_ATTR(uframe_periodic_max, 0644, show_uframe_periodic_max, store_uframe_periodic_max); + + +static inline int create_sysfs_files(struct fusbh200_hcd *fusbh200) +{ + struct device *controller = fusbh200_to_hcd(fusbh200)->self.controller; + int i = 0; + + if (i) + goto out; + + i = device_create_file(controller, &dev_attr_uframe_periodic_max); +out: + return i; +} + +static inline void remove_sysfs_files(struct fusbh200_hcd *fusbh200) +{ + struct device *controller = fusbh200_to_hcd(fusbh200)->self.controller; + + device_remove_file(controller, &dev_attr_uframe_periodic_max); +} +/*-------------------------------------------------------------------------*/ + +/* On some systems, leaving remote wakeup enabled prevents system shutdown. + * The firmware seems to think that powering off is a wakeup event! + * This routine turns off remote wakeup and everything else, on all ports. + */ +static void fusbh200_turn_off_all_ports(struct fusbh200_hcd *fusbh200) +{ + u32 __iomem *status_reg = &fusbh200->regs->port_status; + + fusbh200_writel(fusbh200, PORT_RWC_BITS, status_reg); +} + +/* + * Halt HC, turn off all ports, and let the BIOS use the companion controllers. + * Must be called with interrupts enabled and the lock not held. + */ +static void fusbh200_silence_controller(struct fusbh200_hcd *fusbh200) +{ + fusbh200_halt(fusbh200); + + spin_lock_irq(&fusbh200->lock); + fusbh200->rh_state = FUSBH200_RH_HALTED; + fusbh200_turn_off_all_ports(fusbh200); + spin_unlock_irq(&fusbh200->lock); +} + +/* fusbh200_shutdown kick in for silicon on any bus (not just pci, etc). + * This forcibly disables dma and IRQs, helping kexec and other cases + * where the next system software may expect clean state. + */ +static void fusbh200_shutdown(struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + + spin_lock_irq(&fusbh200->lock); + fusbh200->shutdown = true; + fusbh200->rh_state = FUSBH200_RH_STOPPING; + fusbh200->enabled_hrtimer_events = 0; + spin_unlock_irq(&fusbh200->lock); + + fusbh200_silence_controller(fusbh200); + + hrtimer_cancel(&fusbh200->hrtimer); +} + +/*-------------------------------------------------------------------------*/ + +/* + * fusbh200_work is called from some interrupts, timers, and so on. + * it calls driver completion functions, after dropping fusbh200->lock. + */ +static void fusbh200_work (struct fusbh200_hcd *fusbh200) +{ + /* another CPU may drop fusbh200->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (fusbh200->scanning) { + fusbh200->need_rescan = true; + return; + } + fusbh200->scanning = true; + + rescan: + fusbh200->need_rescan = false; + if (fusbh200->async_count) + scan_async(fusbh200); + if (fusbh200->intr_count > 0) + scan_intr(fusbh200); + if (fusbh200->isoc_count > 0) + scan_isoc(fusbh200); + if (fusbh200->need_rescan) + goto rescan; + fusbh200->scanning = false; + + /* the IO watchdog guards against hardware or driver bugs that + * misplace IRQs, and should let us run completely without IRQs. + * such lossage has been observed on both VT6202 and VT8235. + */ + turn_on_io_watchdog(fusbh200); +} + +/* + * Called when the fusbh200_hcd module is removed. + */ +static void fusbh200_stop (struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + + fusbh200_dbg (fusbh200, "stop\n"); + + /* no more interrupts ... */ + + spin_lock_irq(&fusbh200->lock); + fusbh200->enabled_hrtimer_events = 0; + spin_unlock_irq(&fusbh200->lock); + + fusbh200_quiesce(fusbh200); + fusbh200_silence_controller(fusbh200); + fusbh200_reset (fusbh200); + + hrtimer_cancel(&fusbh200->hrtimer); + remove_sysfs_files(fusbh200); + remove_debug_files (fusbh200); + + /* root hub is shut down separately (first, when possible) */ + spin_lock_irq (&fusbh200->lock); + end_free_itds(fusbh200); + spin_unlock_irq (&fusbh200->lock); + fusbh200_mem_cleanup (fusbh200); + +#ifdef FUSBH200_STATS + fusbh200_dbg(fusbh200, "irq normal %ld err %ld iaa %ld (lost %ld)\n", + fusbh200->stats.normal, fusbh200->stats.error, fusbh200->stats.iaa, + fusbh200->stats.lost_iaa); + fusbh200_dbg (fusbh200, "complete %ld unlink %ld\n", + fusbh200->stats.complete, fusbh200->stats.unlink); +#endif + + dbg_status (fusbh200, "fusbh200_stop completed", + fusbh200_readl(fusbh200, &fusbh200->regs->status)); +} + +/* one-time init, only for memory state */ +static int hcd_fusbh200_init(struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + u32 temp; + int retval; + u32 hcc_params; + struct fusbh200_qh_hw *hw; + + spin_lock_init(&fusbh200->lock); + + /* + * keep io watchdog by default, those good HCDs could turn off it later + */ + fusbh200->need_io_watchdog = 1; + + hrtimer_init(&fusbh200->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + fusbh200->hrtimer.function = fusbh200_hrtimer_func; + fusbh200->next_hrtimer_event = FUSBH200_HRTIMER_NO_EVENT; + + hcc_params = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + + /* + * by default set standard 80% (== 100 usec/uframe) max periodic + * bandwidth as required by USB 2.0 + */ + fusbh200->uframe_periodic_max = 100; + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + fusbh200->periodic_size = DEFAULT_I_TDPS; + INIT_LIST_HEAD(&fusbh200->intr_qh_list); + INIT_LIST_HEAD(&fusbh200->cached_itd_list); + + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + switch (FUSBH200_TUNE_FLS) { + case 0: fusbh200->periodic_size = 1024; break; + case 1: fusbh200->periodic_size = 512; break; + case 2: fusbh200->periodic_size = 256; break; + default: BUG(); + } + } + if ((retval = fusbh200_mem_init(fusbh200, GFP_KERNEL)) < 0) + return retval; + + /* controllers may cache some of the periodic schedule ... */ + fusbh200->i_thresh = 2; + + /* + * dedicate a qh for the async ring head, since we couldn't unlink + * a 'real' qh without stopping the async schedule [4.8]. use it + * as the 'reclamation list head' too. + * its dummy is used in hw_alt_next of many tds, to prevent the qh + * from automatically advancing to the next td after short reads. + */ + fusbh200->async->qh_next.qh = NULL; + hw = fusbh200->async->hw; + hw->hw_next = QH_NEXT(fusbh200, fusbh200->async->qh_dma); + hw->hw_info1 = cpu_to_hc32(fusbh200, QH_HEAD); + hw->hw_token = cpu_to_hc32(fusbh200, QTD_STS_HALT); + hw->hw_qtd_next = FUSBH200_LIST_END(fusbh200); + fusbh200->async->qh_state = QH_STATE_LINKED; + hw->hw_alt_next = QTD_NEXT(fusbh200, fusbh200->async->dummy->qtd_dma); + + /* clear interrupt enables, set irq latency */ + if (log2_irq_thresh < 0 || log2_irq_thresh > 6) + log2_irq_thresh = 0; + temp = 1 << (16 + log2_irq_thresh); + if (HCC_CANPARK(hcc_params)) { + /* HW default park == 3, on hardware that supports it (like + * NVidia and ALI silicon), maximizes throughput on the async + * schedule by avoiding QH fetches between transfers. + * + * With fast usb storage devices and NForce2, "park" seems to + * make problems: throughput reduction (!), data errors... + */ + if (park) { + park = min(park, (unsigned) 3); + temp |= CMD_PARK; + temp |= park << 8; + } + fusbh200_dbg(fusbh200, "park %d\n", park); + } + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + temp &= ~(3 << 2); + temp |= (FUSBH200_TUNE_FLS << 2); + } + fusbh200->command = temp; + + /* Accept arbitrarily long scatter-gather lists */ + if (!(hcd->driver->flags & HCD_LOCAL_MEM)) + hcd->self.sg_tablesize = ~0; + return 0; +} + +/* start HC running; it's halted, hcd_fusbh200_init() has been run (once) */ +static int fusbh200_run (struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + u32 temp; + u32 hcc_params; + + hcd->uses_new_polling = 1; + + /* EHCI spec section 4.1 */ + + fusbh200_writel(fusbh200, fusbh200->periodic_dma, &fusbh200->regs->frame_list); + fusbh200_writel(fusbh200, (u32)fusbh200->async->qh_dma, &fusbh200->regs->async_next); + + /* + * hcc_params controls whether fusbh200->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * pci_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like pci_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dma_supported(), so + * drivers can pass this info along ... like NETIF_F_HIGHDMA, + * Scsi_Host.highmem_io, and so forth. It's readonly to all + * host side drivers though. + */ + hcc_params = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + + // Philips, Intel, and maybe others need CMD_RUN before the + // root hub will detect new devices (why?); NEC doesn't + fusbh200->command &= ~(CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); + fusbh200->command |= CMD_RUN; + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + dbg_cmd (fusbh200, "init", fusbh200->command); + + /* + * Start, enabling full USB 2.0 functionality ... usb 1.1 devices + * are explicitly handed to companion controller(s), so no TT is + * involved with the root hub. (Except where one is integrated, + * and there's no companion controller unless maybe for USB OTG.) + * + * Turning on the CF flag will transfer ownership of all ports + * from the companions to the EHCI controller. If any of the + * companions are in the middle of a port reset at the time, it + * could cause trouble. Write-locking ehci_cf_port_reset_rwsem + * guarantees that no resets are in progress. After we set CF, + * a short delay lets the hardware catch up; new resets shouldn't + * be started before the port switching actions could complete. + */ + down_write(&ehci_cf_port_reset_rwsem); + fusbh200->rh_state = FUSBH200_RH_RUNNING; + fusbh200_readl(fusbh200, &fusbh200->regs->command); /* unblock posted writes */ + msleep(5); + up_write(&ehci_cf_port_reset_rwsem); + fusbh200->last_periodic_enable = ktime_get_real(); + + temp = HC_VERSION(fusbh200, fusbh200_readl(fusbh200, &fusbh200->caps->hc_capbase)); + fusbh200_info (fusbh200, + "USB %x.%x started, EHCI %x.%02x\n", + ((fusbh200->sbrn & 0xf0)>>4), (fusbh200->sbrn & 0x0f), + temp >> 8, temp & 0xff); + + fusbh200_writel(fusbh200, INTR_MASK, + &fusbh200->regs->intr_enable); /* Turn On Interrupts */ + + /* 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. + */ + create_debug_files(fusbh200); + create_sysfs_files(fusbh200); + + return 0; +} + +static int fusbh200_setup(struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + int retval; + + fusbh200->regs = (void __iomem *)fusbh200->caps + + HC_LENGTH(fusbh200, fusbh200_readl(fusbh200, &fusbh200->caps->hc_capbase)); + dbg_hcs_params(fusbh200, "reset"); + dbg_hcc_params(fusbh200, "reset"); + + /* cache this readonly data; minimize chip reads */ + fusbh200->hcs_params = fusbh200_readl(fusbh200, &fusbh200->caps->hcs_params); + + fusbh200->sbrn = HCD_USB2; + + /* data structure init */ + retval = hcd_fusbh200_init(hcd); + if (retval) + return retval; + + retval = fusbh200_halt(fusbh200); + if (retval) + return retval; + + fusbh200_reset(fusbh200); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static irqreturn_t fusbh200_irq (struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + u32 status, masked_status, pcd_status = 0, cmd; + int bh; + + spin_lock (&fusbh200->lock); + + status = fusbh200_readl(fusbh200, &fusbh200->regs->status); + + /* e.g. cardbus physical eject */ + if (status == ~(u32) 0) { + fusbh200_dbg (fusbh200, "device removed\n"); + goto dead; + } + + /* + * We don't use STS_FLR, but some controllers don't like it to + * remain on, so mask it out along with the other status bits. + */ + masked_status = status & (INTR_MASK | STS_FLR); + + /* Shared IRQ? */ + if (!masked_status || unlikely(fusbh200->rh_state == FUSBH200_RH_HALTED)) { + spin_unlock(&fusbh200->lock); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + fusbh200_writel(fusbh200, masked_status, &fusbh200->regs->status); + cmd = fusbh200_readl(fusbh200, &fusbh200->regs->command); + bh = 0; + +#ifdef VERBOSE_DEBUG + /* unrequested/ignored: Frame List Rollover */ + dbg_status (fusbh200, "irq", status); +#endif + + /* INT, ERR, and IAA interrupt rates can be throttled */ + + /* normal [4.15.1.2] or error [4.15.1.1] completion */ + if (likely ((status & (STS_INT|STS_ERR)) != 0)) { + if (likely ((status & STS_ERR) == 0)) + COUNT (fusbh200->stats.normal); + else + COUNT (fusbh200->stats.error); + bh = 1; + } + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + + /* Turn off the IAA watchdog */ + fusbh200->enabled_hrtimer_events &= ~BIT(FUSBH200_HRTIMER_IAA_WATCHDOG); + + /* + * Mild optimization: Allow another IAAD to reset the + * hrtimer, if one occurs before the next expiration. + * In theory we could always cancel the hrtimer, but + * tests show that about half the time it will be reset + * for some other event anyway. + */ + if (fusbh200->next_hrtimer_event == FUSBH200_HRTIMER_IAA_WATCHDOG) + ++fusbh200->next_hrtimer_event; + + /* guard against (alleged) silicon errata */ + if (cmd & CMD_IAAD) + fusbh200_dbg(fusbh200, "IAA with IAAD still set?\n"); + if (fusbh200->async_iaa) { + COUNT(fusbh200->stats.iaa); + end_unlink_async(fusbh200); + } else + fusbh200_dbg(fusbh200, "IAA with nothing unlinked?\n"); + } + + /* remote wakeup [4.3.1] */ + if (status & STS_PCD) { + int pstatus; + u32 __iomem *status_reg = &fusbh200->regs->port_status; + + /* kick root hub later */ + pcd_status = status; + + /* resume root hub? */ + if (fusbh200->rh_state == FUSBH200_RH_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + + pstatus = fusbh200_readl(fusbh200, status_reg); + + if (test_bit(0, &fusbh200->suspended_ports) && + ((pstatus & PORT_RESUME) || + !(pstatus & PORT_SUSPEND)) && + (pstatus & PORT_PE) && + fusbh200->reset_done[0] == 0) { + + /* start 20 msec resume signaling from this port, + * and make khubd collect PORT_STAT_C_SUSPEND to + * stop that signaling. Use 5 ms extra for safety, + * like usb_port_resume() does. + */ + fusbh200->reset_done[0] = jiffies + msecs_to_jiffies(25); + set_bit(0, &fusbh200->resuming_ports); + fusbh200_dbg (fusbh200, "port 1 remote wakeup\n"); + mod_timer(&hcd->rh_timer, fusbh200->reset_done[0]); + } + } + + /* PCI errors [4.15.2.4] */ + if (unlikely ((status & STS_FATAL) != 0)) { + fusbh200_err(fusbh200, "fatal error\n"); + dbg_cmd(fusbh200, "fatal", cmd); + dbg_status(fusbh200, "fatal", status); +dead: + usb_hc_died(hcd); + + /* Don't let the controller do anything more */ + fusbh200->shutdown = true; + fusbh200->rh_state = FUSBH200_RH_STOPPING; + fusbh200->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE); + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + fusbh200_writel(fusbh200, 0, &fusbh200->regs->intr_enable); + fusbh200_handle_controller_death(fusbh200); + + /* Handle completions when the controller stops */ + bh = 0; + } + + if (bh) + fusbh200_work (fusbh200); + spin_unlock (&fusbh200->lock); + if (pcd_status) + usb_hcd_poll_rh_status(hcd); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* + * non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + * + * urb + dev is in hcd.self.controller.urb_list + * we're queueing TDs onto software and hardware lists + * + * hcd-specific init for hcpriv hasn't been done yet + * + * NOTE: control, bulk, and interrupt share the same code to append TDs + * to a (possibly active) QH, and the same QH scanning code. + */ +static int fusbh200_urb_enqueue ( + struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags +) { + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + struct list_head qtd_list; + + INIT_LIST_HEAD (&qtd_list); + + switch (usb_pipetype (urb->pipe)) { + case PIPE_CONTROL: + /* qh_completions() code doesn't handle all the fault cases + * in multi-TD control transfers. Even 1KB is rare anyway. + */ + if (urb->transfer_buffer_length > (16 * 1024)) + return -EMSGSIZE; + /* FALLTHROUGH */ + /* case PIPE_BULK: */ + default: + if (!qh_urb_transaction (fusbh200, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return submit_async(fusbh200, urb, &qtd_list, mem_flags); + + case PIPE_INTERRUPT: + if (!qh_urb_transaction (fusbh200, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit(fusbh200, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: + return itd_submit (fusbh200, urb, mem_flags); + } +} + +/* remove from hardware lists + * completions normally happen asynchronously + */ + +static int fusbh200_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + struct fusbh200_qh *qh; + unsigned long flags; + int rc; + + spin_lock_irqsave (&fusbh200->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + switch (usb_pipetype (urb->pipe)) { + // case PIPE_CONTROL: + // case PIPE_BULK: + default: + qh = (struct fusbh200_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + start_unlink_async(fusbh200, qh); + break; + case QH_STATE_UNLINK: + case QH_STATE_UNLINK_WAIT: + /* already started */ + break; + case QH_STATE_IDLE: + /* QH might be waiting for a Clear-TT-Buffer */ + qh_completions(fusbh200, qh); + break; + } + break; + + case PIPE_INTERRUPT: + qh = (struct fusbh200_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + start_unlink_intr(fusbh200, qh); + break; + case QH_STATE_IDLE: + qh_completions (fusbh200, qh); + break; + default: + fusbh200_dbg (fusbh200, "bogus qh %p state %d\n", + qh, qh->qh_state); + goto done; + } + break; + + case PIPE_ISOCHRONOUS: + // itd... + + // wait till next completion, do it then. + // completion irqs can wait up to 1024 msec, + break; + } +done: + spin_unlock_irqrestore (&fusbh200->lock, flags); + return rc; +} + +/*-------------------------------------------------------------------------*/ + +// bulk qh holds the data toggle + +static void +fusbh200_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + unsigned long flags; + struct fusbh200_qh *qh, *tmp; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +rescan: + spin_lock_irqsave (&fusbh200->lock, flags); + qh = ep->hcpriv; + if (!qh) + goto done; + + /* endpoints can be iso streams. for now, we don't + * accelerate iso completions ... so spin a while. + */ + if (qh->hw == NULL) { + struct fusbh200_iso_stream *stream = ep->hcpriv; + + if (!list_empty(&stream->td_list)) + goto idle_timeout; + + /* BUG_ON(!list_empty(&stream->free_list)); */ + kfree(stream); + goto done; + } + + if (fusbh200->rh_state < FUSBH200_RH_RUNNING) + qh->qh_state = QH_STATE_IDLE; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + for (tmp = fusbh200->async->qh_next.qh; + tmp && tmp != qh; + tmp = tmp->qh_next.qh) + continue; + /* periodic qh self-unlinks on empty, and a COMPLETING qh + * may already be unlinked. + */ + if (tmp) + start_unlink_async(fusbh200, qh); + /* FALL THROUGH */ + case QH_STATE_UNLINK: /* wait for hw to finish? */ + case QH_STATE_UNLINK_WAIT: +idle_timeout: + spin_unlock_irqrestore (&fusbh200->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case QH_STATE_IDLE: /* fully unlinked */ + if (qh->clearing_tt) + goto idle_timeout; + if (list_empty (&qh->qtd_list)) { + qh_destroy(fusbh200, qh); + break; + } + /* else FALL THROUGH */ + default: + /* caller was supposed to have unlinked any requests; + * that's not our job. just leak this memory. + */ + fusbh200_err (fusbh200, "qh %p (#%02x) state %d%s\n", + qh, ep->desc.bEndpointAddress, qh->qh_state, + list_empty (&qh->qtd_list) ? "" : "(has tds)"); + break; + } + done: + ep->hcpriv = NULL; + spin_unlock_irqrestore (&fusbh200->lock, flags); +} + +static void +fusbh200_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + struct fusbh200_qh *qh; + int eptype = usb_endpoint_type(&ep->desc); + int epnum = usb_endpoint_num(&ep->desc); + int is_out = usb_endpoint_dir_out(&ep->desc); + unsigned long flags; + + if (eptype != USB_ENDPOINT_XFER_BULK && eptype != USB_ENDPOINT_XFER_INT) + return; + + spin_lock_irqsave(&fusbh200->lock, flags); + qh = ep->hcpriv; + + /* For Bulk and Interrupt endpoints we maintain the toggle state + * in the hardware; the toggle bits in udev aren't used at all. + * When an endpoint is reset by usb_clear_halt() we must reset + * the toggle bit in the QH. + */ + if (qh) { + usb_settoggle(qh->dev, epnum, is_out, 0); + if (!list_empty(&qh->qtd_list)) { + WARN_ONCE(1, "clear_halt for a busy endpoint\n"); + } else if (qh->qh_state == QH_STATE_LINKED || + qh->qh_state == QH_STATE_COMPLETING) { + + /* The toggle value in the QH can't be updated + * while the QH is active. Unlink it now; + * re-linking will call qh_refresh(). + */ + if (eptype == USB_ENDPOINT_XFER_BULK) + start_unlink_async(fusbh200, qh); + else + start_unlink_intr(fusbh200, qh); + } + } + spin_unlock_irqrestore(&fusbh200->lock, flags); +} + +static int fusbh200_get_frame (struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + return (fusbh200_read_frame_index(fusbh200) >> 3) % fusbh200->periodic_size; +} + +/*-------------------------------------------------------------------------*/ + +/* + * The EHCI in ChipIdea HDRC cannot be a separate module or device, + * because its registers (and irq) are shared between host/gadget/otg + * functions and in order to facilitate role switching we cannot + * give the fusbh200 driver exclusive access to those. + */ +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_LICENSE ("GPL"); + +static const struct hc_driver fusbh200_fusbh200_hc_driver = { + .description = hcd_name, + .product_desc = "Faraday USB2.0 Host Controller", + .hcd_priv_size = sizeof(struct fusbh200_hcd), + + /* + * generic hardware linkage + */ + .irq = fusbh200_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = hcd_fusbh200_init, + .start = fusbh200_run, + .stop = fusbh200_stop, + .shutdown = fusbh200_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = fusbh200_urb_enqueue, + .urb_dequeue = fusbh200_urb_dequeue, + .endpoint_disable = fusbh200_endpoint_disable, + .endpoint_reset = fusbh200_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = fusbh200_get_frame, + + /* + * root hub support + */ + .hub_status_data = fusbh200_hub_status_data, + .hub_control = fusbh200_hub_control, + .bus_suspend = fusbh200_bus_suspend, + .bus_resume = fusbh200_bus_resume, + + .relinquish_port = fusbh200_relinquish_port, + .port_handed_over = fusbh200_port_handed_over, + + .clear_tt_buffer_complete = fusbh200_clear_tt_buffer_complete, +}; + +static void fusbh200_init(struct fusbh200_hcd *fusbh200) +{ + u32 reg; + + reg = fusbh200_readl(fusbh200, &fusbh200->regs->bmcsr); + reg |= BMCSR_INT_POLARITY; + reg &= ~BMCSR_VBUS_OFF; + fusbh200_writel(fusbh200, reg, &fusbh200->regs->bmcsr); + + reg = fusbh200_readl(fusbh200, &fusbh200->regs->bmier); + fusbh200_writel(fusbh200, reg | BMIER_OVC_EN | BMIER_VBUS_ERR_EN, + &fusbh200->regs->bmier); +} + +/** + * fusbh200_hcd_probe - initialize faraday FUSBH200 HCDs + * + * Allocates basic resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + */ +static int fusbh200_hcd_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd; + struct resource *res; + int irq; + int retval = -ENODEV; + struct fusbh200_hcd *fusbh200; + + if (usb_disabled()) + return -ENODEV; + + pdev->dev.power.power_state = PMSG_ON; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(dev)); + return -ENODEV; + } + + irq = res->start; + + hcd = usb_create_hcd(&fusbh200_fusbh200_hc_driver, dev, + dev_name(dev)); + if (!hcd) { + dev_err(dev, "failed to create hcd with err %d\n", retval); + retval = -ENOMEM; + goto fail_create_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(dev)); + retval = -ENODEV; + goto fail_request_resource; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->has_tt = 1; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + fusbh200_fusbh200_hc_driver.description)) { + dev_dbg(dev, "controller already in use\n"); + retval = -EBUSY; + goto fail_request_resource; + } + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) { + dev_err(dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(dev)); + retval = -ENODEV; + goto fail_request_resource; + } + + hcd->regs = ioremap_nocache(res->start, resource_size(res)); + if (hcd->regs == NULL) { + dev_dbg(dev, "error mapping memory\n"); + retval = -EFAULT; + goto fail_ioremap; + } + + fusbh200 = hcd_to_fusbh200(hcd); + + fusbh200->caps = hcd->regs; + + retval = fusbh200_setup(hcd); + if (retval) + goto fail_add_hcd; + + fusbh200_init(fusbh200); + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) { + dev_err(dev, "failed to add hcd with err %d\n", retval); + goto fail_add_hcd; + } + + return retval; + +fail_add_hcd: + iounmap(hcd->regs); +fail_ioremap: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +fail_request_resource: + usb_put_hcd(hcd); +fail_create_hcd: + dev_err(dev, "init %s fail, %d\n", dev_name(dev), retval); + return retval; +} + +/** + * fusbh200_hcd_remove - shutdown processing for EHCI HCDs + * @dev: USB Host Controller being removed + * + * Reverses the effect of fotg2xx_usb_hcd_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + */ +static int fusbh200_hcd_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = dev_get_drvdata(dev); + + if (!hcd) + return 0; + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + return 0; +} + +static struct platform_driver fusbh200_hcd_fusbh200_driver = { + .driver = { + .name = "fusbh200", + }, + .probe = fusbh200_hcd_probe, + .remove = fusbh200_hcd_remove, +}; + +static int __init fusbh200_hcd_init(void) +{ + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + printk(KERN_INFO "%s: " DRIVER_DESC "\n", hcd_name); + set_bit(USB_EHCI_LOADED, &usb_hcds_loaded); + if (test_bit(USB_UHCI_LOADED, &usb_hcds_loaded) || + test_bit(USB_OHCI_LOADED, &usb_hcds_loaded)) + printk(KERN_WARNING "Warning! fusbh200_hcd should always be loaded" + " before uhci_hcd and ohci_hcd, not after\n"); + + pr_debug("%s: block sizes: qh %Zd qtd %Zd itd %Zd\n", + hcd_name, + sizeof(struct fusbh200_qh), sizeof(struct fusbh200_qtd), + sizeof(struct fusbh200_itd)); + +#ifdef DEBUG + fusbh200_debug_root = debugfs_create_dir("fusbh200", usb_debug_root); + if (!fusbh200_debug_root) { + retval = -ENOENT; + goto err_debug; + } +#endif + + retval = platform_driver_register(&fusbh200_hcd_fusbh200_driver); + if (retval < 0) + goto clean; + return retval; + + platform_driver_unregister(&fusbh200_hcd_fusbh200_driver); +clean: +#ifdef DEBUG + debugfs_remove(fusbh200_debug_root); + fusbh200_debug_root = NULL; +err_debug: +#endif + clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); + return retval; +} +module_init(fusbh200_hcd_init); + +static void __exit fusbh200_hcd_cleanup(void) +{ + platform_driver_unregister(&fusbh200_hcd_fusbh200_driver); +#ifdef DEBUG + debugfs_remove(fusbh200_debug_root); +#endif + clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); +} +module_exit(fusbh200_hcd_cleanup); diff --git a/drivers/usb/host/fusbh200.h b/drivers/usb/host/fusbh200.h new file mode 100644 index 0000000..797c9e8 --- /dev/null +++ b/drivers/usb/host/fusbh200.h @@ -0,0 +1,743 @@ +#ifndef __LINUX_FUSBH200_H +#define __LINUX_FUSBH200_H + +/* definitions used for the EHCI driver */ + +/* + * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to + * __leXX (normally) or __beXX (given FUSBH200_BIG_ENDIAN_DESC), depending on + * the host controller implementation. + * + * To facilitate the strongest possible byte-order checking from "sparse" + * and so on, we use __leXX unless that's not practical. + */ +#define __hc32 __le32 +#define __hc16 __le16 + +/* statistics can be kept for tuning/monitoring */ +struct fusbh200_stats { + /* irq usage */ + unsigned long normal; + unsigned long error; + unsigned long iaa; + unsigned long lost_iaa; + + /* termination of urbs from core */ + unsigned long complete; + unsigned long unlink; +}; + +/* fusbh200_hcd->lock guards shared data against other CPUs: + * fusbh200_hcd: async, unlink, periodic (and shadow), ... + * usb_host_endpoint: hcpriv + * fusbh200_qh: qh_next, qtd_list + * fusbh200_qtd: qtd_list + * + * Also, hold this lock when talking to HC registers or + * when updating hw_* fields in shared qh/qtd/... structures. + */ + +#define FUSBH200_MAX_ROOT_PORTS 1 /* see HCS_N_PORTS */ + +/* + * fusbh200_rh_state values of FUSBH200_RH_RUNNING or above mean that the + * controller may be doing DMA. Lower values mean there's no DMA. + */ +enum fusbh200_rh_state { + FUSBH200_RH_HALTED, + FUSBH200_RH_SUSPENDED, + FUSBH200_RH_RUNNING, + FUSBH200_RH_STOPPING +}; + +/* + * Timer events, ordered by increasing delay length. + * Always update event_delays_ns[] and event_handlers[] (defined in + * ehci-timer.c) in parallel with this list. + */ +enum fusbh200_hrtimer_event { + FUSBH200_HRTIMER_POLL_ASS, /* Poll for async schedule off */ + FUSBH200_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ + FUSBH200_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ + FUSBH200_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ + FUSBH200_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ + FUSBH200_HRTIMER_ASYNC_UNLINKS, /* Unlink empty async QHs */ + FUSBH200_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */ + FUSBH200_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ + FUSBH200_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ + FUSBH200_HRTIMER_IO_WATCHDOG, /* Check for missing IRQs */ + FUSBH200_HRTIMER_NUM_EVENTS /* Must come last */ +}; +#define FUSBH200_HRTIMER_NO_EVENT 99 + +struct fusbh200_hcd { /* one per controller */ + /* timing support */ + enum fusbh200_hrtimer_event next_hrtimer_event; + unsigned enabled_hrtimer_events; + ktime_t hr_timeouts[FUSBH200_HRTIMER_NUM_EVENTS]; + struct hrtimer hrtimer; + + int PSS_poll_count; + int ASS_poll_count; + int died_poll_count; + + /* glue to PCI and HCD framework */ + struct fusbh200_caps __iomem *caps; + struct fusbh200_regs __iomem *regs; + struct fusbh200_dbg_port __iomem *debug; + + __u32 hcs_params; /* cached register copy */ + spinlock_t lock; + enum fusbh200_rh_state rh_state; + + /* general schedule support */ + bool scanning:1; + bool need_rescan:1; + bool intr_unlinking:1; + bool async_unlinking:1; + bool shutdown:1; + struct fusbh200_qh *qh_scan_next; + + /* async schedule support */ + struct fusbh200_qh *async; + struct fusbh200_qh *dummy; /* For AMD quirk use */ + struct fusbh200_qh *async_unlink; + struct fusbh200_qh *async_unlink_last; + struct fusbh200_qh *async_iaa; + unsigned async_unlink_cycle; + unsigned async_count; /* async activity count */ + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 /* some HCs can do less */ + unsigned periodic_size; + __hc32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + struct list_head intr_qh_list; + unsigned i_thresh; /* uframes HC might cache */ + + union fusbh200_shadow *pshadow; /* mirror hw periodic table */ + struct fusbh200_qh *intr_unlink; + struct fusbh200_qh *intr_unlink_last; + unsigned intr_unlink_cycle; + unsigned now_frame; /* frame from HC hardware */ + unsigned next_frame; /* scan periodic, start here */ + unsigned intr_count; /* intr activity count */ + unsigned isoc_count; /* isoc activity count */ + unsigned periodic_count; /* periodic activity count */ + unsigned uframe_periodic_max; /* max periodic time per uframe */ + + + /* list of itds completed while now_frame was still active */ + struct list_head cached_itd_list; + struct fusbh200_itd *last_itd_to_free; + + /* per root hub port */ + unsigned long reset_done [FUSBH200_MAX_ROOT_PORTS]; + + /* bit vectors (one bit per port) */ + unsigned long bus_suspended; /* which ports were + already suspended at the start of a bus suspend */ + unsigned long companion_ports; /* which ports are + dedicated to the companion controller */ + unsigned long owned_ports; /* which ports are + owned by the companion during a bus suspend */ + unsigned long port_c_suspend; /* which ports have + the change-suspend feature turned on */ + unsigned long suspended_ports; /* which ports are + suspended */ + unsigned long resuming_ports; /* which ports have + started to resume */ + + /* per-HC memory pools (could be per-bus, but ...) */ + struct dma_pool *qh_pool; /* qh per active urb */ + struct dma_pool *qtd_pool; /* one or more per qh */ + struct dma_pool *itd_pool; /* itd per iso urb */ + + unsigned random_frame; + unsigned long next_statechange; + ktime_t last_periodic_enable; + u32 command; + + /* SILICON QUIRKS */ + unsigned need_io_watchdog:1; + unsigned fs_i_thresh:1; /* Intel iso scheduling */ + + u8 sbrn; /* packed release number */ + + /* irq statistics */ +#ifdef FUSBH200_STATS + struct fusbh200_stats stats; +# define COUNT(x) do { (x)++; } while (0) +#else +# define COUNT(x) do {} while (0) +#endif + + /* debug files */ +#ifdef DEBUG + struct dentry *debug_dir; +#endif +}; + +/* convert between an HCD pointer and the corresponding FUSBH200_HCD */ +static inline struct fusbh200_hcd *hcd_to_fusbh200 (struct usb_hcd *hcd) +{ + return (struct fusbh200_hcd *) (hcd->hcd_priv); +} +static inline struct usb_hcd *fusbh200_to_hcd (struct fusbh200_hcd *fusbh200) +{ + return container_of ((void *) fusbh200, struct usb_hcd, hcd_priv); +} + +/*-------------------------------------------------------------------------*/ + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct fusbh200_caps { + /* these fields are specified as 8 and 16 bit registers, + * but some hosts can't perform 8 or 16 bit PCI accesses. + * some hosts treat caplength and hciversion as parts of a 32-bit + * register, others treat them as two separate registers, this + * affects the memory map for big endian controllers. + */ + u32 hc_capbase; +#define HC_LENGTH(fusbh200, p) (0x00ff&((p) >> /* bits 7:0 / offset 00h */ \ + (fusbh200_big_endian_capbase(fusbh200) ? 24 : 0))) +#define HC_VERSION(fusbh200, p) (0xffff&((p) >> /* bits 31:16 / offset 02h */ \ + (fusbh200_big_endian_capbase(fusbh200) ? 0 : 16))) + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ +#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ + u8 portroute[8]; /* nibbles for routing - offset 0xC */ +}; + + +/* Section 2.3 Host Controller Operational Registers */ +struct fusbh200_regs { + + /* USBCMD: offset 0x00 */ + u32 command; + +/* EHCI 1.1 addendum */ +/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ +#define CMD_PARK (1<<11) /* enable "park" on async qh */ +#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ +#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ +#define CMD_ASE (1<<5) /* async schedule enable */ +#define CMD_PSE (1<<4) /* periodic schedule enable */ +/* 3:2 is periodic frame list size */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ + + /* USBSTS: offset 0x04 */ + u32 status; +#define STS_ASS (1<<15) /* Async Schedule Status */ +#define STS_PSS (1<<14) /* Periodic Schedule Status */ +#define STS_RECL (1<<13) /* Reclamation */ +#define STS_HALT (1<<12) /* Not running (any reason) */ +/* some bits reserved */ + /* these STS_* flags are also intr_enable bits (USBINTR) */ +#define STS_IAA (1<<5) /* Interrupted on async advance */ +#define STS_FATAL (1<<4) /* such as some PCI access errors */ +#define STS_FLR (1<<3) /* frame list rolled over */ +#define STS_PCD (1<<2) /* port change detect */ +#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ +#define STS_INT (1<<0) /* "normal" completion (short, ...) */ + + /* USBINTR: offset 0x08 */ + u32 intr_enable; + + /* FRINDEX: offset 0x0C */ + u32 frame_index; /* current microframe number */ + /* CTRLDSSEGMENT: offset 0x10 */ + u32 segment; /* address bits 63:32 if needed */ + /* PERIODICLISTBASE: offset 0x14 */ + u32 frame_list; /* points to periodic list */ + /* ASYNCLISTADDR: offset 0x18 */ + u32 async_next; /* address of next async queue head */ + + u32 reserved1; + /* PORTSC: offset 0x20 */ + u32 port_status; +/* 31:23 reserved */ +#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_PEC (1<<3) /* port enable change */ +#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 | PORT_PEC) + + u32 reserved2[3]; + + /* BMCSR: offset 0x30 */ + u32 bmcsr; /* Bus Moniter Control/Status Register */ +#define BMCSR_HOST_SPD_TYP (3<<9) +#define BMCSR_VBUS_OFF (1<<4) +#define BMCSR_INT_POLARITY (1<<3) + + /* BMISR: offset 0x34 */ + u32 bmisr; /* Bus Moniter Interrupt Status Register*/ +#define BMISR_OVC (1<<1) + + /* BMIER: offset 0x38 */ + u32 bmier; /* Bus Moniter Interrupt Enable Register */ +#define BMIER_OVC_EN (1<<1) +#define BMIER_VBUS_ERR_EN (1<<0) +}; + +/* Appendix C, Debug port ... intended for use with special "debug devices" + * that can help if there's no serial console. (nonstandard enumeration.) + */ +struct fusbh200_dbg_port { + u32 control; +#define DBGP_OWNER (1<<30) +#define DBGP_ENABLED (1<<28) +#define DBGP_DONE (1<<16) +#define DBGP_INUSE (1<<10) +#define DBGP_ERRCODE(x) (((x)>>7)&0x07) +# define DBGP_ERR_BAD 1 +# define DBGP_ERR_SIGNAL 2 +#define DBGP_ERROR (1<<6) +#define DBGP_GO (1<<5) +#define DBGP_OUT (1<<4) +#define DBGP_LEN(x) (((x)>>0)&0x0f) + u32 pids; +#define DBGP_PID_GET(x) (((x)>>16)&0xff) +#define DBGP_PID_SET(data, tok) (((data)<<8)|(tok)) + u32 data03; + u32 data47; + u32 address; +#define DBGP_EPADDR(dev, ep) (((dev)<<8)|(ep)) +}; + +#ifdef CONFIG_EARLY_PRINTK_DBGP +#include <linux/init.h> +extern int __init early_dbgp_init(char *s); +extern struct console early_dbgp_console; +#endif /* CONFIG_EARLY_PRINTK_DBGP */ + +struct usb_hcd; + +static inline int xen_dbgp_reset_prep(struct usb_hcd *hcd) +{ + return 1; /* Shouldn't this be 0? */ +} + +static inline int xen_dbgp_external_startup(struct usb_hcd *hcd) +{ + return -1; +} + +#ifdef CONFIG_EARLY_PRINTK_DBGP +/* Call backs from fusbh200 host driver to fusbh200 debug driver */ +extern int dbgp_external_startup(struct usb_hcd *); +extern int dbgp_reset_prep(struct usb_hcd *hcd); +#else +static inline int dbgp_reset_prep(struct usb_hcd *hcd) +{ + return xen_dbgp_reset_prep(hcd); +} +static inline int dbgp_external_startup(struct usb_hcd *hcd) +{ + return xen_dbgp_external_startup(hcd); +} +#endif + +/*-------------------------------------------------------------------------*/ + +#define QTD_NEXT(fusbh200, dma) cpu_to_hc32(fusbh200, (u32)dma) + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct fusbh200_qtd { + /* first part defined by EHCI spec */ + __hc32 hw_next; /* see EHCI 3.5.1 */ + __hc32 hw_alt_next; /* see EHCI 3.5.2 */ + __hc32 hw_token; /* see EHCI 3.5.3 */ +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + +#define ACTIVE_BIT(fusbh200) cpu_to_hc32(fusbh200, QTD_STS_ACTIVE) +#define HALT_BIT(fusbh200) cpu_to_hc32(fusbh200, QTD_STS_HALT) +#define STATUS_BIT(fusbh200) cpu_to_hc32(fusbh200, QTD_STS_STS) + + __hc32 hw_buf [5]; /* see EHCI 3.5.4 */ + __hc32 hw_buf_hi [5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + struct urb *urb; /* qtd's urb */ + size_t length; /* length of buffer */ +} __attribute__ ((aligned (32))); + +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK(fusbh200) cpu_to_hc32 (fusbh200, ~0x1f) + +#define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1) + +/*-------------------------------------------------------------------------*/ + +/* type tag from {qh,itd,fstn}->hw_next */ +#define Q_NEXT_TYPE(fusbh200,dma) ((dma) & cpu_to_hc32(fusbh200, 3 << 1)) + +/* + * Now the following defines are not converted using the + * cpu_to_le32() macro anymore, since we have to support + * "dynamic" switching between be and le support, so that the driver + * can be used on one system with SoC EHCI controller using big-endian + * descriptors as well as a normal little-endian PCI EHCI controller. + */ +/* values for that type tag */ +#define Q_TYPE_ITD (0 << 1) +#define Q_TYPE_QH (1 << 1) +#define Q_TYPE_SITD (2 << 1) +#define Q_TYPE_FSTN (3 << 1) + +/* next async queue entry, or pointer to interrupt/periodic QH */ +#define QH_NEXT(fusbh200,dma) (cpu_to_hc32(fusbh200, (((u32)dma)&~0x01f)|Q_TYPE_QH)) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define FUSBH200_LIST_END(fusbh200) cpu_to_hc32(fusbh200, 1) /* "null pointer" to hw */ + +/* + * Entries in periodic shadow table are pointers to one of four kinds + * of data structure. That's dictated by the hardware; a type tag is + * encoded in the low bits of the hardware's periodic schedule. Use + * Q_NEXT_TYPE to get the tag. + * + * For entries in the async schedule, the type tag always says "qh". + */ +union fusbh200_shadow { + struct fusbh200_qh *qh; /* Q_TYPE_QH */ + struct fusbh200_itd *itd; /* Q_TYPE_ITD */ + struct fusbh200_fstn *fstn; /* Q_TYPE_FSTN */ + __hc32 *hw_next; /* (all types) */ + void *ptr; +}; + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + +/* first part defined by EHCI spec */ +struct fusbh200_qh_hw { + __hc32 hw_next; /* see EHCI 3.6.1 */ + __hc32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_CONTROL_EP (1 << 27) /* FS/LS control endpoint */ +#define QH_HEAD (1 << 15) /* Head of async reclamation list */ +#define QH_TOGGLE_CTL (1 << 14) /* Data toggle control */ +#define QH_HIGH_SPEED (2 << 12) /* Endpoint speed */ +#define QH_LOW_SPEED (1 << 12) +#define QH_FULL_SPEED (0 << 12) +#define QH_INACTIVATE (1 << 7) /* Inactivate on next transaction */ + __hc32 hw_info2; /* see EHCI 3.6.2 */ +#define QH_SMASK 0x000000ff +#define QH_CMASK 0x0000ff00 +#define QH_HUBADDR 0x007f0000 +#define QH_HUBPORT 0x3f800000 +#define QH_MULT 0xc0000000 + __hc32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct fusbh200_qtd) */ + __hc32 hw_qtd_next; + __hc32 hw_alt_next; + __hc32 hw_token; + __hc32 hw_buf [5]; + __hc32 hw_buf_hi [5]; +} __attribute__ ((aligned(32))); + +struct fusbh200_qh { + struct fusbh200_qh_hw *hw; /* Must come first */ + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union fusbh200_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + struct list_head intr_node; /* list of intr QHs */ + struct fusbh200_qtd *dummy; + struct fusbh200_qh *unlink_next; /* next on unlink list */ + + unsigned unlink_cycle; + + u8 needs_rescan; /* Dequeue during giveback */ + u8 qh_state; +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ +#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on unlink q */ +#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ + + u8 xacterrs; /* XactErr retry counter */ +#define QH_XACTERR_MAX 32 /* XactErr retry limit */ + + /* periodic schedule info */ + u8 usecs; /* intr bandwidth */ + u8 gap_uf; /* uframes split/csplit gap */ + u8 c_usecs; /* ... split completion bw */ + u16 tt_usecs; /* tt downstream bandwidth */ + unsigned short period; /* polling interval */ + unsigned short start; /* where polling starts */ +#define NO_FRAME ((unsigned short)~0) /* pick new start */ + + struct usb_device *dev; /* access to TT */ + unsigned is_out:1; /* bulk or intr OUT */ + unsigned clearing_tt:1; /* Clear-TT-Buf in progress */ +}; + +/*-------------------------------------------------------------------------*/ + +/* description of one iso transaction (up to 3 KB data if highspeed) */ +struct fusbh200_iso_packet { + /* These will be copied to iTD when scheduling */ + u64 bufp; /* itd->hw_bufp{,_hi}[pg] |= */ + __hc32 transaction; /* itd->hw_transaction[i] |= */ + u8 cross; /* buf crosses pages */ + /* for full speed OUT splits */ + u32 buf1; +}; + +/* temporary schedule data for packets from iso urbs (both speeds) + * each packet is one logical usb transaction to the device (not TT), + * beginning at stream->next_uframe + */ +struct fusbh200_iso_sched { + struct list_head td_list; + unsigned span; + struct fusbh200_iso_packet packet [0]; +}; + +/* + * fusbh200_iso_stream - groups all (s)itds for this endpoint. + * acts like a qh would, if EHCI had them for ISO. + */ +struct fusbh200_iso_stream { + /* first field matches fusbh200_hq, but is NULL */ + struct fusbh200_qh_hw *hw; + + u8 bEndpointAddress; + u8 highspeed; + struct list_head td_list; /* queued itds */ + struct list_head free_list; /* list of unused itds */ + struct usb_device *udev; + struct usb_host_endpoint *ep; + + /* output of (re)scheduling */ + int next_uframe; + __hc32 splits; + + /* the rest is derived from the endpoint descriptor, + * trusting urb->interval == f(epdesc->bInterval) and + * including the extra info for hw_bufp[0..2] + */ + u8 usecs, c_usecs; + u16 interval; + u16 tt_usecs; + u16 maxp; + u16 raw_mask; + unsigned bandwidth; + + /* This is used to initialize iTD's hw_bufp fields */ + __hc32 buf0; + __hc32 buf1; + __hc32 buf2; + + /* this is used to initialize sITD's tt info */ + __hc32 address; +}; + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.3 + * Fig 3-4 "Isochronous Transaction Descriptor (iTD)" + * + * Schedule records for high speed iso xfers + */ +struct fusbh200_itd { + /* first part defined by EHCI spec */ + __hc32 hw_next; /* see EHCI 3.3.1 */ + __hc32 hw_transaction [8]; /* see EHCI 3.3.2 */ +#define FUSBH200_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ +#define FUSBH200_ISOC_BUF_ERR (1<<30) /* Data buffer error */ +#define FUSBH200_ISOC_BABBLE (1<<29) /* babble detected */ +#define FUSBH200_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ +#define FUSBH200_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) +#define FUSBH200_ITD_IOC (1 << 15) /* interrupt on complete */ + +#define ITD_ACTIVE(fusbh200) cpu_to_hc32(fusbh200, FUSBH200_ISOC_ACTIVE) + + __hc32 hw_bufp [7]; /* see EHCI 3.3.3 */ + __hc32 hw_bufp_hi [7]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t itd_dma; /* for this itd */ + union fusbh200_shadow itd_next; /* ptr to periodic q entry */ + + struct urb *urb; + struct fusbh200_iso_stream *stream; /* endpoint's queue */ + struct list_head itd_list; /* list of stream's itds */ + + /* any/all hw_transactions here may be used by that urb */ + unsigned frame; /* where scheduled */ + unsigned pg; + unsigned index[8]; /* in urb->iso_frame_desc */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.96 Section 3.7 + * Periodic Frame Span Traversal Node (FSTN) + * + * Manages split interrupt transactions (using TT) that span frame boundaries + * into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN + * makes the HC jump (back) to a QH to scan for fs/ls QH completions until + * it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work. + */ +struct fusbh200_fstn { + __hc32 hw_next; /* any periodic q entry */ + __hc32 hw_prev; /* qh or FUSBH200_LIST_END */ + + /* the rest is HCD-private */ + dma_addr_t fstn_dma; + union fusbh200_shadow fstn_next; /* ptr to periodic q entry */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* Prepare the PORTSC wakeup flags during controller suspend/resume */ + +#define fusbh200_prepare_ports_for_controller_suspend(fusbh200, do_wakeup) \ + fusbh200_adjust_port_wakeup_flags(fusbh200, true, do_wakeup); + +#define fusbh200_prepare_ports_for_controller_resume(fusbh200) \ + fusbh200_adjust_port_wakeup_flags(fusbh200, false, false); + +/*-------------------------------------------------------------------------*/ + +/* + * Some EHCI controllers have a Transaction Translator built into the + * root hub. This is a non-standard feature. Each controller will need + * to add code to the following inline functions, and call them as + * needed (mostly in root hub code). + */ + +static inline unsigned int +fusbh200_get_speed(struct fusbh200_hcd *fusbh200, unsigned int portsc) +{ + return (readl(&fusbh200->regs->bmcsr) + & BMCSR_HOST_SPD_TYP) >> 9; +} + +/* Returns the speed of a device attached to a port on the root hub. */ +static inline unsigned int +fusbh200_port_speed(struct fusbh200_hcd *fusbh200, unsigned int portsc) +{ + switch (fusbh200_get_speed(fusbh200, portsc)) { + case 0: + return 0; + case 1: + return USB_PORT_STAT_LOW_SPEED; + case 2: + default: + return USB_PORT_STAT_HIGH_SPEED; + } +} + +/*-------------------------------------------------------------------------*/ + +#define fusbh200_has_fsl_portno_bug(e) (0) + +/* + * While most USB host controllers implement their registers in + * little-endian format, a minority (celleb companion chip) implement + * them in big endian format. + * + * This attempts to support either format at compile time without a + * runtime penalty, or both formats with the additional overhead + * of checking a flag bit. + * + */ + +#define fusbh200_big_endian_mmio(e) 0 +#define fusbh200_big_endian_capbase(e) 0 + +static inline unsigned int fusbh200_readl(const struct fusbh200_hcd *fusbh200, + __u32 __iomem * regs) +{ + return readl(regs); +} + +static inline void fusbh200_writel(const struct fusbh200_hcd *fusbh200, + const unsigned int val, __u32 __iomem *regs) +{ + writel(val, regs); +} + +/* cpu to fusbh200 */ +static inline __hc32 cpu_to_hc32 (const struct fusbh200_hcd *fusbh200, const u32 x) +{ + return cpu_to_le32(x); +} + +/* fusbh200 to cpu */ +static inline u32 hc32_to_cpu (const struct fusbh200_hcd *fusbh200, const __hc32 x) +{ + return le32_to_cpu(x); +} + +static inline u32 hc32_to_cpup (const struct fusbh200_hcd *fusbh200, const __hc32 *x) +{ + return le32_to_cpup(x); +} + +/*-------------------------------------------------------------------------*/ + +static inline unsigned fusbh200_read_frame_index(struct fusbh200_hcd *fusbh200) +{ + return fusbh200_readl(fusbh200, &fusbh200->regs->frame_index); +} + +#define fusbh200_itdlen(urb, desc, t) ({ \ + usb_pipein((urb)->pipe) ? \ + (desc)->length - FUSBH200_ITD_LENGTH(t) : \ + FUSBH200_ITD_LENGTH(t); \ +}) +/*-------------------------------------------------------------------------*/ + +#ifndef DEBUG +#define STUB_DEBUG_FILES +#endif /* DEBUG */ + +/*-------------------------------------------------------------------------*/ + +#endif /* __LINUX_FUSBH200_H */ diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 104730d..c0df599 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -577,7 +577,7 @@ static struct hc_driver hwahc_hc_driver = { .product_desc = "Wireless USB HWA host controller", .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd), .irq = NULL, /* FIXME */ - .flags = HCD_USB2, /* FIXME */ + .flags = HCD_USB25, .reset = hwahc_op_reset, .start = hwahc_op_start, .stop = hwahc_op_stop, @@ -588,8 +588,6 @@ static struct hc_driver hwahc_hc_driver = { .hub_status_data = wusbhc_rh_status_data, .hub_control = wusbhc_rh_control, - .bus_suspend = wusbhc_rh_suspend, - .bus_resume = wusbhc_rh_resume, .start_port_reset = wusbhc_rh_start_port_reset, }; diff --git a/drivers/usb/host/imx21-hcd.c b/drivers/usb/host/imx21-hcd.c index f0ebe8e..03dc4d9 100644 --- a/drivers/usb/host/imx21-hcd.c +++ b/drivers/usb/host/imx21-hcd.c @@ -809,26 +809,36 @@ static int imx21_hc_urb_enqueue_isoc(struct usb_hcd *hcd, /* calculate frame */ cur_frame = imx21_hc_get_frame(hcd); - if (urb->transfer_flags & URB_ISO_ASAP) { - if (list_empty(&ep_priv->td_list)) - urb->start_frame = cur_frame + 5; - else - urb->start_frame = list_entry( - ep_priv->td_list.prev, - struct td, list)->frame + urb->interval; - } - urb->start_frame = wrap_frame(urb->start_frame); - if (frame_after(cur_frame, urb->start_frame)) { - dev_dbg(imx21->dev, - "enqueue: adjusting iso start %d (cur=%d) asap=%d\n", - urb->start_frame, cur_frame, - (urb->transfer_flags & URB_ISO_ASAP) != 0); - urb->start_frame = wrap_frame(cur_frame + 1); + i = 0; + if (list_empty(&ep_priv->td_list)) { + urb->start_frame = wrap_frame(cur_frame + 5); + } else { + urb->start_frame = wrap_frame(list_entry(ep_priv->td_list.prev, + struct td, list)->frame + urb->interval); + + if (frame_after(cur_frame, urb->start_frame)) { + dev_dbg(imx21->dev, + "enqueue: adjusting iso start %d (cur=%d) asap=%d\n", + urb->start_frame, cur_frame, + (urb->transfer_flags & URB_ISO_ASAP) != 0); + i = DIV_ROUND_UP(wrap_frame( + cur_frame - urb->start_frame), + urb->interval); + if (urb->transfer_flags & URB_ISO_ASAP) { + urb->start_frame = wrap_frame(urb->start_frame + + i * urb->interval); + i = 0; + } else if (i >= urb->number_of_packets) { + ret = -EXDEV; + goto alloc_dmem_failed; + } + } } /* set up transfers */ + urb_priv->isoc_remaining = urb->number_of_packets - i; td = urb_priv->isoc_td; - for (i = 0; i < urb->number_of_packets; i++, td++) { + for (; i < urb->number_of_packets; i++, td++) { unsigned int offset = urb->iso_frame_desc[i].offset; td->ep = ep; td->urb = urb; @@ -840,7 +850,6 @@ static int imx21_hc_urb_enqueue_isoc(struct usb_hcd *hcd, list_add_tail(&td->list, &ep_priv->td_list); } - urb_priv->isoc_remaining = urb->number_of_packets; dev_vdbg(imx21->dev, "setup %d packets for iso frame %d->%d\n", urb->number_of_packets, urb->start_frame, td->frame); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index a13709e..3df49b1 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -118,7 +118,7 @@ static int of_isp1760_probe(struct platform_device *dev) goto free_gpio; } - dev_set_drvdata(&dev->dev, drvdata); + platform_set_drvdata(dev, drvdata); return ret; free_gpio: @@ -133,9 +133,7 @@ free_data: static int of_isp1760_remove(struct platform_device *dev) { - struct isp1760 *drvdata = dev_get_drvdata(&dev->dev); - - dev_set_drvdata(&dev->dev, NULL); + struct isp1760 *drvdata = platform_get_drvdata(dev); usb_remove_hcd(drvdata->hcd); iounmap(drvdata->hcd->regs); @@ -398,7 +396,7 @@ static int isp1760_plat_probe(struct platform_device *pdev) irqflags, -ENOENT, &pdev->dev, dev_name(&pdev->dev), devflags); - dev_set_drvdata(&pdev->dev, hcd); + platform_set_drvdata(pdev, hcd); if (IS_ERR(hcd)) { pr_warning("isp1760: Failed to register the HCD device\n"); @@ -419,7 +417,7 @@ static int isp1760_plat_remove(struct platform_device *pdev) { struct resource *mem_res; resource_size_t mem_size; - struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); + struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c index 0b815a8..6aaa9c9 100644 --- a/drivers/usb/host/ohci-da8xx.c +++ b/drivers/usb/host/ohci-da8xx.c @@ -401,7 +401,6 @@ static int ohci_hcd_da8xx_drv_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); usb_hcd_da8xx_remove(hcd, dev); - platform_set_drvdata(dev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index fc627fd..a9d3437 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -79,23 +79,8 @@ static const char hcd_name [] = "ohci_hcd"; #include "pci-quirks.h" static void ohci_dump (struct ohci_hcd *ohci, int verbose); -static int ohci_init (struct ohci_hcd *ohci); static void ohci_stop (struct usb_hcd *hcd); -#if defined(CONFIG_PM) || defined(CONFIG_PCI) -static int ohci_restart (struct ohci_hcd *ohci); -#endif - -#ifdef CONFIG_PCI -static void sb800_prefetch(struct ohci_hcd *ohci, int on); -#else -static inline void sb800_prefetch(struct ohci_hcd *ohci, int on) -{ - return; -} -#endif - - #include "ohci-hub.c" #include "ohci-dbg.c" #include "ohci-mem.c" @@ -772,6 +757,32 @@ retry: return 0; } +/* ohci_setup routine for generic controller initialization */ + +int ohci_setup(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci_hcd_init(ohci); + + return ohci_init(ohci); +} +EXPORT_SYMBOL_GPL(ohci_setup); + +/* ohci_start routine for generic controller start of all OHCI bus glue */ +static int ohci_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_run(ohci); + if (ret < 0) { + ohci_err(ohci, "can't start\n"); + ohci_stop(hcd); + } + return ret; +} + /*-------------------------------------------------------------------------*/ /* an interrupt happens */ @@ -953,12 +964,13 @@ static void ohci_stop (struct usb_hcd *hcd) #if defined(CONFIG_PM) || defined(CONFIG_PCI) /* must not be called from interrupt context */ -static int ohci_restart (struct ohci_hcd *ohci) +int ohci_restart(struct ohci_hcd *ohci) { int temp; int i; struct urb_priv *priv; + ohci_init(ohci); spin_lock_irq(&ohci->lock); ohci->rh_state = OHCI_RH_HALTED; @@ -1012,12 +1024,13 @@ static int ohci_restart (struct ohci_hcd *ohci) ohci_dbg(ohci, "restart complete\n"); return 0; } +EXPORT_SYMBOL_GPL(ohci_restart); #endif #ifdef CONFIG_PM -static int __maybe_unused ohci_suspend(struct usb_hcd *hcd, bool do_wakeup) +int ohci_suspend(struct usb_hcd *hcd, bool do_wakeup) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); unsigned long flags; @@ -1035,9 +1048,10 @@ static int __maybe_unused ohci_suspend(struct usb_hcd *hcd, bool do_wakeup) return 0; } +EXPORT_SYMBOL_GPL(ohci_suspend); -static int __maybe_unused ohci_resume(struct usb_hcd *hcd, bool hibernated) +int ohci_resume(struct usb_hcd *hcd, bool hibernated) { struct ohci_hcd *ohci = hcd_to_ohci(hcd); int port; @@ -1085,20 +1099,79 @@ static int __maybe_unused ohci_resume(struct usb_hcd *hcd, bool hibernated) return 0; } +EXPORT_SYMBOL_GPL(ohci_resume); #endif /*-------------------------------------------------------------------------*/ +/* + * Generic structure: This gets copied for platform drivers so that + * individual entries can be overridden as needed. + */ + +static const struct hc_driver ohci_hc_driver = { + .description = hcd_name, + .product_desc = "OHCI Host Controller", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_MEMORY | HCD_USB11, + + /* + * basic lifecycle operations + */ + .reset = ohci_setup, + .start = ohci_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +void ohci_init_driver(struct hc_driver *drv, + const struct ohci_driver_overrides *over) +{ + /* Copy the generic table to drv and then apply the overrides */ + *drv = ohci_hc_driver; + + drv->product_desc = over->product_desc; + drv->hcd_priv_size += over->extra_priv_size; + if (over->reset) + drv->reset = over->reset; +} +EXPORT_SYMBOL_GPL(ohci_init_driver); + +/*-------------------------------------------------------------------------*/ + MODULE_AUTHOR (DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE ("GPL"); -#ifdef CONFIG_PCI -#include "ohci-pci.c" -#define PCI_DRIVER ohci_pci_driver -#endif - #if defined(CONFIG_ARCH_SA1100) && defined(CONFIG_SA1111) #include "ohci-sa1111.c" #define SA1111_DRIVER ohci_hcd_sa1111_driver @@ -1189,30 +1262,6 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ohci_hcd_tilegx_driver #endif -#ifdef CONFIG_USB_OHCI_HCD_PLATFORM -#include "ohci-platform.c" -#define PLATFORM_DRIVER ohci_platform_driver -#endif - -#if !defined(PCI_DRIVER) && \ - !defined(PLATFORM_DRIVER) && \ - !defined(OMAP1_PLATFORM_DRIVER) && \ - !defined(OMAP3_PLATFORM_DRIVER) && \ - !defined(OF_PLATFORM_DRIVER) && \ - !defined(SA1111_DRIVER) && \ - !defined(PS3_SYSTEM_BUS_DRIVER) && \ - !defined(SM501_OHCI_DRIVER) && \ - !defined(TMIO_OHCI_DRIVER) && \ - !defined(S3C2410_PLATFORM_DRIVER) && \ - !defined(EXYNOS_PLATFORM_DRIVER) && \ - !defined(EP93XX_PLATFORM_DRIVER) && \ - !defined(AT91_PLATFORM_DRIVER) && \ - !defined(NXP_PLATFORM_DRIVER) && \ - !defined(DAVINCI_PLATFORM_DRIVER) && \ - !defined(SPEAR_PLATFORM_DRIVER) -#error "missing bus glue for ohci-hcd" -#endif - static int __init ohci_hcd_mod_init(void) { int retval = 0; @@ -1269,12 +1318,6 @@ static int __init ohci_hcd_mod_init(void) goto error_sa1111; #endif -#ifdef PCI_DRIVER - retval = pci_register_driver(&PCI_DRIVER); - if (retval < 0) - goto error_pci; -#endif - #ifdef SM501_OHCI_DRIVER retval = platform_driver_register(&SM501_OHCI_DRIVER); if (retval < 0) @@ -1368,10 +1411,6 @@ static int __init ohci_hcd_mod_init(void) platform_driver_unregister(&SM501_OHCI_DRIVER); error_sm501: #endif -#ifdef PCI_DRIVER - pci_unregister_driver(&PCI_DRIVER); - error_pci: -#endif #ifdef SA1111_DRIVER sa1111_driver_unregister(&SA1111_DRIVER); error_sa1111: @@ -1436,9 +1475,6 @@ static void __exit ohci_hcd_mod_exit(void) #ifdef SM501_OHCI_DRIVER platform_driver_unregister(&SM501_OHCI_DRIVER); #endif -#ifdef PCI_DRIVER - pci_unregister_driver(&PCI_DRIVER); -#endif #ifdef SA1111_DRIVER sa1111_driver_unregister(&SA1111_DRIVER); #endif diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 60ff422..2347ab8 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -176,7 +176,6 @@ __acquires(ohci->lock) if (status == -EBUSY) { if (!autostopped) { spin_unlock_irq (&ohci->lock); - (void) ohci_init (ohci); status = ohci_restart (ohci); usb_root_hub_lost_power(hcd->self.root_hub); diff --git a/drivers/usb/host/ohci-jz4740.c b/drivers/usb/host/ohci-jz4740.c index 8062bb9..d4ef539 100644 --- a/drivers/usb/host/ohci-jz4740.c +++ b/drivers/usb/host/ohci-jz4740.c @@ -221,7 +221,6 @@ static int jz4740_ohci_probe(struct platform_device *pdev) return 0; err_disable: - platform_set_drvdata(pdev, NULL); if (jz4740_ohci->vbus) { regulator_disable(jz4740_ohci->vbus); regulator_put(jz4740_ohci->vbus); @@ -246,8 +245,6 @@ static int jz4740_ohci_remove(struct platform_device *pdev) usb_remove_hcd(hcd); - platform_set_drvdata(pdev, NULL); - if (jz4740_ohci->vbus) { regulator_disable(jz4740_ohci->vbus); regulator_put(jz4740_ohci->vbus); diff --git a/drivers/usb/host/ohci-nxp.c b/drivers/usb/host/ohci-nxp.c index 5d7eb72..7d7d507 100644 --- a/drivers/usb/host/ohci-nxp.c +++ b/drivers/usb/host/ohci-nxp.c @@ -351,7 +351,6 @@ static int usb_hcd_nxp_remove(struct platform_device *pdev) usb_remove_hcd(hcd); nxp_stop_hc(); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); clk_disable(usb_pll_clk); clk_put(usb_pll_clk); @@ -360,8 +359,6 @@ static int usb_hcd_nxp_remove(struct platform_device *pdev) i2c_unregister_device(isp1301_i2c_client); isp1301_i2c_client = NULL; - platform_set_drvdata(pdev, NULL); - return 0; } diff --git a/drivers/usb/host/ohci-octeon.c b/drivers/usb/host/ohci-octeon.c index d44430d..342dc7e 100644 --- a/drivers/usb/host/ohci-octeon.c +++ b/drivers/usb/host/ohci-octeon.c @@ -196,8 +196,6 @@ static int ohci_octeon_drv_remove(struct platform_device *pdev) release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); - return 0; } diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index b1d32fb..8747fa6 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -498,7 +498,6 @@ static int ohci_hcd_omap_drv_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); usb_hcd_omap_remove(hcd, dev); - platform_set_drvdata(dev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-omap3.c b/drivers/usb/host/ohci-omap3.c index 8663851..8f71357 100644 --- a/drivers/usb/host/ohci-omap3.c +++ b/drivers/usb/host/ohci-omap3.c @@ -252,7 +252,7 @@ static struct platform_driver ohci_hcd_omap3_driver = { .shutdown = ohci_hcd_omap3_shutdown, .driver = { .name = "ohci-omap3", - .of_match_table = of_match_ptr(omap_ohci_dt_ids), + .of_match_table = omap_ohci_dt_ids, }, }; diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 951514e..08613e2 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -14,12 +14,19 @@ * This file is licenced under the GPL. */ -#ifndef CONFIG_PCI -#error "This file is PCI bus glue. CONFIG_PCI must be defined." -#endif - -#include <linux/pci.h> #include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "ohci.h" +#include "pci-quirks.h" + +#define DRIVER_DESC "OHCI PCI platform driver" + +static const char hcd_name[] = "ohci-pci"; /*-------------------------------------------------------------------------*/ @@ -123,13 +130,6 @@ static void ohci_quirk_nec_worker(struct work_struct *work) struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work); int status; - status = ohci_init(ohci); - if (status != 0) { - ohci_err(ohci, "Restarting NEC controller failed in %s, %d\n", - "ohci_init", status); - return; - } - status = ohci_restart(ohci); if (status != 0) ohci_err(ohci, "Restarting NEC controller failed in %s, %d\n", @@ -175,19 +175,6 @@ static int ohci_quirk_amd700(struct usb_hcd *hcd) return 0; } -static void sb800_prefetch(struct ohci_hcd *ohci, int on) -{ - struct pci_dev *pdev; - u16 misc; - - pdev = to_pci_dev(ohci_to_hcd(ohci)->self.controller); - pci_read_config_word(pdev, 0x50, &misc); - if (on == 0) - pci_write_config_word(pdev, 0x50, misc & 0xfcff); - else - pci_write_config_word(pdev, 0x50, misc | 0x0300); -} - /* List of quirks for OHCI */ static const struct pci_device_id ohci_pci_quirks[] = { { @@ -249,10 +236,10 @@ static const struct pci_device_id ohci_pci_quirks[] = { static int ohci_pci_reset (struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); int ret = 0; if (hcd->self.controller) { - struct pci_dev *pdev = to_pci_dev(hcd->self.controller); const struct pci_device_id *quirk_id; quirk_id = pci_match_id(ohci_pci_quirks, pdev); @@ -262,94 +249,25 @@ static int ohci_pci_reset (struct usb_hcd *hcd) ret = quirk(hcd); } } - if (ret == 0) { - ohci_hcd_init (ohci); - return ohci_init (ohci); - } - return ret; -} - - -static int ohci_pci_start (struct usb_hcd *hcd) -{ - struct ohci_hcd *ohci = hcd_to_ohci (hcd); - int ret; - -#ifdef CONFIG_PM /* avoid warnings about unused pdev */ - if (hcd->self.controller) { - struct pci_dev *pdev = to_pci_dev(hcd->self.controller); - - /* RWC may not be set for add-in PCI cards, since boot - * firmware probably ignored them. This transfers PCI - * PM wakeup capabilities. - */ - if (device_can_wakeup(&pdev->dev)) - ohci->hc_control |= OHCI_CTRL_RWC; - } -#endif /* CONFIG_PM */ - ret = ohci_run (ohci); - if (ret < 0) { - ohci_err (ohci, "can't start\n"); - ohci_stop (hcd); - } + if (ret == 0) + ret = ohci_setup(hcd); + /* + * After ohci setup RWC may not be set for add-in PCI cards. + * This transfers PCI PM wakeup capabilities. + */ + if (device_can_wakeup(&pdev->dev)) + ohci->hc_control |= OHCI_CTRL_RWC; return ret; } +static struct hc_driver __read_mostly ohci_pci_hc_driver; -/*-------------------------------------------------------------------------*/ - -static const struct hc_driver ohci_pci_hc_driver = { - .description = hcd_name, - .product_desc = "OHCI Host Controller", - .hcd_priv_size = sizeof(struct ohci_hcd), - - /* - * generic hardware linkage - */ - .irq = ohci_irq, - .flags = HCD_MEMORY | HCD_USB11, - - /* - * basic lifecycle operations - */ +static const struct ohci_driver_overrides pci_overrides __initconst = { + .product_desc = "OHCI PCI host controller", .reset = ohci_pci_reset, - .start = ohci_pci_start, - .stop = ohci_stop, - .shutdown = ohci_shutdown, - -#ifdef CONFIG_PM - .pci_suspend = ohci_suspend, - .pci_resume = ohci_resume, -#endif - - /* - * managing i/o requests and associated device resources - */ - .urb_enqueue = ohci_urb_enqueue, - .urb_dequeue = ohci_urb_dequeue, - .endpoint_disable = ohci_endpoint_disable, - - /* - * scheduling support - */ - .get_frame_number = ohci_get_frame, - - /* - * root hub support - */ - .hub_status_data = ohci_hub_status_data, - .hub_control = ohci_hub_control, -#ifdef CONFIG_PM - .bus_suspend = ohci_bus_suspend, - .bus_resume = ohci_bus_resume, -#endif - .start_port_reset = ohci_start_port_reset, }; -/*-------------------------------------------------------------------------*/ - - static const struct pci_device_id pci_ids [] = { { /* handle any USB OHCI controller */ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_OHCI, ~0), @@ -377,3 +295,24 @@ static struct pci_driver ohci_pci_driver = { }, #endif }; + +static int __init ohci_pci_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ohci_init_driver(&ohci_pci_hc_driver, &pci_overrides); + return pci_register_driver(&ohci_pci_driver); +} +module_init(ohci_pci_init); + +static void __exit ohci_pci_cleanup(void) +{ + pci_unregister_driver(&ohci_pci_driver); +} +module_exit(ohci_pci_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index c3e7287..bc30475 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -13,16 +13,28 @@ * * Licensed under the GNU/GPL. See COPYING for details. */ + +#include <linux/hrtimer.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> #include <linux/err.h> #include <linux/platform_device.h> #include <linux/usb/ohci_pdriver.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "ohci.h" + +#define DRIVER_DESC "OHCI generic platform driver" + +static const char hcd_name[] = "ohci-platform"; static int ohci_platform_reset(struct usb_hcd *hcd) { struct platform_device *pdev = to_platform_device(hcd->self.controller); struct usb_ohci_pdata *pdata = pdev->dev.platform_data; struct ohci_hcd *ohci = hcd_to_ohci(hcd); - int err; if (pdata->big_endian_desc) ohci->flags |= OHCI_QUIRK_BE_DESC; @@ -30,58 +42,17 @@ static int ohci_platform_reset(struct usb_hcd *hcd) ohci->flags |= OHCI_QUIRK_BE_MMIO; if (pdata->no_big_frame_no) ohci->flags |= OHCI_QUIRK_FRAME_NO; - - ohci_hcd_init(ohci); - if (pdata->num_ports) ohci->num_ports = pdata->num_ports; - err = ohci_init(ohci); - - return err; -} - -static int ohci_platform_start(struct usb_hcd *hcd) -{ - struct ohci_hcd *ohci = hcd_to_ohci(hcd); - int err; - - err = ohci_run(ohci); - if (err < 0) { - ohci_err(ohci, "can't start\n"); - ohci_stop(hcd); - } - - return err; + return ohci_setup(hcd); } -static const struct hc_driver ohci_platform_hc_driver = { - .description = hcd_name, - .product_desc = "Generic Platform OHCI Controller", - .hcd_priv_size = sizeof(struct ohci_hcd), +static struct hc_driver __read_mostly ohci_platform_hc_driver; - .irq = ohci_irq, - .flags = HCD_MEMORY | HCD_USB11, - - .reset = ohci_platform_reset, - .start = ohci_platform_start, - .stop = ohci_stop, - .shutdown = ohci_shutdown, - - .urb_enqueue = ohci_urb_enqueue, - .urb_dequeue = ohci_urb_dequeue, - .endpoint_disable = ohci_endpoint_disable, - - .get_frame_number = ohci_get_frame, - - .hub_status_data = ohci_hub_status_data, - .hub_control = ohci_hub_control, -#ifdef CONFIG_PM - .bus_suspend = ohci_bus_suspend, - .bus_resume = ohci_bus_resume, -#endif - - .start_port_reset = ohci_start_port_reset, +static const struct ohci_driver_overrides platform_overrides __initconst = { + .product_desc = "Generic Platform OHCI controller", + .reset = ohci_platform_reset, }; static int ohci_platform_probe(struct platform_device *dev) @@ -157,7 +128,6 @@ static int ohci_platform_remove(struct platform_device *dev) usb_remove_hcd(hcd); usb_put_hcd(hcd); - platform_set_drvdata(dev, NULL); if (pdata->power_off) pdata->power_off(dev); @@ -223,3 +193,26 @@ static struct platform_driver ohci_platform_driver = { .pm = &ohci_platform_pm_ops, } }; + +static int __init ohci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ohci_init_driver(&ohci_platform_hc_driver, &platform_overrides); + return platform_driver_register(&ohci_platform_driver); +} +module_init(ohci_platform_init); + +static void __exit ohci_platform_cleanup(void) +{ + platform_driver_unregister(&ohci_platform_driver); +} +module_exit(ohci_platform_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Hauke Mehrtens"); +MODULE_AUTHOR("Alan Stern"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 64c2ed9f..8294e2f 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -185,8 +185,7 @@ err_rmr: static int ohci_hcd_ppc_of_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); - dev_set_drvdata(&op->dev, NULL); + struct usb_hcd *hcd = platform_get_drvdata(op); dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); @@ -203,7 +202,7 @@ static int ohci_hcd_ppc_of_remove(struct platform_device *op) static void ohci_hcd_ppc_of_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index 279b2ef..3a9c01d 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -556,7 +556,6 @@ static int ohci_hcd_pxa27x_drv_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_hcd_pxa27x_remove(hcd, pdev); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c index 88731b7..df4a670 100644 --- a/drivers/usb/host/ohci-q.c +++ b/drivers/usb/host/ohci-q.c @@ -41,6 +41,7 @@ finish_urb(struct ohci_hcd *ohci, struct urb *urb, int status) __releases(ohci->lock) __acquires(ohci->lock) { + struct device *dev = ohci_to_hcd(ohci)->self.controller; // ASSERT (urb->hcpriv != 0); urb_free_priv (ohci, urb->hcpriv); @@ -55,7 +56,7 @@ __acquires(ohci->lock) if (quirk_amdiso(ohci)) usb_amd_quirk_pll_enable(); if (quirk_amdprefetch(ohci)) - sb800_prefetch(ohci, 0); + sb800_prefetch(dev, 0); } break; case PIPE_INTERRUPT: @@ -580,6 +581,7 @@ static void td_submit_urb ( struct urb *urb ) { struct urb_priv *urb_priv = urb->hcpriv; + struct device *dev = ohci_to_hcd(ohci)->self.controller; dma_addr_t data; int data_len = urb->transfer_buffer_length; int cnt = 0; @@ -689,7 +691,7 @@ static void td_submit_urb ( if (quirk_amdiso(ohci)) usb_amd_quirk_pll_disable(); if (quirk_amdprefetch(ohci)) - sb800_prefetch(ohci, 1); + sb800_prefetch(dev, 1); } periodic = ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs++ == 0 && ohci_to_hcd(ohci)->self.bandwidth_int_reqs == 0; diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c index 3b5b908..d479d5d 100644 --- a/drivers/usb/host/ohci-sm501.c +++ b/drivers/usb/host/ohci-sm501.c @@ -207,7 +207,6 @@ static int ohci_hcd_sm501_drv_remove(struct platform_device *pdev) sm501_modify_reg(pdev->dev.parent, SM501_IRQ_MASK, 0, 1 << 6); sm501_unit_power(pdev->dev.parent, SM501_GATE_USB_HOST, 0); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c index 3e19e01..cc9dd9e 100644 --- a/drivers/usb/host/ohci-spear.c +++ b/drivers/usb/host/ohci-spear.c @@ -179,8 +179,6 @@ static int spear_ohci_hcd_drv_remove(struct platform_device *pdev) spear_stop_ohci(ohci_p); usb_put_hcd(hcd); - - platform_set_drvdata(pdev, NULL); return 0; } @@ -232,7 +230,7 @@ static struct platform_driver spear_ohci_hcd_driver = { .driver = { .owner = THIS_MODULE, .name = "spear-ohci", - .of_match_table = of_match_ptr(spear_ohci_id_table), + .of_match_table = spear_ohci_id_table, }, }; diff --git a/drivers/usb/host/ohci-tilegx.c b/drivers/usb/host/ohci-tilegx.c index ea73009..197d514 100644 --- a/drivers/usb/host/ohci-tilegx.c +++ b/drivers/usb/host/ohci-tilegx.c @@ -182,7 +182,6 @@ static int ohci_hcd_tilegx_drv_remove(struct platform_device *pdev) tilegx_stop_ohc(); gxio_usb_host_destroy(&pdata->usb_ctx); destroy_irq(pdata->irq); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c index 5e3a6de..ecb09a5 100644 --- a/drivers/usb/host/ohci-tmio.c +++ b/drivers/usb/host/ohci-tmio.c @@ -287,8 +287,6 @@ static int ohci_hcd_tmio_drv_remove(struct platform_device *dev) iounmap(tmio->ccr); usb_put_hcd(hcd); - platform_set_drvdata(dev, NULL); - return 0; } diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index d329914..e2e5faa 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -421,6 +421,9 @@ struct ohci_hcd { struct dentry *debug_periodic; struct dentry *debug_registers; #endif + /* platform-specific data -- must come last */ + unsigned long priv[0] __aligned(sizeof(s64)); + }; #ifdef CONFIG_PCI @@ -718,3 +721,20 @@ static inline u32 roothub_status (struct ohci_hcd *hc) { return ohci_readl (hc, &hc->regs->roothub.status); } static inline u32 roothub_portstatus (struct ohci_hcd *hc, int i) { return read_roothub (hc, portstatus [i], 0xffe0fce0); } + +/* Declarations of things exported for use by ohci platform drivers */ + +struct ohci_driver_overrides { + const char *product_desc; + size_t extra_priv_size; + int (*reset)(struct usb_hcd *hcd); +}; + +extern void ohci_init_driver(struct hc_driver *drv, + const struct ohci_driver_overrides *over); +extern int ohci_restart(struct ohci_hcd *ohci); +extern int ohci_setup(struct usb_hcd *hcd); +#ifdef CONFIG_PM +extern int ohci_suspend(struct usb_hcd *hcd, bool do_wakeup); +extern int ohci_resume(struct usb_hcd *hcd, bool hibernated); +#endif diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 0f401db..4a6df2d 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -3874,7 +3874,6 @@ static int oxu_drv_probe(struct platform_device *pdev) error_init: kfree(info); - platform_set_drvdata(pdev, NULL); error_alloc: iounmap(base); @@ -3907,7 +3906,6 @@ static int oxu_drv_remove(struct platform_device *pdev) release_mem_region(memstart, memlen); kfree(info); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 4c338ec..b9848e4 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -91,6 +91,19 @@ static struct amd_chipset_info { static DEFINE_SPINLOCK(amd_lock); +void sb800_prefetch(struct device *dev, int on) +{ + u16 misc; + struct pci_dev *pdev = to_pci_dev(dev); + + pci_read_config_word(pdev, 0x50, &misc); + if (on == 0) + pci_write_config_word(pdev, 0x50, misc & 0xfcff); + else + pci_write_config_word(pdev, 0x50, misc | 0x0300); +} +EXPORT_SYMBOL_GPL(sb800_prefetch); + int usb_amd_find_chipset_info(void) { u8 rev = 0; diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h index 7f69a39..4b8a209 100644 --- a/drivers/usb/host/pci-quirks.h +++ b/drivers/usb/host/pci-quirks.h @@ -11,11 +11,13 @@ void usb_amd_quirk_pll_enable(void); bool usb_is_intel_switchable_xhci(struct pci_dev *pdev); void usb_enable_xhci_ports(struct pci_dev *xhci_pdev); void usb_disable_xhci_ports(struct pci_dev *xhci_pdev); +void sb800_prefetch(struct device *dev, int on); #else static inline void usb_amd_quirk_pll_disable(void) {} static inline void usb_amd_quirk_pll_enable(void) {} static inline void usb_amd_dev_put(void) {} static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {} +static inline void sb800_prefetch(struct device *dev, int on) {} #endif /* CONFIG_PCI */ #endif /* __LINUX_USB_PCI_QUIRKS_H */ diff --git a/drivers/usb/host/uhci-grlib.c b/drivers/usb/host/uhci-grlib.c index 511bfc4..53c23ff 100644 --- a/drivers/usb/host/uhci-grlib.c +++ b/drivers/usb/host/uhci-grlib.c @@ -157,9 +157,7 @@ err_rmr: static int uhci_hcd_grlib_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); - - dev_set_drvdata(&op->dev, NULL); + struct usb_hcd *hcd = platform_get_drvdata(op); dev_dbg(&op->dev, "stopping GRLIB GRUSBHC UHCI USB Controller\n"); @@ -183,7 +181,7 @@ static int uhci_hcd_grlib_remove(struct platform_device *op) */ static void uhci_hcd_grlib_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); uhci_hc_died(hcd_to_uhci(hcd)); } diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index f1db61a..d033a0e 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -130,7 +130,6 @@ static int uhci_hcd_platform_remove(struct platform_device *pdev) iounmap(hcd->regs); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); return 0; } @@ -144,7 +143,7 @@ static int uhci_hcd_platform_remove(struct platform_device *pdev) */ static void uhci_hcd_platform_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); uhci_hc_died(hcd_to_uhci(hcd)); } @@ -161,6 +160,6 @@ static struct platform_driver uhci_platform_driver = { .driver = { .name = "platform-uhci", .owner = THIS_MODULE, - .of_match_table = of_match_ptr(platform_uhci_ids), + .of_match_table = platform_uhci_ids, }, }; diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c index c3a6478..ecc88db 100644 --- a/drivers/usb/host/whci/hcd.c +++ b/drivers/usb/host/whci/hcd.c @@ -231,8 +231,6 @@ static struct hc_driver whc_hc_driver = { .hub_status_data = wusbhc_rh_status_data, .hub_control = wusbhc_rh_control, - .bus_suspend = wusbhc_rh_suspend, - .bus_resume = wusbhc_rh_resume, .start_port_reset = wusbhc_rh_start_port_reset, }; diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 5f3a7c7..f2e7689 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -503,11 +503,14 @@ static void xhci_dbg_ep_ctx(struct xhci_hcd *xhci, if (last_ep < 31) last_ep_ctx = last_ep + 1; for (i = 0; i < last_ep_ctx; ++i) { + unsigned int epaddr = xhci_get_endpoint_address(i); struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, ctx, i); dma_addr_t dma = ctx->dma + ((unsigned long)ep_ctx - (unsigned long)ctx->bytes); - xhci_dbg(xhci, "Endpoint %02d Context:\n", i); + xhci_dbg(xhci, "%s Endpoint %02d Context (ep_index %02d):\n", + usb_endpoint_out(epaddr) ? "OUT" : "IN", + epaddr & USB_ENDPOINT_NUMBER_MASK, i); xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info\n", &ep_ctx->ep_info, (unsigned long long)dma, ep_ctx->ep_info); diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h index 377f424..8d7a132 100644 --- a/drivers/usb/host/xhci-ext-caps.h +++ b/drivers/usb/host/xhci-ext-caps.h @@ -71,6 +71,7 @@ /* USB 2.0 xHCI 1.0 hardware LMP capability - section 7.2.2.1.3.2 */ #define XHCI_HLC (1 << 19) +#define XHCI_BLC (1 << 19) /* command register values to disable interrupts and halt the HC */ /* start/stop HC execution - do not write unless HC is halted*/ diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 187a3ec..1d35459 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -867,18 +867,18 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_U1_TIMEOUT: if (hcd->speed != HCD_USB3) goto error; - temp = xhci_readl(xhci, port_array[wIndex] + 1); + temp = xhci_readl(xhci, port_array[wIndex] + PORTPMSC); temp &= ~PORT_U1_TIMEOUT_MASK; temp |= PORT_U1_TIMEOUT(timeout); - xhci_writel(xhci, temp, port_array[wIndex] + 1); + xhci_writel(xhci, temp, port_array[wIndex] + PORTPMSC); break; case USB_PORT_FEAT_U2_TIMEOUT: if (hcd->speed != HCD_USB3) goto error; - temp = xhci_readl(xhci, port_array[wIndex] + 1); + temp = xhci_readl(xhci, port_array[wIndex] + PORTPMSC); temp &= ~PORT_U2_TIMEOUT_MASK; temp |= PORT_U2_TIMEOUT(timeout); - xhci_writel(xhci, temp, port_array[wIndex] + 1); + xhci_writel(xhci, temp, port_array[wIndex] + PORTPMSC); break; default: goto error; @@ -1098,10 +1098,8 @@ int xhci_bus_suspend(struct usb_hcd *hcd) __le32 __iomem *addr; u32 tmp; - /* Add one to the port status register address to get - * the port power control register address. - */ - addr = port_array[port_index] + 1; + /* Get the port power control register address. */ + addr = port_array[port_index] + PORTPMSC; tmp = xhci_readl(xhci, addr); tmp |= PORT_RWE; xhci_writel(xhci, tmp, addr); @@ -1193,7 +1191,7 @@ int xhci_bus_resume(struct usb_hcd *hcd) /* Add one to the port status register address to get * the port power control register address. */ - addr = port_array[port_index] + 1; + addr = port_array[port_index] + PORTPMSC; tmp = xhci_readl(xhci, addr); tmp &= ~PORT_RWE; xhci_writel(xhci, tmp, addr); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index fbf75e5..8c0e119 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1856,6 +1856,7 @@ no_bw: kfree(xhci->usb3_ports); kfree(xhci->port_array); kfree(xhci->rh_bw); + kfree(xhci->ext_caps); xhci->page_size = 0; xhci->page_shift = 0; @@ -2043,7 +2044,7 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) } static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, - __le32 __iomem *addr, u8 major_revision) + __le32 __iomem *addr, u8 major_revision, int max_caps) { u32 temp, port_offset, port_count; int i; @@ -2068,6 +2069,10 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, /* WTF? "Valid values are ‘1’ to MaxPorts" */ return; + /* cache usb2 port capabilities */ + if (major_revision < 0x03 && xhci->num_ext_caps < max_caps) + xhci->ext_caps[xhci->num_ext_caps++] = temp; + /* Check the host's USB2 LPM capability */ if ((xhci->hci_version == 0x96) && (major_revision != 0x03) && (temp & XHCI_L1C)) { @@ -2125,10 +2130,11 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, */ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) { - __le32 __iomem *addr; - u32 offset; + __le32 __iomem *addr, *tmp_addr; + u32 offset, tmp_offset; unsigned int num_ports; int i, j, port_index; + int cap_count = 0; addr = &xhci->cap_regs->hcc_params; offset = XHCI_HCC_EXT_CAPS(xhci_readl(xhci, addr)); @@ -2161,13 +2167,32 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) * See section 5.3.6 for offset calculation. */ addr = &xhci->cap_regs->hc_capbase + offset; + + tmp_addr = addr; + tmp_offset = offset; + + /* count extended protocol capability entries for later caching */ + do { + u32 cap_id; + cap_id = xhci_readl(xhci, tmp_addr); + if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) + cap_count++; + tmp_offset = XHCI_EXT_CAPS_NEXT(cap_id); + tmp_addr += tmp_offset; + } while (tmp_offset); + + xhci->ext_caps = kzalloc(sizeof(*xhci->ext_caps) * cap_count, flags); + if (!xhci->ext_caps) + return -ENOMEM; + while (1) { u32 cap_id; cap_id = xhci_readl(xhci, addr); if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) xhci_add_in_port(xhci, num_ports, addr, - (u8) XHCI_EXT_PORT_MAJOR(cap_id)); + (u8) XHCI_EXT_PORT_MAJOR(cap_id), + cap_count); offset = XHCI_EXT_CAPS_NEXT(cap_id); if (!offset || (xhci->num_usb2_ports + xhci->num_usb3_ports) == num_ports) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index df90fe5..a3a4aa3 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -130,7 +130,7 @@ static int xhci_plat_probe(struct platform_device *pdev) goto unmap_registers; /* USB 2.0 roothub is stored in the platform_device now. */ - hcd = dev_get_drvdata(&pdev->dev); + hcd = platform_get_drvdata(pdev); xhci = hcd_to_xhci(hcd); xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev, dev_name(&pdev->dev), hcd); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1969c00..e02b907 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2799,7 +2799,7 @@ hw_died: return IRQ_HANDLED; } -irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd) +irqreturn_t xhci_msi_irq(int irq, void *hcd) { return xhci_irq(hcd); } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index d8f640b..77113c1 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -218,7 +218,7 @@ static int xhci_setup_msi(struct xhci_hcd *xhci) return ret; } - ret = request_irq(pdev->irq, (irq_handler_t)xhci_msi_irq, + ret = request_irq(pdev->irq, xhci_msi_irq, 0, "xhci_hcd", xhci_to_hcd(xhci)); if (ret) { xhci_dbg(xhci, "disable MSI interrupt\n"); @@ -290,7 +290,7 @@ static int xhci_setup_msix(struct xhci_hcd *xhci) for (i = 0; i < xhci->msix_count; i++) { ret = request_irq(xhci->msix_entries[i].vector, - (irq_handler_t)xhci_msi_irq, + xhci_msi_irq, 0, "xhci_hcd", xhci_to_hcd(xhci)); if (ret) goto disable_msix; @@ -1121,6 +1121,16 @@ unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc) return index; } +/* The reverse operation to xhci_get_endpoint_index. Calculate the USB endpoint + * address from the XHCI endpoint index. + */ +unsigned int xhci_get_endpoint_address(unsigned int ep_index) +{ + unsigned int number = DIV_ROUND_UP(ep_index, 2); + unsigned int direction = ep_index % 2 ? USB_DIR_OUT : USB_DIR_IN; + return direction | number; +} + /* Find the flag for this endpoint (for use in the control context). Use the * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is * bit 1, etc. @@ -3815,6 +3825,56 @@ int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1) return raw_port; } +/* + * Issue an Evaluate Context command to change the Maximum Exit Latency in the + * slot context. If that succeeds, store the new MEL in the xhci_virt_device. + */ +static int xhci_change_max_exit_latency(struct xhci_hcd *xhci, + struct usb_device *udev, u16 max_exit_latency) +{ + struct xhci_virt_device *virt_dev; + struct xhci_command *command; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_slot_ctx *slot_ctx; + unsigned long flags; + int ret; + + spin_lock_irqsave(&xhci->lock, flags); + if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) { + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; + } + + /* Attempt to issue an Evaluate Context command to change the MEL. */ + virt_dev = xhci->devs[udev->slot_id]; + command = xhci->lpm_command; + xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + + ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx); + slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT)); + slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency); + + xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n"); + xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, command->in_ctx, 0); + + /* Issue and wait for the evaluate context command. */ + ret = xhci_configure_endpoint(xhci, udev, command, + true, true); + xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0); + + if (!ret) { + spin_lock_irqsave(&xhci->lock, flags); + virt_dev->current_mel = max_exit_latency; + spin_unlock_irqrestore(&xhci->lock, flags); + } + return ret; +} + #ifdef CONFIG_PM_RUNTIME /* BESL to HIRD Encoding array for USB2 LPM */ @@ -3856,6 +3916,28 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, return besl; } +/* Calculate BESLD, L1 timeout and HIRDM for USB2 PORTHLPMC */ +static int xhci_calculate_usb2_hw_lpm_params(struct usb_device *udev) +{ + u32 field; + int l1; + int besld = 0; + int hirdm = 0; + + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + + /* xHCI l1 is set in steps of 256us, xHCI 1.0 section 5.4.11.2 */ + l1 = udev->l1_params.timeout / 256; + + /* device has preferred BESLD */ + if (field & USB_BESL_DEEP_VALID) { + besld = USB_GET_BESL_DEEP(field); + hirdm = 1; + } + + return PORT_BESLD(besld) | PORT_L1_TIMEOUT(l1) | PORT_HIRDM(hirdm); +} + static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd, struct usb_device *udev) { @@ -3911,7 +3993,7 @@ static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd, * Check device's USB 2.0 extension descriptor to determine whether * HIRD or BESL shoule be used. See USB2.0 LPM errata. */ - pm_addr = port_array[port_num] + 1; + pm_addr = port_array[port_num] + PORTPMSC; hird = xhci_calculate_hird_besl(xhci, udev); temp = PORT_L1DS(udev->slot_id) | PORT_HIRD(hird); xhci_writel(xhci, temp, pm_addr); @@ -3988,11 +4070,12 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, { struct xhci_hcd *xhci = hcd_to_xhci(hcd); __le32 __iomem **port_array; - __le32 __iomem *pm_addr; - u32 temp; + __le32 __iomem *pm_addr, *hlpm_addr; + u32 pm_val, hlpm_val, field; unsigned int port_num; unsigned long flags; - int hird; + int hird, exit_latency; + int ret; if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support || !udev->lpm_capable) @@ -4009,40 +4092,120 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, port_array = xhci->usb2_ports; port_num = udev->portnum - 1; - pm_addr = port_array[port_num] + 1; - temp = xhci_readl(xhci, pm_addr); + pm_addr = port_array[port_num] + PORTPMSC; + pm_val = xhci_readl(xhci, pm_addr); + hlpm_addr = port_array[port_num] + PORTHLPMC; + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n", enable ? "enable" : "disable", port_num); - hird = xhci_calculate_hird_besl(xhci, udev); - if (enable) { - temp &= ~PORT_HIRD_MASK; - temp |= PORT_HIRD(hird) | PORT_RWE; - xhci_writel(xhci, temp, pm_addr); - temp = xhci_readl(xhci, pm_addr); - temp |= PORT_HLE; - xhci_writel(xhci, temp, pm_addr); + /* Host supports BESL timeout instead of HIRD */ + if (udev->usb2_hw_lpm_besl_capable) { + /* if device doesn't have a preferred BESL value use a + * default one which works with mixed HIRD and BESL + * systems. See XHCI_DEFAULT_BESL definition in xhci.h + */ + if ((field & USB_BESL_SUPPORT) && + (field & USB_BESL_BASELINE_VALID)) + hird = USB_GET_BESL_BASELINE(field); + else + hird = udev->l1_params.besl; + + exit_latency = xhci_besl_encoding[hird]; + spin_unlock_irqrestore(&xhci->lock, flags); + + /* USB 3.0 code dedicate one xhci->lpm_command->in_ctx + * input context for link powermanagement evaluate + * context commands. It is protected by hcd->bandwidth + * mutex and is shared by all devices. We need to set + * the max ext latency in USB 2 BESL LPM as well, so + * use the same mutex and xhci_change_max_exit_latency() + */ + mutex_lock(hcd->bandwidth_mutex); + ret = xhci_change_max_exit_latency(xhci, udev, + exit_latency); + mutex_unlock(hcd->bandwidth_mutex); + + if (ret < 0) + return ret; + spin_lock_irqsave(&xhci->lock, flags); + + hlpm_val = xhci_calculate_usb2_hw_lpm_params(udev); + xhci_writel(xhci, hlpm_val, hlpm_addr); + /* flush write */ + xhci_readl(xhci, hlpm_addr); + } else { + hird = xhci_calculate_hird_besl(xhci, udev); + } + + pm_val &= ~PORT_HIRD_MASK; + pm_val |= PORT_HIRD(hird) | PORT_RWE; + xhci_writel(xhci, pm_val, pm_addr); + pm_val = xhci_readl(xhci, pm_addr); + pm_val |= PORT_HLE; + xhci_writel(xhci, pm_val, pm_addr); + /* flush write */ + xhci_readl(xhci, pm_addr); } else { - temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK); - xhci_writel(xhci, temp, pm_addr); + pm_val &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK); + xhci_writel(xhci, pm_val, pm_addr); + /* flush write */ + xhci_readl(xhci, pm_addr); + if (udev->usb2_hw_lpm_besl_capable) { + spin_unlock_irqrestore(&xhci->lock, flags); + mutex_lock(hcd->bandwidth_mutex); + xhci_change_max_exit_latency(xhci, udev, 0); + mutex_unlock(hcd->bandwidth_mutex); + return 0; + } } spin_unlock_irqrestore(&xhci->lock, flags); return 0; } +/* check if a usb2 port supports a given extened capability protocol + * only USB2 ports extended protocol capability values are cached. + * Return 1 if capability is supported + */ +static int xhci_check_usb2_port_capability(struct xhci_hcd *xhci, int port, + unsigned capability) +{ + u32 port_offset, port_count; + int i; + + for (i = 0; i < xhci->num_ext_caps; i++) { + if (xhci->ext_caps[i] & capability) { + /* port offsets starts at 1 */ + port_offset = XHCI_EXT_PORT_OFF(xhci->ext_caps[i]) - 1; + port_count = XHCI_EXT_PORT_COUNT(xhci->ext_caps[i]); + if (port >= port_offset && + port < port_offset + port_count) + return 1; + } + } + return 0; +} + int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); int ret; + int portnum = udev->portnum - 1; ret = xhci_usb2_software_lpm_test(hcd, udev); if (!ret) { xhci_dbg(xhci, "software LPM test succeed\n"); - if (xhci->hw_lpm_support == 1) { + if (xhci->hw_lpm_support == 1 && + xhci_check_usb2_port_capability(xhci, portnum, XHCI_HLC)) { udev->usb2_hw_lpm_capable = 1; + udev->l1_params.timeout = XHCI_L1_TIMEOUT; + udev->l1_params.besl = XHCI_DEFAULT_BESL; + if (xhci_check_usb2_port_capability(xhci, portnum, + XHCI_BLC)) + udev->usb2_hw_lpm_besl_capable = 1; ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1); if (!ret) udev->usb2_hw_lpm_enabled = 1; @@ -4373,56 +4536,6 @@ static u16 xhci_calculate_lpm_timeout(struct usb_hcd *hcd, return timeout; } -/* - * Issue an Evaluate Context command to change the Maximum Exit Latency in the - * slot context. If that succeeds, store the new MEL in the xhci_virt_device. - */ -static int xhci_change_max_exit_latency(struct xhci_hcd *xhci, - struct usb_device *udev, u16 max_exit_latency) -{ - struct xhci_virt_device *virt_dev; - struct xhci_command *command; - struct xhci_input_control_ctx *ctrl_ctx; - struct xhci_slot_ctx *slot_ctx; - unsigned long flags; - int ret; - - spin_lock_irqsave(&xhci->lock, flags); - if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) { - spin_unlock_irqrestore(&xhci->lock, flags); - return 0; - } - - /* Attempt to issue an Evaluate Context command to change the MEL. */ - virt_dev = xhci->devs[udev->slot_id]; - command = xhci->lpm_command; - xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx); - spin_unlock_irqrestore(&xhci->lock, flags); - - ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); - ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); - slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx); - slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT)); - slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency); - - xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n"); - xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, command->in_ctx, 0); - - /* Issue and wait for the evaluate context command. */ - ret = xhci_configure_endpoint(xhci, udev, command, - true, true); - xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0); - - if (!ret) { - spin_lock_irqsave(&xhci->lock, flags); - virt_dev->current_mel = max_exit_latency; - spin_unlock_irqrestore(&xhci->lock, flags); - } - return ret; -} - static int calculate_max_exit_latency(struct usb_device *udev, enum usb3_link_state state_changed, u16 hub_encoded_timeout) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 77600ce..c338741 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -132,6 +132,11 @@ struct xhci_cap_regs { /* Number of registers per port */ #define NUM_PORT_REGS 4 +#define PORTSC 0 +#define PORTPMSC 1 +#define PORTLI 2 +#define PORTHLPMC 3 + /** * struct xhci_op_regs - xHCI Host Controller Operational Registers. * @command: USBCMD - xHC command register @@ -381,6 +386,27 @@ struct xhci_op_regs { #define PORT_L1DS(p) (((p) & 0xff) << 8) #define PORT_HLE (1 << 16) + +/* USB2 Protocol PORTHLPMC */ +#define PORT_HIRDM(p)((p) & 3) +#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2) +#define PORT_BESLD(p)(((p) & 0xf) << 10) + +/* use 512 microseconds as USB2 LPM L1 default timeout. */ +#define XHCI_L1_TIMEOUT 512 + +/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency. + * Safe to use with mixed HIRD and BESL systems (host and device) and is used + * by other operating systems. + * + * XHCI 1.0 errata 8/14/12 Table 13 notes: + * "Software should choose xHC BESL/BESLD field values that do not violate a + * device's resume latency requirements, + * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached, + * or not program values < '4' if BLC = '0' and a BESL device is attached. + */ +#define XHCI_DEFAULT_BESL 4 + /** * struct xhci_intr_reg - Interrupt Register Set * @irq_pending: IMAN - Interrupt Management Register. Used to enable @@ -1532,6 +1558,9 @@ struct xhci_hcd { unsigned sw_lpm_support:1; /* support xHCI 1.0 spec USB2 hardware LPM */ unsigned hw_lpm_support:1; + /* cached usb2 extened protocol capabilites */ + u32 *ext_caps; + unsigned int num_ext_caps; /* Compliance Mode Recovery Data */ struct timer_list comp_mode_recovery_timer; u32 port_status_u0; @@ -1641,6 +1670,7 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci, struct usb_device *udev); unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc); +unsigned int xhci_get_endpoint_address(unsigned int ep_index); unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc); unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index); unsigned int xhci_last_valid_endpoint(u32 added_ctxs); @@ -1745,7 +1775,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated); int xhci_get_frame(struct usb_hcd *hcd); irqreturn_t xhci_irq(struct usb_hcd *hcd); -irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd); +irqreturn_t xhci_msi_irq(int irq, void *hcd); int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev); void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); int xhci_alloc_tt_info(struct xhci_hcd *xhci, diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c index 284b854..eb3c8c1 100644 --- a/drivers/usb/misc/adutux.c +++ b/drivers/usb/misc/adutux.c @@ -25,7 +25,7 @@ #include <linux/module.h> #include <linux/usb.h> #include <linux/mutex.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> #ifdef CONFIG_USB_DEBUG static int debug = 5; @@ -35,8 +35,8 @@ static int debug = 1; /* Use our own dbg macro */ #undef dbg -#define dbg(lvl, format, arg...) \ -do { \ +#define dbg(lvl, format, arg...) \ +do { \ if (debug >= lvl) \ printk(KERN_DEBUG "%s: " format "\n", __FILE__, ##arg); \ } while (0) @@ -58,12 +58,12 @@ MODULE_PARM_DESC(debug, "Debug enabled or not"); /* table of devices that work with this driver */ static const struct usb_device_id device_table[] = { { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID) }, /* ADU100 */ - { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+20) }, /* ADU120 */ - { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+30) }, /* ADU130 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+20) }, /* ADU120 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+30) }, /* ADU130 */ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+100) }, /* ADU200 */ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+108) }, /* ADU208 */ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+118) }, /* ADU218 */ - { }/* Terminating entry */ + { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, device_table); @@ -92,16 +92,16 @@ MODULE_DEVICE_TABLE(usb, device_table); /* Structure to hold all of our device specific stuff */ struct adu_device { struct mutex mtx; - struct usb_device* udev; /* save off the usb device pointer */ - struct usb_interface* interface; + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; unsigned int minor; /* the starting minor number for this device */ char serial_number[8]; int open_count; /* number of times this port has been opened */ - char* read_buffer_primary; + char *read_buffer_primary; int read_buffer_length; - char* read_buffer_secondary; + char *read_buffer_secondary; int secondary_head; int secondary_tail; spinlock_t buflock; @@ -109,14 +109,14 @@ struct adu_device { wait_queue_head_t read_wait; wait_queue_head_t write_wait; - char* interrupt_in_buffer; - struct usb_endpoint_descriptor* interrupt_in_endpoint; - struct urb* interrupt_in_urb; + char *interrupt_in_buffer; + struct usb_endpoint_descriptor *interrupt_in_endpoint; + struct urb *interrupt_in_urb; int read_urb_finished; - char* interrupt_out_buffer; - struct usb_endpoint_descriptor* interrupt_out_endpoint; - struct urb* interrupt_out_urb; + char *interrupt_out_buffer; + struct usb_endpoint_descriptor *interrupt_out_endpoint; + struct urb *interrupt_out_urb; int out_urb_finished; }; @@ -147,10 +147,10 @@ static void adu_abort_transfers(struct adu_device *dev) { unsigned long flags; - dbg(2," %s : enter", __func__); + dbg(2, " %s : enter", __func__); if (dev->udev == NULL) { - dbg(1," %s : udev is null", __func__); + dbg(1, " %s : udev is null", __func__); goto exit; } @@ -172,7 +172,7 @@ static void adu_abort_transfers(struct adu_device *dev) spin_unlock_irqrestore(&dev->buflock, flags); exit: - dbg(2," %s : leave", __func__); + dbg(2, " %s : leave", __func__); } static void adu_delete(struct adu_device *dev) @@ -196,7 +196,7 @@ static void adu_interrupt_in_callback(struct urb *urb) struct adu_device *dev = urb->context; int status = urb->status; - dbg(4," %s : enter, status %d", __func__, status); + dbg(4, " %s : enter, status %d", __func__, status); adu_debug_data(5, __func__, urb->actual_length, urb->transfer_buffer); @@ -205,7 +205,7 @@ static void adu_interrupt_in_callback(struct urb *urb) if (status != 0) { if ((status != -ENOENT) && (status != -ECONNRESET) && (status != -ESHUTDOWN)) { - dbg(1," %s : nonzero status received: %d", + dbg(1, " %s : nonzero status received: %d", __func__, status); } goto exit; @@ -220,10 +220,10 @@ static void adu_interrupt_in_callback(struct urb *urb) dev->interrupt_in_buffer, urb->actual_length); dev->read_buffer_length += urb->actual_length; - dbg(2," %s reading %d ", __func__, + dbg(2, " %s reading %d ", __func__, urb->actual_length); } else { - dbg(1," %s : read_buffer overflow", __func__); + dbg(1, " %s : read_buffer overflow", __func__); } } @@ -234,7 +234,7 @@ exit: wake_up_interruptible(&dev->read_wait); adu_debug_data(5, __func__, urb->actual_length, urb->transfer_buffer); - dbg(4," %s : leave, status %d", __func__, status); + dbg(4, " %s : leave, status %d", __func__, status); } static void adu_interrupt_out_callback(struct urb *urb) @@ -242,8 +242,8 @@ static void adu_interrupt_out_callback(struct urb *urb) struct adu_device *dev = urb->context; int status = urb->status; - dbg(4," %s : enter, status %d", __func__, status); - adu_debug_data(5,__func__, urb->actual_length, urb->transfer_buffer); + dbg(4, " %s : enter, status %d", __func__, status); + adu_debug_data(5, __func__, urb->actual_length, urb->transfer_buffer); if (status != 0) { if ((status != -ENOENT) && @@ -262,7 +262,7 @@ exit: adu_debug_data(5, __func__, urb->actual_length, urb->transfer_buffer); - dbg(4," %s : leave, status %d", __func__, status); + dbg(4, " %s : leave, status %d", __func__, status); } static int adu_open(struct inode *inode, struct file *file) @@ -272,11 +272,12 @@ static int adu_open(struct inode *inode, struct file *file) int subminor; int retval; - dbg(2,"%s : enter", __func__); + dbg(2, "%s : enter", __func__); subminor = iminor(inode); - if ((retval = mutex_lock_interruptible(&adutux_mutex))) { + retval = mutex_lock_interruptible(&adutux_mutex); + if (retval) { dbg(2, "%s : mutex lock failed", __func__); goto exit_no_lock; } @@ -302,7 +303,7 @@ static int adu_open(struct inode *inode, struct file *file) } ++dev->open_count; - dbg(2,"%s : open count %d", __func__, dev->open_count); + dbg(2, "%s : open count %d", __func__, dev->open_count); /* save device in the file's private structure */ file->private_data = dev; @@ -311,7 +312,7 @@ static int adu_open(struct inode *inode, struct file *file) dev->read_buffer_length = 0; /* fixup first read by having urb waiting for it */ - usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, + usb_fill_int_urb(dev->interrupt_in_urb, dev->udev, usb_rcvintpipe(dev->udev, dev->interrupt_in_endpoint->bEndpointAddress), dev->interrupt_in_buffer, @@ -332,23 +333,23 @@ static int adu_open(struct inode *inode, struct file *file) exit_no_device: mutex_unlock(&adutux_mutex); exit_no_lock: - dbg(2,"%s : leave, return value %d ", __func__, retval); + dbg(2, "%s : leave, return value %d ", __func__, retval); return retval; } static void adu_release_internal(struct adu_device *dev) { - dbg(2," %s : enter", __func__); + dbg(2, " %s : enter", __func__); /* decrement our usage count for the device */ --dev->open_count; - dbg(2," %s : open count %d", __func__, dev->open_count); + dbg(2, " %s : open count %d", __func__, dev->open_count); if (dev->open_count <= 0) { adu_abort_transfers(dev); dev->open_count = 0; } - dbg(2," %s : leave", __func__); + dbg(2, " %s : leave", __func__); } static int adu_release(struct inode *inode, struct file *file) @@ -356,17 +357,17 @@ static int adu_release(struct inode *inode, struct file *file) struct adu_device *dev; int retval = 0; - dbg(2," %s : enter", __func__); + dbg(2, " %s : enter", __func__); if (file == NULL) { - dbg(1," %s : file is NULL", __func__); + dbg(1, " %s : file is NULL", __func__); retval = -ENODEV; goto exit; } dev = file->private_data; if (dev == NULL) { - dbg(1," %s : object is NULL", __func__); + dbg(1, " %s : object is NULL", __func__); retval = -ENODEV; goto exit; } @@ -374,7 +375,7 @@ static int adu_release(struct inode *inode, struct file *file) mutex_lock(&adutux_mutex); /* not interruptible */ if (dev->open_count <= 0) { - dbg(1," %s : device not opened", __func__); + dbg(1, " %s : device not opened", __func__); retval = -ENODEV; goto unlock; } @@ -388,7 +389,7 @@ static int adu_release(struct inode *inode, struct file *file) unlock: mutex_unlock(&adutux_mutex); exit: - dbg(2," %s : leave, return value %d", __func__, retval); + dbg(2, " %s : leave, return value %d", __func__, retval); return retval; } @@ -405,10 +406,10 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, unsigned long flags; DECLARE_WAITQUEUE(wait, current); - dbg(2," %s : enter, count = %Zd, file=%p", __func__, count, file); + dbg(2, " %s : enter, count = %Zd, file=%p", __func__, count, file); dev = file->private_data; - dbg(2," %s : dev=%p", __func__, dev); + dbg(2, " %s : dev=%p", __func__, dev); if (mutex_lock_interruptible(&dev->mtx)) return -ERESTARTSYS; @@ -423,15 +424,15 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, /* verify that some data was requested */ if (count == 0) { - dbg(1," %s : read request of 0 bytes", __func__); + dbg(1, " %s : read request of 0 bytes", __func__); goto exit; } timeout = COMMAND_TIMEOUT; - dbg(2," %s : about to start looping", __func__); + dbg(2, " %s : about to start looping", __func__); while (bytes_to_read) { int data_in_secondary = dev->secondary_tail - dev->secondary_head; - dbg(2," %s : while, data_in_secondary=%d, status=%d", + dbg(2, " %s : while, data_in_secondary=%d, status=%d", __func__, data_in_secondary, dev->interrupt_in_urb->status); @@ -456,7 +457,7 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, if (dev->read_buffer_length) { /* we secure access to the primary */ char *tmp; - dbg(2," %s : swap, read_buffer_length = %d", + dbg(2, " %s : swap, read_buffer_length = %d", __func__, dev->read_buffer_length); tmp = dev->read_buffer_secondary; dev->read_buffer_secondary = dev->read_buffer_primary; @@ -472,16 +473,16 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, if (!dev->read_urb_finished) { /* somebody is doing IO */ spin_unlock_irqrestore(&dev->buflock, flags); - dbg(2," %s : submitted already", __func__); + dbg(2, " %s : submitted already", __func__); } else { /* we must initiate input */ - dbg(2," %s : initiate input", __func__); + dbg(2, " %s : initiate input", __func__); dev->read_urb_finished = 0; spin_unlock_irqrestore(&dev->buflock, flags); - usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, - usb_rcvintpipe(dev->udev, - dev->interrupt_in_endpoint->bEndpointAddress), + usb_fill_int_urb(dev->interrupt_in_urb, dev->udev, + usb_rcvintpipe(dev->udev, + dev->interrupt_in_endpoint->bEndpointAddress), dev->interrupt_in_buffer, usb_endpoint_maxp(dev->interrupt_in_endpoint), adu_interrupt_in_callback, @@ -493,7 +494,7 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, if (retval == -ENOMEM) { retval = bytes_read ? bytes_read : -ENOMEM; } - dbg(2," %s : submit failed", __func__); + dbg(2, " %s : submit failed", __func__); goto exit; } } @@ -512,13 +513,13 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, remove_wait_queue(&dev->read_wait, &wait); if (timeout <= 0) { - dbg(2," %s : timeout", __func__); + dbg(2, " %s : timeout", __func__); retval = bytes_read ? bytes_read : -ETIMEDOUT; goto exit; } if (signal_pending(current)) { - dbg(2," %s : signal pending", __func__); + dbg(2, " %s : signal pending", __func__); retval = bytes_read ? bytes_read : -EINTR; goto exit; } @@ -532,9 +533,9 @@ static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, if (should_submit && dev->read_urb_finished) { dev->read_urb_finished = 0; spin_unlock_irqrestore(&dev->buflock, flags); - usb_fill_int_urb(dev->interrupt_in_urb,dev->udev, + usb_fill_int_urb(dev->interrupt_in_urb, dev->udev, usb_rcvintpipe(dev->udev, - dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_endpoint->bEndpointAddress), dev->interrupt_in_buffer, usb_endpoint_maxp(dev->interrupt_in_endpoint), adu_interrupt_in_callback, @@ -551,7 +552,7 @@ exit: /* unlock the device */ mutex_unlock(&dev->mtx); - dbg(2," %s : leave, return value %d", __func__, retval); + dbg(2, " %s : leave, return value %d", __func__, retval); return retval; } @@ -566,7 +567,7 @@ static ssize_t adu_write(struct file *file, const __user char *buffer, unsigned long flags; int retval; - dbg(2," %s : enter, count = %Zd", __func__, count); + dbg(2, " %s : enter, count = %Zd", __func__, count); dev = file->private_data; @@ -584,7 +585,7 @@ static ssize_t adu_write(struct file *file, const __user char *buffer, /* verify that we actually have some data to write */ if (count == 0) { - dbg(1," %s : write request of 0 bytes", __func__); + dbg(1, " %s : write request of 0 bytes", __func__); goto exit; } @@ -597,7 +598,7 @@ static ssize_t adu_write(struct file *file, const __user char *buffer, mutex_unlock(&dev->mtx); if (signal_pending(current)) { - dbg(1," %s : interrupted", __func__); + dbg(1, " %s : interrupted", __func__); set_current_state(TASK_RUNNING); retval = -EINTR; goto exit_onqueue; @@ -614,17 +615,17 @@ static ssize_t adu_write(struct file *file, const __user char *buffer, goto exit_nolock; } - dbg(4," %s : in progress, count = %Zd", __func__, count); + dbg(4, " %s : in progress, count = %Zd", __func__, count); } else { spin_unlock_irqrestore(&dev->buflock, flags); set_current_state(TASK_RUNNING); remove_wait_queue(&dev->write_wait, &waita); - dbg(4," %s : sending, count = %Zd", __func__, count); + dbg(4, " %s : sending, count = %Zd", __func__, count); /* write the data into interrupt_out_buffer from userspace */ buffer_size = usb_endpoint_maxp(dev->interrupt_out_endpoint); bytes_to_write = count > buffer_size ? buffer_size : count; - dbg(4," %s : buffer_size = %Zd, count = %Zd, bytes_to_write = %Zd", + dbg(4, " %s : buffer_size = %Zd, count = %Zd, bytes_to_write = %Zd", __func__, buffer_size, count, bytes_to_write); if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write) != 0) { @@ -664,7 +665,7 @@ static ssize_t adu_write(struct file *file, const __user char *buffer, exit: mutex_unlock(&dev->mtx); exit_nolock: - dbg(2," %s : leave, return value %d", __func__, retval); + dbg(2, " %s : leave, return value %d", __func__, retval); return retval; exit_onqueue: @@ -710,7 +711,7 @@ static int adu_probe(struct usb_interface *interface, int out_end_size; int i; - dbg(2," %s : enter", __func__); + dbg(2, " %s : enter", __func__); if (udev == NULL) { dev_err(&interface->dev, "udev is NULL.\n"); @@ -811,7 +812,7 @@ static int adu_probe(struct usb_interface *interface, dev_err(&interface->dev, "Could not retrieve serial number\n"); goto error; } - dbg(2," %s : serial_number=%s", __func__, dev->serial_number); + dbg(2, " %s : serial_number=%s", __func__, dev->serial_number); /* we can register the device now, as it is ready */ usb_set_intfdata(interface, dev); @@ -832,7 +833,7 @@ static int adu_probe(struct usb_interface *interface, udev->descriptor.idProduct, dev->serial_number, (dev->minor - ADU_MINOR_BASE)); exit: - dbg(2," %s : leave, return value %p (dev)", __func__, dev); + dbg(2, " %s : leave, return value %p (dev)", __func__, dev); return retval; @@ -851,7 +852,7 @@ static void adu_disconnect(struct usb_interface *interface) struct adu_device *dev; int minor; - dbg(2," %s : enter", __func__); + dbg(2, " %s : enter", __func__); dev = usb_get_intfdata(interface); @@ -865,7 +866,7 @@ static void adu_disconnect(struct usb_interface *interface) usb_set_intfdata(interface, NULL); /* if the device is not opened, then we clean up right now */ - dbg(2," %s : open count %d", __func__, dev->open_count); + dbg(2, " %s : open count %d", __func__, dev->open_count); if (!dev->open_count) adu_delete(dev); @@ -874,7 +875,7 @@ static void adu_disconnect(struct usb_interface *interface) dev_info(&interface->dev, "ADU device adutux%d now disconnected\n", (minor - ADU_MINOR_BASE)); - dbg(2," %s : leave", __func__); + dbg(2, " %s : leave", __func__); } /* usb specific object needed to register this driver with the usb subsystem */ diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index d3a1cce..c357839 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -42,9 +42,6 @@ #define USB3503_NRD 0x09 #define USB3503_PDS 0x0a -#define USB3503_PORT1 (1 << 1) -#define USB3503_PORT2 (1 << 2) -#define USB3503_PORT3 (1 << 3) #define USB3503_SP_ILOCK 0xe7 #define USB3503_SPILOCK_CONNECT (1 << 1) @@ -56,6 +53,7 @@ struct usb3503 { enum usb3503_mode mode; struct i2c_client *client; + u8 port_off_mask; int gpio_intn; int gpio_reset; int gpio_connect; @@ -107,11 +105,9 @@ static int usb3503_reset(int gpio_reset, int state) if (gpio_is_valid(gpio_reset)) gpio_set_value(gpio_reset, state); - /* Wait RefClk when RESET_N is released, otherwise Hub will - * not transition to Hub Communication Stage. - */ + /* Wait T_HUBINIT == 4ms for hub logic to stabilize */ if (state) - msleep(100); + usleep_range(4000, 10000); return 0; } @@ -134,12 +130,14 @@ static int usb3503_switch_mode(struct usb3503 *hub, enum usb3503_mode mode) goto err_hubmode; } - /* PDS : Port2,3 Disable For Self Powered Operation */ - err = usb3503_set_bits(i2c, USB3503_PDS, - (USB3503_PORT2 | USB3503_PORT3)); - if (err < 0) { - dev_err(&i2c->dev, "PDS failed (%d)\n", err); - goto err_hubmode; + /* PDS : Disable For Self Powered Operation */ + if (hub->port_off_mask) { + err = usb3503_set_bits(i2c, USB3503_PDS, + hub->port_off_mask); + if (err < 0) { + dev_err(&i2c->dev, "PDS failed (%d)\n", err); + goto err_hubmode; + } } /* CFG1 : SELF_BUS_PWR -> Self-Powerd operation */ @@ -186,6 +184,8 @@ static int usb3503_probe(struct i2c_client *i2c, const struct i2c_device_id *id) struct usb3503 *hub; int err = -ENOMEM; u32 mode = USB3503_MODE_UNKNOWN; + const u32 *property; + int len; hub = kzalloc(sizeof(struct usb3503), GFP_KERNEL); if (!hub) { @@ -197,18 +197,31 @@ static int usb3503_probe(struct i2c_client *i2c, const struct i2c_device_id *id) hub->client = i2c; if (pdata) { + hub->port_off_mask = pdata->port_off_mask; hub->gpio_intn = pdata->gpio_intn; hub->gpio_connect = pdata->gpio_connect; hub->gpio_reset = pdata->gpio_reset; hub->mode = pdata->initial_mode; } else if (np) { + hub->port_off_mask = 0; + + property = of_get_property(np, "disabled-ports", &len); + if (property && (len / sizeof(u32)) > 0) { + int i; + for (i = 0; i < len / sizeof(u32); i++) { + u32 port = be32_to_cpu(property[i]); + if ((1 <= port) && (port <= 3)) + hub->port_off_mask |= (1 << port); + } + } + hub->gpio_intn = of_get_named_gpio(np, "connect-gpios", 0); if (hub->gpio_intn == -EPROBE_DEFER) return -EPROBE_DEFER; hub->gpio_connect = of_get_named_gpio(np, "intn-gpios", 0); if (hub->gpio_connect == -EPROBE_DEFER) return -EPROBE_DEFER; - hub->gpio_reset = of_get_named_gpio(np, "reset-gpios", 0); + hub->gpio_reset = of_get_named_gpio(np, "reset-gpios", 0); if (hub->gpio_reset == -EPROBE_DEFER) return -EPROBE_DEFER; of_property_read_u32(np, "initial-mode", &mode); diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 06f8d29..797e3fd 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -28,6 +28,35 @@ config USB_MUSB_HDRC if USB_MUSB_HDRC choice + bool "MUSB Mode Selection" + default USB_MUSB_DUAL_ROLE if (USB && USB_GADGET) + default USB_MUSB_HOST if (USB && !USB_GADGET) + default USB_MUSB_GADGET if (!USB && USB_GADGET) + +config USB_MUSB_HOST + bool "Host only mode" + depends on USB + help + Select this when you want to use MUSB in host mode only, + thereby the gadget feature will be regressed. + +config USB_MUSB_GADGET + bool "Gadget only mode" + depends on USB_GADGET + help + Select this when you want to use MUSB in gadget mode only, + thereby the host feature will be regressed. + +config USB_MUSB_DUAL_ROLE + bool "Dual Role mode" + depends on (USB && USB_GADGET) + help + This is the default mode of working of MUSB controller where + both host and gadget features are enabled. + +endchoice + +choice prompt "Platform Glue Layer" config USB_MUSB_DAVINCI diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile index 3b85871..2b82ed7 100644 --- a/drivers/usb/musb/Makefile +++ b/drivers/usb/musb/Makefile @@ -6,8 +6,8 @@ obj-$(CONFIG_USB_MUSB_HDRC) += musb_hdrc.o musb_hdrc-y := musb_core.o -musb_hdrc-y += musb_gadget_ep0.o musb_gadget.o -musb_hdrc-y += musb_virthub.o musb_host.o +musb_hdrc-$(CONFIG_USB_MUSB_HOST)$(CONFIG_USB_MUSB_DUAL_ROLE) += musb_virthub.o musb_host.o +musb_hdrc-$(CONFIG_USB_MUSB_GADGET)$(CONFIG_USB_MUSB_DUAL_ROLE) += musb_gadget_ep0.o musb_gadget.o musb_hdrc-$(CONFIG_DEBUG_FS) += musb_debugfs.o # Hardware Glue Layer diff --git a/drivers/usb/musb/blackfin.c b/drivers/usb/musb/blackfin.c index 5e63b16..6ba8439 100644 --- a/drivers/usb/musb/blackfin.c +++ b/drivers/usb/musb/blackfin.c @@ -450,6 +450,7 @@ static u64 bfin_dmamask = DMA_BIT_MASK(32); static int bfin_probe(struct platform_device *pdev) { + struct resource musb_resources[2]; struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; struct platform_device *musb; struct bfin_glue *glue; @@ -479,8 +480,21 @@ static int bfin_probe(struct platform_device *pdev) platform_set_drvdata(pdev, glue); - ret = platform_device_add_resources(musb, pdev->resource, - pdev->num_resources); + memset(musb_resources, 0x00, sizeof(*musb_resources) * + ARRAY_SIZE(musb_resources)); + + musb_resources[0].name = pdev->resource[0].name; + musb_resources[0].start = pdev->resource[0].start; + musb_resources[0].end = pdev->resource[0].end; + musb_resources[0].flags = pdev->resource[0].flags; + + musb_resources[1].name = pdev->resource[1].name; + musb_resources[1].start = pdev->resource[1].start; + musb_resources[1].end = pdev->resource[1].end; + musb_resources[1].flags = pdev->resource[1].flags; + + ret = platform_device_add_resources(musb, musb_resources, + ARRAY_SIZE(musb_resources)); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err3; diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c index b903b74..0da6f64 100644 --- a/drivers/usb/musb/da8xx.c +++ b/drivers/usb/musb/da8xx.c @@ -476,6 +476,7 @@ static u64 da8xx_dmamask = DMA_BIT_MASK(32); static int da8xx_probe(struct platform_device *pdev) { + struct resource musb_resources[2]; struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; struct platform_device *musb; struct da8xx_glue *glue; @@ -521,8 +522,21 @@ static int da8xx_probe(struct platform_device *pdev) platform_set_drvdata(pdev, glue); - ret = platform_device_add_resources(musb, pdev->resource, - pdev->num_resources); + memset(musb_resources, 0x00, sizeof(*musb_resources) * + ARRAY_SIZE(musb_resources)); + + musb_resources[0].name = pdev->resource[0].name; + musb_resources[0].start = pdev->resource[0].start; + musb_resources[0].end = pdev->resource[0].end; + musb_resources[0].flags = pdev->resource[0].flags; + + musb_resources[1].name = pdev->resource[1].name; + musb_resources[1].start = pdev->resource[1].start; + musb_resources[1].end = pdev->resource[1].end; + musb_resources[1].flags = pdev->resource[1].flags; + + ret = platform_device_add_resources(musb, musb_resources, + ARRAY_SIZE(musb_resources)); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err5; diff --git a/drivers/usb/musb/davinci.c b/drivers/usb/musb/davinci.c index bea6cc3..f8aeaf2 100644 --- a/drivers/usb/musb/davinci.c +++ b/drivers/usb/musb/davinci.c @@ -509,6 +509,7 @@ static u64 davinci_dmamask = DMA_BIT_MASK(32); static int davinci_probe(struct platform_device *pdev) { + struct resource musb_resources[2]; struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; struct platform_device *musb; struct davinci_glue *glue; @@ -553,8 +554,21 @@ static int davinci_probe(struct platform_device *pdev) platform_set_drvdata(pdev, glue); - ret = platform_device_add_resources(musb, pdev->resource, - pdev->num_resources); + memset(musb_resources, 0x00, sizeof(*musb_resources) * + ARRAY_SIZE(musb_resources)); + + musb_resources[0].name = pdev->resource[0].name; + musb_resources[0].start = pdev->resource[0].start; + musb_resources[0].end = pdev->resource[0].end; + musb_resources[0].flags = pdev->resource[0].flags; + + musb_resources[1].name = pdev->resource[1].name; + musb_resources[1].start = pdev->resource[1].start; + musb_resources[1].end = pdev->resource[1].end; + musb_resources[1].flags = pdev->resource[1].flags; + + ret = platform_device_add_resources(musb, musb_resources, + ARRAY_SIZE(musb_resources)); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err5; diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 37a261a..29a24ce 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -380,7 +380,6 @@ static void musb_otg_timer_func(unsigned long data) dev_dbg(musb->controller, "HNP: Unhandled mode %s\n", usb_otg_state_string(musb->xceiv->state)); } - musb->ignore_disconnect = 0; spin_unlock_irqrestore(&musb->lock, flags); } @@ -389,7 +388,7 @@ static void musb_otg_timer_func(unsigned long data) */ void musb_hnp_stop(struct musb *musb) { - struct usb_hcd *hcd = musb_to_hcd(musb); + struct usb_hcd *hcd = musb->hcd; void __iomem *mbase = musb->mregs; u8 reg; @@ -404,7 +403,8 @@ void musb_hnp_stop(struct musb *musb) break; case OTG_STATE_B_HOST: dev_dbg(musb->controller, "HNP: Disabling HR\n"); - hcd->self.is_b_host = 0; + if (hcd) + hcd->self.is_b_host = 0; musb->xceiv->state = OTG_STATE_B_PERIPHERAL; MUSB_DEV_MODE(musb); reg = musb_readb(mbase, MUSB_POWER); @@ -484,7 +484,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, musb->xceiv->state = OTG_STATE_A_HOST; musb->is_active = 1; - usb_hcd_resume_root_hub(musb_to_hcd(musb)); + musb_host_resume_root_hub(musb); break; case OTG_STATE_B_WAIT_ACON: musb->xceiv->state = OTG_STATE_B_PERIPHERAL; @@ -501,7 +501,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, case OTG_STATE_A_SUSPEND: /* possibly DISCONNECT is upcoming */ musb->xceiv->state = OTG_STATE_A_HOST; - usb_hcd_resume_root_hub(musb_to_hcd(musb)); + musb_host_resume_root_hub(musb); break; case OTG_STATE_B_WAIT_ACON: case OTG_STATE_B_PERIPHERAL: @@ -643,7 +643,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, * undesired detour through A_WAIT_BCON. */ musb_hnp_stop(musb); - usb_hcd_resume_root_hub(musb_to_hcd(musb)); + musb_host_resume_root_hub(musb); musb_root_disconnect(musb); musb_platform_try_idle(musb, jiffies + msecs_to_jiffies(musb->a_wait_bcon @@ -685,7 +685,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, } if (int_usb & MUSB_INTR_CONNECT) { - struct usb_hcd *hcd = musb_to_hcd(musb); + struct usb_hcd *hcd = musb->hcd; handled = IRQ_HANDLED; musb->is_active = 1; @@ -726,31 +726,27 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, dev_dbg(musb->controller, "HNP: CONNECT, now b_host\n"); b_host: musb->xceiv->state = OTG_STATE_B_HOST; - hcd->self.is_b_host = 1; - musb->ignore_disconnect = 0; + if (musb->hcd) + musb->hcd->self.is_b_host = 1; del_timer(&musb->otg_timer); break; default: if ((devctl & MUSB_DEVCTL_VBUS) == (3 << MUSB_DEVCTL_VBUS_SHIFT)) { musb->xceiv->state = OTG_STATE_A_HOST; - hcd->self.is_b_host = 0; + if (hcd) + hcd->self.is_b_host = 0; } break; } - /* poke the root hub */ - MUSB_HST_MODE(musb); - if (hcd->status_urb) - usb_hcd_poll_rh_status(hcd); - else - usb_hcd_resume_root_hub(hcd); + musb_host_poke_root_hub(musb); dev_dbg(musb->controller, "CONNECT (%s) devctl %02x\n", usb_otg_state_string(musb->xceiv->state), devctl); } - if ((int_usb & MUSB_INTR_DISCONNECT) && !musb->ignore_disconnect) { + if (int_usb & MUSB_INTR_DISCONNECT) { dev_dbg(musb->controller, "DISCONNECT (%s) as %s, devctl %02x\n", usb_otg_state_string(musb->xceiv->state), MUSB_MODE(musb), devctl); @@ -759,7 +755,7 @@ b_host: switch (musb->xceiv->state) { case OTG_STATE_A_HOST: case OTG_STATE_A_SUSPEND: - usb_hcd_resume_root_hub(musb_to_hcd(musb)); + musb_host_resume_root_hub(musb); musb_root_disconnect(musb); if (musb->a_wait_bcon != 0) musb_platform_try_idle(musb, jiffies @@ -772,7 +768,8 @@ b_host: * in hnp_stop() is currently not used... */ musb_root_disconnect(musb); - musb_to_hcd(musb)->self.is_b_host = 0; + if (musb->hcd) + musb->hcd->self.is_b_host = 0; musb->xceiv->state = OTG_STATE_B_PERIPHERAL; MUSB_DEV_MODE(musb); musb_g_disconnect(musb); @@ -818,11 +815,6 @@ b_host: usb_otg_state_string(musb->xceiv->state)); switch (musb->xceiv->state) { case OTG_STATE_A_SUSPEND: - /* We need to ignore disconnect on suspend - * otherwise tusb 2.0 won't reconnect after a - * power cycle, which breaks otg compliance. - */ - musb->ignore_disconnect = 1; musb_g_reset(musb); /* FALLTHROUGH */ case OTG_STATE_A_WAIT_BCON: /* OPT TD.4.7-900ms */ @@ -834,7 +826,6 @@ b_host: + msecs_to_jiffies(TA_WAIT_BCON(musb))); break; case OTG_STATE_A_PERIPHERAL: - musb->ignore_disconnect = 0; del_timer(&musb->otg_timer); musb_g_reset(musb); break; @@ -909,51 +900,6 @@ b_host: /*-------------------------------------------------------------------------*/ -/* -* Program the HDRC to start (enable interrupts, dma, etc.). -*/ -void musb_start(struct musb *musb) -{ - void __iomem *regs = musb->mregs; - u8 devctl = musb_readb(regs, MUSB_DEVCTL); - - dev_dbg(musb->controller, "<== devctl %02x\n", devctl); - - /* Set INT enable registers, enable interrupts */ - musb->intrtxe = musb->epmask; - musb_writew(regs, MUSB_INTRTXE, musb->intrtxe); - musb->intrrxe = musb->epmask & 0xfffe; - musb_writew(regs, MUSB_INTRRXE, musb->intrrxe); - musb_writeb(regs, MUSB_INTRUSBE, 0xf7); - - musb_writeb(regs, MUSB_TESTMODE, 0); - - /* put into basic highspeed mode and start session */ - musb_writeb(regs, MUSB_POWER, MUSB_POWER_ISOUPDATE - | MUSB_POWER_HSENAB - /* ENSUSPEND wedges tusb */ - /* | MUSB_POWER_ENSUSPEND */ - ); - - musb->is_active = 0; - devctl = musb_readb(regs, MUSB_DEVCTL); - devctl &= ~MUSB_DEVCTL_SESSION; - - /* session started after: - * (a) ID-grounded irq, host mode; - * (b) vbus present/connect IRQ, peripheral mode; - * (c) peripheral initiates, using SRP - */ - if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) - musb->is_active = 1; - else - devctl |= MUSB_DEVCTL_SESSION; - - musb_platform_enable(musb); - musb_writeb(regs, MUSB_DEVCTL, devctl); -} - - static void musb_generic_disable(struct musb *musb) { void __iomem *mbase = musb->mregs; @@ -1007,6 +953,7 @@ static void musb_shutdown(struct platform_device *pdev) pm_runtime_get_sync(musb->controller); + musb_host_cleanup(musb); musb_gadget_cleanup(musb); spin_lock_irqsave(&musb->lock, flags); @@ -1763,24 +1710,18 @@ static struct musb *allocate_instance(struct device *dev, struct musb *musb; struct musb_hw_ep *ep; int epnum; - struct usb_hcd *hcd; + int ret; - hcd = usb_create_hcd(&musb_hc_driver, dev, dev_name(dev)); - if (!hcd) + musb = devm_kzalloc(dev, sizeof(*musb), GFP_KERNEL); + if (!musb) return NULL; - /* usbcore sets dev->driver_data to hcd, and sometimes uses that... */ - musb = hcd_to_musb(hcd); INIT_LIST_HEAD(&musb->control); INIT_LIST_HEAD(&musb->in_bulk); INIT_LIST_HEAD(&musb->out_bulk); - hcd->uses_new_polling = 1; - hcd->has_tt = 1; - musb->vbuserr_retry = VBUSERR_RETRY_COUNT; musb->a_wait_bcon = OTG_TIME_A_WAIT_BCON; - dev_set_drvdata(dev, musb); musb->mregs = mbase; musb->ctrl_base = mbase; musb->nIrq = -ENODEV; @@ -1795,7 +1736,16 @@ static struct musb *allocate_instance(struct device *dev, musb->controller = dev; + ret = musb_host_alloc(musb); + if (ret < 0) + goto err_free; + + dev_set_drvdata(dev, musb); + return musb; + +err_free: + return NULL; } static void musb_free(struct musb *musb) @@ -1821,7 +1771,7 @@ static void musb_free(struct musb *musb) dma_controller_destroy(c); } - usb_put_hcd(musb_to_hcd(musb)); + musb_host_free(musb); } /* @@ -1838,7 +1788,6 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) int status; struct musb *musb; struct musb_hdrc_platform_data *plat = dev->platform_data; - struct usb_hcd *hcd; /* The driver might handle more features than the board; OK. * Fail when the board needs a feature that's not enabled. @@ -1864,6 +1813,7 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb->board_set_power = plat->set_power; musb->min_power = plat->min_power; musb->ops = plat->platform_ops; + musb->port_mode = plat->mode; /* The musb_platform_init() call: * - adjusts musb->mregs @@ -1939,13 +1889,6 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb->irq_wake = 0; } - /* host side needs more setup */ - hcd = musb_to_hcd(musb); - otg_set_host(musb->xceiv->otg, &hcd->self); - hcd->self.otg_port = 1; - musb->xceiv->otg->host = &hcd->self; - hcd->power_budget = 2 * (plat->power ? : 250); - /* program PHY to use external vBus if required */ if (plat->extvbus) { u8 busctl = musb_read_ulpi_buscontrol(musb->mregs); @@ -1961,7 +1904,23 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb->xceiv->state = OTG_STATE_B_IDLE; } - status = musb_gadget_setup(musb); + switch (musb->port_mode) { + case MUSB_PORT_MODE_HOST: + status = musb_host_setup(musb, plat->power); + break; + case MUSB_PORT_MODE_GADGET: + status = musb_gadget_setup(musb); + break; + case MUSB_PORT_MODE_DUAL_ROLE: + status = musb_host_setup(musb, plat->power); + if (status < 0) + goto fail3; + status = musb_gadget_setup(musb); + break; + default: + dev_err(dev, "unsupported port mode %d\n", musb->port_mode); + break; + } if (status < 0) goto fail3; diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 7fb4819..7d341c3 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -77,28 +77,17 @@ struct musb_ep; #define is_peripheral_active(m) (!(m)->is_host) #define is_host_active(m) ((m)->is_host) +enum { + MUSB_PORT_MODE_HOST = 1, + MUSB_PORT_MODE_GADGET, + MUSB_PORT_MODE_DUAL_ROLE, +}; + #ifdef CONFIG_PROC_FS #include <linux/fs.h> #define MUSB_CONFIG_PROC_FS #endif -/****************************** PERIPHERAL ROLE *****************************/ - -extern irqreturn_t musb_g_ep0_irq(struct musb *); -extern void musb_g_tx(struct musb *, u8); -extern void musb_g_rx(struct musb *, u8); -extern void musb_g_reset(struct musb *); -extern void musb_g_suspend(struct musb *); -extern void musb_g_resume(struct musb *); -extern void musb_g_wakeup(struct musb *); -extern void musb_g_disconnect(struct musb *); - -/****************************** HOST ROLE ***********************************/ - -extern irqreturn_t musb_h_ep0_irq(struct musb *); -extern void musb_host_tx(struct musb *, u8); -extern void musb_host_rx(struct musb *, u8); - /****************************** CONSTANTS ********************************/ #ifndef MUSB_C_NUM_EPS @@ -373,6 +362,7 @@ struct musb { u8 min_power; /* vbus for periph, in mA/2 */ + int port_mode; /* MUSB_PORT_MODE_* */ bool is_host; int a_wait_bcon; /* VBUS timeout in msecs */ @@ -382,7 +372,6 @@ struct musb { unsigned is_active:1; unsigned is_multipoint:1; - unsigned ignore_disconnect:1; /* during bus resets */ unsigned hb_iso_rx:1; /* high bandwidth iso rx? */ unsigned hb_iso_tx:1; /* high bandwidth iso tx? */ @@ -419,6 +408,7 @@ struct musb { enum musb_g_ep0_state ep0_state; struct usb_gadget g; /* the gadget */ struct usb_gadget_driver *gadget_driver; /* its driver */ + struct usb_hcd *hcd; /* the usb hcd */ /* * FIXME: Remove this flag. @@ -520,7 +510,6 @@ static inline void musb_configure_ep0(struct musb *musb) extern const char musb_driver_name[]; -extern void musb_start(struct musb *musb); extern void musb_stop(struct musb *musb); extern void musb_write_fifo(struct musb_hw_ep *ep, u16 len, const u8 *src); diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index ba70923..0414bc1 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1820,7 +1820,6 @@ static int musb_gadget_start(struct usb_gadget *g, { struct musb *musb = gadget_to_musb(g); struct usb_otg *otg = musb->xceiv->otg; - struct usb_hcd *hcd = musb_to_hcd(musb); unsigned long flags; int retval = 0; @@ -1847,17 +1846,9 @@ static int musb_gadget_start(struct usb_gadget *g, * handles power budgeting ... this way also * ensures HdrcStart is indirectly called. */ - retval = usb_add_hcd(hcd, 0, 0); - if (retval < 0) { - dev_dbg(musb->controller, "add_hcd failed, %d\n", retval); - goto err; - } - if (musb->xceiv->last_event == USB_EVENT_ID) musb_platform_set_vbus(musb, 1); - hcd->self.uses_pio_for_control = 1; - if (musb->xceiv->last_event == USB_EVENT_NONE) pm_runtime_put(musb->controller); @@ -1942,7 +1933,6 @@ static int musb_gadget_stop(struct usb_gadget *g, musb_platform_try_idle(musb, 0); spin_unlock_irqrestore(&musb->lock, flags); - usb_remove_hcd(musb_to_hcd(musb)); /* * FIXME we need to be able to register another * gadget driver here and have everything work; diff --git a/drivers/usb/musb/musb_gadget.h b/drivers/usb/musb/musb_gadget.h index 66b7c5e..0314dfc 100644 --- a/drivers/usb/musb/musb_gadget.h +++ b/drivers/usb/musb/musb_gadget.h @@ -37,6 +37,38 @@ #include <linux/list.h> +#if IS_ENABLED(CONFIG_USB_MUSB_GADGET) || IS_ENABLED(CONFIG_USB_MUSB_DUAL_ROLE) +extern irqreturn_t musb_g_ep0_irq(struct musb *); +extern void musb_g_tx(struct musb *, u8); +extern void musb_g_rx(struct musb *, u8); +extern void musb_g_reset(struct musb *); +extern void musb_g_suspend(struct musb *); +extern void musb_g_resume(struct musb *); +extern void musb_g_wakeup(struct musb *); +extern void musb_g_disconnect(struct musb *); +extern void musb_gadget_cleanup(struct musb *); +extern int musb_gadget_setup(struct musb *); + +#else +static inline irqreturn_t musb_g_ep0_irq(struct musb *musb) +{ + return 0; +} + +static inline void musb_g_tx(struct musb *musb, u8 epnum) {} +static inline void musb_g_rx(struct musb *musb, u8 epnum) {} +static inline void musb_g_reset(struct musb *musb) {} +static inline void musb_g_suspend(struct musb *musb) {} +static inline void musb_g_resume(struct musb *musb) {} +static inline void musb_g_wakeup(struct musb *musb) {} +static inline void musb_g_disconnect(struct musb *musb) {} +static inline void musb_gadget_cleanup(struct musb *musb) {} +static inline int musb_gadget_setup(struct musb *musb) +{ + return 0; +} +#endif + enum buffer_map_state { UN_MAPPED = 0, PRE_MAPPED, @@ -106,14 +138,8 @@ static inline struct musb_request *next_request(struct musb_ep *ep) return container_of(queue->next, struct musb_request, list); } -extern void musb_g_tx(struct musb *musb, u8 epnum); -extern void musb_g_rx(struct musb *musb, u8 epnum); - extern const struct usb_ep_ops musb_g_ep0_ops; -extern int musb_gadget_setup(struct musb *); -extern void musb_gadget_cleanup(struct musb *); - extern void musb_g_giveback(struct musb_ep *, struct usb_request *, int); extern void musb_ep_restart(struct musb *, struct musb_request *); diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index 9d3044b..a9695f5 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -46,7 +46,6 @@ #include "musb_core.h" #include "musb_host.h" - /* MUSB HOST status 22-mar-2006 * * - There's still lots of partial code duplication for fault paths, so @@ -96,6 +95,11 @@ * of transfers between endpoints, or anything clever. */ +struct musb *hcd_to_musb(struct usb_hcd *hcd) +{ + return *(struct musb **) hcd->hcd_priv; +} + static void musb_ep_program(struct musb *musb, u8 epnum, struct urb *urb, int is_out, @@ -269,8 +273,7 @@ musb_start_urb(struct musb *musb, int is_in, struct musb_qh *qh) /* FIXME this doesn't implement that scheduling policy ... * or handle framecounter wrapping */ - if ((urb->transfer_flags & URB_ISO_ASAP) - || (frame >= urb->start_frame)) { + if (1) { /* Always assume URB_ISO_ASAP */ /* REVISIT the SOF irq handler shouldn't duplicate * this code; and we don't init urb->start_frame... */ @@ -311,9 +314,9 @@ __acquires(musb->lock) urb->actual_length, urb->transfer_buffer_length ); - usb_hcd_unlink_urb_from_ep(musb_to_hcd(musb), urb); + usb_hcd_unlink_urb_from_ep(musb->hcd, urb); spin_unlock(&musb->lock); - usb_hcd_giveback_urb(musb_to_hcd(musb), urb, status); + usb_hcd_giveback_urb(musb->hcd, urb, status); spin_lock(&musb->lock); } @@ -625,7 +628,7 @@ static bool musb_tx_dma_program(struct dma_controller *dma, u16 csr; u8 mode; -#ifdef CONFIG_USB_INVENTRA_DMA +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) if (length > channel->max_len) length = channel->max_len; @@ -1455,7 +1458,7 @@ done: if (length > qh->maxpacket) length = qh->maxpacket; /* Unmap the buffer so that CPU can use it */ - usb_hcd_unmap_urb_for_dma(musb_to_hcd(musb), urb); + usb_hcd_unmap_urb_for_dma(musb->hcd, urb); /* * We need to map sg if the transfer_buffer is @@ -1657,7 +1660,7 @@ void musb_host_rx(struct musb *musb, u8 epnum) /* FIXME this is _way_ too much in-line logic for Mentor DMA */ -#ifndef CONFIG_USB_INVENTRA_DMA +#if !defined(CONFIG_USB_INVENTRA_DMA) && !defined(CONFIG_USB_UX500_DMA) if (rx_csr & MUSB_RXCSR_H_REQPKT) { /* REVISIT this happened for a while on some short reads... * the cleanup still needs investigation... looks bad... @@ -1689,7 +1692,7 @@ void musb_host_rx(struct musb *musb, u8 epnum) | MUSB_RXCSR_RXPKTRDY); musb_writew(hw_ep->regs, MUSB_RXCSR, val); -#ifdef CONFIG_USB_INVENTRA_DMA +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) if (usb_pipeisoc(pipe)) { struct usb_iso_packet_descriptor *d; @@ -1745,7 +1748,7 @@ void musb_host_rx(struct musb *musb, u8 epnum) } /* we are expecting IN packets */ -#ifdef CONFIG_USB_INVENTRA_DMA +#if defined(CONFIG_USB_INVENTRA_DMA) || defined(CONFIG_USB_UX500_DMA) if (dma) { struct dma_controller *c; u16 rx_count; @@ -1754,10 +1757,10 @@ void musb_host_rx(struct musb *musb, u8 epnum) rx_count = musb_readw(epio, MUSB_RXCOUNT); - dev_dbg(musb->controller, "RX%d count %d, buffer 0x%x len %d/%d\n", + dev_dbg(musb->controller, "RX%d count %d, buffer 0x%llx len %d/%d\n", epnum, rx_count, - urb->transfer_dma - + urb->actual_length, + (unsigned long long) urb->transfer_dma + + urb->actual_length, qh->offset, urb->transfer_buffer_length); @@ -1869,7 +1872,7 @@ void musb_host_rx(struct musb *musb, u8 epnum) unsigned int received_len; /* Unmap the buffer so that CPU can use it */ - usb_hcd_unmap_urb_for_dma(musb_to_hcd(musb), urb); + usb_hcd_unmap_urb_for_dma(musb->hcd, urb); /* * We need to map sg if the transfer_buffer is @@ -2463,7 +2466,6 @@ static int musb_bus_resume(struct usb_hcd *hcd) return 0; } - #ifndef CONFIG_MUSB_PIO_ONLY #define MUSB_USB_DMA_ALIGN 4 @@ -2575,10 +2577,10 @@ static void musb_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) } #endif /* !CONFIG_MUSB_PIO_ONLY */ -const struct hc_driver musb_hc_driver = { +static const struct hc_driver musb_hc_driver = { .description = "musb-hcd", .product_desc = "MUSB HDRC host driver", - .hcd_priv_size = sizeof(struct musb), + .hcd_priv_size = sizeof(struct musb *), .flags = HCD_USB2 | HCD_MEMORY, /* not using irq handler or reset hooks from usbcore, since @@ -2606,3 +2608,66 @@ const struct hc_driver musb_hc_driver = { /* .start_port_reset = NULL, */ /* .hub_irq_enable = NULL, */ }; + +int musb_host_alloc(struct musb *musb) +{ + struct device *dev = musb->controller; + + /* usbcore sets dev->driver_data to hcd, and sometimes uses that... */ + musb->hcd = usb_create_hcd(&musb_hc_driver, dev, dev_name(dev)); + if (!musb->hcd) + return -EINVAL; + + *musb->hcd->hcd_priv = (unsigned long) musb; + musb->hcd->self.uses_pio_for_control = 1; + musb->hcd->uses_new_polling = 1; + musb->hcd->has_tt = 1; + + return 0; +} + +void musb_host_cleanup(struct musb *musb) +{ + usb_remove_hcd(musb->hcd); + musb->hcd = NULL; +} + +void musb_host_free(struct musb *musb) +{ + usb_put_hcd(musb->hcd); +} + +int musb_host_setup(struct musb *musb, int power_budget) +{ + int ret; + struct usb_hcd *hcd = musb->hcd; + + MUSB_HST_MODE(musb); + musb->xceiv->otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_IDLE; + + otg_set_host(musb->xceiv->otg, &hcd->self); + hcd->self.otg_port = 1; + musb->xceiv->otg->host = &hcd->self; + hcd->power_budget = 2 * (power_budget ? : 250); + + ret = usb_add_hcd(hcd, 0, 0); + if (ret < 0) + return ret; + + return 0; +} + +void musb_host_resume_root_hub(struct musb *musb) +{ + usb_hcd_resume_root_hub(musb->hcd); +} + +void musb_host_poke_root_hub(struct musb *musb) +{ + MUSB_HST_MODE(musb); + if (musb->hcd->status_urb) + usb_hcd_poll_rh_status(musb->hcd); + else + usb_hcd_resume_root_hub(musb->hcd); +} diff --git a/drivers/usb/musb/musb_host.h b/drivers/usb/musb/musb_host.h index 738f7eb..960d735 100644 --- a/drivers/usb/musb/musb_host.h +++ b/drivers/usb/musb/musb_host.h @@ -37,16 +37,6 @@ #include <linux/scatterlist.h> -static inline struct usb_hcd *musb_to_hcd(struct musb *musb) -{ - return container_of((void *) musb, struct usb_hcd, hcd_priv); -} - -static inline struct musb *hcd_to_musb(struct usb_hcd *hcd) -{ - return (struct musb *) (hcd->hcd_priv); -} - /* stored in "usb_host_endpoint.hcpriv" for scheduled endpoints */ struct musb_qh { struct usb_host_endpoint *hep; /* usbcore info */ @@ -86,7 +76,52 @@ static inline struct musb_qh *first_qh(struct list_head *q) } +#if IS_ENABLED(CONFIG_USB_MUSB_HOST) || IS_ENABLED(CONFIG_USB_MUSB_DUAL_ROLE) +extern struct musb *hcd_to_musb(struct usb_hcd *); +extern irqreturn_t musb_h_ep0_irq(struct musb *); +extern int musb_host_alloc(struct musb *); +extern int musb_host_setup(struct musb *, int); +extern void musb_host_cleanup(struct musb *); +extern void musb_host_tx(struct musb *, u8); +extern void musb_host_rx(struct musb *, u8); +extern void musb_root_disconnect(struct musb *musb); +extern void musb_host_free(struct musb *); +extern void musb_host_cleanup(struct musb *); +extern void musb_host_tx(struct musb *, u8); +extern void musb_host_rx(struct musb *, u8); extern void musb_root_disconnect(struct musb *musb); +extern void musb_host_resume_root_hub(struct musb *musb); +extern void musb_host_poke_root_hub(struct musb *musb); +#else +static inline struct musb *hcd_to_musb(struct usb_hcd *hcd) +{ + return NULL; +} + +static inline irqreturn_t musb_h_ep0_irq(struct musb *musb) +{ + return 0; +} + +static inline int musb_host_alloc(struct musb *musb) +{ + return 0; +} + +static inline int musb_host_setup(struct musb *musb, int power_budget) +{ + return 0; +} + +static inline void musb_host_cleanup(struct musb *musb) {} +static inline void musb_host_free(struct musb *musb) {} +static inline void musb_host_tx(struct musb *musb, u8 epnum) {} +static inline void musb_host_rx(struct musb *musb, u8 epnum) {} +static inline void musb_root_disconnect(struct musb *musb) {} +static inline void musb_host_resume_root_hub(struct musb *musb) {} +static inline void musb_host_poll_rh_status(struct musb *musb) {} +static inline void musb_host_poke_root_hub(struct musb *musb) {} +#endif struct usb_hcd; @@ -95,8 +130,6 @@ extern int musb_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); -extern const struct hc_driver musb_hc_driver; - static inline struct urb *next_urb(struct musb_qh *qh) { struct list_head *queue; diff --git a/drivers/usb/musb/musb_virthub.c b/drivers/usb/musb/musb_virthub.c index ef7d110..a523950 100644 --- a/drivers/usb/musb/musb_virthub.c +++ b/drivers/usb/musb/musb_virthub.c @@ -44,6 +44,51 @@ #include "musb_core.h" +/* +* Program the HDRC to start (enable interrupts, dma, etc.). +*/ +static void musb_start(struct musb *musb) +{ + void __iomem *regs = musb->mregs; + u8 devctl = musb_readb(regs, MUSB_DEVCTL); + + dev_dbg(musb->controller, "<== devctl %02x\n", devctl); + + /* Set INT enable registers, enable interrupts */ + musb->intrtxe = musb->epmask; + musb_writew(regs, MUSB_INTRTXE, musb->intrtxe); + musb->intrrxe = musb->epmask & 0xfffe; + musb_writew(regs, MUSB_INTRRXE, musb->intrrxe); + musb_writeb(regs, MUSB_INTRUSBE, 0xf7); + + musb_writeb(regs, MUSB_TESTMODE, 0); + + /* put into basic highspeed mode and start session */ + musb_writeb(regs, MUSB_POWER, MUSB_POWER_ISOUPDATE + | MUSB_POWER_HSENAB + /* ENSUSPEND wedges tusb */ + /* | MUSB_POWER_ENSUSPEND */ + ); + + musb->is_active = 0; + devctl = musb_readb(regs, MUSB_DEVCTL); + devctl &= ~MUSB_DEVCTL_SESSION; + + /* session started after: + * (a) ID-grounded irq, host mode; + * (b) vbus present/connect IRQ, peripheral mode; + * (c) peripheral initiates, using SRP + */ + if (musb->port_mode != MUSB_PORT_MODE_HOST && + (devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) { + musb->is_active = 1; + } else { + devctl |= MUSB_DEVCTL_SESSION; + } + + musb_platform_enable(musb); + musb_writeb(regs, MUSB_DEVCTL, devctl); +} static void musb_port_suspend(struct musb *musb, bool do_suspend) { @@ -145,7 +190,6 @@ static void musb_port_reset(struct musb *musb, bool do_reset) msleep(1); } - musb->ignore_disconnect = true; power &= 0xf0; musb_writeb(mbase, MUSB_POWER, power | MUSB_POWER_RESET); @@ -158,8 +202,6 @@ static void musb_port_reset(struct musb *musb, bool do_reset) musb_writeb(mbase, MUSB_POWER, power & ~MUSB_POWER_RESET); - musb->ignore_disconnect = false; - power = musb_readb(mbase, MUSB_POWER); if (power & MUSB_POWER_HSMODE) { dev_dbg(musb->controller, "high-speed device connected\n"); @@ -170,7 +212,7 @@ static void musb_port_reset(struct musb *musb, bool do_reset) musb->port1_status |= USB_PORT_STAT_ENABLE | (USB_PORT_STAT_C_RESET << 16) | (USB_PORT_STAT_C_ENABLE << 16); - usb_hcd_poll_rh_status(musb_to_hcd(musb)); + usb_hcd_poll_rh_status(musb->hcd); musb->vbuserr_retry = VBUSERR_RETRY_COUNT; } @@ -183,7 +225,7 @@ void musb_root_disconnect(struct musb *musb) musb->port1_status = USB_PORT_STAT_POWER | (USB_PORT_STAT_C_CONNECTION << 16); - usb_hcd_poll_rh_status(musb_to_hcd(musb)); + usb_hcd_poll_rh_status(musb->hcd); musb->is_active = 0; switch (musb->xceiv->state) { @@ -337,7 +379,7 @@ int musb_hub_control( musb->port1_status &= ~(USB_PORT_STAT_SUSPEND | MUSB_PORT_STAT_RESUME); musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16; - usb_hcd_poll_rh_status(musb_to_hcd(musb)); + usb_hcd_poll_rh_status(musb->hcd); /* NOTE: it might really be A_WAIT_BCON ... */ musb->xceiv->state = OTG_STATE_A_HOST; } diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 628b93f..c7c1d7a 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -87,7 +87,7 @@ static void musb_do_idle(unsigned long _musb) musb->port1_status &= ~(USB_PORT_STAT_SUSPEND | MUSB_PORT_STAT_RESUME); musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16; - usb_hcd_poll_rh_status(musb_to_hcd(musb)); + usb_hcd_poll_rh_status(musb->hcd); /* NOTE: it might really be A_WAIT_BCON ... */ musb->xceiv->state = OTG_STATE_A_HOST; } @@ -481,6 +481,7 @@ static u64 omap2430_dmamask = DMA_BIT_MASK(32); static int omap2430_probe(struct platform_device *pdev) { + struct resource musb_resouces[2]; struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; struct omap_musb_board_data *data; struct platform_device *musb; @@ -567,8 +568,21 @@ static int omap2430_probe(struct platform_device *pdev) INIT_WORK(&glue->omap_musb_mailbox_work, omap_musb_mailbox_work); - ret = platform_device_add_resources(musb, pdev->resource, - pdev->num_resources); + memset(musb_resouces, 0x00, sizeof(*musb_resources) * + ARRAY_SIZE(musb_resources)); + + musb_resources[0].name = pdev->resource[0].name; + musb_resources[0].start = pdev->resource[0].start; + musb_resources[0].end = pdev->resource[0].end; + musb_resources[0].flags = pdev->resource[0].flags; + + musb_resources[1].name = pdev->resource[1].name; + musb_resources[1].start = pdev->resource[1].start; + musb_resources[1].end = pdev->resource[1].end; + musb_resources[1].flags = pdev->resource[1].flags; + + ret = platform_device_add_resources(musb, musb_resources, + ARRAY_SIZE(musb_resources)); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err2; diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index 7369ba3..2c06a89 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -1156,6 +1156,7 @@ static u64 tusb_dmamask = DMA_BIT_MASK(32); static int tusb_probe(struct platform_device *pdev) { + struct resource musb_resources[2]; struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; struct platform_device *musb; struct tusb6010_glue *glue; @@ -1185,8 +1186,21 @@ static int tusb_probe(struct platform_device *pdev) platform_set_drvdata(pdev, glue); - ret = platform_device_add_resources(musb, pdev->resource, - pdev->num_resources); + memset(musb_resources, 0x00, sizeof(*musb_resources) * + ARRAY_SIZE(musb_resources)); + + musb_resources[0].name = pdev->resource[0].name; + musb_resources[0].start = pdev->resource[0].start; + musb_resources[0].end = pdev->resource[0].end; + musb_resources[0].flags = pdev->resource[0].flags; + + musb_resources[1].name = pdev->resource[1].name; + musb_resources[1].start = pdev->resource[1].start; + musb_resources[1].end = pdev->resource[1].end; + musb_resources[1].flags = pdev->resource[1].flags; + + ret = platform_device_add_resources(musb, musb_resources, + ARRAY_SIZE(musb_resources)); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err3; diff --git a/drivers/usb/musb/ux500.c b/drivers/usb/musb/ux500.c index 2c80004..028ff4d 100644 --- a/drivers/usb/musb/ux500.c +++ b/drivers/usb/musb/ux500.c @@ -189,6 +189,7 @@ static const struct musb_platform_ops ux500_ops = { static int ux500_probe(struct platform_device *pdev) { + struct resource musb_resources[2]; struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; struct platform_device *musb; struct ux500_glue *glue; @@ -232,8 +233,21 @@ static int ux500_probe(struct platform_device *pdev) platform_set_drvdata(pdev, glue); - ret = platform_device_add_resources(musb, pdev->resource, - pdev->num_resources); + memset(musb_resources, 0x00, sizeof(*musb_resources) * + ARRAY_SIZE(musb_resources)); + + musb_resources[0].name = pdev->resource[0].name; + musb_resources[0].start = pdev->resource[0].start; + musb_resources[0].end = pdev->resource[0].end; + musb_resources[0].flags = pdev->resource[0].flags; + + musb_resources[1].name = pdev->resource[1].name; + musb_resources[1].start = pdev->resource[1].start; + musb_resources[1].end = pdev->resource[1].end; + musb_resources[1].flags = pdev->resource[1].flags; + + ret = platform_device_add_resources(musb, musb_resources, + ARRAY_SIZE(musb_resources)); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err5; diff --git a/drivers/usb/musb/ux500_dma.c b/drivers/usb/musb/ux500_dma.c index 3381206..63e7c8a 100644 --- a/drivers/usb/musb/ux500_dma.c +++ b/drivers/usb/musb/ux500_dma.c @@ -71,8 +71,7 @@ static void ux500_dma_callback(void *private_data) spin_lock_irqsave(&musb->lock, flags); ux500_channel->channel.actual_len = ux500_channel->cur_len; ux500_channel->channel.status = MUSB_DMA_STATUS_FREE; - musb_dma_completion(musb, hw_ep->epnum, - ux500_channel->is_tx); + musb_dma_completion(musb, hw_ep->epnum, ux500_channel->is_tx); spin_unlock_irqrestore(&musb->lock, flags); } @@ -366,7 +365,8 @@ void dma_controller_destroy(struct dma_controller *c) kfree(controller); } -struct dma_controller *dma_controller_create(struct musb *musb, void __iomem *base) +struct dma_controller *dma_controller_create(struct musb *musb, + void __iomem *base) { struct ux500_dma_controller *controller; struct platform_device *pdev = to_platform_device(musb->controller); diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 7ef3eb8..37e747a 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -86,7 +86,7 @@ config OMAP_USB3 on/off the PHY. config SAMSUNG_USBPHY - tristate "Samsung USB PHY Driver" + tristate help Enable this to support Samsung USB phy helper driver for Samsung SoCs. This driver provides common interface to interact, for Samsung USB 2.0 PHY diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c index e5eb1b5..0874023 100644 --- a/drivers/usb/phy/phy-ab8500-usb.c +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -1,10 +1,12 @@ /* * drivers/usb/otg/ab8500_usb.c * - * USB transceiver driver for AB8500 chip + * USB transceiver driver for AB8500 family chips * - * Copyright (C) 2010 ST-Ericsson AB + * Copyright (C) 2010-2013 ST-Ericsson AB * Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> + * Avinash Kumar <avinash.kumar@stericsson.com> + * Thirupathi Chippakurthy <thirupathi.chippakurthy@stericsson.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +31,8 @@ #include <linux/notifier.h> #include <linux/interrupt.h> #include <linux/delay.h> +#include <linux/clk.h> +#include <linux/err.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> #include <linux/usb/musb-ux500.h> @@ -41,21 +45,34 @@ /* Bank AB8500_USB */ #define AB8500_USB_LINE_STAT_REG 0x80 #define AB8505_USB_LINE_STAT_REG 0x94 +#define AB8540_USB_LINK_STAT_REG 0x94 +#define AB9540_USB_LINK_STAT_REG 0x94 +#define AB8540_USB_OTG_CTL_REG 0x87 #define AB8500_USB_PHY_CTRL_REG 0x8A +#define AB8540_VBUS_CTRL_REG 0x82 /* Bank AB8500_DEVELOPMENT */ #define AB8500_BANK12_ACCESS 0x00 /* Bank AB8500_DEBUG */ +#define AB8540_DEBUG 0x32 #define AB8500_USB_PHY_TUNE1 0x05 #define AB8500_USB_PHY_TUNE2 0x06 #define AB8500_USB_PHY_TUNE3 0x07 +/* Bank AB8500_INTERRUPT */ +#define AB8500_IT_SOURCE2_REG 0x01 + #define AB8500_BIT_OTG_STAT_ID (1 << 0) #define AB8500_BIT_PHY_CTRL_HOST_EN (1 << 0) #define AB8500_BIT_PHY_CTRL_DEVICE_EN (1 << 1) #define AB8500_BIT_WD_CTRL_ENABLE (1 << 0) #define AB8500_BIT_WD_CTRL_KICK (1 << 1) +#define AB8500_BIT_SOURCE2_VBUSDET (1 << 7) +#define AB8540_BIT_OTG_CTL_VBUS_VALID_ENA (1 << 0) +#define AB8540_BIT_OTG_CTL_ID_HOST_ENA (1 << 1) +#define AB8540_BIT_OTG_CTL_ID_DEV_ENA (1 << 5) +#define AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA (1 << 0) #define AB8500_WD_KICK_DELAY_US 100 /* usec */ #define AB8500_WD_V11_DISABLE_DELAY_US 100 /* usec */ @@ -112,6 +129,68 @@ enum ab8505_usb_link_status { USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8505, }; +enum ab8540_usb_link_status { + USB_LINK_NOT_CONFIGURED_8540 = 0, + USB_LINK_STD_HOST_NC_8540, + USB_LINK_STD_HOST_C_NS_8540, + USB_LINK_STD_HOST_C_S_8540, + USB_LINK_CDP_8540, + USB_LINK_RESERVED0_8540, + USB_LINK_RESERVED1_8540, + USB_LINK_DEDICATED_CHG_8540, + USB_LINK_ACA_RID_A_8540, + USB_LINK_ACA_RID_B_8540, + USB_LINK_ACA_RID_C_NM_8540, + USB_LINK_RESERVED2_8540, + USB_LINK_RESERVED3_8540, + USB_LINK_HM_IDGND_8540, + USB_LINK_CHARGERPORT_NOT_OK_8540, + USB_LINK_CHARGER_DM_HIGH_8540, + USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8540, + USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_8540, + USB_LINK_STD_UPSTREAM_8540, + USB_LINK_CHARGER_SE1_8540, + USB_LINK_CARKIT_CHGR_1_8540, + USB_LINK_CARKIT_CHGR_2_8540, + USB_LINK_ACA_DOCK_CHGR_8540, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_8540, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_8540, + USB_LINK_SAMSUNG_UART_CBL_PHY_EN_8540, + USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_8540, + USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_8540 +}; + +enum ab9540_usb_link_status { + USB_LINK_NOT_CONFIGURED_9540 = 0, + USB_LINK_STD_HOST_NC_9540, + USB_LINK_STD_HOST_C_NS_9540, + USB_LINK_STD_HOST_C_S_9540, + USB_LINK_CDP_9540, + USB_LINK_RESERVED0_9540, + USB_LINK_RESERVED1_9540, + USB_LINK_DEDICATED_CHG_9540, + USB_LINK_ACA_RID_A_9540, + USB_LINK_ACA_RID_B_9540, + USB_LINK_ACA_RID_C_NM_9540, + USB_LINK_RESERVED2_9540, + USB_LINK_RESERVED3_9540, + USB_LINK_HM_IDGND_9540, + USB_LINK_CHARGERPORT_NOT_OK_9540, + USB_LINK_CHARGER_DM_HIGH_9540, + USB_LINK_PHYEN_NO_VBUS_NO_IDGND_9540, + USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_9540, + USB_LINK_STD_UPSTREAM_9540, + USB_LINK_CHARGER_SE1_9540, + USB_LINK_CARKIT_CHGR_1_9540, + USB_LINK_CARKIT_CHGR_2_9540, + USB_LINK_ACA_DOCK_CHGR_9540, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_EN_9540, + USB_LINK_SAMSUNG_BOOT_CBL_PHY_DISB_9540, + USB_LINK_SAMSUNG_UART_CBL_PHY_EN_9540, + USB_LINK_SAMSUNG_UART_CBL_PHY_DISB_9540, + USB_LINK_MOTOROLA_FACTORY_CBL_PHY_EN_9540 +}; + enum ab8500_usb_mode { USB_IDLE = 0, USB_PERIPHERAL, @@ -119,13 +198,30 @@ enum ab8500_usb_mode { USB_DEDICATED_CHG }; +/* Register USB_LINK_STATUS interrupt */ +#define AB8500_USB_FLAG_USE_LINK_STATUS_IRQ (1 << 0) +/* Register ID_WAKEUP_F interrupt */ +#define AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ (1 << 1) +/* Register VBUS_DET_F interrupt */ +#define AB8500_USB_FLAG_USE_VBUS_DET_IRQ (1 << 2) +/* Driver is using the ab-iddet driver*/ +#define AB8500_USB_FLAG_USE_AB_IDDET (1 << 3) +/* Enable setting regulators voltage */ +#define AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE (1 << 4) +/* Enable the check_vbus_status workaround */ +#define AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS (1 << 5) +/* Enable the vbus host workaround */ +#define AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK (1 << 6) + struct ab8500_usb { struct usb_phy phy; struct device *dev; struct ab8500 *ab8500; unsigned vbus_draw; struct work_struct phy_dis_work; + struct work_struct vbus_event_work; enum ab8500_usb_mode mode; + struct clk *sysclk; struct regulator *v_ape; struct regulator *v_musb; struct regulator *v_ulpi; @@ -133,6 +229,8 @@ struct ab8500_usb { int previous_link_status_state; struct pinctrl *pinctrl; struct pinctrl_state *pins_sleep; + bool enabled_charging_detection; + unsigned int flags; }; static inline struct ab8500_usb *phy_to_ab(struct usb_phy *x) @@ -171,7 +269,7 @@ static void ab8500_usb_regulator_enable(struct ab8500_usb *ab) if (ret) dev_err(ab->dev, "Failed to enable v-ape\n"); - if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + if (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { ab->saved_v_ulpi = regulator_get_voltage(ab->v_ulpi); if (ab->saved_v_ulpi < 0) dev_err(ab->dev, "Failed to get v_ulpi voltage\n"); @@ -191,7 +289,7 @@ static void ab8500_usb_regulator_enable(struct ab8500_usb *ab) if (ret) dev_err(ab->dev, "Failed to enable vddulpivio18\n"); - if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + if (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { volt = regulator_get_voltage(ab->v_ulpi); if ((volt != 1300000) && (volt != 1350000)) dev_err(ab->dev, "Vintcore is not set to 1.3V volt=%d\n", @@ -212,7 +310,7 @@ static void ab8500_usb_regulator_disable(struct ab8500_usb *ab) regulator_disable(ab->v_ulpi); /* USB is not the only consumer of Vintcore, restore old settings */ - if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { + if (ab->flags & AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE) { if (ab->saved_v_ulpi > 0) { ret = regulator_set_voltage(ab->v_ulpi, ab->saved_v_ulpi, ab->saved_v_ulpi); @@ -252,11 +350,23 @@ static void ab8500_usb_phy_enable(struct ab8500_usb *ab, bool sel_host) if (IS_ERR(ab->pinctrl)) dev_err(ab->dev, "could not get/set default pinstate\n"); + if (clk_prepare_enable(ab->sysclk)) + dev_err(ab->dev, "can't prepare/enable clock\n"); + ab8500_usb_regulator_enable(ab); abx500_mask_and_set_register_interruptible(ab->dev, AB8500_USB, AB8500_USB_PHY_CTRL_REG, bit, bit); + + if (ab->flags & AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK) { + if (sel_host) + abx500_set_register_interruptible(ab->dev, + AB8500_USB, AB8540_USB_OTG_CTL_REG, + AB8540_BIT_OTG_CTL_VBUS_VALID_ENA | + AB8540_BIT_OTG_CTL_ID_HOST_ENA | + AB8540_BIT_OTG_CTL_ID_DEV_ENA); + } } static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) @@ -274,6 +384,8 @@ static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) /* Needed to disable the phy.*/ ab8500_usb_wd_workaround(ab); + clk_disable_unprepare(ab->sysclk); + ab8500_usb_regulator_disable(ab); if (!IS_ERR(ab->pinctrl)) { @@ -286,7 +398,8 @@ static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) else if (pinctrl_select_state(ab->pinctrl, ab->pins_sleep)) dev_err(ab->dev, "could not set pins to sleep state\n"); - /* as USB pins are shared with idddet, release them to allow + /* + * as USB pins are shared with iddet, release them to allow * iddet to request them */ pinctrl_put(ab->pinctrl); @@ -298,6 +411,254 @@ static void ab8500_usb_phy_disable(struct ab8500_usb *ab, bool sel_host) #define ab8500_usb_peri_phy_en(ab) ab8500_usb_phy_enable(ab, false) #define ab8500_usb_peri_phy_dis(ab) ab8500_usb_phy_disable(ab, false) +static int ab9540_usb_link_status_update(struct ab8500_usb *ab, + enum ab9540_usb_link_status lsts) +{ + enum ux500_musb_vbus_id_status event = 0; + + dev_dbg(ab->dev, "ab9540_usb_link_status_update %d\n", lsts); + + if (ab->previous_link_status_state == USB_LINK_HM_IDGND_9540 && + (lsts == USB_LINK_STD_HOST_C_NS_9540 || + lsts == USB_LINK_STD_HOST_NC_9540)) + return 0; + + if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_9540 && + (lsts == USB_LINK_STD_HOST_NC_9540)) + return 0; + + ab->previous_link_status_state = lsts; + + switch (lsts) { + case USB_LINK_ACA_RID_B_9540: + event = UX500_MUSB_RIDB; + case USB_LINK_NOT_CONFIGURED_9540: + case USB_LINK_RESERVED0_9540: + case USB_LINK_RESERVED1_9540: + case USB_LINK_RESERVED2_9540: + case USB_LINK_RESERVED3_9540: + if (ab->mode == USB_PERIPHERAL) + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_CLEAN, &ab->vbus_draw); + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + if (event != UX500_MUSB_RIDB) + event = UX500_MUSB_NONE; + /* Fallback to default B_IDLE as nothing is connected. */ + ab->phy.state = OTG_STATE_B_IDLE; + break; + + case USB_LINK_ACA_RID_C_NM_9540: + event = UX500_MUSB_RIDC; + case USB_LINK_STD_HOST_NC_9540: + case USB_LINK_STD_HOST_C_NS_9540: + case USB_LINK_STD_HOST_C_S_9540: + case USB_LINK_CDP_9540: + if (ab->mode == USB_HOST) { + ab->mode = USB_PERIPHERAL; + ab8500_usb_host_phy_dis(ab); + ab8500_usb_peri_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (ab->mode == USB_IDLE) { + ab->mode = USB_PERIPHERAL; + ab8500_usb_peri_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (event != UX500_MUSB_RIDC) + event = UX500_MUSB_VBUS; + break; + + case USB_LINK_ACA_RID_A_9540: + event = UX500_MUSB_RIDA; + case USB_LINK_HM_IDGND_9540: + case USB_LINK_STD_UPSTREAM_9540: + if (ab->mode == USB_PERIPHERAL) { + ab->mode = USB_HOST; + ab8500_usb_peri_phy_dis(ab); + ab8500_usb_host_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (ab->mode == USB_IDLE) { + ab->mode = USB_HOST; + ab8500_usb_host_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + ab->phy.otg->default_a = true; + if (event != UX500_MUSB_RIDA) + event = UX500_MUSB_ID; + + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_DEDICATED_CHG_9540: + ab->mode = USB_DEDICATED_CHG; + event = UX500_MUSB_CHARGER; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_PHYEN_NO_VBUS_NO_IDGND_9540: + case USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_9540: + if (!(is_ab9540_2p0_or_earlier(ab->ab8500))) { + event = UX500_MUSB_NONE; + if (ab->mode == USB_HOST) { + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_host_phy_dis(ab); + ab->mode = USB_IDLE; + } + if (ab->mode == USB_PERIPHERAL) { + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_peri_phy_dis(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_CLEAN, + &ab->vbus_draw); + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + } + } + break; + + default: + break; + } + + return 0; +} + +static int ab8540_usb_link_status_update(struct ab8500_usb *ab, + enum ab8540_usb_link_status lsts) +{ + enum ux500_musb_vbus_id_status event = 0; + + dev_dbg(ab->dev, "ab8540_usb_link_status_update %d\n", lsts); + + if (ab->enabled_charging_detection) { + /* Disable USB Charger detection */ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8540_VBUS_CTRL_REG, + AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA, 0x00); + ab->enabled_charging_detection = false; + } + + /* + * Spurious link_status interrupts are seen in case of a + * disconnection of a device in IDGND and RIDA stage + */ + if (ab->previous_link_status_state == USB_LINK_HM_IDGND_8540 && + (lsts == USB_LINK_STD_HOST_C_NS_8540 || + lsts == USB_LINK_STD_HOST_NC_8540)) + return 0; + + if (ab->previous_link_status_state == USB_LINK_ACA_RID_A_8540 && + (lsts == USB_LINK_STD_HOST_NC_8540)) + return 0; + + ab->previous_link_status_state = lsts; + + switch (lsts) { + case USB_LINK_ACA_RID_B_8540: + event = UX500_MUSB_RIDB; + case USB_LINK_NOT_CONFIGURED_8540: + case USB_LINK_RESERVED0_8540: + case USB_LINK_RESERVED1_8540: + case USB_LINK_RESERVED2_8540: + case USB_LINK_RESERVED3_8540: + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + if (event != UX500_MUSB_RIDB) + event = UX500_MUSB_NONE; + /* + * Fallback to default B_IDLE as nothing + * is connected + */ + ab->phy.state = OTG_STATE_B_IDLE; + break; + + case USB_LINK_ACA_RID_C_NM_8540: + event = UX500_MUSB_RIDC; + case USB_LINK_STD_HOST_NC_8540: + case USB_LINK_STD_HOST_C_NS_8540: + case USB_LINK_STD_HOST_C_S_8540: + case USB_LINK_CDP_8540: + if (ab->mode == USB_IDLE) { + ab->mode = USB_PERIPHERAL; + ab8500_usb_peri_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + if (event != UX500_MUSB_RIDC) + event = UX500_MUSB_VBUS; + break; + + case USB_LINK_ACA_RID_A_8540: + case USB_LINK_ACA_DOCK_CHGR_8540: + event = UX500_MUSB_RIDA; + case USB_LINK_HM_IDGND_8540: + case USB_LINK_STD_UPSTREAM_8540: + if (ab->mode == USB_IDLE) { + ab->mode = USB_HOST; + ab8500_usb_host_phy_en(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_PREPARE, &ab->vbus_draw); + } + ab->phy.otg->default_a = true; + if (event != UX500_MUSB_RIDA) + event = UX500_MUSB_ID; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_DEDICATED_CHG_8540: + ab->mode = USB_DEDICATED_CHG; + event = UX500_MUSB_CHARGER; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + break; + + case USB_LINK_PHYEN_NO_VBUS_NO_IDGND_8540: + case USB_LINK_STD_UPSTREAM_NO_IDGNG_VBUS_8540: + event = UX500_MUSB_NONE; + if (ab->mode == USB_HOST) { + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_host_phy_dis(ab); + ab->mode = USB_IDLE; + } + if (ab->mode == USB_PERIPHERAL) { + atomic_notifier_call_chain(&ab->phy.notifier, + event, &ab->vbus_draw); + ab8500_usb_peri_phy_dis(ab); + atomic_notifier_call_chain(&ab->phy.notifier, + UX500_MUSB_CLEAN, &ab->vbus_draw); + ab->mode = USB_IDLE; + ab->phy.otg->default_a = false; + ab->vbus_draw = 0; + } + break; + + default: + event = UX500_MUSB_NONE; + break; + } + + return 0; +} + static int ab8505_usb_link_status_update(struct ab8500_usb *ab, enum ab8505_usb_link_status lsts) { @@ -498,6 +859,20 @@ static int abx500_usb_link_status_update(struct ab8500_usb *ab) AB8500_USB, AB8505_USB_LINE_STAT_REG, ®); lsts = (reg >> 3) & 0x1F; ret = ab8505_usb_link_status_update(ab, lsts); + } else if (is_ab8540(ab->ab8500)) { + enum ab8540_usb_link_status lsts; + + abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB8540_USB_LINK_STAT_REG, ®); + lsts = (reg >> 3) & 0xFF; + ret = ab8540_usb_link_status_update(ab, lsts); + } else if (is_ab9540(ab->ab8500)) { + enum ab9540_usb_link_status lsts; + + abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB9540_USB_LINK_STAT_REG, ®); + lsts = (reg >> 3) & 0xFF; + ret = ab9540_usb_link_status_update(ab, lsts); } return ret; @@ -553,7 +928,7 @@ static irqreturn_t ab8500_usb_disconnect_irq(int irq, void *data) static irqreturn_t ab8500_usb_link_status_irq(int irq, void *data) { - struct ab8500_usb *ab = (struct ab8500_usb *) data; + struct ab8500_usb *ab = (struct ab8500_usb *)data; abx500_usb_link_status_update(ab); @@ -572,6 +947,69 @@ static void ab8500_usb_phy_disable_work(struct work_struct *work) ab8500_usb_peri_phy_dis(ab); } +/* Check if VBUS is set and linkstatus has not detected a cable. */ +static bool ab8500_usb_check_vbus_status(struct ab8500_usb *ab) +{ + u8 isource2; + u8 reg; + enum ab8540_usb_link_status lsts; + + abx500_get_register_interruptible(ab->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE2_REG, + &isource2); + + /* If Vbus is below 3.6V abort */ + if (!(isource2 & AB8500_BIT_SOURCE2_VBUSDET)) + return false; + + abx500_get_register_interruptible(ab->dev, + AB8500_USB, AB8540_USB_LINK_STAT_REG, + ®); + + lsts = (reg >> 3) & 0xFF; + + /* Check if linkstatus has detected a cable */ + if (lsts) + return false; + + return true; +} + +/* re-trigger charger detection again with watchdog re-kick. */ +static void ab8500_usb_vbus_turn_on_event_work(struct work_struct *work) +{ + struct ab8500_usb *ab = container_of(work, struct ab8500_usb, + vbus_event_work); + + if (ab->mode != USB_IDLE) + return; + + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, + AB8500_BIT_WD_CTRL_ENABLE); + + udelay(100); + + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, + AB8500_BIT_WD_CTRL_ENABLE | AB8500_BIT_WD_CTRL_KICK); + + udelay(100); + + /* Disable Main watchdog */ + abx500_set_register_interruptible(ab->dev, + AB8500_SYS_CTRL2_BLOCK, AB8500_MAIN_WD_CTRL_REG, + 0x0); + + /* Enable USB Charger detection */ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8540_VBUS_CTRL_REG, + AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA, + AB8540_BIT_VBUS_CTRL_CHARG_DET_ENA); + + ab->enabled_charging_detection = true; +} + static unsigned ab8500_eyediagram_workaroud(struct ab8500_usb *ab, unsigned mA) { /* @@ -627,7 +1065,7 @@ static int ab8500_usb_set_peripheral(struct usb_otg *otg, * is fixed. */ - if ((ab->mode != USB_IDLE) && (!gadget)) { + if ((ab->mode != USB_IDLE) && !gadget) { ab->mode = USB_IDLE; schedule_work(&ab->phy_dis_work); } @@ -651,7 +1089,7 @@ static int ab8500_usb_set_host(struct usb_otg *otg, struct usb_bus *host) * is fixed. */ - if ((ab->mode != USB_IDLE) && (!host)) { + if ((ab->mode != USB_IDLE) && !host) { ab->mode = USB_IDLE; schedule_work(&ab->phy_dis_work); } @@ -659,6 +1097,33 @@ static int ab8500_usb_set_host(struct usb_otg *otg, struct usb_bus *host) return 0; } +static void ab8500_usb_restart_phy(struct ab8500_usb *ab) +{ + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_DEVICE_EN, + AB8500_BIT_PHY_CTRL_DEVICE_EN); + + udelay(100); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_DEVICE_EN, + 0); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_HOST_EN, + AB8500_BIT_PHY_CTRL_HOST_EN); + + udelay(100); + + abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_USB, AB8500_USB_PHY_CTRL_REG, + AB8500_BIT_PHY_CTRL_HOST_EN, + 0); +} + static int ab8500_usb_regulator_get(struct ab8500_usb *ab) { int err; @@ -693,48 +1158,197 @@ static int ab8500_usb_irq_setup(struct platform_device *pdev, int err; int irq; - irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS"); - if (irq < 0) { - dev_err(&pdev->dev, "Link status irq not found\n"); - return irq; - } - err = devm_request_threaded_irq(&pdev->dev, irq, NULL, - ab8500_usb_link_status_irq, - IRQF_NO_SUSPEND | IRQF_SHARED, "usb-link-status", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for link status irq\n"); - return err; + if (ab->flags & AB8500_USB_FLAG_USE_LINK_STATUS_IRQ) { + irq = platform_get_irq_byname(pdev, "USB_LINK_STATUS"); + if (irq < 0) { + dev_err(&pdev->dev, "Link status irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_link_status_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-link-status", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for link status irq\n"); + return err; + } } - irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); - if (irq < 0) { - dev_err(&pdev->dev, "ID fall irq not found\n"); - return irq; - } - err = devm_request_threaded_irq(&pdev->dev, irq, NULL, - ab8500_usb_disconnect_irq, - IRQF_NO_SUSPEND | IRQF_SHARED, "usb-id-fall", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for ID fall irq\n"); - return err; + if (ab->flags & AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ) { + irq = platform_get_irq_byname(pdev, "ID_WAKEUP_F"); + if (irq < 0) { + dev_err(&pdev->dev, "ID fall irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-id-fall", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for ID fall irq\n"); + return err; + } } - irq = platform_get_irq_byname(pdev, "VBUS_DET_F"); - if (irq < 0) { - dev_err(&pdev->dev, "VBUS fall irq not found\n"); - return irq; - } - err = devm_request_threaded_irq(&pdev->dev, irq, NULL, - ab8500_usb_disconnect_irq, - IRQF_NO_SUSPEND | IRQF_SHARED, "usb-vbus-fall", ab); - if (err < 0) { - dev_err(ab->dev, "request_irq failed for Vbus fall irq\n"); - return err; + if (ab->flags & AB8500_USB_FLAG_USE_VBUS_DET_IRQ) { + irq = platform_get_irq_byname(pdev, "VBUS_DET_F"); + if (irq < 0) { + dev_err(&pdev->dev, "VBUS fall irq not found\n"); + return irq; + } + err = devm_request_threaded_irq(&pdev->dev, irq, NULL, + ab8500_usb_disconnect_irq, + IRQF_NO_SUSPEND | IRQF_SHARED, + "usb-vbus-fall", ab); + if (err < 0) { + dev_err(ab->dev, "request_irq failed for Vbus fall irq\n"); + return err; + } } return 0; } +static void ab8500_usb_set_ab8500_tuning_values(struct ab8500_usb *ab) +{ + int err; + + /* Enable the PBT/Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); + if (err < 0) + dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x78); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", + err); + + /* Switch to normal mode/disable Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", + err); +} + +static void ab8500_usb_set_ab8505_tuning_values(struct ab8500_usb *ab) +{ + int err; + + /* Enable the PBT/Bank 0x12 access */ + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, + 0x01, 0x01); + if (err < 0) + dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE1, + 0xC8, 0xC8); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE2, + 0x60, 0x60); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", + err); + + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE3, + 0xFC, 0x80); + + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", + err); + + /* Switch to normal mode/disable Bank 0x12 access */ + err = abx500_mask_and_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, + 0x00, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", + err); +} + +static void ab8500_usb_set_ab8540_tuning_values(struct ab8500_usb *ab) +{ + int err; + + err = abx500_set_register_interruptible(ab->dev, + AB8540_DEBUG, AB8500_USB_PHY_TUNE1, 0xCC); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register ret=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8540_DEBUG, AB8500_USB_PHY_TUNE2, 0x60); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register ret=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8540_DEBUG, AB8500_USB_PHY_TUNE3, 0x90); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester ret=%d\n", + err); +} + +static void ab8500_usb_set_ab9540_tuning_values(struct ab8500_usb *ab) +{ + int err; + + /* Enable the PBT/Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); + if (err < 0) + dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x60); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", + err); + + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x80); + if (err < 0) + dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", + err); + + /* Switch to normal mode/disable Bank 0x12 access */ + err = abx500_set_register_interruptible(ab->dev, + AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); + if (err < 0) + dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", + err); +} + static int ab8500_usb_probe(struct platform_device *pdev) { struct ab8500_usb *ab; @@ -772,6 +1386,33 @@ static int ab8500_usb_probe(struct platform_device *pdev) otg->set_host = ab8500_usb_set_host; otg->set_peripheral = ab8500_usb_set_peripheral; + if (is_ab8500(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | + AB8500_USB_FLAG_USE_VBUS_DET_IRQ | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + } else if (is_ab8505(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | + AB8500_USB_FLAG_USE_VBUS_DET_IRQ | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + } else if (is_ab8540(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS | + AB8500_USB_FLAG_USE_VBUS_HOST_QUIRK | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + } else if (is_ab9540(ab->ab8500)) { + ab->flags |= AB8500_USB_FLAG_USE_LINK_STATUS_IRQ | + AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + if (is_ab9540_2p0_or_earlier(ab->ab8500)) + ab->flags |= AB8500_USB_FLAG_USE_ID_WAKEUP_IRQ | + AB8500_USB_FLAG_USE_VBUS_DET_IRQ; + } + + /* Disable regulator voltage setting for AB8500 <= v2.0 */ + if (is_ab8500_2p0_or_earlier(ab->ab8500)) + ab->flags &= ~AB8500_USB_FLAG_REGULATOR_SET_VOLTAGE; + platform_set_drvdata(pdev, ab); ATOMIC_INIT_NOTIFIER_HEAD(&ab->phy.notifier); @@ -779,10 +1420,18 @@ static int ab8500_usb_probe(struct platform_device *pdev) /* all: Disable phy when called from set_host and set_peripheral */ INIT_WORK(&ab->phy_dis_work, ab8500_usb_phy_disable_work); + INIT_WORK(&ab->vbus_event_work, ab8500_usb_vbus_turn_on_event_work); + err = ab8500_usb_regulator_get(ab); if (err) return err; + ab->sysclk = devm_clk_get(ab->dev, "sysclk"); + if (IS_ERR(ab->sysclk)) { + dev_err(ab->dev, "Could not get sysclk.\n"); + return PTR_ERR(ab->sysclk); + } + err = ab8500_usb_irq_setup(pdev, ab); if (err < 0) return err; @@ -793,85 +1442,33 @@ static int ab8500_usb_probe(struct platform_device *pdev) return err; } - /* Phy tuning values for AB8500 */ - if (!is_ab8500_2p0_or_earlier(ab->ab8500)) { - /* Enable the PBT/Bank 0x12 access */ - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x01); - if (err < 0) - dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE1, 0xC8); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE2, 0x00); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", - err); - - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE3, 0x78); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", - err); - - /* Switch to normal mode/disable Bank 0x12 access */ - err = abx500_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, 0x00); - if (err < 0) - dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", - err); - } - - /* Phy tuning values for AB8505 */ - if (is_ab8505(ab->ab8500)) { - /* Enable the PBT/Bank 0x12 access */ - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, - 0x01, 0x01); - if (err < 0) - dev_err(ab->dev, "Failed to enable bank12 access err=%d\n", - err); - - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE1, - 0xC8, 0xC8); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE1 register err=%d\n", - err); - - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE2, - 0x60, 0x60); - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE2 register err=%d\n", - err); - - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEBUG, AB8500_USB_PHY_TUNE3, - 0xFC, 0x80); - - if (err < 0) - dev_err(ab->dev, "Failed to set PHY_TUNE3 regester err=%d\n", - err); - - /* Switch to normal mode/disable Bank 0x12 access */ - err = abx500_mask_and_set_register_interruptible(ab->dev, - AB8500_DEVELOPMENT, AB8500_BANK12_ACCESS, - 0x00, 0x00); - if (err < 0) - dev_err(ab->dev, "Failed to switch bank12 access err=%d\n", - err); - } + if (is_ab8500(ab->ab8500) && !is_ab8500_2p0_or_earlier(ab->ab8500)) + /* Phy tuning values for AB8500 > v2.0 */ + ab8500_usb_set_ab8500_tuning_values(ab); + else if (is_ab8505(ab->ab8500)) + /* Phy tuning values for AB8505 */ + ab8500_usb_set_ab8505_tuning_values(ab); + else if (is_ab8540(ab->ab8500)) + /* Phy tuning values for AB8540 */ + ab8500_usb_set_ab8540_tuning_values(ab); + else if (is_ab9540(ab->ab8500)) + /* Phy tuning values for AB9540 */ + ab8500_usb_set_ab9540_tuning_values(ab); /* Needed to enable ID detection. */ ab8500_usb_wd_workaround(ab); + /* + * This is required for usb-link-status to work properly when a + * cable is connected at boot time. + */ + ab8500_usb_restart_phy(ab); + + if (ab->flags & AB8500_USB_FLAG_USE_CHECK_VBUS_STATUS) { + if (ab8500_usb_check_vbus_status(ab)) + schedule_work(&ab->vbus_event_work); + } + abx500_usb_link_status_update(ab); dev_info(&pdev->dev, "revision 0x%2x driver initialized\n", rev); @@ -884,6 +1481,7 @@ static int ab8500_usb_remove(struct platform_device *pdev) struct ab8500_usb *ab = platform_get_drvdata(pdev); cancel_work_sync(&ab->phy_dis_work); + cancel_work_sync(&ab->vbus_event_work); usb_remove_phy(&ab->phy); @@ -895,11 +1493,20 @@ static int ab8500_usb_remove(struct platform_device *pdev) return 0; } +static struct platform_device_id ab8500_usb_devtype[] = { + { .name = "ab8500-usb", }, + { .name = "ab8540-usb", }, + { .name = "ab9540-usb", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, ab8500_usb_devtype); + static struct platform_driver ab8500_usb_driver = { .probe = ab8500_usb_probe, .remove = ab8500_usb_remove, + .id_table = ab8500_usb_devtype, .driver = { - .name = "ab8500-usb", + .name = "abx5x0-usb", .owner = THIS_MODULE, }, }; @@ -916,7 +1523,6 @@ static void __exit ab8500_usb_exit(void) } module_exit(ab8500_usb_exit); -MODULE_ALIAS("platform:ab8500_usb"); MODULE_AUTHOR("ST-Ericsson AB"); -MODULE_DESCRIPTION("AB8500 usb transceiver driver"); +MODULE_DESCRIPTION("AB8500 family usb transceiver driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/phy/phy-nop.c b/drivers/usb/phy/phy-nop.c index 638cc5d..55445e5d 100644 --- a/drivers/usb/phy/phy-nop.c +++ b/drivers/usb/phy/phy-nop.c @@ -270,7 +270,7 @@ static struct platform_driver nop_usb_xceiv_driver = { .driver = { .name = "nop_usb_xceiv", .owner = THIS_MODULE, - .of_match_table = of_match_ptr(nop_xceiv_dt_ids), + .of_match_table = nop_xceiv_dt_ids, }, }; diff --git a/drivers/usb/phy/phy-omap-usb3.c b/drivers/usb/phy/phy-omap-usb3.c index a6e60b1..efe6e14 100644 --- a/drivers/usb/phy/phy-omap-usb3.c +++ b/drivers/usb/phy/phy-omap-usb3.c @@ -27,7 +27,7 @@ #include <linux/delay.h> #include <linux/usb/omap_control_usb.h> -#define NUM_SYS_CLKS 5 +#define NUM_SYS_CLKS 6 #define PLL_STATUS 0x00000004 #define PLL_GO 0x00000008 #define PLL_CONFIGURATION1 0x0000000C @@ -62,6 +62,7 @@ enum sys_clk_rate { CLK_RATE_12MHZ, CLK_RATE_16MHZ, CLK_RATE_19MHZ, + CLK_RATE_20MHZ, CLK_RATE_26MHZ, CLK_RATE_38MHZ }; @@ -72,6 +73,8 @@ static struct usb_dpll_params omap_usb3_dpll_params[NUM_SYS_CLKS] = { {1172, 8, 4, 20, 65537}, /* 19.2 MHz */ {1250, 12, 4, 20, 0}, /* 26 MHz */ {3125, 47, 4, 20, 92843}, /* 38.4 MHz */ + {1000, 7, 4, 10, 0}, /* 20 MHz */ + }; static int omap_usb3_suspend(struct usb_phy *x, int suspend) @@ -122,6 +125,8 @@ static inline enum sys_clk_rate __get_sys_clk_index(unsigned long rate) return CLK_RATE_16MHZ; case 19200000: return CLK_RATE_19MHZ; + case 20000000: + return CLK_RATE_20MHZ; case 26000000: return CLK_RATE_26MHZ; case 38400000: diff --git a/drivers/usb/phy/phy-samsung-usb.c b/drivers/usb/phy/phy-samsung-usb.c index 7b118ee5..ac025ca 100644 --- a/drivers/usb/phy/phy-samsung-usb.c +++ b/drivers/usb/phy/phy-samsung-usb.c @@ -73,7 +73,7 @@ EXPORT_SYMBOL_GPL(samsung_usbphy_parse_dt); * Here 'on = true' would mean USB PHY block is isolated, hence * de-activated and vice-versa. */ -void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) +void samsung_usbphy_set_isolation_4210(struct samsung_usbphy *sphy, bool on) { void __iomem *reg = NULL; u32 reg_val; @@ -84,32 +84,12 @@ void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) return; } - switch (sphy->drv_data->cpu_type) { - case TYPE_S3C64XX: - /* - * Do nothing: We will add here once S3C64xx goes for DT support - */ - break; - case TYPE_EXYNOS4210: - /* - * Fall through since exynos4210 and exynos5250 have similar - * register architecture: two separate registers for host and - * device phy control with enable bit at position 0. - */ - case TYPE_EXYNOS5250: - if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { - reg = sphy->pmuregs + - sphy->drv_data->devphy_reg_offset; - en_mask = sphy->drv_data->devphy_en_mask; - } else if (sphy->phy_type == USB_PHY_TYPE_HOST) { - reg = sphy->pmuregs + - sphy->drv_data->hostphy_reg_offset; - en_mask = sphy->drv_data->hostphy_en_mask; - } - break; - default: - dev_err(sphy->dev, "Invalid SoC type\n"); - return; + if (sphy->phy_type == USB_PHY_TYPE_DEVICE) { + reg = sphy->pmuregs + sphy->drv_data->devphy_reg_offset; + en_mask = sphy->drv_data->devphy_en_mask; + } else if (sphy->phy_type == USB_PHY_TYPE_HOST) { + reg = sphy->pmuregs + sphy->drv_data->hostphy_reg_offset; + en_mask = sphy->drv_data->hostphy_en_mask; } reg_val = readl(reg); @@ -120,8 +100,13 @@ void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on) reg_val |= en_mask; writel(reg_val, reg); + + if (sphy->drv_data->cpu_type == TYPE_EXYNOS4X12) { + writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL0); + writel(reg_val, sphy->pmuregs + EXYNOS4X12_PHY_HSIC_CTRL1); + } } -EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation); +EXPORT_SYMBOL_GPL(samsung_usbphy_set_isolation_4210); /* * Configure the mode of working of usb-phy here: HOST/DEVICE. @@ -162,73 +147,93 @@ int samsung_usbphy_set_type(struct usb_phy *phy, } EXPORT_SYMBOL_GPL(samsung_usbphy_set_type); +int samsung_usbphy_rate_to_clksel_64xx(struct samsung_usbphy *sphy, + unsigned long rate) +{ + unsigned int clksel; + + switch (rate) { + case 12 * MHZ: + clksel = PHYCLK_CLKSEL_12M; + break; + case 24 * MHZ: + clksel = PHYCLK_CLKSEL_24M; + break; + case 48 * MHZ: + clksel = PHYCLK_CLKSEL_48M; + break; + default: + dev_err(sphy->dev, + "Invalid reference clock frequency: %lu\n", rate); + return -EINVAL; + } + + return clksel; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_rate_to_clksel_64xx); + +int samsung_usbphy_rate_to_clksel_4x12(struct samsung_usbphy *sphy, + unsigned long rate) +{ + unsigned int clksel; + + switch (rate) { + case 9600 * KHZ: + clksel = FSEL_CLKSEL_9600K; + break; + case 10 * MHZ: + clksel = FSEL_CLKSEL_10M; + break; + case 12 * MHZ: + clksel = FSEL_CLKSEL_12M; + break; + case 19200 * KHZ: + clksel = FSEL_CLKSEL_19200K; + break; + case 20 * MHZ: + clksel = FSEL_CLKSEL_20M; + break; + case 24 * MHZ: + clksel = FSEL_CLKSEL_24M; + break; + case 50 * MHZ: + clksel = FSEL_CLKSEL_50M; + break; + default: + dev_err(sphy->dev, + "Invalid reference clock frequency: %lu\n", rate); + return -EINVAL; + } + + return clksel; +} +EXPORT_SYMBOL_GPL(samsung_usbphy_rate_to_clksel_4x12); + /* * Returns reference clock frequency selection value */ int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy) { struct clk *ref_clk; - int refclk_freq = 0; + unsigned long rate; + int refclk_freq; /* * In exynos5250 USB host and device PHY use * external crystal clock XXTI */ if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) - ref_clk = devm_clk_get(sphy->dev, "ext_xtal"); + ref_clk = clk_get(sphy->dev, "ext_xtal"); else - ref_clk = devm_clk_get(sphy->dev, "xusbxti"); + ref_clk = clk_get(sphy->dev, "xusbxti"); if (IS_ERR(ref_clk)) { dev_err(sphy->dev, "Failed to get reference clock\n"); return PTR_ERR(ref_clk); } - if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) { - /* set clock frequency for PLL */ - switch (clk_get_rate(ref_clk)) { - case 9600 * KHZ: - refclk_freq = FSEL_CLKSEL_9600K; - break; - case 10 * MHZ: - refclk_freq = FSEL_CLKSEL_10M; - break; - case 12 * MHZ: - refclk_freq = FSEL_CLKSEL_12M; - break; - case 19200 * KHZ: - refclk_freq = FSEL_CLKSEL_19200K; - break; - case 20 * MHZ: - refclk_freq = FSEL_CLKSEL_20M; - break; - case 50 * MHZ: - refclk_freq = FSEL_CLKSEL_50M; - break; - case 24 * MHZ: - default: - /* default reference clock */ - refclk_freq = FSEL_CLKSEL_24M; - break; - } - } else { - switch (clk_get_rate(ref_clk)) { - case 12 * MHZ: - refclk_freq = PHYCLK_CLKSEL_12M; - break; - case 24 * MHZ: - refclk_freq = PHYCLK_CLKSEL_24M; - break; - case 48 * MHZ: - refclk_freq = PHYCLK_CLKSEL_48M; - break; - default: - if (sphy->drv_data->cpu_type == TYPE_S3C64XX) - refclk_freq = PHYCLK_CLKSEL_48M; - else - refclk_freq = PHYCLK_CLKSEL_24M; - break; - } - } + rate = clk_get_rate(ref_clk); + refclk_freq = sphy->drv_data->rate_to_clksel(sphy, rate); + clk_put(ref_clk); return refclk_freq; diff --git a/drivers/usb/phy/phy-samsung-usb.h b/drivers/usb/phy/phy-samsung-usb.h index 70a9cae..68771bf 100644 --- a/drivers/usb/phy/phy-samsung-usb.h +++ b/drivers/usb/phy/phy-samsung-usb.h @@ -47,6 +47,16 @@ #define RSTCON_HLINK_SWRST (0x1 << 1) #define RSTCON_SWRST (0x1 << 0) +/* EXYNOS4X12 */ +#define EXYNOS4X12_PHY_HSIC_CTRL0 (0x04) +#define EXYNOS4X12_PHY_HSIC_CTRL1 (0x08) + +#define PHYPWR_NORMAL_MASK_HSIC1 (0x7 << 12) +#define PHYPWR_NORMAL_MASK_HSIC0 (0x7 << 9) +#define PHYPWR_NORMAL_MASK_PHY1 (0x7 << 6) + +#define RSTCON_HOSTPHY_SWRST (0xf << 3) + /* EXYNOS5 */ #define EXYNOS5_PHY_HOST_CTRL0 (0x00) @@ -241,9 +251,12 @@ enum samsung_cpu_type { TYPE_S3C64XX, TYPE_EXYNOS4210, + TYPE_EXYNOS4X12, TYPE_EXYNOS5250, }; +struct samsung_usbphy; + /* * struct samsung_usbphy_drvdata - driver data for various SoC variants * @cpu_type: machine identifier @@ -268,6 +281,10 @@ struct samsung_usbphy_drvdata { int hostphy_en_mask; u32 devphy_reg_offset; u32 hostphy_reg_offset; + int (*rate_to_clksel)(struct samsung_usbphy *, unsigned long); + void (*set_isolation)(struct samsung_usbphy *, bool); + void (*phy_enable)(struct samsung_usbphy *); + void (*phy_disable)(struct samsung_usbphy *); }; /* @@ -320,8 +337,13 @@ static inline const struct samsung_usbphy_drvdata } extern int samsung_usbphy_parse_dt(struct samsung_usbphy *sphy); -extern void samsung_usbphy_set_isolation(struct samsung_usbphy *sphy, bool on); +extern void samsung_usbphy_set_isolation_4210(struct samsung_usbphy *sphy, + bool on); extern void samsung_usbphy_cfg_sel(struct samsung_usbphy *sphy); extern int samsung_usbphy_set_type(struct usb_phy *phy, enum samsung_usb_phy_type phy_type); extern int samsung_usbphy_get_refclk_freq(struct samsung_usbphy *sphy); +extern int samsung_usbphy_rate_to_clksel_64xx(struct samsung_usbphy *sphy, + unsigned long rate); +extern int samsung_usbphy_rate_to_clksel_4x12(struct samsung_usbphy *sphy, + unsigned long rate); diff --git a/drivers/usb/phy/phy-samsung-usb2.c b/drivers/usb/phy/phy-samsung-usb2.c index 9d5e273..1011c16 100644 --- a/drivers/usb/phy/phy-samsung-usb2.c +++ b/drivers/usb/phy/phy-samsung-usb2.c @@ -176,6 +176,11 @@ static void samsung_usb2phy_enable(struct samsung_usbphy *sphy) phypwr &= ~PHYPWR_NORMAL_MASK; rstcon |= RSTCON_SWRST; break; + case TYPE_EXYNOS4X12: + phypwr &= ~(PHYPWR_NORMAL_MASK_HSIC0 | + PHYPWR_NORMAL_MASK_HSIC1 | + PHYPWR_NORMAL_MASK_PHY1); + rstcon |= RSTCON_HOSTPHY_SWRST; case TYPE_EXYNOS4210: phypwr &= ~PHYPWR_NORMAL_MASK_PHY0; rstcon |= RSTCON_SWRST; @@ -189,6 +194,8 @@ static void samsung_usb2phy_enable(struct samsung_usbphy *sphy) /* reset all ports of PHY and Link */ writel(rstcon, regs + SAMSUNG_RSTCON); udelay(10); + if (sphy->drv_data->cpu_type == TYPE_EXYNOS4X12) + rstcon &= ~RSTCON_HOSTPHY_SWRST; rstcon &= ~RSTCON_SWRST; writel(rstcon, regs + SAMSUNG_RSTCON); } @@ -239,6 +246,10 @@ static void samsung_usb2phy_disable(struct samsung_usbphy *sphy) case TYPE_S3C64XX: phypwr |= PHYPWR_NORMAL_MASK; break; + case TYPE_EXYNOS4X12: + phypwr |= (PHYPWR_NORMAL_MASK_HSIC0 | + PHYPWR_NORMAL_MASK_HSIC1 | + PHYPWR_NORMAL_MASK_PHY1); case TYPE_EXYNOS4210: phypwr |= PHYPWR_NORMAL_MASK_PHY0; default: @@ -284,17 +295,14 @@ static int samsung_usb2phy_init(struct usb_phy *phy) /* Disable phy isolation */ if (sphy->plat && sphy->plat->pmu_isolation) sphy->plat->pmu_isolation(false); - else - samsung_usbphy_set_isolation(sphy, false); + else if (sphy->drv_data->set_isolation) + sphy->drv_data->set_isolation(sphy, false); /* Selecting Host/OTG mode; After reset USB2.0PHY_CFG: HOST */ samsung_usbphy_cfg_sel(sphy); /* Initialize usb phy registers */ - if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) - samsung_exynos5_usb2phy_enable(sphy); - else - samsung_usb2phy_enable(sphy); + sphy->drv_data->phy_enable(sphy); spin_unlock_irqrestore(&sphy->lock, flags); @@ -334,16 +342,13 @@ static void samsung_usb2phy_shutdown(struct usb_phy *phy) } /* De-initialize usb phy registers */ - if (sphy->drv_data->cpu_type == TYPE_EXYNOS5250) - samsung_exynos5_usb2phy_disable(sphy); - else - samsung_usb2phy_disable(sphy); + sphy->drv_data->phy_disable(sphy); /* Enable phy isolation */ if (sphy->plat && sphy->plat->pmu_isolation) sphy->plat->pmu_isolation(true); - else - samsung_usbphy_set_isolation(sphy, true); + else if (sphy->drv_data->set_isolation) + sphy->drv_data->set_isolation(sphy, true); spin_unlock_irqrestore(&sphy->lock, flags); @@ -408,7 +413,10 @@ static int samsung_usb2phy_probe(struct platform_device *pdev) sphy->phy.label = "samsung-usb2phy"; sphy->phy.init = samsung_usb2phy_init; sphy->phy.shutdown = samsung_usb2phy_shutdown; - sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + + sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + if (sphy->ref_clk_freq < 0) + return -EINVAL; sphy->phy.otg = otg; sphy->phy.otg->phy = &sphy->phy; @@ -438,18 +446,40 @@ static int samsung_usb2phy_remove(struct platform_device *pdev) static const struct samsung_usbphy_drvdata usb2phy_s3c64xx = { .cpu_type = TYPE_S3C64XX, .devphy_en_mask = S3C64XX_USBPHY_ENABLE, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_64xx, + .set_isolation = NULL, /* TODO */ + .phy_enable = samsung_usb2phy_enable, + .phy_disable = samsung_usb2phy_disable, }; static const struct samsung_usbphy_drvdata usb2phy_exynos4 = { .cpu_type = TYPE_EXYNOS4210, .devphy_en_mask = EXYNOS_USBPHY_ENABLE, .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_64xx, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_usb2phy_enable, + .phy_disable = samsung_usb2phy_disable, +}; + +static const struct samsung_usbphy_drvdata usb2phy_exynos4x12 = { + .cpu_type = TYPE_EXYNOS4X12, + .devphy_en_mask = EXYNOS_USBPHY_ENABLE, + .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_usb2phy_enable, + .phy_disable = samsung_usb2phy_disable, }; static struct samsung_usbphy_drvdata usb2phy_exynos5 = { .cpu_type = TYPE_EXYNOS5250, .hostphy_en_mask = EXYNOS_USBPHY_ENABLE, .hostphy_reg_offset = EXYNOS_USBHOST_PHY_CTRL_OFFSET, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_exynos5_usb2phy_enable, + .phy_disable = samsung_exynos5_usb2phy_disable, }; #ifdef CONFIG_OF @@ -461,6 +491,9 @@ static const struct of_device_id samsung_usbphy_dt_match[] = { .compatible = "samsung,exynos4210-usb2phy", .data = &usb2phy_exynos4, }, { + .compatible = "samsung,exynos4x12-usb2phy", + .data = &usb2phy_exynos4x12, + }, { .compatible = "samsung,exynos5250-usb2phy", .data = &usb2phy_exynos5 }, @@ -477,6 +510,9 @@ static struct platform_device_id samsung_usbphy_driver_ids[] = { .name = "exynos4210-usb2phy", .driver_data = (unsigned long)&usb2phy_exynos4, }, { + .name = "exynos4x12-usb2phy", + .driver_data = (unsigned long)&usb2phy_exynos4x12, + }, { .name = "exynos5250-usb2phy", .driver_data = (unsigned long)&usb2phy_exynos5, }, diff --git a/drivers/usb/phy/phy-samsung-usb3.c b/drivers/usb/phy/phy-samsung-usb3.c index 5a9efcb..300e0cf 100644 --- a/drivers/usb/phy/phy-samsung-usb3.c +++ b/drivers/usb/phy/phy-samsung-usb3.c @@ -65,7 +65,7 @@ static u32 samsung_usb3phy_set_refclk(struct samsung_usbphy *sphy) return reg; } -static int samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) +static void samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) { void __iomem *regs = sphy->regs; u32 phyparam0; @@ -133,8 +133,6 @@ static int samsung_exynos5_usb3phy_enable(struct samsung_usbphy *sphy) phyclkrst &= ~(PHYCLKRST_PORTRESET); writel(phyclkrst, regs + EXYNOS5_DRD_PHYCLKRST); - - return 0; } static void samsung_exynos5_usb3phy_disable(struct samsung_usbphy *sphy) @@ -184,10 +182,11 @@ static int samsung_usb3phy_init(struct usb_phy *phy) samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); /* Disable phy isolation */ - samsung_usbphy_set_isolation(sphy, false); + if (sphy->drv_data->set_isolation) + sphy->drv_data->set_isolation(sphy, false); /* Initialize usb phy registers */ - samsung_exynos5_usb3phy_enable(sphy); + sphy->drv_data->phy_enable(sphy); spin_unlock_irqrestore(&sphy->lock, flags); @@ -218,10 +217,11 @@ static void samsung_usb3phy_shutdown(struct usb_phy *phy) samsung_usbphy_set_type(&sphy->phy, USB_PHY_TYPE_DEVICE); /* De-initialize usb phy registers */ - samsung_exynos5_usb3phy_disable(sphy); + sphy->drv_data->phy_disable(sphy); /* Enable phy isolation */ - samsung_usbphy_set_isolation(sphy, true); + if (sphy->drv_data->set_isolation) + sphy->drv_data->set_isolation(sphy, true); spin_unlock_irqrestore(&sphy->lock, flags); @@ -274,7 +274,10 @@ static int samsung_usb3phy_probe(struct platform_device *pdev) sphy->phy.init = samsung_usb3phy_init; sphy->phy.shutdown = samsung_usb3phy_shutdown; sphy->drv_data = samsung_usbphy_get_driver_data(pdev); - sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + + sphy->ref_clk_freq = samsung_usbphy_get_refclk_freq(sphy); + if (sphy->ref_clk_freq < 0) + return -EINVAL; spin_lock_init(&sphy->lock); @@ -300,6 +303,10 @@ static int samsung_usb3phy_remove(struct platform_device *pdev) static struct samsung_usbphy_drvdata usb3phy_exynos5 = { .cpu_type = TYPE_EXYNOS5250, .devphy_en_mask = EXYNOS_USBPHY_ENABLE, + .rate_to_clksel = samsung_usbphy_rate_to_clksel_4x12, + .set_isolation = samsung_usbphy_set_isolation_4210, + .phy_enable = samsung_exynos5_usb3phy_enable, + .phy_disable = samsung_exynos5_usb3phy_disable, }; #ifdef CONFIG_OF diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index 17d8112..5d9af11 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -1,9 +1,11 @@ /* * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2013 NVIDIA Corporation * * Author: * Erik Gilling <konkers@google.com> * Benoit Goby <benoit@android.com> + * Venu Byravarasu <vbyravarasu@nvidia.com> * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -30,9 +32,7 @@ #include <linux/usb/ulpi.h> #include <asm/mach-types.h> #include <linux/usb/tegra_usb_phy.h> - -#define TEGRA_USB_BASE 0xC5000000 -#define TEGRA_USB_SIZE SZ_16K +#include <linux/module.h> #define ULPI_VIEWPORT 0x170 @@ -198,32 +198,15 @@ static struct tegra_utmip_config utmip_default[] = { static int utmip_pad_open(struct tegra_usb_phy *phy) { - phy->pad_clk = clk_get_sys("utmip-pad", NULL); + phy->pad_clk = devm_clk_get(phy->dev, "utmi-pads"); if (IS_ERR(phy->pad_clk)) { pr_err("%s: can't get utmip pad clock\n", __func__); return PTR_ERR(phy->pad_clk); } - if (phy->is_legacy_phy) { - phy->pad_regs = phy->regs; - } else { - phy->pad_regs = ioremap(TEGRA_USB_BASE, TEGRA_USB_SIZE); - if (!phy->pad_regs) { - pr_err("%s: can't remap usb registers\n", __func__); - clk_put(phy->pad_clk); - return -ENOMEM; - } - } return 0; } -static void utmip_pad_close(struct tegra_usb_phy *phy) -{ - if (!phy->is_legacy_phy) - iounmap(phy->pad_regs); - clk_put(phy->pad_clk); -} - static void utmip_pad_power_on(struct tegra_usb_phy *phy) { unsigned long val, flags; @@ -299,7 +282,7 @@ static void utmi_phy_clk_disable(struct tegra_usb_phy *phy) val &= ~USB_SUSP_SET; writel(val, base + USB_SUSP_CTRL); } else - phy->set_phcd(&phy->u_phy, true); + tegra_ehci_set_phcd(&phy->u_phy, true); if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) < 0) pr_err("%s: timeout waiting for phy to stabilize\n", __func__); @@ -321,7 +304,7 @@ static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) val &= ~USB_SUSP_CLR; writel(val, base + USB_SUSP_CTRL); } else - phy->set_phcd(&phy->u_phy, false); + tegra_ehci_set_phcd(&phy->u_phy, false); if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, USB_PHY_CLK_VALID)) @@ -444,7 +427,7 @@ static int utmi_phy_power_on(struct tegra_usb_phy *phy) utmi_phy_clk_enable(phy); if (!phy->is_legacy_phy) - phy->set_pts(&phy->u_phy, 0); + tegra_ehci_set_pts(&phy->u_phy, 0); return 0; } @@ -541,11 +524,18 @@ static int ulpi_phy_power_on(struct tegra_usb_phy *phy) int ret; unsigned long val; void __iomem *base = phy->regs; - struct tegra_ulpi_config *config = phy->config; - gpio_direction_output(config->reset_gpio, 0); + ret = gpio_direction_output(phy->reset_gpio, 0); + if (ret < 0) { + dev_err(phy->dev, "gpio %d not set to 0\n", phy->reset_gpio); + return ret; + } msleep(5); - gpio_direction_output(config->reset_gpio, 1); + ret = gpio_direction_output(phy->reset_gpio, 1); + if (ret < 0) { + dev_err(phy->dev, "gpio %d not set to 1\n", phy->reset_gpio); + return ret; + } clk_prepare_enable(phy->clk); msleep(1); @@ -603,63 +593,15 @@ static int ulpi_phy_power_on(struct tegra_usb_phy *phy) static int ulpi_phy_power_off(struct tegra_usb_phy *phy) { - struct tegra_ulpi_config *config = phy->config; - clk_disable(phy->clk); - return gpio_direction_output(config->reset_gpio, 0); -} - -static int tegra_phy_init(struct usb_phy *x) -{ - struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); - struct tegra_ulpi_config *ulpi_config; - int err; - - if (phy->is_ulpi_phy) { - ulpi_config = phy->config; - phy->clk = clk_get_sys(NULL, ulpi_config->clk); - if (IS_ERR(phy->clk)) { - pr_err("%s: can't get ulpi clock\n", __func__); - err = -ENXIO; - goto err1; - } - if (!gpio_is_valid(ulpi_config->reset_gpio)) - ulpi_config->reset_gpio = - of_get_named_gpio(phy->dev->of_node, - "nvidia,phy-reset-gpio", 0); - if (!gpio_is_valid(ulpi_config->reset_gpio)) { - pr_err("%s: invalid reset gpio: %d\n", __func__, - ulpi_config->reset_gpio); - err = -EINVAL; - goto err1; - } - gpio_request(ulpi_config->reset_gpio, "ulpi_phy_reset_b"); - gpio_direction_output(ulpi_config->reset_gpio, 0); - phy->ulpi = otg_ulpi_create(&ulpi_viewport_access_ops, 0); - phy->ulpi->io_priv = phy->regs + ULPI_VIEWPORT; - } else { - err = utmip_pad_open(phy); - if (err < 0) - goto err1; - } - return 0; -err1: - clk_disable_unprepare(phy->pll_u); - clk_put(phy->pll_u); - return err; + return gpio_direction_output(phy->reset_gpio, 0); } static void tegra_usb_phy_close(struct usb_phy *x) { struct tegra_usb_phy *phy = container_of(x, struct tegra_usb_phy, u_phy); - if (phy->is_ulpi_phy) - clk_put(phy->clk); - else - utmip_pad_close(phy); clk_disable_unprepare(phy->pll_u); - clk_put(phy->pll_u); - kfree(phy); } static int tegra_usb_phy_power_on(struct tegra_usb_phy *phy) @@ -687,54 +629,63 @@ static int tegra_usb_phy_suspend(struct usb_phy *x, int suspend) return tegra_usb_phy_power_on(phy); } -struct tegra_usb_phy *tegra_usb_phy_open(struct device *dev, int instance, - void __iomem *regs, void *config, enum tegra_usb_phy_mode phy_mode, - void (*set_pts)(struct usb_phy *x, u8 pts_val), - void (*set_phcd)(struct usb_phy *x, bool enable)) +static int ulpi_open(struct tegra_usb_phy *phy) +{ + int err; + + phy->clk = devm_clk_get(phy->dev, "ulpi-link"); + if (IS_ERR(phy->clk)) { + pr_err("%s: can't get ulpi clock\n", __func__); + return PTR_ERR(phy->clk); + } + + err = devm_gpio_request(phy->dev, phy->reset_gpio, "ulpi_phy_reset_b"); + if (err < 0) { + dev_err(phy->dev, "request failed for gpio: %d\n", + phy->reset_gpio); + return err; + } + + err = gpio_direction_output(phy->reset_gpio, 0); + if (err < 0) { + dev_err(phy->dev, "gpio %d direction not set to output\n", + phy->reset_gpio); + return err; + } + + phy->ulpi = otg_ulpi_create(&ulpi_viewport_access_ops, 0); + if (!phy->ulpi) { + dev_err(phy->dev, "otg_ulpi_create returned NULL\n"); + err = -ENOMEM; + return err; + } + phy->ulpi->io_priv = phy->regs + ULPI_VIEWPORT; + return 0; +} + +static int tegra_usb_phy_init(struct tegra_usb_phy *phy) { - struct tegra_usb_phy *phy; unsigned long parent_rate; int i; int err; - struct device_node *np = dev->of_node; - - phy = kzalloc(sizeof(struct tegra_usb_phy), GFP_KERNEL); - if (!phy) - return ERR_PTR(-ENOMEM); - - phy->instance = instance; - phy->regs = regs; - phy->config = config; - phy->mode = phy_mode; - phy->dev = dev; - phy->is_legacy_phy = - of_property_read_bool(np, "nvidia,has-legacy-mode"); - phy->set_pts = set_pts; - phy->set_phcd = set_phcd; - err = of_property_match_string(np, "phy_type", "ulpi"); - if (err < 0) - phy->is_ulpi_phy = false; - else - phy->is_ulpi_phy = true; - - if (!phy->config) { - if (phy->is_ulpi_phy) { - pr_err("%s: ulpi phy configuration missing", __func__); - err = -EINVAL; - goto err0; - } else { - phy->config = &utmip_default[instance]; - } + + if (!phy->is_ulpi_phy) { + if (phy->is_legacy_phy) + phy->config = &utmip_default[0]; + else + phy->config = &utmip_default[2]; } - phy->pll_u = clk_get_sys(NULL, "pll_u"); + phy->pll_u = devm_clk_get(phy->dev, "pll_u"); if (IS_ERR(phy->pll_u)) { pr_err("Can't get pll_u clock\n"); - err = PTR_ERR(phy->pll_u); - goto err0; + return PTR_ERR(phy->pll_u); } - clk_prepare_enable(phy->pll_u); + + err = clk_prepare_enable(phy->pll_u); + if (err) + return err; parent_rate = clk_get_rate(clk_get_parent(phy->pll_u)); for (i = 0; i < ARRAY_SIZE(tegra_freq_table); i++) { @@ -746,23 +697,22 @@ struct tegra_usb_phy *tegra_usb_phy_open(struct device *dev, int instance, if (!phy->freq) { pr_err("invalid pll_u parent rate %ld\n", parent_rate); err = -EINVAL; - goto err1; + goto fail; } - phy->u_phy.init = tegra_phy_init; - phy->u_phy.shutdown = tegra_usb_phy_close; - phy->u_phy.set_suspend = tegra_usb_phy_suspend; + if (phy->is_ulpi_phy) + err = ulpi_open(phy); + else + err = utmip_pad_open(phy); + if (err < 0) + goto fail; - return phy; + return 0; -err1: +fail: clk_disable_unprepare(phy->pll_u); - clk_put(phy->pll_u); -err0: - kfree(phy); - return ERR_PTR(err); + return err; } -EXPORT_SYMBOL_GPL(tegra_usb_phy_open); void tegra_usb_phy_preresume(struct usb_phy *x) { @@ -801,3 +751,121 @@ void tegra_ehci_phy_restore_end(struct usb_phy *x) } EXPORT_SYMBOL_GPL(tegra_ehci_phy_restore_end); +static int tegra_usb_phy_probe(struct platform_device *pdev) +{ + struct resource *res; + struct tegra_usb_phy *tegra_phy = NULL; + struct device_node *np = pdev->dev.of_node; + int err; + + tegra_phy = devm_kzalloc(&pdev->dev, sizeof(*tegra_phy), GFP_KERNEL); + if (!tegra_phy) { + dev_err(&pdev->dev, "unable to allocate memory for USB2 PHY\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + return -ENXIO; + } + + tegra_phy->regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!tegra_phy->regs) { + dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + return -ENOMEM; + } + + tegra_phy->is_legacy_phy = + of_property_read_bool(np, "nvidia,has-legacy-mode"); + + err = of_property_match_string(np, "phy_type", "ulpi"); + if (err < 0) { + tegra_phy->is_ulpi_phy = false; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&pdev->dev, "Failed to get UTMI Pad regs\n"); + return -ENXIO; + } + + tegra_phy->pad_regs = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!tegra_phy->regs) { + dev_err(&pdev->dev, "Failed to remap UTMI Pad regs\n"); + return -ENOMEM; + } + } else { + tegra_phy->is_ulpi_phy = true; + + tegra_phy->reset_gpio = + of_get_named_gpio(np, "nvidia,phy-reset-gpio", 0); + if (!gpio_is_valid(tegra_phy->reset_gpio)) { + dev_err(&pdev->dev, "invalid gpio: %d\n", + tegra_phy->reset_gpio); + return tegra_phy->reset_gpio; + } + } + + err = of_property_match_string(np, "dr_mode", "otg"); + if (err < 0) { + err = of_property_match_string(np, "dr_mode", "peripheral"); + if (err < 0) + tegra_phy->mode = TEGRA_USB_PHY_MODE_HOST; + else + tegra_phy->mode = TEGRA_USB_PHY_MODE_DEVICE; + } else + tegra_phy->mode = TEGRA_USB_PHY_MODE_OTG; + + tegra_phy->dev = &pdev->dev; + err = tegra_usb_phy_init(tegra_phy); + if (err < 0) + return err; + + tegra_phy->u_phy.shutdown = tegra_usb_phy_close; + tegra_phy->u_phy.set_suspend = tegra_usb_phy_suspend; + + dev_set_drvdata(&pdev->dev, tegra_phy); + return 0; +} + +static struct of_device_id tegra_usb_phy_id_table[] = { + { .compatible = "nvidia,tegra20-usb-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_usb_phy_id_table); + +static struct platform_driver tegra_usb_phy_driver = { + .probe = tegra_usb_phy_probe, + .driver = { + .name = "tegra-phy", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tegra_usb_phy_id_table), + }, +}; +module_platform_driver(tegra_usb_phy_driver); + +static int tegra_usb_phy_match(struct device *dev, void *data) +{ + struct tegra_usb_phy *tegra_phy = dev_get_drvdata(dev); + struct device_node *dn = data; + + return (tegra_phy->dev->of_node == dn) ? 1 : 0; +} + +struct usb_phy *tegra_usb_get_phy(struct device_node *dn) +{ + struct device *dev; + struct tegra_usb_phy *tegra_phy; + + dev = driver_find_device(&tegra_usb_phy_driver.driver, NULL, dn, + tegra_usb_phy_match); + if (!dev) + return ERR_PTR(-EPROBE_DEFER); + + tegra_phy = dev_get_drvdata(dev); + + return &tegra_phy->u_phy; +} +EXPORT_SYMBOL_GPL(tegra_usb_get_phy); diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 1d55762..8c3a42e 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -710,6 +710,16 @@ config USB_SERIAL_QT2 To compile this driver as a module, choose M here: the module will be called quatech-serial. +config USB_SERIAL_FLASHLOADER + tristate "Infineon Modem Flashloader USB interface driver" + help + Say Y here if you want to download Infineon Modem + via USB Flashloader serial driver. + + To compile this driver as a module, choose M here: the + module will be called flashloader. + + config USB_SERIAL_DEBUG tristate "USB Debugging Device" help diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index cec63fa..f713011 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -65,3 +65,4 @@ obj-$(CONFIG_USB_SERIAL_VIVOPAY_SERIAL) += vivopay-serial.o obj-$(CONFIG_USB_SERIAL_XSENS_MT) += xsens_mt.o obj-$(CONFIG_USB_SERIAL_ZIO) += zio.o obj-$(CONFIG_USB_SERIAL_ZTE) += zte_ev.o +obj-$(CONFIG_USB_SERIAL_FLASHLOADER) += flashloader.o diff --git a/drivers/usb/serial/ark3116.c b/drivers/usb/serial/ark3116.c index 40e7fd9..293a770 100644 --- a/drivers/usb/serial/ark3116.c +++ b/drivers/usb/serial/ark3116.c @@ -414,7 +414,7 @@ static int ark3116_ioctl(struct tty_struct *tty, memset(&serstruct, 0, sizeof(serstruct)); serstruct.type = PORT_16654; serstruct.line = port->serial->minor; - serstruct.port = port->number; + serstruct.port = port->port_number; serstruct.custom_divisor = 0; serstruct.baud_base = 460800; diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 3c4db6d..f053b30 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -43,7 +43,7 @@ static ssize_t show_port_number(struct device *dev, { struct usb_serial_port *port = to_usb_serial_port(dev); - return sprintf(buf, "%d\n", port->number - port->serial->minor); + return sprintf(buf, "%d\n", port->port_number); } static DEVICE_ATTR(port_number, S_IRUGO, show_port_number, NULL); @@ -80,7 +80,7 @@ static int usb_serial_device_probe(struct device *dev) goto exit_with_autopm; } - minor = port->number; + minor = port->minor; tty_register_device(usb_serial_tty_driver, minor, dev); dev_info(&port->serial->dev->dev, "%s converter now attached to ttyUSB%d\n", @@ -106,7 +106,7 @@ static int usb_serial_device_remove(struct device *dev) /* make sure suspend/resume doesn't race against port_remove */ usb_autopm_get_interface(port->serial->interface); - minor = port->number; + minor = port->minor; tty_unregister_device(usb_serial_tty_driver, minor); device_remove_file(&port->dev, &dev_attr_port_number); diff --git a/drivers/usb/serial/console.c b/drivers/usb/serial/console.c index 5f3bcd3..1b81102 100644 --- a/drivers/usb/serial/console.c +++ b/drivers/usb/serial/console.c @@ -210,7 +210,7 @@ static void usb_console_write(struct console *co, if (count == 0) return; - pr_debug("%s - port %d, %d byte(s)\n", __func__, port->number, count); + pr_debug("%s - minor %d, %d byte(s)\n", __func__, port->minor, count); if (!port->port.console) { pr_debug("%s - port not opened\n", __func__); diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 2c65955..d6ef2f8 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -666,8 +666,6 @@ static void cp210x_set_termios(struct tty_struct *tty, unsigned int bits; unsigned int modem_ctl[4]; - dev_dbg(dev, "%s - port %d\n", __func__, port->number); - if (!tty) return; diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index 0821201..e948dc0 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -435,7 +435,7 @@ static void cypress_set_dead(struct usb_serial_port *port) spin_unlock_irqrestore(&priv->lock, flags); dev_err(&port->dev, "cypress_m8 suspending failing port %d - " - "interval might be too short\n", port->number); + "interval might be too short\n", port->port_number); } @@ -667,7 +667,7 @@ static int cypress_write(struct tty_struct *tty, struct usb_serial_port *port, { struct cypress_private *priv = usb_get_serial_port_data(port); - dev_dbg(&port->dev, "%s - port %d, %d bytes\n", __func__, port->number, count); + dev_dbg(&port->dev, "%s - %d bytes\n", __func__, count); /* line control commands, which need to be executed immediately, are not put into the buffer for obvious reasons. diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 7b807d3..19b467f 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -1304,11 +1304,7 @@ static void digi_release(struct usb_serial *serial) static int digi_port_probe(struct usb_serial_port *port) { - unsigned port_num; - - port_num = port->number - port->serial->minor; - - return digi_port_init(port, port_num); + return digi_port_init(port, port->port_number); } static int digi_port_remove(struct usb_serial_port *port) diff --git a/drivers/usb/serial/f81232.c b/drivers/usb/serial/f81232.c index 7d8dd5a..3d53039 100644 --- a/drivers/usb/serial/f81232.c +++ b/drivers/usb/serial/f81232.c @@ -288,15 +288,14 @@ static int f81232_ioctl(struct tty_struct *tty, struct serial_struct ser; struct usb_serial_port *port = tty->driver_data; - dev_dbg(&port->dev, "%s (%d) cmd = 0x%04x\n", __func__, - port->number, cmd); + dev_dbg(&port->dev, "%s cmd = 0x%04x\n", __func__, cmd); switch (cmd) { case TIOCGSERIAL: memset(&ser, 0, sizeof ser); ser.type = PORT_16654; ser.line = port->serial->minor; - ser.port = port->number; + ser.port = port->port_number; ser.baud_base = 460800; if (copy_to_user((void __user *)arg, &ser, sizeof ser)) diff --git a/drivers/usb/serial/flashloader.c b/drivers/usb/serial/flashloader.c new file mode 100644 index 0000000..e6f5c10 --- /dev/null +++ b/drivers/usb/serial/flashloader.c @@ -0,0 +1,39 @@ +/* + * Infineon Flashloader driver + * + * Copyright (C) 2013 Wei Shuai <cpuwolf@gmail.com> + * + * 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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/tty.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/usb/serial.h> +#include <linux/uaccess.h> + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(0x8087, 0x0716) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_serial_driver flashloader_device = { + .driver = { + .owner = THIS_MODULE, + .name = "flashloader", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static struct usb_serial_driver * const serial_drivers[] = { + &flashloader_device, NULL +}; + +module_usb_serial_driver(serial_drivers, id_table); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c index b110c57..04b5ed9 100644 --- a/drivers/usb/serial/garmin_gps.c +++ b/drivers/usb/serial/garmin_gps.c @@ -948,9 +948,9 @@ static void garmin_close(struct usb_serial_port *port) { struct garmin_data *garmin_data_p = usb_get_serial_port_data(port); - dev_dbg(&port->dev, "%s - port %d - mode=%d state=%d flags=0x%X\n", - __func__, port->number, garmin_data_p->mode, - garmin_data_p->state, garmin_data_p->flags); + dev_dbg(&port->dev, "%s - mode=%d state=%d flags=0x%X\n", + __func__, garmin_data_p->mode, garmin_data_p->state, + garmin_data_p->flags); garmin_clear(garmin_data_p); diff --git a/drivers/usb/serial/io_edgeport.c b/drivers/usb/serial/io_edgeport.c index 1477e85..0c27ff3 100644 --- a/drivers/usb/serial/io_edgeport.c +++ b/drivers/usb/serial/io_edgeport.c @@ -915,8 +915,8 @@ static int edge_open(struct tty_struct *tty, struct usb_serial_port *port) return -ENOMEM; } - dev_dbg(dev, "%s(%d) - Initialize TX fifo to %d bytes\n", - __func__, port->number, edge_port->maxTxCredits); + dev_dbg(dev, "%s - Initialize TX fifo to %d bytes\n", + __func__, edge_port->maxTxCredits); return 0; } @@ -1122,9 +1122,8 @@ static int edge_write(struct tty_struct *tty, struct usb_serial_port *port, copySize = min((unsigned int)count, (edge_port->txCredits - fifo->count)); - dev_dbg(&port->dev, "%s(%d) of %d byte(s) Fifo room %d -- will copy %d bytes\n", - __func__, port->number, count, - edge_port->txCredits - fifo->count, copySize); + dev_dbg(&port->dev, "%s of %d byte(s) Fifo room %d -- will copy %d bytes\n", + __func__, count, edge_port->txCredits - fifo->count, copySize); /* catch writes of 0 bytes which the tty driver likes to give us, and when txCredits is empty */ @@ -1216,9 +1215,8 @@ static void send_more_port_data(struct edgeport_serial *edge_serial, if (edge_port->write_in_progress || !edge_port->open || (fifo->count == 0)) { - dev_dbg(dev, "%s(%d) EXIT - fifo %d, PendingWrite = %d\n", - __func__, edge_port->port->number, - fifo->count, edge_port->write_in_progress); + dev_dbg(dev, "%s EXIT - fifo %d, PendingWrite = %d\n", + __func__, fifo->count, edge_port->write_in_progress); goto exit_send; } @@ -1230,9 +1228,8 @@ static void send_more_port_data(struct edgeport_serial *edge_serial, * it's better to wait for more credits so we can do a larger write. */ if (edge_port->txCredits < EDGE_FW_GET_TX_CREDITS_SEND_THRESHOLD(edge_port->maxTxCredits, EDGE_FW_BULK_MAX_PACKET_SIZE)) { - dev_dbg(dev, "%s(%d) Not enough credit - fifo %d TxCredit %d\n", - __func__, edge_port->port->number, fifo->count, - edge_port->txCredits); + dev_dbg(dev, "%s Not enough credit - fifo %d TxCredit %d\n", + __func__, fifo->count, edge_port->txCredits); goto exit_send; } @@ -1256,10 +1253,8 @@ static void send_more_port_data(struct edgeport_serial *edge_serial, edge_port->write_in_progress = false; goto exit_send; } - buffer[0] = IOSP_BUILD_DATA_HDR1(edge_port->port->number - - edge_port->port->serial->minor, count); - buffer[1] = IOSP_BUILD_DATA_HDR2(edge_port->port->number - - edge_port->port->serial->minor, count); + buffer[0] = IOSP_BUILD_DATA_HDR1(edge_port->port->port_number, count); + buffer[1] = IOSP_BUILD_DATA_HDR2(edge_port->port->port_number, count); /* now copy our data */ bytesleft = fifo->size - fifo->tail; @@ -1377,8 +1372,7 @@ static int edge_chars_in_buffer(struct tty_struct *tty) edge_port->txfifo.count; spin_unlock_irqrestore(&edge_port->ep_lock, flags); if (num_chars) { - dev_dbg(&port->dev, "%s(port %d) - returns %d\n", __func__, - port->number, num_chars); + dev_dbg(&port->dev, "%s - returns %d\n", __func__, num_chars); } return num_chars; @@ -1576,7 +1570,7 @@ static int get_serial_info(struct edgeport_port *edge_port, tmp.type = PORT_16550A; tmp.line = edge_port->port->serial->minor; - tmp.port = edge_port->port->number; + tmp.port = edge_port->port->port_number; tmp.irq = 0; tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; tmp.xmit_fifo_size = edge_port->maxTxCredits; @@ -1601,15 +1595,15 @@ static int edge_ioctl(struct tty_struct *tty, DEFINE_WAIT(wait); struct edgeport_port *edge_port = usb_get_serial_port_data(port); - dev_dbg(&port->dev, "%s - port %d, cmd = 0x%x\n", __func__, port->number, cmd); + dev_dbg(&port->dev, "%s - cmd = 0x%x\n", __func__, cmd); switch (cmd) { case TIOCSERGETLSR: - dev_dbg(&port->dev, "%s (%d) TIOCSERGETLSR\n", __func__, port->number); + dev_dbg(&port->dev, "%s TIOCSERGETLSR\n", __func__); return get_lsr_info(edge_port, (unsigned int __user *) arg); case TIOCGSERIAL: - dev_dbg(&port->dev, "%s (%d) TIOCGSERIAL\n", __func__, port->number); + dev_dbg(&port->dev, "%s TIOCGSERIAL\n", __func__); return get_serial_info(edge_port, (struct serial_struct __user *) arg); } return -ENOIOCTLCMD; @@ -2181,9 +2175,8 @@ static int send_iosp_ext_cmd(struct edgeport_port *edge_port, currentCommand = buffer; - MAKE_CMD_EXT_CMD(¤tCommand, &length, - edge_port->port->number - edge_port->port->serial->minor, - command, param); + MAKE_CMD_EXT_CMD(¤tCommand, &length, edge_port->port->port_number, + command, param); status = write_cmd_usb(edge_port, buffer, length); if (status) { @@ -2266,18 +2259,16 @@ static int send_cmd_write_baud_rate(struct edgeport_port *edge_port, int cmdLen = 0; int divisor; int status; - unsigned char number = - edge_port->port->number - edge_port->port->serial->minor; + u32 number = edge_port->port->port_number; if (edge_serial->is_epic && !edge_serial->epic_descriptor.Supports.IOSPSetBaudRate) { - dev_dbg(dev, "SendCmdWriteBaudRate - NOT Setting baud rate for port = %d, baud = %d\n", - edge_port->port->number, baudRate); + dev_dbg(dev, "SendCmdWriteBaudRate - NOT Setting baud rate for port, baud = %d\n", + baudRate); return 0; } - dev_dbg(dev, "%s - port = %d, baud = %d\n", __func__, - edge_port->port->number, baudRate); + dev_dbg(dev, "%s - baud = %d\n", __func__, baudRate); status = calc_baud_rate_divisor(dev, baudRate, &divisor); if (status) { @@ -2388,9 +2379,8 @@ static int send_cmd_write_uart_register(struct edgeport_port *edge_port, currCmd = cmdBuffer; /* Build a cmd in the buffer to write the given register */ - MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, - edge_port->port->number - edge_port->port->serial->minor, - regNum, regValue); + MAKE_CMD_WRITE_REG(&currCmd, &cmdLen, edge_port->port->port_number, + regNum, regValue); status = write_cmd_usb(edge_port, cmdBuffer, cmdLen); if (status) { @@ -2424,8 +2414,6 @@ static void change_port_settings(struct tty_struct *tty, __u8 txFlow; int status; - dev_dbg(dev, "%s - port %d\n", __func__, edge_port->port->number); - if (!edge_port->open && !edge_port->openPending) { dev_dbg(dev, "%s - port not opened\n", __func__); diff --git a/drivers/usb/serial/io_ti.c b/drivers/usb/serial/io_ti.c index 1be6ba7..d32bf2b 100644 --- a/drivers/usb/serial/io_ti.c +++ b/drivers/usb/serial/io_ti.c @@ -259,7 +259,7 @@ static int send_cmd(struct usb_device *dev, __u8 command, /* clear tx/rx buffers and fifo in TI UMP */ static int purge_port(struct usb_serial_port *port, __u16 mask) { - int port_number = port->number - port->serial->minor; + int port_number = port->port_number; dev_dbg(&port->dev, "%s - port %d, mask %x\n", __func__, port_number, mask); @@ -1392,7 +1392,8 @@ stayinbootmode: static int ti_do_config(struct edgeport_port *port, int feature, int on) { - int port_number = port->port->number - port->port->serial->minor; + int port_number = port->port->port_number; + on = !!on; /* 1 or 0 not bitmask */ return send_cmd(port->port->serial->dev, feature, (__u8)(UMPM_UART1_PORT + port_number), @@ -1637,7 +1638,7 @@ static void edge_bulk_in_callback(struct urb *urb) return; } - port_number = edge_port->port->number - edge_port->port->serial->minor; + port_number = edge_port->port->port_number; if (edge_port->lsr_event) { edge_port->lsr_event = 0; @@ -1730,7 +1731,7 @@ static int edge_open(struct tty_struct *tty, struct usb_serial_port *port) if (edge_port == NULL) return -ENODEV; - port_number = port->number - port->serial->minor; + port_number = port->port_number; switch (port_number) { case 0: edge_port->uart_base = UMPMEM_BASE_UART1; @@ -1908,7 +1909,7 @@ static void edge_close(struct usb_serial_port *port) spin_unlock_irqrestore(&edge_port->ep_lock, flags); dev_dbg(&port->dev, "%s - send umpc_close_port\n", __func__); - port_number = port->number - port->serial->minor; + port_number = port->port_number; send_cmd(serial->dev, UMPC_CLOSE_PORT, (__u8)(UMPM_UART1_PORT + port_number), 0, NULL, 0); @@ -2137,10 +2138,7 @@ static void change_port_settings(struct tty_struct *tty, int baud; unsigned cflag; int status; - int port_number = edge_port->port->number - - edge_port->port->serial->minor; - - dev_dbg(dev, "%s - port %d\n", __func__, edge_port->port->number); + int port_number = edge_port->port->port_number; config = kmalloc (sizeof (*config), GFP_KERNEL); if (!config) { @@ -2284,7 +2282,6 @@ static void edge_set_termios(struct tty_struct *tty, tty->termios.c_cflag, tty->termios.c_iflag); dev_dbg(&port->dev, "%s - old clfag %08x old iflag %08x\n", __func__, old_termios->c_cflag, old_termios->c_iflag); - dev_dbg(&port->dev, "%s - port %d\n", __func__, port->number); if (edge_port == NULL) return; @@ -2367,7 +2364,7 @@ static int get_serial_info(struct edgeport_port *edge_port, tmp.type = PORT_16550A; tmp.line = edge_port->port->serial->minor; - tmp.port = edge_port->port->number; + tmp.port = edge_port->port->port_number; tmp.irq = 0; tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; tmp.xmit_fifo_size = edge_port->port->bulk_out_size; @@ -2386,7 +2383,7 @@ static int edge_ioctl(struct tty_struct *tty, struct usb_serial_port *port = tty->driver_data; struct edgeport_port *edge_port = usb_get_serial_port_data(port); - dev_dbg(&port->dev, "%s - port %d, cmd = 0x%x\n", __func__, port->number, cmd); + dev_dbg(&port->dev, "%s - cmd = 0x%x\n", __func__, cmd); switch (cmd) { case TIOCGSERIAL: diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c index 3549d07..5a97972 100644 --- a/drivers/usb/serial/keyspan.c +++ b/drivers/usb/serial/keyspan.c @@ -152,7 +152,7 @@ static void keyspan_set_termios(struct tty_struct *tty, p_priv = usb_get_serial_port_data(port); d_details = p_priv->device_details; cflag = tty->termios.c_cflag; - device_port = port->number - port->serial->minor; + device_port = port->port_number; /* Baud rate calculation takes baud rate as an integer so other rates can be generated if desired. */ @@ -234,8 +234,8 @@ static int keyspan_write(struct tty_struct *tty, dataOffset = 1; } - dev_dbg(&port->dev, "%s - for port %d (%d chars), flip=%d\n", - __func__, port->number, count, p_priv->out_flip); + dev_dbg(&port->dev, "%s - %d chars, flip=%d\n", __func__, count, + p_priv->out_flip); for (left = count; left > 0; left -= todo) { todo = left; @@ -520,12 +520,7 @@ static void usa28_instat_callback(struct urb *urb) goto exit; } - /* - dev_dbg(&urb->dev->dev, - "%s %x %x %x %x %x %x %x %x %x %x %x %x", __func__, - data[0], data[1], data[2], data[3], data[4], data[5], - data[6], data[7], data[8], data[9], data[10], data[11]); - */ + /*dev_dbg(&urb->dev->dev, "%s %12ph", __func__, data);*/ /* Now do something useful with the data */ msg = (struct keyspan_usa28_portStatusMessage *)data; @@ -607,11 +602,7 @@ static void usa49_instat_callback(struct urb *urb) goto exit; } - /* - dev_dbg(&urb->dev->dev, "%s: %x %x %x %x %x %x %x %x %x %x %x", - __func__, data[0], data[1], data[2], data[3], data[4], - data[5], data[6], data[7], data[8], data[9], data[10]); - */ + /*dev_dbg(&urb->dev->dev, "%s: %11ph", __func__, data);*/ /* Now do something useful with the data */ msg = (struct keyspan_usa49_portStatusMessage *)data; @@ -1050,7 +1041,7 @@ static int keyspan_open(struct tty_struct *tty, struct usb_serial_port *port) /* get the terminal config for the setup message now so we don't * need to send 2 of them */ - device_port = port->number - port->serial->minor; + device_port = port->port_number; if (tty) { cflag = tty->termios.c_cflag; /* Baud rate calculation takes baud rate as an integer @@ -1556,7 +1547,7 @@ static int keyspan_usa26_send_setup(struct usb_serial *serial, s_priv = usb_get_serial_data(serial); p_priv = usb_get_serial_port_data(port); d_details = s_priv->device_details; - device_port = port->number - port->serial->minor; + device_port = port->port_number; this_urb = p_priv->outcont_urb; @@ -1700,7 +1691,7 @@ static int keyspan_usa28_send_setup(struct usb_serial *serial, s_priv = usb_get_serial_data(serial); p_priv = usb_get_serial_port_data(port); d_details = s_priv->device_details; - device_port = port->number - port->serial->minor; + device_port = port->port_number; /* only do something if we have a bulk out endpoint */ this_urb = p_priv->outcont_urb; @@ -1830,17 +1821,16 @@ static int keyspan_usa49_send_setup(struct usb_serial *serial, this_urb = s_priv->glocont_urb; /* Work out which port within the device is being setup */ - device_port = port->number - port->serial->minor; + device_port = port->port_number; /* Make sure we have an urb then send the message */ if (this_urb == NULL) { - dev_dbg(&port->dev, "%s - oops no urb for port %d.\n", __func__, port->number); + dev_dbg(&port->dev, "%s - oops no urb for port.\n", __func__); return -1; } - dev_dbg(&port->dev, "%s - endpoint %d port %d (%d)\n", - __func__, usb_pipeendpoint(this_urb->pipe), - port->number, device_port); + dev_dbg(&port->dev, "%s - endpoint %d (%d)\n", + __func__, usb_pipeendpoint(this_urb->pipe), device_port); /* Save reset port val for resend. Don't overwrite resend for open/close condition. */ @@ -1855,7 +1845,6 @@ static int keyspan_usa49_send_setup(struct usb_serial *serial, memset(&msg, 0, sizeof(struct keyspan_usa49_portControlMessage)); - /*msg.portNumber = port->number;*/ msg.portNumber = device_port; /* Only set baud rate if it's changed */ @@ -2145,12 +2134,11 @@ static int keyspan_usa67_send_setup(struct usb_serial *serial, this_urb = s_priv->glocont_urb; /* Work out which port within the device is being setup */ - device_port = port->number - port->serial->minor; + device_port = port->port_number; /* Make sure we have an urb then send the message */ if (this_urb == NULL) { - dev_dbg(&port->dev, "%s - oops no urb for port %d.\n", __func__, - port->number); + dev_dbg(&port->dev, "%s - oops no urb for port.\n", __func__); return -1; } @@ -2391,7 +2379,7 @@ static int keyspan_port_probe(struct usb_serial_port *port) /* Setup values for the various callback routines */ cback = &keyspan_callbacks[d_details->msg_format]; - port_num = port->number - port->serial->minor; + port_num = port->port_number; /* Do indat endpoints first, once for each flip */ endp = d_details->indat_endpoints[port_num]; diff --git a/drivers/usb/serial/metro-usb.c b/drivers/usb/serial/metro-usb.c index 47e2477..40ccf6e 100644 --- a/drivers/usb/serial/metro-usb.c +++ b/drivers/usb/serial/metro-usb.c @@ -224,8 +224,8 @@ static int metrousb_open(struct tty_struct *tty, struct usb_serial_port *port) result = metrousb_send_unidirectional_cmd(UNI_CMD_OPEN, port); if (result) { dev_err(&port->dev, - "%s - failed to configure device for port number=%d, error code=%d\n", - __func__, port->number, result); + "%s - failed to configure device, error code=%d\n", + __func__, result); goto exit; } diff --git a/drivers/usb/serial/mos7720.c b/drivers/usb/serial/mos7720.c index f27c621..f79ae7f 100644 --- a/drivers/usb/serial/mos7720.c +++ b/drivers/usb/serial/mos7720.c @@ -1047,7 +1047,7 @@ static int mos7720_open(struct tty_struct *tty, struct usb_serial_port *port) * * 0x08 : SP1/2 Control Reg */ - port_number = port->number - port->serial->minor; + port_number = port->port_number; read_mos_reg(serial, port_number, LSR, &data); dev_dbg(&port->dev, "SS::%p LSR:%x\n", mos7720_port, data); @@ -1066,7 +1066,7 @@ static int mos7720_open(struct tty_struct *tty, struct usb_serial_port *port) write_mos_reg(serial, port_number, SP_CONTROL_REG, 0x00); read_mos_reg(serial, dummy, SP_CONTROL_REG, &data); - data = data | (port->number - port->serial->minor + 1); + data = data | (port->port_number + 1); write_mos_reg(serial, dummy, SP_CONTROL_REG, data); mos7720_port->shadowLCR = 0x83; write_mos_reg(serial, port_number, LCR, mos7720_port->shadowLCR); @@ -1147,8 +1147,8 @@ static void mos7720_close(struct usb_serial_port *port) usb_kill_urb(port->write_urb); usb_kill_urb(port->read_urb); - write_mos_reg(serial, port->number - port->serial->minor, MCR, 0x00); - write_mos_reg(serial, port->number - port->serial->minor, IER, 0x00); + write_mos_reg(serial, port->port_number, MCR, 0x00); + write_mos_reg(serial, port->port_number, IER, 0x00); mos7720_port->open = 0; } @@ -1172,8 +1172,7 @@ static void mos7720_break(struct tty_struct *tty, int break_state) data = mos7720_port->shadowLCR & ~UART_LCR_SBC; mos7720_port->shadowLCR = data; - write_mos_reg(serial, port->number - port->serial->minor, - LCR, mos7720_port->shadowLCR); + write_mos_reg(serial, port->port_number, LCR, mos7720_port->shadowLCR); } /* @@ -1304,8 +1303,8 @@ static void mos7720_throttle(struct tty_struct *tty) /* if we are implementing RTS/CTS, toggle that line */ if (tty->termios.c_cflag & CRTSCTS) { mos7720_port->shadowMCR &= ~UART_MCR_RTS; - write_mos_reg(port->serial, port->number - port->serial->minor, - MCR, mos7720_port->shadowMCR); + write_mos_reg(port->serial, port->port_number, MCR, + mos7720_port->shadowMCR); if (status != 0) return; } @@ -1336,8 +1335,8 @@ static void mos7720_unthrottle(struct tty_struct *tty) /* if we are implementing RTS/CTS, toggle that line */ if (tty->termios.c_cflag & CRTSCTS) { mos7720_port->shadowMCR |= UART_MCR_RTS; - write_mos_reg(port->serial, port->number - port->serial->minor, - MCR, mos7720_port->shadowMCR); + write_mos_reg(port->serial, port->port_number, MCR, + mos7720_port->shadowMCR); if (status != 0) return; } @@ -1361,7 +1360,7 @@ static int set_higher_rates(struct moschip_port *mos7720_port, * Init Sequence for higher rates ***********************************************/ dev_dbg(&port->dev, "Sending Setting Commands ..........\n"); - port_number = port->number - port->serial->minor; + port_number = port->port_number; write_mos_reg(serial, port_number, IER, 0x00); write_mos_reg(serial, port_number, FCR, 0x00); @@ -1487,7 +1486,7 @@ static int send_cmd_write_baud_rate(struct moschip_port *mos7720_port, port = mos7720_port->port; serial = port->serial; - number = port->number - port->serial->minor; + number = port->port_number; dev_dbg(&port->dev, "%s - baud = %d\n", __func__, baudrate); /* Calculate the Divisor */ @@ -1538,7 +1537,7 @@ static void change_port_settings(struct tty_struct *tty, port = mos7720_port->port; serial = port->serial; - port_number = port->number - port->serial->minor; + port_number = port->port_number; if (!mos7720_port->open) { dev_dbg(&port->dev, "%s - port not opened\n", __func__); @@ -1731,7 +1730,7 @@ static int get_lsr_info(struct tty_struct *tty, struct usb_serial_port *port = tty->driver_data; unsigned int result = 0; unsigned char data = 0; - int port_number = port->number - port->serial->minor; + int port_number = port->port_number; int count; count = mos7720_chars_in_buffer(tty); @@ -1793,8 +1792,8 @@ static int mos7720_tiocmset(struct tty_struct *tty, mcr &= ~UART_MCR_LOOP; mos7720_port->shadowMCR = mcr; - write_mos_reg(port->serial, port->number - port->serial->minor, - MCR, mos7720_port->shadowMCR); + write_mos_reg(port->serial, port->port_number, MCR, + mos7720_port->shadowMCR); return 0; } @@ -1838,8 +1837,8 @@ static int set_modem_info(struct moschip_port *mos7720_port, unsigned int cmd, } mos7720_port->shadowMCR = mcr; - write_mos_reg(port->serial, port->number - port->serial->minor, - MCR, mos7720_port->shadowMCR); + write_mos_reg(port->serial, port->port_number, MCR, + mos7720_port->shadowMCR); return 0; } @@ -1856,7 +1855,7 @@ static int get_serial_info(struct moschip_port *mos7720_port, tmp.type = PORT_16550A; tmp.line = mos7720_port->port->serial->minor; - tmp.port = mos7720_port->port->number; + tmp.port = mos7720_port->port->port_number; tmp.irq = 0; tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; tmp.xmit_fifo_size = NUM_URBS * URB_TRANSFER_BUFFER_SIZE; diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 7e99808..f981b08 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -303,15 +303,12 @@ static int mos7840_set_uart_reg(struct usb_serial_port *port, __u16 reg, /* For the UART control registers, the application number need to be Or'ed */ if (port->serial->num_ports == 4) { - val |= (((__u16) port->number - - (__u16) (port->serial->minor)) + 1) << 8; + val |= ((__u16)port->port_number + 1) << 8; } else { - if (((__u16) port->number - (__u16) (port->serial->minor)) == 0) { - val |= (((__u16) port->number - - (__u16) (port->serial->minor)) + 1) << 8; + if (port->port_number == 0) { + val |= ((__u16)port->port_number + 1) << 8; } else { - val |= (((__u16) port->number - - (__u16) (port->serial->minor)) + 2) << 8; + val |= ((__u16)port->port_number + 2) << 8; } } dev_dbg(&port->dev, "%s application number is %x\n", __func__, val); @@ -340,16 +337,12 @@ static int mos7840_get_uart_reg(struct usb_serial_port *port, __u16 reg, /* Wval is same as application number */ if (port->serial->num_ports == 4) { - Wval = - (((__u16) port->number - (__u16) (port->serial->minor)) + - 1) << 8; + Wval = ((__u16)port->port_number + 1) << 8; } else { - if (((__u16) port->number - (__u16) (port->serial->minor)) == 0) { - Wval = (((__u16) port->number - - (__u16) (port->serial->minor)) + 1) << 8; + if (port->port_number == 0) { + Wval = ((__u16)port->port_number + 1) << 8; } else { - Wval = (((__u16) port->number - - (__u16) (port->serial->minor)) + 2) << 8; + Wval = ((__u16)port->port_number + 2) << 8; } } dev_dbg(&port->dev, "%s application number is %x\n", __func__, Wval); @@ -631,9 +624,7 @@ static void mos7840_interrupt_callback(struct urb *urb) for (i = 0; i < serial->num_ports; i++) { mos7840_port = mos7840_get_port_private(serial->port[i]); - wval = - (((__u16) serial->port[i]->number - - (__u16) (serial->minor)) + 1) << 8; + wval = ((__u16)serial->port[i]->port_number + 1) << 8; if (mos7840_port->open) { if (sp[i] & 0x01) { dev_dbg(&urb->dev->dev, "SP%d No Interrupt !!!\n", i); @@ -1065,8 +1056,8 @@ static int mos7840_open(struct tty_struct *tty, struct usb_serial_port *port) * (can't set it up in mos7840_startup as the * * structures were not set up at that time.) */ - dev_dbg(&port->dev, "port number is %d\n", port->number); - dev_dbg(&port->dev, "serial number is %d\n", port->serial->minor); + dev_dbg(&port->dev, "port number is %d\n", port->port_number); + dev_dbg(&port->dev, "minor number is %d\n", port->serial->minor); dev_dbg(&port->dev, "Bulkin endpoint is %d\n", port->bulk_in_endpointAddress); dev_dbg(&port->dev, "BulkOut endpoint is %d\n", port->bulk_out_endpointAddress); dev_dbg(&port->dev, "Interrupt endpoint is %d\n", port->interrupt_in_endpointAddress); @@ -1074,9 +1065,7 @@ static int mos7840_open(struct tty_struct *tty, struct usb_serial_port *port) mos7840_port->read_urb = port->read_urb; /* set up our bulk in urb */ - if ((serial->num_ports == 2) - && ((((__u16)port->number - - (__u16)(port->serial->minor)) % 2) != 0)) { + if ((serial->num_ports == 2) && (((__u16)port->port_number % 2) != 0)) { usb_fill_bulk_urb(mos7840_port->read_urb, serial->dev, usb_rcvbulkpipe(serial->dev, @@ -1199,7 +1188,7 @@ static void mos7840_close(struct usb_serial_port *port) mos7840_port->read_urb_busy = false; port0->open_ports--; - dev_dbg(&port->dev, "%s in close%d:in port%d\n", __func__, port0->open_ports, port->number); + dev_dbg(&port->dev, "%s in close%d\n", __func__, port0->open_ports); if (port0->open_ports == 0) { if (serial->port[0]->interrupt_in_urb) { dev_dbg(&port->dev, "Shutdown interrupt_in_urb\n"); @@ -1435,9 +1424,7 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port, memcpy(urb->transfer_buffer, current_position, transfer_size); /* fill urb with data and submit */ - if ((serial->num_ports == 2) - && ((((__u16)port->number - - (__u16)(port->serial->minor)) % 2) != 0)) { + if ((serial->num_ports == 2) && (((__u16)port->port_number % 2) != 0)) { usb_fill_bulk_urb(urb, serial->dev, usb_sndbulkpipe(serial->dev, @@ -1732,10 +1719,9 @@ static int mos7840_send_cmd_write_baud_rate(struct moschip_port *mos7840_port, if (mos7840_serial_paranoia_check(port->serial, __func__)) return -1; - number = mos7840_port->port->number - mos7840_port->port->serial->minor; + number = mos7840_port->port->port_number; - dev_dbg(&port->dev, "%s - port = %d, baud = %d\n", __func__, - mos7840_port->port->number, baudRate); + dev_dbg(&port->dev, "%s - baud = %d\n", __func__, baudRate); /* reset clk_uart_sel in spregOffset */ if (baudRate > 115200) { #ifdef HW_flow_control @@ -2016,7 +2002,6 @@ static void mos7840_set_termios(struct tty_struct *tty, tty->termios.c_cflag, RELEVANT_IFLAG(tty->termios.c_iflag)); dev_dbg(&port->dev, "%s - old clfag %08x old iflag %08x\n", __func__, old_termios->c_cflag, RELEVANT_IFLAG(old_termios->c_iflag)); - dev_dbg(&port->dev, "%s - port %d\n", __func__, port->number); /* change the port settings to the new ones specified */ @@ -2084,7 +2069,7 @@ static int mos7840_get_serial_info(struct moschip_port *mos7840_port, tmp.type = PORT_16550A; tmp.line = mos7840_port->port->serial->minor; - tmp.port = mos7840_port->port->number; + tmp.port = mos7840_port->port->port_number; tmp.irq = 0; tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; tmp.xmit_fifo_size = NUM_URBS * URB_TRANSFER_BUFFER_SIZE; @@ -2240,7 +2225,7 @@ static int mos7840_port_probe(struct usb_serial_port *port) /* we set up the pointers to the endpoints in the mos7840_open * * function, as the structures aren't created yet. */ - pnum = port->number - serial->minor; + pnum = port->port_number; dev_dbg(&port->dev, "mos7840_startup: configuring port %d\n", pnum); mos7840_port = kzalloc(sizeof(struct moschip_port), GFP_KERNEL); @@ -2261,7 +2246,6 @@ static int mos7840_port_probe(struct usb_serial_port *port) * usb-serial.c:get_free_serial() and cannot therefore be used * to index device instances */ mos7840_port->port_num = pnum + 1; - dev_dbg(&port->dev, "port->number = %d\n", port->number); dev_dbg(&port->dev, "port->serial->minor = %d\n", port->serial->minor); dev_dbg(&port->dev, "mos7840_port->port_num = %d\n", mos7840_port->port_num); dev_dbg(&port->dev, "serial->minor = %d\n", serial->minor); diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c index 5f4b0cd..6e1ee85 100644 --- a/drivers/usb/serial/opticon.c +++ b/drivers/usb/serial/opticon.c @@ -367,7 +367,7 @@ static int opticon_ioctl(struct tty_struct *tty, { struct usb_serial_port *port = tty->driver_data; - dev_dbg(&port->dev, "%s - port %d, cmd = 0x%x\n", __func__, port->number, cmd); + dev_dbg(&port->dev, "%s - cmd = 0x%x\n", __func__, cmd); switch (cmd) { case TIOCGSERIAL: diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c index 048cd44..042ae6b 100644 --- a/drivers/usb/serial/pl2303.c +++ b/drivers/usb/serial/pl2303.c @@ -275,7 +275,7 @@ static void pl2303_set_termios(struct tty_struct *tty, u8 control; const int baud_sup[] = { 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200, 28800, 38400, - 57600, 115200, 230400, 460800, 614400, + 57600, 115200, 230400, 460800, 500000, 614400, 921600, 1228800, 2457600, 3000000, 6000000 }; int baud_floor, baud_ceil; int k; @@ -301,8 +301,7 @@ static void pl2303_set_termios(struct tty_struct *tty, i = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, 0, 0, buf, 7, 100); - dev_dbg(&port->dev, "0xa1:0x21:0:0 %d - %x %x %x %x %x %x %x\n", i, - buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + dev_dbg(&port->dev, "0xa1:0x21:0:0 %d - %7ph\n", i, buf); if (cflag & CSIZE) { switch (cflag & CSIZE) { @@ -449,8 +448,7 @@ static void pl2303_set_termios(struct tty_struct *tty, i = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE, 0, 0, buf, 7, 100); - dev_dbg(&port->dev, "0xa1:0x21:0:0 %d - %x %x %x %x %x %x %x\n", i, - buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6]); + dev_dbg(&port->dev, "0xa1:0x21:0:0 %d - %7ph\n", i, buf); if (cflag & CRTSCTS) { if (spriv->type == HX) @@ -642,7 +640,7 @@ static int pl2303_ioctl(struct tty_struct *tty, memset(&ser, 0, sizeof ser); ser.type = PORT_16654; ser.line = port->serial->minor; - ser.port = port->number; + ser.port = port->port_number; ser.baud_base = 460800; if (copy_to_user((void __user *)arg, &ser, sizeof ser)) diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index 02b0803..f2ca7d8 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -343,7 +343,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) int status; unsigned long flags; - device_port = (u16) (port->number - port->serial->minor); + device_port = port->port_number; serial = port->serial; @@ -388,9 +388,8 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) status = qt2_set_port_config(serial->dev, device_port, DEFAULT_BAUD_RATE, UART_LCR_WLEN8); if (status < 0) { - dev_err(&port->dev, - "%s - initial setup failed for port %i (%i)\n", - __func__, port->number, device_port); + dev_err(&port->dev, "%s - initial setup failed (%i)\n", + __func__, device_port); return status; } @@ -523,7 +522,7 @@ static void qt2_process_flush(struct usb_serial_port *port, unsigned char *ch) return; } -void qt2_process_read_urb(struct urb *urb) +static void qt2_process_read_urb(struct urb *urb) { struct usb_serial *serial; struct qt2_serial_private *serial_priv; diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 8894665..de958c5 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -914,7 +914,7 @@ static int sierra_port_probe(struct usb_serial_port *port) /* This is really the usb-serial port number of the interface * rather than the interface number. */ - ifnum = port->number - serial->minor; + ifnum = port->port_number; himemoryp = &typeA_interface_list; } diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index c92c5ed..07e5c9b 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -476,7 +476,7 @@ static int ti_open(struct tty_struct *tty, struct usb_serial_port *port) if (mutex_lock_interruptible(&tdev->td_open_close_lock)) return -ERESTARTSYS; - port_number = port->number - port->serial->minor; + port_number = port->port_number; tport->tp_msr = 0; tport->tp_shadow_mcr |= (TI_MCR_RTS | TI_MCR_DTR); @@ -618,7 +618,7 @@ static void ti_close(struct usb_serial_port *port) kfifo_reset_out(&tport->write_fifo); spin_unlock_irqrestore(&tport->tp_lock, flags); - port_number = port->number - port->serial->minor; + port_number = port->port_number; dev_dbg(&port->dev, "%s - sending TI_CLOSE_PORT\n", __func__); status = ti_command_out_sync(tdev, TI_CLOSE_PORT, @@ -776,7 +776,7 @@ static void ti_set_termios(struct tty_struct *tty, tcflag_t cflag, iflag; int baud; int status; - int port_number = port->number - port->serial->minor; + int port_number = port->port_number; unsigned int mcr; cflag = tty->termios.c_cflag; @@ -1262,7 +1262,7 @@ static int ti_get_lsr(struct ti_port *tport, u8 *lsr) int size, status; struct ti_device *tdev = tport->tp_tdev; struct usb_serial_port *port = tport->tp_port; - int port_number = port->number - port->serial->minor; + int port_number = port->port_number; struct ti_port_status *data; size = sizeof(struct ti_port_status); @@ -1309,7 +1309,7 @@ static int ti_get_serial_info(struct ti_port *tport, ret_serial.type = PORT_16550A; ret_serial.line = port->serial->minor; - ret_serial.port = port->number - port->serial->minor; + ret_serial.port = port->port_number; ret_serial.flags = tport->tp_flags; ret_serial.xmit_fifo_size = TI_WRITE_BUF_SIZE; ret_serial.baud_base = tport->tp_tdev->td_is_3410 ? 921600 : 460800; diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 5f6b1ff..a47fa71 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -105,9 +105,10 @@ static struct usb_serial *get_free_serial(struct usb_serial *serial, *minor = i; j = 0; dev_dbg(&serial->interface->dev, "%s - minor base = %d\n", __func__, *minor); - for (i = *minor; (i < (*minor + num_ports)) && (i < SERIAL_TTY_MINORS); ++i) { + for (i = *minor; (i < (*minor + num_ports)) && (i < SERIAL_TTY_MINORS); ++i, ++j) { serial_table[i] = serial; - serial->port[j++]->number = i; + serial->port[j]->minor = i; + serial->port[j]->port_number = i - *minor; } mutex_unlock(&table_lock); return serial; @@ -1048,7 +1049,7 @@ static int usb_serial_probe(struct usb_interface *interface, /* register all of the individual ports with the driver core */ for (i = 0; i < num_ports; ++i) { port = serial->port[i]; - dev_set_name(&port->dev, "ttyUSB%d", port->number); + dev_set_name(&port->dev, "ttyUSB%d", port->minor); dev_dbg(ddev, "registering %s", dev_name(&port->dev)); device_enable_async_suspend(&port->dev); diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index ece326e..eacc27d 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -125,7 +125,7 @@ static int get_serial_info(struct usb_serial_port *port, memset(&tmp, 0, sizeof(tmp)); tmp.line = port->serial->minor; - tmp.port = port->number; + tmp.port = port->port_number; tmp.baud_base = tty_get_baud_rate(port->port.tty); tmp.close_delay = port->port.close_delay / 10; tmp.closing_wait = port->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c index 347caad..7eb34cd 100644 --- a/drivers/usb/serial/whiteheat.c +++ b/drivers/usb/serial/whiteheat.c @@ -462,7 +462,7 @@ static int whiteheat_ioctl(struct tty_struct *tty, memset(&serstruct, 0, sizeof(serstruct)); serstruct.type = PORT_16654; serstruct.line = port->serial->minor; - serstruct.port = port->number; + serstruct.port = port->port_number; serstruct.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; serstruct.xmit_fifo_size = kfifo_size(&port->write_fifo); serstruct.custom_divisor = 0; @@ -626,7 +626,7 @@ static int firm_open(struct usb_serial_port *port) { struct whiteheat_simple open_command; - open_command.port = port->number - port->serial->minor + 1; + open_command.port = port->port_number + 1; return firm_send_command(port, WHITEHEAT_OPEN, (__u8 *)&open_command, sizeof(open_command)); } @@ -636,7 +636,7 @@ static int firm_close(struct usb_serial_port *port) { struct whiteheat_simple close_command; - close_command.port = port->number - port->serial->minor + 1; + close_command.port = port->port_number + 1; return firm_send_command(port, WHITEHEAT_CLOSE, (__u8 *)&close_command, sizeof(close_command)); } @@ -649,7 +649,7 @@ static void firm_setup_port(struct tty_struct *tty) struct whiteheat_port_settings port_settings; unsigned int cflag = tty->termios.c_cflag; - port_settings.port = port->number - port->serial->minor + 1; + port_settings.port = port->port_number + 1; /* get the byte size */ switch (cflag & CSIZE) { @@ -726,7 +726,7 @@ static int firm_set_rts(struct usb_serial_port *port, __u8 onoff) { struct whiteheat_set_rdb rts_command; - rts_command.port = port->number - port->serial->minor + 1; + rts_command.port = port->port_number + 1; rts_command.state = onoff; return firm_send_command(port, WHITEHEAT_SET_RTS, (__u8 *)&rts_command, sizeof(rts_command)); @@ -737,7 +737,7 @@ static int firm_set_dtr(struct usb_serial_port *port, __u8 onoff) { struct whiteheat_set_rdb dtr_command; - dtr_command.port = port->number - port->serial->minor + 1; + dtr_command.port = port->port_number + 1; dtr_command.state = onoff; return firm_send_command(port, WHITEHEAT_SET_DTR, (__u8 *)&dtr_command, sizeof(dtr_command)); @@ -748,7 +748,7 @@ static int firm_set_break(struct usb_serial_port *port, __u8 onoff) { struct whiteheat_set_rdb break_command; - break_command.port = port->number - port->serial->minor + 1; + break_command.port = port->port_number + 1; break_command.state = onoff; return firm_send_command(port, WHITEHEAT_SET_BREAK, (__u8 *)&break_command, sizeof(break_command)); @@ -759,7 +759,7 @@ static int firm_purge(struct usb_serial_port *port, __u8 rxtx) { struct whiteheat_purge purge_command; - purge_command.port = port->number - port->serial->minor + 1; + purge_command.port = port->port_number + 1; purge_command.what = rxtx; return firm_send_command(port, WHITEHEAT_PURGE, (__u8 *)&purge_command, sizeof(purge_command)); @@ -770,7 +770,7 @@ static int firm_get_dtr_rts(struct usb_serial_port *port) { struct whiteheat_simple get_dr_command; - get_dr_command.port = port->number - port->serial->minor + 1; + get_dr_command.port = port->port_number + 1; return firm_send_command(port, WHITEHEAT_GET_DTR_RTS, (__u8 *)&get_dr_command, sizeof(get_dr_command)); } @@ -780,7 +780,7 @@ static int firm_report_tx_done(struct usb_serial_port *port) { struct whiteheat_simple close_command; - close_command.port = port->number - port->serial->minor + 1; + close_command.port = port->port_number + 1; return firm_send_command(port, WHITEHEAT_REPORT_TX_DONE, (__u8 *)&close_command, sizeof(close_command)); } diff --git a/drivers/usb/storage/alauda.c b/drivers/usb/storage/alauda.c index 77a2ddf..6636a58 100644 --- a/drivers/usb/storage/alauda.c +++ b/drivers/usb/storage/alauda.c @@ -249,11 +249,7 @@ static void nand_init_ecc(void) { /* compute 3-byte ecc on 256 bytes */ static void nand_compute_ecc(unsigned char *data, unsigned char *ecc) { int i, j, a; - unsigned char par, bit, bits[8]; - - par = 0; - for (j = 0; j < 8; j++) - bits[j] = 0; + unsigned char par = 0, bit, bits[8] = {0}; /* collect 16 checksum bits */ for (i = 0; i < 256; i++) { diff --git a/drivers/usb/storage/sddr09.c b/drivers/usb/storage/sddr09.c index 732027f..073a2c3 100644 --- a/drivers/usb/storage/sddr09.c +++ b/drivers/usb/storage/sddr09.c @@ -219,11 +219,7 @@ static void nand_init_ecc(void) { /* compute 3-byte ecc on 256 bytes */ static void nand_compute_ecc(unsigned char *data, unsigned char *ecc) { int i, j, a; - unsigned char par, bit, bits[8]; - - par = 0; - for (j = 0; j < 8; j++) - bits[j] = 0; + unsigned char par = 0, bit, bits[8] = {0}; /* collect 16 checksum bits */ for (i = 0; i < 256; i++) { diff --git a/drivers/usb/wusbcore/devconnect.c b/drivers/usb/wusbcore/devconnect.c index 1d36531..33a1278 100644 --- a/drivers/usb/wusbcore/devconnect.c +++ b/drivers/usb/wusbcore/devconnect.c @@ -455,8 +455,8 @@ static void __wusbhc_keep_alive(struct wusbhc *wusbhc) dev_err(dev, "KEEPALIVE: device %u timed out\n", wusb_dev->addr); __wusbhc_dev_disconnect(wusbhc, wusb_port); - } else if (time_after(jiffies, wusb_dev->entry_ts + tt/2)) { - /* Approaching timeout cut out, need to refresh */ + } else if (time_after(jiffies, wusb_dev->entry_ts + tt/3)) { + /* Approaching timeout cut off, need to refresh */ ie->bDeviceAddress[keep_alives++] = wusb_dev->addr; } } @@ -1062,7 +1062,7 @@ int wusbhc_devconnect_start(struct wusbhc *wusbhc) wusbhc->wuie_host_info = hi; queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, - (wusbhc->trust_timeout*CONFIG_HZ)/1000/2); + msecs_to_jiffies(wusbhc->trust_timeout / 2)); return 0; diff --git a/drivers/usb/wusbcore/rh.c b/drivers/usb/wusbcore/rh.c index 59ff254..bdb0cc3 100644 --- a/drivers/usb/wusbcore/rh.c +++ b/drivers/usb/wusbcore/rh.c @@ -393,26 +393,6 @@ int wusbhc_rh_control(struct usb_hcd *usb_hcd, u16 reqntype, u16 wValue, } EXPORT_SYMBOL_GPL(wusbhc_rh_control); -int wusbhc_rh_suspend(struct usb_hcd *usb_hcd) -{ - struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); - dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, - usb_hcd, wusbhc); - /* dump_stack(); */ - return -ENOSYS; -} -EXPORT_SYMBOL_GPL(wusbhc_rh_suspend); - -int wusbhc_rh_resume(struct usb_hcd *usb_hcd) -{ - struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); - dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__, - usb_hcd, wusbhc); - /* dump_stack(); */ - return -ENOSYS; -} -EXPORT_SYMBOL_GPL(wusbhc_rh_resume); - int wusbhc_rh_start_port_reset(struct usb_hcd *usb_hcd, unsigned port_idx) { struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); diff --git a/drivers/usb/wusbcore/wa-nep.c b/drivers/usb/wusbcore/wa-nep.c index f67f7f1..ada4e08 100644 --- a/drivers/usb/wusbcore/wa-nep.c +++ b/drivers/usb/wusbcore/wa-nep.c @@ -134,9 +134,10 @@ static void wa_notif_dispatch(struct work_struct *ws) case WA_NOTIF_TRANSFER: wa_handle_notif_xfer(wa, notif_hdr); break; + case HWA_NOTIF_BPST_ADJ: + break; /* no action needed for BPST ADJ. */ case DWA_NOTIF_RWAKE: case DWA_NOTIF_PORTSTATUS: - case HWA_NOTIF_BPST_ADJ: /* FIXME: unimplemented WA NOTIFs */ /* fallthru */ default: diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c index f0d546c..9429c12 100644 --- a/drivers/usb/wusbcore/wa-rpipe.c +++ b/drivers/usb/wusbcore/wa-rpipe.c @@ -251,8 +251,8 @@ static int __rpipe_reset(struct wahc *wa, unsigned index) static struct usb_wireless_ep_comp_descriptor epc0 = { .bLength = sizeof(epc0), .bDescriptorType = USB_DT_WIRELESS_ENDPOINT_COMP, -/* .bMaxBurst = 1, */ - .bMaxSequence = 31, + .bMaxBurst = 1, + .bMaxSequence = 2, }; /* @@ -317,6 +317,7 @@ static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa, struct device *dev = &wa->usb_iface->dev; struct usb_device *usb_dev = urb->dev; struct usb_wireless_ep_comp_descriptor *epcd; + u32 ack_window, epcd_max_sequence; u8 unauth; epcd = rpipe_epc_find(dev, ep); @@ -333,8 +334,11 @@ static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa, rpipe->descr.wBlocks = cpu_to_le16(16); /* given */ /* ep0 maxpktsize is 0x200 (WUSB1.0[4.8.1]) */ rpipe->descr.wMaxPacketSize = cpu_to_le16(ep->desc.wMaxPacketSize); - rpipe->descr.bHSHubAddress = 0; /* reserved: zero */ - rpipe->descr.bHSHubPort = wusb_port_no_to_idx(urb->dev->portnum); + + rpipe->descr.hwa_bMaxBurst = max(min_t(unsigned int, + epcd->bMaxBurst, 16U), 1U); + rpipe->descr.hwa_bDeviceInfoIndex = + wusb_port_no_to_idx(urb->dev->portnum); /* FIXME: use maximum speed as supported or recommended by device */ rpipe->descr.bSpeed = usb_pipeendpoint(urb->pipe) == 0 ? UWB_PHY_RATE_53 : UWB_PHY_RATE_200; @@ -344,23 +348,24 @@ static int rpipe_aim(struct wa_rpipe *rpipe, struct wahc *wa, le16_to_cpu(rpipe->descr.wRPipeIndex), usb_pipeendpoint(urb->pipe), rpipe->descr.bSpeed); - /* see security.c:wusb_update_address() */ - if (unlikely(urb->dev->devnum == 0x80)) - rpipe->descr.bDeviceAddress = 0; - else - rpipe->descr.bDeviceAddress = urb->dev->devnum | unauth; + rpipe->descr.hwa_reserved = 0; + rpipe->descr.bEndpointAddress = ep->desc.bEndpointAddress; /* FIXME: bDataSequence */ rpipe->descr.bDataSequence = 0; - /* FIXME: dwCurrentWindow */ - rpipe->descr.dwCurrentWindow = cpu_to_le32(1); - /* FIXME: bMaxDataSequence */ - rpipe->descr.bMaxDataSequence = epcd->bMaxSequence - 1; + + /* start with base window of hwa_bMaxBurst bits starting at 0. */ + ack_window = 0xFFFFFFFF >> (32 - rpipe->descr.hwa_bMaxBurst); + rpipe->descr.dwCurrentWindow = cpu_to_le32(ack_window); + epcd_max_sequence = max(min_t(unsigned int, + epcd->bMaxSequence, 32U), 2U); + rpipe->descr.bMaxDataSequence = epcd_max_sequence - 1; rpipe->descr.bInterval = ep->desc.bInterval; /* FIXME: bOverTheAirInterval */ rpipe->descr.bOverTheAirInterval = 0; /* 0 if not isoc */ /* FIXME: xmit power & preamble blah blah */ - rpipe->descr.bmAttribute = ep->desc.bmAttributes & 0x03; + rpipe->descr.bmAttribute = (ep->desc.bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK); /* rpipe->descr.bmCharacteristics RO */ /* FIXME: bmRetryOptions */ rpipe->descr.bmRetryOptions = 15; @@ -387,10 +392,8 @@ static int rpipe_check_aim(const struct wa_rpipe *rpipe, const struct wahc *wa, const struct usb_host_endpoint *ep, const struct urb *urb, gfp_t gfp) { - int result = 0; /* better code for lack of companion? */ + int result = 0; struct device *dev = &wa->usb_iface->dev; - struct usb_device *usb_dev = urb->dev; - u8 unauth = (usb_dev->wusb && !usb_dev->authenticated) ? 0x80 : 0; u8 portnum = wusb_port_no_to_idx(urb->dev->portnum); #define AIM_CHECK(rdf, val, text) \ @@ -403,13 +406,10 @@ static int rpipe_check_aim(const struct wa_rpipe *rpipe, const struct wahc *wa, WARN_ON(1); \ } \ } while (0) - AIM_CHECK(wMaxPacketSize, cpu_to_le16(ep->desc.wMaxPacketSize), - "(%u vs %u)"); - AIM_CHECK(bHSHubPort, portnum, "(%u vs %u)"); + AIM_CHECK(hwa_bDeviceInfoIndex, portnum, "(%u vs %u)"); AIM_CHECK(bSpeed, usb_pipeendpoint(urb->pipe) == 0 ? UWB_PHY_RATE_53 : UWB_PHY_RATE_200, "(%u vs %u)"); - AIM_CHECK(bDeviceAddress, urb->dev->devnum | unauth, "(%u vs %u)"); AIM_CHECK(bEndpointAddress, ep->desc.bEndpointAddress, "(%u vs %u)"); AIM_CHECK(bInterval, ep->desc.bInterval, "(%u vs %u)"); AIM_CHECK(bmAttribute, ep->desc.bmAttributes & 0x03, "(%u vs %u)"); diff --git a/drivers/usb/wusbcore/wusbhc.c b/drivers/usb/wusbcore/wusbhc.c index 0faca16..c35ee43 100644 --- a/drivers/usb/wusbcore/wusbhc.c +++ b/drivers/usb/wusbcore/wusbhc.c @@ -75,12 +75,11 @@ static ssize_t wusb_trust_timeout_store(struct device *dev, result = -EINVAL; goto out; } - /* FIXME: maybe we should check for range validity? */ - wusbhc->trust_timeout = trust_timeout; + wusbhc->trust_timeout = min_t(unsigned, trust_timeout, 500); cancel_delayed_work(&wusbhc->keep_alive_timer); flush_workqueue(wusbd); queue_delayed_work(wusbd, &wusbhc->keep_alive_timer, - (trust_timeout * CONFIG_HZ)/1000/2); + msecs_to_jiffies(wusbhc->trust_timeout / 2)); out: return result < 0 ? result : size; } diff --git a/drivers/usb/wusbcore/wusbhc.h b/drivers/usb/wusbcore/wusbhc.h index 3a2d091..b4a4fa7 100644 --- a/drivers/usb/wusbcore/wusbhc.h +++ b/drivers/usb/wusbcore/wusbhc.h @@ -399,8 +399,6 @@ extern void wusbhc_rh_destroy(struct wusbhc *); extern int wusbhc_rh_status_data(struct usb_hcd *, char *); extern int wusbhc_rh_control(struct usb_hcd *, u16, u16, u16, char *, u16); -extern int wusbhc_rh_suspend(struct usb_hcd *); -extern int wusbhc_rh_resume(struct usb_hcd *); extern int wusbhc_rh_start_port_reset(struct usb_hcd *, unsigned); /* MMC handling */ diff --git a/drivers/uwb/drp.c b/drivers/uwb/drp.c index 3fbcf78..16ada83 100644 --- a/drivers/uwb/drp.c +++ b/drivers/uwb/drp.c @@ -67,14 +67,14 @@ static void uwb_rc_set_drp_cmd_done(struct uwb_rc *rc, void *arg, } else dev_err(&rc->uwb_dev.dev, "SET-DRP-IE: timeout\n"); - spin_lock_bh(&rc->rsvs_lock); + spin_lock_irq(&rc->rsvs_lock); if (rc->set_drp_ie_pending > 1) { rc->set_drp_ie_pending = 0; uwb_rsv_queue_update(rc); } else { rc->set_drp_ie_pending = 0; } - spin_unlock_bh(&rc->rsvs_lock); + spin_unlock_irq(&rc->rsvs_lock); } /** diff --git a/drivers/uwb/est.c b/drivers/uwb/est.c index 86ed7e6..457f31d 100644 --- a/drivers/uwb/est.c +++ b/drivers/uwb/est.c @@ -436,7 +436,6 @@ ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb, unsigned long flags; unsigned itr; u16 type_event_high, event; - u8 *ptr = (u8 *) rceb; read_lock_irqsave(&uwb_est_lock, flags); size = -ENOSPC; @@ -453,12 +452,12 @@ ssize_t uwb_est_find_size(struct uwb_rc *rc, const struct uwb_rceb *rceb, if (size != -ENOENT) goto out; } - dev_dbg(dev, "event 0x%02x/%04x/%02x: no handlers available; " - "RCEB %02x %02x %02x %02x\n", + dev_dbg(dev, + "event 0x%02x/%04x/%02x: no handlers available; RCEB %4ph\n", (unsigned) rceb->bEventType, (unsigned) le16_to_cpu(rceb->wEvent), (unsigned) rceb->bEventContext, - ptr[0], ptr[1], ptr[2], ptr[3]); + rceb); size = -ENOENT; out: read_unlock_irqrestore(&uwb_est_lock, flags); diff --git a/drivers/uwb/hwa-rc.c b/drivers/uwb/hwa-rc.c index 810c90a..0621abe 100644 --- a/drivers/uwb/hwa-rc.c +++ b/drivers/uwb/hwa-rc.c @@ -900,6 +900,12 @@ static const struct usb_device_id hwarc_id_table[] = { /* Intel i1480 (using firmware 1.3PA2-20070828) */ { USB_DEVICE_AND_INTERFACE_INFO(0x8086, 0x0c3b, 0xe0, 0x01, 0x02), .driver_info = WUSB_QUIRK_WHCI_CMD_EVT }, + /* Alereon 5310 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5310, 0xe0, 0x01, 0x02), + .driver_info = WUSB_QUIRK_WHCI_CMD_EVT }, + /* Alereon 5611 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x13dc, 0x5611, 0xe0, 0x01, 0x02), + .driver_info = WUSB_QUIRK_WHCI_CMD_EVT }, /* Generic match for the Radio Control interface */ { USB_INTERFACE_INFO(0xe0, 0x01, 0x02), }, { }, diff --git a/drivers/uwb/rsv.c b/drivers/uwb/rsv.c index f4ae05f..738e8a8 100644 --- a/drivers/uwb/rsv.c +++ b/drivers/uwb/rsv.c @@ -872,7 +872,7 @@ void uwb_rsv_queue_update(struct uwb_rc *rc) */ void uwb_rsv_sched_update(struct uwb_rc *rc) { - spin_lock_bh(&rc->rsvs_lock); + spin_lock_irq(&rc->rsvs_lock); if (!delayed_work_pending(&rc->rsv_update_work)) { if (rc->set_drp_ie_pending > 0) { rc->set_drp_ie_pending++; @@ -881,7 +881,7 @@ void uwb_rsv_sched_update(struct uwb_rc *rc) uwb_rsv_queue_update(rc); } unlock: - spin_unlock_bh(&rc->rsvs_lock); + spin_unlock_irq(&rc->rsvs_lock); } /* diff --git a/drivers/uwb/whci.c b/drivers/uwb/whci.c index f48093e..c9df8ba 100644 --- a/drivers/uwb/whci.c +++ b/drivers/uwb/whci.c @@ -253,19 +253,7 @@ static struct pci_driver whci_driver = { .remove = whci_remove, }; -static int __init whci_init(void) -{ - return pci_register_driver(&whci_driver); -} - -static void __exit whci_exit(void) -{ - pci_unregister_driver(&whci_driver); -} - -module_init(whci_init); -module_exit(whci_exit); - +module_pci_driver(whci_driver); MODULE_DESCRIPTION("WHCI UWB Multi-interface Controller enumerator"); MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/usb3503.h b/include/linux/platform_data/usb3503.h index 85dcc70..1d1b6ef 100644 --- a/include/linux/platform_data/usb3503.h +++ b/include/linux/platform_data/usb3503.h @@ -3,6 +3,10 @@ #define USB3503_I2C_NAME "usb3503" +#define USB3503_OFF_PORT1 (1 << 1) +#define USB3503_OFF_PORT2 (1 << 2) +#define USB3503_OFF_PORT3 (1 << 3) + enum usb3503_mode { USB3503_MODE_UNKNOWN, USB3503_MODE_HUB, @@ -11,6 +15,7 @@ enum usb3503_mode { struct usb3503_platform_data { enum usb3503_mode initial_mode; + u8 port_off_mask; int gpio_intn; int gpio_connect; int gpio_reset; diff --git a/include/linux/usb.h b/include/linux/usb.h index a0bee5a..a232b7e 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -394,6 +394,22 @@ enum usb_port_connect_type { }; /* + * USB 2.0 Link Power Management (LPM) parameters. + */ +struct usb2_lpm_parameters { + /* Best effort service latency indicate how long the host will drive + * resume on an exit from L1. + */ + unsigned int besl; + + /* Timeout value in microseconds for the L1 inactivity (LPM) timer. + * When the timer counts to zero, the parent hub will initiate a LPM + * transition to L1. + */ + int timeout; +}; + +/* * USB 3.0 Link Power Management (LPM) parameters. * * PEL and SEL are USB 3.0 Link PM latencies for device-initiated LPM exit. @@ -468,6 +484,7 @@ struct usb3_lpm_parameters { * @wusb: device is Wireless USB * @lpm_capable: device supports LPM * @usb2_hw_lpm_capable: device can perform USB2 hardware LPM + * @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM * @usb2_hw_lpm_enabled: USB2 hardware LPM enabled * @usb3_lpm_enabled: USB3 hardware LPM enabled * @string_langid: language ID for strings @@ -487,6 +504,7 @@ struct usb3_lpm_parameters { * specific data for the device. * @slot_id: Slot ID assigned by xHCI * @removable: Device can be physically removed from this port + * @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout. * @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout. * @u2_params: exit latencies for USB3 U2 LPM state, and hub-initiated timeout. * @lpm_disable_count: Ref count used by usb_disable_lpm() and usb_enable_lpm() @@ -538,6 +556,7 @@ struct usb_device { unsigned wusb:1; unsigned lpm_capable:1; unsigned usb2_hw_lpm_capable:1; + unsigned usb2_hw_lpm_besl_capable:1; unsigned usb2_hw_lpm_enabled:1; unsigned usb3_lpm_enabled:1; int string_langid; @@ -566,6 +585,7 @@ struct usb_device { struct wusb_dev *wusb_dev; int slot_id; enum usb_device_removable removable; + struct usb2_lpm_parameters l1_params; struct usb3_lpm_parameters u1_params; struct usb3_lpm_parameters u2_params; unsigned lpm_disable_count; @@ -717,6 +737,7 @@ const struct usb_device_id *usb_match_id(struct usb_interface *interface, extern int usb_match_one_id(struct usb_interface *interface, const struct usb_device_id *id); +extern int usb_for_each_dev(void *data, int (*fn)(struct usb_device *, void *)); extern struct usb_interface *usb_find_interface(struct usb_driver *drv, int minor); extern struct usb_interface *usb_ifnum_to_if(const struct usb_device *dev, diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index f5f5c7d..1e88377 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -218,6 +218,7 @@ struct hc_driver { #define HCD_SHARED 0x0004 /* Two (or more) usb_hcds share HW */ #define HCD_USB11 0x0010 /* USB 1.1 */ #define HCD_USB2 0x0020 /* USB 2.0 */ +#define HCD_USB25 0x0030 /* Wireless USB 1.0 (USB 2.5)*/ #define HCD_USB3 0x0040 /* USB 3.0 */ #define HCD_MASK 0x0070 diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 302ddf5..3fa68b6 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -37,7 +37,8 @@ * @serial: pointer back to the struct usb_serial owner of this port. * @port: pointer to the corresponding tty_port for this port. * @lock: spinlock to grab when updating portions of this structure. - * @number: the number of the port (the minor number). + * @minor: the minor number of the port + * @port_number: the struct usb_serial port number of this port (starts at 0) * @interrupt_in_buffer: pointer to the interrupt in buffer for this port. * @interrupt_in_urb: pointer to the interrupt in struct urb for this port. * @interrupt_in_endpointAddress: endpoint address for the interrupt in pipe @@ -80,7 +81,8 @@ struct usb_serial_port { struct usb_serial *serial; struct tty_port port; spinlock_t lock; - unsigned char number; + u32 minor; + u8 port_number; unsigned char *interrupt_in_buffer; struct urb *interrupt_in_urb; diff --git a/include/linux/usb/tegra_usb_phy.h b/include/linux/usb/tegra_usb_phy.h index 1b7519a..0cd15d2 100644 --- a/include/linux/usb/tegra_usb_phy.h +++ b/include/linux/usb/tegra_usb_phy.h @@ -42,6 +42,7 @@ enum tegra_usb_phy_port_speed { enum tegra_usb_phy_mode { TEGRA_USB_PHY_MODE_DEVICE, TEGRA_USB_PHY_MODE_HOST, + TEGRA_USB_PHY_MODE_OTG, }; struct tegra_xtal_freq; @@ -61,14 +62,10 @@ struct tegra_usb_phy { struct device *dev; bool is_legacy_phy; bool is_ulpi_phy; - void (*set_pts)(struct usb_phy *x, u8 pts_val); - void (*set_phcd)(struct usb_phy *x, bool enable); + int reset_gpio; }; -struct tegra_usb_phy *tegra_usb_phy_open(struct device *dev, int instance, - void __iomem *regs, void *config, enum tegra_usb_phy_mode phy_mode, - void (*set_pts)(struct usb_phy *x, u8 pts_val), - void (*set_phcd)(struct usb_phy *x, bool enable)); +struct usb_phy *tegra_usb_get_phy(struct device_node *dn); void tegra_usb_phy_preresume(struct usb_phy *phy); @@ -79,4 +76,8 @@ void tegra_ehci_phy_restore_start(struct usb_phy *phy, void tegra_ehci_phy_restore_end(struct usb_phy *phy); +void tegra_ehci_set_pts(struct usb_phy *x, u8 pts_val); + +void tegra_ehci_set_phcd(struct usb_phy *x, bool enable); + #endif /* __TEGRA_USB_PHY_H */ diff --git a/include/linux/usb/wusb-wa.h b/include/linux/usb/wusb-wa.h index f9dec37..6be985b 100644 --- a/include/linux/usb/wusb-wa.h +++ b/include/linux/usb/wusb-wa.h @@ -92,11 +92,20 @@ struct usb_rpipe_descriptor { __le16 wRPipeIndex; __le16 wRequests; __le16 wBlocks; /* rw if 0 */ - __le16 wMaxPacketSize; /* rw? */ - u8 bHSHubAddress; /* reserved: 0 */ - u8 bHSHubPort; /* ??? FIXME ??? */ + __le16 wMaxPacketSize; /* rw */ + union { + u8 dwa_bHSHubAddress; /* rw: DWA. */ + u8 hwa_bMaxBurst; /* rw: HWA. */ + }; + union { + u8 dwa_bHSHubPort; /* rw: DWA. */ + u8 hwa_bDeviceInfoIndex; /* rw: HWA. */ + }; u8 bSpeed; /* rw: xfer rate 'enum uwb_phy_rate' */ - u8 bDeviceAddress; /* rw: Target device address */ + union { + u8 dwa_bDeviceAddress; /* rw: DWA Target device address. */ + u8 hwa_reserved; /* rw: HWA. */ + }; u8 bEndpointAddress; /* rw: Target EP address */ u8 bDataSequence; /* ro: Current Data sequence */ __le32 dwCurrentWindow; /* ro */ |