===================== =======================================
c_chmask capture channel mask
- c_srate capture sampling rate
+ c_srate list of capture sampling rates (comma-separated)
c_ssize capture sample size (bytes)
c_mute_present capture mute control enable
c_volume_present capture volume control enable
c_volume_res capture volume control resolution
(in 1/256 dB)
p_chmask playback channel mask
- p_srate playback sampling rate
+ p_srate list of playback sampling rates (comma-separated)
p_ssize playback sample size (bytes)
p_mute_present playback mute control enable
p_volume_present playback volume control enable
(in 1/256 dB)
req_number the number of pre-allocated requests
for both capture and playback
+ function_name name of the interface
===================== =======================================
===================== =======================================
c_chmask capture channel mask
- c_srate capture sampling rate
+ c_srate list of capture sampling rates (comma-separated)
c_ssize capture sample size (bytes)
+ c_hs_bint capture bInterval for HS/SS (1-4: fixed, 0: auto)
c_sync capture synchronization type
(async/adaptive)
c_mute_present capture mute control enable
(in 1/256 dB)
fb_max maximum extra bandwidth in async mode
p_chmask playback channel mask
- p_srate playback sampling rate
+ p_srate list of playback sampling rates (comma-separated)
p_ssize playback sample size (bytes)
+ p_hs_bint playback bInterval for HS/SS (1-4: fixed, 0: auto)
p_mute_present playback mute control enable
p_volume_present playback volume control enable
p_volume_min playback volume control min value
(in 1/256 dB)
req_number the number of pre-allocated requests
for both capture and playback
+ function_name name of the interface
===================== =======================================
--- /dev/null
+What: /sys/bus/platform/drivers/eud/.../enable
+Date: February 2022
+Contact: Souradeep Chowdhury <quic_schowdhu@quicinc.com>
+Description:
+ The Enable/Disable sysfs interface for Embedded
+ USB Debugger(EUD). This enables and disables the
+ EUD based on a 1 or a 0 value. By enabling EUD,
+ the user is able to activate the mini-usb hub of
+ EUD for debug and trace capabilities.
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/soc/qcom/qcom,eud.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Qualcomm Embedded USB Debugger
+
+maintainers:
+ - Souradeep Chowdhury <quic_schowdhu@quicinc.com>
+
+description:
+ This binding is used to describe the Qualcomm Embedded USB Debugger, which is
+ mini USB-hub implemented on chip to support USB-based debug capabilities.
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - qcom,sc7280-eud
+ - const: qcom,eud
+
+ reg:
+ items:
+ - description: EUD Base Register Region
+ - description: EUD Mode Manager Register
+
+ interrupts:
+ description: EUD interrupt
+ maxItems: 1
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+ description:
+ These ports is to be attached to the endpoint of the DWC3 controller node
+ and type C connector node. The controller has the "usb-role-switch"
+ property.
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: This port is to be attached to the DWC3 controller.
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: This port is to be attached to the type C connector.
+
+required:
+ - compatible
+ - reg
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ eud@88e0000 {
+ compatible = "qcom,sc7280-eud","qcom,eud";
+ reg = <0x88e0000 0x2000>,
+ <0x88e2000 0x1000>;
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ eud_ep: endpoint {
+ remote-endpoint = <&usb2_role_switch>;
+ };
+ };
+ port@1 {
+ reg = <1>;
+ eud_con: endpoint {
+ remote-endpoint = <&con_eud>;
+ };
+ };
+ };
+ };
- const: st,stm32mp15-hsotg
- const: snps,dwc2
- const: samsung,s3c6400-hsotg
+ - const: intel,socfpga-agilex-hsotg
reg:
maxItems: 1
2 - used by mt2712 etc, revision 2 following IPM rule;
101 - used by mt8183, specific 1.01;
102 - used by mt8192, specific 1.02;
- enum: [1, 2, 101, 102]
+ 103 - used by mt8195, IP0, specific 1.03;
+ 104 - used by mt8195, IP1, specific 1.04;
+ 105 - used by mt8195, IP2, specific 1.05;
+ 106 - used by mt8195, IP3, specific 1.06;
+ enum: [1, 2, 101, 102, 103, 104, 105, 106]
mediatek,u3p-dis-msk:
$ref: /schemas/types.yaml#/definitions/uint32
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/microchip,mpfs-musb.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip MPFS USB Controller Device Tree Bindings
+
+allOf:
+ - $ref: usb-drd.yaml#
+
+maintainers:
+ - Conor Dooley <conor.dooley@microchip.com>
+
+properties:
+ compatible:
+ enum:
+ - microchip,mpfs-musb
+
+ dr_mode: true
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ minItems: 2
+ maxItems: 2
+
+ interrupt-names:
+ items:
+ - const: dma
+ - const: mc
+
+ clocks:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - clocks
+
+additionalProperties: false
+
+examples:
+ - |
+ #include "dt-bindings/clock/microchip,mpfs-clock.h"
+ usb@20201000 {
+ compatible = "microchip,mpfs-musb";
+ reg = <0x20201000 0x1000>;
+ clocks = <&clkcfg CLK_USB>;
+ interrupt-parent = <&plic>;
+ interrupts = <86>, <87>;
+ interrupt-names = "dma", "mc";
+ dr_mode = "host";
+ };
+
+...
- qcom,ipq4019-dwc3
- qcom,ipq6018-dwc3
- qcom,ipq8064-dwc3
+ - qcom,msm8953-dwc3
- qcom,msm8996-dwc3
- qcom,msm8998-dwc3
- qcom,sc7180-dwc3
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/usb/richtek,rt1719.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Richtek RT1719 sink-only Type-C PD controller bindings
+
+maintainers:
+ - ChiYuan Huang <cy_huang@richtek.com>
+
+description: |
+ The RT1719 is a sink-only USB Type-C contoller that complies with the latest
+ USB Type-C and PD standards. It does the USB Type-C detection including attach
+ and orientation. It integrates the physical layer of the USB BMC power
+ delivery protocol to allow up to 100W of power. The BMC PD block enables full
+ support for alternative interfaces of the Type-C specification.
+
+properties:
+ compatible:
+ enum:
+ - richtek,rt1719
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ wakeup-source:
+ description: enable IRQ remote wakeup, see power/wakeup-source.txt
+ type: boolean
+
+ connector:
+ type: object
+ $ref: ../connector/usb-connector.yaml#
+ description:
+ Properties for usb c connector.
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - connector
+ - interrupts
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ i2c0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ rt1719@43 {
+ compatible = "richtek,rt1719";
+ reg = <0x43>;
+ interrupts-extended = <&gpio26 2 IRQ_TYPE_LEVEL_LOW>;
+ wakeup-source;
+
+ connector {
+ compatible = "usb-c-connector";
+ label = "USB-C";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ endpoint {
+ remote-endpoint = <&usb_hs>;
+ };
+ };
+ port@1 {
+ reg = <1>;
+ endpoint {
+ remote-endpoint = <&usb_ss>;
+ };
+ };
+ };
+ };
+ };
+ };
+...
Value for REFCLKPER field of GUCTL register for reference clock period in
nanoseconds, when the hardware set default does not match the actual
clock.
- minimum: 1
- maximum: 0x3ff
+
+ This binding is deprecated. Instead, provide an appropriate reference clock.
+ minimum: 8
+ maximum: 62
+ deprecated: true
snps,rx-thr-num-pkt-prd:
description:
items:
enum: [1, 4, 8, 16, 32, 64, 128, 256]
+ port:
+ $ref: /schemas/graph.yaml#/properties/port
+ description:
+ This port is used with the 'usb-role-switch' property to connect the
+ dwc3 to type C connector.
+
unevaluatedProperties: false
required:
--- /dev/null
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/willsemi,wusb3801.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: WUSB3801 Type-C port controller DT bindings
+
+description:
+ The Will Semiconductor WUSB3801 is a USB Type-C port controller which
+ supports role and plug orientation detection using the CC pins. It is
+ compatible with the USB Type-C Cable and Connector Specification v1.2.
+
+maintainers:
+ - Samuel Holland <samuel@sholland.org>
+
+properties:
+ compatible:
+ enum:
+ - willsemi,wusb3801
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ connector:
+ type: object
+ $ref: ../connector/usb-connector.yaml#
+ description:
+ The managed USB Type-C connector. Since WUSB3801 does not support
+ Power Delivery, the node should have the "pd-disable" property.
+
+ properties:
+ compatible:
+ const: usb-c-connector
+
+ required:
+ - pd-disable
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - connector
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ tcpc@60 {
+ compatible = "willsemi,wusb3801";
+ reg = <0x60>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <4 IRQ_TYPE_LEVEL_LOW>;
+
+ connector {
+ compatible = "usb-c-connector";
+ label = "USB-C";
+ vbus-supply = <&otg_switch>;
+ power-role = "dual";
+ try-power-role = "sink";
+ data-role = "dual";
+ typec-power-opmode = "default";
+ pd-disable;
+ };
+ };
+ };
description: Wi2Wi, Inc.
"^wiligear,.*":
description: Wiligear, Ltd.
+ "^willsemi,.*":
+ description: Will Semiconductor Ltd.
"^winbond,.*":
description: Winbond Electronics corp.
"^wingtech,.*":
================ ====================================================
c_chmask capture channel mask
- c_srate capture sampling rate
+ c_srate list of capture sampling rates (comma-separated)
c_ssize capture sample size (bytes)
c_sync capture synchronization type (async/adaptive)
c_mute_present capture mute control enable
c_volume_min capture volume control min value (in 1/256 dB)
c_volume_max capture volume control max value (in 1/256 dB)
c_volume_res capture volume control resolution (in 1/256 dB)
+ c_hs_bint capture bInterval for HS/SS (1-4: fixed, 0: auto)
fb_max maximum extra bandwidth in async mode
p_chmask playback channel mask
- p_srate playback sampling rate
+ p_srate list of playback sampling rates (comma-separated)
p_ssize playback sample size (bytes)
p_mute_present playback mute control enable
p_volume_present playback volume control enable
p_volume_min playback volume control min value (in 1/256 dB)
p_volume_max playback volume control max value (in 1/256 dB)
p_volume_res playback volume control resolution (in 1/256 dB)
+ p_hs_bint playback bInterval for HS/SS (1-4: fixed, 0: auto)
req_number the number of pre-allocated request for both capture
and playback
+ function_name name of the interface
================ ====================================================
The attributes have sane default values.
================ ====================================================
c_chmask capture channel mask
- c_srate capture sampling rate
+ c_srate list of capture sampling rates (comma-separated)
c_ssize capture sample size (bytes)
c_mute_present capture mute control enable
c_volume_present capture volume control enable
c_volume_max capture volume control max value (in 1/256 dB)
c_volume_res capture volume control resolution (in 1/256 dB)
p_chmask playback channel mask
- p_srate playback sampling rate
+ p_srate list of playback sampling rates (comma-separated)
p_ssize playback sample size (bytes)
p_mute_present playback mute control enable
p_volume_present playback volume control enable
p_volume_res playback volume control resolution (in 1/256 dB)
req_number the number of pre-allocated requests for both capture
and playback
+ function_name name of the interface
================ ====================================================
The attributes have sane default values.
F: sound/soc/codecs/wsa881x.c
F: sound/soc/qcom/
+QCOM EMBEDDED USB DEBUGGER (EUD)
+M: Souradeep Chowdhury <quic_schowdhu@quicinc.com>
+L: linux-arm-msm@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-eud
+F: Documentation/devicetree/bindings/soc/qcom/qcom,eud.yaml
+F: drivers/usb/misc/qcom_eud.c
+
QCOM IPA DRIVER
M: Alex Elder <elder@kernel.org>
L: netdev@vger.kernel.org
};
usb0: usb@ffb00000 {
- compatible = "snps,dwc2";
+ compatible = "intel,socfpga-agilex-hsotg", "snps,dwc2";
reg = <0xffb00000 0x40000>;
interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
phys = <&usbphy0>;
};
usb1: usb@ffb40000 {
- compatible = "snps,dwc2";
+ compatible = "intel,socfpga-agilex-hsotg", "snps,dwc2";
reg = <0xffb40000 0x40000>;
interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>;
phys = <&usbphy0>;
interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>;
phys = <&qusb_phy_0>, <&usb0_ssphy>;
phy-names = "usb2-phy", "usb3-phy";
+ clocks = <&xo>;
+ clock-names = "ref";
tx-fifo-resize;
snps,is-utmi-l1-suspend;
snps,hird-threshold = /bits/ 8 <0x0>;
snps,dis_u2_susphy_quirk;
snps,dis_u3_susphy_quirk;
- snps,ref-clock-period-ns = <0x32>;
dr_mode = "host";
};
};
modem-init;
};
+&usb_2_dwc3 {
+ dr_mode = "otg";
+};
+
&pmk8350_rtc {
status = "okay";
};
phys = <&usb_2_hsphy>;
phy-names = "usb2-phy";
maximum-speed = "high-speed";
+ usb-role-switch;
+ port {
+ usb2_role_switch: endpoint {
+ remote-endpoint = <&eud_ep>;
+ };
+ };
};
};
interrupts = <GIC_SPI 582 IRQ_TYPE_LEVEL_HIGH>;
};
+ eud: eud@88e0000 {
+ compatible = "qcom,sc7280-eud","qcom,eud";
+ reg = <0 0x88e0000 0 0x2000>,
+ <0 0x88e2000 0 0x1000>;
+ interrupts-extended = <&pdc 11 IRQ_TYPE_LEVEL_HIGH>;
+ ports {
+ port@0 {
+ eud_ep: endpoint {
+ remote-endpoint = <&usb2_role_switch>;
+ };
+ };
+ port@1 {
+ eud_con: endpoint {
+ remote-endpoint = <&con_eud>;
+ };
+ };
+ };
+ };
+
+ eud_typec: connector {
+ compatible = "usb-c-connector";
+ ports {
+ port@0 {
+ con_eud: endpoint {
+ remote-endpoint = <&eud_con>;
+ };
+ };
+ };
+ };
+
nsp_noc: interconnect@a0c0000 {
reg = <0 0x0a0c0000 0 0x10000>;
compatible = "qcom,sc7280-nsp-noc";
clocks = <&zynqmp_clk UART1_REF>, <&zynqmp_clk LPD_LSBUS>;
};
-&usb0 {
+&dwc3_0 {
clocks = <&zynqmp_clk USB0_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>;
};
-&usb1 {
+&dwc3_1 {
clocks = <&zynqmp_clk USB1_BUS_REF>, <&zynqmp_clk USB3_DUAL_REF>;
};
status = "disabled";
compatible = "xlnx,zynqmp-dwc3";
reg = <0x0 0xff9d0000 0x0 0x100>;
- clock-names = "bus_clk", "ref_clk";
power-domains = <&zynqmp_firmware PD_USB_0>;
resets = <&zynqmp_reset ZYNQMP_RESET_USB0_CORERESET>,
<&zynqmp_reset ZYNQMP_RESET_USB0_HIBERRESET>,
interrupt-parent = <&gic>;
interrupt-names = "dwc_usb3", "otg";
interrupts = <0 65 4>, <0 69 4>;
+ clock-names = "bus_early", "ref";
#stream-id-cells = <1>;
iommus = <&smmu 0x860>;
snps,quirk-frame-length-adjustment = <0x20>;
status = "disabled";
compatible = "xlnx,zynqmp-dwc3";
reg = <0x0 0xff9e0000 0x0 0x100>;
- clock-names = "bus_clk", "ref_clk";
power-domains = <&zynqmp_firmware PD_USB_1>;
resets = <&zynqmp_reset ZYNQMP_RESET_USB1_CORERESET>,
<&zynqmp_reset ZYNQMP_RESET_USB1_HIBERRESET>,
interrupt-parent = <&gic>;
interrupt-names = "dwc_usb3", "otg";
interrupts = <0 70 4>, <0 74 4>;
+ clock-names = "bus_early", "ref";
#stream-id-cells = <1>;
iommus = <&smmu 0x861>;
snps,quirk-frame-length-adjustment = <0x20>;
int ep_id = TRB_TO_EP_INDEX(field3) - 1;
int type = TRB_FIELD_TO_TYPE(field3);
unsigned int ep_num;
- int ret = 0;
+ int ret;
u32 temp;
ep_num = DIV_ROUND_UP(ep_id, 2);
switch (type) {
case TRB_LINK:
- ret += snprintf(str, size,
- "LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c",
- field1, field0, GET_INTR_TARGET(field2),
- cdnsp_trb_type_string(type),
- field3 & TRB_IOC ? 'I' : 'i',
- field3 & TRB_CHAIN ? 'C' : 'c',
- field3 & TRB_TC ? 'T' : 't',
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c",
+ field1, field0, GET_INTR_TARGET(field2),
+ cdnsp_trb_type_string(type),
+ field3 & TRB_IOC ? 'I' : 'i',
+ field3 & TRB_CHAIN ? 'C' : 'c',
+ field3 & TRB_TC ? 'T' : 't',
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_TRANSFER:
case TRB_COMPLETION:
case TRB_PORT_STATUS:
case TRB_HC_EVENT:
- ret += snprintf(str, size,
- "ep%d%s(%d) type '%s' TRB %08x%08x status '%s'"
- " len %ld slot %ld flags %c:%c",
- ep_num, ep_id % 2 ? "out" : "in",
- TRB_TO_EP_INDEX(field3),
- cdnsp_trb_type_string(type), field1, field0,
- cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)),
- EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3),
- field3 & EVENT_DATA ? 'E' : 'e',
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "ep%d%s(%d) type '%s' TRB %08x%08x status '%s'"
+ " len %ld slot %ld flags %c:%c",
+ ep_num, ep_id % 2 ? "out" : "in",
+ TRB_TO_EP_INDEX(field3),
+ cdnsp_trb_type_string(type), field1, field0,
+ cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)),
+ EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3),
+ field3 & EVENT_DATA ? 'E' : 'e',
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_MFINDEX_WRAP:
- ret += snprintf(str, size, "%s: flags %c",
- cdnsp_trb_type_string(type),
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size, "%s: flags %c",
+ cdnsp_trb_type_string(type),
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_SETUP:
- ret += snprintf(str, size,
- "type '%s' bRequestType %02x bRequest %02x "
- "wValue %02x%02x wIndex %02x%02x wLength %d "
- "length %ld TD size %ld intr %ld Setup ID %ld "
- "flags %c:%c:%c",
- cdnsp_trb_type_string(type),
- field0 & 0xff,
- (field0 & 0xff00) >> 8,
- (field0 & 0xff000000) >> 24,
- (field0 & 0xff0000) >> 16,
- (field1 & 0xff00) >> 8,
- field1 & 0xff,
- (field1 & 0xff000000) >> 16 |
- (field1 & 0xff0000) >> 16,
- TRB_LEN(field2), GET_TD_SIZE(field2),
- GET_INTR_TARGET(field2),
- TRB_SETUPID_TO_TYPE(field3),
- field3 & TRB_IDT ? 'D' : 'd',
- field3 & TRB_IOC ? 'I' : 'i',
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "type '%s' bRequestType %02x bRequest %02x "
+ "wValue %02x%02x wIndex %02x%02x wLength %d "
+ "length %ld TD size %ld intr %ld Setup ID %ld "
+ "flags %c:%c:%c",
+ cdnsp_trb_type_string(type),
+ field0 & 0xff,
+ (field0 & 0xff00) >> 8,
+ (field0 & 0xff000000) >> 24,
+ (field0 & 0xff0000) >> 16,
+ (field1 & 0xff00) >> 8,
+ field1 & 0xff,
+ (field1 & 0xff000000) >> 16 |
+ (field1 & 0xff0000) >> 16,
+ TRB_LEN(field2), GET_TD_SIZE(field2),
+ GET_INTR_TARGET(field2),
+ TRB_SETUPID_TO_TYPE(field3),
+ field3 & TRB_IDT ? 'D' : 'd',
+ field3 & TRB_IOC ? 'I' : 'i',
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_DATA:
- ret += snprintf(str, size,
- "type '%s' Buffer %08x%08x length %ld TD size %ld "
- "intr %ld flags %c:%c:%c:%c:%c:%c:%c",
- cdnsp_trb_type_string(type),
- field1, field0, TRB_LEN(field2),
- GET_TD_SIZE(field2),
- GET_INTR_TARGET(field2),
- field3 & TRB_IDT ? 'D' : 'i',
- field3 & TRB_IOC ? 'I' : 'i',
- field3 & TRB_CHAIN ? 'C' : 'c',
- field3 & TRB_NO_SNOOP ? 'S' : 's',
- field3 & TRB_ISP ? 'I' : 'i',
- field3 & TRB_ENT ? 'E' : 'e',
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "type '%s' Buffer %08x%08x length %ld TD size %ld "
+ "intr %ld flags %c:%c:%c:%c:%c:%c:%c",
+ cdnsp_trb_type_string(type),
+ field1, field0, TRB_LEN(field2),
+ GET_TD_SIZE(field2),
+ GET_INTR_TARGET(field2),
+ field3 & TRB_IDT ? 'D' : 'i',
+ field3 & TRB_IOC ? 'I' : 'i',
+ field3 & TRB_CHAIN ? 'C' : 'c',
+ field3 & TRB_NO_SNOOP ? 'S' : 's',
+ field3 & TRB_ISP ? 'I' : 'i',
+ field3 & TRB_ENT ? 'E' : 'e',
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_STATUS:
- ret += snprintf(str, size,
- "Buffer %08x%08x length %ld TD size %ld intr"
- "%ld type '%s' flags %c:%c:%c:%c",
- field1, field0, TRB_LEN(field2),
- GET_TD_SIZE(field2),
- GET_INTR_TARGET(field2),
- cdnsp_trb_type_string(type),
- field3 & TRB_IOC ? 'I' : 'i',
- field3 & TRB_CHAIN ? 'C' : 'c',
- field3 & TRB_ENT ? 'E' : 'e',
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "Buffer %08x%08x length %ld TD size %ld intr"
+ "%ld type '%s' flags %c:%c:%c:%c",
+ field1, field0, TRB_LEN(field2),
+ GET_TD_SIZE(field2),
+ GET_INTR_TARGET(field2),
+ cdnsp_trb_type_string(type),
+ field3 & TRB_IOC ? 'I' : 'i',
+ field3 & TRB_CHAIN ? 'C' : 'c',
+ field3 & TRB_ENT ? 'E' : 'e',
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_NORMAL:
case TRB_ISOC:
case TRB_EVENT_DATA:
case TRB_TR_NOOP:
- ret += snprintf(str, size,
- "type '%s' Buffer %08x%08x length %ld "
- "TD size %ld intr %ld "
- "flags %c:%c:%c:%c:%c:%c:%c:%c:%c",
- cdnsp_trb_type_string(type),
- field1, field0, TRB_LEN(field2),
- GET_TD_SIZE(field2),
- GET_INTR_TARGET(field2),
- field3 & TRB_BEI ? 'B' : 'b',
- field3 & TRB_IDT ? 'T' : 't',
- field3 & TRB_IOC ? 'I' : 'i',
- field3 & TRB_CHAIN ? 'C' : 'c',
- field3 & TRB_NO_SNOOP ? 'S' : 's',
- field3 & TRB_ISP ? 'I' : 'i',
- field3 & TRB_ENT ? 'E' : 'e',
- field3 & TRB_CYCLE ? 'C' : 'c',
- !(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v');
+ ret = snprintf(str, size,
+ "type '%s' Buffer %08x%08x length %ld "
+ "TD size %ld intr %ld "
+ "flags %c:%c:%c:%c:%c:%c:%c:%c:%c",
+ cdnsp_trb_type_string(type),
+ field1, field0, TRB_LEN(field2),
+ GET_TD_SIZE(field2),
+ GET_INTR_TARGET(field2),
+ field3 & TRB_BEI ? 'B' : 'b',
+ field3 & TRB_IDT ? 'T' : 't',
+ field3 & TRB_IOC ? 'I' : 'i',
+ field3 & TRB_CHAIN ? 'C' : 'c',
+ field3 & TRB_NO_SNOOP ? 'S' : 's',
+ field3 & TRB_ISP ? 'I' : 'i',
+ field3 & TRB_ENT ? 'E' : 'e',
+ field3 & TRB_CYCLE ? 'C' : 'c',
+ !(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v');
break;
case TRB_CMD_NOOP:
case TRB_ENABLE_SLOT:
- ret += snprintf(str, size, "%s: flags %c",
- cdnsp_trb_type_string(type),
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size, "%s: flags %c",
+ cdnsp_trb_type_string(type),
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_DISABLE_SLOT:
- ret += snprintf(str, size, "%s: slot %ld flags %c",
- cdnsp_trb_type_string(type),
- TRB_TO_SLOT_ID(field3),
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size, "%s: slot %ld flags %c",
+ cdnsp_trb_type_string(type),
+ TRB_TO_SLOT_ID(field3),
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_ADDR_DEV:
- ret += snprintf(str, size,
- "%s: ctx %08x%08x slot %ld flags %c:%c",
- cdnsp_trb_type_string(type), field1, field0,
- TRB_TO_SLOT_ID(field3),
- field3 & TRB_BSR ? 'B' : 'b',
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "%s: ctx %08x%08x slot %ld flags %c:%c",
+ cdnsp_trb_type_string(type), field1, field0,
+ TRB_TO_SLOT_ID(field3),
+ field3 & TRB_BSR ? 'B' : 'b',
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_CONFIG_EP:
- ret += snprintf(str, size,
- "%s: ctx %08x%08x slot %ld flags %c:%c",
- cdnsp_trb_type_string(type), field1, field0,
- TRB_TO_SLOT_ID(field3),
- field3 & TRB_DC ? 'D' : 'd',
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "%s: ctx %08x%08x slot %ld flags %c:%c",
+ cdnsp_trb_type_string(type), field1, field0,
+ TRB_TO_SLOT_ID(field3),
+ field3 & TRB_DC ? 'D' : 'd',
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_EVAL_CONTEXT:
- ret += snprintf(str, size,
- "%s: ctx %08x%08x slot %ld flags %c",
- cdnsp_trb_type_string(type), field1, field0,
- TRB_TO_SLOT_ID(field3),
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "%s: ctx %08x%08x slot %ld flags %c",
+ cdnsp_trb_type_string(type), field1, field0,
+ TRB_TO_SLOT_ID(field3),
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_RESET_EP:
case TRB_HALT_ENDPOINT:
case TRB_FLUSH_ENDPOINT:
- ret += snprintf(str, size,
- "%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c",
- cdnsp_trb_type_string(type),
- ep_num, ep_id % 2 ? "out" : "in",
- TRB_TO_EP_INDEX(field3), field1, field0,
- TRB_TO_SLOT_ID(field3),
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c",
+ cdnsp_trb_type_string(type),
+ ep_num, ep_id % 2 ? "out" : "in",
+ TRB_TO_EP_INDEX(field3), field1, field0,
+ TRB_TO_SLOT_ID(field3),
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_STOP_RING:
- ret += snprintf(str, size,
- "%s: ep%d%s(%d) slot %ld sp %d flags %c",
- cdnsp_trb_type_string(type),
- ep_num, ep_id % 2 ? "out" : "in",
- TRB_TO_EP_INDEX(field3),
- TRB_TO_SLOT_ID(field3),
- TRB_TO_SUSPEND_PORT(field3),
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "%s: ep%d%s(%d) slot %ld sp %d flags %c",
+ cdnsp_trb_type_string(type),
+ ep_num, ep_id % 2 ? "out" : "in",
+ TRB_TO_EP_INDEX(field3),
+ TRB_TO_SLOT_ID(field3),
+ TRB_TO_SUSPEND_PORT(field3),
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_SET_DEQ:
- ret += snprintf(str, size,
- "%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld flags %c",
- cdnsp_trb_type_string(type),
- ep_num, ep_id % 2 ? "out" : "in",
- TRB_TO_EP_INDEX(field3), field1, field0,
- TRB_TO_STREAM_ID(field2),
- TRB_TO_SLOT_ID(field3),
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size,
+ "%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld flags %c",
+ cdnsp_trb_type_string(type),
+ ep_num, ep_id % 2 ? "out" : "in",
+ TRB_TO_EP_INDEX(field3), field1, field0,
+ TRB_TO_STREAM_ID(field2),
+ TRB_TO_SLOT_ID(field3),
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_RESET_DEV:
- ret += snprintf(str, size, "%s: slot %ld flags %c",
- cdnsp_trb_type_string(type),
- TRB_TO_SLOT_ID(field3),
- field3 & TRB_CYCLE ? 'C' : 'c');
+ ret = snprintf(str, size, "%s: slot %ld flags %c",
+ cdnsp_trb_type_string(type),
+ TRB_TO_SLOT_ID(field3),
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
case TRB_ENDPOINT_NRDY:
- temp = TRB_TO_HOST_STREAM(field2);
-
- ret += snprintf(str, size,
- "%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c",
- cdnsp_trb_type_string(type),
- ep_num, ep_id % 2 ? "out" : "in",
- TRB_TO_EP_INDEX(field3), temp,
- temp == STREAM_PRIME_ACK ? "(PRIME)" : "",
- temp == STREAM_REJECTED ? "(REJECTED)" : "",
- TRB_TO_DEV_STREAM(field0),
- field3 & TRB_STAT ? 'S' : 's',
- field3 & TRB_CYCLE ? 'C' : 'c');
+ temp = TRB_TO_HOST_STREAM(field2);
+
+ ret = snprintf(str, size,
+ "%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c",
+ cdnsp_trb_type_string(type),
+ ep_num, ep_id % 2 ? "out" : "in",
+ TRB_TO_EP_INDEX(field3), temp,
+ temp == STREAM_PRIME_ACK ? "(PRIME)" : "",
+ temp == STREAM_REJECTED ? "(REJECTED)" : "",
+ TRB_TO_DEV_STREAM(field0),
+ field3 & TRB_STAT ? 'S' : 's',
+ field3 & TRB_CYCLE ? 'C' : 'c');
break;
default:
- ret += snprintf(str, size,
- "type '%s' -> raw %08x %08x %08x %08x",
- cdnsp_trb_type_string(type),
- field0, field1, field2, field3);
+ ret = snprintf(str, size,
+ "type '%s' -> raw %08x %08x %08x %08x",
+ cdnsp_trb_type_string(type),
+ field0, field1, field2, field3);
}
+ if (ret >= size)
+ pr_info("CDNSP: buffer overflowed.\n");
+
return str;
}
enum usb_device_speed speed)
{
u32 fs_speed = 0;
- u64 temp_64;
u32 temp;
int ret;
- temp_64 = cdnsp_read_64(&pdev->ir_set->erst_dequeue);
- temp_64 &= ~ERST_PTR_MASK;
temp = readl(&pdev->ir_set->irq_control);
temp &= ~IMOD_INTERVAL_MASK;
temp |= ((IMOD_DEFAULT_INTERVAL / 250) & IMOD_INTERVAL_MASK);
{
/*
* host doesn't care B_SESSION_VALID event
- * so clear and disbale BSV irq
+ * so clear and disable BSV irq
*/
if (ci->is_otg)
hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/acpi.h>
+#include <linux/debugfs.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/clk/clk-conf.h>
return 0;
}
+static int ulpi_regs_read(struct seq_file *seq, void *data)
+{
+ struct ulpi *ulpi = seq->private;
+
+#define ulpi_print(name, reg) do { \
+ int ret = ulpi_read(ulpi, reg); \
+ if (ret < 0) \
+ return ret; \
+ seq_printf(seq, name " %.02x\n", ret); \
+} while (0)
+
+ ulpi_print("Vendor ID Low ", ULPI_VENDOR_ID_LOW);
+ ulpi_print("Vendor ID High ", ULPI_VENDOR_ID_HIGH);
+ ulpi_print("Product ID Low ", ULPI_PRODUCT_ID_LOW);
+ ulpi_print("Product ID High ", ULPI_PRODUCT_ID_HIGH);
+ ulpi_print("Function Control ", ULPI_FUNC_CTRL);
+ ulpi_print("Interface Control ", ULPI_IFC_CTRL);
+ ulpi_print("OTG Control ", ULPI_OTG_CTRL);
+ ulpi_print("USB Interrupt Enable Rising ", ULPI_USB_INT_EN_RISE);
+ ulpi_print("USB Interrupt Enable Falling", ULPI_USB_INT_EN_FALL);
+ ulpi_print("USB Interrupt Status ", ULPI_USB_INT_STS);
+ ulpi_print("USB Interrupt Latch ", ULPI_USB_INT_LATCH);
+ ulpi_print("Debug ", ULPI_DEBUG);
+ ulpi_print("Scratch Register ", ULPI_SCRATCH);
+ ulpi_print("Carkit Control ", ULPI_CARKIT_CTRL);
+ ulpi_print("Carkit Interrupt Delay ", ULPI_CARKIT_INT_DELAY);
+ ulpi_print("Carkit Interrupt Enable ", ULPI_CARKIT_INT_EN);
+ ulpi_print("Carkit Interrupt Status ", ULPI_CARKIT_INT_STS);
+ ulpi_print("Carkit Interrupt Latch ", ULPI_CARKIT_INT_LATCH);
+ ulpi_print("Carkit Pulse Control ", ULPI_CARKIT_PLS_CTRL);
+ ulpi_print("Transmit Positive Width ", ULPI_TX_POS_WIDTH);
+ ulpi_print("Transmit Negative Width ", ULPI_TX_NEG_WIDTH);
+ ulpi_print("Receive Polarity Recovery ", ULPI_POLARITY_RECOVERY);
+
+ return 0;
+}
+
+static int ulpi_regs_open(struct inode *inode, struct file *f)
+{
+ struct ulpi *ulpi = inode->i_private;
+
+ return single_open(f, ulpi_regs_read, ulpi);
+}
+
+static const struct file_operations ulpi_regs_ops = {
+ .owner = THIS_MODULE,
+ .open = ulpi_regs_open,
+ .release = single_release,
+ .read = seq_read,
+ .llseek = seq_lseek
+};
+
+#define ULPI_ROOT debugfs_lookup(KBUILD_MODNAME, NULL)
+
static int ulpi_register(struct device *dev, struct ulpi *ulpi)
{
int ret;
+ struct dentry *root;
ulpi->dev.parent = dev; /* needed early for ops */
ulpi->dev.bus = &ulpi_bus;
return ret;
}
+ root = debugfs_create_dir(dev_name(dev), ULPI_ROOT);
+ debugfs_create_file("regs", 0444, root, ulpi, &ulpi_regs_ops);
+
dev_dbg(&ulpi->dev, "registered ULPI PHY: vendor %04x, product %04x\n",
ulpi->id.vendor, ulpi->id.product);
*/
void ulpi_unregister_interface(struct ulpi *ulpi)
{
+ debugfs_remove_recursive(debugfs_lookup(dev_name(&ulpi->dev),
+ ULPI_ROOT));
device_unregister(&ulpi->dev);
}
EXPORT_SYMBOL_GPL(ulpi_unregister_interface);
static int __init ulpi_init(void)
{
- return bus_register(&ulpi_bus);
+ int ret;
+ struct dentry *root;
+
+ root = debugfs_create_dir(KBUILD_MODNAME, NULL);
+ ret = bus_register(&ulpi_bus);
+ if (ret)
+ debugfs_remove(root);
+ return ret;
}
subsys_initcall(ulpi_init);
static void __exit ulpi_exit(void)
{
bus_unregister(&ulpi_bus);
+ debugfs_remove_recursive(ULPI_ROOT);
}
module_exit(ulpi_exit);
/* Hard limit, necessary to avoid arithmetic overflow */
#define USBFS_XFER_MAX (UINT_MAX / 2 - 1000000)
-static atomic64_t usbfs_memory_usage; /* Total memory currently allocated */
+static DEFINE_SPINLOCK(usbfs_memory_usage_lock);
+static u64 usbfs_memory_usage; /* Total memory currently allocated */
/* Check whether it's okay to allocate more memory for a transfer */
static int usbfs_increase_memory_usage(u64 amount)
{
- u64 lim;
+ u64 lim, total_mem;
+ unsigned long flags;
+ int ret;
lim = READ_ONCE(usbfs_memory_mb);
lim <<= 20;
- atomic64_add(amount, &usbfs_memory_usage);
-
- if (lim > 0 && atomic64_read(&usbfs_memory_usage) > lim) {
- atomic64_sub(amount, &usbfs_memory_usage);
- return -ENOMEM;
- }
+ ret = 0;
+ spin_lock_irqsave(&usbfs_memory_usage_lock, flags);
+ total_mem = usbfs_memory_usage + amount;
+ if (lim > 0 && total_mem > lim)
+ ret = -ENOMEM;
+ else
+ usbfs_memory_usage = total_mem;
+ spin_unlock_irqrestore(&usbfs_memory_usage_lock, flags);
- return 0;
+ return ret;
}
/* Memory for a transfer is being deallocated */
static void usbfs_decrease_memory_usage(u64 amount)
{
- atomic64_sub(amount, &usbfs_memory_usage);
+ unsigned long flags;
+
+ spin_lock_irqsave(&usbfs_memory_usage_lock, flags);
+ if (amount > usbfs_memory_usage)
+ usbfs_memory_usage = 0;
+ else
+ usbfs_memory_usage -= amount;
+ spin_unlock_irqrestore(&usbfs_memory_usage_lock, flags);
}
static int connected(struct usb_dev_state *ps)
hcd->rsrc_len, driver->description))
break;
}
- if (region == PCI_ROM_RESOURCE) {
+ if (region == PCI_STD_NUM_BARS) {
dev_dbg(&dev->dev, "no i/o regions available\n");
retval = -EBUSY;
goto put_hcd;
status);
}
- /* Check for disconnect or reset */
- if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
+ /*
+ * Check for disconnect or reset, and bail out after several
+ * reset attempts to avoid warm reset loop.
+ */
+ if (status == 0 || status == -ENOTCONN || status == -ENODEV ||
+ (status == -EBUSY && i == PORT_RESET_TRIES - 1)) {
usb_clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_RESET);
retval = usb_get_bos_descriptor(udev);
if (!retval) {
udev->lpm_capable = usb_device_supports_lpm(udev);
+ udev->lpm_disable_count = 1;
usb_set_lpm_parameters(udev);
}
}
*/
usb_disable_usb2_hardware_lpm(udev);
- /* Disable LPM while we reset the device and reinstall the alt settings.
- * Device-initiated LPM, and system exit latency settings are cleared
- * when the device is reset, so we have to set them up again.
- */
- ret = usb_unlocked_disable_lpm(udev);
- if (ret) {
- dev_err(&udev->dev, "%s Failed to disable LPM\n", __func__);
- goto re_enumerate_no_bos;
- }
-
bos = udev->bos;
udev->bos = NULL;
re_enumerate:
usb_release_bos_descriptor(udev);
udev->bos = bos;
-re_enumerate_no_bos:
- /* LPM state doesn't matter when we're about to destroy the device. */
hub_port_logical_disconnect(parent_hub, port1);
return -ENODEV;
}
if (!parent_handle)
return NULL;
- acpi_bus_get_device(parent_handle, &adev);
+ adev = acpi_fetch_acpi_dev(parent_handle);
port1 = port_dev->portnum;
}
* Drivers for USB interfaces should normally record such references in
* their probe() methods, when they bind to an interface, and release
* them by calling usb_put_dev(), in their disconnect() methods.
+ * However, if a driver does not access the usb_device structure after
+ * its disconnect() method returns then refcounting is not necessary,
+ * because the USB core guarantees that a usb_device will not be
+ * deallocated until after all of its interface drivers have been unbound.
*
* Return: A pointer to the device with the incremented reference counter.
*/
* Drivers for USB interfaces should normally record such references in
* their probe() methods, when they bind to an interface, and release
* them by calling usb_put_intf(), in their disconnect() methods.
+ * However, if a driver does not access the usb_interface structure after
+ * its disconnect() method returns then refcounting is not necessary,
+ * because the USB core guarantees that a usb_interface will not be
+ * deallocated until after its driver has been unbound.
*
* Return: A pointer to the interface with the incremented reference counter.
*/
p->phy_utmi_width = 8;
}
+static void dwc2_set_socfpga_agilex_params(struct dwc2_hsotg *hsotg)
+{
+ struct dwc2_core_params *p = &hsotg->params;
+
+ p->power_down = DWC2_POWER_DOWN_PARAM_NONE;
+ p->no_clock_gating = true;
+}
+
static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg)
{
struct dwc2_core_params *p = &hsotg->params;
.data = dwc2_set_stm32mp15_fsotg_params },
{ .compatible = "st,stm32mp15-hsotg",
.data = dwc2_set_stm32mp15_hsotg_params },
+ { .compatible = "intel,socfpga-agilex-hsotg",
+ .data = dwc2_set_socfpga_agilex_params },
{},
};
MODULE_DEVICE_TABLE(of, dwc2_of_match_table);
*/
static void dwc3_ref_clk_period(struct dwc3 *dwc)
{
+ unsigned long period;
+ unsigned long fladj;
+ unsigned long decr;
+ unsigned long rate;
u32 reg;
- if (dwc->ref_clk_per == 0)
+ if (dwc->ref_clk) {
+ rate = clk_get_rate(dwc->ref_clk);
+ if (!rate)
+ return;
+ period = NSEC_PER_SEC / rate;
+ } else if (dwc->ref_clk_per) {
+ period = dwc->ref_clk_per;
+ rate = NSEC_PER_SEC / period;
+ } else {
return;
+ }
reg = dwc3_readl(dwc->regs, DWC3_GUCTL);
reg &= ~DWC3_GUCTL_REFCLKPER_MASK;
- reg |= FIELD_PREP(DWC3_GUCTL_REFCLKPER_MASK, dwc->ref_clk_per);
+ reg |= FIELD_PREP(DWC3_GUCTL_REFCLKPER_MASK, period);
dwc3_writel(dwc->regs, DWC3_GUCTL, reg);
-}
+ if (DWC3_VER_IS_PRIOR(DWC3, 250A))
+ return;
+
+ /*
+ * The calculation below is
+ *
+ * 125000 * (NSEC_PER_SEC / (rate * period) - 1)
+ *
+ * but rearranged for fixed-point arithmetic. The division must be
+ * 64-bit because 125000 * NSEC_PER_SEC doesn't fit in 32 bits (and
+ * neither does rate * period).
+ *
+ * Note that rate * period ~= NSEC_PER_SECOND, minus the number of
+ * nanoseconds of error caused by the truncation which happened during
+ * the division when calculating rate or period (whichever one was
+ * derived from the other). We first calculate the relative error, then
+ * scale it to units of 8 ppm.
+ */
+ fladj = div64_u64(125000ULL * NSEC_PER_SEC, (u64)rate * period);
+ fladj -= 125000;
+
+ /*
+ * The documented 240MHz constant is scaled by 2 to get PLS1 as well.
+ */
+ decr = 480000000 / rate;
+
+ reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
+ reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK
+ & ~DWC3_GFLADJ_240MHZDECR
+ & ~DWC3_GFLADJ_240MHZDECR_PLS1;
+ reg |= FIELD_PREP(DWC3_GFLADJ_REFCLK_FLADJ_MASK, fladj)
+ | FIELD_PREP(DWC3_GFLADJ_240MHZDECR, decr >> 1)
+ | FIELD_PREP(DWC3_GFLADJ_240MHZDECR_PLS1, decr & 1);
+ dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
+}
/**
* dwc3_free_one_event_buffer - Frees one event buffer
return 0;
}
+static int dwc3_clk_enable(struct dwc3 *dwc)
+{
+ int ret;
+
+ ret = clk_prepare_enable(dwc->bus_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(dwc->ref_clk);
+ if (ret)
+ goto disable_bus_clk;
+
+ ret = clk_prepare_enable(dwc->susp_clk);
+ if (ret)
+ goto disable_ref_clk;
+
+ return 0;
+
+disable_ref_clk:
+ clk_disable_unprepare(dwc->ref_clk);
+disable_bus_clk:
+ clk_disable_unprepare(dwc->bus_clk);
+ return ret;
+}
+
+static void dwc3_clk_disable(struct dwc3 *dwc)
+{
+ clk_disable_unprepare(dwc->susp_clk);
+ clk_disable_unprepare(dwc->ref_clk);
+ clk_disable_unprepare(dwc->bus_clk);
+}
+
static void dwc3_core_exit(struct dwc3 *dwc)
{
dwc3_event_buffers_cleanup(dwc);
usb_phy_set_suspend(dwc->usb3_phy, 1);
phy_power_off(dwc->usb2_generic_phy);
phy_power_off(dwc->usb3_generic_phy);
- clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks);
+ dwc3_clk_disable(dwc);
reset_control_assert(dwc->reset);
}
return PTR_ERR(dwc->reset);
if (dev->of_node) {
- ret = devm_clk_bulk_get_all(dev, &dwc->clks);
- if (ret == -EPROBE_DEFER)
- return ret;
/*
* Clocks are optional, but new DT platforms should support all
* clocks as required by the DT-binding.
*/
- if (ret < 0)
- dwc->num_clks = 0;
- else
- dwc->num_clks = ret;
-
+ dwc->bus_clk = devm_clk_get_optional(dev, "bus_early");
+ if (IS_ERR(dwc->bus_clk))
+ return dev_err_probe(dev, PTR_ERR(dwc->bus_clk),
+ "could not get bus clock\n");
+
+ dwc->ref_clk = devm_clk_get_optional(dev, "ref");
+ if (IS_ERR(dwc->ref_clk))
+ return dev_err_probe(dev, PTR_ERR(dwc->ref_clk),
+ "could not get ref clock\n");
+
+ dwc->susp_clk = devm_clk_get_optional(dev, "suspend");
+ if (IS_ERR(dwc->susp_clk))
+ return dev_err_probe(dev, PTR_ERR(dwc->susp_clk),
+ "could not get suspend clock\n");
}
ret = reset_control_deassert(dwc->reset);
if (ret)
return ret;
- ret = clk_bulk_prepare_enable(dwc->num_clks, dwc->clks);
+ ret = dwc3_clk_enable(dwc);
if (ret)
goto assert_reset;
pm_runtime_disable(&pdev->dev);
disable_clks:
- clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks);
+ dwc3_clk_disable(dwc);
assert_reset:
reset_control_assert(dwc->reset);
if (ret)
return ret;
- ret = clk_bulk_prepare_enable(dwc->num_clks, dwc->clks);
+ ret = dwc3_clk_enable(dwc);
if (ret)
goto assert_reset;
return 0;
disable_clks:
- clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks);
+ dwc3_clk_disable(dwc);
assert_reset:
reset_control_assert(dwc->reset);
/* Global Frame Length Adjustment Register */
#define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7)
#define DWC3_GFLADJ_30MHZ_MASK 0x3f
+#define DWC3_GFLADJ_REFCLK_FLADJ_MASK GENMASK(21, 8)
+#define DWC3_GFLADJ_240MHZDECR GENMASK(30, 24)
+#define DWC3_GFLADJ_240MHZDECR_PLS1 BIT(31)
/* Global User Control Register*/
#define DWC3_GUCTL_REFCLKPER_MASK 0xffc00000
* @eps: endpoint array
* @gadget: device side representation of the peripheral controller
* @gadget_driver: pointer to the gadget driver
- * @clks: array of clocks
- * @num_clks: number of clocks
+ * @bus_clk: clock for accessing the registers
+ * @ref_clk: reference clock
+ * @susp_clk: clock used when the SS phy is in low power (S3) state
* @reset: reset control
* @regs: base address for our registers
* @regs_size: address space size
struct usb_gadget *gadget;
struct usb_gadget_driver *gadget_driver;
- struct clk_bulk_data *clks;
- int num_clks;
+ struct clk *bus_clk;
+ struct clk *ref_clk;
+ struct clk *susp_clk;
struct reset_control *reset;
#include <linux/extcon.h>
#include <linux/of_graph.h>
+#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/property.h>
if (IS_ERR(dwc->role_sw))
return PTR_ERR(dwc->role_sw);
+ if (dwc->dev->of_node) {
+ /* populate connector entry */
+ int ret = devm_of_platform_populate(dwc->dev);
+
+ if (ret) {
+ usb_role_switch_unregister(dwc->role_sw);
+ dwc->role_sw = NULL;
+ dev_err(dwc->dev, "DWC3 platform devices creation failed: %i\n", ret);
+ return ret;
+ }
+ }
+
dwc3_set_mode(dwc, mode);
return 0;
}
{}
};
+static const struct property_entry dwc3_pci_intel_phy_charger_detect_properties[] = {
+ PROPERTY_ENTRY_STRING("dr_mode", "peripheral"),
+ PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"),
+ PROPERTY_ENTRY_BOOL("linux,phy_charger_detect"),
+ {}
+};
+
static const struct property_entry dwc3_pci_intel_byt_properties[] = {
PROPERTY_ENTRY_STRING("dr_mode", "peripheral"),
PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"),
.properties = dwc3_pci_intel_properties,
};
+static const struct software_node dwc3_pci_intel_phy_charger_detect_swnode = {
+ .properties = dwc3_pci_intel_phy_charger_detect_properties,
+};
+
static const struct software_node dwc3_pci_intel_byt_swnode = {
.properties = dwc3_pci_intel_byt_properties,
};
.properties = dwc3_pci_mr_properties,
};
-static int dwc3_pci_quirks(struct dwc3_pci *dwc)
+static int dwc3_pci_quirks(struct dwc3_pci *dwc,
+ const struct software_node *swnode)
{
struct pci_dev *pdev = dwc->pci;
gpiod_put(gpio);
usleep_range(10000, 11000);
}
+
+ /*
+ * Make the pdev name predictable (only 1 DWC3 on BYT)
+ * and patch the phy dev-name into the lookup table so
+ * that the phy-driver can get the GPIOs.
+ */
+ dwc->dwc3->id = PLATFORM_DEVID_NONE;
+ platform_bytcr_gpios.dev_id = "dwc3.ulpi";
+
+ /*
+ * Some Android tablets with a Crystal Cove PMIC
+ * (INT33FD), rely on the TUSB1211 phy for charger
+ * detection. These can be identified by them _not_
+ * using the standard ACPI battery and ac drivers.
+ */
+ if (acpi_dev_present("INT33FD", "1", 2) &&
+ acpi_quirk_skip_acpi_ac_and_battery()) {
+ dev_info(&pdev->dev, "Using TUSB1211 phy for charger detection\n");
+ swnode = &dwc3_pci_intel_phy_charger_detect_swnode;
+ }
}
}
- return 0;
+ return device_add_software_node(&dwc->dwc3->dev, swnode);
}
#ifdef CONFIG_PM
dwc->dwc3->dev.parent = dev;
ACPI_COMPANION_SET(&dwc->dwc3->dev, ACPI_COMPANION(dev));
- ret = device_add_software_node(&dwc->dwc3->dev, (void *)id->driver_data);
- if (ret < 0)
- goto err;
-
- ret = dwc3_pci_quirks(dwc);
+ ret = dwc3_pci_quirks(dwc, (void *)id->driver_data);
if (ret)
goto err;
data_len, ret);
data_len -= ret;
- buf = kmalloc(sizeof(*buf) + data_len, GFP_KERNEL);
+ buf = kmalloc(struct_size(buf, storage, data_len), GFP_KERNEL);
if (!buf)
return -ENOMEM;
buf->length = data_len;
buf->data = buf->storage;
- memcpy(buf->storage, data + ret, data_len);
+ memcpy(buf->storage, data + ret, flex_array_size(buf, storage, data_len));
/*
* At this point read_buffer is NULL or READ_BUFFER_DROP (if
int msf = common->cmnd[1] & 0x02;
int start_track = common->cmnd[6];
u8 *buf = (u8 *)bh->buf;
+ u8 format;
+ int i, len;
if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */
start_track > 1) {
return -EINVAL;
}
- memset(buf, 0, 20);
- buf[1] = (20-2); /* TOC data length */
- buf[2] = 1; /* First track number */
- buf[3] = 1; /* Last track number */
- buf[5] = 0x16; /* Data track, copying allowed */
- buf[6] = 0x01; /* Only track is number 1 */
- store_cdrom_address(&buf[8], msf, 0);
+ format = common->cmnd[2] & 0xf;
+ /*
+ * Check if CDB is old style SFF-8020i
+ * i.e. format is in 2 MSBs of byte 9
+ * Mac OS-X host sends us this.
+ */
+ if (format == 0)
+ format = (common->cmnd[9] >> 6) & 0x3;
+
+ switch (format) {
+ case 0:
+ /* Formatted TOC */
+ len = 4 + 2*8; /* 4 byte header + 2 descriptors */
+ memset(buf, 0, len);
+ buf[1] = len - 2; /* TOC Length excludes length field */
+ buf[2] = 1; /* First track number */
+ buf[3] = 1; /* Last track number */
+ buf[5] = 0x16; /* Data track, copying allowed */
+ buf[6] = 0x01; /* Only track is number 1 */
+ store_cdrom_address(&buf[8], msf, 0);
+
+ buf[13] = 0x16; /* Lead-out track is data */
+ buf[14] = 0xAA; /* Lead-out track number */
+ store_cdrom_address(&buf[16], msf, curlun->num_sectors);
+ return len;
+
+ case 2:
+ /* Raw TOC */
+ len = 4 + 3*11; /* 4 byte header + 3 descriptors */
+ memset(buf, 0, len); /* Header + A0, A1 & A2 descriptors */
+ buf[1] = len - 2; /* TOC Length excludes length field */
+ buf[2] = 1; /* First complete session */
+ buf[3] = 1; /* Last complete session */
+
+ buf += 4;
+ /* fill in A0, A1 and A2 points */
+ for (i = 0; i < 3; i++) {
+ buf[0] = 1; /* Session number */
+ buf[1] = 0x16; /* Data track, copying allowed */
+ /* 2 - Track number 0 -> TOC */
+ buf[3] = 0xA0 + i; /* A0, A1, A2 point */
+ /* 4, 5, 6 - Min, sec, frame is zero */
+ buf[8] = 1; /* Pmin: last track number */
+ buf += 11; /* go to next track descriptor */
+ }
+ buf -= 11; /* go back to A2 descriptor */
- buf[13] = 0x16; /* Lead-out track is data */
- buf[14] = 0xAA; /* Lead-out track number */
- store_cdrom_address(&buf[16], msf, curlun->num_sectors);
- return 20;
+ /* For A2, 7, 8, 9, 10 - zero, Pmin, Psec, Pframe of Lead out */
+ store_cdrom_address(&buf[7], msf, curlun->num_sectors);
+ return len;
+
+ default:
+ /* Multi-session, PMA, ATIP, CD-TEXT not supported/required */
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
}
static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh)
common->data_size_from_cmnd =
get_unaligned_be16(&common->cmnd[7]);
reply = check_command(common, 10, DATA_DIR_TO_HOST,
- (7<<6) | (1<<1), 1,
+ (0xf<<6) | (3<<1), 1,
"READ TOC");
if (reply == 0)
reply = do_read_toc(common, bh);
{
struct f_phonet *fp;
struct f_phonet_opts *opts;
- int size;
- size = sizeof(*fp) + (phonet_rxq_size * sizeof(struct usb_request *));
- fp = kzalloc(size, GFP_KERNEL);
+ fp = kzalloc(struct_size(fp, out_reqv, phonet_rxq_size), GFP_KERNEL);
if (!fp)
return ERR_PTR(-ENOMEM);
static void gser_unbind(struct usb_configuration *c, struct usb_function *f)
{
+ struct f_gser *gser = func_to_gser(f);
+
+ /* Ensure port is disconnected before unbinding */
+ gserial_disconnect(&gser->port);
usb_free_all_descriptors(f);
}
* f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API)
*
* Copyright (C) 2016 Ruslan Bilovol <ruslan.bilovol@gmail.com>
+ * Copyright (C) 2021 Julian Scheel <julian@jusst.de>
*
* This driver doesn't expect any real Audio codec to be present
* on the device - the audio streams are simply sinked to and
/* Interrupt IN endpoint of AC interface */
struct usb_ep *int_ep;
atomic_t int_count;
+ int ctl_id; /* EP id */
+ int c_srate; /* current capture srate */
+ int p_srate; /* current playback prate */
};
static inline struct f_uac1 *func_to_uac1(struct usb_function *f)
.wFormatTag = cpu_to_le16(UAC_FORMAT_TYPE_I_PCM),
};
-DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(UAC_MAX_RATES);
+#define uac_format_type_i_discrete_descriptor \
+ uac_format_type_i_discrete_descriptor_##UAC_MAX_RATES
-static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = {
- .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+static struct uac_format_type_i_discrete_descriptor as_out_type_i_desc = {
+ .bLength = 0, /* filled on rate setup */
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_FORMAT_TYPE,
.bFormatType = UAC_FORMAT_TYPE_I,
.bSubframeSize = 2,
.bBitResolution = 16,
- .bSamFreqType = 1,
+ .bSamFreqType = 0, /* filled on rate setup */
};
/* Standard ISO OUT Endpoint Descriptor */
.wLockDelay = cpu_to_le16(1),
};
-static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = {
- .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+static struct uac_format_type_i_discrete_descriptor as_in_type_i_desc = {
+ .bLength = 0, /* filled on rate setup */
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubtype = UAC_FORMAT_TYPE,
.bFormatType = UAC_FORMAT_TYPE_I,
.bSubframeSize = 2,
.bBitResolution = 16,
- .bSamFreqType = 1,
+ .bSamFreqType = 0, /* filled on rate setup */
};
/* Standard ISO OUT Endpoint Descriptor */
};
static struct usb_string strings_uac1[] = {
- [STR_AC_IF].s = "AC Interface",
+ /* [STR_AC_IF].s = DYNAMIC, */
[STR_USB_OUT_IT].s = "Playback Input terminal",
[STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels",
[STR_IO_OUT_OT].s = "Playback Output terminal",
* This function is an ALSA sound card following USB Audio Class Spec 1.0.
*/
+static void uac_cs_attr_sample_rate(struct usb_ep *ep, struct usb_request *req)
+{
+ struct usb_function *fn = ep->driver_data;
+ struct usb_composite_dev *cdev = fn->config->cdev;
+ struct g_audio *agdev = func_to_g_audio(fn);
+ struct f_uac1 *uac1 = func_to_uac1(fn);
+ u8 *buf = (u8 *)req->buf;
+ u32 val = 0;
+
+ if (req->actual != 3) {
+ WARN(cdev, "Invalid data size for UAC_EP_CS_ATTR_SAMPLE_RATE.\n");
+ return;
+ }
+
+ val = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+ if (uac1->ctl_id == (USB_DIR_IN | 2)) {
+ uac1->p_srate = val;
+ u_audio_set_playback_srate(agdev, uac1->p_srate);
+ } else if (uac1->ctl_id == (USB_DIR_OUT | 1)) {
+ uac1->c_srate = val;
+ u_audio_set_capture_srate(agdev, uac1->c_srate);
+ }
+}
+
static void audio_notify_complete(struct usb_ep *_ep, struct usb_request *req)
{
struct g_audio *audio = req->context;
const struct usb_ctrlrequest *ctrl)
{
struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = f->config->cdev->req;
+ struct f_uac1 *uac1 = func_to_uac1(f);
int value = -EOPNOTSUPP;
u16 ep = le16_to_cpu(ctrl->wIndex);
u16 len = le16_to_cpu(ctrl->wLength);
u16 w_value = le16_to_cpu(ctrl->wValue);
+ u8 cs = w_value >> 8;
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
ctrl->bRequest, w_value, len, ep);
switch (ctrl->bRequest) {
- case UAC_SET_CUR:
+ case UAC_SET_CUR: {
+ if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+ cdev->gadget->ep0->driver_data = f;
+ uac1->ctl_id = ep;
+ req->complete = uac_cs_attr_sample_rate;
+ }
value = len;
break;
+ }
case UAC_SET_MIN:
break;
const struct usb_ctrlrequest *ctrl)
{
struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = f->config->cdev->req;
+ struct f_uac1 *uac1 = func_to_uac1(f);
+ u8 *buf = (u8 *)req->buf;
int value = -EOPNOTSUPP;
- u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+ u8 ep = le16_to_cpu(ctrl->wIndex);
u16 len = le16_to_cpu(ctrl->wLength);
u16 w_value = le16_to_cpu(ctrl->wValue);
+ u8 cs = w_value >> 8;
+ u32 val = 0;
DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
ctrl->bRequest, w_value, len, ep);
switch (ctrl->bRequest) {
- case UAC_GET_CUR:
+ case UAC_GET_CUR: {
+ if (cs == UAC_EP_CS_ATTR_SAMPLE_RATE) {
+ if (ep == (USB_DIR_IN | 2))
+ val = uac1->p_srate;
+ else if (ep == (USB_DIR_OUT | 1))
+ val = uac1->c_srate;
+ buf[2] = (val >> 16) & 0xff;
+ buf[1] = (val >> 8) & 0xff;
+ buf[0] = val & 0xff;
+ }
+ value = len;
+ break;
+ }
case UAC_GET_MIN:
case UAC_GET_MAX:
case UAC_GET_RES:
usb_ep_disable(uac1->int_ep);
}
+static void
+f_audio_suspend(struct usb_function *f)
+{
+ struct f_uac1 *uac1 = func_to_uac1(f);
+
+ u_audio_suspend(&uac1->g_audio);
+}
+
/*-------------------------------------------------------------------------*/
static struct uac_feature_unit_descriptor *build_fu_desc(int chmask)
{
} else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
dev_err(dev, "Error: incorrect capture sample size\n");
return -EINVAL;
- } else if (!opts->p_srate) {
+ } else if (!opts->p_srates[0]) {
dev_err(dev, "Error: incorrect playback sampling rate\n");
return -EINVAL;
- } else if (!opts->c_srate) {
+ } else if (!opts->c_srates[0]) {
dev_err(dev, "Error: incorrect capture sampling rate\n");
return -EINVAL;
}
struct f_uac1_opts *audio_opts;
struct usb_ep *ep = NULL;
struct usb_string *us;
- u8 *sam_freq;
- int rate;
int ba_iface_id;
int status;
+ int idx, i;
status = f_audio_validate_opts(audio, dev);
if (status)
audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst);
+ strings_uac1[STR_AC_IF].s = audio_opts->function_name;
+
us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1));
if (IS_ERR(us))
return PTR_ERR(us);
}
/* Set sample rates */
- rate = audio_opts->c_srate;
- sam_freq = as_out_type_i_desc.tSamFreq[0];
- memcpy(sam_freq, &rate, 3);
- rate = audio_opts->p_srate;
- sam_freq = as_in_type_i_desc.tSamFreq[0];
- memcpy(sam_freq, &rate, 3);
+ for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+ if (audio_opts->c_srates[i] == 0)
+ break;
+ memcpy(as_out_type_i_desc.tSamFreq[idx++],
+ &audio_opts->c_srates[i], 3);
+ }
+ as_out_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+ as_out_type_i_desc.bSamFreqType = idx;
+
+ for (i = 0, idx = 0; i < UAC_MAX_RATES; i++) {
+ if (audio_opts->p_srates[i] == 0)
+ break;
+ memcpy(as_in_type_i_desc.tSamFreq[idx++],
+ &audio_opts->p_srates[i], 3);
+ }
+ as_in_type_i_desc.bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(idx);
+ as_in_type_i_desc.bSamFreqType = idx;
+ uac1->p_srate = audio_opts->p_srates[0];
+ uac1->c_srate = audio_opts->c_srates[0];
/* allocate instance-specific interface IDs, and patch descriptors */
status = usb_interface_id(c, f);
audio->out_ep_maxpsize = le16_to_cpu(as_out_ep_desc.wMaxPacketSize);
audio->in_ep_maxpsize = le16_to_cpu(as_in_ep_desc.wMaxPacketSize);
audio->params.c_chmask = audio_opts->c_chmask;
- audio->params.c_srate = audio_opts->c_srate;
+ memcpy(audio->params.c_srates, audio_opts->c_srates,
+ sizeof(audio->params.c_srates));
audio->params.c_ssize = audio_opts->c_ssize;
if (FUIN_EN(audio_opts)) {
audio->params.p_fu.id = USB_IN_FU_ID;
audio->params.p_fu.volume_res = audio_opts->p_volume_res;
}
audio->params.p_chmask = audio_opts->p_chmask;
- audio->params.p_srate = audio_opts->p_srate;
+ memcpy(audio->params.p_srates, audio_opts->p_srates,
+ sizeof(audio->params.p_srates));
audio->params.p_ssize = audio_opts->p_ssize;
if (FUOUT_EN(audio_opts)) {
audio->params.c_fu.id = USB_OUT_FU_ID;
\
CONFIGFS_ATTR(f_uac1_opts_, name)
+#define UAC1_RATE_ATTRIBUTE(name) \
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ struct f_uac1_opts *opts = to_f_uac1_opts(item); \
+ int result = 0; \
+ int i; \
+ \
+ mutex_lock(&opts->lock); \
+ page[0] = '\0'; \
+ for (i = 0; i < UAC_MAX_RATES; i++) { \
+ if (opts->name##s[i] == 0) \
+ break; \
+ result += sprintf(page + strlen(page), "%u,", \
+ opts->name##s[i]); \
+ } \
+ if (strlen(page) > 0) \
+ page[strlen(page) - 1] = '\n'; \
+ mutex_unlock(&opts->lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct f_uac1_opts *opts = to_f_uac1_opts(item); \
+ char *split_page = NULL; \
+ int ret = -EINVAL; \
+ char *token; \
+ u32 num; \
+ int i; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ i = 0; \
+ memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
+ split_page = kstrdup(page, GFP_KERNEL); \
+ while ((token = strsep(&split_page, ",")) != NULL) { \
+ ret = kstrtou32(token, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ opts->name##s[i++] = num; \
+ ret = len; \
+ }; \
+ \
+end: \
+ kfree(split_page); \
+ mutex_unlock(&opts->lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
+#define UAC1_ATTRIBUTE_STRING(name) \
+static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ struct f_uac1_opts *opts = to_f_uac1_opts(item); \
+ int result; \
+ \
+ mutex_lock(&opts->lock); \
+ result = snprintf(page, sizeof(opts->name), "%s", opts->name); \
+ mutex_unlock(&opts->lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct f_uac1_opts *opts = to_f_uac1_opts(item); \
+ int ret = 0; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ ret = snprintf(opts->name, min(sizeof(opts->name), len), \
+ "%s", page); \
+ \
+end: \
+ mutex_unlock(&opts->lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(f_uac1_opts_, name)
+
UAC1_ATTRIBUTE(u32, c_chmask);
-UAC1_ATTRIBUTE(u32, c_srate);
+UAC1_RATE_ATTRIBUTE(c_srate);
UAC1_ATTRIBUTE(u32, c_ssize);
UAC1_ATTRIBUTE(u32, p_chmask);
-UAC1_ATTRIBUTE(u32, p_srate);
+UAC1_RATE_ATTRIBUTE(p_srate);
UAC1_ATTRIBUTE(u32, p_ssize);
UAC1_ATTRIBUTE(u32, req_number);
UAC1_ATTRIBUTE(s16, c_volume_min);
UAC1_ATTRIBUTE(s16, c_volume_max);
UAC1_ATTRIBUTE(s16, c_volume_res);
+UAC1_ATTRIBUTE_STRING(function_name);
static struct configfs_attribute *f_uac1_attrs[] = {
&f_uac1_opts_attr_c_chmask,
&f_uac1_opts_attr_c_volume_max,
&f_uac1_opts_attr_c_volume_res,
+ &f_uac1_opts_attr_function_name,
+
NULL,
};
&f_uac1_func_type);
opts->c_chmask = UAC1_DEF_CCHMASK;
- opts->c_srate = UAC1_DEF_CSRATE;
+ opts->c_srates[0] = UAC1_DEF_CSRATE;
opts->c_ssize = UAC1_DEF_CSSIZE;
opts->p_chmask = UAC1_DEF_PCHMASK;
- opts->p_srate = UAC1_DEF_PSRATE;
+ opts->p_srates[0] = UAC1_DEF_PSRATE;
opts->p_ssize = UAC1_DEF_PSSIZE;
opts->p_mute_present = UAC1_DEF_MUTE_PRESENT;
opts->c_volume_res = UAC1_DEF_RES_DB;
opts->req_number = UAC1_DEF_REQ_NUM;
+
+ snprintf(opts->function_name, sizeof(opts->function_name), "AC Interface");
+
return &opts->func_inst;
}
uac1->g_audio.func.get_alt = f_audio_get_alt;
uac1->g_audio.func.setup = f_audio_setup;
uac1->g_audio.func.disable = f_audio_disable;
+ uac1->g_audio.func.suspend = f_audio_suspend;
uac1->g_audio.func.free_func = f_audio_free;
return &uac1->g_audio.func;
/* Interrupt IN endpoint of AC interface */
struct usb_ep *int_ep;
atomic_t int_count;
+ /* transient state, only valid during handling of a single control request */
+ int clock_id;
};
static inline struct f_uac2 *func_to_uac2(struct usb_function *f)
STR_AS_IN_ALT1,
};
-static char clksrc_in[8];
-static char clksrc_out[8];
-
static struct usb_string strings_fn[] = {
- [STR_ASSOC].s = "Source/Sink",
+ /* [STR_ASSOC].s = DYNAMIC, */
[STR_IF_CTRL].s = "Topology Control",
- [STR_CLKSRC_IN].s = clksrc_in,
- [STR_CLKSRC_OUT].s = clksrc_out,
+ [STR_CLKSRC_IN].s = "Input Clock",
+ [STR_CLKSRC_OUT].s = "Output Clock",
[STR_USB_IT].s = "USBH Out",
[STR_IO_IT].s = "USBD Out",
[STR_USB_OT].s = "USBH In",
{ },
};
+static const char *const speed_names[] = {
+ [USB_SPEED_UNKNOWN] = "UNKNOWN",
+ [USB_SPEED_LOW] = "LS",
+ [USB_SPEED_FULL] = "FS",
+ [USB_SPEED_HIGH] = "HS",
+ [USB_SPEED_WIRELESS] = "W",
+ [USB_SPEED_SUPER] = "SS",
+ [USB_SPEED_SUPER_PLUS] = "SS+",
+};
+
static struct usb_gadget_strings str_fn = {
.language = 0x0409, /* en-us */
.strings = strings_fn,
.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
/* .bClockID = DYNAMIC */
.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
- .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+ .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL),
.bAssocTerminal = 0,
};
.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
/* .bClockID = DYNAMIC */
.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
- .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
+ .bmControls = (CONTROL_RDWR << CLK_FREQ_CTRL),
.bAssocTerminal = 0,
};
/* .bmAttributes = DYNAMIC */
/* .wMaxPacketSize = DYNAMIC */
- .bInterval = 4,
+ /* .bInterval = DYNAMIC */
};
static struct usb_endpoint_descriptor ss_epout_desc = {
.bEndpointAddress = USB_DIR_OUT,
/* .bmAttributes = DYNAMIC */
/* .wMaxPacketSize = DYNAMIC */
- .bInterval = 4,
+ /* .bInterval = DYNAMIC */
};
static struct usb_ss_ep_comp_descriptor ss_epout_desc_comp = {
.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
/* .wMaxPacketSize = DYNAMIC */
- .bInterval = 4,
+ /* .bInterval = DYNAMIC */
};
static struct usb_endpoint_descriptor ss_epin_desc = {
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
/* .wMaxPacketSize = DYNAMIC */
- .bInterval = 4,
+ /* .bInterval = DYNAMIC */
};
static struct usb_ss_ep_comp_descriptor ss_epin_desc_comp = {
__le32 dCUR;
};
-struct cntrl_range_lay3 {
- __le16 wNumSubRanges;
+struct cntrl_subrange_lay3 {
__le32 dMIN;
__le32 dMAX;
__le32 dRES;
} __packed;
-static int set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts,
- struct usb_endpoint_descriptor *ep_desc,
- enum usb_device_speed speed, bool is_playback)
-{
- int chmask, srate, ssize;
- u16 max_size_bw, max_size_ep;
- unsigned int factor;
+#define ranges_lay3_size(c) (sizeof(c.wNumSubRanges) \
+ + le16_to_cpu(c.wNumSubRanges) \
+ * sizeof(struct cntrl_subrange_lay3))
- switch (speed) {
- case USB_SPEED_FULL:
- max_size_ep = 1023;
- factor = 1000;
- break;
+#define DECLARE_UAC2_CNTRL_RANGES_LAY3(k, n) \
+ struct cntrl_ranges_lay3_##k { \
+ __le16 wNumSubRanges; \
+ struct cntrl_subrange_lay3 r[n]; \
+} __packed
- case USB_SPEED_HIGH:
- case USB_SPEED_SUPER:
- max_size_ep = 1024;
- factor = 8000;
- break;
+DECLARE_UAC2_CNTRL_RANGES_LAY3(srates, UAC_MAX_RATES);
- default:
- return -EINVAL;
+static int get_max_srate(const int *srates)
+{
+ int i, max_srate = 0;
+
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (srates[i] == 0)
+ break;
+ if (srates[i] > max_srate)
+ max_srate = srates[i];
}
+ return max_srate;
+}
+
+static int get_max_bw_for_bint(const struct f_uac2_opts *uac2_opts,
+ u8 bint, unsigned int factor, bool is_playback)
+{
+ int chmask, srate, ssize;
+ u16 max_size_bw;
if (is_playback) {
chmask = uac2_opts->p_chmask;
- srate = uac2_opts->p_srate;
+ srate = get_max_srate(uac2_opts->p_srates);
ssize = uac2_opts->p_ssize;
} else {
chmask = uac2_opts->c_chmask;
- srate = uac2_opts->c_srate;
+ srate = get_max_srate(uac2_opts->c_srates);
ssize = uac2_opts->c_ssize;
}
srate = srate * (1000 + uac2_opts->fb_max) / 1000;
// updated srate is always bigger, therefore DIV_ROUND_UP always yields +1
max_size_bw = num_channels(chmask) * ssize *
- (DIV_ROUND_UP(srate, factor / (1 << (ep_desc->bInterval - 1))));
+ (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))));
} else {
// adding 1 frame provision for Win10
max_size_bw = num_channels(chmask) * ssize *
- (DIV_ROUND_UP(srate, factor / (1 << (ep_desc->bInterval - 1))) + 1);
+ (DIV_ROUND_UP(srate, factor / (1 << (bint - 1))) + 1);
}
- ep_desc->wMaxPacketSize = cpu_to_le16(min_t(u16, max_size_bw,
- max_size_ep));
+ return max_size_bw;
+}
+
+static int set_ep_max_packet_size_bint(struct device *dev, const struct f_uac2_opts *uac2_opts,
+ struct usb_endpoint_descriptor *ep_desc,
+ enum usb_device_speed speed, bool is_playback)
+{
+ u16 max_size_bw, max_size_ep;
+ u8 bint, opts_bint;
+ char *dir;
+
+ switch (speed) {
+ case USB_SPEED_FULL:
+ max_size_ep = 1023;
+ // fixed
+ bint = ep_desc->bInterval;
+ max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 1000, is_playback);
+ break;
+
+ case USB_SPEED_HIGH:
+ case USB_SPEED_SUPER:
+ max_size_ep = 1024;
+ if (is_playback)
+ opts_bint = uac2_opts->p_hs_bint;
+ else
+ opts_bint = uac2_opts->c_hs_bint;
+
+ if (opts_bint > 0) {
+ /* fixed bint */
+ bint = opts_bint;
+ max_size_bw = get_max_bw_for_bint(uac2_opts, bint, 8000, is_playback);
+ } else {
+ /* checking bInterval from 4 to 1 whether the required bandwidth fits */
+ for (bint = 4; bint > 0; --bint) {
+ max_size_bw = get_max_bw_for_bint(
+ uac2_opts, bint, 8000, is_playback);
+ if (max_size_bw <= max_size_ep)
+ break;
+ }
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (is_playback)
+ dir = "Playback";
+ else
+ dir = "Capture";
+
+ if (max_size_bw <= max_size_ep)
+ dev_dbg(dev,
+ "%s %s: Would use wMaxPacketSize %d and bInterval %d\n",
+ speed_names[speed], dir, max_size_bw, bint);
+ else {
+ dev_warn(dev,
+ "%s %s: Req. wMaxPacketSize %d at bInterval %d > max ISOC %d, may drop data!\n",
+ speed_names[speed], dir, max_size_bw, bint, max_size_ep);
+ max_size_bw = max_size_ep;
+ }
+
+ ep_desc->wMaxPacketSize = cpu_to_le16(max_size_bw);
+ ep_desc->bInterval = bint;
return 0;
}
static int afunc_validate_opts(struct g_audio *agdev, struct device *dev)
{
struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
-
- if (!opts->p_chmask && !opts->c_chmask) {
- dev_err(dev, "Error: no playback and capture channels\n");
- return -EINVAL;
- } else if (opts->p_chmask & ~UAC2_CHANNEL_MASK) {
- dev_err(dev, "Error: unsupported playback channels mask\n");
- return -EINVAL;
- } else if (opts->c_chmask & ~UAC2_CHANNEL_MASK) {
- dev_err(dev, "Error: unsupported capture channels mask\n");
- return -EINVAL;
- } else if ((opts->p_ssize < 1) || (opts->p_ssize > 4)) {
- dev_err(dev, "Error: incorrect playback sample size\n");
- return -EINVAL;
- } else if ((opts->c_ssize < 1) || (opts->c_ssize > 4)) {
- dev_err(dev, "Error: incorrect capture sample size\n");
- return -EINVAL;
- } else if (!opts->p_srate) {
- dev_err(dev, "Error: incorrect playback sampling rate\n");
+ const char *msg = NULL;
+
+ if (!opts->p_chmask && !opts->c_chmask)
+ msg = "no playback and capture channels";
+ else if (opts->p_chmask & ~UAC2_CHANNEL_MASK)
+ msg = "unsupported playback channels mask";
+ else if (opts->c_chmask & ~UAC2_CHANNEL_MASK)
+ msg = "unsupported capture channels mask";
+ else if ((opts->p_ssize < 1) || (opts->p_ssize > 4))
+ msg = "incorrect playback sample size";
+ else if ((opts->c_ssize < 1) || (opts->c_ssize > 4))
+ msg = "incorrect capture sample size";
+ else if (!opts->p_srates[0])
+ msg = "incorrect playback sampling rate";
+ else if (!opts->c_srates[0])
+ msg = "incorrect capture sampling rate";
+
+ else if (opts->p_volume_max <= opts->p_volume_min)
+ msg = "incorrect playback volume max/min";
+ else if (opts->c_volume_max <= opts->c_volume_min)
+ msg = "incorrect capture volume max/min";
+ else if (opts->p_volume_res <= 0)
+ msg = "negative/zero playback volume resolution";
+ else if (opts->c_volume_res <= 0)
+ msg = "negative/zero capture volume resolution";
+
+ else if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res)
+ msg = "incorrect playback volume resolution";
+ else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res)
+ msg = "incorrect capture volume resolution";
+
+ else if ((opts->p_hs_bint < 0) || (opts->p_hs_bint > 4))
+ msg = "incorrect playback HS/SS bInterval (1-4: fixed, 0: auto)";
+ else if ((opts->c_hs_bint < 0) || (opts->c_hs_bint > 4))
+ msg = "incorrect capture HS/SS bInterval (1-4: fixed, 0: auto)";
+
+ if (msg) {
+ dev_err(dev, "Error: %s\n", msg);
return -EINVAL;
- } else if (!opts->c_srate) {
- dev_err(dev, "Error: incorrect capture sampling rate\n");
- return -EINVAL;
- }
-
- if (opts->p_volume_max <= opts->p_volume_min) {
- dev_err(dev, "Error: incorrect playback volume max/min\n");
- return -EINVAL;
- } else if (opts->c_volume_max <= opts->c_volume_min) {
- dev_err(dev, "Error: incorrect capture volume max/min\n");
- return -EINVAL;
- } else if (opts->p_volume_res <= 0) {
- dev_err(dev, "Error: negative/zero playback volume resolution\n");
- return -EINVAL;
- } else if (opts->c_volume_res <= 0) {
- dev_err(dev, "Error: negative/zero capture volume resolution\n");
- return -EINVAL;
- }
-
- if ((opts->p_volume_max - opts->p_volume_min) % opts->p_volume_res) {
- dev_err(dev, "Error: incorrect playback volume resolution\n");
- return -EINVAL;
- } else if ((opts->c_volume_max - opts->c_volume_min) % opts->c_volume_res) {
- dev_err(dev, "Error: incorrect capture volume resolution\n");
- return -EINVAL;
}
return 0;
if (ret)
return ret;
+ strings_fn[STR_ASSOC].s = uac2_opts->function_name;
+
us = usb_gstrings_attach(cdev, fn_strings, ARRAY_SIZE(strings_fn));
if (IS_ERR(us))
return PTR_ERR(us);
*bma = cpu_to_le32(control);
}
- snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", uac2_opts->p_srate);
- snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", uac2_opts->c_srate);
-
ret = usb_interface_id(cfg, fn);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
std_ac_if_desc.bNumEndpoints = 1;
}
+ hs_epin_desc.bInterval = uac2_opts->p_hs_bint;
+ ss_epin_desc.bInterval = uac2_opts->p_hs_bint;
+ hs_epout_desc.bInterval = uac2_opts->c_hs_bint;
+ ss_epout_desc.bInterval = uac2_opts->c_hs_bint;
+
/* Calculate wMaxPacketSize according to audio bandwidth */
- ret = set_ep_max_packet_size(uac2_opts, &fs_epin_desc, USB_SPEED_FULL,
- true);
+ ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epin_desc,
+ USB_SPEED_FULL, true);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size(uac2_opts, &fs_epout_desc, USB_SPEED_FULL,
- false);
+ ret = set_ep_max_packet_size_bint(dev, uac2_opts, &fs_epout_desc,
+ USB_SPEED_FULL, false);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size(uac2_opts, &hs_epin_desc, USB_SPEED_HIGH,
- true);
+ ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epin_desc,
+ USB_SPEED_HIGH, true);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size(uac2_opts, &hs_epout_desc, USB_SPEED_HIGH,
- false);
+ ret = set_ep_max_packet_size_bint(dev, uac2_opts, &hs_epout_desc,
+ USB_SPEED_HIGH, false);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size(uac2_opts, &ss_epin_desc, USB_SPEED_SUPER,
- true);
+ ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epin_desc,
+ USB_SPEED_SUPER, true);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
}
- ret = set_ep_max_packet_size(uac2_opts, &ss_epout_desc, USB_SPEED_SUPER,
- false);
+ ret = set_ep_max_packet_size_bint(dev, uac2_opts, &ss_epout_desc,
+ USB_SPEED_SUPER, false);
if (ret < 0) {
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
return ret;
agdev->gadget = gadget;
agdev->params.p_chmask = uac2_opts->p_chmask;
- agdev->params.p_srate = uac2_opts->p_srate;
+ memcpy(agdev->params.p_srates, uac2_opts->p_srates,
+ sizeof(agdev->params.p_srates));
agdev->params.p_ssize = uac2_opts->p_ssize;
if (FUIN_EN(uac2_opts)) {
agdev->params.p_fu.id = USB_IN_FU_ID;
agdev->params.p_fu.volume_res = uac2_opts->p_volume_res;
}
agdev->params.c_chmask = uac2_opts->c_chmask;
- agdev->params.c_srate = uac2_opts->c_srate;
+ memcpy(agdev->params.c_srates, uac2_opts->c_srates,
+ sizeof(agdev->params.c_srates));
agdev->params.c_ssize = uac2_opts->c_ssize;
if (FUOUT_EN(uac2_opts)) {
agdev->params.c_fu.id = USB_OUT_FU_ID;
usb_ep_disable(uac2->int_ep);
}
+static void
+afunc_suspend(struct usb_function *fn)
+{
+ struct f_uac2 *uac2 = func_to_uac2(fn);
+
+ u_audio_suspend(&uac2->g_audio);
+}
+
static int
in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
- int p_srate, c_srate;
+ u32 p_srate, c_srate;
- p_srate = opts->p_srate;
- c_srate = opts->c_srate;
+ u_audio_get_playback_srate(agdev, &p_srate);
+ u_audio_get_capture_srate(agdev, &c_srate);
if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
int value = -EOPNOTSUPP;
- int p_srate, c_srate;
-
- p_srate = opts->p_srate;
- c_srate = opts->c_srate;
if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
- struct cntrl_range_lay3 r;
+ struct cntrl_ranges_lay3_srates rs;
+ int i;
+ int wNumSubRanges = 0;
+ int srate;
+ int *srates;
if (entity_id == USB_IN_CLK_ID)
- r.dMIN = cpu_to_le32(p_srate);
+ srates = opts->p_srates;
else if (entity_id == USB_OUT_CLK_ID)
- r.dMIN = cpu_to_le32(c_srate);
+ srates = opts->c_srates;
else
return -EOPNOTSUPP;
-
- r.dMAX = r.dMIN;
- r.dRES = 0;
- r.wNumSubRanges = cpu_to_le16(1);
-
- value = min_t(unsigned int, w_length, sizeof(r));
- memcpy(req->buf, &r, value);
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ srate = srates[i];
+ if (srate == 0)
+ break;
+
+ rs.r[wNumSubRanges].dMIN = cpu_to_le32(srate);
+ rs.r[wNumSubRanges].dMAX = cpu_to_le32(srate);
+ rs.r[wNumSubRanges].dRES = 0;
+ wNumSubRanges++;
+ dev_dbg(&agdev->gadget->dev,
+ "%s(): clk %d: rate ID %d: %d\n",
+ __func__, entity_id, wNumSubRanges, srate);
+ }
+ rs.wNumSubRanges = cpu_to_le16(wNumSubRanges);
+ value = min_t(unsigned int, w_length, ranges_lay3_size(rs));
+ dev_dbg(&agdev->gadget->dev, "%s(): sending %d rates, size %d\n",
+ __func__, rs.wNumSubRanges, value);
+ memcpy(req->buf, &rs, value);
} else {
dev_err(&agdev->gadget->dev,
"%s:%d control_selector=%d TODO!\n",
return -EOPNOTSUPP;
}
+static void uac2_cs_control_sam_freq(struct usb_ep *ep, struct usb_request *req)
+{
+ struct usb_function *fn = ep->driver_data;
+ struct g_audio *agdev = func_to_g_audio(fn);
+ struct f_uac2 *uac2 = func_to_uac2(fn);
+ u32 val;
+
+ if (req->actual != 4)
+ return;
+
+ val = le32_to_cpu(*((__le32 *)req->buf));
+ dev_dbg(&agdev->gadget->dev, "%s val: %d.\n", __func__, val);
+ if (uac2->clock_id == USB_IN_CLK_ID) {
+ u_audio_set_playback_srate(agdev, val);
+ } else if (uac2->clock_id == USB_OUT_CLK_ID) {
+ u_audio_set_capture_srate(agdev, val);
+ }
+}
+
static void
out_rq_cur_complete(struct usb_ep *ep, struct usb_request *req)
{
static int
out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
{
+ struct usb_composite_dev *cdev = fn->config->cdev;
struct usb_request *req = fn->config->cdev->req;
struct g_audio *agdev = func_to_g_audio(fn);
struct f_uac2_opts *opts = g_audio_to_uac2_opts(agdev);
u16 w_value = le16_to_cpu(cr->wValue);
u8 entity_id = (w_index >> 8) & 0xff;
u8 control_selector = w_value >> 8;
+ u8 clock_id = w_index >> 8;
if ((entity_id == USB_IN_CLK_ID) || (entity_id == USB_OUT_CLK_ID)) {
- if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
+ if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
+ dev_dbg(&agdev->gadget->dev,
+ "control_selector UAC2_CS_CONTROL_SAM_FREQ, clock: %d\n", clock_id);
+ cdev->gadget->ep0->driver_data = fn;
+ uac2->clock_id = clock_id;
+ req->complete = uac2_cs_control_sam_freq;
return w_length;
+ }
} else if ((FUIN_EN(opts) && (entity_id == USB_IN_FU_ID)) ||
(FUOUT_EN(opts) && (entity_id == USB_OUT_FU_ID))) {
memcpy(&uac2->setup_cr, cr, sizeof(*cr));
.release = f_uac2_attr_release,
};
+#define uac2_kstrtou8 kstrtou8
#define uac2_kstrtou32 kstrtou32
#define uac2_kstrtos16 kstrtos16
#define uac2_kstrtobool(s, base, res) kstrtobool((s), (res))
+static const char *u8_fmt = "%u\n";
static const char *u32_fmt = "%u\n";
static const char *s16_fmt = "%hd\n";
static const char *bool_fmt = "%u\n";
\
CONFIGFS_ATTR(f_uac2_opts_, name)
+#define UAC2_RATE_ATTRIBUTE(name) \
+static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ struct f_uac2_opts *opts = to_f_uac2_opts(item); \
+ int result = 0; \
+ int i; \
+ \
+ mutex_lock(&opts->lock); \
+ page[0] = '\0'; \
+ for (i = 0; i < UAC_MAX_RATES; i++) { \
+ if (opts->name##s[i] == 0) \
+ break; \
+ result += sprintf(page + strlen(page), "%u,", \
+ opts->name##s[i]); \
+ } \
+ if (strlen(page) > 0) \
+ page[strlen(page) - 1] = '\n'; \
+ mutex_unlock(&opts->lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct f_uac2_opts *opts = to_f_uac2_opts(item); \
+ char *split_page = NULL; \
+ int ret = -EINVAL; \
+ char *token; \
+ u32 num; \
+ int i; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ i = 0; \
+ memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
+ split_page = kstrdup(page, GFP_KERNEL); \
+ while ((token = strsep(&split_page, ",")) != NULL) { \
+ ret = kstrtou32(token, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ opts->name##s[i++] = num; \
+ ret = len; \
+ }; \
+ \
+end: \
+ kfree(split_page); \
+ mutex_unlock(&opts->lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(f_uac2_opts_, name)
+
+#define UAC2_ATTRIBUTE_STRING(name) \
+static ssize_t f_uac2_opts_##name##_show(struct config_item *item, \
+ char *page) \
+{ \
+ struct f_uac2_opts *opts = to_f_uac2_opts(item); \
+ int result; \
+ \
+ mutex_lock(&opts->lock); \
+ result = snprintf(page, sizeof(opts->name), "%s", opts->name); \
+ mutex_unlock(&opts->lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct f_uac2_opts *opts = to_f_uac2_opts(item); \
+ int ret = 0; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ ret = snprintf(opts->name, min(sizeof(opts->name), len), \
+ "%s", page); \
+ \
+end: \
+ mutex_unlock(&opts->lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(f_uac2_opts_, name)
+
UAC2_ATTRIBUTE(u32, p_chmask);
-UAC2_ATTRIBUTE(u32, p_srate);
+UAC2_RATE_ATTRIBUTE(p_srate);
UAC2_ATTRIBUTE(u32, p_ssize);
+UAC2_ATTRIBUTE(u8, p_hs_bint);
UAC2_ATTRIBUTE(u32, c_chmask);
-UAC2_ATTRIBUTE(u32, c_srate);
+UAC2_RATE_ATTRIBUTE(c_srate);
UAC2_ATTRIBUTE_SYNC(c_sync);
UAC2_ATTRIBUTE(u32, c_ssize);
+UAC2_ATTRIBUTE(u8, c_hs_bint);
UAC2_ATTRIBUTE(u32, req_number);
UAC2_ATTRIBUTE(bool, p_mute_present);
UAC2_ATTRIBUTE(s16, c_volume_max);
UAC2_ATTRIBUTE(s16, c_volume_res);
UAC2_ATTRIBUTE(u32, fb_max);
+UAC2_ATTRIBUTE_STRING(function_name);
static struct configfs_attribute *f_uac2_attrs[] = {
&f_uac2_opts_attr_p_chmask,
&f_uac2_opts_attr_p_srate,
&f_uac2_opts_attr_p_ssize,
+ &f_uac2_opts_attr_p_hs_bint,
&f_uac2_opts_attr_c_chmask,
&f_uac2_opts_attr_c_srate,
&f_uac2_opts_attr_c_ssize,
+ &f_uac2_opts_attr_c_hs_bint,
&f_uac2_opts_attr_c_sync,
&f_uac2_opts_attr_req_number,
&f_uac2_opts_attr_fb_max,
&f_uac2_opts_attr_c_volume_max,
&f_uac2_opts_attr_c_volume_res,
+ &f_uac2_opts_attr_function_name,
+
NULL,
};
&f_uac2_func_type);
opts->p_chmask = UAC2_DEF_PCHMASK;
- opts->p_srate = UAC2_DEF_PSRATE;
+ opts->p_srates[0] = UAC2_DEF_PSRATE;
opts->p_ssize = UAC2_DEF_PSSIZE;
+ opts->p_hs_bint = UAC2_DEF_PHSBINT;
opts->c_chmask = UAC2_DEF_CCHMASK;
- opts->c_srate = UAC2_DEF_CSRATE;
+ opts->c_srates[0] = UAC2_DEF_CSRATE;
opts->c_ssize = UAC2_DEF_CSSIZE;
+ opts->c_hs_bint = UAC2_DEF_CHSBINT;
opts->c_sync = UAC2_DEF_CSYNC;
opts->p_mute_present = UAC2_DEF_MUTE_PRESENT;
opts->req_number = UAC2_DEF_REQ_NUM;
opts->fb_max = FBACK_FAST_MAX;
+
+ snprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink");
+
return &opts->func_inst;
}
uac2->g_audio.func.set_alt = afunc_set_alt;
uac2->g_audio.func.get_alt = afunc_get_alt;
uac2->g_audio.func.disable = afunc_disable;
+ uac2->g_audio.func.suspend = afunc_suspend;
uac2->g_audio.func.setup = afunc_setup;
uac2->g_audio.func.free_func = afunc_free;
UAC_P_PITCH_CTRL,
UAC_MUTE_CTRL,
UAC_VOLUME_CTRL,
+ UAC_RATE_CTRL,
};
/* Runtime data params for one stream */
s16 volume;
int mute;
+ struct snd_kcontrol *snd_kctl_rate; /* read-only current rate */
+ int srate; /* selected samplerate */
+ int active; /* playback/capture running */
+
spinlock_t lock; /* lock for control transfers */
};
struct snd_pcm_runtime *runtime;
struct uac_rtd_params *prm = req->context;
struct snd_uac_chip *uac = prm->uac;
- struct g_audio *audio_dev = uac->audio_dev;
- struct uac_params *params = &audio_dev->params;
unsigned int frames, p_pktsize;
unsigned long long pitched_rate_mil, p_pktsize_residue_mil,
residue_frames_mil, div_result;
*/
unsigned long long p_interval_mil = uac->p_interval * 1000000ULL;
- pitched_rate_mil = (unsigned long long)
- params->p_srate * prm->pitch;
+ pitched_rate_mil = (unsigned long long) prm->srate * prm->pitch;
div_result = pitched_rate_mil;
do_div(div_result, uac->p_interval);
do_div(div_result, 1000000);
frames = (unsigned int) div_result;
pr_debug("p_srate %d, pitch %d, interval_mil %llu, frames %d\n",
- params->p_srate, prm->pitch, p_interval_mil, frames);
+ prm->srate, prm->pitch, p_interval_mil, frames);
p_pktsize = min_t(unsigned int,
uac->p_framesize * frames,
struct uac_rtd_params *prm = req->context;
struct snd_uac_chip *uac = prm->uac;
struct g_audio *audio_dev = uac->audio_dev;
- struct uac_params *params = &audio_dev->params;
int status = req->status;
/* i/f shutting down */
__func__, status, req->actual, req->length);
u_audio_set_fback_frequency(audio_dev->gadget->speed, audio_dev->out_ep,
- params->c_srate, prm->pitch,
+ prm->srate, prm->pitch,
req->buf);
if (usb_ep_queue(ep, req, GFP_ATOMIC))
struct snd_pcm_runtime *runtime = substream->runtime;
struct g_audio *audio_dev;
struct uac_params *params;
+ struct uac_rtd_params *prm;
int p_ssize, c_ssize;
- int p_srate, c_srate;
int p_chmask, c_chmask;
audio_dev = uac->audio_dev;
params = &audio_dev->params;
p_ssize = params->p_ssize;
c_ssize = params->c_ssize;
- p_srate = params->p_srate;
- c_srate = params->c_srate;
p_chmask = params->p_chmask;
c_chmask = params->c_chmask;
uac->p_residue_mil = 0;
runtime->hw = uac_pcm_hardware;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- runtime->hw.rate_min = p_srate;
runtime->hw.formats = uac_ssize_to_fmt(p_ssize);
runtime->hw.channels_min = num_channels(p_chmask);
- runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize
- / runtime->hw.periods_min;
+ prm = &uac->p_prm;
} else {
- runtime->hw.rate_min = c_srate;
runtime->hw.formats = uac_ssize_to_fmt(c_ssize);
runtime->hw.channels_min = num_channels(c_chmask);
- runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize
- / runtime->hw.periods_min;
+ prm = &uac->c_prm;
}
+ runtime->hw.period_bytes_min = 2 * prm->max_psize
+ / runtime->hw.periods_min;
+ runtime->hw.rate_min = prm->srate;
runtime->hw.rate_max = runtime->hw.rate_min;
runtime->hw.channels_max = runtime->hw.channels_min;
dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__);
}
+static void set_active(struct uac_rtd_params *prm, bool active)
+{
+ // notifying through the Rate ctrl
+ struct snd_kcontrol *kctl = prm->snd_kctl_rate;
+ unsigned long flags;
+
+ spin_lock_irqsave(&prm->lock, flags);
+ if (prm->active != active) {
+ prm->active = active;
+ snd_ctl_notify(prm->uac->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ &kctl->id);
+ }
+ spin_unlock_irqrestore(&prm->lock, flags);
+}
+
+int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate)
+{
+ struct uac_params *params = &audio_dev->params;
+ struct snd_uac_chip *uac = audio_dev->uac;
+ struct uac_rtd_params *prm;
+ int i;
+ unsigned long flags;
+
+ dev_dbg(&audio_dev->gadget->dev, "%s: srate %d\n", __func__, srate);
+ prm = &uac->c_prm;
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (params->c_srates[i] == srate) {
+ spin_lock_irqsave(&prm->lock, flags);
+ prm->srate = srate;
+ spin_unlock_irqrestore(&prm->lock, flags);
+ return 0;
+ }
+ if (params->c_srates[i] == 0)
+ break;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_capture_srate);
+
+int u_audio_get_capture_srate(struct g_audio *audio_dev, u32 *val)
+{
+ struct snd_uac_chip *uac = audio_dev->uac;
+ struct uac_rtd_params *prm;
+ unsigned long flags;
+
+ prm = &uac->c_prm;
+ spin_lock_irqsave(&prm->lock, flags);
+ *val = prm->srate;
+ spin_unlock_irqrestore(&prm->lock, flags);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_get_capture_srate);
+
+int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate)
+{
+ struct uac_params *params = &audio_dev->params;
+ struct snd_uac_chip *uac = audio_dev->uac;
+ struct uac_rtd_params *prm;
+ int i;
+ unsigned long flags;
+
+ dev_dbg(&audio_dev->gadget->dev, "%s: srate %d\n", __func__, srate);
+ prm = &uac->p_prm;
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (params->p_srates[i] == srate) {
+ spin_lock_irqsave(&prm->lock, flags);
+ prm->srate = srate;
+ spin_unlock_irqrestore(&prm->lock, flags);
+ return 0;
+ }
+ if (params->p_srates[i] == 0)
+ break;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(u_audio_set_playback_srate);
+
+int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val)
+{
+ struct snd_uac_chip *uac = audio_dev->uac;
+ struct uac_rtd_params *prm;
+ unsigned long flags;
+
+ prm = &uac->p_prm;
+ spin_lock_irqsave(&prm->lock, flags);
+ *val = prm->srate;
+ spin_unlock_irqrestore(&prm->lock, flags);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(u_audio_get_playback_srate);
+
int u_audio_start_capture(struct g_audio *audio_dev)
{
struct snd_uac_chip *uac = audio_dev->uac;
struct uac_params *params = &audio_dev->params;
int req_len, i;
- ep = audio_dev->out_ep;
prm = &uac->c_prm;
+ dev_dbg(dev, "start capture with rate %d\n", prm->srate);
+ ep = audio_dev->out_ep;
config_ep_by_speed(gadget, &audio_dev->func, ep);
req_len = ep->maxpacket;
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
}
+ set_active(&uac->c_prm, true);
+
ep_fback = audio_dev->in_ep_fback;
if (!ep_fback)
return 0;
*/
prm->pitch = 1000000;
u_audio_set_fback_frequency(audio_dev->gadget->speed, ep,
- params->c_srate, prm->pitch,
+ prm->srate, prm->pitch,
req_fback->buf);
if (usb_ep_queue(ep_fback, req_fback, GFP_ATOMIC))
{
struct snd_uac_chip *uac = audio_dev->uac;
+ set_active(&uac->c_prm, false);
if (audio_dev->in_ep_fback)
free_ep_fback(&uac->c_prm, audio_dev->in_ep_fback);
free_ep(&uac->c_prm, audio_dev->out_ep);
int req_len, i;
unsigned int p_pktsize;
- ep = audio_dev->in_ep;
prm = &uac->p_prm;
+ dev_dbg(dev, "start playback with rate %d\n", prm->srate);
+ ep = audio_dev->in_ep;
config_ep_by_speed(gadget, &audio_dev->func, ep);
ep_desc = ep->desc;
uac->p_interval = factor / (1 << (ep_desc->bInterval - 1));
p_pktsize = min_t(unsigned int,
uac->p_framesize *
- (params->p_srate / uac->p_interval),
+ (prm->srate / uac->p_interval),
ep->maxpacket);
req_len = p_pktsize;
dev_err(dev, "%s:%d Error!\n", __func__, __LINE__);
}
+ set_active(&uac->p_prm, true);
+
return 0;
}
EXPORT_SYMBOL_GPL(u_audio_start_playback);
{
struct snd_uac_chip *uac = audio_dev->uac;
+ set_active(&uac->p_prm, false);
free_ep(&uac->p_prm, audio_dev->in_ep);
}
EXPORT_SYMBOL_GPL(u_audio_stop_playback);
+void u_audio_suspend(struct g_audio *audio_dev)
+{
+ struct snd_uac_chip *uac = audio_dev->uac;
+
+ set_active(&uac->p_prm, false);
+ set_active(&uac->c_prm, false);
+}
+EXPORT_SYMBOL_GPL(u_audio_suspend);
+
int u_audio_get_volume(struct g_audio *audio_dev, int playback, s16 *val)
{
struct snd_uac_chip *uac = audio_dev->uac;
return change;
}
+static int get_max_srate(const int *srates)
+{
+ int i, max_srate = 0;
+
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (srates[i] == 0)
+ break;
+ if (srates[i] > max_srate)
+ max_srate = srates[i];
+ }
+ return max_srate;
+}
+
+static int get_min_srate(const int *srates)
+{
+ int i, min_srate = INT_MAX;
+
+ for (i = 0; i < UAC_MAX_RATES; i++) {
+ if (srates[i] == 0)
+ break;
+ if (srates[i] < min_srate)
+ min_srate = srates[i];
+ }
+ return min_srate;
+}
+
+static int u_audio_rate_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ const int *srates;
+ struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+ struct snd_uac_chip *uac = prm->uac;
+ struct g_audio *audio_dev = uac->audio_dev;
+ struct uac_params *params = &audio_dev->params;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+
+ if (prm == &uac->c_prm)
+ srates = params->c_srates;
+ else
+ srates = params->p_srates;
+ uinfo->value.integer.min = get_min_srate(srates);
+ uinfo->value.integer.max = get_max_srate(srates);
+ return 0;
+}
+
+static int u_audio_rate_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct uac_rtd_params *prm = snd_kcontrol_chip(kcontrol);
+ unsigned long flags;
+
+ spin_lock_irqsave(&prm->lock, flags);
+ if (prm->active)
+ ucontrol->value.integer.value[0] = prm->srate;
+ else
+ /* not active: reporting zero rate */
+ ucontrol->value.integer.value[0] = 0;
+ spin_unlock_irqrestore(&prm->lock, flags);
+ return 0;
+}
static struct snd_kcontrol_new u_audio_controls[] = {
[UAC_FBACK_CTRL] {
.get = u_audio_volume_get,
.put = u_audio_volume_put,
},
+ [UAC_RATE_CTRL] {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "", /* will be filled later */
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = u_audio_rate_info,
+ .get = u_audio_rate_get,
+ },
};
int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
spin_lock_init(&prm->lock);
uac->c_prm.uac = uac;
prm->max_psize = g_audio->out_ep_maxpsize;
+ prm->srate = params->c_srates[0];
prm->reqs = kcalloc(params->req_number,
sizeof(struct usb_request *),
spin_lock_init(&prm->lock);
uac->p_prm.uac = uac;
prm->max_psize = g_audio->in_ep_maxpsize;
+ prm->srate = params->p_srates[0];
prm->reqs = kcalloc(params->req_number,
sizeof(struct usb_request *),
prm->volume_min = fu->volume_min;
prm->volume_res = fu->volume_res;
}
+
+ /* Add rate control */
+ snprintf(ctrl_name, sizeof(ctrl_name),
+ "%s Rate", direction);
+ u_audio_controls[UAC_RATE_CTRL].name = ctrl_name;
+
+ kctl = snd_ctl_new1(&u_audio_controls[UAC_RATE_CTRL], prm);
+ if (!kctl) {
+ err = -ENOMEM;
+ goto snd_fail;
+ }
+
+ kctl->id.device = pcm->device;
+ kctl->id.subdevice = 0;
+
+ err = snd_ctl_add(card, kctl);
+ if (err < 0)
+ goto snd_fail;
+ prm->snd_kctl_rate = kctl;
}
strscpy(card->driver, card_name, sizeof(card->driver));
#define __U_AUDIO_H
#include <linux/usb/composite.h>
+#include "uac_common.h"
/*
* Same maximum frequency deviation on the slower side as in
struct uac_params {
/* playback */
int p_chmask; /* channel mask */
- int p_srate; /* rate in Hz */
+ int p_srates[UAC_MAX_RATES]; /* available rates in Hz (0 terminated list) */
int p_ssize; /* sample size */
struct uac_fu_params p_fu; /* Feature Unit parameters */
/* capture */
int c_chmask; /* channel mask */
- int c_srate; /* rate in Hz */
+ int c_srates[UAC_MAX_RATES]; /* available rates in Hz (0 terminated list) */
int c_ssize; /* sample size */
struct uac_fu_params c_fu; /* Feature Unit parameters */
+ /* rates are dynamic, in uac_rtd_params */
+
int req_number; /* number of preallocated requests */
int fb_max; /* upper frequency drift feedback limit per-mil */
};
int u_audio_start_playback(struct g_audio *g_audio);
void u_audio_stop_playback(struct g_audio *g_audio);
+int u_audio_get_capture_srate(struct g_audio *audio_dev, u32 *val);
+int u_audio_set_capture_srate(struct g_audio *audio_dev, int srate);
+int u_audio_get_playback_srate(struct g_audio *audio_dev, u32 *val);
+int u_audio_set_playback_srate(struct g_audio *audio_dev, int srate);
+
int u_audio_get_volume(struct g_audio *g_audio, int playback, s16 *val);
int u_audio_set_volume(struct g_audio *g_audio, int playback, s16 val);
int u_audio_get_mute(struct g_audio *g_audio, int playback, int *val);
int u_audio_set_mute(struct g_audio *g_audio, int playback, int val);
+void u_audio_suspend(struct g_audio *g_audio);
+
#endif /* __U_AUDIO_H */
#define __U_UAC1_H
#include <linux/usb/composite.h>
+#include "uac_common.h"
#define UAC1_OUT_EP_MAX_PACKET_SIZE 200
#define UAC1_DEF_CCHMASK 0x3
struct f_uac1_opts {
struct usb_function_instance func_inst;
int c_chmask;
- int c_srate;
+ int c_srates[UAC_MAX_RATES];
int c_ssize;
int p_chmask;
- int p_srate;
+ int p_srates[UAC_MAX_RATES];
int p_ssize;
bool p_mute_present;
int req_number;
unsigned bound:1;
+ char function_name[32];
+
struct mutex lock;
int refcnt;
};
#define U_UAC2_H
#include <linux/usb/composite.h>
+#include "uac_common.h"
#define UAC2_DEF_PCHMASK 0x3
#define UAC2_DEF_PSRATE 48000
#define UAC2_DEF_PSSIZE 2
+#define UAC2_DEF_PHSBINT 0
#define UAC2_DEF_CCHMASK 0x3
#define UAC2_DEF_CSRATE 64000
#define UAC2_DEF_CSSIZE 2
+#define UAC2_DEF_CHSBINT 0
#define UAC2_DEF_CSYNC USB_ENDPOINT_SYNC_ASYNC
#define UAC2_DEF_MUTE_PRESENT 1
struct f_uac2_opts {
struct usb_function_instance func_inst;
int p_chmask;
- int p_srate;
+ int p_srates[UAC_MAX_RATES];
int p_ssize;
+ u8 p_hs_bint;
int c_chmask;
- int c_srate;
+ int c_srates[UAC_MAX_RATES];
int c_ssize;
int c_sync;
+ u8 c_hs_bint;
bool p_mute_present;
bool p_volume_present;
int fb_max;
bool bound;
+ char function_name[32];
+
struct mutex lock;
int refcnt;
};
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ */
+
+#ifndef UAC_COMMON_H
+#define UAC_COMMON_H
+
+#define UAC_MAX_RATES 10 /* maximum number of rates configurable by f_uac1/2 */
+#endif
/* Playback(USB-IN) Default Stereo - Fl/Fr */
static int p_chmask = UAC2_DEF_PCHMASK;
-module_param(p_chmask, uint, S_IRUGO);
+module_param(p_chmask, uint, 0444);
MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
/* Playback Default 48 KHz */
-static int p_srate = UAC2_DEF_PSRATE;
-module_param(p_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
+static int p_srates[UAC_MAX_RATES] = {UAC2_DEF_PSRATE};
+static int p_srates_cnt = 1;
+module_param_array_named(p_srate, p_srates, uint, &p_srates_cnt, 0444);
+MODULE_PARM_DESC(p_srate, "Playback Sampling Rates (array)");
/* Playback Default 16bits/sample */
static int p_ssize = UAC2_DEF_PSSIZE;
-module_param(p_ssize, uint, S_IRUGO);
+module_param(p_ssize, uint, 0444);
MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
+/* Playback bInterval for HS/SS (1-4: fixed, 0: auto) */
+static u8 p_hs_bint = UAC2_DEF_PHSBINT;
+module_param(p_hs_bint, byte, 0444);
+MODULE_PARM_DESC(p_hs_bint,
+ "Playback bInterval for HS/SS (1-4: fixed, 0: auto)");
+
/* Capture(USB-OUT) Default Stereo - Fl/Fr */
static int c_chmask = UAC2_DEF_CCHMASK;
-module_param(c_chmask, uint, S_IRUGO);
+module_param(c_chmask, uint, 0444);
MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
/* Capture Default 64 KHz */
-static int c_srate = UAC2_DEF_CSRATE;
-module_param(c_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
+static int c_srates[UAC_MAX_RATES] = {UAC2_DEF_CSRATE};
+static int c_srates_cnt = 1;
+module_param_array_named(c_srate, c_srates, uint, &c_srates_cnt, 0444);
+MODULE_PARM_DESC(c_srate, "Capture Sampling Rates (array)");
/* Capture Default 16bits/sample */
static int c_ssize = UAC2_DEF_CSSIZE;
-module_param(c_ssize, uint, S_IRUGO);
+module_param(c_ssize, uint, 0444);
MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
+
+/* capture bInterval for HS/SS (1-4: fixed, 0: auto) */
+static u8 c_hs_bint = UAC2_DEF_CHSBINT;
+module_param(c_hs_bint, byte, 0444);
+MODULE_PARM_DESC(c_hs_bint,
+ "Capture bInterval for HS/SS (1-4: fixed, 0: auto)");
+
#else
#ifndef CONFIG_GADGET_UAC1_LEGACY
#include "u_uac1.h"
/* Playback(USB-IN) Default Stereo - Fl/Fr */
static int p_chmask = UAC1_DEF_PCHMASK;
-module_param(p_chmask, uint, S_IRUGO);
+module_param(p_chmask, uint, 0444);
MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
/* Playback Default 48 KHz */
-static int p_srate = UAC1_DEF_PSRATE;
-module_param(p_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
+static int p_srates[UAC_MAX_RATES] = {UAC1_DEF_PSRATE};
+static int p_srates_cnt = 1;
+module_param_array_named(p_srate, p_srates, uint, &p_srates_cnt, 0444);
+MODULE_PARM_DESC(p_srate, "Playback Sampling Rates (array)");
/* Playback Default 16bits/sample */
static int p_ssize = UAC1_DEF_PSSIZE;
-module_param(p_ssize, uint, S_IRUGO);
+module_param(p_ssize, uint, 0444);
MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
/* Capture(USB-OUT) Default Stereo - Fl/Fr */
static int c_chmask = UAC1_DEF_CCHMASK;
-module_param(c_chmask, uint, S_IRUGO);
+module_param(c_chmask, uint, 0444);
MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
/* Capture Default 48 KHz */
-static int c_srate = UAC1_DEF_CSRATE;
-module_param(c_srate, uint, S_IRUGO);
-MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
+static int c_srates[UAC_MAX_RATES] = {UAC1_DEF_CSRATE};
+static int c_srates_cnt = 1;
+module_param_array_named(c_srate, c_srates, uint, &c_srates_cnt, 0444);
+MODULE_PARM_DESC(c_srate, "Capture Sampling Rates (array)");
/* Capture Default 16bits/sample */
static int c_ssize = UAC1_DEF_CSSIZE;
-module_param(c_ssize, uint, S_IRUGO);
+module_param(c_ssize, uint, 0444);
MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
#else /* CONFIG_GADGET_UAC1_LEGACY */
#include "u_uac1_legacy.h"
static char *fn_play = FILE_PCM_PLAYBACK;
-module_param(fn_play, charp, S_IRUGO);
+module_param(fn_play, charp, 0444);
MODULE_PARM_DESC(fn_play, "Playback PCM device file name");
static char *fn_cap = FILE_PCM_CAPTURE;
-module_param(fn_cap, charp, S_IRUGO);
+module_param(fn_cap, charp, 0444);
MODULE_PARM_DESC(fn_cap, "Capture PCM device file name");
static char *fn_cntl = FILE_CONTROL;
-module_param(fn_cntl, charp, S_IRUGO);
+module_param(fn_cntl, charp, 0444);
MODULE_PARM_DESC(fn_cntl, "Control device file name");
static int req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE;
-module_param(req_buf_size, int, S_IRUGO);
+module_param(req_buf_size, int, 0444);
MODULE_PARM_DESC(req_buf_size, "ISO OUT endpoint request buffer size");
static int req_count = UAC1_REQ_COUNT;
-module_param(req_count, int, S_IRUGO);
+module_param(req_count, int, 0444);
MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count");
static int audio_buf_size = UAC1_AUDIO_BUF_SIZE;
-module_param(audio_buf_size, int, S_IRUGO);
+module_param(audio_buf_size, int, 0444);
MODULE_PARM_DESC(audio_buf_size, "Audio buffer size");
#endif /* CONFIG_GADGET_UAC1_LEGACY */
#endif
{
#ifndef CONFIG_GADGET_UAC1
struct f_uac2_opts *uac2_opts;
+ int i;
#else
#ifndef CONFIG_GADGET_UAC1_LEGACY
struct f_uac1_opts *uac1_opts;
+ int i;
#else
struct f_uac1_legacy_opts *uac1_opts;
#endif
#ifndef CONFIG_GADGET_UAC1
uac2_opts = container_of(fi_uac2, struct f_uac2_opts, func_inst);
uac2_opts->p_chmask = p_chmask;
- uac2_opts->p_srate = p_srate;
+
+ for (i = 0; i < p_srates_cnt; ++i)
+ uac2_opts->p_srates[i] = p_srates[i];
+
uac2_opts->p_ssize = p_ssize;
+ uac2_opts->p_hs_bint = p_hs_bint;
uac2_opts->c_chmask = c_chmask;
- uac2_opts->c_srate = c_srate;
+
+ for (i = 0; i < c_srates_cnt; ++i)
+ uac2_opts->c_srates[i] = c_srates[i];
+
uac2_opts->c_ssize = c_ssize;
+ uac2_opts->c_hs_bint = c_hs_bint;
uac2_opts->req_number = UAC2_DEF_REQ_NUM;
#else
#ifndef CONFIG_GADGET_UAC1_LEGACY
uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst);
uac1_opts->p_chmask = p_chmask;
- uac1_opts->p_srate = p_srate;
+
+ for (i = 0; i < p_srates_cnt; ++i)
+ uac1_opts->p_srates[i] = p_srates[i];
+
uac1_opts->p_ssize = p_ssize;
uac1_opts->c_chmask = c_chmask;
- uac1_opts->c_srate = c_srate;
+
+ for (i = 0; i < c_srates_cnt; ++i)
+ uac1_opts->c_srates[i] = c_srates[i];
+
uac1_opts->c_ssize = c_ssize;
uac1_opts->req_number = UAC1_DEF_REQ_NUM;
#else /* CONFIG_GADGET_UAC1_LEGACY */
#include <linux/workqueue.h>
/* XUSB_DEV registers */
-#define SPARAM 0x000
-#define SPARAM_ERSTMAX_MASK GENMASK(20, 16)
-#define SPARAM_ERSTMAX(x) (((x) << 16) & SPARAM_ERSTMAX_MASK)
#define DB 0x004
#define DB_TARGET_MASK GENMASK(15, 8)
#define DB_TARGET(x) (((x) << 8) & DB_TARGET_MASK)
BUILD_EP_CONTEXT_RW(avg_trb_len, tx_info, 0, 0xffff)
BUILD_EP_CONTEXT_RW(max_esit_payload, tx_info, 16, 0xffff)
BUILD_EP_CONTEXT_RW(edtla, rsvd[0], 0, 0xffffff)
-BUILD_EP_CONTEXT_RW(seq_num, rsvd[0], 24, 0xff)
+BUILD_EP_CONTEXT_RW(rsvd, rsvd[0], 24, 0x1)
BUILD_EP_CONTEXT_RW(partial_td, rsvd[0], 25, 0x1)
+BUILD_EP_CONTEXT_RW(splitxstate, rsvd[0], 26, 0x1)
+BUILD_EP_CONTEXT_RW(seq_num, rsvd[0], 27, 0x1f)
BUILD_EP_CONTEXT_RW(cerrcnt, rsvd[1], 18, 0x3)
BUILD_EP_CONTEXT_RW(data_offset, rsvd[2], 0, 0x1ffff)
BUILD_EP_CONTEXT_RW(numtrbs, rsvd[2], 22, 0x1f)
ep_reload(xudc, ep->index);
ep_ctx_write_state(ep->context, EP_STATE_RUNNING);
+ ep_ctx_write_rsvd(ep->context, 0);
+ ep_ctx_write_partial_td(ep->context, 0);
+ ep_ctx_write_splitxstate(ep->context, 0);
ep_ctx_write_seq_num(ep->context, 0);
ep_reload(xudc, ep->index);
xudc->setup_seq_num = 0;
xudc->queued_setup_packet = false;
- ep_ctx_write_seq_num(ep0->context, xudc->setup_seq_num);
+ ep_ctx_write_rsvd(ep0->context, 0);
+ ep_ctx_write_partial_td(ep0->context, 0);
+ ep_ctx_write_splitxstate(ep0->context, 0);
+ ep_ctx_write_seq_num(ep0->context, 0);
deq_ptr = trb_virt_to_phys(ep0, &ep0->transfer_ring[ep0->deq_ptr]);
unsigned int i;
u32 val;
- val = xudc_readl(xudc, SPARAM);
- val &= ~(SPARAM_ERSTMAX_MASK);
- val |= SPARAM_ERSTMAX(XUDC_NR_EVENT_RINGS);
- xudc_writel(xudc, val, SPARAM);
-
for (i = 0; i < ARRAY_SIZE(xudc->event_ring); i++) {
memset(xudc->event_ring[i], 0, XUDC_EVENT_RING_SIZE *
sizeof(*xudc->event_ring[i]));
static int fill_buffer(struct debug_buffer *buf)
{
- int ret = 0;
+ int ret;
if (!buf->output_buf)
buf->output_buf = vmalloc(buf->alloc_size);
size_t len, loff_t *offset)
{
struct debug_buffer *buf = file->private_data;
- int ret = 0;
+ int ret;
mutex_lock(&buf->mutex);
if (buf->count == 0) {
/* defined here to avoid adding to pci_ids.h for single instance use */
#define PCI_DEVICE_ID_INTEL_CE4100_USB 0x2e70
+#define PCI_VENDOR_ID_ASPEED 0x1a03
+#define PCI_DEVICE_ID_ASPEED_EHCI 0x2603
+
/*-------------------------------------------------------------------------*/
#define PCI_DEVICE_ID_INTEL_QUARK_X1000_SOC 0x0939
static inline bool is_intel_quark_x1000(struct pci_dev *pdev)
ehci->has_synopsys_hc_bug = 1;
}
break;
+ case PCI_VENDOR_ID_ASPEED:
+ if (pdev->device == PCI_DEVICE_ID_ASPEED_EHCI) {
+ ehci_info(ehci, "applying Aspeed HC workaround\n");
+ ehci->is_aspeed = 1;
+ }
+ break;
}
/* optional debug port, normally in the first BAR */
#define hcd_to_ehci_priv(h) ((struct ehci_platform_priv *)hcd_to_ehci(h)->priv)
#define BCM_USB_FIFO_THRESHOLD 0x00800040
-#define bcm_iproc_insnreg01 hostpc[0]
struct ehci_platform_priv {
struct clk *clks[EHCI_MAX_CLKS];
if (of_device_is_compatible(pdev->dev.of_node, "brcm,xgs-iproc-ehci"))
ehci_writel(ehci, BCM_USB_FIFO_THRESHOLD,
- &ehci->regs->bcm_iproc_insnreg01);
+ &ehci->regs->brcm_insnreg[1]);
return 0;
}
/* fill a qtd, returning how much of the buffer we were able to queue up */
-static int
+static unsigned int
qtd_fill(struct ehci_hcd *ehci, struct ehci_qtd *qtd, dma_addr_t buf,
size_t len, int token, int maxpacket)
{
- int i, count;
+ unsigned int count;
u64 addr = buf;
+ int i;
/* one buffer entry per 4K ... first might be short or unaligned */
qtd->hw_buf[0] = cpu_to_hc32(ehci, (u32)addr);
* and may serve as a control status ack
*/
for (;;) {
- int this_qtd_len;
+ unsigned int this_qtd_len;
this_qtd_len = qtd_fill(ehci, qtd, buf, this_sg_len, token,
maxpacket);
iso_sched_alloc(unsigned packets, gfp_t mem_flags)
{
struct ehci_iso_sched *iso_sched;
- int size = sizeof(*iso_sched);
- size += packets * sizeof(struct ehci_iso_packet);
- iso_sched = kzalloc(size, mem_flags);
+ iso_sched = kzalloc(struct_size(iso_sched, packet, packets), mem_flags);
if (likely(iso_sched != NULL))
INIT_LIST_HEAD(&iso_sched->td_list);
gfp_t mem_flags)
{
struct fotg210_iso_sched *iso_sched;
- int size = sizeof(*iso_sched);
- size += packets * sizeof(struct fotg210_iso_packet);
- iso_sched = kzalloc(size, mem_flags);
+ iso_sched = kzalloc(struct_size(iso_sched, packet, packets), mem_flags);
if (likely(iso_sched != NULL))
INIT_LIST_HEAD(&iso_sched->td_list);
static int fill_buffer(struct debug_buffer *buf)
{
- int ret = 0;
+ int ret;
if (!buf->page)
buf->page = (char *)get_zeroed_page(GFP_KERNEL);
size_t len, loff_t *offset)
{
struct debug_buffer *buf = file->private_data;
- int ret = 0;
+ int ret;
mutex_lock(&buf->mutex);
if (buf->count == 0) {
mod_delayed_work(system_wq, &dbc->event_work, 1);
}
-static void xhci_do_dbc_exit(struct xhci_hcd *xhci)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&xhci->lock, flags);
- kfree(xhci->dbc);
- xhci->dbc = NULL;
- spin_unlock_irqrestore(&xhci->lock, flags);
-}
-
-static int xhci_do_dbc_init(struct xhci_hcd *xhci)
-{
- u32 reg;
- struct xhci_dbc *dbc;
- unsigned long flags;
- void __iomem *base;
- int dbc_cap_offs;
-
- base = &xhci->cap_regs->hc_capbase;
- dbc_cap_offs = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_DEBUG);
- if (!dbc_cap_offs)
- return -ENODEV;
-
- dbc = kzalloc(sizeof(*dbc), GFP_KERNEL);
- if (!dbc)
- return -ENOMEM;
-
- dbc->regs = base + dbc_cap_offs;
-
- /* We will avoid using DbC in xhci driver if it's in use. */
- reg = readl(&dbc->regs->control);
- if (reg & DBC_CTRL_DBC_ENABLE) {
- kfree(dbc);
- return -EBUSY;
- }
-
- spin_lock_irqsave(&xhci->lock, flags);
- if (xhci->dbc) {
- spin_unlock_irqrestore(&xhci->lock, flags);
- kfree(dbc);
- return -EBUSY;
- }
- xhci->dbc = dbc;
- spin_unlock_irqrestore(&xhci->lock, flags);
-
- dbc->xhci = xhci;
- dbc->dev = xhci_to_hcd(xhci)->self.sysdev;
- INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events);
- spin_lock_init(&dbc->lock);
-
- return 0;
-}
-
static ssize_t dbc_show(struct device *dev,
struct device_attribute *attr,
char *buf)
static DEVICE_ATTR_RW(dbc);
-int xhci_dbc_init(struct xhci_hcd *xhci)
+struct xhci_dbc *
+xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *driver)
{
+ struct xhci_dbc *dbc;
int ret;
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
- ret = xhci_do_dbc_init(xhci);
- if (ret)
- goto init_err3;
+ dbc = kzalloc(sizeof(*dbc), GFP_KERNEL);
+ if (!dbc)
+ return NULL;
- ret = xhci_dbc_tty_probe(xhci);
- if (ret)
- goto init_err2;
+ dbc->regs = base;
+ dbc->dev = dev;
+ dbc->driver = driver;
+
+ if (readl(&dbc->regs->control) & DBC_CTRL_DBC_ENABLE)
+ return NULL;
+
+ INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events);
+ spin_lock_init(&dbc->lock);
ret = device_create_file(dev, &dev_attr_dbc);
if (ret)
- goto init_err1;
+ goto err;
- return 0;
+ return dbc;
+err:
+ kfree(dbc);
+ return NULL;
+}
+
+/* undo what xhci_alloc_dbc() did */
+void xhci_dbc_remove(struct xhci_dbc *dbc)
+{
+ if (!dbc)
+ return;
+ /* stop hw, stop wq and call dbc->ops->stop() */
+ xhci_dbc_stop(dbc);
+
+ /* remove sysfs files */
+ device_remove_file(dbc->dev, &dev_attr_dbc);
+
+ kfree(dbc);
+}
+
+
+int xhci_create_dbc_dev(struct xhci_hcd *xhci)
+{
+ struct device *dev;
+ void __iomem *base;
+ int ret;
+ int dbc_cap_offs;
+
+ /* create all parameters needed resembling a dbc device */
+ dev = xhci_to_hcd(xhci)->self.controller;
+ base = &xhci->cap_regs->hc_capbase;
+
+ dbc_cap_offs = xhci_find_next_ext_cap(base, 0, XHCI_EXT_CAPS_DEBUG);
+ if (!dbc_cap_offs)
+ return -ENODEV;
+
+ /* already allocated and in use */
+ if (xhci->dbc)
+ return -EBUSY;
+
+ ret = xhci_dbc_tty_probe(dev, base + dbc_cap_offs, xhci);
-init_err1:
- xhci_dbc_tty_remove(xhci->dbc);
-init_err2:
- xhci_do_dbc_exit(xhci);
-init_err3:
return ret;
}
-void xhci_dbc_exit(struct xhci_hcd *xhci)
+void xhci_remove_dbc_dev(struct xhci_hcd *xhci)
{
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ unsigned long flags;
if (!xhci->dbc)
return;
- device_remove_file(dev, &dev_attr_dbc);
xhci_dbc_tty_remove(xhci->dbc);
- xhci_dbc_stop(xhci->dbc);
- xhci_do_dbc_exit(xhci);
+ spin_lock_irqsave(&xhci->lock, flags);
+ xhci->dbc = NULL;
+ spin_unlock_irqrestore(&xhci->lock, flags);
}
#ifdef CONFIG_PM
return ret;
}
#endif /* CONFIG_PM */
+
+int xhci_dbc_init(void)
+{
+ return dbc_tty_init();
+}
+
+void xhci_dbc_exit(void)
+{
+ dbc_tty_exit();
+}
struct dbc_port {
struct tty_port port;
spinlock_t port_lock; /* port access */
+ int minor;
struct list_head read_pool;
struct list_head read_queue;
}
#ifdef CONFIG_USB_XHCI_DBGCAP
-int xhci_dbc_init(struct xhci_hcd *xhci);
-void xhci_dbc_exit(struct xhci_hcd *xhci);
-int xhci_dbc_tty_probe(struct xhci_hcd *xhci);
+int xhci_create_dbc_dev(struct xhci_hcd *xhci);
+void xhci_remove_dbc_dev(struct xhci_hcd *xhci);
+int xhci_dbc_init(void);
+void xhci_dbc_exit(void);
+int dbc_tty_init(void);
+void dbc_tty_exit(void);
+int xhci_dbc_tty_probe(struct device *dev, void __iomem *res, struct xhci_hcd *xhci);
void xhci_dbc_tty_remove(struct xhci_dbc *dbc);
+struct xhci_dbc *xhci_alloc_dbc(struct device *dev, void __iomem *res,
+ const struct dbc_driver *driver);
+void xhci_dbc_remove(struct xhci_dbc *dbc);
struct dbc_request *dbc_alloc_request(struct xhci_dbc *dbc,
unsigned int direction,
gfp_t flags);
int xhci_dbc_resume(struct xhci_hcd *xhci);
#endif /* CONFIG_PM */
#else
-static inline int xhci_dbc_init(struct xhci_hcd *xhci)
+static inline int xhci_create_dbc_dev(struct xhci_hcd *xhci)
{
return 0;
}
-static inline void xhci_dbc_exit(struct xhci_hcd *xhci)
+static inline void xhci_remove_dbc_dev(struct xhci_hcd *xhci)
+{
+}
+static inline int xhci_dbc_init(void)
+{
+ return 0;
+}
+static inline void xhci_dbc_exit(void)
{
}
-
static inline int xhci_dbc_suspend(struct xhci_hcd *xhci)
{
return 0;
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
+#include <linux/idr.h>
#include "xhci.h"
#include "xhci-dbgcap.h"
-static int dbc_tty_init(void);
-static void dbc_tty_exit(void);
-
static struct tty_driver *dbc_tty_driver;
+static struct idr dbc_tty_minors;
+static DEFINE_MUTEX(dbc_tty_minors_lock);
static inline struct dbc_port *dbc_to_port(struct xhci_dbc *dbc)
{
static int dbc_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
- struct dbc_port *port = driver->driver_state;
+ struct dbc_port *port;
+
+ mutex_lock(&dbc_tty_minors_lock);
+ port = idr_find(&dbc_tty_minors, tty->index);
+ mutex_unlock(&dbc_tty_minors_lock);
+
+ if (!port)
+ return -ENXIO;
tty->driver_data = port;
xhci_dbc_tty_init_port(dbc, port);
+ mutex_lock(&dbc_tty_minors_lock);
+ port->minor = idr_alloc(&dbc_tty_minors, port, 0, 64, GFP_KERNEL);
+ mutex_unlock(&dbc_tty_minors_lock);
+
+ if (port->minor < 0) {
+ ret = port->minor;
+ goto err_idr;
+ }
+
ret = kfifo_alloc(&port->write_fifo, DBC_WRITE_BUF_SIZE, GFP_KERNEL);
if (ret)
goto err_exit_port;
goto err_free_requests;
tty_dev = tty_port_register_device(&port->port,
- dbc_tty_driver, 0, NULL);
+ dbc_tty_driver, port->minor, NULL);
if (IS_ERR(tty_dev)) {
ret = PTR_ERR(tty_dev);
goto err_free_requests;
err_free_fifo:
kfifo_free(&port->write_fifo);
err_exit_port:
+ idr_remove(&dbc_tty_minors, port->minor);
+err_idr:
xhci_dbc_tty_exit_port(port);
dev_err(dbc->dev, "can't register tty port, err %d\n", ret);
if (!port->registered)
return;
- tty_unregister_device(dbc_tty_driver, 0);
+ tty_unregister_device(dbc_tty_driver, port->minor);
xhci_dbc_tty_exit_port(port);
port->registered = false;
+ mutex_lock(&dbc_tty_minors_lock);
+ idr_remove(&dbc_tty_minors, port->minor);
+ mutex_unlock(&dbc_tty_minors_lock);
+
kfifo_free(&port->write_fifo);
xhci_dbc_free_requests(&port->read_pool);
xhci_dbc_free_requests(&port->read_queue);
.disconnect = xhci_dbc_tty_unregister_device,
};
-int xhci_dbc_tty_probe(struct xhci_hcd *xhci)
+int xhci_dbc_tty_probe(struct device *dev, void __iomem *base, struct xhci_hcd *xhci)
{
- struct xhci_dbc *dbc = xhci->dbc;
+ struct xhci_dbc *dbc;
struct dbc_port *port;
int status;
- /* dbc_tty_init will be called by module init() in the future */
- status = dbc_tty_init();
- if (status)
- return status;
+ if (!dbc_tty_driver)
+ return -ENODEV;
port = kzalloc(sizeof(*port), GFP_KERNEL);
- if (!port) {
+ if (!port)
+ return -ENOMEM;
+
+ dbc = xhci_alloc_dbc(dev, base, &dbc_driver);
+
+ if (!dbc) {
status = -ENOMEM;
- goto out;
+ goto out2;
}
- dbc->driver = &dbc_driver;
dbc->priv = port;
-
- dbc_tty_driver->driver_state = port;
+ /* get rid of xhci once this is a real driver binding to a device */
+ xhci->dbc = dbc;
return 0;
-out:
- /* dbc_tty_exit will be called by module_exit() in the future */
- dbc_tty_exit();
+out2:
+ kfree(port);
+
return status;
}
{
struct dbc_port *port = dbc_to_port(dbc);
- dbc->driver = NULL;
- dbc->priv = NULL;
+ xhci_dbc_remove(dbc);
kfree(port);
-
- /* dbc_tty_exit will be called by module_exit() in the future */
- dbc_tty_exit();
}
-static int dbc_tty_init(void)
+int dbc_tty_init(void)
{
int ret;
- dbc_tty_driver = tty_alloc_driver(1, TTY_DRIVER_REAL_RAW |
+ idr_init(&dbc_tty_minors);
+
+ dbc_tty_driver = tty_alloc_driver(64, TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV);
- if (IS_ERR(dbc_tty_driver))
+ if (IS_ERR(dbc_tty_driver)) {
+ idr_destroy(&dbc_tty_minors);
return PTR_ERR(dbc_tty_driver);
+ }
dbc_tty_driver->driver_name = "dbc_serial";
dbc_tty_driver->name = "ttyDBC";
if (ret) {
pr_err("Can't register dbc tty driver\n");
tty_driver_kref_put(dbc_tty_driver);
+ idr_destroy(&dbc_tty_minors);
}
+
return ret;
}
-static void dbc_tty_exit(void)
+void dbc_tty_exit(void)
{
if (dbc_tty_driver) {
tty_unregister_driver(dbc_tty_driver);
tty_driver_kref_put(dbc_tty_driver);
dbc_tty_driver = NULL;
}
+
+ idr_destroy(&dbc_tty_minors);
}
/* If the cycle state is 0, set the cycle bit to 1 for all the TRBs */
if (cycle_state == 0) {
for (i = 0; i < TRBS_PER_SEGMENT; i++)
- seg->trbs[i].link.control |= cpu_to_le32(TRB_CYCLE);
+ seg->trbs[i].link.control = cpu_to_le32(TRB_CYCLE);
}
seg->dma = dma;
seg->next = NULL;
(TRBS_PER_SEGMENT - 1);
/* Allocate number of segments we needed, or double the ring size */
- num_segs = ring->num_segs > num_segs_needed ?
- ring->num_segs : num_segs_needed;
+ num_segs = max(ring->num_segs, num_segs_needed);
ret = xhci_alloc_segments_for_ring(xhci, &first, &last,
num_segs, ring->cycle_state, ring->type,
xhci->event_ring = NULL;
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed event ring");
- if (xhci->lpm_command)
- xhci_free_command(xhci, xhci->lpm_command);
- xhci->lpm_command = NULL;
if (xhci->cmd_ring)
xhci_ring_free(xhci, xhci->cmd_ring);
xhci->cmd_ring = NULL;
"// Setting command ring address to 0x%016llx", val_64);
xhci_write_64(xhci, val_64, &xhci->op_regs->cmd_ring);
- xhci->lpm_command = xhci_alloc_command_with_ctx(xhci, true, flags);
- if (!xhci->lpm_command)
- goto fail;
-
/* Reserve one command ring TRB for disabling LPM.
* Since the USB core grabs the shared usb_bus bandwidth mutex before
* disabling LPM, we only need to reserve one TRB for all devices.
struct mu3h_sch_bw_info *bw_info;
struct mu3h_sch_tt *tt = NULL;
u32 len_bw_budget_table;
- size_t mem_size;
bw_info = get_bw_info(mtk, udev, ep);
if (!bw_info)
else
len_bw_budget_table = 1;
- mem_size = sizeof(struct mu3h_sch_ep_info) +
- len_bw_budget_table * sizeof(u32);
- sch_ep = kzalloc(mem_size, GFP_KERNEL);
+ sch_ep = kzalloc(struct_size(sch_ep, bw_budget_table,
+ len_bw_budget_table),
+ GFP_KERNEL);
if (!sch_ep)
return ERR_PTR(-ENOMEM);
#define WC0_SSUSB0_CDEN BIT(6)
#define WC0_IS_SPM_EN BIT(1)
+/* mt8195 */
+#define PERI_WK_CTRL0_8195 0x04
+#define WC0_IS_P_95 BIT(30) /* polarity */
+#define WC0_IS_C_95(x) ((u32)(((x) & 0x7) << 27))
+#define WC0_IS_EN_P3_95 BIT(26)
+#define WC0_IS_EN_P2_95 BIT(25)
+#define WC0_IS_EN_P1_95 BIT(24)
+
+#define PERI_WK_CTRL1_8195 0x20
+#define WC1_IS_C_95(x) ((u32)(((x) & 0xf) << 28))
+#define WC1_IS_P_95 BIT(12)
+#define WC1_IS_EN_P0_95 BIT(6)
+
/* mt2712 etc */
#define PERI_SSUSB_SPM_CTRL 0x0
#define SSC_IP_SLEEP_EN BIT(4)
SSUSB_UWK_V2,
SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */
SSUSB_UWK_V1_2, /* specific revision 1.2 */
+ SSUSB_UWK_V1_3, /* mt8195 IP0 */
+ SSUSB_UWK_V1_4, /* mt8195 IP1 */
+ SSUSB_UWK_V1_5, /* mt8195 IP2 */
+ SSUSB_UWK_V1_6, /* mt8195 IP3 */
};
/*
msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN;
val = enable ? msk : 0;
break;
+ case SSUSB_UWK_V1_3:
+ reg = mtk->uwk_reg_base + PERI_WK_CTRL1_8195;
+ msk = WC1_IS_EN_P0_95 | WC1_IS_C_95(0xf) | WC1_IS_P_95;
+ val = enable ? (WC1_IS_EN_P0_95 | WC1_IS_C_95(0x1)) : 0;
+ break;
+ case SSUSB_UWK_V1_4:
+ reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195;
+ msk = WC0_IS_EN_P1_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95;
+ val = enable ? (WC0_IS_EN_P1_95 | WC0_IS_C_95(0x1)) : 0;
+ break;
+ case SSUSB_UWK_V1_5:
+ reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195;
+ msk = WC0_IS_EN_P2_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95;
+ val = enable ? (WC0_IS_EN_P2_95 | WC0_IS_C_95(0x1)) : 0;
+ break;
+ case SSUSB_UWK_V1_6:
+ reg = mtk->uwk_reg_base + PERI_WK_CTRL0_8195;
+ msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95;
+ val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0;
+ break;
case SSUSB_UWK_V2:
reg = mtk->uwk_reg_base + PERI_SSUSB_SPM_CTRL;
msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN;
return devm_clk_bulk_get_optional(mtk->dev, BULK_CLKS_NUM, clks);
}
-static int xhci_mtk_ldos_enable(struct xhci_hcd_mtk *mtk)
+static int xhci_mtk_vregs_get(struct xhci_hcd_mtk *mtk)
{
- int ret;
+ struct regulator_bulk_data *supplies = mtk->supplies;
- ret = regulator_enable(mtk->vbus);
- if (ret) {
- dev_err(mtk->dev, "failed to enable vbus\n");
- return ret;
- }
+ supplies[0].supply = "vbus";
+ supplies[1].supply = "vusb33";
- ret = regulator_enable(mtk->vusb33);
- if (ret) {
- dev_err(mtk->dev, "failed to enable vusb33\n");
- regulator_disable(mtk->vbus);
- return ret;
- }
- return 0;
-}
-
-static void xhci_mtk_ldos_disable(struct xhci_hcd_mtk *mtk)
-{
- regulator_disable(mtk->vbus);
- regulator_disable(mtk->vusb33);
+ return devm_regulator_bulk_get(mtk->dev, BULK_VREGS_NUM, supplies);
}
static void xhci_mtk_quirks(struct device *dev, struct xhci_hcd *xhci)
return -ENOMEM;
mtk->dev = dev;
- mtk->vbus = devm_regulator_get(dev, "vbus");
- if (IS_ERR(mtk->vbus)) {
- dev_err(dev, "fail to get vbus\n");
- return PTR_ERR(mtk->vbus);
- }
- mtk->vusb33 = devm_regulator_get(dev, "vusb33");
- if (IS_ERR(mtk->vusb33)) {
- dev_err(dev, "fail to get vusb33\n");
- return PTR_ERR(mtk->vusb33);
- }
+ ret = xhci_mtk_vregs_get(mtk);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get regulators\n");
ret = xhci_mtk_clks_get(mtk);
if (ret)
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
- ret = xhci_mtk_ldos_enable(mtk);
+ ret = regulator_bulk_enable(BULK_VREGS_NUM, mtk->supplies);
if (ret)
goto disable_pm;
clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
disable_ldos:
- xhci_mtk_ldos_disable(mtk);
+ regulator_bulk_disable(BULK_VREGS_NUM, mtk->supplies);
disable_pm:
pm_runtime_put_noidle(dev);
usb_put_hcd(hcd);
xhci_mtk_sch_exit(mtk);
clk_bulk_disable_unprepare(BULK_CLKS_NUM, mtk->clks);
- xhci_mtk_ldos_disable(mtk);
+ regulator_bulk_disable(BULK_VREGS_NUM, mtk->supplies);
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
#include <linux/clk.h>
#include <linux/hashtable.h>
+#include <linux/regulator/consumer.h>
#include "xhci.h"
#define BULK_CLKS_NUM 5
+#define BULK_VREGS_NUM 2
/* support at most 64 ep, use 32 size hash table */
#define SCH_EP_HASH_BITS 5
int num_u3_ports;
int u2p_dis_msk;
int u3p_dis_msk;
- struct regulator *vusb33;
- struct regulator *vbus;
struct clk_bulk_data clks[BULK_CLKS_NUM];
+ struct regulator_bulk_data supplies[BULK_VREGS_NUM];
unsigned int has_ippc:1;
unsigned int lpm_support:1;
unsigned int u2_lpm_disable:1;
if (!sysdev)
sysdev = &pdev->dev;
- /* Try to set 64-bit DMA first */
if (WARN_ON(!sysdev->dma_mask))
/* Platform did not initialize dma_mask */
- ret = dma_coerce_mask_and_coherent(sysdev,
- DMA_BIT_MASK(64));
+ ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
else
ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64));
-
- /* If seting 64-bit DMA mask fails, fall back to 32-bit DMA mask */
- if (ret) {
- ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32));
- if (ret)
- return ret;
- }
+ if (ret)
+ return ret;
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Finished xhci_run for USB2 roothub");
- xhci_dbc_init(xhci);
+ xhci_create_dbc_dev(xhci);
xhci_debugfs_init(xhci);
return;
}
- xhci_dbc_exit(xhci);
+ xhci_remove_dbc_dev(xhci);
spin_lock_irq(&xhci->lock);
xhci->xhc_state |= XHCI_STATE_HALTED;
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = &vdev->eps[ep_index];
- if (!ep)
- goto done;
/* wait for hub_tt_work to finish clearing hub TT */
if (ep->ep_state & EP_CLEARING_TT) {
return;
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = &vdev->eps[ep_index];
- if (!ep)
- return;
/* Bail out if toggle is already being cleared by a endpoint reset */
spin_lock_irqsave(&xhci->lock, flags);
unsigned long flags;
int ret;
+ command = xhci_alloc_command_with_ctx(xhci, true, GFP_KERNEL);
+ if (!command)
+ return -ENOMEM;
+
spin_lock_irqsave(&xhci->lock, flags);
virt_dev = xhci->devs[udev->slot_id];
}
/* Attempt to issue an Evaluate Context command to change the MEL. */
- command = xhci->lpm_command;
ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx);
if (!ctrl_ctx) {
spin_unlock_irqrestore(&xhci->lock, flags);
+ xhci_free_command(xhci, command);
xhci_warn(xhci, "%s: Could not get input context, bad type.\n",
__func__);
return -ENOMEM;
virt_dev->current_mel = max_exit_latency;
spin_unlock_irqrestore(&xhci->lock, flags);
}
+
+ xhci_free_command(xhci, command);
+
return ret;
}
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);
readl(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);
readl_poll_timeout(ports[port_num]->addr, pm_val,
(pm_val & PORT_PLS_MASK) == XDEV_U0,
100, 10000);
return -ENODEV;
xhci_debugfs_create_root();
+ xhci_dbc_init();
return 0;
}
static void __exit xhci_hcd_fini(void)
{
xhci_debugfs_remove_root();
+ xhci_dbc_exit();
}
module_init(xhci_hcd_init);
/* slot enabling and address device helpers */
/* these are not thread safe so use mutex */
struct mutex mutex;
- /* For USB 3.0 LPM enable/disable. */
- struct xhci_command *lpm_command;
/* Internal mirror of the HW's dcbaa */
struct xhci_virt_device *devs[MAX_HC_SLOTS];
/* For keeping track of bandwidth domains per roothub. */
Say Y here if you want to control the backlight of Apple Cinema
Displays over USB. This driver provides a sysfs interface.
+config USB_QCOM_EUD
+ tristate "QCOM Embedded USB Debugger(EUD) Driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ select USB_ROLE_SWITCH
+ help
+ This module enables support for Qualcomm Technologies, Inc.
+ Embedded USB Debugger (EUD). The EUD is a control peripheral
+ which reports VBUS attach/detach events and has USB-based
+ debug and trace capabilities. On selecting m, the module name
+ that is built is qcom_eud.ko
+
config APPLE_MFI_FASTCHARGE
tristate "Fast charge control for iOS devices"
select POWER_SUPPLY
obj-$(CONFIG_USB_LCD) += usblcd.o
obj-$(CONFIG_USB_LD) += ldusb.o
obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o
+obj-$(CONFIG_USB_QCOM_EUD) += qcom_eud.o
obj-$(CONFIG_USB_TEST) += usbtest.o
obj-$(CONFIG_USB_EHSET_TEST_FIXTURE) += ehset.o
obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/usb/role.h>
+
+#define EUD_REG_INT1_EN_MASK 0x0024
+#define EUD_REG_INT_STATUS_1 0x0044
+#define EUD_REG_CTL_OUT_1 0x0074
+#define EUD_REG_VBUS_INT_CLR 0x0080
+#define EUD_REG_CSR_EUD_EN 0x1014
+#define EUD_REG_SW_ATTACH_DET 0x1018
+#define EUD_REG_EUD_EN2 0x0000
+
+#define EUD_ENABLE BIT(0)
+#define EUD_INT_PET_EUD BIT(0)
+#define EUD_INT_VBUS BIT(2)
+#define EUD_INT_SAFE_MODE BIT(4)
+#define EUD_INT_ALL (EUD_INT_VBUS | EUD_INT_SAFE_MODE)
+
+struct eud_chip {
+ struct device *dev;
+ struct usb_role_switch *role_sw;
+ void __iomem *base;
+ void __iomem *mode_mgr;
+ unsigned int int_status;
+ int irq;
+ bool enabled;
+ bool usb_attached;
+};
+
+static int enable_eud(struct eud_chip *priv)
+{
+ writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN);
+ writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE,
+ priv->base + EUD_REG_INT1_EN_MASK);
+ writel(1, priv->mode_mgr + EUD_REG_EUD_EN2);
+
+ return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE);
+}
+
+static void disable_eud(struct eud_chip *priv)
+{
+ writel(0, priv->base + EUD_REG_CSR_EUD_EN);
+ writel(0, priv->mode_mgr + EUD_REG_EUD_EN2);
+}
+
+static ssize_t enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct eud_chip *chip = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", chip->enabled);
+}
+
+static ssize_t enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct eud_chip *chip = dev_get_drvdata(dev);
+ bool enable;
+ int ret;
+
+ if (kstrtobool(buf, &enable))
+ return -EINVAL;
+
+ if (enable) {
+ ret = enable_eud(chip);
+ if (!ret)
+ chip->enabled = enable;
+ else
+ disable_eud(chip);
+ } else {
+ disable_eud(chip);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(enable);
+
+static struct attribute *eud_attrs[] = {
+ &dev_attr_enable.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(eud);
+
+static void usb_attach_detach(struct eud_chip *chip)
+{
+ u32 reg;
+
+ /* read ctl_out_1[4] to find USB attach or detach event */
+ reg = readl(chip->base + EUD_REG_CTL_OUT_1);
+ chip->usb_attached = reg & EUD_INT_SAFE_MODE;
+}
+
+static void pet_eud(struct eud_chip *chip)
+{
+ u32 reg;
+ int ret;
+
+ /* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been
+ * disconnected and we need to detach the pet to check if EUD is in safe
+ * mode before attaching again.
+ */
+ reg = readl(chip->base + EUD_REG_SW_ATTACH_DET);
+ if (reg & EUD_INT_PET_EUD) {
+ /* Detach & Attach pet for EUD */
+ writel(0, chip->base + EUD_REG_SW_ATTACH_DET);
+ /* Delay to make sure detach pet is done before attach pet */
+ ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET,
+ reg, (reg == 0), 1, 100);
+ if (ret) {
+ dev_err(chip->dev, "Detach pet failed\n");
+ return;
+ }
+ }
+ /* Attach pet for EUD */
+ writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET);
+}
+
+static irqreturn_t handle_eud_irq(int irq, void *data)
+{
+ struct eud_chip *chip = data;
+ u32 reg;
+
+ reg = readl(chip->base + EUD_REG_INT_STATUS_1);
+ switch (reg & EUD_INT_ALL) {
+ case EUD_INT_VBUS:
+ usb_attach_detach(chip);
+ return IRQ_WAKE_THREAD;
+ case EUD_INT_SAFE_MODE:
+ pet_eud(chip);
+ return IRQ_HANDLED;
+ default:
+ return IRQ_NONE;
+ }
+}
+
+static irqreturn_t handle_eud_irq_thread(int irq, void *data)
+{
+ struct eud_chip *chip = data;
+ int ret;
+
+ if (chip->usb_attached)
+ ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE);
+ else
+ ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST);
+ if (ret)
+ dev_err(chip->dev, "failed to set role switch\n");
+
+ /* set and clear vbus_int_clr[0] to clear interrupt */
+ writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR);
+ writel(0, chip->base + EUD_REG_VBUS_INT_CLR);
+
+ return IRQ_HANDLED;
+}
+
+static void eud_role_switch_release(void *data)
+{
+ struct eud_chip *chip = data;
+
+ usb_role_switch_put(chip->role_sw);
+}
+
+static int eud_probe(struct platform_device *pdev)
+{
+ struct eud_chip *chip;
+ int ret;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = &pdev->dev;
+
+ ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip);
+ if (ret)
+ return dev_err_probe(chip->dev, ret,
+ "failed to add role switch release action\n");
+
+ chip->role_sw = usb_role_switch_get(&pdev->dev);
+ if (IS_ERR(chip->role_sw))
+ return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw),
+ "failed to get role switch\n");
+
+ chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(chip->base))
+ return PTR_ERR(chip->base);
+
+ chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(chip->mode_mgr))
+ return PTR_ERR(chip->mode_mgr);
+
+ chip->irq = platform_get_irq(pdev, 0);
+ ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq,
+ handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip);
+ if (ret)
+ return dev_err_probe(chip->dev, ret, "failed to allocate irq\n");
+
+ enable_irq_wake(chip->irq);
+
+ platform_set_drvdata(pdev, chip);
+
+ return 0;
+}
+
+static int eud_remove(struct platform_device *pdev)
+{
+ struct eud_chip *chip = platform_get_drvdata(pdev);
+
+ if (chip->enabled)
+ disable_eud(chip);
+
+ device_init_wakeup(&pdev->dev, false);
+ disable_irq_wake(chip->irq);
+
+ return 0;
+}
+
+static const struct of_device_id eud_dt_match[] = {
+ { .compatible = "qcom,sc7280-eud" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, eud_dt_match);
+
+static struct platform_driver eud_driver = {
+ .probe = eud_probe,
+ .remove = eud_remove,
+ .driver = {
+ .name = "qcom_eud",
+ .dev_groups = eud_groups,
+ .of_match_table = eud_dt_match,
+ },
+};
+module_platform_driver(eud_driver);
+
+MODULE_DESCRIPTION("QTI EUD driver");
+MODULE_LICENSE("GPL v2");
source "drivers/usb/typec/tipd/Kconfig"
+config TYPEC_RT1719
+ tristate "Richtek RT1719 Sink Only Type-C controller driver"
+ depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y or M here if your system has Richtek RT1719 sink only
+ Type-C port controller driver.
+
+ If you choose to build this driver as a dynamically linked module, the
+ module will be called rt1719.ko
+
config TYPEC_HD3SS3220
tristate "TI HD3SS3220 Type-C DRP Port controller driver"
depends on I2C
It will also enable the VBUS output to connected devices when a
DFP connection is made.
+config TYPEC_WUSB3801
+ tristate "Willsemi WUSB3801 Type-C port controller driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y or M here if your system has a WUSB3801 Type-C port controller.
+
+ If you choose to build this driver as a dynamically linked module, the
+ module will be called wusb3801.ko.
+
source "drivers/usb/typec/mux/Kconfig"
source "drivers/usb/typec/altmodes/Kconfig"
obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o
obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o
obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o
+obj-$(CONFIG_TYPEC_RT1719) += rt1719.o
+obj-$(CONFIG_TYPEC_WUSB3801) += wusb3801.o
obj-$(CONFIG_TYPEC) += mux/
}
EXPORT_SYMBOL_GPL(typec_get_drvdata);
+int typec_get_fw_cap(struct typec_capability *cap,
+ struct fwnode_handle *fwnode)
+{
+ const char *cap_str;
+ int ret;
+
+ cap->fwnode = fwnode;
+
+ ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
+ if (ret < 0)
+ return ret;
+
+ ret = typec_find_port_power_role(cap_str);
+ if (ret < 0)
+ return ret;
+ cap->type = ret;
+
+ /* USB data support is optional */
+ ret = fwnode_property_read_string(fwnode, "data-role", &cap_str);
+ if (ret == 0) {
+ ret = typec_find_port_data_role(cap_str);
+ if (ret < 0)
+ return ret;
+ cap->data = ret;
+ }
+
+ /* Get the preferred power role for a DRP */
+ if (cap->type == TYPEC_PORT_DRP) {
+ cap->prefer_role = TYPEC_NO_PREFERRED_ROLE;
+
+ ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str);
+ if (ret == 0) {
+ ret = typec_find_power_role(cap_str);
+ if (ret < 0)
+ return ret;
+ cap->prefer_role = ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(typec_get_fw_cap);
+
/**
* typec_port_register_altmode - Register USB Type-C Port Alternate Mode
* @port: USB Type-C Port that supports the alternate mode
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/role.h>
+#include <linux/usb/typec.h>
+
+#define RT1719_REG_TXCTRL1 0x03
+#define RT1719_REG_TXCTRL2 0x04
+#define RT1719_REG_POLICYINFO 0x0E
+#define RT1719_REG_SRCPDO1 0x11
+#define RT1719_REG_MASKS 0x2D
+#define RT1719_REG_EVENTS 0x33
+#define RT1719_REG_STATS 0x37
+#define RT1719_REG_PSELINFO 0x3C
+#define RT1719_REG_USBSETINFO 0x3E
+#define RT1719_REG_VENID 0x82
+
+#define RT1719_UNIQUE_PID 0x1719
+#define RT1719_REQDRSWAP_MASK BIT(7)
+#define RT1719_EVALMODE_MASK BIT(4)
+#define RT1719_REQSRCPDO_MASK GENMASK(2, 0)
+#define RT1719_TXSPDOREQ_MASK BIT(7)
+#define RT1719_INT_DRSW_ACCEPT BIT(23)
+#define RT1719_INT_RX_SRCCAP BIT(21)
+#define RT1719_INT_VBUS_DCT BIT(6)
+#define RT1719_INT_VBUS_PRESENT BIT(5)
+#define RT1719_INT_PE_SNK_RDY BIT(2)
+#define RT1719_CC1_STAT GENMASK(9, 8)
+#define RT1719_CC2_STAT GENMASK(11, 10)
+#define RT1719_POLARITY_MASK BIT(23)
+#define RT1719_DATAROLE_MASK BIT(22)
+#define RT1719_PDSPECREV_MASK GENMASK(21, 20)
+#define RT1719_SPDOSEL_MASK GENMASK(18, 16)
+#define RT1719_SPDONUM_MASK GENMASK(15, 13)
+#define RT1719_ATTACH_VBUS BIT(12)
+#define RT1719_ATTACH_DBG BIT(10)
+#define RT1719_ATTACH_SNK BIT(9)
+#define RT1719_ATTACHDEV_MASK (RT1719_ATTACH_VBUS | RT1719_ATTACH_DBG | \
+ RT1719_ATTACH_SNK)
+#define RT1719_PE_EXP_CONTRACT BIT(2)
+#define RT1719_PSEL_SUPPORT BIT(15)
+#define RT1719_TBLSEL_MASK BIT(6)
+#define RT1719_LATPSEL_MASK GENMASK(5, 0)
+#define RT1719_USBINFO_MASK GENMASK(1, 0)
+#define RT1719_USB_DFPUFP 3
+#define RT1719_MAX_SRCPDO 7
+
+enum {
+ SNK_PWR_OPEN = 0,
+ SNK_PWR_DEF,
+ SNK_PWR_1P5A,
+ SNK_PWR_3A
+};
+
+enum {
+ USBPD_SPECREV_1_0 = 0,
+ USBPD_SPECREV_2_0,
+ USBPD_SPECREV_3_0
+};
+
+enum rt1719_snkcap {
+ RT1719_SNKCAP_5V = 0,
+ RT1719_SNKCAP_9V,
+ RT1719_SNKCAP_12V,
+ RT1719_SNKCAP_15V,
+ RT1719_SNKCAP_20V,
+ RT1719_MAX_SNKCAP
+};
+
+struct rt1719_psel_cap {
+ u8 lomask;
+ u8 himask;
+ u32 milliwatt;
+ u32 milliamp;
+};
+
+struct rt1719_data {
+ struct device *dev;
+ struct regmap *regmap;
+ struct typec_port *port;
+ struct usb_role_switch *role_sw;
+ struct power_supply *psy;
+ struct typec_partner *partner;
+ struct power_supply_desc psy_desc;
+ struct usb_pd_identity partner_ident;
+ struct typec_partner_desc partner_desc;
+ struct completion req_completion;
+ enum power_supply_usb_type usb_type;
+ bool attached;
+ bool pd_capable;
+ bool drswap_support;
+ u32 voltage;
+ u32 req_voltage;
+ u32 max_current;
+ u32 op_current;
+ u32 spdos[RT1719_MAX_SRCPDO];
+ u16 snkcaps[RT1719_MAX_SNKCAP];
+ int spdo_num;
+ int spdo_sel;
+ u32 conn_info;
+ u16 conn_stat;
+};
+
+static const enum power_supply_usb_type rt1719_psy_usb_types[] = {
+ POWER_SUPPLY_USB_TYPE_C,
+ POWER_SUPPLY_USB_TYPE_PD,
+ POWER_SUPPLY_USB_TYPE_PD_PPS
+};
+
+static const enum power_supply_property rt1719_psy_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_USB_TYPE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW
+};
+
+static int rt1719_read16(struct rt1719_data *data, unsigned int reg, u16 *val)
+{
+ __le16 regval;
+ int ret;
+
+ ret = regmap_raw_read(data->regmap, reg, ®val, sizeof(regval));
+ if (ret)
+ return ret;
+
+ *val = le16_to_cpu(regval);
+ return 0;
+}
+
+static int rt1719_read32(struct rt1719_data *data, unsigned int reg, u32 *val)
+{
+ __le32 regval;
+ int ret;
+
+ ret = regmap_raw_read(data->regmap, reg, ®val, sizeof(regval));
+ if (ret)
+ return ret;
+
+ *val = le32_to_cpu(regval);
+ return 0;
+}
+
+static int rt1719_write32(struct rt1719_data *data, unsigned int reg, u32 val)
+{
+ __le32 regval = cpu_to_le32(val);
+
+ return regmap_raw_write(data->regmap, reg, ®val, sizeof(regval));
+}
+
+static enum typec_pwr_opmode rt1719_get_pwr_opmode(u32 conn, u16 stat)
+{
+ u16 cc1, cc2, cc_stat;
+
+ cc1 = FIELD_GET(RT1719_CC1_STAT, stat);
+ cc2 = FIELD_GET(RT1719_CC2_STAT, stat);
+
+ if (conn & RT1719_ATTACH_SNK) {
+ if (conn & RT1719_POLARITY_MASK)
+ cc_stat = cc2;
+ else
+ cc_stat = cc1;
+
+ switch (cc_stat) {
+ case SNK_PWR_3A:
+ return TYPEC_PWR_MODE_3_0A;
+ case SNK_PWR_1P5A:
+ return TYPEC_PWR_MODE_1_5A;
+ }
+ } else if (conn & RT1719_ATTACH_DBG) {
+ if ((cc1 == SNK_PWR_1P5A && cc2 == SNK_PWR_DEF) ||
+ (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_1P5A))
+ return TYPEC_PWR_MODE_1_5A;
+ else if ((cc1 == SNK_PWR_3A && cc2 == SNK_PWR_DEF) ||
+ (cc1 == SNK_PWR_DEF && cc2 == SNK_PWR_3A))
+ return TYPEC_PWR_MODE_3_0A;
+ }
+
+ return TYPEC_PWR_MODE_USB;
+}
+
+static enum typec_data_role rt1719_get_data_role(u32 conn)
+{
+ if (conn & RT1719_DATAROLE_MASK)
+ return TYPEC_HOST;
+ return TYPEC_DEVICE;
+}
+
+static void rt1719_set_data_role(struct rt1719_data *data,
+ enum typec_data_role data_role,
+ bool attached)
+{
+ enum usb_role usb_role = USB_ROLE_NONE;
+
+ if (attached) {
+ if (data_role == TYPEC_HOST)
+ usb_role = USB_ROLE_HOST;
+ else
+ usb_role = USB_ROLE_DEVICE;
+ }
+
+ usb_role_switch_set_role(data->role_sw, usb_role);
+ typec_set_data_role(data->port, data_role);
+}
+
+static void rt1719_update_data_role(struct rt1719_data *data)
+{
+ if (!data->attached)
+ return;
+
+ rt1719_set_data_role(data, rt1719_get_data_role(data->conn_info), true);
+}
+
+static void rt1719_register_partner(struct rt1719_data *data)
+{
+ u16 spec_rev = 0;
+
+ if (data->pd_capable) {
+ u32 rev;
+
+ rev = FIELD_GET(RT1719_PDSPECREV_MASK, data->conn_info);
+ switch (rev) {
+ case USBPD_SPECREV_3_0:
+ spec_rev = 0x0300;
+ break;
+ case USBPD_SPECREV_2_0:
+ spec_rev = 0x0200;
+ break;
+ default:
+ spec_rev = 0x0100;
+ break;
+ }
+ }
+
+ /* Just to prevent multiple times attach */
+ if (data->partner)
+ typec_unregister_partner(data->partner);
+
+ memset(&data->partner_ident, 0, sizeof(data->partner_ident));
+ data->partner_desc.usb_pd = data->pd_capable;
+ data->partner_desc.pd_revision = spec_rev;
+
+ if (data->conn_info & RT1719_ATTACH_DBG)
+ data->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG;
+ else
+ data->partner_desc.accessory = TYPEC_ACCESSORY_NONE;
+
+ data->partner = typec_register_partner(data->port, &data->partner_desc);
+}
+
+static void rt1719_attach(struct rt1719_data *data)
+{
+ enum typec_pwr_opmode pwr_opmode;
+ enum typec_data_role data_role;
+ u32 volt = 5000, curr = 500;
+
+ if (!(data->conn_info & RT1719_ATTACHDEV_MASK))
+ return;
+
+ pwr_opmode = rt1719_get_pwr_opmode(data->conn_info, data->conn_stat);
+ data_role = rt1719_get_data_role(data->conn_info);
+
+ typec_set_pwr_opmode(data->port, pwr_opmode);
+ rt1719_set_data_role(data, data_role, true);
+
+ if (data->conn_info & RT1719_ATTACH_SNK)
+ rt1719_register_partner(data);
+
+ if (pwr_opmode == TYPEC_PWR_MODE_3_0A)
+ curr = 3000;
+ else if (pwr_opmode == TYPEC_PWR_MODE_1_5A)
+ curr = 1500;
+
+ data->voltage = volt * 1000;
+ data->max_current = data->op_current = curr * 1000;
+ data->attached = true;
+
+ power_supply_changed(data->psy);
+}
+
+static void rt1719_detach(struct rt1719_data *data)
+{
+ if (!data->attached || (data->conn_info & RT1719_ATTACHDEV_MASK))
+ return;
+
+ typec_unregister_partner(data->partner);
+ data->partner = NULL;
+
+ typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_USB);
+ rt1719_set_data_role(data, TYPEC_DEVICE, false);
+
+ memset32(data->spdos, 0, RT1719_MAX_SRCPDO);
+ data->spdo_num = 0;
+ data->voltage = data->max_current = data->op_current = 0;
+ data->attached = data->pd_capable = false;
+
+ data->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+ power_supply_changed(data->psy);
+}
+
+static void rt1719_update_operating_status(struct rt1719_data *data)
+{
+ enum power_supply_usb_type usb_type = POWER_SUPPLY_USB_TYPE_PD;
+ u32 voltage, max_current, op_current;
+ int i, snk_sel;
+
+ for (i = 0; i < data->spdo_num; i++) {
+ u32 pdo = data->spdos[i];
+ enum pd_pdo_type type = pdo_type(pdo);
+
+ if (type == PDO_TYPE_APDO) {
+ usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS;
+ break;
+ }
+ }
+
+ data->spdo_sel = FIELD_GET(RT1719_SPDOSEL_MASK, data->conn_info);
+ if (data->spdo_sel <= 0)
+ return;
+
+ data->usb_type = usb_type;
+
+ voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]);
+ max_current = pdo_max_current(data->spdos[data->spdo_sel - 1]);
+
+ switch (voltage) {
+ case 5000:
+ snk_sel = RT1719_SNKCAP_5V;
+ break;
+ case 9000:
+ snk_sel = RT1719_SNKCAP_9V;
+ break;
+ case 12000:
+ snk_sel = RT1719_SNKCAP_12V;
+ break;
+ case 15000:
+ snk_sel = RT1719_SNKCAP_15V;
+ break;
+ case 20000:
+ snk_sel = RT1719_SNKCAP_20V;
+ break;
+ default:
+ return;
+ }
+
+ op_current = min(max_current, pdo_max_current(data->snkcaps[snk_sel]));
+
+ /* covert mV/mA to uV/uA */
+ data->voltage = voltage * 1000;
+ data->max_current = max_current * 1000;
+ data->op_current = op_current * 1000;
+
+ power_supply_changed(data->psy);
+}
+
+static void rt1719_update_pwr_opmode(struct rt1719_data *data)
+{
+ if (!data->attached)
+ return;
+
+ if (!data->pd_capable) {
+ data->pd_capable = true;
+
+ typec_set_pwr_opmode(data->port, TYPEC_PWR_MODE_PD);
+ rt1719_register_partner(data);
+ }
+
+ rt1719_update_operating_status(data);
+}
+
+static void rt1719_update_source_pdos(struct rt1719_data *data)
+{
+ int spdo_num = FIELD_GET(RT1719_SPDONUM_MASK, data->conn_info);
+ __le32 src_pdos[RT1719_MAX_SRCPDO] = { };
+ int i, ret;
+
+ if (!data->attached)
+ return;
+
+ ret = regmap_raw_read(data->regmap, RT1719_REG_SRCPDO1, src_pdos,
+ sizeof(__le32) * spdo_num);
+ if (ret)
+ return;
+
+ data->spdo_num = spdo_num;
+ for (i = 0; i < spdo_num; i++)
+ data->spdos[i] = le32_to_cpu(src_pdos[i]);
+}
+
+static int rt1719_dr_set(struct typec_port *port, enum typec_data_role role)
+{
+ struct rt1719_data *data = typec_get_drvdata(port);
+ enum typec_data_role cur_role;
+ int ret;
+
+ if (!data->attached || !data->pd_capable || !data->drswap_support)
+ return -EOPNOTSUPP;
+
+ if (data->spdo_num > 0 && !(data->spdos[0] & PDO_FIXED_DATA_SWAP))
+ return -EINVAL;
+
+ cur_role = rt1719_get_data_role(data->conn_info);
+ if (cur_role == role)
+ return 0;
+
+ ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1,
+ RT1719_REQDRSWAP_MASK, RT1719_REQDRSWAP_MASK);
+ if (ret)
+ return ret;
+
+ reinit_completion(&data->req_completion);
+ ret = wait_for_completion_timeout(&data->req_completion,
+ msecs_to_jiffies(400));
+ if (ret == 0)
+ return -ETIMEDOUT;
+
+ cur_role = rt1719_get_data_role(data->conn_info);
+ if (cur_role != role)
+ return -EAGAIN;
+
+ rt1719_set_data_role(data, role, true);
+ return 0;
+}
+
+static const struct typec_operations rt1719_port_ops = {
+ .dr_set = rt1719_dr_set,
+};
+
+static int rt1719_usbpd_request_voltage(struct rt1719_data *data)
+{
+ u32 src_voltage;
+ int snk_sel, src_sel = -1;
+ int i, ret;
+
+ if (!data->attached || !data->pd_capable || data->spdo_sel <= 0)
+ return -EINVAL;
+
+ src_voltage = pdo_fixed_voltage(data->spdos[data->spdo_sel - 1]);
+ if (src_voltage == data->req_voltage)
+ return 0;
+
+ switch (data->req_voltage) {
+ case 5000:
+ snk_sel = RT1719_SNKCAP_5V;
+ break;
+ case 9000:
+ snk_sel = RT1719_SNKCAP_9V;
+ break;
+ case 12000:
+ snk_sel = RT1719_SNKCAP_12V;
+ break;
+ case 15000:
+ snk_sel = RT1719_SNKCAP_15V;
+ break;
+ case 20000:
+ snk_sel = RT1719_SNKCAP_20V;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!(data->snkcaps[snk_sel] & RT1719_PSEL_SUPPORT))
+ return -EINVAL;
+
+ for (i = 0; i < data->spdo_num; i++) {
+ enum pd_pdo_type type = pdo_type(data->spdos[i]);
+
+ if (type != PDO_TYPE_FIXED)
+ continue;
+
+ src_voltage = pdo_fixed_voltage(data->spdos[i]);
+ if (src_voltage == data->req_voltage) {
+ src_sel = i;
+ break;
+ }
+ }
+
+ if (src_sel == -1)
+ return -EOPNOTSUPP;
+
+ ret = regmap_update_bits(data->regmap, RT1719_REG_TXCTRL1,
+ RT1719_EVALMODE_MASK | RT1719_REQSRCPDO_MASK,
+ RT1719_EVALMODE_MASK | (src_sel + 1));
+ ret |= regmap_update_bits(data->regmap, RT1719_REG_TXCTRL2,
+ RT1719_TXSPDOREQ_MASK, RT1719_TXSPDOREQ_MASK);
+ if (ret)
+ return ret;
+
+ reinit_completion(&data->req_completion);
+ ret = wait_for_completion_timeout(&data->req_completion,
+ msecs_to_jiffies(400));
+ if (!ret)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int rt1719_psy_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct rt1719_data *data = power_supply_get_drvdata(psy);
+
+ if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) {
+ data->req_voltage = val->intval / 1000;
+ return rt1719_usbpd_request_voltage(data);
+ }
+
+ return -EINVAL;
+}
+
+static int rt1719_psy_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct rt1719_data *data = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = data->attached ? 1 : 0;
+ break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = data->usb_type;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = data->voltage;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = data->max_current;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = data->op_current;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int rt1719_psy_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW)
+ return 1;
+ return 0;
+}
+
+static int devm_rt1719_psy_register(struct rt1719_data *data)
+{
+ struct power_supply_config psy_cfg = { };
+ char *psy_name;
+
+ psy_cfg.fwnode = dev_fwnode(data->dev);
+ psy_cfg.drv_data = data;
+
+ psy_name = devm_kasprintf(data->dev, GFP_KERNEL, "rt1719-source-psy-%s",
+ dev_name(data->dev));
+ if (!psy_name)
+ return -ENOMEM;
+
+ data->psy_desc.name = psy_name;
+ data->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ data->psy_desc.usb_types = rt1719_psy_usb_types;
+ data->psy_desc.num_usb_types = ARRAY_SIZE(rt1719_psy_usb_types);
+ data->psy_desc.properties = rt1719_psy_properties;
+ data->psy_desc.num_properties = ARRAY_SIZE(rt1719_psy_properties);
+ data->psy_desc.get_property = rt1719_psy_get_property;
+ data->psy_desc.set_property = rt1719_psy_set_property;
+ data->psy_desc.property_is_writeable = rt1719_psy_property_is_writeable;
+
+ data->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+ data->psy = devm_power_supply_register(data->dev, &data->psy_desc,
+ &psy_cfg);
+
+ return PTR_ERR_OR_ZERO(data->psy);
+}
+
+static irqreturn_t rt1719_irq_handler(int irq, void *priv)
+{
+ struct rt1719_data *data = priv;
+ u32 events, conn_info;
+ u16 conn_stat;
+ int ret;
+
+ ret = rt1719_read32(data, RT1719_REG_EVENTS, &events);
+ ret |= rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info);
+ ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat);
+ if (ret)
+ return IRQ_NONE;
+
+ data->conn_info = conn_info;
+ data->conn_stat = conn_stat;
+
+ events &= (RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+ RT1719_INT_VBUS_PRESENT | RT1719_INT_VBUS_DCT |
+ RT1719_INT_PE_SNK_RDY);
+
+ if (events & RT1719_INT_DRSW_ACCEPT)
+ rt1719_update_data_role(data);
+
+ if (events & RT1719_INT_VBUS_PRESENT)
+ rt1719_attach(data);
+
+ if (events & RT1719_INT_VBUS_DCT)
+ rt1719_detach(data);
+
+ if (events & RT1719_INT_RX_SRCCAP)
+ rt1719_update_source_pdos(data);
+
+ if (events & RT1719_INT_PE_SNK_RDY) {
+ complete(&data->req_completion);
+ rt1719_update_pwr_opmode(data);
+ }
+
+ /* Write 1 to clear already handled events */
+ rt1719_write32(data, RT1719_REG_EVENTS, events);
+
+ return IRQ_HANDLED;
+}
+
+static int rt1719_irq_init(struct rt1719_data *data)
+{
+ struct i2c_client *i2c = to_i2c_client(data->dev);
+ u32 irq_enable;
+ int ret;
+
+ irq_enable = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+ RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT |
+ RT1719_INT_PE_SNK_RDY;
+
+ ret = rt1719_write32(data, RT1719_REG_MASKS, irq_enable);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to config irq enable\n");
+ return ret;
+ }
+
+ return devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL,
+ rt1719_irq_handler, IRQF_ONESHOT,
+ dev_name(&i2c->dev), data);
+}
+
+static int rt1719_init_attach_state(struct rt1719_data *data)
+{
+ u32 conn_info, irq_clear;
+ u16 conn_stat;
+ int ret;
+
+ irq_clear = RT1719_INT_DRSW_ACCEPT | RT1719_INT_RX_SRCCAP |
+ RT1719_INT_VBUS_DCT | RT1719_INT_VBUS_PRESENT |
+ RT1719_INT_PE_SNK_RDY;
+
+ ret = rt1719_read32(data, RT1719_REG_POLICYINFO, &conn_info);
+ ret |= rt1719_read16(data, RT1719_REG_STATS, &conn_stat);
+ ret |= rt1719_write32(data, RT1719_REG_EVENTS, irq_clear);
+ if (ret)
+ return ret;
+
+ data->conn_info = conn_info;
+ data->conn_stat = conn_stat;
+
+ if (conn_info & RT1719_ATTACHDEV_MASK)
+ rt1719_attach(data);
+
+ if (conn_info & RT1719_PE_EXP_CONTRACT) {
+ rt1719_update_source_pdos(data);
+ rt1719_update_pwr_opmode(data);
+ }
+
+ return 0;
+}
+
+#define RT1719_PSEL_CAPINFO(_lomask, _milliwatt, _himask, _milliamp) { \
+ .lomask = _lomask, \
+ .milliwatt = _milliwatt, \
+ .himask = _himask, \
+ .milliamp = _milliamp, \
+}
+
+static const struct rt1719_psel_cap rt1719_psel_caps[] = {
+ RT1719_PSEL_CAPINFO(0x18, 75000, 0x10, 5000),
+ RT1719_PSEL_CAPINFO(0x18, 60000, 0x10, 4500),
+ RT1719_PSEL_CAPINFO(0x18, 45000, 0x10, 4000),
+ RT1719_PSEL_CAPINFO(0x18, 30000, 0x10, 3500),
+ RT1719_PSEL_CAPINFO(0x18, 25000, 0x10, 3000),
+ RT1719_PSEL_CAPINFO(0x18, 20000, 0x10, 2500),
+ RT1719_PSEL_CAPINFO(0x18, 15000, 0x10, 2000),
+ RT1719_PSEL_CAPINFO(0x18, 10000, 0x10, 1000),
+ RT1719_PSEL_CAPINFO(0x1C, 60000, 0x1F, 5000),
+ RT1719_PSEL_CAPINFO(0x1C, 45000, 0x1F, 4500),
+ RT1719_PSEL_CAPINFO(0x1C, 30000, 0x1F, 4000),
+ RT1719_PSEL_CAPINFO(0x1C, 24000, 0x1F, 3500),
+ RT1719_PSEL_CAPINFO(0x1C, 15000, 0x1F, 3000),
+ RT1719_PSEL_CAPINFO(0x1C, 10000, 0x1F, 2500),
+ RT1719_PSEL_CAPINFO(0x0C, 60000, 0x1F, 2000),
+ RT1719_PSEL_CAPINFO(0x0C, 45000, 0x1F, 1000),
+ RT1719_PSEL_CAPINFO(0x0C, 36000, 0x08, 5000),
+ RT1719_PSEL_CAPINFO(0x0C, 30000, 0x08, 4500),
+ RT1719_PSEL_CAPINFO(0x0C, 24000, 0x08, 4000),
+ RT1719_PSEL_CAPINFO(0x0C, 15000, 0x08, 3500),
+ RT1719_PSEL_CAPINFO(0x0C, 10000, 0x08, 3000),
+ RT1719_PSEL_CAPINFO(0x1E, 45000, 0x08, 2500),
+ RT1719_PSEL_CAPINFO(0x1E, 36000, 0x08, 2000),
+ RT1719_PSEL_CAPINFO(0x1E, 27000, 0x08, 1500),
+ RT1719_PSEL_CAPINFO(0x1E, 20000, 0x08, 1000),
+ RT1719_PSEL_CAPINFO(0x1E, 15000, 0x0F, 5000),
+ RT1719_PSEL_CAPINFO(0x1E, 9000, 0x0F, 4500),
+ RT1719_PSEL_CAPINFO(0x0E, 45000, 0x0F, 4000),
+ RT1719_PSEL_CAPINFO(0x0E, 36000, 0x0F, 3500),
+ RT1719_PSEL_CAPINFO(0x0E, 27000, 0x0F, 3000),
+ RT1719_PSEL_CAPINFO(0x0E, 20000, 0x0F, 2500),
+ RT1719_PSEL_CAPINFO(0x0E, 15000, 0x0F, 2000),
+ RT1719_PSEL_CAPINFO(0x0E, 9000, 0x0F, 1500),
+ RT1719_PSEL_CAPINFO(0x06, 45000, 0x0F, 1000),
+ RT1719_PSEL_CAPINFO(0x06, 36000, 0x0F, 500),
+ RT1719_PSEL_CAPINFO(0x06, 27000, 0x04, 5000),
+ RT1719_PSEL_CAPINFO(0x06, 24000, 0x04, 4500),
+ RT1719_PSEL_CAPINFO(0x06, 18000, 0x04, 4000),
+ RT1719_PSEL_CAPINFO(0x06, 12000, 0x04, 3500),
+ RT1719_PSEL_CAPINFO(0x06, 9000, 0x04, 3000),
+ RT1719_PSEL_CAPINFO(0x1F, 25000, 0x04, 2500),
+ RT1719_PSEL_CAPINFO(0x1F, 20000, 0x04, 2000),
+ RT1719_PSEL_CAPINFO(0x1F, 15000, 0x04, 1500),
+ RT1719_PSEL_CAPINFO(0x1F, 10000, 0x04, 1000),
+ RT1719_PSEL_CAPINFO(0x1F, 7500, 0x07, 5000),
+ RT1719_PSEL_CAPINFO(0x0F, 25000, 0x07, 4500),
+ RT1719_PSEL_CAPINFO(0x0F, 20000, 0x07, 4000),
+ RT1719_PSEL_CAPINFO(0x0F, 15000, 0x07, 3500),
+ RT1719_PSEL_CAPINFO(0x0F, 10000, 0x07, 3000),
+ RT1719_PSEL_CAPINFO(0x0F, 7500, 0x07, 2500),
+ RT1719_PSEL_CAPINFO(0x07, 25000, 0x07, 2000),
+ RT1719_PSEL_CAPINFO(0x07, 20000, 0x07, 1500),
+ RT1719_PSEL_CAPINFO(0x07, 15000, 0x07, 1000),
+ RT1719_PSEL_CAPINFO(0x07, 10000, 0x07, 500),
+ RT1719_PSEL_CAPINFO(0x07, 7500, 0x03, 5000),
+ RT1719_PSEL_CAPINFO(0x03, 25000, 0x03, 4500),
+ RT1719_PSEL_CAPINFO(0x03, 20000, 0x03, 4000),
+ RT1719_PSEL_CAPINFO(0x03, 15000, 0x03, 3500),
+ RT1719_PSEL_CAPINFO(0x03, 10000, 0x03, 3000),
+ RT1719_PSEL_CAPINFO(0x03, 7500, 0x03, 2500),
+ RT1719_PSEL_CAPINFO(0x01, 15000, 0x03, 2000),
+ RT1719_PSEL_CAPINFO(0x01, 10000, 0x03, 1500),
+ RT1719_PSEL_CAPINFO(0x01, 7500, 0x03, 1000),
+ RT1719_PSEL_CAPINFO(0x01, 2500, 0x03, 500)
+};
+
+static u16 rt1719_gen_snkcap_by_current(const struct rt1719_psel_cap *psel_cap,
+ enum rt1719_snkcap capsel)
+{
+ u16 cap = RT1719_PSEL_SUPPORT;
+
+ if (!(psel_cap->himask & BIT(capsel)))
+ return 0;
+
+ cap |= psel_cap->milliamp / 10;
+ return cap;
+}
+
+static u16 rt1719_gen_snkcap_by_watt(const struct rt1719_psel_cap *psel_cap,
+ enum rt1719_snkcap capsel)
+{
+ u32 volt_div[RT1719_MAX_SNKCAP] = { 5, 9, 12, 15, 20 };
+ u16 cap = RT1719_PSEL_SUPPORT;
+
+ if (!(psel_cap->lomask & BIT(capsel)))
+ return 0;
+
+ cap |= min(psel_cap->milliwatt / volt_div[capsel], (u32)5000) / 10;
+ return cap;
+}
+
+static u16 rt1719_gen_snkcap(unsigned int pselinfo, enum rt1719_snkcap capsel)
+{
+ int psel = FIELD_GET(RT1719_LATPSEL_MASK, pselinfo);
+ const struct rt1719_psel_cap *psel_cap;
+ bool by_current = false;
+
+ if (pselinfo & RT1719_TBLSEL_MASK)
+ by_current = true;
+
+ psel_cap = rt1719_psel_caps + psel;
+ if (by_current)
+ return rt1719_gen_snkcap_by_current(psel_cap, capsel);
+
+ return rt1719_gen_snkcap_by_watt(psel_cap, capsel);
+}
+
+static int rt1719_get_caps(struct rt1719_data *data)
+{
+ unsigned int pselinfo, usbinfo;
+ int i, ret;
+
+ ret = regmap_read(data->regmap, RT1719_REG_PSELINFO, &pselinfo);
+ ret |= regmap_read(data->regmap, RT1719_REG_USBSETINFO, &usbinfo);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < RT1719_MAX_SNKCAP; i++)
+ data->snkcaps[i] = rt1719_gen_snkcap(pselinfo, i);
+
+ usbinfo = FIELD_GET(RT1719_USBINFO_MASK, usbinfo);
+ if (usbinfo == RT1719_USB_DFPUFP)
+ data->drswap_support = true;
+
+ return 0;
+}
+
+static int rt1719_check_exist(struct rt1719_data *data)
+{
+ u16 pid;
+ int ret;
+
+ ret = rt1719_read16(data, RT1719_REG_VENID, &pid);
+ if (ret)
+ return ret;
+
+ if (pid != RT1719_UNIQUE_PID) {
+ dev_err(data->dev, "Incorrect PID 0x%04x\n", pid);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static const struct regmap_config rt1719_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+};
+
+static int rt1719_probe(struct i2c_client *i2c)
+{
+ struct rt1719_data *data;
+ struct fwnode_handle *fwnode;
+ struct typec_capability typec_cap = { };
+ int ret;
+
+ data = devm_kzalloc(&i2c->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = &i2c->dev;
+ init_completion(&data->req_completion);
+
+ data->regmap = devm_regmap_init_i2c(i2c, &rt1719_regmap_config);
+ if (IS_ERR(data->regmap)) {
+ ret = PTR_ERR(data->regmap);
+ dev_err(&i2c->dev, "Failed to init regmap (%d)\n", ret);
+ return ret;
+ }
+
+ ret = rt1719_check_exist(data);
+ if (ret)
+ return ret;
+
+ ret = rt1719_get_caps(data);
+ if (ret)
+ return ret;
+
+ fwnode = device_get_named_child_node(&i2c->dev, "connector");
+ if (!fwnode)
+ return -ENODEV;
+
+ data->role_sw = fwnode_usb_role_switch_get(fwnode);
+ if (IS_ERR(data->role_sw)) {
+ ret = PTR_ERR(data->role_sw);
+ dev_err(&i2c->dev, "Failed to get usb role switch (%d)\n", ret);
+ goto err_fwnode_put;
+ }
+
+ ret = devm_rt1719_psy_register(data);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to register psy (%d)\n", ret);
+ goto err_role_put;
+ }
+
+ typec_cap.revision = USB_TYPEC_REV_1_2;
+ typec_cap.pd_revision = 0x300; /* USB-PD spec release 3.0 */
+ typec_cap.type = TYPEC_PORT_SNK;
+ typec_cap.data = TYPEC_PORT_DRD;
+ typec_cap.ops = &rt1719_port_ops;
+ typec_cap.fwnode = fwnode;
+ typec_cap.driver_data = data;
+ typec_cap.accessory[0] = TYPEC_ACCESSORY_DEBUG;
+
+ data->partner_desc.identity = &data->partner_ident;
+
+ data->port = typec_register_port(&i2c->dev, &typec_cap);
+ if (IS_ERR(data->port)) {
+ ret = PTR_ERR(data->port);
+ dev_err(&i2c->dev, "Failed to register typec port (%d)\n", ret);
+ goto err_role_put;
+ }
+
+ ret = rt1719_init_attach_state(data);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to init attach state (%d)\n", ret);
+ goto err_role_put;
+ }
+
+ ret = rt1719_irq_init(data);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to init irq\n");
+ goto err_role_put;
+ }
+
+ fwnode_handle_put(fwnode);
+
+ i2c_set_clientdata(i2c, data);
+
+ return 0;
+
+err_role_put:
+ usb_role_switch_put(data->role_sw);
+err_fwnode_put:
+ fwnode_handle_put(fwnode);
+
+ return ret;
+}
+
+static int rt1719_remove(struct i2c_client *i2c)
+{
+ struct rt1719_data *data = i2c_get_clientdata(i2c);
+
+ typec_unregister_port(data->port);
+ usb_role_switch_put(data->role_sw);
+
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused rt1719_device_table[] = {
+ { .compatible = "richtek,rt1719", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rt1719_device_table);
+
+static struct i2c_driver rt1719_driver = {
+ .driver = {
+ .name = "rt1719",
+ .of_match_table = rt1719_device_table,
+ },
+ .probe_new = rt1719_probe,
+ .remove = rt1719_remove,
+};
+module_i2c_driver(rt1719_driver);
+
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("Richtek RT1719 Sink Only USBPD Controller Driver");
+MODULE_LICENSE("GPL v2");
struct fwnode_handle *fwnode)
{
const char *opmode_str;
- const char *cap_str;
int ret;
u32 mw, frs_current;
*/
fw_devlink_purge_absent_suppliers(fwnode);
- /* USB data support is optional */
- ret = fwnode_property_read_string(fwnode, "data-role", &cap_str);
- if (ret == 0) {
- ret = typec_find_port_data_role(cap_str);
- if (ret < 0)
- return ret;
- port->typec_caps.data = ret;
- }
-
- ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
+ ret = typec_get_fw_cap(&port->typec_caps, fwnode);
if (ret < 0)
return ret;
- ret = typec_find_port_power_role(cap_str);
- if (ret < 0)
- return ret;
- port->typec_caps.type = ret;
port->port_type = port->typec_caps.type;
port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable");
if (port->port_type == TYPEC_PORT_SRC)
return 0;
- /* Get the preferred power role for DRP */
- ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str);
- if (ret < 0)
- return ret;
-
- port->typec_caps.prefer_role = typec_find_power_role(cap_str);
- if (port->typec_caps.prefer_role < 0)
- return -EINVAL;
sink:
port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Willsemi WUSB3801 Type-C port controller driver
+ *
+ * Copyright (C) 2022 Samuel Holland <samuel@sholland.org>
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/typec.h>
+
+#define WUSB3801_REG_DEVICE_ID 0x01
+#define WUSB3801_REG_CTRL0 0x02
+#define WUSB3801_REG_INT 0x03
+#define WUSB3801_REG_STAT 0x04
+#define WUSB3801_REG_CTRL1 0x05
+#define WUSB3801_REG_TEST00 0x06
+#define WUSB3801_REG_TEST01 0x07
+#define WUSB3801_REG_TEST02 0x08
+#define WUSB3801_REG_TEST03 0x09
+#define WUSB3801_REG_TEST04 0x0a
+#define WUSB3801_REG_TEST05 0x0b
+#define WUSB3801_REG_TEST06 0x0c
+#define WUSB3801_REG_TEST07 0x0d
+#define WUSB3801_REG_TEST08 0x0e
+#define WUSB3801_REG_TEST09 0x0f
+#define WUSB3801_REG_TEST0A 0x10
+#define WUSB3801_REG_TEST0B 0x11
+#define WUSB3801_REG_TEST0C 0x12
+#define WUSB3801_REG_TEST0D 0x13
+#define WUSB3801_REG_TEST0E 0x14
+#define WUSB3801_REG_TEST0F 0x15
+#define WUSB3801_REG_TEST10 0x16
+#define WUSB3801_REG_TEST11 0x17
+#define WUSB3801_REG_TEST12 0x18
+
+#define WUSB3801_DEVICE_ID_VERSION_ID GENMASK(7, 3)
+#define WUSB3801_DEVICE_ID_VENDOR_ID GENMASK(2, 0)
+
+#define WUSB3801_CTRL0_DIS_ACC_SUPPORT BIT(7)
+#define WUSB3801_CTRL0_TRY GENMASK(6, 5)
+#define WUSB3801_CTRL0_TRY_NONE (0x0 << 5)
+#define WUSB3801_CTRL0_TRY_SNK (0x1 << 5)
+#define WUSB3801_CTRL0_TRY_SRC (0x2 << 5)
+#define WUSB3801_CTRL0_CURRENT GENMASK(4, 3) /* SRC */
+#define WUSB3801_CTRL0_CURRENT_DEFAULT (0x0 << 3)
+#define WUSB3801_CTRL0_CURRENT_1_5A (0x1 << 3)
+#define WUSB3801_CTRL0_CURRENT_3_0A (0x2 << 3)
+#define WUSB3801_CTRL0_ROLE GENMASK(2, 1)
+#define WUSB3801_CTRL0_ROLE_SNK (0x0 << 1)
+#define WUSB3801_CTRL0_ROLE_SRC (0x1 << 1)
+#define WUSB3801_CTRL0_ROLE_DRP (0x2 << 1)
+#define WUSB3801_CTRL0_INT_MASK BIT(0)
+
+#define WUSB3801_INT_ATTACHED BIT(0)
+#define WUSB3801_INT_DETACHED BIT(1)
+
+#define WUSB3801_STAT_VBUS_DETECTED BIT(7)
+#define WUSB3801_STAT_CURRENT GENMASK(6, 5) /* SNK */
+#define WUSB3801_STAT_CURRENT_STANDBY (0x0 << 5)
+#define WUSB3801_STAT_CURRENT_DEFAULT (0x1 << 5)
+#define WUSB3801_STAT_CURRENT_1_5A (0x2 << 5)
+#define WUSB3801_STAT_CURRENT_3_0A (0x3 << 5)
+#define WUSB3801_STAT_PARTNER GENMASK(4, 2)
+#define WUSB3801_STAT_PARTNER_STANDBY (0x0 << 2)
+#define WUSB3801_STAT_PARTNER_SNK (0x1 << 2)
+#define WUSB3801_STAT_PARTNER_SRC (0x2 << 2)
+#define WUSB3801_STAT_PARTNER_AUDIO (0x3 << 2)
+#define WUSB3801_STAT_PARTNER_DEBUG (0x4 << 2)
+#define WUSB3801_STAT_ORIENTATION GENMASK(1, 0)
+#define WUSB3801_STAT_ORIENTATION_NONE (0x0 << 0)
+#define WUSB3801_STAT_ORIENTATION_CC1 (0x1 << 0)
+#define WUSB3801_STAT_ORIENTATION_CC2 (0x2 << 0)
+#define WUSB3801_STAT_ORIENTATION_BOTH (0x3 << 0)
+
+#define WUSB3801_CTRL1_SM_RESET BIT(0)
+
+#define WUSB3801_TEST01_VENDOR_SUB_ID (BIT(8) | BIT(6))
+
+#define WUSB3801_TEST02_FORCE_ERR_RCY BIT(8)
+
+#define WUSB3801_TEST0A_WAIT_VBUS BIT(5)
+
+struct wusb3801 {
+ struct typec_capability cap;
+ struct device *dev;
+ struct typec_partner *partner;
+ struct typec_port *port;
+ struct regmap *regmap;
+ struct regulator *vbus_supply;
+ unsigned int partner_type;
+ enum typec_port_type port_type;
+ enum typec_pwr_opmode pwr_opmode;
+ bool vbus_on;
+};
+
+static enum typec_role wusb3801_get_default_role(struct wusb3801 *wusb3801)
+{
+ switch (wusb3801->port_type) {
+ case TYPEC_PORT_SRC:
+ return TYPEC_SOURCE;
+ case TYPEC_PORT_SNK:
+ return TYPEC_SINK;
+ case TYPEC_PORT_DRP:
+ default:
+ if (wusb3801->cap.prefer_role == TYPEC_SOURCE)
+ return TYPEC_SOURCE;
+ return TYPEC_SINK;
+ }
+}
+
+static int wusb3801_map_port_type(enum typec_port_type type)
+{
+ switch (type) {
+ case TYPEC_PORT_SRC:
+ return WUSB3801_CTRL0_ROLE_SRC;
+ case TYPEC_PORT_SNK:
+ return WUSB3801_CTRL0_ROLE_SNK;
+ case TYPEC_PORT_DRP:
+ default:
+ return WUSB3801_CTRL0_ROLE_DRP;
+ }
+}
+
+static int wusb3801_map_pwr_opmode(enum typec_pwr_opmode mode)
+{
+ switch (mode) {
+ case TYPEC_PWR_MODE_USB:
+ default:
+ return WUSB3801_CTRL0_CURRENT_DEFAULT;
+ case TYPEC_PWR_MODE_1_5A:
+ return WUSB3801_CTRL0_CURRENT_1_5A;
+ case TYPEC_PWR_MODE_3_0A:
+ return WUSB3801_CTRL0_CURRENT_3_0A;
+ }
+}
+
+static unsigned int wusb3801_map_try_role(int role)
+{
+ switch (role) {
+ case TYPEC_NO_PREFERRED_ROLE:
+ default:
+ return WUSB3801_CTRL0_TRY_NONE;
+ case TYPEC_SINK:
+ return WUSB3801_CTRL0_TRY_SNK;
+ case TYPEC_SOURCE:
+ return WUSB3801_CTRL0_TRY_SRC;
+ }
+}
+
+static enum typec_orientation wusb3801_unmap_orientation(unsigned int status)
+{
+ switch (status & WUSB3801_STAT_ORIENTATION) {
+ case WUSB3801_STAT_ORIENTATION_NONE:
+ case WUSB3801_STAT_ORIENTATION_BOTH:
+ default:
+ return TYPEC_ORIENTATION_NONE;
+ case WUSB3801_STAT_ORIENTATION_CC1:
+ return TYPEC_ORIENTATION_NORMAL;
+ case WUSB3801_STAT_ORIENTATION_CC2:
+ return TYPEC_ORIENTATION_REVERSE;
+ }
+}
+
+static enum typec_pwr_opmode wusb3801_unmap_pwr_opmode(unsigned int status)
+{
+ switch (status & WUSB3801_STAT_CURRENT) {
+ case WUSB3801_STAT_CURRENT_STANDBY:
+ case WUSB3801_STAT_CURRENT_DEFAULT:
+ default:
+ return TYPEC_PWR_MODE_USB;
+ case WUSB3801_STAT_CURRENT_1_5A:
+ return TYPEC_PWR_MODE_1_5A;
+ case WUSB3801_STAT_CURRENT_3_0A:
+ return TYPEC_PWR_MODE_3_0A;
+ }
+}
+
+static int wusb3801_try_role(struct typec_port *port, int role)
+{
+ struct wusb3801 *wusb3801 = typec_get_drvdata(port);
+
+ return regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0,
+ WUSB3801_CTRL0_TRY,
+ wusb3801_map_try_role(role));
+}
+
+static int wusb3801_port_type_set(struct typec_port *port,
+ enum typec_port_type type)
+{
+ struct wusb3801 *wusb3801 = typec_get_drvdata(port);
+ int ret;
+
+ ret = regmap_update_bits(wusb3801->regmap, WUSB3801_REG_CTRL0,
+ WUSB3801_CTRL0_ROLE,
+ wusb3801_map_port_type(type));
+ if (ret)
+ return ret;
+
+ wusb3801->port_type = type;
+
+ return 0;
+}
+
+static const struct typec_operations wusb3801_typec_ops = {
+ .try_role = wusb3801_try_role,
+ .port_type_set = wusb3801_port_type_set,
+};
+
+static int wusb3801_hw_init(struct wusb3801 *wusb3801)
+{
+ return regmap_write(wusb3801->regmap, WUSB3801_REG_CTRL0,
+ wusb3801_map_try_role(wusb3801->cap.prefer_role) |
+ wusb3801_map_pwr_opmode(wusb3801->pwr_opmode) |
+ wusb3801_map_port_type(wusb3801->port_type));
+}
+
+static void wusb3801_hw_update(struct wusb3801 *wusb3801)
+{
+ struct typec_port *port = wusb3801->port;
+ struct device *dev = wusb3801->dev;
+ unsigned int partner_type, status;
+ int ret;
+
+ ret = regmap_read(wusb3801->regmap, WUSB3801_REG_STAT, &status);
+ if (ret) {
+ dev_warn(dev, "Failed to read port status: %d\n", ret);
+ status = 0;
+ }
+ dev_dbg(dev, "status = 0x%02x\n", status);
+
+ partner_type = status & WUSB3801_STAT_PARTNER;
+
+ if (partner_type == WUSB3801_STAT_PARTNER_SNK) {
+ if (!wusb3801->vbus_on) {
+ ret = regulator_enable(wusb3801->vbus_supply);
+ if (ret)
+ dev_warn(dev, "Failed to enable VBUS: %d\n", ret);
+ wusb3801->vbus_on = true;
+ }
+ } else {
+ if (wusb3801->vbus_on) {
+ regulator_disable(wusb3801->vbus_supply);
+ wusb3801->vbus_on = false;
+ }
+ }
+
+ if (partner_type != wusb3801->partner_type) {
+ struct typec_partner_desc desc = {};
+ enum typec_data_role data_role;
+ enum typec_role pwr_role = wusb3801_get_default_role(wusb3801);
+
+ switch (partner_type) {
+ case WUSB3801_STAT_PARTNER_STANDBY:
+ break;
+ case WUSB3801_STAT_PARTNER_SNK:
+ pwr_role = TYPEC_SOURCE;
+ break;
+ case WUSB3801_STAT_PARTNER_SRC:
+ pwr_role = TYPEC_SINK;
+ break;
+ case WUSB3801_STAT_PARTNER_AUDIO:
+ desc.accessory = TYPEC_ACCESSORY_AUDIO;
+ break;
+ case WUSB3801_STAT_PARTNER_DEBUG:
+ desc.accessory = TYPEC_ACCESSORY_DEBUG;
+ break;
+ }
+
+ if (wusb3801->partner) {
+ typec_unregister_partner(wusb3801->partner);
+ wusb3801->partner = NULL;
+ }
+
+ if (partner_type != WUSB3801_STAT_PARTNER_STANDBY) {
+ wusb3801->partner = typec_register_partner(port, &desc);
+ if (IS_ERR(wusb3801->partner))
+ dev_err(dev, "Failed to register partner: %ld\n",
+ PTR_ERR(wusb3801->partner));
+ }
+
+ data_role = pwr_role == TYPEC_SOURCE ? TYPEC_HOST : TYPEC_DEVICE;
+ typec_set_data_role(port, data_role);
+ typec_set_pwr_role(port, pwr_role);
+ typec_set_vconn_role(port, pwr_role);
+ }
+
+ typec_set_pwr_opmode(wusb3801->port,
+ partner_type == WUSB3801_STAT_PARTNER_SRC
+ ? wusb3801_unmap_pwr_opmode(status)
+ : wusb3801->pwr_opmode);
+ typec_set_orientation(wusb3801->port,
+ wusb3801_unmap_orientation(status));
+
+ wusb3801->partner_type = partner_type;
+}
+
+static irqreturn_t wusb3801_irq(int irq, void *data)
+{
+ struct wusb3801 *wusb3801 = data;
+ unsigned int dummy;
+
+ /*
+ * The interrupt register must be read in order to clear the IRQ,
+ * but all of the useful information is in the status register.
+ */
+ regmap_read(wusb3801->regmap, WUSB3801_REG_INT, &dummy);
+
+ wusb3801_hw_update(wusb3801);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = WUSB3801_REG_TEST12,
+};
+
+static int wusb3801_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fwnode_handle *connector;
+ struct wusb3801 *wusb3801;
+ const char *cap_str;
+ int ret;
+
+ wusb3801 = devm_kzalloc(dev, sizeof(*wusb3801), GFP_KERNEL);
+ if (!wusb3801)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, wusb3801);
+
+ wusb3801->dev = dev;
+
+ wusb3801->regmap = devm_regmap_init_i2c(client, &config);
+ if (IS_ERR(wusb3801->regmap))
+ return PTR_ERR(wusb3801->regmap);
+
+ wusb3801->vbus_supply = devm_regulator_get(dev, "vbus");
+ if (IS_ERR(wusb3801->vbus_supply))
+ return PTR_ERR(wusb3801->vbus_supply);
+
+ connector = device_get_named_child_node(dev, "connector");
+ if (!connector)
+ return -ENODEV;
+
+ ret = typec_get_fw_cap(&wusb3801->cap, connector);
+ if (ret)
+ goto err_put_connector;
+ wusb3801->port_type = wusb3801->cap.type;
+
+ ret = fwnode_property_read_string(connector, "typec-power-opmode", &cap_str);
+ if (ret)
+ goto err_put_connector;
+
+ ret = typec_find_pwr_opmode(cap_str);
+ if (ret < 0 || ret == TYPEC_PWR_MODE_PD)
+ goto err_put_connector;
+ wusb3801->pwr_opmode = ret;
+
+ /* Initialize the hardware with the devicetree settings. */
+ ret = wusb3801_hw_init(wusb3801);
+ if (ret)
+ return ret;
+
+ wusb3801->cap.revision = USB_TYPEC_REV_1_2;
+ wusb3801->cap.accessory[0] = TYPEC_ACCESSORY_AUDIO;
+ wusb3801->cap.accessory[1] = TYPEC_ACCESSORY_DEBUG;
+ wusb3801->cap.orientation_aware = true;
+ wusb3801->cap.driver_data = wusb3801;
+ wusb3801->cap.ops = &wusb3801_typec_ops;
+
+ wusb3801->port = typec_register_port(dev, &wusb3801->cap);
+ if (IS_ERR(wusb3801->port)) {
+ ret = PTR_ERR(wusb3801->port);
+ goto err_put_connector;
+ }
+
+ /* Initialize the port attributes from the hardware state. */
+ wusb3801_hw_update(wusb3801);
+
+ ret = request_threaded_irq(client->irq, NULL, wusb3801_irq,
+ IRQF_ONESHOT, dev_name(dev), wusb3801);
+ if (ret)
+ goto err_unregister_port;
+
+ fwnode_handle_put(connector);
+
+ return 0;
+
+err_unregister_port:
+ typec_unregister_port(wusb3801->port);
+err_put_connector:
+ fwnode_handle_put(connector);
+
+ return ret;
+}
+
+static int wusb3801_remove(struct i2c_client *client)
+{
+ struct wusb3801 *wusb3801 = i2c_get_clientdata(client);
+
+ free_irq(client->irq, wusb3801);
+
+ if (wusb3801->partner)
+ typec_unregister_partner(wusb3801->partner);
+ typec_unregister_port(wusb3801->port);
+
+ if (wusb3801->vbus_on)
+ regulator_disable(wusb3801->vbus_supply);
+
+ return 0;
+}
+
+static const struct of_device_id wusb3801_of_match[] = {
+ { .compatible = "willsemi,wusb3801" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, wusb3801_of_match);
+
+static struct i2c_driver wusb3801_driver = {
+ .probe_new = wusb3801_probe,
+ .remove = wusb3801_remove,
+ .driver = {
+ .name = "wusb3801",
+ .of_match_table = wusb3801_of_match,
+ },
+};
+
+module_i2c_driver(wusb3801_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("Willsemi WUSB3801 Type-C port controller driver");
+MODULE_LICENSE("GPL");
},
};
-static struct list_head vudc_devices = LIST_HEAD_INIT(vudc_devices);
+static LIST_HEAD(vudc_devices);
static int __init init(void)
{
void *typec_get_drvdata(struct typec_port *port);
+int typec_get_fw_cap(struct typec_capability *cap,
+ struct fwnode_handle *fwnode);
+
int typec_find_pwr_opmode(const char *name);
int typec_find_orientation(const char *name);
int typec_find_port_power_role(const char *name);