diff --git a/Documentation/ABI/testing/sysfs-lontium-firmware b/Documentation/ABI/testing/sysfs-lontium-firmware new file mode 100644 index 0000000000000..8700933ba0f02 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-lontium-firmware @@ -0,0 +1,10 @@ +What: /sys/bus/i2c/drivers//.../firmware +Date: July 2026 +KernelVersion: 7.3 +Contact: Mohit Dsor +Description: + Read: Returns the current firmware version loaded on the + Lontium bridge chip in hex format (e.g. "0x0105"). + + Write: Triggers a firmware upgrade of the chip using the + firmware file loaded via the firmware loader. diff --git a/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml b/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml index db895c50e2d25..9e6d3af429713 100644 --- a/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml +++ b/Documentation/devicetree/bindings/crypto/qcom,inline-crypto-engine.yaml @@ -21,6 +21,7 @@ properties: - qcom,sa8775p-inline-crypto-engine - qcom,sc7180-inline-crypto-engine - qcom,sc7280-inline-crypto-engine + - qcom,shikra-inline-crypto-engine - qcom,sm8450-inline-crypto-engine - qcom,sm8550-inline-crypto-engine - qcom,sm8650-inline-crypto-engine @@ -55,14 +56,25 @@ required: additionalProperties: false +# Do not extend the list. +# Legacy SoCs are allowed for single clock. +# New SoCs must provide both clocks and power domains. allOf: - if: - properties: - compatible: - contains: - enum: - - qcom,eliza-inline-crypto-engine - - qcom,milos-inline-crypto-engine + not: + properties: + compatible: + contains: + enum: + - qcom,kaanapali-inline-crypto-engine + - qcom,qcs8300-inline-crypto-engine + - qcom,sa8775p-inline-crypto-engine + - qcom,sc7180-inline-crypto-engine + - qcom,sc7280-inline-crypto-engine + - qcom,sm8450-inline-crypto-engine + - qcom,sm8550-inline-crypto-engine + - qcom,sm8650-inline-crypto-engine + - qcom,sm8750-inline-crypto-engine then: required: diff --git a/Documentation/devicetree/bindings/crypto/qcom,prng.yaml b/Documentation/devicetree/bindings/crypto/qcom,prng.yaml index dc270c8aedf3a..5de52d7a745c3 100644 --- a/Documentation/devicetree/bindings/crypto/qcom,prng.yaml +++ b/Documentation/devicetree/bindings/crypto/qcom,prng.yaml @@ -30,6 +30,7 @@ properties: - qcom,sa8255p-trng - qcom,sa8775p-trng - qcom,sc7280-trng + - qcom,shikra-trng - qcom,sm8450-trng - qcom,sm8550-trng - qcom,sm8650-trng diff --git a/Documentation/devicetree/bindings/crypto/qcom-qce.yaml b/Documentation/devicetree/bindings/crypto/qcom-qce.yaml index 08febd66c22ba..5a653757ee752 100644 --- a/Documentation/devicetree/bindings/crypto/qcom-qce.yaml +++ b/Documentation/devicetree/bindings/crypto/qcom-qce.yaml @@ -54,6 +54,7 @@ properties: - qcom,qcs8300-qce - qcom,sa8775p-qce - qcom,sc7280-qce + - qcom,shikra-qce - qcom,sm6350-qce - qcom,sm8250-qce - qcom,sm8350-qce diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml index 429a06057ae85..194aeaac601f3 100644 --- a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml @@ -4,19 +4,23 @@ $id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Lontium LT9611(UXC) 2 Port MIPI to HDMI Bridge +title: Lontium LT9611(UXC/C/EX/UXD) 2 Port MIPI DSI to HDMI Bridge maintainers: - Vinod Koul description: | - The LT9611 and LT9611UXC are bridge devices which convert DSI to HDMI + The LT9611, LT9611UXC, LT9611C, LT9611EX, and LT9611UXD + are bridge devices which convert DSI to HDMI properties: compatible: enum: - lontium,lt9611 + - lontium,lt9611c + - lontium,lt9611ex - lontium,lt9611uxc + - lontium,lt9611uxd reg: maxItems: 1 @@ -78,6 +82,16 @@ required: allOf: - $ref: /schemas/sound/dai-common.yaml# + - if: + properties: + compatible: + contains: + const: lontium,lt9611c + then: + properties: + ports: + properties: + port@1: false unevaluatedProperties: false diff --git a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml index 0923fb189ada9..c90d47d4a2543 100644 --- a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml +++ b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml @@ -12,6 +12,15 @@ maintainers: allOf: - $ref: dma-controller.yaml# + - if: + not: + properties: + compatible: + contains: + const: qcom,shikra-bam-dma + then: + properties: + qcom,vmid: false properties: compatible: @@ -29,6 +38,8 @@ properties: - enum: # SDM845, SM6115, SM8150, SM8250 and QCM2290 - qcom,bam-v1.7.4 + # Shikra + - qcom,shikra-bam-dma - const: qcom,bam-v1.7.0 clocks: @@ -48,7 +59,7 @@ properties: iommus: minItems: 1 - maxItems: 6 + maxItems: 7 num-channels: $ref: /schemas/types.yaml#/definitions/uint32 @@ -81,6 +92,21 @@ properties: Indicates that the bam is powered up by a remote processor but must be initialized by the local processor. + qcom,vmid: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 8 + items: + minimum: 1 + maximum: 63 + description: + Destination VMIDs of the remote processor(s) that read the per-channel + descriptor FIFOs as an AXI master. When present, the driver SCM-assigns + each FIFO to these VMIDs so the remote access does not trigger an XPU + violation. HLOS is always retained as the source owner and must not be + listed. Optional even when the qcom,shikra-bam-dma compatible is + present; not valid on any other compatible in this schema. + reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml b/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml index 33746c238513d..27f0fdf285c17 100644 --- a/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml +++ b/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml @@ -22,7 +22,13 @@ description: | properties: compatible: - const: qcom,bam-dmux + oneOf: + - const: qcom,bam-dmux + - items: + - enum: + # Shikra + - qcom,shikra-bam-dmux + - const: qcom,bam-dmux interrupts: description: diff --git a/Documentation/devicetree/bindings/pci/qcom,shikra-pcie.yaml b/Documentation/devicetree/bindings/pci/qcom,shikra-pcie.yaml new file mode 100644 index 0000000000000..f9d1dba9dd2e8 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/qcom,shikra-pcie.yaml @@ -0,0 +1,211 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pci/qcom,shikra-pcie.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Shikra PCI Express Root Complex + +maintainers: + - Bjorn Andersson + - Manivannan Sadhasivam + +description: + Qualcomm Shikra SoC (and compatible) PCIe root complex controller is based on + the Synopsys DesignWare PCIe IP. + +properties: + compatible: + const: qcom,shikra-pcie + + reg: + minItems: 5 + maxItems: 6 + + reg-names: + minItems: 5 + items: + - const: parf # Qualcomm specific registers + - const: dbi # DesignWare PCIe registers + - const: elbi # External local bus interface registers + - const: atu # ATU address space + - const: config # PCIe configuration space + - const: mhi # MHI registers + + clocks: + minItems: 7 + maxItems: 9 + + clock-names: + minItems: 7 + items: + - const: aux # Auxiliary clock + - const: cfg # Configuration clock + - const: bus_master # Master AXI clock + - const: bus_slave # Slave AXI clock + - const: slave_q2a # Slave Q2A clock + - const: ddrss_memnoc_pcie # PCIe SF MEMNOC clock + - const: tile # PCIe tile SYS NoC clock + - const: qmip_pcie_ahb # QMIP PCIe AHB clock + + interrupts: + minItems: 8 + maxItems: 9 + + interrupt-names: + minItems: 8 + items: + - const: msi0 + - const: msi1 + - const: msi2 + - const: msi3 + - const: msi4 + - const: msi5 + - const: msi6 + - const: msi7 + - const: global + + resets: + minItems: 1 + maxItems: 2 + + reset-names: + minItems: 1 + items: + - const: pci # PCIe core reset + - const: link_down # PCIe link down reset + +required: + - power-domains + - resets + - reset-names + +allOf: + - $ref: qcom,pcie-common.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + pcie@45e8000 { + device_type = "pci"; + compatible = "qcom,shikra-pcie"; + reg = <0x0 0x045e8000 0x0 0x3000>, + <0x0 0x60000000 0x0 0xf1d>, + <0x0 0x60000f20 0x0 0xa8>, + <0x0 0x60001000 0x0 0x1000>, + <0x0 0x60100000 0x0 0x100000>, + <0x0 0x045eb000 0x0 0x1000>; + reg-names = "parf", + "dbi", + "elbi", + "atu", + "config", + "mhi"; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x01000000 0x0 0x00000000 0x0 0x60200000 0x0 0x100000>, + <0x02000000 0x0 0x60300000 0x0 0x60300000 0x0 0x3d00000>, + <0x03000000 0x4 0x00000000 0x4 0x00000000 0x3 0x0000000>; + bus-range = <0x00 0xff>; + + linux,pci-domain = <0>; + num-lanes = <1>; + + interrupts = , + , + , + , + , + , + , + , + ; + interrupt-names = "msi0", + "msi1", + "msi2", + "msi3", + "msi4", + "msi5", + "msi6", + "msi7", + "global"; + + interrupt-map = <0 0 0 1 &intc 0 0 0 499 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &intc 0 0 0 500 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &intc 0 0 0 501 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &intc 0 0 0 502 IRQ_TYPE_LEVEL_HIGH>; + interrupt-map-mask = <0 0 0 0x7>; + #interrupt-cells = <1>; + + clocks = <&gcc GCC_PCIE_AUX_CLK>, + <&gcc GCC_PCIE_CFG_AHB_CLK>, + <&gcc GCC_PCIE_MSTR_AXI_CLK>, + <&gcc GCC_PCIE_SLV_AXI_CLK>, + <&gcc GCC_PCIE_SLV_Q2A_AXI_CLK>, + <&gcc GCC_DDRSS_MEMNOC_PCIE_SF_CLK>, + <&gcc GCC_PCIE_TILE_AXI_SYS_NOC_CLK>, + <&gcc GCC_QMIP_PCIE_CFG_AHB_CLK>; + clock-names = "aux", + "cfg", + "bus_master", + "bus_slave", + "slave_q2a", + "ddrss_memnoc_pcie", + "tile", + "qmip_pcie_ahb"; + + assigned-clocks = <&gcc GCC_PCIE_AUX_CLK>; + assigned-clock-rates = <19200000>; + + interconnects = <&system_noc MASTER_PCIE2_0 RPM_ALWAYS_TAG + &mc_virt SLAVE_EBI_CH0 RPM_ALWAYS_TAG>, + <&mem_noc MASTER_AMPSS_M0 RPM_ACTIVE_TAG + &config_noc SLAVE_PCIE2_0 RPM_ACTIVE_TAG>; + + interconnect-names = "pcie-mem", + "cpu-pcie"; + + iommu-map = <0x0 &apps_smmu 0x800 0x1>, + <0x100 &apps_smmu 0x801 0x1>; + + resets = <&gcc GCC_PCIE_BCR>; + reset-names = "pci"; + + power-domains = <&gcc GCC_PCIE_GDSC>; + + max-link-speed = <2>; + + operating-points-v2 = <&pcie_opp_table>; + + status = "disabled"; + + pcie_opp_table: opp-table { + compatible = "operating-points-v2"; + + /* GEN 1 x1 */ + opp-2500000 { + opp-hz = /bits/ 64 <2500000>; + required-opps = <&rpmpd_opp_nom>; + opp-peak-kBps = <250000 1>; + opp-level = <1>; + }; + + /* GEN 2 x1 */ + opp-5000000 { + opp-hz = /bits/ 64 <5000000>; + required-opps = <&rpmpd_opp_nom>; + opp-peak-kBps = <500000 1>; + opp-level = <2>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/pci/toshiba,tc9563.yaml b/Documentation/devicetree/bindings/pci/toshiba,tc9563.yaml index b3ad05d90201c..f9f71f28aa92b 100644 --- a/Documentation/devicetree/bindings/pci/toshiba,tc9563.yaml +++ b/Documentation/devicetree/bindings/pci/toshiba,tc9563.yaml @@ -26,6 +26,11 @@ properties: reg: maxItems: 1 + gpio-controller: true + + '#gpio-cells': + const: 2 + resx-gpios: maxItems: 1 description: @@ -69,6 +74,17 @@ $defs: type: object properties: + reset-gpios: + description: + Specify the TC9563 GPIO used to reset the endpoint + connected to the particular TC9563 downstream port. + + ep-pwr-en-gpios: + description: + Specify the TC9563 GPIO used for enabling power to + the endpoint connected to the particular TC9563 + downstream port. + toshiba,tx-amplitude-microvolt: description: Change Tx Margin setting for low power consumption. @@ -104,7 +120,7 @@ examples: #address-cells = <3>; #size-cells = <2>; - pcie@0 { + tc9563: pcie@0 { device_type = "pci"; reg = <0x0 0x0 0x0 0x0 0x0>; @@ -120,6 +136,7 @@ examples: device_type = "pci"; #address-cells = <3>; #size-cells = <2>; + #gpio-cells = <2>; ranges; bus-range = <0x02 0xff>; @@ -154,6 +171,9 @@ examples: device_type = "pci"; ranges; bus-range = <0x04 0xff>; + + ep-pwr-en-gpio = <&tc9563 2 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tc9563 5 GPIO_ACTIVE_LOW>; }; pcie@3,0 { diff --git a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml index 108cf9dc86ea0..b9b0fa26347bb 100644 --- a/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml +++ b/Documentation/devicetree/bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml @@ -34,6 +34,7 @@ properties: - qcom,sdm845-qmp-pcie-phy - qcom,sdx55-qmp-pcie-phy - qcom,sdx65-qmp-gen4x2-pcie-phy + - qcom,shikra-qmp-gen2x1-pcie-phy - qcom,sm8150-qmp-gen3x1-pcie-phy - qcom,sm8150-qmp-gen3x2-pcie-phy - qcom,sm8250-qmp-gen3x1-pcie-phy @@ -166,6 +167,7 @@ allOf: - qcom,sdm845-qhp-pcie-phy - qcom,sdm845-qmp-pcie-phy - qcom,sdx55-qmp-pcie-phy + - qcom,shikra-qmp-gen2x1-pcie-phy - qcom,sm8150-qmp-gen3x1-pcie-phy - qcom,sm8150-qmp-gen3x2-pcie-phy - qcom,sm8250-qmp-gen3x1-pcie-phy diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.yaml index e51acdcaafaf6..01250b556985b 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.yaml @@ -120,7 +120,9 @@ allOf: properties: qcom,glink-channels: items: - - const: adsp_apps + - enum: + - adsp_apps + - modem_apps power-domains: false else: properties: diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom-stats.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom-stats.yaml index 686a7ef2f48af..43751115c5b4e 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom-stats.yaml +++ b/Documentation/devicetree/bindings/soc/qcom/qcom-stats.yaml @@ -27,6 +27,7 @@ properties: - qcom,msm8226-rpm-stats - qcom,msm8916-rpm-stats - qcom,msm8974-rpm-stats + - qcom,shikra-rpm-stats reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-rx-macro.yaml b/Documentation/devicetree/bindings/sound/qcom,lpass-rx-macro.yaml index 4988e7ed6e34b..d2f68b02566b8 100644 --- a/Documentation/devicetree/bindings/sound/qcom,lpass-rx-macro.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-rx-macro.yaml @@ -19,6 +19,7 @@ properties: - qcom,sm8450-lpass-rx-macro - qcom,sm8550-lpass-rx-macro - qcom,sc8280xp-lpass-rx-macro + - qcom,shikra-lpass-rx-macro - items: - enum: - qcom,eliza-lpass-rx-macro diff --git a/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml b/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml index aea31fbdad376..23f00de82cd36 100644 --- a/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,lpass-va-macro.yaml @@ -19,6 +19,7 @@ properties: - qcom,sm8450-lpass-va-macro - qcom,sm8550-lpass-va-macro - qcom,sc8280xp-lpass-va-macro + - qcom,shikra-lpass-va-macro - items: - enum: - qcom,eliza-lpass-va-macro diff --git a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml index 9e5b30d9c6e62..d9a6bc4a14f28 100644 --- a/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,q6apm-dai.yaml @@ -10,7 +10,15 @@ maintainers: - Srinivas Kandagatla description: | - This binding describes the Qualcomm APM DAIs in DSP + This binding describes the Qualcomm APM DAIs in DSP. + + The DSP that runs the APM must be able to reach the PCM buffers. On + existing targets the DSP runs in a stage-1 context and the buffers are + described to the SMMU via iommus. On targets whose APM DSP runs in a + stage-2 protected context that cannot use the SMMU, the buffers are + reachable only after hypervisor (SCM) memory assignment to the fixed + set of consumer VMIDs that own that protected context, described by + qcom,vmids. The two access models are mutually exclusive. properties: compatible: @@ -20,9 +28,42 @@ properties: minItems: 1 maxItems: 2 + memory-region: + description: + Reserved-memory carveouts (shared-dma-pool, no-map) that hold the + PCM buffers and must be SCM-assigned to the VMIDs in qcom,vmids. + The first entry is the control-path buffer; the second entry is + the data-path buffer. Only used together with qcom,vmids. + minItems: 1 + maxItems: 2 + + qcom,vmids: + description: + Virtual Machine IDs (VMIDs) of the processors that consume the PCM + buffers and therefore must be granted access through SCM memory + assignment. Required on targets whose APM DSP runs in a stage-2 + protected context and cannot use the SMMU; mutually exclusive with + iommus. SCM assignment replaces the buffer's current owners with the + supplied set, so the driver always adds HLOS to the destination list + itself to retain host access; HLOS must not be listed here. All + listed VMIDs and HLOS receive read-write access. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 2 + uniqueItems: true + required: - compatible - - iommus + +oneOf: + - required: + - iommus + - required: + - qcom,vmids + +dependentRequired: + memory-region: + - qcom,vmids additionalProperties: false @@ -32,3 +73,30 @@ examples: compatible = "qcom,q6apm-dais"; iommus = <&apps_smmu 0x1801 0x0>; }; + - | + #include + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + audio_heap_mem: audio-heap@a1000000 { + compatible = "shared-dma-pool"; + reg = <0x0 0xa1000000 0x0 0x100000>; + no-map; + }; + + audio_mdsp_carveout_mem: audio-carveout@a1100000 { + compatible = "shared-dma-pool"; + reg = <0x0 0xa1100000 0x0 0x400000>; + no-map; + }; + }; + + dais { + compatible = "qcom,q6apm-dais"; + /* index 0: control path, index 1: data path */ + memory-region = <&audio_heap_mem>, <&audio_mdsp_carveout_mem>; + qcom,vmids = ; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,qaif-cpu.yaml b/Documentation/devicetree/bindings/sound/qcom,qaif-cpu.yaml new file mode 100644 index 0000000000000..1343561147338 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,qaif-cpu.yaml @@ -0,0 +1,307 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,qaif-cpu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Audio Interface (QAIF) CPU DAI Controller + +maintainers: + - Harendra Gautam + +description: | + The Qualcomm Audio Interface (QAIF) is a fully configurable DMA-based + audio subsystem controller. It serialises and deserialises PCM audio + between system memory and external serial audio peripherals (PCM, TDM, + I2S, MI2S) through the AIF path, and transfers parallel audio between + memory and an internal WCD codec through the CIF path. + + AIF (Audio Interface): up to 13 multi-lane Unified Audio Interfaces, + each supporting up to 8 independent data lanes. Each lane is individually + configurable as TX (output/speaker) or RX (input/mic). All lanes of an + interface share a single bit clock and frame sync. Supported modes are + PCM (short/long sync), TDM, and MI2S (stereo/mono). Per-interface + configuration includes sync source (master/slave), sync mode, sync delay, + sync inversion, slot width (8/16/24/32-bit), sample width, active slot + masks (up to 32 slots), bits-per-lane frame size, lane enable/direction + masks, loopback, output-enable control, and full-cycle path support for + long chip-to-chip connections. + + CIF (Codec Interface): up to 10 RDDMA (playback) and 10 WRDMA (capture) + channels connecting to an internal codec over a parallel bus. Each channel + supports active-channel enable mask (up to 16 channels), frame-sync + selection, frame-sync delay, frame-sync output gating, dynamic clock + gating, and 16-bit packing/unpacking. + + Note on RX/TX naming convention: in QAIF, TX refers to the output + (speaker/playback) direction and RX refers to the input (mic/capture) + direction. This applies to both AIF lane directions and CIF slot/mask + properties. + + DMA engine: RDDMA fetches audio from DDR/TCM/LPM into a shared SRAM + latency buffer (SHRAM) and drains it to the interface. WRDMA collects + data from the interface into SHRAM and writes it to memory. Each DMA + owns a private SHRAM region defined by start address and length registers. + Burst sizes of 1/2/4/8/16 beats (64-bit) are supported with up to 4 + outstanding transactions per DMA. Two QSB master ports (QXM0 for TCM, + QXM1 for DDR/LPM) provide the memory interface. + + Resources are partitioned among up to 5 Execution Engines (EEs) via + EE map registers. Each EE owns a set of DMAs, audio interfaces, and + interface groups, and receives its own independent interrupt output. + The interrupt hierarchy has a two-level structure: a summary register + identifies the event class (DMA period, underflow/overflow, error + response, audio interface underflow/overflow, group done, rate detector, + VFR), and per-resource status registers identify the specific channel. + + Interface grouping (bonding) allows up to 6 groups of audio and codec + interfaces to start synchronously and align their DMA period interrupts + within half a frame duration using the RDDMA padding feature. + + Two rate detector blocks measure the frequency of incoming frame sync or + word select signals and generate interrupts on rate change, undetected + rate, or sync timeout. + + Block diagram:: + + System Memory (DDR / LPM / TCM) + +---------------------------------+ + | Circular Buffers (ping-pong) | + +----------+----------+-----------+ + | ^ + 64-bit AXI 64-bit AXI + | | + +----------v----------+-----------+ + | QSB Master Ports | + | +----------+ +----------+ | + | | QXM0 | | QXM1 | | + | +----+-----+ +-----+----+ | + +-------|--------------|----------+ + | | + +-------v--------------v----------+ + | Shared RAM (SHRAM) | + | +------------+ +------------+ | + | | QXM0 Read | | QXM0 Write | | + | | SHRAM | | SHRAM | | + | +------------+ +------------+ | + | +------------+ +------------+ | + | | QXM1 Read | | QXM1 Write | | + | | SHRAM | | SHRAM | | + | +------------+ +------------+ | + +---+--------+--------+-------+---+ + | | | | + +---v--+ +--v---+ +--v---+ +-v----+ + |RDDMA | |RDDMA | |WRDMA | |WRDMA | + | AIF | | CIF | | AIF | | CIF | + |[0..n]| |[0..n]| |[0..n]| |[0..n]| + +--+---+ +--+---+ +--+---+ +-+----+ + | | ^ ^ + | TX | TX | RX | RX + v v | | + +--+--------------------+ +----+----------+ + | Unified Audio Intf | | Codec DMA | + | (AIF 0..12) | | Interface | + | | | (CIF) | + | AUD_INTFa block: | | | + | - Serializer (TX) | | RDDMA: DDR -> | + | - De-serializer (RX) | | internal | + | - Sync gen/detect | | codec | + | - Up to 8 data lanes | | WRDMA: codec | + | - PCM / TDM / MI2S | | -> DDR | + | - Near Pad Logic | | Up to 16 ch | + +--+--------------------+ +----+----------+ + | Lane 0..7 (TX/RX) | Parallel bus + | Bit clk + Frame sync | + Frame sync + v v + +--+--------+ +------+------+ + | External | | Internal | + | Serial | | Digital | + | Peripherals| | Codec | + | (PCM/TDM/ | | (Bolero/ | + | MI2S) | | WCD) | + +-----------+ +-------------+ + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: qcom,shikra-qaif-cpu + + reg: + maxItems: 1 + description: QAIF core registers + + interrupts: + maxItems: 1 + description: + QAIF summary interrupt for this Execution Engine. Signals DMA period + completion, buffer underflow/overflow, error response, audio interface + underflow/overflow, interface-group done, rate detector and VFR events. + + iommus: + maxItems: 1 + description: Phandle to apps_smmu node with sid mask + + dma-coherent: true + + clocks: + minItems: 5 + maxItems: 9 + + clock-names: + description: + The first five clocks are always required. Any further entries are the + per-AIF bit clocks (aif_if_ibit), supplied in order starting from + AIF interface 0 for as many serial AIF interfaces as the board uses. + minItems: 5 + items: + - const: lpass_config + - const: lpass_core_axim + - const: aud_dma + - const: aud_dma_mem + - const: bus + - const: aif_if0_ibit + - const: aif_if1_ibit + - const: aif_if2_ibit + - const: aif_if3_ibit + + '#sound-dai-cells': + const: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + +patternProperties: + '^aif@[0-9a-f]+$': + type: object + description: + QAIF AIF interface child node for PCM, TDM or MI2S configuration. + + properties: + reg: + maxItems: 1 + description: + DAI ID of the AIF interface (QAIF_MI2S_AIF* or QAIF_TDM_AIF*). + + dai-format: + description: Serial audio interface format (MI2S, TDM or PCM). + enum: [i2s, dsp_a, dsp_b] + + dai-tdm-slot-width: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [8, 16, 24, 32] + description: + Slot width in bits. + + dai-tdm-slot-num: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Number of slots per lane. + minimum: 1 + maximum: 32 + + dai-tdm-slot-rx-mask: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Active RX slots, one entry per slot. A nonzero value marks the + slot as active. + minItems: 1 + maxItems: 32 + + dai-tdm-slot-tx-mask: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Active TX slots, one entry per slot. A nonzero value marks the + slot as active. + minItems: 1 + maxItems: 32 + + qcom,qaif-aif-lane-map: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Per-lane direction map, one entry per lane starting from lane 0. + Use 0 for TX (output/speaker) and 1 for RX (input/mic). + minItems: 1 + maxItems: 8 + items: + enum: [0, 1] + + required: + - reg + - dai-format + - qcom,qaif-aif-lane-map + + if: + properties: + dai-format: + enum: [dsp_a, dsp_b] + then: + required: + - dai-tdm-slot-width + - dai-tdm-slot-num + - dai-tdm-slot-rx-mask + - dai-tdm-slot-tx-mask + + additionalProperties: false + +required: + - compatible + - reg + - interrupts + - iommus + - clocks + - clock-names + - '#sound-dai-cells' + - '#address-cells' + - '#size-cells' + +unevaluatedProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + qaif_cpu: audio-controller@a000000 { + compatible = "qcom,shikra-qaif-cpu"; + reg = <0x0 0x0a000000 0x0 0x20000>; + interrupts = <0 331 4>; + iommus = <&apps_smmu 0x1c0 0x0>; + clocks = <&gcc 213>, + <&gcc 214>, + <&audiocorecc 15>, + <&audiocorecc 17>, + <&audiocorecc 18>, + <&audiocorecc 5>, + <&audiocorecc 8>, + <&audiocorecc 11>, + <&audiocorecc 14>; + clock-names = "lpass_config", + "lpass_core_axim", + "aud_dma", + "aud_dma_mem", + "bus", + "aif_if0_ibit", + "aif_if1_ibit", + "aif_if2_ibit", + "aif_if3_ibit"; + #sound-dai-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + aif@d7 { + reg = <215>; + dai-format = "dsp_a"; + dai-tdm-slot-width = <32>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-rx-mask = <1 1>; + dai-tdm-slot-tx-mask = <1 1>; + qcom,qaif-aif-lane-map = <1 0>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml index dae440ecab590..1c220cffa9bb5 100644 --- a/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml +++ b/Documentation/devicetree/bindings/sound/qcom,sm8250.yaml @@ -46,6 +46,9 @@ properties: - qcom,sc7180-qdsp6-sndcard - qcom,sc8280xp-sndcard - qcom,sdm845-sndcard + - qcom,shikra-cqm-sndcard + - qcom,shikra-cqs-sndcard + - qcom,shikra-iqs-sndcard - qcom,sm8250-sndcard - qcom,sm8450-sndcard - qcom,x1e80100-sndcard @@ -91,6 +94,19 @@ patternProperties: sound-dai: maxItems: 1 + dai-tdm-slot-num: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Number of slots in use + + dai-tdm-slot-width: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Width, in bits, of each slot + + patternProperties: + '^dai-tdm-slot-[rt]x-mask$': + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Slot mask for active TDM slots + platform: description: Holds subnode which indicates platform dai. type: object @@ -110,6 +126,19 @@ patternProperties: minItems: 1 maxItems: 8 + dai-tdm-slot-num: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Number of slots in use + + dai-tdm-slot-width: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Width, in bits, of each slot + + patternProperties: + '^dai-tdm-slot-[rt]x-mask$': + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Slot mask for active TDM slots + required: - link-name - cpu diff --git a/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml b/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml new file mode 100644 index 0000000000000..00a6a05e94431 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,wsa8855.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/qcom,wsa8855.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm WSA8855 stereo smart speaker amplifier + +maintainers: + - Srinivas Kandagatla + - Prasad Kumpatla + +description: + WSA885X is a Qualcomm Aqstic stereo smart speaker amplifier. It uses a PCM + audio interface, an I2C control interface, and a Class-H amplifier path for + high efficiency, low output noise, and low idle power consumption. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: qcom,wsa8855 + + reg: + maxItems: 1 + + '#sound-dai-cells': + const: 0 + + powerdown-gpios: + description: GPIO controlling the SD_N powerdown pin. + maxItems: 1 + + reset-gpios: + description: GPIO controlling the SD_N powerdown pin. + maxItems: 1 + + interrupts: + maxItems: 1 + + vdd-1p8-supply: true + + vdd-io-supply: true + + qcom,battery-config: + description: + Battery topology connected to the speaker amplifier. 1S indicates one + battery cell in series, while 2S indicates two battery cells in series. + $ref: /schemas/types.yaml#/definitions/string + default: 1s + enum: + - 1s + - 2s + +required: + - compatible + - reg + - '#sound-dai-cells' + - interrupts + - vdd-1p8-supply + - vdd-io-supply + +oneOf: + - required: + - powerdown-gpios + - required: + - reset-gpios + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + speaker@c { + compatible = "qcom,wsa8855"; + reg = <0x0c>; + #sound-dai-cells = <0>; + powerdown-gpios = <&tlmm 11 GPIO_ACTIVE_LOW>; + interrupt-parent = <&tlmm>; + interrupts = <77 IRQ_TYPE_EDGE_FALLING>; + vdd-1p8-supply = <&vreg_l2g_1p8>; + vdd-io-supply = <&vreg_l1g_1p2>; + qcom,battery-config = "1s"; + }; + }; +... diff --git a/Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml b/Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml index 9447a2f371b56..5b06cc1a5f781 100644 --- a/Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml +++ b/Documentation/devicetree/bindings/soundwire/qcom,soundwire.yaml @@ -215,6 +215,12 @@ properties: maximum: 4 - const: 0xff + qcom,swr-master-ee-val: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: + Execution-environment value used to route SoundWire master + interrupts to CPU0 or CPU1. + label: maxItems: 1 diff --git a/arch/arm64/boot/dts/qcom/pm4125.dtsi b/arch/arm64/boot/dts/qcom/pm4125.dtsi index 3dc8d667d091c..77de9db8c1192 100644 --- a/arch/arm64/boot/dts/qcom/pm4125.dtsi +++ b/arch/arm64/boot/dts/qcom/pm4125.dtsi @@ -8,6 +8,35 @@ #include #include +/ { + thermal-zones { + pm4125-thermal { + polling-delay-passive = <100>; + thermal-sensors = <&pm4125_tz>; + + trips { + pm4125_trip0: trip0 { + temperature = <105000>; + hysteresis = <0>; + type = "passive"; + }; + + pm4125_trip1: trip1 { + temperature = <125000>; + hysteresis = <0>; + type = "hot"; + }; + + pm4125_trip2: trip2 { + temperature = <155000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + }; +}; + &spmi_bus { pmic@0 { compatible = "qcom,pm2250", "qcom,spmi-pmic"; @@ -65,6 +94,81 @@ status = "disabled"; }; + pm4125_tz: temp-alarm@2400 { + compatible = "qcom,spmi-temp-alarm"; + reg = <0x2400>; + interrupts = <0x0 0x24 0x0 IRQ_TYPE_EDGE_BOTH>; + io-channels = <&pm4125_adc ADC5_DIE_TEMP>; + io-channel-names = "thermal"; + #thermal-sensor-cells = <0>; + }; + + pm4125_adc: adc@3100 { + compatible = "qcom,spmi-adc5"; + reg = <0x3100>; + #address-cells = <1>; + #size-cells = <0>; + #io-channel-cells = <1>; + interrupts = <0x0 0x31 0x0 IRQ_TYPE_EDGE_RISING>; + + /* Channel nodes */ + channel@0 { + reg = ; + label = "ref_gnd"; + qcom,pre-scaling = <1 1>; + }; + + channel@1 { + reg = ; + label = "vref_1p25"; + qcom,pre-scaling = <1 1>; + }; + + channel@6 { + reg = ; + label = "die_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@7 { + reg = ; + label = "usb_in_i_uv"; + qcom,pre-scaling = <1 1>; + }; + + channel@8 { + reg = ; + label = "usb_in_v_div_16"; + qcom,pre-scaling = <1 16>; + }; + + channel@9 { + reg = ; + label = "chg_temp"; + qcom,pre-scaling = <1 1>; + }; + + channel@4b { + reg = ; + label = "bat_id"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; + + channel@83 { + reg = ; + label = "vph_pwr"; + qcom,pre-scaling = <1 3>; + }; + + channel@84 { + reg = ; + label = "vbat_sns"; + qcom,pre-scaling = <1 3>; + }; + }; + rtc@6000 { compatible = "qcom,pm8941-rtc"; reg = <0x6000>, <0x6100>; diff --git a/arch/arm64/boot/dts/qcom/pm8005.dtsi b/arch/arm64/boot/dts/qcom/pm8005.dtsi index 0f0ab2da83055..ce31a85450200 100644 --- a/arch/arm64/boot/dts/qcom/pm8005.dtsi +++ b/arch/arm64/boot/dts/qcom/pm8005.dtsi @@ -4,6 +4,34 @@ #include #include +/ { + thermal-zones { + pm8005-thermal { + thermal-sensors = <&pm8005_tz>; + + trips { + pm8005_trip0: trip0 { + temperature = <105000>; + hysteresis = <0>; + type = "passive"; + }; + + pm8005_trip1: trip1 { + temperature = <125000>; + hysteresis = <0>; + type = "hot"; + }; + + pm8005_trip2: trip2 { + temperature = <145000>; + hysteresis = <0>; + type = "critical"; + }; + }; + }; + }; +}; + &spmi_bus { pm8005_lsid0: pmic@4 { compatible = "qcom,pm8005", "qcom,spmi-pmic"; @@ -11,6 +39,13 @@ #address-cells = <1>; #size-cells = <0>; + pm8005_tz: temp-alarm@2400 { + compatible = "qcom,spmi-temp-alarm"; + reg = <0x2400>; + interrupts = <0x4 0x24 0x0 IRQ_TYPE_EDGE_BOTH>; + #thermal-sensor-cells = <0>; + }; + pm8005_gpios: gpio@c000 { compatible = "qcom,pm8005-gpio", "qcom,spmi-gpio"; reg = <0xc000>; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts index 73bee6bbc93d6..d0ad019ba2c7e 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-evk.dts @@ -5,8 +5,10 @@ /dts-v1/; +#include #include "shikra-cqm-som.dtsi" #include "shikra-evk.dtsi" +#include / { model = "Qualcomm Technologies, Inc. Shikra CQM EVK"; @@ -15,6 +17,7 @@ aliases { mmc0 = &sdhc_1; + mmc1 = &sdhc_2; /* SDC2 SD card slot */ serial0 = &uart0; }; @@ -31,6 +34,74 @@ pinctrl-names = "default"; }; + sound: sound { + compatible = "qcom,shikra-cqm-sndcard"; + model = "shikra-cqm-evk"; + + audio-routing = "IN1_HPHL", "HPHL_OUT", + "IN2_HPHR", "HPHR_OUT", + "AMIC2", "MIC BIAS2", + "VA DMIC0", "vdd-micb", + "VA DMIC1", "vdd-micb", + "VA DMIC2", "vdd-micb", + "VA DMIC3", "vdd-micb"; + + pinctrl-0 = <&i2s2_default>, <&dmic_eldo_en_default>; + pinctrl-names = "default"; + + headset-capture-dai-link { + link-name = "Headphones Capture"; + + codec { + sound-dai = <&pmic4125_codec 1>, + <&swr1 0>, + <&vamacro 0>; + }; + + cpu { + sound-dai = <&qaif_cpu QAIF_CDC_DMA_VA_TX0>; + }; + }; + + headset-playback-dai-link { + link-name = "Headphones Playback"; + + codec { + sound-dai = <&pmic4125_codec 0>, + <&swr0 0>, + <&rxmacro 0>; + }; + + cpu { + sound-dai = <&qaif_cpu QAIF_CDC_DMA_RX0>; + }; + }; + + wsa-speaker-dai-link { + link-name = "WSA Speaker Playback"; + + codec { + sound-dai = <&wsa885x_i2c>; + }; + + cpu { + sound-dai = <&qaif_cpu QAIF_TDM_AIF2>; + }; + }; + + va-dmic-dai-link { + link-name = "VA DMIC Capture"; + + codec { + sound-dai = <&vamacro 0>; + }; + + cpu { + sound-dai = <&qaif_cpu QAIF_CDC_DMA_VA_TX0>; + }; + }; + }; + vreg_disp_n: regulator-vreg-disp-n { compatible = "regulator-fixed"; regulator-name = "vreg_disp_n"; @@ -52,6 +123,31 @@ status = "okay"; }; +&gpr { + status = "disabled"; +}; + +&i2c3 { + status = "okay"; + + wsa885x_i2c: speaker@c { + compatible = "qcom,wsa8855"; + reg = <0x0c>; + + pinctrl-names = "default"; + pinctrl-0 = <&wsa885x_i2c_spkr_sd_n>; + + interrupts = <77 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&tlmm>; + powerdown-gpios = <&tlmm 2 GPIO_ACTIVE_LOW>; + vdd-1p8-supply = <&pm4125_l15>; + vdd-io-supply = <&pm4125_l15>; + qcom,battery-config = "1s"; + + #sound-dai-cells = <0>; + }; +}; + &mdss { status = "okay"; }; @@ -99,6 +195,27 @@ regulator-allow-set-load; }; +&pcie_phy { + vdda-phy-supply = <&pm4125_l13>; + vdda-pll-supply = <&pm4125_l9>; + + status = "okay"; +}; + +&qaif_cpu { + status = "okay"; + + aif@d7 { + reg = ; + dai-format = "dsp_a"; + dai-tdm-slot-width = <32>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-rx-mask = <1 1>; + dai-tdm-slot-tx-mask = <1 1>; + qcom,qaif-aif-lane-map = <1 0>; + }; +}; + &remoteproc_cdsp { firmware-name = "qcom/shikra/cdsp.mbn"; @@ -118,6 +235,10 @@ status = "okay"; }; +&rxmacro { + status = "okay"; +}; + &sdhc_1 { vmmc-supply = <&pm4125_l20>; vqmmc-supply = <&pm4125_l14>; @@ -134,7 +255,73 @@ status = "okay"; }; +&sdhc_2 { + vmmc-supply = <&pm4125_l21>; + vqmmc-supply = <&pm4125_l4>; + + status = "okay"; +}; + +&spmi_bus { + pmic@0 { + pmic4125_codec: audio-codec@f000 { + compatible = "qcom,pm4125-codec"; + reg = <0xf000>; + vdd-io-supply = <&pm4125_l15>; + vdd-cp-supply = <&pm4125_s1>; + vdd-pa-vpos-supply = <&pm4125_s1>; + + vdd-mic-bias-supply = <&pm4125_l22>; + qcom,micbias1-microvolt = <1800000>; + qcom,micbias2-microvolt = <1800000>; + qcom,micbias3-microvolt = <1800000>; + + qcom,rx-device = <&pm4125_rx>; + qcom,tx-device = <&pm4125_tx>; + #sound-dai-cells = <1>; + + status = "okay"; + }; + }; +}; + +&swr0 { + status = "okay"; + + pm4125_rx: codec@0,4 { + compatible = "sdw20217010c00"; + reg = <0 4>; + qcom,rx-port-mapping = <1 2 3 4 5>; + }; +}; + +&swr1 { + status = "okay"; + + pm4125_tx: codec@0,3 { + compatible = "sdw20217010c00"; + reg = <0 3>; + qcom,tx-port-mapping = <2 2 3 4>; + }; +}; + &tlmm { + dmic_eldo_en_default: dmic-eldo-default-active-state { + pins = "gpio71"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + output-high; + }; + + i2s2_default: i2s2-default-active-state { + pins = "gpio100", "gpio101", "gpio102", "gpio103"; + function = "i2s2"; + drive-strength = <8>; + output-high; + bias-disable; + }; + panel_rst_n: panel-rst-n-state { pins = "gpio3"; function = "gpio"; @@ -178,6 +365,23 @@ drive-strength = <2>; bias-disable; }; + + wsa885x_i2c_spkr_sd_n: wsa885x-i2c-spkr-sd-n-active-state { + pins = "gpio2"; + function = "gpio"; + input-disable; + output-enable; + }; +}; + +&spi5 { + status = "okay"; + + tpm@0 { + compatible = "st,st33htpm-spi", "tcg,tpm_tis-spi"; + reg = <0>; + spi-max-frequency = <20000000>; + }; }; &usb_1_hsphy { @@ -202,3 +406,11 @@ status = "okay"; }; + +&vamacro { + pinctrl-0 = <&dmic01_default>, <&dmic23_default>, <&tx_swr_active>; + pinctrl-names = "default"; + + qcom,dmic-sample-rate = <4800000>; + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi b/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi index 8ac42ff625a0e..f71c32b1bc73a 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-cqm-som.dtsi @@ -4,6 +4,7 @@ */ #include +#include #include #include "shikra.dtsi" @@ -32,6 +33,68 @@ }; }; + msm_therm_bridge: msm-therm-bridge { + compatible = "generic-adc-thermal"; + io-channels = <&pm4125_adc ADC5_AMUX_THM3_100K_PU>; + io-channel-names = "sensor-channel"; + #thermal-sensor-cells = <0>; + }; + + pa_therm_bridge: pa-therm-bridge { + compatible = "generic-adc-thermal"; + io-channels = <&pm4125_adc ADC5_AMUX_THM1_100K_PU>; + io-channel-names = "sensor-channel"; + #thermal-sensor-cells = <0>; + }; + + quiet_therm_bridge: quiet-therm-bridge { + compatible = "generic-adc-thermal"; + io-channels = <&pm4125_adc ADC5_AMUX_THM2_100K_PU>; + io-channel-names = "sensor-channel"; + #thermal-sensor-cells = <0>; + }; + + thermal-zones { + sys-1-thermal { + polling-delay-passive = <2000>; + thermal-sensors = <&pa_therm_bridge>; + + trips { + active-config0 { + temperature = <80000>; + hysteresis = <2000>; + type = "hot"; + }; + }; + }; + + sys-2-thermal { + polling-delay-passive = <2000>; + thermal-sensors = <&quiet_therm_bridge>; + + trips { + active-config0 { + temperature = <80000>; + hysteresis = <2000>; + type = "hot"; + }; + }; + }; + + sys-3-thermal { + polling-delay-passive = <2000>; + thermal-sensors = <&msm_therm_bridge>; + + trips { + active-config0 { + temperature = <80000>; + hysteresis = <2000>; + type = "hot"; + }; + }; + }; + }; + wcn3988-pmu { compatible = "qcom,wcn3988-pmu"; @@ -69,7 +132,64 @@ }; }; +&pm4125_adc { + pinctrl-0 = <&pm4125_adc_gpio5_default>, <&pm4125_adc_gpio6_default>; + pinctrl-names = "default"; + + channel@4d { + reg = ; + label = "pa_therm"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; + + channel@4e { + reg = ; + label = "quiet_therm"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; + + channel@4f { + reg = ; + label = "msm_therm"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; + + channel@54 { + reg = ; + label = "chgr_skin"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; + + channel@55 { + reg = ; + label = "gnss_therm"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; +}; + &pm4125_gpios { + pm4125_adc_gpio5_default: pm4125-adc-gpio5-state { + pins = "gpio5"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-high-impedance; + }; + + pm4125_adc_gpio6_default: pm4125-adc-gpio6-state { + pins = "gpio6"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-high-impedance; + }; + vol_up_n: vol-up-n-state { pins = "gpio9"; function = PMIC_GPIO_FUNC_NORMAL; @@ -93,6 +213,11 @@ regulators { compatible = "qcom,rpm-pm2250-regulators"; + pm4125_s1: s1 { + regulator-min-microvolt = <1396000>; + regulator-max-microvolt = <1396000>; + }; + pm4125_s2: s2 { regulator-min-microvolt = <1000000>; regulator-max-microvolt = <1200000>; @@ -197,7 +322,6 @@ &tlmm { gpio-reserved-ranges = <6 4>, /* Fingerprint SPI */ - <14 4>, /* eSE SPI */ <30 2>, /* NFC SPI */ <138 1>, /* NFC Secure IO */ <155 11>; /* eMMC Boot */ diff --git a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts index 90af44c08b939..8b8e9e5c17d04 100644 --- a/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-cqs-evk.dts @@ -7,6 +7,7 @@ #include "shikra-cqm-som.dtsi" #include "shikra-evk.dtsi" +#include / { model = "Qualcomm Technologies, Inc. Shikra CQS EVK"; @@ -15,6 +16,7 @@ aliases { mmc0 = &sdhc_1; + mmc1 = &sdhc_2; /* SDC2 SD card slot */ serial0 = &uart0; }; @@ -31,6 +33,57 @@ pinctrl-names = "default"; }; + sound { + compatible = "qcom,shikra-cqs-sndcard"; + model = "shikra-cqs-evk"; + + pinctrl-0 = <&i2s2_default>, <&dmic_eldo_en_default>; + pinctrl-names = "default"; + + audio-routing = "VA DMIC0", "vdd-micb", + "VA DMIC1", "vdd-micb", + "VA DMIC2", "vdd-micb", + "VA DMIC3", "vdd-micb"; + + va-dmic-dai-link { + link-name = "VA DMIC Capture"; + + codec { + sound-dai = <&vamacro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai VA_CODEC_DMA_TX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + wsa-playback-dai-link { + link-name = "WSA Playback"; + + codec { + sound-dai = <&wsa885x_i2c>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + dai-tdm-slot-rx-mask = <1 1>; + }; + + cpu { + sound-dai = <&q6apmbedai SECONDARY_TDM_RX_0>; + dai-tdm-slot-num = <2>; + dai-tdm-slot-width = <32>; + dai-tdm-slot-rx-mask = <1 1>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + }; + vreg_disp_n: regulator-vreg-disp-n { compatible = "regulator-fixed"; regulator-name = "vreg_disp_n"; @@ -48,6 +101,27 @@ }; }; +&i2c3 { + status = "okay"; + + wsa885x_i2c: speaker@c { + compatible = "qcom,wsa8855"; + reg = <0x0c>; + + pinctrl-names = "default"; + pinctrl-0 = <&wsa885x_i2c_spkr_sd_n>; + + interrupts = <77 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&tlmm>; + powerdown-gpios = <&tlmm 2 GPIO_ACTIVE_LOW>; + vdd-1p8-supply = <&pm4125_l15>; + vdd-io-supply = <&pm4125_l15>; + qcom,battery-config = "1s"; + + #sound-dai-cells = <0>; + }; +}; + &mdss { status = "okay"; }; @@ -95,6 +169,24 @@ regulator-allow-set-load; }; +&pcie_phy { + vdda-phy-supply = <&pm4125_l13>; + vdda-pll-supply = <&pm4125_l9>; + + status = "okay"; +}; + +&q6apmbedai { + #address-cells = <1>; + #size-cells = <0>; + dai@40 { + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_QAIF_IF2_IBIT + LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "bclk"; + }; +}; + &remoteproc_cdsp { firmware-name = "qcom/shikra/cdsp.mbn"; @@ -130,7 +222,30 @@ status = "okay"; }; +&sdhc_2 { + vmmc-supply = <&pm4125_l21>; + vqmmc-supply = <&pm4125_l4>; + + status = "okay"; +}; + &tlmm { + dmic_eldo_en_default: dmic-eldo-default-active-state { + pins = "gpio71"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + output-high; + }; + + i2s2_default: i2s2-default-active-state { + pins = "gpio100", "gpio101", "gpio102", "gpio103"; + function = "i2s2"; + drive-strength = <8>; + output-high; + bias-disable; + }; + panel_rst_n: panel-rst-n-state { pins = "gpio3"; function = "gpio"; @@ -174,6 +289,23 @@ drive-strength = <2>; bias-disable; }; + + wsa885x_i2c_spkr_sd_n: wsa885x-i2c-spkr-sd-n-active-state { + pins = "gpio2"; + function = "gpio"; + input-disable; + output-enable; + }; +}; + +&spi5 { + status = "okay"; + + tpm@0 { + compatible = "st,st33htpm-spi", "tcg,tpm_tis-spi"; + reg = <0>; + spi-max-frequency = <20000000>; + }; }; &usb_1_hsphy { @@ -198,3 +330,16 @@ status = "okay"; }; + +&vamacro { + pinctrl-0 = <&dmic01_default>, <&dmic23_default>, <&tx_swr_active>; + pinctrl-names = "default"; + + clocks = <&q6prmcc LPASS_CLK_ID_TX_CORE_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_TX_CORE_NPL_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "mclk", + "npl"; + + qcom,dmic-sample-rate = <4800000>; + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/qcom/shikra-evk.dtsi b/arch/arm64/boot/dts/qcom/shikra-evk.dtsi index 7cdbd257e9c97..2a8d199f300e9 100644 --- a/arch/arm64/boot/dts/qcom/shikra-evk.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-evk.dtsi @@ -3,6 +3,32 @@ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ +/ { + aliases { + i2c3 = &i2c3; + }; + + vreg_0p9: regulator-0v9 { + compatible = "regulator-fixed"; + regulator-name = "VREG_0P9"; + + regulator-min-microvolt = <900000>; + regulator-max-microvolt = <900000>; + regulator-always-on; + regulator-boot-on; + }; + + vreg_1p8: regulator-1v8 { + compatible = "regulator-fixed"; + regulator-name = "VREG_1P8"; + + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + regulator-boot-on; + }; +}; + &gpu { status = "okay"; }; @@ -11,12 +37,157 @@ firmware-name = "qcom/shikra/a704_zap.mbn"; }; +&i2c3 { + status = "okay"; +}; + +&iris { + firmware-name = "qcom/vpu/ar50lt_p1_gen2_s6.mbn"; + + status = "okay"; +}; + +&pcie { + wake-gpios = <&tlmm 119 GPIO_ACTIVE_LOW>; + + iommu-map = <0x0 &apps_smmu 0x0800 0x1>, + <0x100 &apps_smmu 0x0801 0x1>, + <0x208 &apps_smmu 0x0802 0x1>, + <0x210 &apps_smmu 0x0803 0x1>, + <0x218 &apps_smmu 0x0804 0x1>, + <0x300 &apps_smmu 0x0805 0x1>, + <0x400 &apps_smmu 0x0806 0x1>, + <0x500 &apps_smmu 0x0807 0x1>, + <0x501 &apps_smmu 0x0808 0x1>; + + pinctrl-0 = <&pcie_default_state>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie_port0 { + + tc9563: pcie@0,0 { + compatible = "pci1179,0623"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + #gpio-cells = <2>; + gpio-controller; + + device_type = "pci"; + ranges; + bus-range = <0x2 0xff>; + + vddc-supply = <&vreg_0p9>; + vdd18-supply = <&vreg_1p8>; + vdd09-supply = <&vreg_0p9>; + vddio1-supply = <&vreg_1p8>; + vddio2-supply = <&vreg_1p8>; + vddio18-supply = <&vreg_1p8>; + + i2c-parent = <&i2c3 0x77>; + + resx-gpios = <&tlmm 118 GPIO_ACTIVE_LOW>; + + pinctrl-0 = <&tc9563_resx_n>; + pinctrl-names = "default"; + + pcie@1,0 { + reg = <0x20800 0x0 0x0 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + + device_type = "pci"; + ranges; + bus-range = <0x3 0xff>; + + ep-pwr-en-gpios = <&tc9563 2 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tc9563 5 GPIO_ACTIVE_LOW>; + }; + + pcie@2,0 { + reg = <0x21000 0x0 0x0 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + + device_type = "pci"; + ranges; + bus-range = <0x4 0xff>; + + ep-pwr-en-gpios = <&tc9563 4 GPIO_ACTIVE_HIGH>; + reset-gpios = <&tc9563 3 GPIO_ACTIVE_LOW>; + }; + + pcie@3,0 { + reg = <0x21800 0x0 0x0 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + device_type = "pci"; + ranges; + bus-range = <0x5 0xff>; + + pci@0,0 { + reg = <0x50000 0x0 0x0 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + device_type = "pci"; + ranges; + }; + + pci@0,1 { + reg = <0x50100 0x0 0x0 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + device_type = "pci"; + ranges; + }; + }; + }; +}; + &qupv3_0 { firmware-name = "qcom/shikra/qupv3fw.elf"; status = "okay"; }; +&sdhc_2 { + pinctrl-0 = <&sdc2_default &sdc2_card_det_n>; + pinctrl-1 = <&sdc2_sleep &sdc2_card_det_n>; + pinctrl-names = "default", "sleep"; + + no-sdio; + no-mmc; + + cd-gpios = <&tlmm 89 GPIO_ACTIVE_LOW>; +}; + +&tlmm { + pcie_default_state:pcie-default-state{ + clkreq-pins{ + pins="gpio117"; + function="pcie0_clk_req_n"; + drive-strength=<2>; + bias-pull-up; + }; + + wake-pins{ + pins="gpio119"; + function="gpio"; + drive-strength=<2>; + bias-pull-up; + }; + }; + + tc9563_resx_n: tc9563-resx-state { + pins = "gpio118"; + function = "gpio"; + bias-disable; + }; +}; + &uart0 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts index 543d769125ef3..713006e0492c0 100644 --- a/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts +++ b/arch/arm64/boot/dts/qcom/shikra-iqs-evk.dts @@ -7,6 +7,8 @@ #include "shikra-iqs-som.dtsi" #include "shikra-evk.dtsi" +#include +#include / { model = "Qualcomm Technologies, Inc. Shikra IQS EVK"; @@ -15,12 +17,219 @@ aliases { mmc0 = &sdhc_1; + mmc1 = &sdhc_2; /* SDC2 SD card slot */ serial0 = &uart0; }; chosen { stdout-path = "serial0:115200n8"; }; + + hdmi-connector { + compatible = "hdmi-connector"; + type = "a"; + + port { + hdmi_con: endpoint { + remote-endpoint = <<9611_out>; + }; + }; + }; + + sound { + compatible = "qcom,shikra-iqs-sndcard"; + model = "shikra-iqs-evk"; + + pinctrl-0 = <&i2s0_default>, <&ext_mclk2_active>; + pinctrl-names = "default"; + + audio-routing = "IN34", "Headset Mic", + "Headset Mic", "MICBIAS", + "DMICL", "Int Mic", + "Int Mic", "MICBIAS", + "Headphone", "HPL", + "Headphone", "HPR", + "Speaker", "SPKL", + "Speaker", "SPKR", + "VA DMIC0", "vdd-micb", + "VA DMIC1", "vdd-micb", + "VA DMIC2", "vdd-micb", + "VA DMIC3", "vdd-micb"; + + pri-i2s-playback-dai-link { + link-name = "Analog Playback"; + + codec { + sound-dai = <&max98091>; + }; + + cpu { + sound-dai = <&q6apmbedai PRIMARY_MI2S_RX>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + pri-i2s-capture-dai-link { + link-name = "Analog Capture"; + + codec { + sound-dai = <&max98091>; + }; + + cpu { + sound-dai = <&q6apmbedai PRIMARY_MI2S_TX>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + + va-dmic-dai-link { + link-name = "VA DMIC Capture"; + + codec { + sound-dai = <&vamacro 0>; + }; + + cpu { + sound-dai = <&q6apmbedai VA_CODEC_DMA_TX_0>; + }; + + platform { + sound-dai = <&q6apm>; + }; + }; + }; + + vreg_lt9611_vcc: regulator-lt9611-vcc { + compatible = "regulator-fixed"; + regulator-name = "lt9611_vcc"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpio = <&pm8150_gpios 4 GPIO_ACTIVE_HIGH>; + enable-active-high; + pinctrl-0 = <&hdmi_reg_en>; + pinctrl-names = "default"; + }; + + vreg_lt9611_vdd: regulator-lt9611-vdd { + compatible = "regulator-fixed"; + regulator-name = "lt9611_vdd"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; +}; + +&i2c3 { + status = "okay"; + + max98091: audio-codec@10 { + compatible = "maxim,max98091"; + reg = <0x10>; + + pinctrl-0 = <&max98091_default>; + pinctrl-names = "default"; + + interrupts-extended = <&tlmm 28 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&q6prmcc LPASS_CLK_ID_MCLK_2 LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "mclk"; + assigned-clocks = <&q6prmcc LPASS_CLK_ID_MCLK_2 LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + assigned-clock-rates = <12288000>; + #sound-dai-cells = <0>; + }; +}; + +&i2c4 { + status = "okay"; + + lt9611uxd: lt9611uxd@41 { + compatible = "lontium,lt9611uxd"; + reg = <0x41>; + interrupts-extended = <&tlmm 85 IRQ_TYPE_EDGE_FALLING>; + reset-gpios = <&tlmm 76 GPIO_ACTIVE_LOW>; + vcc-supply = <&vreg_lt9611_vcc>; + vdd-supply = <&vreg_lt9611_vdd>; + + pinctrl-0 = <<9611_irq_pin <9611_rst_pin>; + pinctrl-names = "default"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + lt9611_a: endpoint { + remote-endpoint = <&mdss_dsi0_out>; + }; + }; + + port@2 { + reg = <2>; + + lt9611_out: endpoint { + remote-endpoint = <&hdmi_con>; + }; + }; + }; + }; +}; + +&mdss { + status = "okay"; +}; + +&mdss_dsi0 { + vdda-supply = <&pm8150_l11>; + + status = "okay"; +}; + +&mdss_dsi0_out { + remote-endpoint = <<9611_a>; + data-lanes = <0 1 2 3>; +}; + +&mdss_dsi0_phy { + status = "okay"; +}; + +&pcie_phy { + vdda-phy-supply = <&pm8150_l12>; + vdda-pll-supply = <&pm8150_l9>; + + status = "okay"; +}; + +&pm8150_gpios { + hdmi_reg_en: hdmi-reg-en-state { + pins = "gpio4"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-disable; + }; +}; + +&pm8150_l11 { + regulator-min-microvolt = <1232000>; + regulator-max-microvolt = <1232000>; + regulator-allow-set-load; +}; + +&q6apmbedai { + #address-cells = <1>; + #size-cells = <0>; + + dai@16 { + reg = ; + clocks = <&q6prmcc LPASS_CLK_ID_QAIF_IF0_IBIT LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "bclk"; + }; }; &remoteproc_cdsp { @@ -58,6 +267,67 @@ status = "okay"; }; +&sdhc_2 { + vmmc-supply = <&pm8150_l10>; + vqmmc-supply = <&pm8150_l2>; + + status = "okay"; +}; + +&tlmm { + dmic_eldo_en_default: dmic-eldo-default-active-state { + pins = "gpio71"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + output-high; + }; + + ext_mclk2_active: ext-mclk2-state { + pins = "gpio110"; + function = "ext_mclk"; + drive-strength = <8>; + bias-disable; + }; + + i2s0_default: i2s0-default-active-state { + pins = "gpio105", "gpio106", "gpio107", "gpio108", "gpio109"; + function = "i2s0"; + drive-strength = <8>; + bias-disable; + }; + + lt9611_irq_pin: lt9611-irq-state { + pins = "gpio85"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + + lt9611_rst_pin: lt9611-rst-state { + pins = "gpio76"; + function = "gpio"; + drive-strength = <8>; + bias-disable; + }; + + max98091_default: max98091-default-state { + pins = "gpio28"; + function = "gpio"; + bias-pull-up; + }; +}; + +&spi5 { + status = "okay"; + + tpm@0 { + compatible = "st,st33htpm-spi", "tcg,tpm_tis-spi"; + reg = <0>; + spi-max-frequency = <20000000>; + }; +}; + &usb_1_hsphy { vdd-supply = <&pm8150_l4>; vdda-pll-supply = <&pm8150_l12>; @@ -80,3 +350,17 @@ status = "okay"; }; + +&vamacro { + clocks = <&q6prmcc LPASS_CLK_ID_TX_CORE_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>, + <&q6prmcc LPASS_CLK_ID_TX_CORE_NPL_MCLK LPASS_CLK_ATTRIBUTE_COUPLE_NO>; + clock-names = "mclk", + "npl"; + + pinctrl-0 = <&dmic01_default>, <&dmic23_default>, <&dmic_eldo_en_default>; + pinctrl-names = "default"; + + qcom,dmic-sample-rate = <4800000>; + + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/qcom/shikra-iqs-som.dtsi b/arch/arm64/boot/dts/qcom/shikra-iqs-som.dtsi index 4ff97945274dd..a6769364c1921 100644 --- a/arch/arm64/boot/dts/qcom/shikra-iqs-som.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra-iqs-som.dtsi @@ -4,6 +4,7 @@ */ #include +#include #include #include "shikra.dtsi" @@ -31,6 +32,44 @@ }; }; + thermal-zones { + sys-1-thermal { + thermal-sensors = <&pm8150_adc_tm 0>; + + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + + sys-2-thermal { + thermal-sensors = <&pm8150_adc_tm 1>; + + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + + sys-3-thermal { + thermal-sensors = <&pm8150_adc_tm 2>; + + trips { + active-config0 { + temperature = <125000>; + hysteresis = <1000>; + type = "passive"; + }; + }; + }; + }; + vreg_wcn_3p3: regulator-wcn-3p3 { compatible = "regulator-fixed"; regulator-name = "wcn_3p3"; @@ -76,7 +115,81 @@ }; }; +&pm8150_adc { + pinctrl-0 = <&pm8150_adc_gpio2_default>, <&pm8150_adc_gpio3_default>; + pinctrl-names = "default"; + + channel@4d { + reg = ; + label = "msm_therm"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; + + channel@4e { + reg = ; + label = "quiet_therm"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; + + channel@52 { + reg = ; + label = "gnss_therm"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; + + channel@55 { + reg = ; + label = "pa_therm"; + qcom,ratiometric; + qcom,hw-settle-time = <200>; + qcom,pre-scaling = <1 1>; + }; +}; + +&pm8150_adc_tm { + status = "okay"; + + pa-therm@0 { + reg = <0>; + io-channels = <&pm8150_adc ADC5_GPIO4_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + + quiet-therm@1 { + reg = <1>; + io-channels = <&pm8150_adc ADC5_AMUX_THM2_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; + + msm-therm@2 { + reg = <2>; + io-channels = <&pm8150_adc ADC5_AMUX_THM1_100K_PU>; + qcom,ratiometric; + qcom,hw-settle-time-us = <200>; + }; +}; + &pm8150_gpios { + pm8150_adc_gpio2_default: pm8150-adc-gpio2-state { + pins = "gpio2"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-high-impedance; + }; + + pm8150_adc_gpio3_default: pm8150-adc-gpio3-state { + pins = "gpio3"; + function = PMIC_GPIO_FUNC_NORMAL; + bias-high-impedance; + }; + vol_up_n: vol-up-n-state { pins = "gpio6"; function = PMIC_GPIO_FUNC_NORMAL; @@ -219,7 +332,6 @@ &tlmm { gpio-reserved-ranges = <6 4>, /* Fingerprint SPI */ - <14 4>, /* eSE SPI */ <30 2>, /* NFC SPI */ <138 1>, /* NFC Secure IO */ <155 11>; /* eMMC Boot */ diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index fbbc6b8fe78bf..89766aa775f26 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,8 @@ #include #include #include +#include +#include #include / { @@ -328,6 +331,53 @@ }; }; }; + + sdc2_default: sdc2-default-state { + clk-pins { + pins = "sdc2_clk"; + drive-strength = <14>; + bias-disable; + }; + + cmd-pins { + pins = "sdc2_cmd"; + drive-strength = <14>; + bias-pull-up; + }; + + data-pins { + pins = "sdc2_data"; + drive-strength = <14>; + bias-pull-up; + }; + }; + + sdc2_sleep: sdc2-sleep-state { + clk-pins { + pins = "sdc2_clk"; + drive-strength = <2>; + bias-disable; + }; + + cmd-pins { + pins = "sdc2_cmd"; + drive-strength = <2>; + bias-pull-up; + }; + + data-pins { + pins = "sdc2_data"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + sdc2_card_det_n: sd-card-det-n-state { + pins = "gpio89"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; }; mpm: interrupt-controller { @@ -379,7 +429,14 @@ }; audio_heap_mem: audio-heap@86200000 { - reg = <0x0 0x86200000 0x0 0x100000>; + compatible = "shared-dma-pool"; + reg = <0x0 0x86200000 0x0 0x40000>; + no-map; + }; + + audio_mdsp_carveout_mem: audio-mdsp-carveout@86240000 { + compatible = "shared-dma-pool"; + reg = <0x0 0x86240000 0x0 0x100000>; no-map; }; @@ -432,6 +489,16 @@ reg = <0x0 0xb4702000 0x0 0x40000>; no-map; }; + + /* + * The Iris VPU reserves IOVA below 0x25800000 (600MB), + * DMA into that range triggers unhandled SMMU faults and + * spontaneous reboots, so reserve it to keep IOMMU + * allocation above this boundary. + */ + iris_iova: iris-iova { + iommu-addresses = <&iris 0x0 0x0 0x0 0x25800000>; + }; }; smp2p-cdsp { @@ -858,6 +925,70 @@ bias-disable; }; + dmic01_default: dmic01-default-state { + clk-pins { + pins = "gpio96"; + function = "dmic"; + drive-strength = <8>; + output-high; + }; + + data-pins { + pins = "gpio97"; + function = "dmic"; + drive-strength = <8>; + bias-disable; + }; + }; + + dmic23_default: dmic23-default-state { + clk-pins { + pins = "gpio98"; + function = "dmic"; + drive-strength = <8>; + output-high; + }; + + data-pins { + pins = "gpio99"; + function = "dmic"; + drive-strength = <8>; + bias-disable; + }; + }; + + tx_swr_active: tx-swr-active-state { + clk-pins { + pins = "gpio105"; + function = "swr0_tx"; + drive-strength = <8>; + bias-disable; + }; + + data-pins { + pins = "gpio106"; + function = "swr0_tx"; + drive-strength = <8>; + bias-bus-hold; + }; + }; + + rx_swr_active: rx-swr-active-state { + clk-pins { + pins = "gpio107"; + function = "swr0_rx"; + drive-strength = <8>; + bias-disable; + }; + + data-pins { + pins = "gpio108", "gpio109"; + function = "swr0_rx"; + drive-strength = <8>; + bias-bus-hold; + }; + }; + sdc1_state_on: sdc1-on-state { clk-pins { pins = "sdc1_clk"; @@ -1104,6 +1235,41 @@ #interconnect-cells = <2>; }; + cryptobam: dma-controller@1b04000 { + compatible = "qcom,bam-v1.7.4", "qcom,bam-v1.7.0"; + reg = <0x0 0x01b04000 0x0 0x24000>; + interrupts = ; + #dma-cells = <1>; + iommus = <&apps_smmu 0x84 0x0011>, + <&apps_smmu 0x86 0x0011>, + <&apps_smmu 0x92 0x0>, + <&apps_smmu 0x94 0x0011>, + <&apps_smmu 0x96 0x0011>, + <&apps_smmu 0x98 0x0001>, + <&apps_smmu 0x9f 0x0>; + qcom,ee = <0>; + qcom,controlled-remotely; + num-channels = <16>; + qcom,num-ees = <4>; + }; + + crypto: crypto@1b3a000 { + compatible = "qcom,shikra-qce", "qcom,sm8150-qce", "qcom,qce"; + reg = <0x0 0x01b3a000 0x0 0x6000>; + dmas = <&cryptobam 4>, <&cryptobam 5>; + dma-names = "rx", "tx"; + iommus = <&apps_smmu 0x84 0x0011>, + <&apps_smmu 0x86 0x0011>, + <&apps_smmu 0x92 0x0>, + <&apps_smmu 0x94 0x0011>, + <&apps_smmu 0x96 0x0011>, + <&apps_smmu 0x98 0x0001>, + <&apps_smmu 0x9f 0x0>; + interconnects = <&system_noc MASTER_CRYPTO_CORE0 0 + &mc_virt SLAVE_EBI_CH0 0>; + interconnect-names = "memory"; + }; + qfprom: efuse@1b44000 { compatible = "qcom,shikra-qfprom", "qcom,qfprom"; reg = <0x0 0x01b44000 0x0 0x3000>; @@ -1138,7 +1304,7 @@ "obsrvr", "intr", "cnfg"; - interrupts-extended = <&mpm 86 IRQ_TYPE_EDGE_RISING>; + interrupts-extended = <&mpm 86 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "periph_irq"; interrupt-controller; #interrupt-cells = <4>; @@ -1160,6 +1326,165 @@ #thermal-sensor-cells = <1>; }; + rng: rng@4454000 { + compatible = "qcom,shikra-trng", "qcom,trng"; + reg = <0x0 0x04454000 0x0 0x1000>; + }; + + pcie: pcie@45e8000 { + device_type = "pci"; + compatible = "qcom,shikra-pcie"; + reg = <0x0 0x045e8000 0x0 0x3000>, + <0x0 0x60000000 0x0 0xf1d>, + <0x0 0x60000f20 0x0 0xa8>, + <0x0 0x60001000 0x0 0x1000>, + <0x0 0x60100000 0x0 0x100000>, + <0x0 0x045eb000 0x0 0x1000>; + reg-names = "parf", + "dbi", + "elbi", + "atu", + "config", + "mhi"; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x01000000 0x0 0x00000000 0x0 0x60200000 0x0 0x100000>, + <0x02000000 0x0 0x60300000 0x0 0x60300000 0x0 0x3d00000>, + <0x03000000 0x4 0x00000000 0x4 0x00000000 0x3 0x0000000>; + bus-range = <0x00 0xff>; + + linux,pci-domain = <0>; + num-lanes = <1>; + + interrupts = , + , + , + , + , + , + , + , + ; + interrupt-names = "msi0", + "msi1", + "msi2", + "msi3", + "msi4", + "msi5", + "msi6", + "msi7", + "global"; + + interrupt-map = <0 0 0 1 &intc 0 0 0 499 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 2 &intc 0 0 0 500 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 3 &intc 0 0 0 501 IRQ_TYPE_LEVEL_HIGH>, + <0 0 0 4 &intc 0 0 0 502 IRQ_TYPE_LEVEL_HIGH>; + interrupt-map-mask = <0 0 0 0x7>; + #interrupt-cells = <1>; + + clocks = <&gcc GCC_PCIE_AUX_CLK>, + <&gcc GCC_PCIE_CFG_AHB_CLK>, + <&gcc GCC_PCIE_MSTR_AXI_CLK>, + <&gcc GCC_PCIE_SLV_AXI_CLK>, + <&gcc GCC_PCIE_SLV_Q2A_AXI_CLK>, + <&gcc GCC_DDRSS_MEMNOC_PCIE_SF_CLK>, + <&gcc GCC_PCIE_TILE_AXI_SYS_NOC_CLK>, + <&gcc GCC_QMIP_PCIE_CFG_AHB_CLK>; + clock-names = "aux", + "cfg", + "bus_master", + "bus_slave", + "slave_q2a", + "ddrss_memnoc_pcie", + "tile", + "qmip_pcie_ahb"; + + assigned-clocks = <&gcc GCC_PCIE_AUX_CLK>; + assigned-clock-rates = <19200000>; + + interconnects = <&system_noc MASTER_PCIE2_0 RPM_ALWAYS_TAG + &mc_virt SLAVE_EBI_CH0 RPM_ALWAYS_TAG>, + <&mem_noc MASTER_AMPSS_M0 RPM_ACTIVE_TAG + &config_noc SLAVE_PCIE2_0 RPM_ACTIVE_TAG>; + + interconnect-names = "pcie-mem", + "cpu-pcie"; + + iommu-map = <0x0 &apps_smmu 0x800 0x1>, + <0x100 &apps_smmu 0x801 0x1>; + + resets = <&gcc GCC_PCIE_BCR>; + reset-names = "pci"; + + power-domains = <&gcc GCC_PCIE_GDSC>; + + max-link-speed = <2>; + + operating-points-v2 = <&pcie_opp_table>; + + status = "disabled"; + + pcie_opp_table: opp-table { + compatible = "operating-points-v2"; + + /* GEN 1 x1 */ + opp-2500000 { + opp-hz = /bits/ 64 <2500000>; + required-opps = <&rpmpd_opp_nom>; + opp-peak-kBps = <250000 1>; + opp-level = <1>; + }; + + /* GEN 2 x1 */ + opp-5000000 { + opp-hz = /bits/ 64 <5000000>; + required-opps = <&rpmpd_opp_nom>; + opp-peak-kBps = <500000 1>; + opp-level = <2>; + }; + }; + + pcie_port0: pcie@0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + #address-cells = <3>; + #size-cells = <2>; + ranges; + bus-range = <0x01 0x8>; + + phys = <&pcie_phy>; + }; + }; + + pcie_phy: phy@45ee000 { + compatible = "qcom,shikra-qmp-gen2x1-pcie-phy"; + reg = <0x0 0x045ee000 0x0 0x1000>; + + clocks = <&gcc GCC_PCIE_AUX_CLK>, + <&gcc GCC_PCIE_CFG_AHB_CLK>, + <&gcc GCC_PCIE_CLKREF_EN>, + <&gcc GCC_PCIE_RCHNG_PHY_CLK>, + <&gcc GCC_PCIE_PIPE_CLK>; + clock-names = "aux", + "cfg_ahb", + "ref", + "refgen", + "pipe"; + + resets = <&gcc GCC_PCIE_PHY_BCR>; + reset-names = "phy"; + + assigned-clocks = <&gcc GCC_PCIE_RCHNG_PHY_CLK>; + assigned-clock-rates = <100000000>; + + #clock-cells = <0>; + clock-output-names = "pcie_pipe_clk"; + + #phy-cells = <0>; + + status = "disabled"; + }; + rpm_msg_ram: sram@45f0000 { compatible = "qcom,rpm-msg-ram", "mmio-sram"; reg = <0x0 0x045f0000 0x0 0x7000>; @@ -1174,7 +1499,7 @@ }; sram@4690000 { - compatible = "qcom,rpm-stats"; + compatible = "qcom,shikra-rpm-stats", "qcom,rpm-stats"; reg = <0x0 0x04690000 0x0 0x14000>; }; @@ -1243,6 +1568,17 @@ }; }; + sdhc_ice: crypto@4748000 { + compatible = "qcom,shikra-inline-crypto-engine", + "qcom,inline-crypto-engine"; + reg = <0x0 0x04748000 0x0 0x18000>; + clocks = <&gcc GCC_SDCC1_ICE_CORE_CLK>, + <&gcc GCC_SDCC1_AHB_CLK>; + clock-names = "core", + "iface"; + power-domains = <&rpmpd RPMHPD_CX>; + }; + gpi_dma0: dma-controller@4a00000 { compatible = "qcom,shikra-gpi-dma", "qcom,sm6350-gpi-dma"; reg = <0x0 0x04a00000 0x0 0x60000>; @@ -1271,6 +1607,56 @@ iommus = <&apps_smmu 0xf6 0x0>; }; + sdhc_2: mmc@4784000 { + compatible = "qcom,shikra-sdhci", "qcom,sdhci-msm-v5"; + reg = <0x0 0x04784000 0x0 0x1000>; + + interrupts = , + ; + interrupt-names = "hc_irq", + "pwr_irq"; + + bus-width = <4>; + + clocks = <&gcc GCC_SDCC2_AHB_CLK>, + <&gcc GCC_SDCC2_APPS_CLK>, + <&rpmcc RPM_SMD_XO_CLK_SRC>; + clock-names = "iface", + "core", + "xo"; + + qcom,dll-config = <0x0007442c>; + qcom,ddr-config = <0x80040868>; + + iommus = <&apps_smmu 0x0a0 0x0>; + + interconnects = <&system_noc MASTER_SDCC_2 RPM_ALWAYS_TAG + &mc_virt SLAVE_EBI_CH0 RPM_ALWAYS_TAG>, + <&mem_noc MASTER_AMPSS_M0 RPM_ACTIVE_TAG + &config_noc SLAVE_SDCC_2 RPM_ACTIVE_TAG>; + interconnect-names = "sdhc-ddr", + "cpu-sdhc"; + + power-domains = <&rpmpd RPMPD_VDDCX>; + operating-points-v2 = <&sdhc2_opp_table>; + + status = "disabled"; + + sdhc2_opp_table: opp-table-2 { + compatible = "operating-points-v2"; + + opp-100000000 { + opp-hz = /bits/ 64 <100000000>; + required-opps = <&rpmpd_opp_low_svs>; + }; + + opp-202000000 { + opp-hz = /bits/ 64 <202000000>; + required-opps = <&rpmpd_opp_svs_plus>; + }; + }; + }; + qupv3_0: geniqup@4ac0000 { compatible = "qcom,geni-se-qup"; reg = <0x0 0x04ac0000 0x0 0x2000>; @@ -2264,6 +2650,67 @@ power-domains = <&gpucc GPU_CX_GDSC>; }; + iris: video-codec@5a00000 { + compatible = "qcom,shikra-venus", "qcom,qcm2290-venus"; + reg = <0x0 0x05a00000 0x0 0x200000>; + interrupts = ; + + power-domains = <&gcc GCC_VENUS_GDSC>, + <&gcc GCC_VCODEC0_GDSC>, + <&rpmpd QCM2290_VDDCX>; + power-domain-names = "venus", + "vcodec0", + "cx"; + operating-points-v2 = <&venus_opp_table>; + + clocks = <&gcc GCC_VIDEO_VENUS_CTL_CLK>, + <&gcc GCC_VIDEO_AHB_CLK>, + <&gcc GCC_VENUS_CTL_AXI_CLK>, + <&gcc GCC_VIDEO_THROTTLE_CORE_CLK>, + <&gcc GCC_VIDEO_VCODEC0_SYS_CLK>, + <&gcc GCC_VCODEC0_AXI_CLK>; + clock-names = "core", + "iface", + "bus", + "throttle", + "vcodec0_core", + "vcodec0_bus"; + + memory-region = <&video_mem>, <&iris_iova>; + interconnects = <&mmnrt_virt MASTER_VIDEO_P0 RPM_ALWAYS_TAG + &mc_virt SLAVE_EBI_CH0 RPM_ALWAYS_TAG>, + <&mem_noc MASTER_AMPSS_M0 RPM_ACTIVE_TAG + &config_noc SLAVE_VENUS_CFG RPM_ACTIVE_TAG>; + interconnect-names = "video-mem", + "cpu-cfg"; + + iommus = <&apps_smmu 0x780 0x20>; + + venus_opp_table: opp-table { + compatible = "operating-points-v2"; + + opp-133333333 { + opp-hz = /bits/ 64 <133333333>; + required-opps = <&rpmpd_opp_low_svs>; + }; + + opp-240000000 { + opp-hz = /bits/ 64 <240000000>; + required-opps = <&rpmpd_opp_svs>; + }; + + opp-300000000 { + opp-hz = /bits/ 64 <300000000>; + required-opps = <&rpmpd_opp_svs_plus>; + }; + + opp-384000000 { + opp-hz = /bits/ 64 <384000000>; + required-opps = <&rpmpd_opp_nom>; + }; + }; + }; + camss: camss@5c11000 { compatible = "qcom,shikra-camss", "qcom,qcm2290-camss"; @@ -2602,6 +3049,7 @@ }; }; + dispcc: clock-controller@5f00000 { compatible = "qcom,shikra-dispcc", "qcom,qcm2290-dispcc"; reg = <0x0 0x05f00000 0x0 0x20000>; @@ -2660,9 +3108,188 @@ mboxes = <&apcs_glb 12>; qcom,remote-pid = <1>; label = "mpss"; + + gpr: gpr { + compatible = "qcom,gpr"; + qcom,glink-channels = "modem_apps"; + qcom,domain = ; + qcom,intents = <200 20>; + #address-cells = <1>; + #size-cells = <0>; + + q6apm: service@1 { + compatible = "qcom,q6apm"; + reg = ; + #sound-dai-cells = <0>; + + q6apmbedai: bedais { + compatible = "qcom,q6apm-lpass-dais"; + #sound-dai-cells = <1>; + }; + + q6apmdai: dais { + compatible = "qcom,q6apm-dais"; + memory-region = <&audio_heap_mem>, + <&audio_mdsp_carveout_mem>; + qcom,vmid = ; + }; + }; + + q6prm: service@2 { + compatible = "qcom,q6prm"; + reg = ; + + q6prmcc: clock-controller { + compatible = "qcom,q6prm-lpass-clocks"; + #clock-cells = <2>; + }; + }; + }; }; }; + qaif_cpu: audio@a000000 { + compatible = "qcom,shikra-qaif-cpu"; + reg = <0x0 0x0a000000 0x0 0x20000>; + + interrupts = ; + + clocks = <&gcc GCC_LPASS_CONFIG_CLK>, + <&gcc GCC_LPASS_CORE_AXIM_CLK>, + <&audiocorecc AUDIO_CORE_CC_AUD_DMA_CLK>, + <&audiocorecc AUDIO_CORE_CC_AUD_DMA_MEM_CLK>, + <&audiocorecc AUDIO_CORE_CC_BUS_CLK>, + <&audiocorecc AUDIO_CORE_CC_AIF_IF0_IBIT_CLK>, + <&audiocorecc AUDIO_CORE_CC_AIF_IF1_IBIT_CLK>, + <&audiocorecc AUDIO_CORE_CC_AIF_IF2_IBIT_CLK>, + <&audiocorecc AUDIO_CORE_CC_AIF_IF3_IBIT_CLK>; + clock-names = "lpass_config", + "lpass_core_axim", + "aud_dma", + "aud_dma_mem", + "bus", + "aif_if0_ibit", + "aif_if1_ibit", + "aif_if2_ibit", + "aif_if3_ibit"; + + iommus = <&apps_smmu 0x1c0 0x0>; + + #sound-dai-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + + status = "disabled"; + }; + + rxmacro: codec@a040000 { + compatible = "qcom,shikra-lpass-rx-macro"; + reg = <0x0 0x0a040000 0x0 0x1000>; + + pinctrl-0 = <&rx_swr_active>; + pinctrl-names = "default"; + + clocks = <&audiocorecc AUDIO_CORE_CC_RX_MCLK_CLK>, + <&audiocorecc AUDIO_CORE_CC_RX_MCLK_2X_CLK>, + <&vamacro>; + clock-names = "mclk", + "npl", + "fsgen"; + + #clock-cells = <0>; + clock-output-names = "mclk"; + #sound-dai-cells = <1>; + status = "disabled"; + }; + + swr0: soundwire@a060000 { + compatible = "qcom,soundwire-v3.1.0"; + reg = <0x0 0x0a060000 0x0 0x10000>; + qcom,swr-master-ee-val = <0>; + + interrupts = ; + + clocks = <&rxmacro>; + clock-names = "iface"; + + label = "RX"; + qcom,din-ports = <0>; + qcom,dout-ports = <5>; + + resets = <&audiocore_csr AUDIO_CORE_CSR_RX_SWR_CGCR>; + reset-names = "swr_audio_cgcr"; + + qcom,ports-sinterval-low = /bits/ 8 <0x03 0x1f 0x1f 0x07 0x00>; + qcom,ports-offset1 = /bits/ 8 <0x00 0x00 0x0b 0x01 0x00>; + qcom,ports-offset2 = /bits/ 8 <0x00 0x00 0x0b 0x00 0x00>; + qcom,ports-hstart = /bits/ 8 <0xff 0x03 0xff 0xff 0xff>; + qcom,ports-hstop = /bits/ 8 <0xff 0x06 0xff 0xff 0xff>; + qcom,ports-word-length = /bits/ 8 <0x01 0x07 0x04 0xff 0xff>; + qcom,ports-block-pack-mode = /bits/ 8 <0xff 0x00 0x01 0xff 0xff>; + qcom,ports-block-group-count = /bits/ 8 <0xff 0xff 0xff 0xff 0x00>; + qcom,ports-lane-control = /bits/ 8 <0x01 0x00 0x00 0x00 0x00>; + + #sound-dai-cells = <1>; + #address-cells = <2>; + #size-cells = <0>; + status = "disabled"; + }; + + vamacro: codec@a078000 { + compatible = "qcom,shikra-lpass-va-macro"; + reg = <0x0 0x0a078000 0x0 0x2000>; + + pinctrl-0 = <&tx_swr_active>; + pinctrl-names = "default"; + + clocks = <&audiocorecc AUDIO_CORE_CC_TX_MCLK_CLK>, + <&audiocorecc AUDIO_CORE_CC_TX_MCLK_2X_CLK>; + clock-names = "mclk", + "npl"; + + #clock-cells = <0>; + #sound-dai-cells = <1>; + clock-output-names = "fsgen"; + status = "disabled"; + }; + + swr1: soundwire@a080000 { + compatible = "qcom,soundwire-v3.1.0"; + reg = <0x0 0x0a080000 0x0 0x10000>; + qcom,swr-master-ee-val = <0>; + + interrupts = , + ; + interrupt-names = "core", "wakeup"; + + clocks = <&vamacro>; + clock-names = "iface"; + + label = "VA_TX"; + + qcom,din-ports = <4>; + qcom,dout-ports = <0>; + + resets = <&audiocore_csr AUDIO_CORE_CSR_TX_SWR_CGCR>; + reset-names = "swr_audio_cgcr"; + + qcom,ports-sinterval-low = /bits/ 8 <0x01 0x01 0x03 0x03>; + qcom,ports-offset1 = /bits/ 8 <0x00 0x00 0x01 0x01>; + qcom,ports-offset2 = /bits/ 8 <0x00 0x00 0x00 0x00>; + qcom,ports-hstart = /bits/ 8 <0xff 0xff 0xff 0xff>; + qcom,ports-hstop = /bits/ 8 <0xff 0xff 0xff 0xff>; + qcom,ports-word-length = /bits/ 8 <0xff 0xff 0xff 0xff>; + qcom,ports-block-pack-mode = /bits/ 8 <0xff 0xff 0xff 0xff>; + qcom,ports-block-group-count = /bits/ 8 <0xff 0xff 0xff 0xff>; + qcom,ports-lane-control = /bits/ 8 <0x01 0x02 0x00 0x00>; + + #sound-dai-cells = <1>; + #address-cells = <2>; + #size-cells = <0>; + status = "disabled"; + }; + audiocorecc: clock-controller@a0a0000 { compatible = "qcom,shikra-audiocorecc"; reg = <0x0 0x0a0a0000 0x0 0x10000>; diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 06d2b3deeae61..b669fb7087de1 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -521,6 +521,7 @@ CONFIG_WLCORE_SDIO=m CONFIG_WWAN=m CONFIG_MHI_WWAN_CTRL=m CONFIG_MHI_WWAN_MBIM=m +CONFIG_QCOM_BAM_DMUX=m CONFIG_INPUT_EVDEV=y CONFIG_KEYBOARD_ADC=m CONFIG_KEYBOARD_GPIO=y @@ -962,6 +963,7 @@ CONFIG_DRM_I2C_NXP_TDA998X=m CONFIG_DRM_ITE_IT6263=m CONFIG_DRM_LONTIUM_LT8912B=m CONFIG_DRM_LONTIUM_LT9611=m +CONFIG_DRM_LONTIUM_LT9611C=m CONFIG_DRM_LONTIUM_LT9611UXC=m CONFIG_DRM_LONTIUM_LT8713SX=m CONFIG_DRM_ITE_IT66121=m @@ -1099,6 +1101,7 @@ CONFIG_SND_SOC_QCOM=m CONFIG_SND_SOC_APQ8016_SBC=m CONFIG_SND_SOC_QDSP6_USB=m CONFIG_SND_SOC_MSM8996=m +CONFIG_SND_SOC_QCOM_QAIF=m CONFIG_SND_SOC_SDM845=m CONFIG_SND_SOC_SM8250=m CONFIG_SND_SOC_SC8280XP=m @@ -1153,6 +1156,7 @@ CONFIG_SND_SOC_GTM601=m CONFIG_SND_SOC_MAX98090=m CONFIG_SND_SOC_MSM8916_WCD_ANALOG=m CONFIG_SND_SOC_MSM8916_WCD_DIGITAL=m +CONFIG_SND_SOC_PM4125_SDW=m CONFIG_SND_SOC_RK3308=m CONFIG_SND_SOC_RK817=m CONFIG_SND_SOC_RT5640=m @@ -1175,6 +1179,7 @@ CONFIG_SND_SOC_WM8978=m CONFIG_SND_SOC_WSA881X=m CONFIG_SND_SOC_WSA883X=m CONFIG_SND_SOC_WSA884X=m +CONFIG_SND_SOC_WSA885X=m CONFIG_SND_SOC_NAU8822=m CONFIG_SND_SOC_LPASS_WSA_MACRO=m CONFIG_SND_SOC_LPASS_VA_MACRO=m diff --git a/drivers/clk/qcom/gcc-qcm2290.c b/drivers/clk/qcom/gcc-qcm2290.c index c6de8d368ec4c..636c420daea94 100644 --- a/drivers/clk/qcom/gcc-qcm2290.c +++ b/drivers/clk/qcom/gcc-qcm2290.c @@ -116,7 +116,7 @@ static const struct alpha_pll_config gpll10_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055B, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll10 = { @@ -148,7 +148,7 @@ static const struct alpha_pll_config gpll11_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055B, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll11 = { @@ -309,7 +309,7 @@ static const struct alpha_pll_config gpll8_config = { .post_div_val = 0x1 << 8, .post_div_mask = GENMASK(11, 8), .config_ctl_val = 0x4001055B, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll8 = { diff --git a/drivers/clk/qcom/gcc-shikra.c b/drivers/clk/qcom/gcc-shikra.c index d5222756f214c..787741a47a860 100644 --- a/drivers/clk/qcom/gcc-shikra.c +++ b/drivers/clk/qcom/gcc-shikra.c @@ -123,7 +123,7 @@ static const struct alpha_pll_config gpll10_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll10 = { @@ -156,7 +156,7 @@ static const struct alpha_pll_config gpll11_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll11 = { @@ -361,7 +361,7 @@ static const struct alpha_pll_config gpll8_config = { .post_div_val = BIT(8), .post_div_mask = GENMASK(11, 8), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, }; static struct clk_alpha_pll gpll8 = { @@ -1244,6 +1244,8 @@ static struct clk_rcg2 gcc_emac0_ptp_clk_src = { }; static const struct freq_tbl ftbl_gcc_emac0_rgmii_clk_src[] = { + F(2500000, P_GPLL0_OUT_AUX2, 10, 1, 12), + F(25000000, P_GPLL0_OUT_AUX2, 12, 0, 0), F(50000000, P_GPLL0_OUT_AUX2, 6, 0, 0), F(125000000, P_GPLL12_OUT_AUX2, 4, 0, 0), F(250000000, P_GPLL12_OUT_EARLY, 4, 0, 0), diff --git a/drivers/clk/qcom/gcc-sm6115.c b/drivers/clk/qcom/gcc-sm6115.c index 4c3804701e243..4bd2ebc158946 100644 --- a/drivers/clk/qcom/gcc-sm6115.c +++ b/drivers/clk/qcom/gcc-sm6115.c @@ -120,7 +120,7 @@ static const struct alpha_pll_config gpll10_config = { .vco_mask = GENMASK(21, 20), .main_output_mask = BIT(0), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, .test_ctl_hi_mask = 0x1, }; @@ -173,7 +173,7 @@ static const struct alpha_pll_config gpll11_config = { .vco_val = 0x2 << 20, .vco_mask = GENMASK(21, 20), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, .test_ctl_hi_mask = 0x1, }; @@ -367,7 +367,7 @@ static const struct alpha_pll_config gpll8_config = { .post_div_val = 0x1 << 8, .post_div_mask = GENMASK(11, 8), .config_ctl_val = 0x4001055b, - .test_ctl_hi1_val = 0x1, + .test_ctl_hi_val = 0x1, .test_ctl_hi_mask = 0x1, }; diff --git a/drivers/dma/qcom/Kconfig b/drivers/dma/qcom/Kconfig index c71b0b5d536bd..8f20b33bce84d 100644 --- a/drivers/dma/qcom/Kconfig +++ b/drivers/dma/qcom/Kconfig @@ -15,6 +15,7 @@ config QCOM_BAM_DMA depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM) select DMA_ENGINE select DMA_VIRTUAL_CHANNELS + select QCOM_SCM help Enable support for the QCOM BAM DMA controller. This controller provides DMA capabilities for a variety of on-chip devices. diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c index 1bb26af0405f3..0b12e8e894eda 100644 --- a/drivers/dma/qcom/bam_dma.c +++ b/drivers/dma/qcom/bam_dma.c @@ -42,6 +42,7 @@ #include #include #include +#include #include "../dmaengine.h" #include "../virt-dma.h" @@ -389,6 +390,9 @@ struct bam_chan { struct bam_desc_hw *fifo_virt; dma_addr_t fifo_phys; + /* SCM source-VMID bitmask of the FIFO, 0 if not SCM-assigned */ + u64 fifo_src_perms; + /* fifo markers */ unsigned short head; /* start of active descriptor entries */ unsigned short tail; /* end of active descriptor entries */ @@ -423,6 +427,10 @@ struct bam_device { const struct reg_offset_data *layout; + /* destination VMIDs for SCM assignment of descriptor FIFOs */ + u32 *vmids; + int num_vmids; + struct clk *bamclk; int irq; @@ -559,6 +567,126 @@ static void bam_chan_init_hw(struct bam_chan *bchan, bchan->tail = 0; } +/** + * bam_parse_vmids - Parse the optional qcom,vmid property + * @bdev: bam device + * + * Reads the list of destination VMIDs from qcom,vmid, if present. HLOS is + * always the source owner and must not be listed. Absent property leaves + * num_vmids 0. + */ +static int bam_parse_vmids(struct bam_device *bdev) +{ + struct device *dev = bdev->dev; + int n, i, ret; + + n = of_property_count_u32_elems(dev->of_node, "qcom,vmid"); + if (n == -EINVAL) + return 0; + + if (n < 0) + return n; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + bdev->vmids = devm_kcalloc(dev, n, sizeof(*bdev->vmids), GFP_KERNEL); + if (!bdev->vmids) + return -ENOMEM; + + ret = of_property_read_u32_array(dev->of_node, "qcom,vmid", + bdev->vmids, n); + if (ret) + return ret; + + for (i = 0; i < n; i++) { + if (bdev->vmids[i] == QCOM_SCM_VMID_HLOS) { + dev_err(dev, "qcom,vmid must not include HLOS (%u)\n", + QCOM_SCM_VMID_HLOS); + return -EINVAL; + } + } + + bdev->num_vmids = n; + + return 0; +} + +/** + * bam_assign_fifo - SCM-assign a channel's descriptor FIFO to the remote VMIDs + * @bdev: bam device + * @bchan: bam channel + * + * Grants HLOS plus the configured qcom,vmid VMIDs access to the FIFO, so + * the remote EE can read it. The updated source-VMID bitmask is stored in + * bchan->fifo_src_perms for bam_fifo_can_free() to reverse. + */ +static int bam_assign_fifo(struct bam_device *bdev, struct bam_chan *bchan) +{ + struct qcom_scm_vmperm *dst __free(kfree) = NULL; + u64 src = BIT_ULL(QCOM_SCM_VMID_HLOS); + int i, ret; + + if (!bdev->num_vmids) + return 0; + + dst = kcalloc(bdev->num_vmids + 1, sizeof(*dst), GFP_KERNEL); + if (!dst) + return -ENOMEM; + + dst[0].vmid = QCOM_SCM_VMID_HLOS; + dst[0].perm = QCOM_SCM_PERM_RW; + + for (i = 0; i < bdev->num_vmids; i++) { + dst[i + 1].vmid = bdev->vmids[i]; + dst[i + 1].perm = QCOM_SCM_PERM_RW; + } + + ret = qcom_scm_assign_mem(bchan->fifo_phys, BAM_DESC_FIFO_SIZE, + &src, dst, bdev->num_vmids + 1); + if (ret) { + dev_err(bdev->dev, "SCM assign fifo chan %u failed: %d\n", + bchan->id, ret); + return ret; + } + + bchan->fifo_src_perms = src; + + return 0; +} + +/** + * bam_fifo_can_free - Reclaim a channel's descriptor FIFO to HLOS + * @bdev: bam device + * @bchan: bam channel + * + * Returns true if the FIFO may now be freed. On SCM failure the remote VMID + * still has access, so the caller must leak the buffer instead of freeing it. + */ +static bool bam_fifo_can_free(struct bam_device *bdev, struct bam_chan *bchan) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + int ret; + + if (!bchan->fifo_src_perms) + return true; + + ret = qcom_scm_assign_mem(bchan->fifo_phys, BAM_DESC_FIFO_SIZE, + &bchan->fifo_src_perms, &hlos, 1); + if (ret) { + dev_err(bdev->dev, "SCM reclaim fifo chan %u failed: %d; leaking\n", + bchan->id, ret); + return false; + } + + bchan->fifo_src_perms = 0; + + return true; +} + /** * bam_alloc_chan - Allocate channel resources for DMA channel. * @chan: specified channel @@ -570,16 +698,29 @@ static int bam_alloc_chan(struct dma_chan *chan) struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; - if (bchan->fifo_virt) - return 0; - - /* allocate FIFO descriptor space, but only if necessary */ - bchan->fifo_virt = dma_alloc_wc(bdev->dev, BAM_DESC_FIFO_SIZE, - &bchan->fifo_phys, GFP_KERNEL); - + /* + * Remote-owned BAMs keep the FIFO allocated and SCM-assigned to the + * remote VMID across power cycles (see bam_free_chan), so allocate and + * assign it only once; the block and pipe are still re-initialised on + * every power-on below. + */ if (!bchan->fifo_virt) { - dev_err(bdev->dev, "Failed to allocate desc fifo\n"); - return -ENOMEM; + /* allocate FIFO descriptor space, but only if necessary */ + bchan->fifo_virt = dma_alloc_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + &bchan->fifo_phys, GFP_KERNEL); + if (!bchan->fifo_virt) { + dev_err(bdev->dev, "Failed to allocate desc fifo\n"); + return -ENOMEM; + } + + if (bam_assign_fifo(bdev, bchan)) { + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bchan->fifo_virt, bchan->fifo_phys); + bchan->fifo_virt = NULL; + return -EIO; + } + } else if (!bdev->num_vmids) { + return 0; } if (bdev->active_channels++ == 0 && bdev->powered_remotely) @@ -613,12 +754,29 @@ static void bam_free_chan(struct dma_chan *chan) goto err; } + /* + * Remote-owned BAMs (qcom,vmid) keep the descriptor FIFO allocated and + * SCM-assigned across power cycles: the remote may already have cut + * power, so pipe-register access would fault, and TZ still holds the + * grant for the next restart (the FIFO is reclaimed and freed once in + * bam_dma_remove). Only drop local channel state here so the block and + * pipe are re-initialised on the next power-on; skip all MMIO. + */ + if (bdev->num_vmids) { + scoped_guard(spinlock_irqsave, &bchan->vc.lock) + bchan->initialized = 0; + bdev->active_channels--; + goto err; + } + scoped_guard(spinlock_irqsave, &bchan->vc.lock) bam_reset_channel(bchan); - dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, bchan->fifo_virt, - bchan->fifo_phys); + if (bam_fifo_can_free(bdev, bchan)) + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bchan->fifo_virt, bchan->fifo_phys); bchan->fifo_virt = NULL; + bdev->active_channels--; /* mask irq for pipe/channel */ val = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE)); @@ -628,7 +786,7 @@ static void bam_free_chan(struct dma_chan *chan) /* disable irq */ writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_IRQ_EN)); - if (--bdev->active_channels == 0 && bdev->powered_remotely) { + if (bdev->active_channels == 0 && bdev->powered_remotely) { /* s/w reset bam */ val = readl_relaxed(bam_addr(bdev, 0, BAM_CTRL)); val |= BAM_SW_RST; @@ -767,7 +925,9 @@ static int bam_dma_terminate_all(struct dma_chan *chan) if (!list_empty(&bchan->desc_list)) { async_desc = list_first_entry(&bchan->desc_list, struct bam_async_desc, desc_node); - bam_chan_init_hw(bchan, async_desc->dir); + /* Remote-owned BAM: pipe reset may fault, skip it. */ + if (!bchan->bdev->num_vmids) + bam_chan_init_hw(bchan, async_desc->dir); } list_for_each_entry_safe(async_desc, tmp, @@ -1282,6 +1442,10 @@ static int bam_dma_probe(struct platform_device *pdev) bdev->powered_remotely = of_property_read_bool(pdev->dev.of_node, "qcom,powered-remotely"); + ret = bam_parse_vmids(bdev); + if (ret) + return ret; + if (bdev->controlled_remotely || bdev->powered_remotely) bdev->bamclk = devm_clk_get_optional(bdev->dev, "bam_clk"); else @@ -1333,7 +1497,7 @@ static int bam_dma_probe(struct platform_device *pdev) bam_channel_init(bdev, &bdev->channels[i], i); ret = devm_request_irq(bdev->dev, bdev->irq, bam_dma_irq, - IRQF_TRIGGER_HIGH, "bam_dma", bdev); + IRQF_TRIGGER_NONE, "bam_dma", bdev); if (ret) goto err_bam_channel_exit; @@ -1418,9 +1582,10 @@ static void bam_dma_remove(struct platform_device *pdev) if (!bdev->channels[i].fifo_virt) continue; - dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, - bdev->channels[i].fifo_virt, - bdev->channels[i].fifo_phys); + if (bam_fifo_can_free(bdev, &bdev->channels[i])) + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bdev->channels[i].fifo_virt, + bdev->channels[i].fifo_phys); } tasklet_kill(&bdev->task); diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 4a57d49b4c6d3..d1597548e5787 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -177,6 +177,24 @@ config DRM_LONTIUM_LT9611 HDMI signals Please say Y if you have such hardware. +config DRM_LONTIUM_LT9611C + tristate "Lontium LT9611C DSI/HDMI bridge" + select SND_SOC_HDMI_CODEC if SND_SOC + depends on OF && I2C + select CRC8 + select FW_LOADER + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_HDMI_STATE_HELPER + select REGMAP_I2C + help + Driver for the Lontium LT9611C(EX/UXD) DSI to HDMI bridge chip. + It converts single or dual MIPI DSI and I2S signals to HDMI + output. Supports HDMI 1.4 (LT9611C/EX) and HDMI 2.0 (LT9611UXD). + Say Y if you have hardware using this chip. + config DRM_LONTIUM_LT9611UXC tristate "Lontium LT9611UXC DSI/HDMI bridge" select SND_SOC_HDMI_CODEC if SND_SOC diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index 15cc821d85b7e..f322f3f89f6bf 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o +obj-$(CONFIG_DRM_LONTIUM_LT9611C) += lontium-lt9611c.o obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o diff --git a/drivers/gpu/drm/bridge/lontium-lt9611c.c b/drivers/gpu/drm/bridge/lontium-lt9611c.c new file mode 100644 index 0000000000000..e6887f2c89a6c --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt9611c.c @@ -0,0 +1,1283 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Lontium Semiconductor, Inc. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FW_SIZE (64 * 1024) +#define LT_PAGE_SIZE 256 +#define FW_FILE "lt9611c_fw.bin" +#define LT9611C_CRC_POLYNOMIAL 0x31 +#define LT9611C_PAGE_CONTROL 0xff +#define LT9611C_INFOFRAME_MAX_SIZE 32 +#define LT9611C_CMD_HDR_SIZE 4 +#define LT9611C_CMD_Y0_SIZE 1 /* Y0 echo byte in ACK response */ +#define LT9611C_EDID_BUF_SIZE 32 + +struct lt9611c_cmd_hdr { + u8 func; + u8 type; + u8 seq; + u8 sep; +}; + +/* lt9611c_cmd_hdr.func values */ +#define LT9611C_FUNC_WRITE 0x57 /* 'W' */ +#define LT9611C_FUNC_READ 0x52 /* 'R' */ +#define LT9611C_FUNC_ACK 0x41 /* 'A' */ + +/* lt9611c_cmd_hdr.type values */ +#define LT9611C_TYPE_MIPI 0x4d /* 'M' */ +#define LT9611C_TYPE_LVDS 0x4c /* 'L' */ +#define LT9611C_TYPE_HDMI 0x48 /* 'H' */ +#define LT9611C_TYPE_AUDIO 0x41 /* 'A' */ +#define LT9611C_TYPE_CUSTOM 0x43 /* 'C' */ + +/* lt9611c_cmd_hdr.sep is always ':' */ +#define LT9611C_CMD_SEP 0x3a /* ':' */ + +struct lt9611c_cmd { + struct lt9611c_cmd_hdr hdr; + const u8 *data; + size_t data_len; +}; + +struct lt9611c_rsp { + struct lt9611c_cmd_hdr hdr; + u8 *data; + unsigned int data_len; +}; + +enum lt9611_chip_type { + CHIP_LT9611C = 0, + CHIP_LT9611EX, + CHIP_LT9611UXD, +}; + +struct lt9611c_chip_data { + enum lt9611_chip_type chip_type; + unsigned long long max_tmds_rate; +}; + +static const struct lt9611c_chip_data lt9611c_chip_data[] = { + [CHIP_LT9611C] = { CHIP_LT9611C, 340000000 }, + [CHIP_LT9611EX] = { CHIP_LT9611EX, 340000000 }, + [CHIP_LT9611UXD] = { CHIP_LT9611UXD, 600000000 }, +}; + +struct lt9611c { + struct device *dev; + struct i2c_client *client; + struct drm_bridge bridge; + struct regmap *regmap; + struct mutex mcu_lock; + struct work_struct work; + struct device_node *dsi0_node; + struct device_node *dsi1_node; + struct mipi_dsi_device *dsi0; + struct mipi_dsi_device *dsi1; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[2]; + int fw_version; + /* Chip variant: C/EX/UXD */ + enum lt9611_chip_type chip_type; + unsigned long long max_tmds_rate; + bool hdmi_connected; +}; + +DECLARE_CRC8_TABLE(lt9611c_crc8_table); + +static const struct regmap_range_cfg lt9611c_ranges[] = { + { + .name = "register_range", + .range_min = 0, + .range_max = 0xfe9c, + .selector_reg = LT9611C_PAGE_CONTROL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct regmap_config lt9611c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xfe9c, + .ranges = lt9611c_ranges, + .num_ranges = ARRAY_SIZE(lt9611c_ranges), +}; + +static int lt9611c_read_write_flow(struct lt9611c *lt9611c, + const struct lt9611c_cmd *cmd, + struct lt9611c_rsp *rsp) +{ + int ret; + unsigned int i; + unsigned int temp; + unsigned int max_params = 0xe0dd - 0xe0b0 + 1; + + regmap_write(lt9611c->regmap, 0xe0de, 0x01); + + ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp, + temp == 0x01, 1000, 200 * 1000); + if (ret) + return -ETIMEDOUT; + + ret = regmap_bulk_write(lt9611c->regmap, 0xe0b0, &cmd->hdr, + LT9611C_CMD_HDR_SIZE); + if (ret) + return ret; + + for (i = 0; cmd->data && i < cmd->data_len && + (LT9611C_CMD_HDR_SIZE + i) < max_params; i++) + regmap_write(lt9611c->regmap, + 0xe0b0 + LT9611C_CMD_HDR_SIZE + i, cmd->data[i]); + + regmap_write(lt9611c->regmap, 0xe0de, 0x02); + + ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp, + temp == 0x02, 1000, 200 * 1000); + if (ret) + return -ETIMEDOUT; + + ret = regmap_bulk_read(lt9611c->regmap, 0xe085, + &rsp->hdr, LT9611C_CMD_HDR_SIZE); + if (ret) + return ret; + + + if (rsp->data && rsp->data_len) + ret = regmap_bulk_read(lt9611c->regmap, + 0xe085 + LT9611C_CMD_HDR_SIZE, + rsp->data, rsp->data_len); + + return ret; +} + +static void lt9611c_lock(struct lt9611c *lt9611c) +{ + mutex_lock(<9611c->mcu_lock); + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); +} + +static void lt9611c_unlock(struct lt9611c *lt9611c) +{ + regmap_write(lt9611c->regmap, 0xe0ee, 0x00); + mutex_unlock(<9611c->mcu_lock); +} + +static void lt9611c_config_parameters(struct lt9611c *lt9611c) +{ + const struct reg_sequence seq_write_paras[] = { + REG_SEQ0(0xe0ee, 0x01), + REG_SEQ0(0xe103, 0x3f), /*fifo rst*/ + REG_SEQ0(0xe103, 0xff), + REG_SEQ0(0xe05e, 0xc1), + REG_SEQ0(0xe058, 0x00), + REG_SEQ0(0xe059, 0x50), + REG_SEQ0(0xe05a, 0x10), + REG_SEQ0(0xe05a, 0x00), + REG_SEQ0(0xe058, 0x21), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write_paras, ARRAY_SIZE(seq_write_paras)); +} + +static void lt9611c_wren(struct lt9611c *lt9611c) +{ + regmap_write(lt9611c->regmap, 0xe05a, 0x04); + regmap_write(lt9611c->regmap, 0xe05a, 0x00); +} + +static void lt9611c_wrdi(struct lt9611c *lt9611c) +{ + regmap_write(lt9611c->regmap, 0xe05a, 0x08); + regmap_write(lt9611c->regmap, 0xe05a, 0x00); +} + +static void lt9611c_erase_op(struct lt9611c *lt9611c, u32 addr) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe0ee, 0x01), + REG_SEQ0(0xe05a, 0x04), + REG_SEQ0(0xe05a, 0x00), + REG_SEQ0(0xe05b, (addr >> 16) & 0xff), + REG_SEQ0(0xe05c, (addr >> 8) & 0xff), + REG_SEQ0(0xe05d, addr & 0xff), + REG_SEQ0(0xe05a, 0x01), + REG_SEQ0(0xe05a, 0x00), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static unsigned int read_flash_reg_status(struct lt9611c *lt9611c) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe103, 0x3f), + REG_SEQ0(0xe103, 0xff), + REG_SEQ0(0xe05e, 0x40), + REG_SEQ0(0xe056, 0x05), + REG_SEQ0(0xe055, 0x25), + REG_SEQ0(0xe055, 0x01), + REG_SEQ0(0xe058, 0x21), + }; + unsigned int status; + int ret; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); + ret = regmap_read(lt9611c->regmap, 0xe05f, &status); + if (ret) + return 0xff; + + return status; +} + +static void lt9611c_crc_to_sram(struct lt9611c *lt9611c) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe051, 0x00), + REG_SEQ0(0xe055, 0xc0), + REG_SEQ0(0xe055, 0x80), + REG_SEQ0(0xe05e, 0xc0), + REG_SEQ0(0xe058, 0x21), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static void lt9611c_data_to_sram(struct lt9611c *lt9611c) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe051, 0xff), + REG_SEQ0(0xe055, 0x80), + REG_SEQ0(0xe05e, 0xc0), + REG_SEQ0(0xe058, 0x21), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static void lt9611c_sram_to_flash(struct lt9611c *lt9611c, size_t addr) +{ + const struct reg_sequence seq_write[] = { + REG_SEQ0(0xe05b, (addr >> 16) & 0xff), + REG_SEQ0(0xe05c, (addr >> 8) & 0xff), + REG_SEQ0(0xe05d, addr & 0xff), + REG_SEQ0(0xe05a, 0x30), + REG_SEQ0(0xe05a, 0x00), + }; + + regmap_multi_reg_write(lt9611c->regmap, seq_write, ARRAY_SIZE(seq_write)); +} + +static int lt9611c_block_erase(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + unsigned int block_num; + unsigned int flash_status = 0; + u32 flash_addr = 0; + int ret; + + for (block_num = 0; block_num < 2; block_num++) { + flash_addr = block_num * 0x008000; + lt9611c_erase_op(lt9611c, flash_addr); + msleep(100); + ret = read_poll_timeout(read_flash_reg_status, flash_status, + !(flash_status & 0x01), + 50 * USEC_PER_MSEC, 2500 * USEC_PER_MSEC, + false, lt9611c); + if (ret) { + dev_err(dev, "flash erase timeout for block %u\n", block_num); + return ret; + } + } + + return 0; +} + +static int lt9611c_write_data(struct lt9611c *lt9611c, const struct firmware *fw, size_t addr) +{ + struct device *dev = lt9611c->dev; + int ret; + unsigned int page = 0, num = 0, i = 0; + size_t size, index; + const u8 *data; + u8 value; + + data = fw->data; + size = fw->size; + page = DIV_ROUND_UP(size, LT_PAGE_SIZE); + if (page * LT_PAGE_SIZE > FW_SIZE) { + dev_err(dev, "firmware size out of range\n"); + return -EINVAL; + } + + dev_dbg(dev, "%u pages, total size %zu byte\n", page, size); + + for (num = 0; num < page; num++) { + lt9611c_data_to_sram(lt9611c); + + for (i = 0; i < LT_PAGE_SIZE; i++) { + index = num * LT_PAGE_SIZE + i; + value = (index < size) ? data[index] : 0xff; + + ret = regmap_write(lt9611c->regmap, 0xe059, value); + if (ret < 0) { + dev_err(dev, "write error at page %u, index %u\n", num, i); + return ret; + } + } + + lt9611c_wren(lt9611c); + lt9611c_sram_to_flash(lt9611c, addr); + + addr += LT_PAGE_SIZE; + } + + lt9611c_wrdi(lt9611c); + + return 0; +} + +static int lt9611c_write_crc(struct lt9611c *lt9611c, u8 fw_crc, size_t addr) +{ + struct device *dev = lt9611c->dev; + int ret; + + lt9611c_crc_to_sram(lt9611c); + ret = regmap_write(lt9611c->regmap, 0xe059, fw_crc); + if (ret < 0) { + dev_err(dev, "failed to write crc\n"); + return ret; + } + + lt9611c_wren(lt9611c); + lt9611c_sram_to_flash(lt9611c, addr); + lt9611c_wrdi(lt9611c); + + dev_dbg(dev, "crc 0x%02x written to flash at addr 0x%zx\n", fw_crc, addr); + + return 0; +} + +static void lt9611c_reset(struct lt9611c *lt9611c) +{ + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1); + usleep_range(10000, 12000); + + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); + msleep(400); +} + +static int lt9611c_upgrade_result(struct lt9611c *lt9611c, u8 fw_crc) +{ + struct device *dev = lt9611c->dev; + unsigned int crc_result; + int ret; + + regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + ret = regmap_read(lt9611c->regmap, 0xe021, &crc_result); + if (ret) + return dev_err_probe(dev, ret, "failed to read firmware crc\n"); + + if (crc_result != fw_crc) { + dev_err(dev, "lt9611c fw upgrade failed, expected crc=0x%02x, read crc=0x%02x\n", + fw_crc, crc_result); + return -EIO; + } + + dev_info(dev, "lt9611c firmware upgrade success, crc=0x%02x\n", crc_result); + return 0; +} + +static int lt9611c_firmware_upgrade(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + const struct firmware *fw; + u8 *buffer; + size_t total_size = FW_SIZE - 1; + u8 fw_crc; + int ret; + + /* load firmware — must happen outside the mcu_lock */ + ret = request_firmware(&fw, FW_FILE, dev); + if (ret) + return dev_err_probe(dev, ret, "failed to load '%s'\n", FW_FILE); + + if (fw->size > total_size) { + dev_err(dev, "firmware too large (%zu > %zu)\n", fw->size, total_size); + ret = -EINVAL; + goto out_release_fw; + } + dev_dbg(dev, "firmware size: %zu bytes\n", fw->size); + + buffer = kzalloc(total_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out_release_fw; + } + + memcpy(buffer, fw->data, fw->size); + memset(buffer + fw->size, 0xff, total_size - fw->size); + + fw_crc = crc8(lt9611c_crc8_table, buffer, total_size, 0); + kfree(buffer); + + dev_info(dev, "starting firmware upgrade, size: %zu bytes, crc: 0x%02x\n", + fw->size, fw_crc); + + /* hardware access requires mcu_lock */ + lt9611c_lock(lt9611c); + + lt9611c_config_parameters(lt9611c); + ret = lt9611c_block_erase(lt9611c); + if (ret < 0) + goto out_unlock; + + ret = lt9611c_write_data(lt9611c, fw, 0); + if (ret < 0) { + dev_err(dev, "failed to write firmware data\n"); + goto out_unlock; + } + + ret = lt9611c_write_crc(lt9611c, fw_crc, FW_SIZE - 1); + if (ret < 0) { + dev_err(dev, "failed to write firmware crc\n"); + goto out_unlock; + } + + lt9611c_reset(lt9611c); + ret = lt9611c_upgrade_result(lt9611c, fw_crc); + +out_unlock: + lt9611c_unlock(lt9611c); +out_release_fw: + release_firmware(fw); + return ret; +} + +static struct lt9611c *bridge_to_lt9611c(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lt9611c, bridge); +} + +static const struct lt9611c *bridge_to_lt9611c_const(const struct drm_bridge *bridge) +{ + return container_of_const(bridge, struct lt9611c, bridge); +} + +static irqreturn_t lt9611c_irq_thread_handler(int irq, void *dev_id) +{ + struct lt9611c *lt9611c = dev_id; + struct device *dev = lt9611c->dev; + int ret; + unsigned int irq_status; + + guard(mutex)(<9611c->mcu_lock); + + ret = regmap_read(lt9611c->regmap, 0xe084, &irq_status); + if (ret) { + dev_err(dev, "failed to read irq status: %d\n", ret); + return IRQ_HANDLED; + } + + if (!(irq_status & BIT(0))) + return IRQ_NONE; + + /*Clear interrupt: hardware requires two writes with delay*/ + regmap_write(lt9611c->regmap, 0xe0df, irq_status & BIT(0)); + usleep_range(10000, 12000); + regmap_write(lt9611c->regmap, 0xe0df, irq_status & (~BIT(0))); + + schedule_work(<9611c->work); + + return IRQ_HANDLED; +} + +static void lt9611c_hpd_work(struct work_struct *work) +{ + struct lt9611c *lt9611c = container_of(work, struct lt9611c, work); + struct device *dev = lt9611c->dev; + static const u8 hpd_data[] = { 0x00 }; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x31, LT9611C_CMD_SEP }, + .data = hpd_data, + .data_len = 1, + }; + u8 hpd_status; + struct lt9611c_rsp rsp = { .data = &hpd_status, .data_len = 1 }; + bool connected; + int ret; + + /* Added delay as need time to reflect hpd after interrupt*/ + msleep(200); + + mutex_lock(<9611c->mcu_lock); + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "failed to read HPD status\n"); + else + lt9611c->hdmi_connected = (hpd_status == 0x02); + + connected = lt9611c->hdmi_connected; + mutex_unlock(<9611c->mcu_lock); + + drm_bridge_hpd_notify(<9611c->bridge, + connected ? connector_status_connected : + connector_status_disconnected); +} + +static int lt9611c_regulator_init(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + int ret; + + lt9611c->supplies[0].supply = "vcc"; + lt9611c->supplies[1].supply = "vdd"; + + ret = devm_regulator_bulk_get(dev, 2, lt9611c->supplies); + + return ret; +} + +static struct mipi_dsi_device *lt9611c_attach_dsi(struct lt9611c *lt9611c, + struct device_node *dsi_node) +{ + const struct mipi_dsi_device_info info = { "lt9611c", 0, NULL }; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + struct device *dev = lt9611c->dev; + int ret; + + host = of_find_mipi_dsi_host_by_node(dsi_node); + if (!host) + return ERR_PTR(dev_err_probe(dev, -EPROBE_DEFER, "failed to find dsi host\n")); + + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (IS_ERR(dsi)) + return ERR_PTR(dev_err_probe(dev, PTR_ERR(dsi), "failed to create dsi device\n")); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO_HSE; + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) + return ERR_PTR(dev_err_probe(dev, ret, "failed to attach dsi to host\n")); + + return dsi; +} + +static int lt9611c_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + + return drm_bridge_attach(encoder, lt9611c->bridge.next_bridge, bridge, flags); +} + +static enum drm_mode_status +lt9611c_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long tmds_rate) +{ + const struct lt9611c *lt9611c = bridge_to_lt9611c_const(bridge); + + if (tmds_rate > lt9611c->max_tmds_rate) + return MODE_CLOCK_HIGH; + + if (tmds_rate < 25000000) + return MODE_CLOCK_LOW; + + return MODE_OK; +} + +static void lt9611c_video_setup(struct lt9611c *lt9611c, + const struct drm_display_mode *mode) +{ + struct device *dev = lt9611c->dev; + int ret; + u32 h_total, hactive, hsync_len, hfront_porch, hback_porch; + u32 v_total, vactive, vsync_len, vfront_porch, vback_porch; + u8 timing_data[22]; + struct lt9611c_rsp rsp = {}; + u8 framerate; + u8 vic = 0x00; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_MIPI, 0x33, LT9611C_CMD_SEP }, + .data = timing_data, + .data_len = ARRAY_SIZE(timing_data), + }; + + guard(mutex)(<9611c->mcu_lock); + h_total = mode->htotal; + hactive = mode->hdisplay; + hsync_len = mode->hsync_end - mode->hsync_start; + hfront_porch = mode->hsync_start - mode->hdisplay; + hback_porch = mode->htotal - mode->hsync_end; + + v_total = mode->vtotal; + vactive = mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + vfront_porch = mode->vsync_start - mode->vdisplay; + vback_porch = mode->vtotal - mode->vsync_end; + framerate = drm_mode_vrefresh(mode); + vic = drm_match_cea_mode(mode); + + dev_dbg(dev, "hactive=%d, vactive=%d\n", hactive, vactive); + dev_dbg(dev, "framerate=%d\n", framerate); + dev_dbg(dev, "vic = 0x%02x\n", vic); + + put_unaligned_be16(h_total, &timing_data[0]); + put_unaligned_be16(hactive, &timing_data[2]); + put_unaligned_be16(hfront_porch, &timing_data[4]); + put_unaligned_be16(hsync_len, &timing_data[6]); + put_unaligned_be16(hback_porch, &timing_data[8]); + put_unaligned_be16(v_total, &timing_data[10]); + put_unaligned_be16(vactive, &timing_data[12]); + put_unaligned_be16(vfront_porch, &timing_data[14]); + put_unaligned_be16(vsync_len, &timing_data[16]); + put_unaligned_be16(vback_porch, &timing_data[18]); + timing_data[20] = framerate; + timing_data[21] = vic; + + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "video set failed\n"); +} + +static void lt9611c_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + struct drm_connector *connector; + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + struct drm_display_mode *mode; + + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + if (WARN_ON(!connector)) + return; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (WARN_ON(!conn_state)) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (WARN_ON(!crtc_state)) + return; + + mode = &crtc_state->adjusted_mode; + + lt9611c_video_setup(lt9611c, mode); +} + +static enum drm_connector_status +lt9611c_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + struct device *dev = lt9611c->dev; + int ret; + bool connected = false; + static const u8 hpd_data[] = { 0x00 }; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x31, LT9611C_CMD_SEP }, + .data = hpd_data, + .data_len = 1, + }; + u8 hpd_status; + struct lt9611c_rsp rsp = { .data = &hpd_status, .data_len = 1 }; + + guard(mutex)(<9611c->mcu_lock); + + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) + dev_err(dev, "failed to read HPD status (err=%d)\n", ret); + else + connected = (hpd_status == 0x02); + + lt9611c->hdmi_connected = connected; + + return connected ? connector_status_connected : + connector_status_disconnected; +} + +static int lt9611c_get_edid_block(void *data, u8 *buf, + unsigned int block, size_t len) +{ + struct lt9611c *lt9611c = data; + struct device *dev = lt9611c->dev; + u8 edid_raw[LT9611C_CMD_Y0_SIZE + LT9611C_EDID_BUF_SIZE]; + u8 y0; + int ret, i, offset = 0; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_READ, LT9611C_TYPE_HDMI, 0x33, LT9611C_CMD_SEP }, + }; + struct lt9611c_rsp rsp = { + .data = edid_raw, + .data_len = LT9611C_CMD_Y0_SIZE + LT9611C_EDID_BUF_SIZE, + }; + + if (len != 128) + return -EINVAL; + guard(mutex)(<9611c->mcu_lock); + + for (i = 0; i < 4; i++) { + y0 = block * 4 + i; + cmd.data = &y0; + cmd.data_len = 1; + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret) { + dev_err(dev, "Failed to read EDID block %u packet %d\n", + block, i); + return ret; + } + memcpy(buf + offset, &edid_raw[LT9611C_CMD_Y0_SIZE], LT9611C_EDID_BUF_SIZE); + offset += LT9611C_EDID_BUF_SIZE; + } + + return 0; +} + +static const struct drm_edid *lt9611c_bridge_edid_read(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + + return drm_edid_read_custom(connector, lt9611c_get_edid_block, lt9611c); +} + +static int lt9611c_write_infoframe(struct lt9611c *lt9611c, u8 type, + const u8 *buffer, size_t len) +{ + u8 extra[1 + LT9611C_INFOFRAME_MAX_SIZE]; + struct lt9611c_rsp rsp = {}; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x35, LT9611C_CMD_SEP }, + }; + + if (WARN_ON(len > LT9611C_INFOFRAME_MAX_SIZE)) + return -EINVAL; + + extra[0] = type; + memcpy(&extra[1], buffer, len); + cmd.data = extra; + cmd.data_len = 1 + len; + + guard(mutex)(<9611c->mcu_lock); + + return lt9611c_read_write_flow(lt9611c, &cmd, &rsp); +} + +static int lt9611c_clear_infoframe(struct lt9611c *lt9611c, u8 type) +{ + u8 clear_data = type; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x42, LT9611C_CMD_SEP }, + .data = &clear_data, + .data_len = 1, + }; + struct lt9611c_rsp rsp = {}; + + guard(mutex)(<9611c->mcu_lock); + + return lt9611c_read_write_flow(lt9611c, &cmd, &rsp); +} + +static int lt9611c_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + return lt9611c_write_infoframe(bridge_to_lt9611c(bridge), 0x01, buffer, len); +} + +static int lt9611c_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) +{ + return lt9611c_clear_infoframe(bridge_to_lt9611c(bridge), 0x01); +} + +static int lt9611c_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + return lt9611c_write_infoframe(bridge_to_lt9611c(bridge), 0x04, buffer, len); +} + +static int lt9611c_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + return lt9611c_clear_infoframe(bridge_to_lt9611c(bridge), 0x04); +} + +static int lt9611c_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + return lt9611c_write_infoframe(bridge_to_lt9611c(bridge), 0x02, buffer, len); +} + +static int lt9611c_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) +{ + return lt9611c_clear_infoframe(bridge_to_lt9611c(bridge), 0x02); +} + +static int lt9611c_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct lt9611c *lt9611c = bridge_to_lt9611c(bridge); + u8 audio_extra[2]; + struct lt9611c_rsp rsp = {}; + int ret; + struct lt9611c_cmd cmd = { + .hdr = { LT9611C_FUNC_WRITE, LT9611C_TYPE_HDMI, 0x36, LT9611C_CMD_SEP }, + .data = audio_extra, + .data_len = ARRAY_SIZE(audio_extra) }; + + if (hparms->sample_width == 32) + return -EINVAL; + + switch (fmt->fmt) { + case HDMI_I2S: + audio_extra[0] = 0x01; + break; + case HDMI_SPDIF: + audio_extra[0] = 0x02; + break; + default: + return -EINVAL; + } + + audio_extra[1] = hparms->channels; + + scoped_guard(mutex, <9611c->mcu_lock) { + ret = lt9611c_read_write_flow(lt9611c, &cmd, &rsp); + if (ret < 0) { + dev_err(lt9611c->dev, "set audio info failed!\n"); + return ret; + } + } + + return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, + &hparms->cea); +} + +static void lt9611c_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector); +} + +static const struct drm_bridge_funcs lt9611c_bridge_funcs = { + .attach = lt9611c_bridge_attach, + .detect = lt9611c_bridge_detect, + .edid_read = lt9611c_bridge_edid_read, + .atomic_enable = lt9611c_bridge_atomic_enable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + + .hdmi_tmds_char_rate_valid = lt9611c_hdmi_tmds_char_rate_valid, + .hdmi_write_avi_infoframe = lt9611c_hdmi_write_avi_infoframe, + .hdmi_clear_avi_infoframe = lt9611c_hdmi_clear_avi_infoframe, + .hdmi_write_hdmi_infoframe = lt9611c_hdmi_write_hdmi_infoframe, + .hdmi_clear_hdmi_infoframe = lt9611c_hdmi_clear_hdmi_infoframe, + .hdmi_write_audio_infoframe = lt9611c_hdmi_write_audio_infoframe, + .hdmi_clear_audio_infoframe = lt9611c_hdmi_clear_audio_infoframe, + + .hdmi_audio_prepare = lt9611c_hdmi_audio_prepare, + .hdmi_audio_shutdown = lt9611c_hdmi_audio_shutdown, +}; + +static int lt9611c_parse_dt(struct device *dev, + struct lt9611c *lt9611c) +{ + int ret; + + lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1); + if (!lt9611c->dsi0_node) + return dev_err_probe(dev, -ENODEV, "failed to get remote node for primary dsi\n"); + + lt9611c->dsi1_node = of_graph_get_remote_node(dev->of_node, 1, -1); + + if (lt9611c->dsi1_node && lt9611c->chip_type == CHIP_LT9611C) { + ret = dev_err_probe(dev, -EINVAL, + "LT9611C does not support dual DSI\n"); + goto err_put_dsi1; + } + + lt9611c->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 2, -1); + if (IS_ERR(lt9611c->bridge.next_bridge)) { + ret = PTR_ERR(lt9611c->bridge.next_bridge); + goto err_put_dsi1; + } + + return 0; + +err_put_dsi1: + of_node_put(lt9611c->dsi1_node); + of_node_put(lt9611c->dsi0_node); + return ret; +} + +static int lt9611c_read_version(struct lt9611c *lt9611c) +{ + u8 buf[2]; + int ret; + + ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + if (ret) + return ret; + + ret = regmap_bulk_read(lt9611c->regmap, 0xe080, buf, ARRAY_SIZE(buf)); + if (ret) + return ret; + + return (buf[0] << 8) | buf[1]; +} + +static int lt9611c_read_chipid(struct lt9611c *lt9611c) +{ + struct device *dev = lt9611c->dev; + u8 chipid[2]; + int ret; + + ret = regmap_write(lt9611c->regmap, 0xe0ee, 0x01); + if (ret) + return ret; + + ret = regmap_bulk_read(lt9611c->regmap, 0xe100, chipid, 2); + if (ret) + return ret; + + if (chipid[0] != 0x23 || chipid[1] != 0x06) { + dev_err(dev, "ChipID: 0x%02x 0x%02x\n", chipid[0], chipid[1]); + return -ENODEV; + } + + return 0; +} + +static ssize_t firmware_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + int ret; + + dev_warn(dev, "starting firmware upgrade — display will be disrupted\n"); + + ret = lt9611c_firmware_upgrade(lt9611c); + if (ret < 0) { + dev_err(dev, "upgrade failure\n"); + return ret; + } + + lt9611c_lock(lt9611c); + lt9611c->fw_version = lt9611c_read_version(lt9611c); + lt9611c_unlock(lt9611c); + + if (lt9611c->fw_version < 0) + dev_warn(dev, "upgrade succeeded but failed to read new fw version\n"); + else + dev_info(dev, "firmware upgrade succeeded, version: 0x%04x\n", + lt9611c->fw_version); + + return len; +} + +static ssize_t firmware_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%04x\n", lt9611c->fw_version); +} + +static DEVICE_ATTR_RW(firmware); + +static struct attribute *lt9611c_attrs[] = { + &dev_attr_firmware.attr, + NULL, +}; + +static const struct attribute_group lt9611c_attr_group = { + .attrs = lt9611c_attrs, +}; + +static const struct attribute_group *lt9611c_attr_groups[] = { + <9611c_attr_group, + NULL, +}; + +static int lt9611c_probe(struct i2c_client *client) +{ + struct lt9611c *lt9611c; + struct device *dev = &client->dev; + bool fw_updated = false; + int ret; + + crc8_populate_msb(lt9611c_crc8_table, LT9611C_CRC_POLYNOMIAL); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(dev, -ENODEV, "device doesn't support I2C\n"); + + lt9611c = devm_drm_bridge_alloc(dev, struct lt9611c, bridge, <9611c_bridge_funcs); + if (IS_ERR(lt9611c)) + return dev_err_probe(dev, PTR_ERR(lt9611c), "drm bridge alloc failed.\n"); + + lt9611c->dev = dev; + lt9611c->client = client; + + const struct lt9611c_chip_data *cdata = i2c_get_match_data(client); + + if (!cdata) + return dev_err_probe(dev, -EINVAL, "no match data for device\n"); + + lt9611c->chip_type = cdata->chip_type; + lt9611c->max_tmds_rate = cdata->max_tmds_rate; + + ret = devm_mutex_init(dev, <9611c->mcu_lock); + if (ret) + return dev_err_probe(dev, ret, "failed to init mutex\n"); + + lt9611c->regmap = devm_regmap_init_i2c(client, <9611c_regmap_config); + if (IS_ERR(lt9611c->regmap)) + return dev_err_probe(dev, PTR_ERR(lt9611c->regmap), "regmap i2c init failed\n"); + + ret = lt9611c_parse_dt(dev, lt9611c); + if (ret) + return dev_err_probe(dev, ret, "failed to parse device tree\n"); + + lt9611c->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lt9611c->reset_gpio)) { + ret = PTR_ERR(lt9611c->reset_gpio); + goto err_of_put; + } + + ret = lt9611c_regulator_init(lt9611c); + if (ret < 0) + goto err_of_put; + + ret = regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + if (ret) + goto err_of_put; + + lt9611c_reset(lt9611c); + + lt9611c_lock(lt9611c); + + ret = lt9611c_read_chipid(lt9611c); + if (ret < 0) { + dev_err(dev, "failed to read chip id.\n"); + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + } + +retry: + lt9611c->fw_version = lt9611c_read_version(lt9611c); + if (lt9611c->fw_version < 0) { + dev_err(dev, "failed to read fw version\n"); + ret = -EOPNOTSUPP; + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + + } else if (lt9611c->fw_version == 0) { + if (!fw_updated) { + fw_updated = true; + lt9611c_unlock(lt9611c); + ret = lt9611c_firmware_upgrade(lt9611c); + if (ret < 0) + goto err_disable_regulators; + lt9611c_lock(lt9611c); + goto retry; + + } else { + dev_err(dev, "fw version 0x%04x, update failed\n", lt9611c->fw_version); + ret = -EOPNOTSUPP; + lt9611c_unlock(lt9611c); + goto err_disable_regulators; + } + } + + lt9611c_unlock(lt9611c); + dev_dbg(dev, "current version:0x%04x", lt9611c->fw_version); + + INIT_WORK(<9611c->work, lt9611c_hpd_work); + + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + lt9611c_irq_thread_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT | + IRQF_NO_AUTOEN, + "lt9611c", lt9611c); + if (ret) { + dev_err(dev, "failed to request irq\n"); + goto err_disable_regulators; + } + + lt9611c->bridge.of_node = client->dev.of_node; + lt9611c->bridge.ops = DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | + DRM_BRIDGE_OP_HDMI | + DRM_BRIDGE_OP_HDMI_AUDIO; + lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + lt9611c->bridge.vendor = "Lontium"; + lt9611c->bridge.product = "LT9611C"; + + lt9611c->bridge.hdmi_audio_dev = dev; + lt9611c->bridge.hdmi_audio_max_i2s_playback_channels = 8; + lt9611c->bridge.hdmi_audio_dai_port = 2; + + drm_bridge_add(<9611c->bridge); + + /* Attach primary DSI */ + lt9611c->dsi0 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi0_node); + if (IS_ERR(lt9611c->dsi0)) { + ret = PTR_ERR(lt9611c->dsi0); + goto err_remove_bridge; + } + + /* Attach secondary DSI, if specified */ + if (lt9611c->dsi1_node) { + lt9611c->dsi1 = lt9611c_attach_dsi(lt9611c, lt9611c->dsi1_node); + if (IS_ERR(lt9611c->dsi1)) { + ret = PTR_ERR(lt9611c->dsi1); + goto err_remove_bridge; + } + } + + lt9611c->hdmi_connected = false; + i2c_set_clientdata(client, lt9611c); + enable_irq(client->irq); + + return 0; + +err_remove_bridge: + drm_bridge_remove(<9611c->bridge); + cancel_work_sync(<9611c->work); + +err_disable_regulators: + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + +err_of_put: + of_node_put(lt9611c->dsi1_node); + of_node_put(lt9611c->dsi0_node); + + return ret; +} + +static void lt9611c_remove(struct i2c_client *client) +{ + struct lt9611c *lt9611c = i2c_get_clientdata(client); + + disable_irq(client->irq); + cancel_work_sync(<9611c->work); + drm_bridge_remove(<9611c->bridge); + regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + of_node_put(lt9611c->dsi1_node); + of_node_put(lt9611c->dsi0_node); +} + +static int lt9611c_bridge_suspend(struct device *dev) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + int ret; + + disable_irq(lt9611c->client->irq); + cancel_work_sync(<9611c->work); + + gpiod_set_value_cansleep(lt9611c->reset_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + if (ret) { + dev_err(lt9611c->dev, "regulator bulk disable failed.\n"); + gpiod_set_value_cansleep(lt9611c->reset_gpio, 0); + enable_irq(lt9611c->client->irq); + return ret; + } + + return 0; +} + +static int lt9611c_bridge_resume(struct device *dev) +{ + struct lt9611c *lt9611c = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies); + if (ret) { + dev_err(lt9611c->dev, "regulator bulk enable failed.\n"); + return ret; + } + lt9611c_reset(lt9611c); + enable_irq(lt9611c->client->irq); + + return ret; +} + +static const struct dev_pm_ops lt9611c_bridge_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lt9611c_bridge_suspend, + lt9611c_bridge_resume) +}; + +static struct i2c_device_id lt9611c_id[] = { + { "lontium,lt9611c", (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611C] }, + { "lontium,lt9611ex", (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611EX] }, + { "lontium,lt9611uxd", (kernel_ulong_t)<9611c_chip_data[CHIP_LT9611UXD] }, + { /* sentinel */ } +}; + +static const struct of_device_id lt9611c_match_table[] = { + { .compatible = "lontium,lt9611c", .data = <9611c_chip_data[CHIP_LT9611C] }, + { .compatible = "lontium,lt9611ex", .data = <9611c_chip_data[CHIP_LT9611EX] }, + { .compatible = "lontium,lt9611uxd", .data = <9611c_chip_data[CHIP_LT9611UXD] }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lt9611c_match_table); + +static struct i2c_driver lt9611c_driver = { + .driver = { + .name = "lt9611c", + .of_match_table = lt9611c_match_table, + .pm = <9611c_bridge_pm_ops, + .dev_groups = lt9611c_attr_groups, + }, + .probe = lt9611c_probe, + .remove = lt9611c_remove, + .id_table = lt9611c_id, +}; +module_i2c_driver(lt9611c_driver); + +MODULE_AUTHOR("SunYun Yang "); +MODULE_DESCRIPTION("Lontium LT9611C(EX/UXD) MIPI DSI to HDMI driver"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(FW_FILE); diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c index cc70508d4fdba..8b117922871b2 100644 --- a/drivers/gpu/drm/drm_atomic_state_helper.c +++ b/drivers/gpu/drm/drm_atomic_state_helper.c @@ -799,6 +799,7 @@ EXPORT_SYMBOL(drm_atomic_helper_bridge_duplicate_state); * @state: bridge state to destroy * * Destroys a bridge state previously created by + * &drm_atomic_helper_bridge_create_state(), * &drm_atomic_helper_bridge_reset() or * &drm_atomic_helper_bridge_duplicate_state(). This helper is meant to be * used as a bridge &drm_bridge_funcs.atomic_destroy_state hook for bridges @@ -818,8 +819,9 @@ EXPORT_SYMBOL(drm_atomic_helper_bridge_destroy_state); * @state: bridge state to initialize * * Initializes the bridge state to default values. This is meant to be called - * by the bridge &drm_bridge_funcs.atomic_reset hook for bridges that subclass - * the bridge state. + * by the bridge &drm_bridge_funcs.atomic_create_state or + * &drm_bridge_funcs.atomic_reset hook for bridges that subclass the bridge + * state. */ void __drm_atomic_helper_bridge_reset(struct drm_bridge *bridge, struct drm_bridge_state *state) @@ -841,6 +843,25 @@ EXPORT_SYMBOL(__drm_atomic_helper_bridge_reset); */ struct drm_bridge_state * drm_atomic_helper_bridge_reset(struct drm_bridge *bridge) +{ + return drm_atomic_helper_bridge_create_state(bridge); +} +EXPORT_SYMBOL(drm_atomic_helper_bridge_reset); + +/** + * drm_atomic_helper_bridge_create_state - default + * &drm_bridge_funcs.atomic_create_state hook for bridges + * @bridge: bridge object + * + * Allocates and initializes pristine @drm_bridge_state. + * + * This is useful for drivers that don't subclass @drm_bridge_state. + * + * RETURNS: + * Pointer to new bridge state, or ERR_PTR on failure. + */ +struct drm_bridge_state * +drm_atomic_helper_bridge_create_state(struct drm_bridge *bridge) { struct drm_bridge_state *bridge_state; @@ -851,4 +872,4 @@ drm_atomic_helper_bridge_reset(struct drm_bridge *bridge) __drm_atomic_helper_bridge_reset(bridge, bridge_state); return bridge_state; } -EXPORT_SYMBOL(drm_atomic_helper_bridge_reset); +EXPORT_SYMBOL(drm_atomic_helper_bridge_create_state); diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 687b36eea0c70..ef06c1aa509ad 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -500,7 +500,10 @@ drm_bridge_atomic_create_priv_state(struct drm_private_obj *obj) struct drm_bridge *bridge = drm_priv_to_bridge(obj); struct drm_bridge_state *state; - state = bridge->funcs->atomic_reset(bridge); + if (bridge->funcs->atomic_create_state) + state = bridge->funcs->atomic_create_state(bridge); + else + state = bridge->funcs->atomic_reset(bridge); if (IS_ERR(state)) return ERR_CAST(state); @@ -515,7 +518,8 @@ static const struct drm_private_state_funcs drm_bridge_priv_state_funcs = { static bool drm_bridge_is_atomic(struct drm_bridge *bridge) { - return bridge->funcs->atomic_reset != NULL; + return (bridge->funcs->atomic_create_state || + bridge->funcs->atomic_reset); } /** diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig index 958dbc7347fa8..1b133c5623161 100644 --- a/drivers/net/wwan/Kconfig +++ b/drivers/net/wwan/Kconfig @@ -65,6 +65,7 @@ config MHI_WWAN_MBIM config QCOM_BAM_DMUX tristate "Qualcomm BAM-DMUX WWAN network driver" depends on (DMA_ENGINE && PM && QCOM_SMEM_STATE) || COMPILE_TEST + select QCOM_SCM help The BAM Data Multiplexer provides access to the network data channels of modems integrated into many older Qualcomm SoCs, e.g. Qualcomm diff --git a/drivers/net/wwan/qcom_bam_dmux.c b/drivers/net/wwan/qcom_bam_dmux.c index cc6ace8d64371..247230b720e60 100644 --- a/drivers/net/wwan/qcom_bam_dmux.c +++ b/drivers/net/wwan/qcom_bam_dmux.c @@ -9,10 +9,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -62,6 +64,7 @@ struct bam_dmux_skb_dma { struct bam_dmux *dmux; struct sk_buff *skb; dma_addr_t addr; + void *rx_virt; /* non-NULL: slot in the coherent RX block */ }; struct bam_dmux { @@ -75,6 +78,10 @@ struct bam_dmux { struct completion pc_ack_completion; struct dma_chan *rx, *tx; + /* Single coherent block backing all RX buffers, NULL if unused */ + void *rx_buf; + dma_addr_t rx_buf_dma; + u64 rx_buf_perms; /* SCM source-VMID bitmask of rx_buf */ struct bam_dmux_skb_dma rx_skbs[BAM_DMUX_NUM_SKB]; struct bam_dmux_skb_dma tx_skbs[BAM_DMUX_NUM_SKB]; spinlock_t tx_lock; /* Protect tx_skbs, tx_next_skb */ @@ -92,6 +99,10 @@ struct bam_dmux_netdev { u8 ch; }; +struct bam_dmux_data { + u32 vmid; +}; + static void bam_dmux_pc_vote(struct bam_dmux *dmux, bool enable) { reinit_completion(&dmux->pc_ack_completion); @@ -111,6 +122,9 @@ static bool bam_dmux_skb_dma_map(struct bam_dmux_skb_dma *skb_dma, { struct device *dev = skb_dma->dmux->dev; + if (skb_dma->rx_virt) /* coherent RX slot: addr pre-assigned */ + return true; + skb_dma->addr = dma_map_single(dev, skb_dma->skb->data, skb_dma->skb->len, dir); if (dma_mapping_error(dev, skb_dma->addr)) { dev_err(dev, "Failed to DMA map buffer\n"); @@ -124,6 +138,9 @@ static bool bam_dmux_skb_dma_map(struct bam_dmux_skb_dma *skb_dma, static void bam_dmux_skb_dma_unmap(struct bam_dmux_skb_dma *skb_dma, enum dma_data_direction dir) { + if (skb_dma->rx_virt) /* coherent RX slot: nothing to unmap */ + return; + dma_unmap_single(skb_dma->dmux->dev, skb_dma->addr, skb_dma->skb->len, dir); skb_dma->addr = 0; } @@ -468,9 +485,10 @@ static bool bam_dmux_skb_dma_submit_rx(struct bam_dmux_skb_dma *skb_dma) { struct bam_dmux *dmux = skb_dma->dmux; struct dma_async_tx_descriptor *desc; + size_t len = skb_dma->rx_virt ? BAM_DMUX_BUFFER_SIZE : skb_dma->skb->len; desc = dmaengine_prep_slave_single(dmux->rx, skb_dma->addr, - skb_dma->skb->len, DMA_DEV_TO_MEM, + len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); if (!desc) { dev_err(dmux->dev, "Failed to prepare RX DMA buffer\n"); @@ -485,6 +503,10 @@ static bool bam_dmux_skb_dma_submit_rx(struct bam_dmux_skb_dma *skb_dma) static bool bam_dmux_skb_dma_queue_rx(struct bam_dmux_skb_dma *skb_dma, gfp_t gfp) { + /* Coherent RX slots have rx_virt and addr pre-assigned at probe. */ + if (skb_dma->rx_virt) + return bam_dmux_skb_dma_submit_rx(skb_dma); + if (!skb_dma->skb) { skb_dma->skb = __netdev_alloc_skb(NULL, BAM_DMUX_BUFFER_SIZE, gfp); if (!skb_dma->skb) @@ -499,9 +521,10 @@ static bool bam_dmux_skb_dma_queue_rx(struct bam_dmux_skb_dma *skb_dma, gfp_t gf static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma) { struct bam_dmux *dmux = skb_dma->dmux; - struct sk_buff *skb = skb_dma->skb; - struct bam_dmux_hdr *hdr = (struct bam_dmux_hdr *)skb->data; + struct bam_dmux_hdr *hdr = skb_dma->rx_virt ? skb_dma->rx_virt : + (struct bam_dmux_hdr *)skb_dma->skb->data; struct net_device *netdev = dmux->netdevs[hdr->ch]; + struct sk_buff *skb; if (!netdev || !netif_running(netdev)) { dev_warn(dmux->dev, "Data for inactive channel %u\n", hdr->ch); @@ -514,10 +537,18 @@ static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma) return; } - skb_dma->skb = NULL; /* Hand over to network stack */ - - skb_pull(skb, sizeof(*hdr)); - skb_trim(skb, hdr->len); + if (skb_dma->rx_virt) { + /* Coherent block is not page-backed: copy out to a real skb */ + skb = netdev_alloc_skb(netdev, hdr->len); + if (!skb) + return; + skb_put_data(skb, (u8 *)skb_dma->rx_virt + sizeof(*hdr), hdr->len); + } else { + skb = skb_dma->skb; + skb_dma->skb = NULL; /* Hand over to network stack */ + skb_pull(skb, sizeof(*hdr)); + skb_trim(skb, hdr->len); + } skb->dev = netdev; /* Only Raw-IP/QMAP is supported by this driver */ @@ -574,10 +605,14 @@ static void bam_dmux_rx_callback(void *data) { struct bam_dmux_skb_dma *skb_dma = data; struct bam_dmux *dmux = skb_dma->dmux; - struct sk_buff *skb = skb_dma->skb; - struct bam_dmux_hdr *hdr = (struct bam_dmux_hdr *)skb->data; + struct bam_dmux_hdr *hdr; - bam_dmux_skb_dma_unmap(skb_dma, DMA_FROM_DEVICE); + if (skb_dma->rx_virt) { + hdr = skb_dma->rx_virt; /* coherent RX: no skb to unmap */ + } else { + bam_dmux_skb_dma_unmap(skb_dma, DMA_FROM_DEVICE); + hdr = (struct bam_dmux_hdr *)skb_dma->skb->data; + } if (hdr->magic != BAM_DMUX_HDR_MAGIC) { dev_err(dmux->dev, "Invalid magic in header: %#x\n", hdr->magic); @@ -644,6 +679,9 @@ static void bam_dmux_free_skbs(struct bam_dmux_skb_dma skbs[], for (i = 0; i < BAM_DMUX_NUM_SKB; i++) { struct bam_dmux_skb_dma *skb_dma = &skbs[i]; + if (skb_dma->rx_virt) /* coherent block freed at remove */ + continue; + if (skb_dma->addr) bam_dmux_skb_dma_unmap(skb_dma, dir); if (skb_dma->skb) { @@ -762,6 +800,71 @@ static int __maybe_unused bam_dmux_runtime_resume(struct device *dev) return 0; } +static int bam_dmux_alloc_coherent_rx(struct bam_dmux *dmux) +{ + struct device *dev = dmux->dev; + const struct bam_dmux_data *data = of_device_get_match_data(dev); + size_t size = BAM_DMUX_NUM_SKB * BAM_DMUX_BUFFER_SIZE; + u64 src = BIT_ULL(QCOM_SCM_VMID_HLOS); + struct qcom_scm_vmperm dst[2]; + int i, ret; + + if (!data) + return 0; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + dst[0].vmid = QCOM_SCM_VMID_HLOS; + dst[0].perm = QCOM_SCM_PERM_RW; + dst[1].vmid = data->vmid; + dst[1].perm = QCOM_SCM_PERM_RW; + + dmux->rx_buf = dma_alloc_coherent(dev, size, &dmux->rx_buf_dma, GFP_KERNEL); + if (!dmux->rx_buf) + return -ENOMEM; + + for (i = 0; i < BAM_DMUX_NUM_SKB; i++) { + dmux->rx_skbs[i].rx_virt = dmux->rx_buf + i * BAM_DMUX_BUFFER_SIZE; + dmux->rx_skbs[i].addr = dmux->rx_buf_dma + i * BAM_DMUX_BUFFER_SIZE; + } + + ret = qcom_scm_assign_mem(dmux->rx_buf_dma, size, &src, dst, ARRAY_SIZE(dst)); + if (ret) { + dev_err(dev, "SCM assign RX block failed: %d\n", ret); + dma_free_coherent(dev, size, dmux->rx_buf, dmux->rx_buf_dma); + dmux->rx_buf = NULL; + return ret; + } + dmux->rx_buf_perms = src; + + return 0; +} + +static void bam_dmux_free_coherent_rx(struct bam_dmux *dmux) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + size_t size = BAM_DMUX_NUM_SKB * BAM_DMUX_BUFFER_SIZE; + + if (!dmux->rx_buf) + return; + + if (dmux->rx_buf_perms) { + if (qcom_scm_assign_mem(dmux->rx_buf_dma, size, &dmux->rx_buf_perms, + &hlos, 1)) { + dev_err(dmux->dev, "SCM reclaim RX block failed; leaking\n"); + return; + } + dmux->rx_buf_perms = 0; + } + + dma_free_coherent(dmux->dev, size, dmux->rx_buf, dmux->rx_buf_dma); + dmux->rx_buf = NULL; +} + static int bam_dmux_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -809,6 +912,10 @@ static int bam_dmux_probe(struct platform_device *pdev) dmux->tx_skbs[i].dmux = dmux; } + ret = bam_dmux_alloc_coherent_rx(dmux); + if (ret) + return ret; + /* Runtime PM manages our own power vote. * Note that the RX path may be active even if we are runtime suspended, * since it is controlled by the remote side. @@ -845,6 +952,7 @@ static int bam_dmux_probe(struct platform_device *pdev) err_disable_pm: pm_runtime_disable(dev); pm_runtime_dont_use_autosuspend(dev); + bam_dmux_free_coherent_rx(dmux); return ret; } @@ -879,13 +987,19 @@ static void bam_dmux_remove(struct platform_device *pdev) disable_irq(dmux->pc_irq); bam_dmux_power_off(dmux); bam_dmux_free_skbs(dmux->tx_skbs, DMA_TO_DEVICE); + bam_dmux_free_coherent_rx(dmux); } static const struct dev_pm_ops bam_dmux_pm_ops = { SET_RUNTIME_PM_OPS(bam_dmux_runtime_suspend, bam_dmux_runtime_resume, NULL) }; +static const struct bam_dmux_data shikra_data = { + .vmid = QCOM_SCM_VMID_NAV, +}; + static const struct of_device_id bam_dmux_of_match[] = { + { .compatible = "qcom,shikra-bam-dmux", .data = &shikra_data }, { .compatible = "qcom,bam-dmux" }, { /* sentinel */ } }; diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index d8eb52857f69c..19daadee65f7c 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -2309,6 +2309,7 @@ static const struct of_device_id qcom_pcie_match[] = { { .compatible = "qcom,pcie-sm8450-pcie1", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-sm8550", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-x1e80100", .data = &cfg_sc8280xp }, + { .compatible = "qcom,shikra-pcie", .data = &cfg_1_9_0 }, { } }; diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c index 1555e8a9b3ca1..de68b98366452 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c @@ -62,6 +62,8 @@ #define TC9563_TX_MARGIN_MIN_UA 400000 +#define TC9563_GPIO_NONE (-1) + /* * From TC9563 PORSYS rev 0.2, figure 1.1 POR boot sequence * wait for 10ms for the internal osc frequency to stabilize. @@ -91,6 +93,10 @@ struct tc9563_pwrctrl_cfg { u8 nfts[2]; /* GEN1 & GEN2 */ bool disable_dfe; bool disable_port; + + int ep_reset_gpio; + int ep_pwr_en_gpio; + }; #define TC9563_PWRCTL_MAX_SUPPLY 6 @@ -256,6 +262,89 @@ static int tc9563_pwrctrl_disable_port(struct tc9563_pwrctrl *tc9563, ARRAY_SIZE(common_pwroff_seq)); } +static int tc9563_pwrctrl_ep_pwr_en(struct tc9563_pwrctrl *tc9563, + enum tc9563_pwrctrl_ports port, + bool enable, int ep_pwr_en_gpio) +{ + u32 ep_pwr_en_gpio_mask, val; + int ret; + + if (ep_pwr_en_gpio == TC9563_GPIO_NONE) + return 0; + + ep_pwr_en_gpio_mask = BIT(ep_pwr_en_gpio); + + /* Set TC9563 GPIO as output */ + ret = tc9563_pwrctrl_i2c_read(tc9563->client, TC9563_GPIO_CONFIG, + &val); + if (ret) + return ret; + + val &= ~ep_pwr_en_gpio_mask; + + ret = tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_GPIO_CONFIG, + val); + if (ret) + return ret; + + /* Toggle 0->1 to enable power */ + ret = tc9563_pwrctrl_i2c_read(tc9563->client, TC9563_RESET_GPIO, + &val); + if (ret) + return ret; + + val = enable ? (val | ep_pwr_en_gpio_mask) : (val & ~ep_pwr_en_gpio_mask); + + return tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_RESET_GPIO, val); + +} + +static int tc9563_pwrctrl_ep_assert_deassert_reset(struct tc9563_pwrctrl *tc9563, + enum tc9563_pwrctrl_ports port, + bool deassert, int ep_reset_gpio) +{ + u32 ep_reset_gpio_mask, val; + int ret; + + if (ep_reset_gpio == TC9563_GPIO_NONE) { + switch (port) { + case TC9563_DSP1: + ep_reset_gpio = 0x2; + break; + case TC9563_DSP2: + ep_reset_gpio = 0x3; + break; + default: + return 0; + } + } + + ep_reset_gpio_mask = BIT(ep_reset_gpio); + + /* Set TC9563 GPIO as output */ + ret = tc9563_pwrctrl_i2c_read(tc9563->client, TC9563_GPIO_CONFIG, + &val); + if (ret) + return ret; + + val &= ~ep_reset_gpio_mask; + + ret = tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_GPIO_CONFIG, + val); + if (ret) + return ret; + + /* Assert-deassert endpoint reset */ + ret = tc9563_pwrctrl_i2c_read(tc9563->client, TC9563_RESET_GPIO, + &val); + if (ret) + return ret; + + val = deassert ? (val | ep_reset_gpio_mask) : (val & ~ep_reset_gpio_mask); + + return tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_RESET_GPIO, val); +} + static int tc9563_pwrctrl_set_l0s_l1_entry_delay(struct tc9563_pwrctrl *tc9563, enum tc9563_pwrctrl_ports port, bool is_l1, u32 ns) @@ -400,28 +489,17 @@ static int tc9563_pwrctrl_set_nfts(struct tc9563_pwrctrl *tc9563, ARRAY_SIZE(nfts_seq)); } -static int tc9563_pwrctrl_assert_deassert_reset(struct tc9563_pwrctrl *tc9563, - bool deassert) -{ - int ret, val; - - ret = tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_GPIO_CONFIG, - TC9563_GPIO_MASK); - if (ret) - return ret; - - val = deassert ? TC9563_GPIO_DEASSERT_BITS : 0; - - return tc9563_pwrctrl_i2c_write(tc9563->client, TC9563_RESET_GPIO, val); -} - static int tc9563_pwrctrl_parse_device_dt(struct tc9563_pwrctrl *tc9563, struct device_node *node, enum tc9563_pwrctrl_ports port) { struct tc9563_pwrctrl_cfg *cfg = &tc9563->cfg[port]; + struct of_phandle_args args; int ret; + cfg->ep_reset_gpio = TC9563_GPIO_NONE; + cfg->ep_pwr_en_gpio = TC9563_GPIO_NONE; + /* Disable port if the status of the port is disabled. */ if (!of_device_is_available(node)) { cfg->disable_port = true; @@ -436,6 +514,18 @@ static int tc9563_pwrctrl_parse_device_dt(struct tc9563_pwrctrl *tc9563, if (ret && ret != -EINVAL) return ret; + ret = of_parse_phandle_with_fixed_args(node, "ep-pwr-en-gpios", 2, 0, &args); + if (ret && ret != -ENOENT) + return ret; + else if (!ret) + cfg->ep_pwr_en_gpio = args.args[0]; + + ret = of_parse_phandle_with_fixed_args(node, "reset-gpios", 2, 0, &args); + if (ret && ret != -ENOENT) + return ret; + else if (!ret) + cfg->ep_reset_gpio = args.args[0]; + ret = of_property_read_u32(node, "toshiba,tx-amplitude-microvolt", &cfg->tx_amp); if (ret && ret != -EINVAL) return ret; @@ -478,18 +568,28 @@ static int tc9563_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl) fsleep(TC9563_OSC_STAB_DELAY_US); - ret = tc9563_pwrctrl_assert_deassert_reset(tc9563, false); - if (ret) - goto power_off; - for (i = 0; i < TC9563_MAX; i++) { cfg = &tc9563->cfg[i]; + ret = tc9563_pwrctrl_disable_port(tc9563, i); if (ret) { dev_err(dev, "Disabling port failed\n"); goto power_off; } + ret = tc9563_pwrctrl_ep_assert_deassert_reset(tc9563, i, false, + cfg->ep_reset_gpio); + if (ret) { + dev_err(dev, "Assert EP reset failed\n"); + goto power_off; + } + + ret = tc9563_pwrctrl_ep_pwr_en(tc9563, i, true, cfg->ep_pwr_en_gpio); + if (ret) { + dev_err(dev, "Enabling EP Power failed\n"); + goto power_off; + } + ret = tc9563_pwrctrl_set_l0s_l1_entry_delay(tc9563, i, false, cfg->l0s_delay); if (ret) { dev_err(dev, "Setting L0s entry delay failed\n"); @@ -519,11 +619,17 @@ static int tc9563_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl) dev_err(dev, "Disabling DFE failed\n"); goto power_off; } + + ret = tc9563_pwrctrl_ep_assert_deassert_reset(tc9563, i, true, + cfg->ep_reset_gpio); + if (ret) { + dev_err(dev, "De-assert EP reset failed\n"); + goto power_off; + } + } - ret = tc9563_pwrctrl_assert_deassert_reset(tc9563, true); - if (!ret) - return 0; + return 0; power_off: tc9563_pwrctrl_power_off(&tc9563->pwrctrl); @@ -601,7 +707,7 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) port++; ret = tc9563_pwrctrl_parse_device_dt(tc9563, child1, port); - if (ret) + if (port + 1 >= TC9563_MAX || ret) break; } } diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c index d3effad7a074b..e0995e4d5f2dc 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-pcie.c @@ -2172,6 +2172,50 @@ static const struct qmp_phy_init_tbl sdx65_qmp_pcie_pcs_lane1_tbl[] = { QMP_PHY_INIT_CFG(QPHY_V5_20_PCS_LANE1_INSIG_MX_CTRL2, 0x00), }; +static const struct qmp_phy_init_tbl shikra_pcie_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BIAS_EN_CLKBUFLR_EN, 0x18), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CLK_ENABLE1, 0x10), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BG_TRIM, 0xf), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_LOCK_CMP_EN, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_VCO_TUNE_MAP, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_VCO_TUNE_TIMER1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_VCO_TUNE_TIMER2, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CMN_CONFIG, 0x6), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_PLL_IVCO, 0xf), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_HSCLK_SEL, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SVS_MODE_CLK_SEL, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CORE_CLK_EN, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CORECLK_DIV, 0xa), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_RESETSM_CNTRL, 0x20), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BG_TIMER, 0x9), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SYSCLK_EN_SEL, 0xa), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_DIV_FRAC_START3_MODE0, 0x3), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_LOCK_CMP3_MODE0, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_LOCK_CMP2_MODE0, 0xd), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_LOCK_CMP1_MODE0, 0x04), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CLK_SELECT, 0x35), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SYS_CLK_CTRL, 0x2), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SYSCLK_BUF_ENABLE, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CP_CTRL_MODE0, 0x4), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_PLL_CCTRL_MODE0, 0x30), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_INTEGLOOP_GAIN1_MODE0, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BIAS_EN_CTRL_BY_PSM, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_BG_TIMER, 0xa), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_EN_CENTER, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_PER2, 0x1), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_ADJ_PER1, 0x2), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_ADJ_PER2, 0x0), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_STEP_SIZE1, 0x2f), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_SSC_STEP_SIZE2, 0x19), + QMP_PHY_INIT_CFG(QSERDES_V2_COM_CLK_EP_DIV, 0x19), +}; + static const struct qmp_phy_init_tbl sm8450_qmp_gen3_pcie_serdes_tbl[] = { QMP_PHY_INIT_CFG(QSERDES_V5_COM_SYSCLK_EN_SEL, 0x08), QMP_PHY_INIT_CFG(QSERDES_V5_COM_CLK_SELECT, 0x34), @@ -3911,6 +3955,32 @@ static const struct qmp_phy_cfg sdm845_qhp_pciephy_cfg = { .phy_status = PHYSTATUS, }; +static const struct qmp_phy_cfg shikra_pciephy_cfg = { + .lanes = 1, + + .offsets = &qmp_pcie_offsets_v2, + + .tbls = { + .serdes = shikra_pcie_serdes_tbl, + .serdes_num = ARRAY_SIZE(shikra_pcie_serdes_tbl), + .tx = qcs615_pcie_tx_tbl, + .tx_num = ARRAY_SIZE(qcs615_pcie_tx_tbl), + .rx = qcs615_pcie_rx_tbl, + .rx_num = ARRAY_SIZE(qcs615_pcie_rx_tbl), + .pcs = qcs615_pcie_pcs_tbl, + .pcs_num = ARRAY_SIZE(qcs615_pcie_pcs_tbl), + }, + .reset_list = sdm845_pciephy_reset_l, + .num_resets = ARRAY_SIZE(sdm845_pciephy_reset_l), + .vreg_list = qmp_phy_vreg_l, + .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l), + .regs = pciephy_v2_regs_layout, + + .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, + .phy_status = PHYSTATUS, +}; + + static const struct qmp_phy_cfg sm8250_qmp_gen3x1_pciephy_cfg = { .lanes = 1, @@ -5603,6 +5673,9 @@ static const struct of_device_id qmp_pcie_of_match_table[] = { }, { .compatible = "qcom,sdx65-qmp-gen4x2-pcie-phy", .data = &sdx65_qmp_pciephy_cfg, + }, { + .compatible = "qcom,shikra-qmp-gen2x1-pcie-phy", + .data = &shikra_pciephy_cfg, }, { .compatible = "qcom,sm8150-qmp-gen3x1-pcie-phy", .data = &sm8250_qmp_gen3x1_pciephy_cfg, diff --git a/drivers/pinctrl/qcom/pinctrl-shikra.c b/drivers/pinctrl/qcom/pinctrl-shikra.c index 0fc98369948cb..55aec2f675e6b 100644 --- a/drivers/pinctrl/qcom/pinctrl-shikra.c +++ b/drivers/pinctrl/qcom/pinctrl-shikra.c @@ -44,6 +44,7 @@ .intr_status_bit = 0, \ .intr_wakeup_enable_bit = 7, \ .intr_wakeup_present_bit = 6, \ + .intr_target_width = 4, \ .intr_target_bit = 8, \ .intr_target_kpss_val = 3, \ .intr_raw_status_bit = 4, \ diff --git a/drivers/soc/qcom/qcom_pd_mapper.c b/drivers/soc/qcom/qcom_pd_mapper.c index 0dc1a7946050c..4850268e67b1d 100644 --- a/drivers/soc/qcom/qcom_pd_mapper.c +++ b/drivers/soc/qcom/qcom_pd_mapper.c @@ -462,6 +462,11 @@ static const struct qcom_pdm_domain_data *sc8280xp_domains[] = { NULL, }; +static const struct qcom_pdm_domain_data *shikra_domains[] = { + &mpss_wlan_pd, + NULL, +}; + /* Unlike SDM660, SDM630/636 lack CDSP */ static const struct qcom_pdm_domain_data *sdm630_domains[] = { &adsp_audio_pd, @@ -606,6 +611,7 @@ static const struct of_device_id qcom_pdm_domains[] __maybe_unused = { { .compatible = "qcom,sc7280", .data = sc7280_domains, }, { .compatible = "qcom,sc8180x", .data = sc8180x_domains, }, { .compatible = "qcom,sc8280xp", .data = sc8280xp_domains, }, + { .compatible = "qcom,shikra", .data = shikra_domains, }, { .compatible = "qcom,sdm630", .data = sdm630_domains, }, { .compatible = "qcom,sdm636", .data = sdm630_domains, }, { .compatible = "qcom,sda660", .data = sdm660_domains, }, diff --git a/drivers/soc/qcom/qcom_stats.c b/drivers/soc/qcom/qcom_stats.c index 2e380faf90803..2f1615e583bbc 100644 --- a/drivers/soc/qcom/qcom_stats.c +++ b/drivers/soc/qcom/qcom_stats.c @@ -376,6 +376,14 @@ static const struct stats_config rpm_data_dba0 = { .subsystem_stats_in_smem = false, }; +static const struct stats_config rpm_data_shikra = { + .stats_offset = 0, + .num_records = 2, + .appended_stats_avail = true, + .dynamic_offset = true, + .subsystem_stats_in_smem = true, +}; + static const struct stats_config rpmh_data_sdm845 = { .stats_offset = 0x48, .num_records = 2, @@ -401,6 +409,7 @@ static const struct of_device_id qcom_stats_table[] = { { .compatible = "qcom,rpm-stats", .data = &rpm_data }, { .compatible = "qcom,rpmh-stats", .data = &rpmh_data }, { .compatible = "qcom,sdm845-rpmh-stats", .data = &rpmh_data_sdm845 }, + { .compatible = "qcom,shikra-rpm-stats", .data = &rpm_data_shikra }, { } }; MODULE_DEVICE_TABLE(of, qcom_stats_table); diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 3d8f5a81eff19..f4b8ff77bc2f8 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -26,6 +26,7 @@ #define SWRM_COMP_STATUS 0x014 #define SWRM_LINK_MANAGER_EE 0x018 #define SWRM_EE_CPU 1 +#define SWRM_MAX_EE 1 #define SWRM_FRM_GEN_ENABLED BIT(0) #define SWRM_VERSION_1_3_0 0x01030000 #define SWRM_VERSION_1_5_1 0x01050001 @@ -118,6 +119,7 @@ #define SWRM_V2_0_CLK_CTRL 0x5060 #define SWRM_V2_0_CLK_CTRL_CLK_START BIT(0) #define SWRM_V2_0_LINK_STATUS 0x5064 +#define SWRM_V2_REG_EE_STRIDE 0x1000 #define SWRM_DP_PORT_CTRL_EN_CHAN_SHFT 0x18 #define SWRM_DP_PORT_CTRL_OFFSET2_SHFT 0x10 @@ -202,6 +204,7 @@ struct qcom_swrm_ctrl { struct mutex port_lock; struct clk *hclk; int irq; + u32 ee; unsigned int version; int wake_irq; int num_din_ports; @@ -222,6 +225,7 @@ struct qcom_swrm_ctrl { u32 slave_status; u32 wr_fifo_depth; bool clock_stop_not_supported; + unsigned int reg_layout_local[SWRM_OFFSET_DP_SAMPLECTRL2_BANK + 1]; }; struct qcom_swrm_data { @@ -328,6 +332,36 @@ static const struct qcom_swrm_data swrm_v3_0_data = { }; #define to_qcom_sdw(b) container_of(b, struct qcom_swrm_ctrl, bus) +static void qcom_swrm_set_ee_register_layout(struct qcom_swrm_ctrl *ctrl, + const struct qcom_swrm_data *data) +{ + int ee_offset; + + memcpy(ctrl->reg_layout_local, data->reg_layout, + sizeof(ctrl->reg_layout_local)); + ctrl->reg_layout = ctrl->reg_layout_local; + + if (ctrl->version < SWRM_VERSION_2_0_0) + return; + + /* + * Current register constants map EE1. For EE0, use the EE register + * window stride to access status/IRQ/FIFO registers. + */ + ee_offset = ((int)ctrl->ee - SWRM_EE_CPU) * SWRM_V2_REG_EE_STRIDE; + if (!ee_offset) + return; + + ctrl->reg_layout_local[SWRM_REG_FRAME_GEN_ENABLED] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_INTERRUPT_STATUS] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_INTERRUPT_CLEAR] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_INTERRUPT_CPU_EN] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_CMD_FIFO_WR_CMD] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_CMD_FIFO_RD_CMD] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_CMD_FIFO_STATUS] += ee_offset; + ctrl->reg_layout_local[SWRM_REG_CMD_FIFO_RD_FIFO_ADDR] += ee_offset; +} + static int qcom_swrm_ahb_reg_read(struct qcom_swrm_ctrl *ctrl, int reg, u32 *val) { @@ -904,12 +938,13 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) ctrl->reg_write(ctrl, SWRM_MCP_CFG_ADDR, val); if (ctrl->version == SWRM_VERSION_1_7_0) { - ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU); + ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, - SWRM_MCP_BUS_CLK_START << SWRM_EE_CPU); + SWRM_MCP_BUS_CLK_START << ctrl->ee); } else if (ctrl->version >= SWRM_VERSION_2_0_0) { - ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU); - ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL, + ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); + ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL + + ((int)ctrl->ee - SWRM_EE_CPU) * SWRM_V2_REG_EE_STRIDE, SWRM_V2_0_CLK_CTRL_CLK_START); } else { ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START); @@ -935,11 +970,9 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CLEAR], 0xFFFFFFFF); - /* enable CPU IRQs */ - if (ctrl->mmio) { - ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN], - SWRM_INTERRUPT_STATUS_RMSK); - } + /* enable CPU IRQs for the selected EE window */ + ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CPU_EN], + SWRM_INTERRUPT_STATUS_RMSK); /* Set IRQ to PULSE */ ctrl->reg_write(ctrl, SWRM_COMP_CFG_ADDR, @@ -1545,7 +1578,22 @@ static int qcom_swrm_probe(struct platform_device *pdev) return -ENOMEM; data = of_device_get_match_data(dev); + ctrl->ee = SWRM_EE_CPU; + ret = of_property_read_u32(dev->of_node, "qcom,swr-master-ee-val", &ctrl->ee); + if (ret) + ret = of_property_read_u32(dev->of_node, "qcom,ee", &ctrl->ee); + if (ret) + ctrl->ee = SWRM_EE_CPU; + if (ctrl->ee > SWRM_MAX_EE) { + dev_warn(dev, "invalid SoundWire EE %u, using EE%u\n", + ctrl->ee, SWRM_EE_CPU); + ctrl->ee = SWRM_EE_CPU; + } ctrl->max_reg = data->max_reg; + /* + * Defer EE register window selection until HW version is known. + * For v2.0+ the IRQ/FIFO window is EE-banked. + */ ctrl->reg_layout = data->reg_layout; ctrl->rows_index = sdw_find_row_index(data->default_rows); ctrl->cols_index = sdw_find_col_index(data->default_cols); @@ -1623,6 +1671,7 @@ static int qcom_swrm_probe(struct platform_device *pdev) prop->default_row = data->default_rows; ctrl->reg_read(ctrl, SWRM_COMP_HW_VERSION, &ctrl->version); + qcom_swrm_set_ee_register_layout(ctrl, data); ret = devm_request_threaded_irq(dev, ctrl->irq, NULL, qcom_swrm_irq_handler, @@ -1733,16 +1782,19 @@ static int __maybe_unused swrm_runtime_resume(struct device *dev) reset_control_reset(ctrl->audio_cgcr); if (ctrl->version == SWRM_VERSION_1_7_0) { - ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU); + ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, - SWRM_MCP_BUS_CLK_START << SWRM_EE_CPU); + SWRM_MCP_BUS_CLK_START << ctrl->ee); } else if (ctrl->version >= SWRM_VERSION_2_0_0) { - ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, SWRM_EE_CPU); - ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL, + ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); + ctrl->reg_write(ctrl, SWRM_V2_0_CLK_CTRL + + ((int)ctrl->ee - SWRM_EE_CPU) * + SWRM_V2_REG_EE_STRIDE, SWRM_V2_0_CLK_CTRL_CLK_START); } else { ctrl->reg_write(ctrl, SWRM_MCP_BUS_CTRL, SWRM_MCP_BUS_CLK_START); } + ctrl->reg_write(ctrl, ctrl->reg_layout[SWRM_REG_INTERRUPT_CLEAR], SWRM_INTERRUPT_STATUS_MASTER_CLASH_DET); diff --git a/include/drm/drm_atomic_state_helper.h b/include/drm/drm_atomic_state_helper.h index 61a3b38ad49fd..45e3d9db2b96b 100644 --- a/include/drm/drm_atomic_state_helper.h +++ b/include/drm/drm_atomic_state_helper.h @@ -99,4 +99,6 @@ void drm_atomic_helper_bridge_destroy_state(struct drm_bridge *bridge, void __drm_atomic_helper_bridge_reset(struct drm_bridge *bridge, struct drm_bridge_state *state); struct drm_bridge_state * +drm_atomic_helper_bridge_create_state(struct drm_bridge *bridge); +struct drm_bridge_state * drm_atomic_helper_bridge_reset(struct drm_bridge *bridge); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 4ba3a5deef9a6..c0703af611bae 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -530,6 +530,22 @@ struct drm_bridge_funcs { */ struct drm_bridge_state *(*atomic_reset)(struct drm_bridge *bridge); + /** + * @atomic_create_state: + * + * Allocate a pristine, initialized, state for the bridge + * object and return it. This callback must have no side + * effects: in particular, the returned state must not be + * assigned to the object's state pointer and it must not affect + * the hardware state. + * + * RETURNS: + * + * A new, pristine, bridge state instance or an error pointer + * on failure. + */ + struct drm_bridge_state *(*atomic_create_state)(struct drm_bridge *bridge); + /** * @detect: * @@ -1371,7 +1387,8 @@ drm_bridge_get_current_state(struct drm_bridge *bridge) * drm_atomic_private_obj_init(), so we need to make sure we're * working with one before we try to use the lock. */ - if (!bridge->funcs || !bridge->funcs->atomic_reset) + if (!bridge->funcs || + !(bridge->funcs->atomic_reset || bridge->funcs->atomic_create_state)) return NULL; drm_modeset_lock_assert_held(&bridge->base.lock); diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h index 45850f2d43425..3bc3925ab6654 100644 --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h @@ -233,6 +233,35 @@ /* Clock ID for RX CORE MCLK2 2X MCLK */ #define LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 70 +#define LPASS_CLK_ID_QAIF_IF0_IBIT 71 +#define LPASS_CLK_ID_QAIF_IF0_EBIT 72 +#define LPASS_CLK_ID_QAIF_IF1_IBIT 73 +#define LPASS_CLK_ID_QAIF_IF1_EBIT 74 +#define LPASS_CLK_ID_QAIF_IF2_IBIT 75 +#define LPASS_CLK_ID_QAIF_IF2_EBIT 76 +#define LPASS_CLK_ID_QAIF_IF3_IBIT 77 +#define LPASS_CLK_ID_QAIF_IF3_EBIT 78 +#define LPASS_CLK_ID_QAIF_IF4_IBIT 79 +#define LPASS_CLK_ID_QAIF_IF4_EBIT 80 +#define LPASS_CLK_ID_QAIF_IF5_IBIT 81 +#define LPASS_CLK_ID_QAIF_IF5_EBIT 82 +#define LPASS_CLK_ID_QAIF_IF6_IBIT 83 +#define LPASS_CLK_ID_QAIF_IF6_EBIT 84 +#define LPASS_CLK_ID_QAIF_IF7_IBIT 85 +#define LPASS_CLK_ID_QAIF_IF7_EBIT 86 +#define LPASS_CLK_ID_QAIF_IF8_IBIT 87 +#define LPASS_CLK_ID_QAIF_IF8_EBIT 88 +#define LPASS_CLK_ID_QAIF_IF9_IBIT 89 +#define LPASS_CLK_ID_QAIF_IF9_EBIT 90 +#define LPASS_CLK_ID_QAIF_IF10_IBIT 91 +#define LPASS_CLK_ID_QAIF_IF10_EBIT 92 +#define LPASS_CLK_ID_QAIF_IF11_IBIT 93 +#define LPASS_CLK_ID_QAIF_IF11_EBIT 94 +#define LPASS_CLK_ID_QAIF_IF12_IBIT 95 +#define LPASS_CLK_ID_QAIF_IF12_EBIT 96 +#define LPASS_CLK_ID_VA_QAIF_IF0_IBIT 97 +#define LPASS_CLK_ID_VA_QAIF_IF0_EBIT 98 + #define LPASS_HW_AVTIMER_VOTE 101 #define LPASS_HW_MACRO_VOTE 102 #define LPASS_HW_DCODEC_VOTE 103 diff --git a/include/dt-bindings/sound/qcom,qaif.h b/include/dt-bindings/sound/qcom,qaif.h new file mode 100644 index 0000000000000..24706efd07d14 --- /dev/null +++ b/include/dt-bindings/sound/qcom,qaif.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * DAI IDs for the Qualcomm Audio Interface (QAIF) controller. + * QAIF_MI2S_AIF* and QAIF_TDM_AIF* values are used in sound-dai + * references and as the reg value of aif@N child nodes. + * QAIF_CDC_DMA_* values are for sound-dai references only and must + * not be used as aif@N child node reg values. + */ +#ifndef __DT_QCOM_QAIF_H +#define __DT_QCOM_QAIF_H + +/* + * MI2S DAI IDs -- one per physical AIF port in MI2S mode. + * Each port supports up to 2 channels (stereo I2S) over a single + * data lane sharing a bit clock and frame sync. + */ +#define QAIF_MI2S_AIF0 200 +#define QAIF_MI2S_AIF1 201 +#define QAIF_MI2S_AIF2 202 +#define QAIF_MI2S_AIF3 203 +#define QAIF_MI2S_AIF4 204 +#define QAIF_MI2S_AIF5 205 +#define QAIF_MI2S_AIF6 206 +#define QAIF_MI2S_AIF7 207 +#define QAIF_MI2S_AIF8 208 +#define QAIF_MI2S_AIF9 209 +#define QAIF_MI2S_AIF10 210 +#define QAIF_MI2S_AIF11 211 +#define QAIF_MI2S_AIF12 212 + +/* + * TDM DAI IDs -- one per physical AIF port in TDM mode. + * Each port supports up to 8 channels over up to 8 independent data + * lanes sharing a single bit clock and frame sync. + */ +#define QAIF_TDM_AIF0 213 +#define QAIF_TDM_AIF1 214 +#define QAIF_TDM_AIF2 215 +#define QAIF_TDM_AIF3 216 +#define QAIF_TDM_AIF4 217 +#define QAIF_TDM_AIF5 218 +#define QAIF_TDM_AIF6 219 +#define QAIF_TDM_AIF7 220 +#define QAIF_TDM_AIF8 221 +#define QAIF_TDM_AIF9 222 +#define QAIF_TDM_AIF10 223 +#define QAIF_TDM_AIF11 224 +#define QAIF_TDM_AIF12 225 + +/* + * CIF (Codec Interface) RX DAI IDs -- playback to internal codec. + * RDDMA channels fetch audio from memory and drain it to the codec. + */ +#define QAIF_CDC_DMA_RX0 226 +#define QAIF_CDC_DMA_RX1 227 +#define QAIF_CDC_DMA_RX2 228 +#define QAIF_CDC_DMA_RX3 229 +#define QAIF_CDC_DMA_RX4 230 +#define QAIF_CDC_DMA_RX5 231 +#define QAIF_CDC_DMA_RX6 232 +#define QAIF_CDC_DMA_RX7 233 +#define QAIF_CDC_DMA_RX8 234 +#define QAIF_CDC_DMA_RX9 235 + +/* + * CIF (Codec Interface) TX DAI IDs -- capture from internal codec. + * WRDMA channels collect audio from the codec and write it to memory. + */ +#define QAIF_CDC_DMA_TX0 236 +#define QAIF_CDC_DMA_TX1 237 +#define QAIF_CDC_DMA_TX2 238 +#define QAIF_CDC_DMA_TX3 239 +#define QAIF_CDC_DMA_TX4 240 +#define QAIF_CDC_DMA_TX5 241 +#define QAIF_CDC_DMA_TX6 242 +#define QAIF_CDC_DMA_TX7 243 +#define QAIF_CDC_DMA_TX8 244 +#define QAIF_CDC_DMA_TX9 245 + +/* + * CIF (Codec Interface) VA TX DAI IDs -- capture from voice activity codec. + * WRDMA channels collect audio from the VA codec and write it to memory. + */ +#define QAIF_CDC_DMA_VA_TX0 246 +#define QAIF_CDC_DMA_VA_TX1 247 +#define QAIF_CDC_DMA_VA_TX2 248 +#define QAIF_CDC_DMA_VA_TX3 249 +#define QAIF_CDC_DMA_VA_TX4 250 +#define QAIF_CDC_DMA_VA_TX5 251 +#define QAIF_CDC_DMA_VA_TX6 252 +#define QAIF_CDC_DMA_VA_TX7 253 +#define QAIF_CDC_DMA_VA_TX8 254 +#define QAIF_CDC_DMA_VA_TX9 255 + +#endif /* __DT_QCOM_QAIF_H */ diff --git a/include/sound/soc.h b/include/sound/soc.h index 10ad80f930c2e..9d2b16a7ac2dc 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1329,6 +1329,9 @@ int snd_soc_of_parse_tdm_slot(struct device_node *np, unsigned int *rx_mask, unsigned int *slots, unsigned int *slot_width); +int snd_soc_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name); void snd_soc_of_parse_node_prefix(struct device_node *np, struct snd_soc_codec_conf *codec_conf, struct device_node *of_node, diff --git a/include/uapi/sound/snd_ar_tokens.h b/include/uapi/sound/snd_ar_tokens.h index 6b8102eaa121f..1700e3f5cb64a 100644 --- a/include/uapi/sound/snd_ar_tokens.h +++ b/include/uapi/sound/snd_ar_tokens.h @@ -168,6 +168,60 @@ enum ar_event_types { * LOG_WAIT = 0, * LOG_IMMEDIATELY = 1 * + * %AR_TKN_U16_MODULE_SYNC_SRC: Frame sync source + * AR_AUDIO_IF_SYNC_SRC_EXTERNAL = 0, + * AR_AUDIO_IF_SYNC_SRC_INTERNAL = 1 + * + * %AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: Enable data-out tri-state control + * AR_AUDIO_IF_CTRL_DATA_OE_DISABLE = 0, + * AR_AUDIO_IF_CTRL_DATA_OE_ENABLE = 1 + * + * %AR_TKN_U32_MODULE_SLOT_MASK: Active TDM slot bitmask + * + * %AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: Number of slots per TDM frame + * + * %AR_TKN_U16_MODULE_SLOT_WIDTH: Slot width in bits (16 or 32) + * + * %AR_TKN_U16_MODULE_SYNC_MODE: Frame sync mode + * AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT = 0, + * AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT = 1, + * AR_AUDIO_IF_FRAME_SYNC_MODE_LONG = 2 + * + * %AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: Invert frame sync pulse polarity + * AR_AUDIO_IF_SYNC_NORMAL = 0, + * AR_AUDIO_IF_SYNC_INVERTED = 1 + * + * %AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: Data delay relative to frame sync + * AR_AUDIO_IF_DATA_DELAY_NONE = 0, + * AR_AUDIO_IF_DATA_DELAY_1_CYCLE = 1, + * AR_AUDIO_IF_DATA_DELAY_2_CYCLE = 2 + * + * %AR_TKN_U16_MODULE_INTF_MODE: Audio IF interface mode + * AR_AUDIO_IF_INTF_MODE_TDM = 0, + * AR_AUDIO_IF_INTF_MODE_PCM = 1, + * AR_AUDIO_IF_INTF_MODE_I2S = 2 + * + * %AR_TKN_U16_MODULE_QAIF_TYPE: QAIF hardware port type index + * AR_AUDIO_IF_QAIF = 0, + * AR_AUDIO_IF_QAIF_VA = 1 + * + * %AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: Active lane bitmask for multi-lane + * + * %AR_TKN_U32_MODULE_FRAME_SYNC_RATE: Frame sync rate in Hz + * + * %AR_TKN_U16_MODULE_BIT_CLK_TYPE: Bit clock type + * AR_AUDIO_IF_BIT_CLK_INTERNAL = 0, + * AR_AUDIO_IF_BIT_CLK_EXTERNAL = 1, + * AR_AUDIO_IF_BIT_CLK_SKIP = 2 + * + * %AR_TKN_U8_MODULE_INV_INT_BIT_CLK: Invert internal bit clock + * AR_AUDIO_IF_CLK_NORMAL = 0, + * AR_AUDIO_IF_CLK_INVERTED = 1 + * + * %AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: Invert external bit clock + * AR_AUDIO_IF_CLK_NORMAL = 0, + * AR_AUDIO_IF_CLK_INVERTED = 1 + * * %AR_TKN_DAI_INDEX: dai index * */ @@ -240,6 +294,45 @@ enum ar_event_types { #define AR_TKN_U32_MODULE_LOG_TAP_POINT_ID 260 #define AR_TKN_U32_MODULE_LOG_MODE 261 +#define AR_TKN_U16_MODULE_SYNC_SRC 262 +#define AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE 263 +#define AR_TKN_U32_MODULE_SLOT_MASK 264 +#define AR_TKN_U16_MODULE_NSLOTS_PER_FRAME 265 +#define AR_TKN_U16_MODULE_SLOT_WIDTH 266 +#define AR_TKN_U16_MODULE_SYNC_MODE 267 +#define AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE 268 +#define AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY 269 +#define AR_TKN_U16_MODULE_INTF_MODE 270 +#define AR_TKN_U16_MODULE_QAIF_TYPE 271 +#define AR_TKN_U32_MODULE_ACTIVE_LANE_MASK 272 +#define AR_TKN_U32_MODULE_FRAME_SYNC_RATE 273 +#define AR_TKN_U16_MODULE_BIT_CLK_TYPE 274 +#define AR_TKN_U8_MODULE_INV_INT_BIT_CLK 275 +#define AR_TKN_U8_MODULE_INV_EXT_BIT_CLK 276 + +#define AR_AUDIO_IF_SYNC_SRC_EXTERNAL 0 +#define AR_AUDIO_IF_SYNC_SRC_INTERNAL 1 +#define AR_AUDIO_IF_CTRL_DATA_OE_DISABLE 0 +#define AR_AUDIO_IF_CTRL_DATA_OE_ENABLE 1 +#define AR_AUDIO_IF_INTF_MODE_TDM 0 +#define AR_AUDIO_IF_INTF_MODE_PCM 1 +#define AR_AUDIO_IF_INTF_MODE_I2S 2 +#define AR_AUDIO_IF_QAIF 0 +#define AR_AUDIO_IF_QAIF_VA 1 +#define AR_AUDIO_IF_FRAME_SYNC_MODE_SHORT 0 +#define AR_AUDIO_IF_FRAME_SYNC_MODE_ONE_SLOT 1 +#define AR_AUDIO_IF_FRAME_SYNC_MODE_LONG 2 +#define AR_AUDIO_IF_SYNC_NORMAL 0 +#define AR_AUDIO_IF_SYNC_INVERTED 1 +#define AR_AUDIO_IF_DATA_DELAY_NONE 0 +#define AR_AUDIO_IF_DATA_DELAY_1_CYCLE 1 +#define AR_AUDIO_IF_DATA_DELAY_2_CYCLE 2 +#define AR_AUDIO_IF_BIT_CLK_INTERNAL 0 +#define AR_AUDIO_IF_BIT_CLK_EXTERNAL 1 +#define AR_AUDIO_IF_BIT_CLK_SKIP 2 +#define AR_AUDIO_IF_CLK_NORMAL 0 +#define AR_AUDIO_IF_CLK_INVERTED 1 + #define SND_SOC_AR_TPLG_MODULE_CFG_TYPE 0x01001006 struct audioreach_module_priv_data { __le32 size; /* size in bytes of the array, including all elements */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 252f683be3c18..4bae3c246ca06 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -367,6 +367,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_WSA881X imply SND_SOC_WSA883X imply SND_SOC_WSA884X + imply SND_SOC_WSA885X imply SND_SOC_ZL38060 help Normally ASoC codec drivers are only built if a machine driver which @@ -2770,6 +2771,16 @@ config SND_SOC_WSA884X This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D Smart Speaker Amplifier. +config SND_SOC_WSA885X + tristate "WSA885X Codec" + depends on I2C + select REGMAP_I2C + help + This enables support for Qualcomm WSA885X Stereo Smart Speaker + Amplifier. The codec driver programs the amplifier register + map and exposes the DAI and mixer controls used by Qualcomm + audio machine drivers. + config SND_SOC_ZL38060 tristate "Microsemi ZL38060 Connected Home Audio Processor" depends on SPI_MASTER diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index aa0396e5b575b..7a37f44724a6a 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -426,6 +426,7 @@ snd-soc-wm-hubs-y := wm_hubs.o snd-soc-wsa881x-y := wsa881x.o snd-soc-wsa883x-y := wsa883x.o snd-soc-wsa884x-y := wsa884x.o +snd-soc-wsa885x-y := wsa885x.o snd-soc-zl38060-y := zl38060.o # Amp snd-soc-max9877-y := max9877.o @@ -876,6 +877,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o +obj-$(CONFIG_SND_SOC_WSA885X) += snd-soc-wsa885x.o obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o # Amp diff --git a/sound/soc/codecs/lpass-macro-common.h b/sound/soc/codecs/lpass-macro-common.h index 10ad682019fa7..86adad190a102 100644 --- a/sound/soc/codecs/lpass-macro-common.h +++ b/sound/soc/codecs/lpass-macro-common.h @@ -10,6 +10,8 @@ #define LPASS_MACRO_FLAG_HAS_NPL_CLOCK BIT(0) /* The soundwire block should be internally reset at probe */ #define LPASS_MACRO_FLAG_RESET_SWR BIT(1) +/* FS counter control bit[7] must be toggled (v4.0) */ +#define LPASS_MACRO_FLAG_BYPASS_FS_CONTROL BIT(2) enum lpass_version { LPASS_VER_9_0_0, @@ -30,6 +32,7 @@ enum lpass_codec_version { LPASS_CODEC_VERSION_2_7, LPASS_CODEC_VERSION_2_8, LPASS_CODEC_VERSION_2_9, + LPASS_CODEC_VERSION_4_0, }; struct lpass_macro { @@ -68,6 +71,8 @@ static inline const char *lpass_macro_get_codec_version_string(int version) return "v2.7"; case LPASS_CODEC_VERSION_2_8: return "v2.8"; + case LPASS_CODEC_VERSION_4_0: + return "v4.0"; default: break; } diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c index 6233aa9f5bc6d..3a3dec6b14cda 100644 --- a/sound/soc/codecs/lpass-rx-macro.c +++ b/sound/soc/codecs/lpass-rx-macro.c @@ -646,6 +646,7 @@ struct rx_macro { int clsh_users; int rx_mclk_cnt; enum lpass_codec_version codec_version; + bool bypass_fs_control; int rxn_reg_stride; int rxn_reg_stride2; bool is_ear_mode_on; @@ -1612,6 +1613,7 @@ static bool rx_is_rw_register(struct device *dev, unsigned int reg) case LPASS_CODEC_VERSION_2_6: case LPASS_CODEC_VERSION_2_7: case LPASS_CODEC_VERSION_2_8: + case LPASS_CODEC_VERSION_4_0: return rx_2_5_is_rw_register(dev, reg); default: break; @@ -2043,6 +2045,11 @@ static void rx_macro_mclk_enable(struct rx_macro *rx, bool mclk_enable) CDC_RX_CLK_MCLK2_ENABLE); regmap_update_bits(regmap, CDC_RX_CLK_RST_CTRL_FS_CNT_CONTROL, CDC_RX_FS_MCLK_CNT_CLR_MASK, 0x00); + + if (rx->bypass_fs_control) + regmap_update_bits(regmap, + CDC_RX_CLK_RST_CTRL_FS_CNT_CONTROL, + 0x80, 0x80); regmap_update_bits(regmap, CDC_RX_CLK_RST_CTRL_FS_CNT_CONTROL, CDC_RX_FS_MCLK_CNT_EN_MASK, CDC_RX_FS_MCLK_CNT_ENABLE); @@ -3648,6 +3655,7 @@ static int rx_macro_component_probe(struct snd_soc_component *component) case LPASS_CODEC_VERSION_2_6: case LPASS_CODEC_VERSION_2_7: case LPASS_CODEC_VERSION_2_8: + case LPASS_CODEC_VERSION_4_0: controls = rx_macro_2_5_snd_controls; num_controls = ARRAY_SIZE(rx_macro_2_5_snd_controls); widgets = rx_macro_2_5_dapm_widgets; @@ -3809,6 +3817,7 @@ static int rx_macro_probe(struct platform_device *pdev) return PTR_ERR(base); rx->codec_version = lpass_macro_get_codec_version(); + rx->bypass_fs_control = !!(flags & LPASS_MACRO_FLAG_BYPASS_FS_CONTROL); struct reg_default *reg_defaults __free(kfree) = NULL; switch (rx->codec_version) { @@ -3831,6 +3840,7 @@ static int rx_macro_probe(struct platform_device *pdev) case LPASS_CODEC_VERSION_2_6: case LPASS_CODEC_VERSION_2_7: case LPASS_CODEC_VERSION_2_8: + case LPASS_CODEC_VERSION_4_0: rx->rxn_reg_stride = 0xc0; rx->rxn_reg_stride2 = 0x0; def_count = ARRAY_SIZE(rx_defaults) + ARRAY_SIZE(rx_2_5_defaults); @@ -3961,6 +3971,10 @@ static const struct of_device_id rx_macro_dt_match[] = { }, { .compatible = "qcom,sc8280xp-lpass-rx-macro", .data = (void *)LPASS_MACRO_FLAG_HAS_NPL_CLOCK, + }, { + .compatible = "qcom,shikra-lpass-rx-macro", + .data = (void *)(LPASS_MACRO_FLAG_HAS_NPL_CLOCK | + LPASS_MACRO_FLAG_BYPASS_FS_CONTROL), }, { } }; diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c index 58a5798823d7f..16aa64cc3f0b8 100644 --- a/sound/soc/codecs/lpass-va-macro.c +++ b/sound/soc/codecs/lpass-va-macro.c @@ -155,7 +155,57 @@ #define CDC_VA_TX3_TX_PATH_SEC5 (0x05A4) #define CDC_VA_TX3_TX_PATH_SEC6 (0x05A8) +/* ADPT control registers - Shikra adaptive filter blocks */ +#define CDC_VA_CDC_ADPT0_ADPT_CTRL (0x0800) +#define CDC_VA_CDC_ADPT0_ADPT_GAIN_0 (0x0804) +#define CDC_VA_CDC_ADPT0_ADPT_GAIN_1 (0x0808) +#define CDC_VA_CDC_ADPT0_DH_FSM_CTRL (0x080C) +#define CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_0 (0x0810) +#define CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_1 (0x0814) +#define CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_2 (0x0818) +#define CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_3 (0x081C) +#define CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_4 (0x0820) +#define CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_5 (0x0824) + +#define CDC_VA_CDC_ADPT1_ADPT_CTRL (0x0880) +#define CDC_VA_CDC_ADPT1_ADPT_GAIN_0 (0x0884) +#define CDC_VA_CDC_ADPT1_ADPT_GAIN_1 (0x0888) +#define CDC_VA_CDC_ADPT1_DH_FSM_CTRL (0x088C) +#define CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_0 (0x0890) +#define CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_1 (0x0894) +#define CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_2 (0x0898) +#define CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_3 (0x089C) +#define CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_4 (0x08A0) +#define CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_5 (0x08A4) +#define CDC_VA_CDC_ADPT1_DBG_CTRL (0x08B0) +#define CDC_VA_CDC_ADPT1_DBG_PDM_RATE_CTRL_0 (0x08B2) +#define CDC_VA_CDC_ADPT1_DBG_PDM_RATE_CTRL_1 (0x08B4) +#define CDC_VA_CDC_ADPT1_SPARE0 (0x08B8) + +#define CDC_VA_CDC_ADPT2_ADPT_CTRL (0x0900) +#define CDC_VA_CDC_ADPT2_ADPT_GAIN_0 (0x0904) +#define CDC_VA_CDC_ADPT2_ADPT_GAIN_1 (0x0908) +#define CDC_VA_CDC_ADPT2_DH_FSM_CTRL (0x090C) +#define CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_0 (0x0910) +#define CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_1 (0x0914) +#define CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_2 (0x0918) +#define CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_3 (0x091C) +#define CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_4 (0x0920) +#define CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_5 (0x0924) + +#define CDC_VA_CDC_ADPT3_ADPT_CTRL (0x0980) +#define CDC_VA_CDC_ADPT3_ADPT_GAIN_0 (0x0984) +#define CDC_VA_CDC_ADPT3_ADPT_GAIN_1 (0x0988) +#define CDC_VA_CDC_ADPT3_DH_FSM_CTRL (0x098C) +#define CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_0 (0x0990) +#define CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_1 (0x0994) +#define CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_2 (0x0998) +#define CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_3 (0x099C) +#define CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_4 (0x09A0) +#define CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_5 (0x09A4) + #define VA_MAX_OFFSET (0x07A8) +#define VA_SHIKRA_MAX_OFFSET (0x0980) #define VA_MACRO_NUM_DECIMATORS 4 #define VA_MACRO_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ @@ -209,6 +259,8 @@ struct va_macro { u16 dmic_clk_div; bool has_swr_master; bool has_npl_clk; + bool bypass_fs_control; + bool has_adpt_block; int dec_mode[VA_MACRO_NUM_DECIMATORS]; struct regmap *regmap; @@ -235,28 +287,10 @@ struct va_macro { struct va_macro_data { bool has_swr_master; bool has_npl_clk; + bool bypass_fs_control; + bool has_adpt_block; int version; -}; - -static const struct va_macro_data sm8250_va_data = { - .has_swr_master = false, - .has_npl_clk = false, - .version = LPASS_CODEC_VERSION_1_0, -}; - -static const struct va_macro_data sc7280_va_data = { - .has_swr_master = false, - .has_npl_clk = false, -}; - -static const struct va_macro_data sm8450_va_data = { - .has_swr_master = true, - .has_npl_clk = true, -}; - -static const struct va_macro_data sm8550_va_data = { - .has_swr_master = true, - .has_npl_clk = false, + const struct regmap_config *regmap_config; }; static bool va_is_volatile_register(struct device *dev, unsigned int reg) @@ -429,6 +463,10 @@ static bool va_is_rw_register(struct device *dev, unsigned int reg) case CDC_VA_TX3_TX_PATH_SEC4: case CDC_VA_TX3_TX_PATH_SEC5: case CDC_VA_TX3_TX_PATH_SEC6: + case CDC_VA_CDC_ADPT0_ADPT_CTRL: + case CDC_VA_CDC_ADPT1_ADPT_CTRL: + case CDC_VA_CDC_ADPT2_ADPT_CTRL: + case CDC_VA_CDC_ADPT3_ADPT_CTRL: return true; } @@ -462,6 +500,179 @@ static const struct regmap_config va_regmap_config = { .writeable_reg = va_is_rw_register, }; +static const struct reg_default va_shikra_defaults[] = { + /* VA macro */ + { CDC_VA_CLK_RST_CTRL_MCLK_CONTROL, 0x00}, + { CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL, 0x00}, + { CDC_VA_CLK_RST_CTRL_SWR_CONTROL, 0x00}, + { CDC_VA_TOP_CSR_TOP_CFG0, 0x00}, + { CDC_VA_TOP_CSR_DMIC0_CTL, 0x00}, + { CDC_VA_TOP_CSR_DMIC1_CTL, 0x00}, + { CDC_VA_TOP_CSR_DMIC2_CTL, 0x00}, + { CDC_VA_TOP_CSR_DMIC3_CTL, 0x00}, + { CDC_VA_TOP_CSR_DMIC_CFG, 0x80}, + { CDC_VA_TOP_CSR_DEBUG_BUS, 0x00}, + { CDC_VA_TOP_CSR_DEBUG_EN, 0x00}, + { CDC_VA_TOP_CSR_TX_I2S_CTL, 0x0C}, + { CDC_VA_TOP_CSR_I2S_CLK, 0x00}, + { CDC_VA_TOP_CSR_I2S_RESET, 0x00}, + { CDC_VA_TOP_CSR_CORE_ID_0, 0x00}, + { CDC_VA_TOP_CSR_CORE_ID_1, 0x00}, + { CDC_VA_TOP_CSR_CORE_ID_2, 0x00}, + { CDC_VA_TOP_CSR_CORE_ID_3, 0x00}, + { CDC_VA_TOP_CSR_SWR_MIC_CTL0, 0xEE}, + { CDC_VA_TOP_CSR_SWR_MIC_CTL1, 0xEE}, + { CDC_VA_TOP_CSR_SWR_MIC_CTL2, 0xEE}, + { CDC_VA_TOP_CSR_SWR_CTRL, 0x06}, + /* VA core */ + { CDC_VA_INP_MUX_ADC_MUX0_CFG0, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX0_CFG1, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX1_CFG0, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX1_CFG1, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX2_CFG0, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX2_CFG1, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX3_CFG0, 0x00}, + { CDC_VA_INP_MUX_ADC_MUX3_CFG1, 0x00}, + { CDC_VA_TX0_TX_PATH_CTL, 0x04}, + { CDC_VA_TX0_TX_PATH_CFG0, 0x10}, + { CDC_VA_TX0_TX_PATH_CFG1, 0x0B}, + { CDC_VA_TX0_TX_VOL_CTL, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC0, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC1, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC2, 0x01}, + { CDC_VA_TX0_TX_PATH_SEC3, 0x3C}, + { CDC_VA_TX0_TX_PATH_SEC4, 0x20}, + { CDC_VA_TX0_TX_PATH_SEC5, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC6, 0x00}, + { CDC_VA_TX0_TX_PATH_SEC7, 0x25}, + { CDC_VA_TX1_TX_PATH_CTL, 0x04}, + { CDC_VA_TX1_TX_PATH_CFG0, 0x10}, + { CDC_VA_TX1_TX_PATH_CFG1, 0x0B}, + { CDC_VA_TX1_TX_VOL_CTL, 0x00}, + { CDC_VA_TX1_TX_PATH_SEC0, 0x00}, + { CDC_VA_TX1_TX_PATH_SEC1, 0x00}, + { CDC_VA_TX1_TX_PATH_SEC2, 0x01}, + { CDC_VA_TX1_TX_PATH_SEC3, 0x3C}, + { CDC_VA_TX1_TX_PATH_SEC4, 0x20}, + { CDC_VA_TX1_TX_PATH_SEC5, 0x00}, + { CDC_VA_TX1_TX_PATH_SEC6, 0x00}, + { CDC_VA_TX2_TX_PATH_CTL, 0x04}, + { CDC_VA_TX2_TX_PATH_CFG0, 0x10}, + { CDC_VA_TX2_TX_PATH_CFG1, 0x0B}, + { CDC_VA_TX2_TX_VOL_CTL, 0x00}, + { CDC_VA_TX2_TX_PATH_SEC0, 0x00}, + { CDC_VA_TX2_TX_PATH_SEC1, 0x00}, + { CDC_VA_TX2_TX_PATH_SEC2, 0x01}, + { CDC_VA_TX2_TX_PATH_SEC3, 0x3C}, + { CDC_VA_TX2_TX_PATH_SEC4, 0x20}, + { CDC_VA_TX2_TX_PATH_SEC5, 0x00}, + { CDC_VA_TX2_TX_PATH_SEC6, 0x00}, + { CDC_VA_TX3_TX_PATH_CTL, 0x04}, + { CDC_VA_TX3_TX_PATH_CFG0, 0x10}, + { CDC_VA_TX3_TX_PATH_CFG1, 0x0B}, + { CDC_VA_TX3_TX_VOL_CTL, 0x00}, + { CDC_VA_TX3_TX_PATH_SEC0, 0x00}, + { CDC_VA_TX3_TX_PATH_SEC1, 0x00}, + { CDC_VA_TX3_TX_PATH_SEC2, 0x01}, + { CDC_VA_TX3_TX_PATH_SEC3, 0x3C}, + { CDC_VA_TX3_TX_PATH_SEC4, 0x20}, + { CDC_VA_TX3_TX_PATH_SEC5, 0x00}, + { CDC_VA_TX3_TX_PATH_SEC6, 0x00}, + /* ADPT blocks - Shikra adaptive filter control */ + + /* CDC ADPT0 - adaptive filter */ + { CDC_VA_CDC_ADPT0_ADPT_CTRL, 0x51}, + { CDC_VA_CDC_ADPT0_ADPT_GAIN_0, 0x11}, + { CDC_VA_CDC_ADPT0_ADPT_GAIN_1, 0x01}, + { CDC_VA_CDC_ADPT0_DH_FSM_CTRL, 0x02}, + { CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_0, 0x77}, + { CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_1, 0x64}, + { CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_2, 0x00}, + { CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_3, 0x41}, + { CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_4, 0x04}, + { CDC_VA_CDC_ADPT0_CUTOFF_FSM_CTRL_5, 0x01}, + + /* CDC ADPT1 */ + { CDC_VA_CDC_ADPT1_ADPT_CTRL, 0x51}, + { CDC_VA_CDC_ADPT1_ADPT_GAIN_0, 0x11}, + { CDC_VA_CDC_ADPT1_ADPT_GAIN_1, 0x01}, + { CDC_VA_CDC_ADPT1_DH_FSM_CTRL, 0x02}, + { CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_0, 0x77}, + { CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_1, 0x64}, + { CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_2, 0x00}, + { CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_3, 0x41}, + { CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_4, 0x04}, + { CDC_VA_CDC_ADPT1_CUTOFF_FSM_CTRL_5, 0x01}, + + /* CDC ADPT2 */ + { CDC_VA_CDC_ADPT2_ADPT_CTRL, 0x51}, + { CDC_VA_CDC_ADPT2_ADPT_GAIN_0, 0x11}, + { CDC_VA_CDC_ADPT2_ADPT_GAIN_1, 0x01}, + { CDC_VA_CDC_ADPT2_DH_FSM_CTRL, 0x02}, + { CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_0, 0x77}, + { CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_1, 0x64}, + { CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_2, 0x00}, + { CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_3, 0x41}, + { CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_4, 0x04}, + { CDC_VA_CDC_ADPT2_CUTOFF_FSM_CTRL_5, 0x01}, + + /* CDC ADPT3 */ + { CDC_VA_CDC_ADPT3_ADPT_CTRL, 0x51}, + { CDC_VA_CDC_ADPT3_ADPT_GAIN_0, 0x11}, + { CDC_VA_CDC_ADPT3_ADPT_GAIN_1, 0x01}, + { CDC_VA_CDC_ADPT3_DH_FSM_CTRL, 0x02}, + { CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_0, 0x77}, + { CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_1, 0x64}, + { CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_2, 0x00}, + { CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_3, 0x41}, + { CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_4, 0x04}, + { CDC_VA_CDC_ADPT3_CUTOFF_FSM_CTRL_5, 0x01}, +}; + +static const struct regmap_config shikra_va_regmap_config = { + .name = "va_macro", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .cache_type = REGCACHE_FLAT, + .reg_defaults = va_shikra_defaults, + .num_reg_defaults = ARRAY_SIZE(va_shikra_defaults), + .max_register = VA_SHIKRA_MAX_OFFSET, + .volatile_reg = va_is_volatile_register, + .readable_reg = va_is_readable_register, + .writeable_reg = va_is_rw_register, +}; + +static const struct va_macro_data sc7280_va_data = { + .has_swr_master = false, + .has_npl_clk = false, +}; + +static const struct va_macro_data sm8250_va_data = { + .has_swr_master = false, + .has_npl_clk = false, + .version = LPASS_CODEC_VERSION_1_0, +}; + +static const struct va_macro_data sm8450_va_data = { + .has_swr_master = true, + .has_npl_clk = true, +}; + +static const struct va_macro_data shikra_va_data = { + .has_swr_master = true, + .has_npl_clk = true, + .bypass_fs_control = true, + .has_adpt_block = true, + .version = LPASS_CODEC_VERSION_4_0, + .regmap_config = &shikra_va_regmap_config, +}; + +static const struct va_macro_data sm8550_va_data = { + .has_swr_master = true, + .has_npl_clk = false, +}; + static int va_clk_rsc_fs_gen_request(struct va_macro *va, bool enable) { struct regmap *regmap = va->regmap; @@ -474,6 +685,10 @@ static int va_clk_rsc_fs_gen_request(struct va_macro *va, bool enable) regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL, CDC_VA_FS_CONTROL_EN | CDC_VA_FS_COUNTER_CLR, CDC_VA_FS_CONTROL_EN | CDC_VA_FS_COUNTER_CLR); + + if (va->bypass_fs_control) + regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL, + 0x80, 0x80); regmap_update_bits(regmap, CDC_VA_CLK_RST_CTRL_FS_CNT_CONTROL, CDC_VA_FS_CONTROL_EN | CDC_VA_FS_COUNTER_CLR, CDC_VA_FS_CONTROL_EN); @@ -502,7 +717,7 @@ static int va_macro_mclk_enable(struct va_macro *va, bool mclk_enable) if (mclk_enable) { va_clk_rsc_fs_gen_request(va, true); regcache_mark_dirty(regmap); - regcache_sync_region(regmap, 0x0, VA_MAX_OFFSET); + regcache_sync_region(regmap, 0x0, regmap_get_max_register(regmap)); } else { va_clk_rsc_fs_gen_request(va, false); } @@ -748,6 +963,7 @@ static int va_macro_enable_dec(struct snd_soc_dapm_widget *w, unsigned int decimator; u16 tx_vol_ctl_reg, dec_cfg_reg, hpf_gate_reg; u16 tx_gain_ctl_reg; + u16 adapt_ctrl; u8 hpf_cut_off_freq; struct va_macro *va = snd_soc_component_get_drvdata(comp); @@ -762,6 +978,8 @@ static int va_macro_enable_dec(struct snd_soc_dapm_widget *w, VA_MACRO_TX_PATH_OFFSET * decimator; tx_gain_ctl_reg = CDC_VA_TX0_TX_VOL_CTL + VA_MACRO_TX_PATH_OFFSET * decimator; + adapt_ctrl = CDC_VA_CDC_ADPT0_ADPT_CTRL + + (decimator * VA_MACRO_TX_PATH_OFFSET); switch (event) { case SND_SOC_DAPM_PRE_PMU: @@ -771,6 +989,8 @@ static int va_macro_enable_dec(struct snd_soc_dapm_widget *w, /* Enable TX PGA Mute */ break; case SND_SOC_DAPM_POST_PMU: + if (va->has_adpt_block) + snd_soc_component_update_bits(comp, adapt_ctrl, 0xFF, 0x00); /* Enable TX CLK */ snd_soc_component_update_bits(comp, tx_vol_ctl_reg, CDC_VA_TX_PATH_CLK_EN_MASK, @@ -1516,6 +1736,15 @@ static int va_macro_set_lpass_codec_version(struct va_macro *va) default: break; } + } else if (maj == 4) { + switch (min) { + case 0: + version = LPASS_CODEC_VERSION_4_0; + break; + default: + break; + } + } if (version == LPASS_CODEC_VERSION_UNKNOWN) { @@ -1581,7 +1810,15 @@ static int va_macro_probe(struct platform_device *pdev) goto err; } - va->regmap = devm_regmap_init_mmio(dev, base, &va_regmap_config); + data = of_device_get_match_data(dev); + va->has_swr_master = data->has_swr_master; + va->has_npl_clk = data->has_npl_clk; + va->bypass_fs_control = data->bypass_fs_control; + va->has_adpt_block = data->has_adpt_block; + + va->regmap = devm_regmap_init_mmio(dev, base, + data->regmap_config ? data->regmap_config + : &va_regmap_config); if (IS_ERR(va->regmap)) { ret = -EINVAL; goto err; @@ -1589,10 +1826,6 @@ static int va_macro_probe(struct platform_device *pdev) dev_set_drvdata(dev, va); - data = of_device_get_match_data(dev); - va->has_swr_master = data->has_swr_master; - va->has_npl_clk = data->has_npl_clk; - /* mclk rate */ clk_set_rate(va->mclk, 2 * VA_MACRO_MCLK_FREQ); @@ -1766,6 +1999,7 @@ static const struct of_device_id va_macro_dt_match[] = { { .compatible = "qcom,sm8450-lpass-va-macro", .data = &sm8450_va_data }, { .compatible = "qcom,sm8550-lpass-va-macro", .data = &sm8550_va_data }, { .compatible = "qcom,sc8280xp-lpass-va-macro", .data = &sm8450_va_data }, + { .compatible = "qcom,shikra-lpass-va-macro", .data = &shikra_va_data }, {} }; MODULE_DEVICE_TABLE(of, va_macro_dt_match); diff --git a/sound/soc/codecs/wsa885x.c b/sound/soc/codecs/wsa885x.c new file mode 100644 index 0000000000000..1faa8541a872e --- /dev/null +++ b/sound/soc/codecs/wsa885x.c @@ -0,0 +1,1533 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +/* WSA885X codec driver */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Control Registers - Audio Processing */ +#define WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S 0x0000 +#define WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK 0x0004 +#define WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX 0x0140 +#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID 0x0208 +#define WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX 0x0240 +#define WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER 0x0340 +#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0 0x4405 +#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1 0x4406 +#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB 0x4409 +#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB 0x6409 +#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB 0x440a +#define WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB 0x640a +#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS 0x0a04 +#define WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS 0x0a40 +#define WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE 0x0b10 +#define WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX 0x0e40 + +/* Analog Top Registers - Power and Clock Control */ +#define WSA885X_ANA_TOP_PON_CKSK_CTL_0 0x800d +#define WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG 0x8024 +#define WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG 0x8025 +#define WSA885X_ANA_TOP_BG_TVP_OVRD_CTL 0x8034 + +/* Analog PLL Registers */ +#define WSA885X_ANA_PLL_DIV_CTL_0 0x8090 +#define WSA885X_ANA_PLL_DIV_CTL_1 0x8091 +#define WSA885X_ANA_TOP_PLL_VCO_CTL 0x8092 +#define WSA885X_ANA_TOP_PLL_LOOPFILT_0 0x8093 +#define WSA885X_ANA_TOP_PLL_OVRD_CTL 0x8098 +#define WSA885X_ANA_TOP_PLL_STATUS_0 0x809a +#define WSA885X_ANA_TOP_PLL_STATUS_1 0x809b + +/* Analog Boost Control Registers */ +#define WSA885X_ANA_TOP_BOOST_STB_CTRL2 0x805b +#define WSA885X_ANA_TOP_BOOST_STB_CTRL3 0x805c +#define WSA885X_ANA_TOP_BOOST_BYP_CTRL2 0x805e +#define WSA885X_ANA_TOP_BOOST_BYP_CTRL3 0x805f +#define WSA885X_ANA_TOP_BOOST_MISC 0x8063 +#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2 0x8065 +#define WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4 0x8067 + +/* Analog IV Sense ADC Registers */ +#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2 0x80ca +#define WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3 0x80cb +#define WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL 0x80cc +#define WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2 0x80d0 + +/* Analog Speaker Power Stage Registers */ +#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3 0x8108 +#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3 0x810b +#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3 0x810e +#define WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3 0x8111 +#define WSA885X_ANA_TOP_SPK_TOP_SPARE3 0x813c +#define WSA885X_SPK_TOP_LF_CH1_CTRL11 0x811c +#define WSA885X_SPK_TOP_LF_CH1_TUNE1 0x811d +#define WSA885X_SPK_TOP_LF_CH2_TUNE1 0x8129 +#define WSA885X_SPK_TOP_LF_CH1_CTRL9 0x811a +#define WSA885X_SPK_TOP_LF_CH2_CTRL9 0x8126 +#define WSA885X_SPK_TOP_LF_CH2_CTRL11 0x8128 +#define WSA885X_SPK_TOP_COMMON_CTRL2 0x8102 +#define WSA885X_SPK_TOP_COMMON_TUNE1 0x8103 +#define WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1 0x80ba +#define WSA885X_DIG_CTRL0_TOP_CLK_CFG 0x8418 +#define WSA885X_DIG_CTRL0_SDCA_COMMIT 0x8419 +#define WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE 0x841a +#define WSA885X_DIG_CTRL0_SYS_CLK_SEL 0x841b +#define WSA885X_DIG_CTRL0_CDC_CLK_CTL 0x841c +#define WSA885X_DIG_CTRL0_PA_FSM_CTL 0x8420 +#define WSA885X_DIG_CTRL0_POWER_FSM_CTL0 0x8423 +#define WSA885X_DIG_CTRL0_POWER_FSM_CTL1 0x8424 +#define WSA885X_DIG_CTRL0_PA0_FSM_CTL1 0x842b +#define WSA885X_DIG_CTRL0_PA1_FSM_CTL1 0x8435 +#define WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL 0x8458 +#define WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL 0x8470 +#define WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1 0x84b4 +#define WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1 0x84b7 +#define WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1 0x84A0 +#define WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1 0x84A4 + +/* Digital Control 1 Registers - I2S/TDM Interface */ +#define WSA885X_DIG_CTRL1_I2S_CTL0 0x85A0 +#define WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX 0x85A2 +#define WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX 0x85A3 +#define WSA885X_DIG_CTRL1_I2S_TDM_CTL0 0x85A7 +#define WSA885X_DIG_CTRL1_I2S_TDM_CTL1 0x85A9 +#define WSA885X_DIG_CTRL1_I2S_TDM_CH_RX 0x85AA +#define WSA885X_DIG_CTRL1_I2S_TDM_CH_TX 0x85AB +#define WSA885X_DIG_CTRL1_I2S_RESET_CTL 0x85AE + +/* CDC RX Path Registers - Audio Data Path */ +#define WSA885X_CDC_RX0_RX_PATH_CFG0 0x8601 +#define WSA885X_CDC_RX0_RX_PATH_CFG1 0x8602 +#define WSA885X_CDC_RX0_RX_PATH_CTL 0x8606 +#define WSA885X_RX0_RX_PATH_DSMDEM_CTL 0x8613 +#define WSA885X_CDC_RX1_RX_PATH_CFG0 0x8621 +#define WSA885X_CDC_RX1_RX_PATH_CFG1 0x8622 +#define WSA885X_CDC_RX1_RX_PATH_CTL 0x8626 +#define WSA885X_RX1_RX_PATH_DSMDEM_CTL 0x8633 + +/* CDC Compander Registers - Dynamic Range Control */ +#define WSA885X_CDC_COMPANDER0_CTL0 0x8640 +#define WSA885X_CDC_COMPANDER0_CTL7 0x8647 +#define WSA885X_CDC_COMPANDER1_CTL0 0x8660 +#define WSA885X_CDC_COMPANDER1_CTL7 0x8667 + +/* CDC Speaker Protection Registers - IV Sense */ +#define WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL 0x86A1 +#define WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL 0x86B1 +#define WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL 0x86A9 +#define WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL 0x86B9 + +/* CDC Class-H Registers - Headroom Control */ +#define WSA885X_CDC_CLSH_V1P8_BP_CTL1 0x86CD +#define WSA885X_CDC_CLSH_V1P8_BP_CTL0 0x86CC +#define WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0 0x86C7 +#define WSA885X_CDC_CLSH_CLSH_V_HD_PA 0x86C3 +#define WSA885X_CDC_CLSH_V1P8_BP_CTL2 0x86CE + +/* Driver Constants */ +#define WSA885X_CLK_RATE_FIXED 73728000 +#define WSA885X_NUM_REGS 0x03 + +/* Interrupt Registers */ +#define WSA885X_INTR_STATUS0 0x8584 +#define WSA885X_INTR_MASK0 0x8581 +#define WSA885X_INTR_CLEAR0 0x8587 + +/* Power and PA FSM Control Registers */ +#define WSA885X_PA0_FSM_CTL0 0x842A +#define WSA885X_PA1_FSM_CTL0 0x8434 + +/* Digital Control GPIO and Interrupt Registers */ +#define WSA885X_DIG_CTRL1_PIN_CT 0x8510 +#define WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL 0x8518 +#define WSA885X_DIG_CTRL1_INTR_MODE 0x8580 + +#define WSA885X_I2S_CTL0_PCM_RATE_MASK GENMASK(4, 1) +#define WSA885X_I2S_CTL0_ENABLE_MASK BIT(0) +#define WSA885X_I2S_CTL0_PCM_RATE(v) \ + FIELD_PREP(WSA885X_I2S_CTL0_PCM_RATE_MASK, (v)) +#define WSA885X_I2S_CTL0_PCM_RATE_8KHZ 0x0 +#define WSA885X_I2S_CTL0_PCM_RATE_16KHZ 0x1 +#define WSA885X_I2S_CTL0_PCM_RATE_32KHZ 0x2 +#define WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ 0x3 +#define WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ 0x4 +#define WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ 0x5 +#define WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ 0x6 +#define WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK GENMASK(2, 0) +#define WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK GENMASK(6, 4) +#define WSA885X_I2S_CFG0_TDM_TX_SLOT0(v) \ + FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, (v)) +#define WSA885X_I2S_CFG0_TDM_TX_SLOT1(v) \ + FIELD_PREP_CONST(WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, (v)) +#define WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK GENMASK(2, 0) +#define WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK GENMASK(6, 4) +#define WSA885X_I2S_CFG1_TDM_TX_SLOT2(v) \ + FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, (v)) +#define WSA885X_I2S_CFG1_TDM_TX_SLOT3(v) \ + FIELD_PREP_CONST(WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, (v)) +#define WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK BIT(0) +#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK GENMASK(3, 2) +#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2 \ + FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 0) +#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4 \ + FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 1) +#define WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8 \ + FIELD_PREP_CONST(WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, 3) +#define WSA885X_I2S_TDM_CH_TX_CH0_EN BIT(0) +#define WSA885X_I2S_TDM_CH_TX_CH1_EN BIT(1) +#define WSA885X_I2S_TDM_CH_TX_CH2_EN BIT(2) +#define WSA885X_I2S_TDM_CH_TX_CH3_EN BIT(3) +#define WSA885X_I2S_TDM_CH_RX_CH0_EN BIT(0) +#define WSA885X_I2S_TDM_CH_RX_CH3_EN BIT(3) +#define WSA885X_I2S_RESET_CTL_RESET_MASK BIT(0) +#define WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK BIT(2) +#define WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK BIT(3) +#define WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK BIT(2) + +#define WSA885X_I2S_TX_SLOT_ISENSE0 0x1 +#define WSA885X_I2S_TX_SLOT_ISENSE1 0x2 +#define WSA885X_I2S_TX_SLOT_CUR_SENSE0 0x5 +#define WSA885X_I2S_TX_SLOT_CUR_SENSE1 0x6 + +/* RX Sample Rate Index Values - Audio Playback Path */ +#define WSA885X_RX_RATE_8000HZ 0x00 +#define WSA885X_RX_RATE_16000HZ 0x01 +#define WSA885X_RX_RATE_32000HZ 0x02 +#define WSA885X_RX_RATE_44100HZ 0x03 +#define WSA885X_RX_RATE_48000HZ 0x04 +#define WSA885X_RX_RATE_96000HZ 0x05 +#define WSA885X_RX_RATE_192000HZ 0x06 +#define WSA885X_RX_RATE_384000HZ 0x07 + +/* VI Sample Rate Index Values - Voltage/Current Sensing Path */ +#define WSA885X_VI_RATE_8000HZ 0x00 +#define WSA885X_VI_RATE_16000HZ 0x01 +#define WSA885X_VI_RATE_44100HZ 0x02 +#define WSA885X_VI_RATE_48000HZ 0x03 +#define WSA885X_VI_RATE_96000HZ 0x04 +#define WSA885X_VI_RATE_22050HZ 0x05 +#define WSA885X_VI_RATE_24000HZ 0x06 +#define WSA885X_VI_RATE_192000HZ 0x07 +#define WSA885X_VI_RATE_384000HZ 0x08 + +/* Channel Configuration Masks */ +#define WSA885X_CHANNEL_STEREO 0x03 +#define WSA885X_CHANNEL_MONO_LEFT 0x01 +#define WSA885X_CHANNEL_MONO_RIGHT 0x02 + +#define WSA885X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) + +#define WSA885X_PLL_LOCK_BIT BIT(0) + +#define WSA885X_FU21_VOL_STEPS 124 +#define WSA885X_USAGE_MODE_MAX 8 +static const DECLARE_TLV_DB_SCALE(wsa885x_fu21_digital_gain, -8400, 100, 0); + +static bool wsa885x_is_valid_rx_slot_mask(u32 mask) +{ + return mask == WSA885X_CHANNEL_MONO_LEFT || + mask == WSA885X_CHANNEL_MONO_RIGHT || + mask == WSA885X_CHANNEL_STEREO; +} + +static const char *const wsa885x_supply_name[] = { + "vdd-1p8", + "vdd-io", +}; + +enum { + WSA885X_BATT_1S = 1, + WSA885X_BATT_2S, +}; + +enum { + WSA885X_IRQ_INT_SAF2WAR = 0, + WSA885X_IRQ_INT_WAR2SAF, + WSA885X_IRQ_INT_DISABLE, + WSA885X_IRQ_INT_PA0_OCP, + WSA885X_IRQ_INT_PA1_OCP, + WSA885X_IRQ_INT_CLIP0, + WSA885X_IRQ_INT_CLIP1, + WSA885X_IRQ_INT_CLK_WD, + WSA885X_IRQ_INT_INTR_GPIO1_PIN, + WSA885X_IRQ_INT_INTR_GPIO2_PIN, + WSA885X_IRQ_INT_UVLO, + WSA885X_IRQ_INT_BOP, + WSA885X_IRQ_INT_PA0_FSM_ERR, + WSA885X_IRQ_INT_PA1_FSM_ERR, + WSA885X_IRQ_INT_MAIN_FSM_ERR, + WSA885X_IRQ_INT_PCM_DATA0_WD, + WSA885X_IRQ_INT_PCM_DATA1_WD, + WSA885X_IRQ_INT_PCM_DATA0_DC, + WSA885X_IRQ_INT_PCM_DATA1_DC, + WSA885X_IRQ_INT_PLL_UNLOCKED, + WSA885X_IRQ_INT_PROT_MODE_CHANGE, + WSA885X_IRQ_INT_PB_CLOCK_VALID, + WSA885X_IRQ_INT_SENSE_CLOCK_VALID, + WSA885X_IRQ_MAX, +}; + +struct wsa885x_priv { + struct i2c_client *client; + struct regmap *regmap; + struct device *dev; + struct snd_soc_component *component; + struct gpio_desc *sd_n; + struct reset_control *sd_reset; + u32 usage_mode; + u32 rx_slot_mask; + u32 batt_conf; + int stereo_vol_db; + struct mutex state_lock; /* protects mutable control state */ +}; + +struct wsa885x_reg_update { + unsigned int reg; + unsigned int mask; + unsigned int val; +}; + +static const struct regmap_range_cfg wsa885x_regmap_ranges[] = { + { + .range_min = 0, + .range_max = 0x88ff, + .selector_reg = 0x0, + .selector_mask = 0xFF, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct reg_default wsa885x_codec_reg_defaults[] = { + {WSA885X_SMP_AMP_CTRL_STEREO_STEREO_SMP_AMP_CTRL_I2S, 0x00}, + {WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01}, + {WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x00}, + {WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, 0x00}, + {WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, 0x00}, + {WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, 0x04}, + {WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01}, + {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01}, + {WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01}, + {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, 0xac}, + {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00}, + {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, 0xac}, + {WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00}, + {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03}, + {WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, 0x03}, + {WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, 0x03}, + {WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x00}, + {WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x19}, + {WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x22}, + {WSA885X_ANA_PLL_DIV_CTL_0, 0x0c}, + {WSA885X_ANA_PLL_DIV_CTL_1, 0x50}, + {WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00}, + {WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xb4}, + {WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00}, + {WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00}, + {WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x03}, + {WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x3c}, + {WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc5}, + {WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x13}, + {WSA885X_ANA_TOP_BOOST_MISC, 0x79}, + {WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x00}, + {WSA885X_SPK_TOP_COMMON_CTRL2, 0x08}, + {WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x09}, + {WSA885X_SPK_TOP_LF_CH1_TUNE1, 0x00}, + {WSA885X_SPK_TOP_LF_CH2_TUNE1, 0x00}, + {WSA885X_SPK_TOP_LF_CH1_CTRL9, 0x00}, + {WSA885X_SPK_TOP_LF_CH2_CTRL9, 0x00}, + {WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x09}, + {WSA885X_SPK_TOP_COMMON_TUNE1, 0x03}, + {WSA885X_IVSENSE_VSNS_ISNS_CTL_CH1, 0x00}, + {WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0e}, + {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x40}, + {WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0xff}, + {WSA885X_ANA_TOP_PLL_STATUS_0, 0x00}, + {WSA885X_ANA_TOP_PLL_STATUS_1, 0x00}, + {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x84}, + {WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x02}, + {WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x00}, + {WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0xe0}, + {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa4}, + {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0xc9}, + {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa4}, + {WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0xc9}, + {WSA885X_DIG_CTRL0_TOP_CLK_CFG, 0x00}, + {WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x00}, + {WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00}, + {WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00}, + {WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00}, + {WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05}, + {WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x00}, + {WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x45}, + {WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x45}, + {WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x7f}, + {WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00}, + {WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01}, + {WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01}, + {WSA885X_DIG_CTRL1_I2S_CTL0, 0x06}, + {WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, 0x00}, + {WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, 0x00}, + {WSA885X_DIG_CTRL1_I2S_TDM_CTL0, 0x00}, + {WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x05}, + {WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, 0x00}, + {WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00}, + {WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, WSA885X_I2S_TDM_CH_RX_CH3_EN}, + {WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89}, + {WSA885X_CDC_RX0_RX_PATH_CFG1, 0x64}, + {WSA885X_CDC_RX0_RX_PATH_CTL, 0x24}, + {WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01}, + {WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89}, + {WSA885X_CDC_RX1_RX_PATH_CFG1, 0x64}, + {WSA885X_CDC_RX1_RX_PATH_CTL, 0x04}, + {WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01}, + {WSA885X_CDC_COMPANDER0_CTL0, 0x01}, + {WSA885X_CDC_COMPANDER0_CTL7, 0x2a}, + {WSA885X_CDC_COMPANDER1_CTL0, 0x01}, + {WSA885X_CDC_COMPANDER1_CTL7, 0x2a}, + {WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14}, + {WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14}, + {WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14}, + {WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14}, + {WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50}, + {WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c}, + {WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d}, + {WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03}, + {WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05}, +}; + +static void wsa885x_multi_update_bits(struct regmap *regmap, + const struct wsa885x_reg_update *updates, + size_t num_updates) +{ + size_t i; + + for (i = 0; i < num_updates; i++) + regmap_update_bits(regmap, updates[i].reg, + updates[i].mask, updates[i].val); +} + +static void wsa885x_toggle_irq_bit(struct wsa885x_priv *wsa885x, + unsigned int reg, unsigned int mask) +{ + regmap_update_bits(wsa885x->regmap, reg, mask, 0); + regmap_update_bits(wsa885x->regmap, reg, mask, mask); +} + +static void wsa885x_pulse_irq_bit(struct wsa885x_priv *wsa885x, + unsigned int reg, unsigned int mask) +{ + regmap_update_bits(wsa885x->regmap, reg, mask, 0); + regmap_update_bits(wsa885x->regmap, reg, mask, mask); + regmap_update_bits(wsa885x->regmap, reg, mask, 0); +} + +static int wsa885x_tdm_ctl0_slot_num_val(int slots, unsigned int *slot_num_val) +{ + if (!slot_num_val) + return -EINVAL; + + switch (slots) { + case 2: + *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_2; + return 0; + case 4: + *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_4; + return 0; + case 8: + *slot_num_val = WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_8; + return 0; + default: + return -EINVAL; + } +} + +static int wsa885x_reg_update_sequence(struct regmap *regmap, int slots) +{ + static const struct reg_sequence regs[] = { + { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x15 }, + { WSA885X_DIG_CTRL1_I2S_TDM_CTL1, 0x11 }, + }; + unsigned int slot_num_val; + int ret; + + if (!regmap) + return -EINVAL; + + ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); + if (ret) + return ret; + + regmap_multi_reg_write(regmap, regs, ARRAY_SIZE(regs)); + + regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, + WSA885X_I2S_TDM_CTL0_NUM_CHANNELS_MASK, + slot_num_val); + regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CTL0, + WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK, + WSA885X_I2S_TDM_CTL0_I2S_TDM_EN_MASK); + regmap_write(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, + WSA885X_I2S_TDM_CH_TX_CH0_EN); + regmap_update_bits(regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, + WSA885X_I2S_TDM_CH_TX_CH1_EN, + WSA885X_I2S_TDM_CH_TX_CH1_EN); + + return 0; +} + +static int wsa885x_wait_for_pll_lock(struct wsa885x_priv *wsa885x) +{ + unsigned int status = 0; + int cnt = 0, ret = 0; + + do { + usleep_range(1000, 1100); + ret = regmap_read(wsa885x->regmap, WSA885X_ANA_TOP_PLL_STATUS_0, &status); + if (ret) { + dev_err(wsa885x->dev, "PLL status read failed: %d\n", ret); + return ret; + } + + if (status & WSA885X_PLL_LOCK_BIT) + return 0; + } while (++cnt < 20); + + dev_warn(wsa885x->dev, "PLL lock timeout after 20ms, status=0x%x\n", status); + return -ETIMEDOUT; +} + +static int wsa885x_2s_conf(struct wsa885x_priv *wsa885x) +{ + static const struct reg_sequence regs[] = { + { WSA885X_SPK_TOP_COMMON_TUNE1, 0x26 }, + { WSA885X_SPK_TOP_LF_CH1_CTRL11, 0x0d }, + { WSA885X_SPK_TOP_LF_CH2_CTRL11, 0x0d }, + { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x71 }, + { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0xAA }, + }; + + return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); +} + +static const struct reg_sequence wsa885x_reg_init[] = { + { WSA885X_CDC_RX0_RX_PATH_CTL, 0x24 }, + { WSA885X_CDC_RX1_RX_PATH_CTL, 0x24 }, + { WSA885X_RX0_RX_PATH_DSMDEM_CTL, 0x01 }, + { WSA885X_RX1_RX_PATH_DSMDEM_CTL, 0x01 }, + { WSA885X_CDC_COMPANDER0_CTL0, 0x01 }, + { WSA885X_CDC_COMPANDER1_CTL0, 0x01 }, + { WSA885X_CDC_VSENSE0_SPKR_PROT_PATH_CTL, 0x14 }, + { WSA885X_CDC_VSENSE1_SPKR_PROT_PATH_CTL, 0x14 }, + { WSA885X_CDC_ISENSE0_SPKR_PROT_PATH_CTL, 0x14 }, + { WSA885X_CDC_ISENSE1_SPKR_PROT_PATH_CTL, 0x14 }, + { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x0f }, + { WSA885X_DIG_CTRL0_CDC_CLK_CTL, 0x4f }, + { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x02 }, + { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x00 }, + { WSA885X_DIG_CTRL0_CDC_RXTX_FSCNT_CTL, 0x01 }, + { WSA885X_SMP_AMP_CTRL_STEREO_CMT_GRP_MASK, 0x01 }, + { WSA885X_CDC_RX0_RX_PATH_CFG1, 0x60 }, + { WSA885X_CDC_RX1_RX_PATH_CFG1, 0x60 }, + { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_CTRL3, 0xa5 }, + { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_CTRL3, 0xa5 }, + { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL2, 0x85 }, + { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0c }, + { WSA885X_ANA_TOP_IVSENSE_ADC_MODE_CTL3, 0x0e }, + { WSA885X_ANA_TOP_IVSENSE_ADC_REF_CTL, 0x0c }, + { WSA885X_DIG_CTRL0_GAIN_RAMP0_CTL1, 0x01 }, + { WSA885X_DIG_CTRL0_GAIN_RAMP1_CTL1, 0x01 }, + { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x88 }, + { WSA885X_CDC_RX0_RX_PATH_CFG0, 0x89 }, + { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x88 }, + { WSA885X_CDC_RX1_RX_PATH_CFG0, 0x89 }, + { WSA885X_ANA_TOP_BOOST_STB_CTRL2, 0x82 }, + { WSA885X_ANA_TOP_BOOST_STB_CTRL3, 0x34 }, + { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL2, 0x41 }, + { WSA885X_ANA_TOP_BOOST_PWRSTAGE_CTRL4, 0x7f }, + { WSA885X_CDC_CLSH_V1P8_BP_CTL1, 0x50 }, + { WSA885X_CDC_CLSH_V1P8_BP_CTL0, 0x6c }, + { WSA885X_CDC_CLSH_CLSH_SIG_DP_CTL0, 0x0d }, + { WSA885X_CDC_CLSH_CLSH_V_HD_PA, 0x03 }, + { WSA885X_DIG_CTRL0_POWER_FSM_CTL0, 0x05 }, + { WSA885X_ANA_TOP_PON_CKSK_CTL_0, 0x20 }, + { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH1_TUNE3, 0x45 }, + { WSA885X_ANA_TOP_SPK_TOP_PWRSTG_CH2_TUNE3, 0x45 }, + { WSA885X_CDC_CLSH_V1P8_BP_CTL2, 0x05 }, + { WSA885X_ANA_TOP_BG_TVP_UVLO1_PROG, 0x35 }, + { WSA885X_ANA_TOP_BG_TVP_UVLO2_PROG, 0x21 }, + { WSA885X_ANA_TOP_BOOST_BYP_CTRL2, 0xc7 }, + { WSA885X_ANA_TOP_BOOST_BYP_CTRL3, 0x11 }, + { WSA885X_ANA_TOP_IVSENSE_ADC_CDAC_CAL_CTL2, 0x80 }, + { WSA885X_ANA_TOP_SPK_TOP_SPARE3, 0x08 }, + { WSA885X_DIG_CTRL0_PA0_FSM_CTL1, 0x47 }, + { WSA885X_DIG_CTRL0_PA1_FSM_CTL1, 0x47 }, + { WSA885X_CDC_COMPANDER0_CTL7, 0x34 }, + { WSA885X_CDC_COMPANDER1_CTL7, 0x34 }, + { WSA885X_DIG_CTRL0_VBAT_THRM_FLT_CTL, 0x79 }, +}; + +static int wsa885x_hw_init(struct wsa885x_priv *wsa885x) +{ + static const struct reg_sequence regs[] = { + { WSA885X_DIG_CTRL1_SPMI_PAD_GPIO2_CTL, 0x2e }, + { WSA885X_DIG_CTRL1_INTR_MODE, 0x01 }, + { WSA885X_DIG_CTRL1_PIN_CT, 0x04 }, + }; + int ret; + + ret = regmap_multi_reg_write(wsa885x->regmap, wsa885x_reg_init, + ARRAY_SIZE(wsa885x_reg_init)); + if (ret) + return ret; + + if (wsa885x->batt_conf == WSA885X_BATT_2S) { + ret = wsa885x_2s_conf(wsa885x); + if (ret) + return ret; + } + + return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); +} + +static int wsa885x_unmask_interrupts(struct wsa885x_priv *wsa885x) +{ + static const struct reg_sequence regs[] = { + { WSA885X_INTR_MASK0, 0x00 }, + { WSA885X_INTR_MASK0 + 1, 0x00 }, + { WSA885X_INTR_MASK0 + 2, 0xf8 }, + }; + + return regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); +} + +static int wsa885x_wait_for_pde_state(struct wsa885x_priv *wsa885x, int ps) +{ + unsigned int act_ps = 0, clock_valid = 0; + int rc = 0, cnt = 0; + + if (ps < 0 || ps > 3) + return -EINVAL; + + do { + usleep_range(1000, 1500); + rc = regmap_read(wsa885x->regmap, + WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS, + &act_ps); + if (rc) { + dev_err(wsa885x->dev, "PDE state read failed: %d\n", rc); + return rc; + } + if (act_ps == ps) + return 0; + } while (++cnt < 5); + + if (regmap_read(wsa885x->regmap, + WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID, + &clock_valid)) + dev_err(wsa885x->dev, + "PDE power state %d request failed, actual_ps %d, clock_valid read failed\n", + ps, act_ps); + else + dev_err(wsa885x->dev, + "PDE power state %d request failed, actual_ps %d, clock_valid:%d\n", + ps, act_ps, clock_valid); + + return -ETIMEDOUT; +} + +static void wsa885x_program_stereo_volume(struct wsa885x_priv *wsa885x, + int stereo_vol_db, bool commit) +{ + regmap_write(wsa885x->regmap, + WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_MSB, + (u8)(s8)stereo_vol_db); + regmap_write(wsa885x->regmap, + WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00); + regmap_write(wsa885x->regmap, + WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_MSB, + (u8)(s8)stereo_vol_db); + regmap_write(wsa885x->regmap, + WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00); + + if (commit) + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); +} + +static int wsa885x_codec_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct wsa885x_priv *wsa885x; + u8 pcm_rate, cs21_sample_rate_idx, cs24_sample_rate_idx; + + wsa885x = snd_soc_component_get_drvdata(dai->component); + + switch (params_rate(params)) { + case 8000: + pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_8KHZ; + cs21_sample_rate_idx = WSA885X_RX_RATE_8000HZ; + cs24_sample_rate_idx = WSA885X_VI_RATE_8000HZ; + break; + case 16000: + pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_16KHZ; + cs21_sample_rate_idx = WSA885X_RX_RATE_16000HZ; + cs24_sample_rate_idx = WSA885X_VI_RATE_16000HZ; + break; + case 32000: + pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_32KHZ; + cs21_sample_rate_idx = WSA885X_RX_RATE_32000HZ; + cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; + break; + case 44100: + pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; + cs21_sample_rate_idx = WSA885X_RX_RATE_44100HZ; + cs24_sample_rate_idx = WSA885X_VI_RATE_44100HZ; + break; + case 48000: + pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_48_OR_44KHZ; + cs21_sample_rate_idx = WSA885X_RX_RATE_48000HZ; + cs24_sample_rate_idx = WSA885X_VI_RATE_48000HZ; + break; + case 88200: + case 96000: + pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_96_OR_88KHZ; + cs21_sample_rate_idx = WSA885X_RX_RATE_96000HZ; + cs24_sample_rate_idx = WSA885X_VI_RATE_96000HZ; + break; + case 176400: + case 192000: + pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_192_OR_176KHZ; + cs21_sample_rate_idx = WSA885X_RX_RATE_192000HZ; + cs24_sample_rate_idx = WSA885X_VI_RATE_192000HZ; + break; + case 352800: + case 384000: + pcm_rate = WSA885X_I2S_CTL0_PCM_RATE_384_OR_352KHZ; + cs21_sample_rate_idx = WSA885X_RX_RATE_384000HZ; + cs24_sample_rate_idx = WSA885X_VI_RATE_384000HZ; + break; + default: + dev_err(wsa885x->dev, "sampling rate %d is not supported\n", params_rate(params)); + return -EINVAL; + } + + regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, + WSA885X_I2S_CTL0_PCM_RATE_MASK | + WSA885X_I2S_CTL0_ENABLE_MASK, + WSA885X_I2S_CTL0_PCM_RATE(pcm_rate) | + WSA885X_I2S_CTL0_ENABLE_MASK); + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, 0x00); + regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS21_SAMPLERATEINDEX, + cs21_sample_rate_idx); + regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_CS24_SAMPLERATEINDEX, + cs24_sample_rate_idx); + + mutex_lock(&wsa885x->state_lock); + wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); + mutex_unlock(&wsa885x->state_lock); + + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01); + + return 0; +} + +static int wsa885x_codec_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_slot_mask, + unsigned int rx_slot_mask, int slots, + int slot_width) +{ + static const struct wsa885x_reg_update stereo_updates[] = { + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, + WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, + WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_ISENSE1) }, + { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT2_MASK, + WSA885X_I2S_CFG1_TDM_TX_SLOT2(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, + { WSA885X_DIG_CTRL1_I2S_CFG1_TDM_TX, WSA885X_I2S_CFG1_TDM_TX_SLOT3_MASK, + WSA885X_I2S_CFG1_TDM_TX_SLOT3(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, + }; + static const struct wsa885x_reg_update mono_left_updates[] = { + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, + WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE0) }, + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, + WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE0) }, + }; + static const struct wsa885x_reg_update mono_right_updates[] = { + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT0_MASK, + WSA885X_I2S_CFG0_TDM_TX_SLOT0(WSA885X_I2S_TX_SLOT_ISENSE1) }, + { WSA885X_DIG_CTRL1_I2S_CFG0_TDM_TX, WSA885X_I2S_CFG0_TDM_TX_SLOT1_MASK, + WSA885X_I2S_CFG0_TDM_TX_SLOT1(WSA885X_I2S_TX_SLOT_CUR_SENSE1) }, + }; + struct wsa885x_priv *wsa885x; + unsigned int slot_num_val; + u32 mask; + int ret; + + wsa885x = snd_soc_component_get_drvdata(dai->component); + + ret = wsa885x_tdm_ctl0_slot_num_val(slots, &slot_num_val); + if (ret) { + dev_err(wsa885x->dev, "%s: unsupported slot count %d\n", + __func__, slots); + return ret; + } + + if (rx_slot_mask && !wsa885x_is_valid_rx_slot_mask(rx_slot_mask)) { + dev_err(wsa885x->dev, + "%s: unsupported rx_slot_mask 0x%x\n", + __func__, rx_slot_mask); + return -EINVAL; + } + + mutex_lock(&wsa885x->state_lock); + if (rx_slot_mask) + wsa885x->rx_slot_mask = rx_slot_mask; + else if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) + wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; + mask = wsa885x->rx_slot_mask; + + regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, + WSA885X_I2S_RESET_CTL_RESET_MASK, + WSA885X_I2S_RESET_CTL_RESET_MASK); + + if (mask == WSA885X_CHANNEL_STEREO) { + wsa885x_multi_update_bits(wsa885x->regmap, stereo_updates, + ARRAY_SIZE(stereo_updates)); + ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); + if (ret) + goto exit_unlock; + regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, + WSA885X_I2S_TDM_CH_TX_CH2_EN, + WSA885X_I2S_TDM_CH_TX_CH2_EN); + regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_TX, + WSA885X_I2S_TDM_CH_TX_CH3_EN, + WSA885X_I2S_TDM_CH_TX_CH3_EN); + } else if (mask == WSA885X_CHANNEL_MONO_LEFT) { + wsa885x_multi_update_bits(wsa885x->regmap, mono_left_updates, + ARRAY_SIZE(mono_left_updates)); + ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); + if (ret) + goto exit_unlock; + } else if (mask == WSA885X_CHANNEL_MONO_RIGHT) { + wsa885x_multi_update_bits(wsa885x->regmap, mono_right_updates, + ARRAY_SIZE(mono_right_updates)); + ret = wsa885x_reg_update_sequence(wsa885x->regmap, slots); + if (ret) + goto exit_unlock; + } + + regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_CTL0, + WSA885X_I2S_CTL0_ENABLE_MASK, + WSA885X_I2S_CTL0_ENABLE_MASK); + regmap_update_bits(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_RESET_CTL, + WSA885X_I2S_RESET_CTL_RESET_MASK, 0); + + ret = 0; + +exit_unlock: + mutex_unlock(&wsa885x->state_lock); + + return ret; +} + +static int wsa885x_codec_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + static const struct reg_sequence pll_prep[] = { + { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x03 }, + { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x04 }, + { WSA885X_ANA_TOP_PLL_LOOPFILT_0, 0xB4 }, + { WSA885X_ANA_TOP_PLL_VCO_CTL, 0x00 }, + { WSA885X_ANA_TOP_PLL_OVRD_CTL, 0x00 }, + }; + static const struct reg_sequence pll_cleanup[] = { + { WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x00 }, + { WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00 }, + { WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00 }, + }; + struct wsa885x_priv *wsa885x; + u32 pll_div; + int ret = 0; + + wsa885x = snd_soc_component_get_drvdata(dai->component); + + if (!freq) + return -EINVAL; + if (WSA885X_CLK_RATE_FIXED % freq) + return -EINVAL; + pll_div = WSA885X_CLK_RATE_FIXED / freq; + if (pll_div > 0xff) + return -EINVAL; + + regmap_multi_reg_write(wsa885x->regmap, pll_prep, ARRAY_SIZE(pll_prep)); + regmap_write(wsa885x->regmap, WSA885X_ANA_PLL_DIV_CTL_0, pll_div); + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_CLK_SOURCE_ENABLE, 0x02); + + ret = wsa885x_wait_for_pll_lock(wsa885x); + if (ret) { + dev_err(wsa885x->dev, "PLL lock failed, aborting sysclk configuration\n"); + regmap_multi_reg_write(wsa885x->regmap, pll_cleanup, + ARRAY_SIZE(pll_cleanup)); + return ret; + } + + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_SYS_CLK_SEL, 0x00); + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_POWER_FSM_CTL1, 0x01); + regmap_write(wsa885x->regmap, WSA885X_ANA_TOP_BG_TVP_OVRD_CTL, 0x00); + + return 0; +} + +static int wsa885x_codec_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + static const struct reg_sequence mute_regs[] = { + { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, + { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x03 }, + }; + static const struct reg_sequence mute_commit_regs[] = { + { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x01 }, + { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x01 }, + { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, + }; + static const struct reg_sequence unmute_prep_head_regs[] = { + { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, + }; + static const struct reg_sequence unmute_prep_tail_regs[] = { + { WSA885X_SMP_AMP_CTRL_STEREO_IT21_CLUSERINDEX, 0x01 }, + { WSA885X_SMP_AMP_CTRL_STEREO_PPU21_POSTURENUMBER, 0x01 }, + }; + static const struct reg_sequence unmute_volume_regs[] = { + { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X0_LSB, 0x00 }, + { WSA885X_SMP_AMP_CTRL_STEREO_FU21_CH_VOL_CH2X1_LSB, 0x00 }, + }; + static const struct reg_sequence unmute_commit_regs[] = { + { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, + { WSA885X_SMP_AMP_CTRL_STEREO_PDE23_REQ_PS, 0x00 }, + }; + static const struct reg_sequence unmute_finish_regs[] = { + { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X0, 0x00 }, + { WSA885X_SMP_AMP_CTRL_STEREO_FU21_MUTE_CH2X1, 0x00 }, + { WSA885X_DIG_CTRL0_SDCA_COMMIT, 0x01 }, + }; + struct wsa885x_priv *wsa885x; + int ret = 0, ps0 = 0, ps3 = 3; + + wsa885x = snd_soc_component_get_drvdata(dai->component); + + if (stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + mutex_lock(&wsa885x->state_lock); + + if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { + ret = -EINVAL; + goto exit_unlock; + } + + if (!wsa885x_is_valid_rx_slot_mask(wsa885x->rx_slot_mask)) + wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; + + if (mute) { + regmap_multi_reg_write(wsa885x->regmap, mute_regs, + ARRAY_SIZE(mute_regs)); + ret = wsa885x_wait_for_pde_state(wsa885x, ps3); + if (ret) { + dev_err(wsa885x->dev, + "PS3 transition failed: %d\n", ret); + } else { + regmap_multi_reg_write(wsa885x->regmap, mute_commit_regs, + ARRAY_SIZE(mute_commit_regs)); + } + } else { + regmap_multi_reg_write(wsa885x->regmap, unmute_prep_head_regs, + ARRAY_SIZE(unmute_prep_head_regs)); + regmap_write(wsa885x->regmap, WSA885X_SMP_AMP_CTRL_STEREO_OT23_USAGE, + wsa885x->usage_mode); + regmap_multi_reg_write(wsa885x->regmap, unmute_prep_tail_regs, + ARRAY_SIZE(unmute_prep_tail_regs)); + wsa885x_program_stereo_volume(wsa885x, wsa885x->stereo_vol_db, false); + regmap_multi_reg_write(wsa885x->regmap, unmute_volume_regs, + ARRAY_SIZE(unmute_volume_regs)); + regmap_multi_reg_write(wsa885x->regmap, unmute_commit_regs, + ARRAY_SIZE(unmute_commit_regs)); + ret = wsa885x_wait_for_pde_state(wsa885x, ps0); + if (ret) + goto exit_unlock; + + if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_STEREO) { + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, + WSA885X_I2S_TDM_CH_RX_CH0_EN | + WSA885X_I2S_TDM_CH_RX_CH3_EN); + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x03); + } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_LEFT) { + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, + WSA885X_I2S_TDM_CH_RX_CH3_EN); + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x01); + } else if (wsa885x->rx_slot_mask == WSA885X_CHANNEL_MONO_RIGHT) { + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL1_I2S_TDM_CH_RX, + WSA885X_I2S_TDM_CH_RX_CH0_EN); + regmap_write(wsa885x->regmap, WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x02); + } + + regmap_multi_reg_write(wsa885x->regmap, unmute_finish_regs, + ARRAY_SIZE(unmute_finish_regs)); + } + +exit_unlock: + mutex_unlock(&wsa885x->state_lock); + + return ret; +} + +static int wsa885x_codec_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + static const struct reg_sequence regs[] = { + { WSA885X_DIG_CTRL0_PA_FSM_CTL, 0x00 }, + }; + struct wsa885x_priv *wsa885x; + + wsa885x = snd_soc_component_get_drvdata(dai->component); + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + mutex_lock(&wsa885x->state_lock); + regmap_multi_reg_write(wsa885x->regmap, regs, ARRAY_SIZE(regs)); + mutex_unlock(&wsa885x->state_lock); + + return 0; +} + +static const struct snd_soc_dai_ops wsa885x_dai_ops = { + .hw_params = wsa885x_codec_hw_params, + .set_tdm_slot = wsa885x_codec_set_tdm_slot, + .set_sysclk = wsa885x_codec_set_sysclk, + .mute_stream = wsa885x_codec_mute_stream, + .hw_free = wsa885x_codec_hw_free, +}; + +static struct snd_soc_dai_driver wsa885x_dai[] = { + { + .name = "wsa885x_dai_drv", + .playback = { + .stream_name = "WSA885X TDM Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WSA885X_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &wsa885x_dai_ops, + }, +}; + +static void wsa885x_reset_assert(void *data) +{ + struct wsa885x_priv *wsa885x = data; + + if (wsa885x->sd_reset) + reset_control_assert(wsa885x->sd_reset); + else + gpiod_direction_output(wsa885x->sd_n, 1); +} + +static void wsa885x_reset_deassert(struct wsa885x_priv *wsa885x) +{ + if (wsa885x->sd_reset) + reset_control_deassert(wsa885x->sd_reset); + else + gpiod_direction_output(wsa885x->sd_n, 0); +} + +static int wsa885x_get_reset(struct device *dev, struct wsa885x_priv *wsa885x) +{ + wsa885x->sd_reset = devm_reset_control_get_optional_shared(dev, NULL); + if (IS_ERR(wsa885x->sd_reset)) + return dev_err_probe(dev, PTR_ERR(wsa885x->sd_reset), + "Failed to get reset\n"); + else if (wsa885x->sd_reset) + return 0; + + wsa885x->sd_n = devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); + if (IS_ERR(wsa885x->sd_n)) + return dev_err_probe(dev, PTR_ERR(wsa885x->sd_n), + "Shutdown Control GPIO not found\n"); + + return 0; +} + +static bool wsa885x_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WSA885X_ANA_TOP_PLL_STATUS_0: + case WSA885X_ANA_TOP_PLL_STATUS_1: + case WSA885X_DIG_CTRL0_SDCA_COMMIT: + case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: + case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: + case WSA885X_INTR_STATUS0: + case WSA885X_INTR_STATUS0 + 1: + case WSA885X_INTR_STATUS0 + 2: + case WSA885X_INTR_CLEAR0: + case WSA885X_INTR_CLEAR0 + 1: + case WSA885X_INTR_CLEAR0 + 2: + return true; + default: + return false; + } +} + +static bool wsa885x_readable_register(struct device *dev, unsigned int reg) +{ + if (reg == WSA885X_INTR_CLEAR0 || + reg == WSA885X_INTR_CLEAR0 + 1 || + reg == WSA885X_INTR_CLEAR0 + 2) + return false; + return reg <= 0x88ff; +} + +static bool wsa885x_writeable_register(struct device *dev, unsigned int reg) +{ + if (reg > 0x88ff) + return false; + + switch (reg) { + case WSA885X_ANA_TOP_PLL_STATUS_0: + case WSA885X_ANA_TOP_PLL_STATUS_1: + case WSA885X_INTR_STATUS0: + case WSA885X_INTR_STATUS0 + 1: + case WSA885X_INTR_STATUS0 + 2: + case WSA885X_SMP_AMP_CTRL_STEREO_PDE23_ACT_PS: + case WSA885X_SMP_AMP_CTRL_STEREO_CS21_CLOCK_VALID: + return false; + default: + return true; + } +} + +static const struct regmap_config wsa885x_regmap_cfg = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x88FF, + .ranges = wsa885x_regmap_ranges, + .num_ranges = ARRAY_SIZE(wsa885x_regmap_ranges), + .reg_defaults = wsa885x_codec_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(wsa885x_codec_reg_defaults), + .volatile_reg = wsa885x_volatile_register, + .writeable_reg = wsa885x_writeable_register, + .readable_reg = wsa885x_readable_register, + .cache_type = REGCACHE_MAPLE, + .use_single_read = true, + .use_single_write = true, +}; + +static int wsa885x_component_probe(struct snd_soc_component *component) +{ + struct wsa885x_priv *wsa885x = + snd_soc_component_get_drvdata(component); + int ret; + + wsa885x->component = component; + snd_soc_component_init_regmap(component, wsa885x->regmap); + + ret = wsa885x_hw_init(wsa885x); + if (ret) + return ret; + + return wsa885x_unmask_interrupts(wsa885x); +} + +static int wsa885x_stereo_gain_offset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); + int val; + + mutex_lock(&wsa885x->state_lock); + val = wsa885x->stereo_vol_db + 84; + mutex_unlock(&wsa885x->state_lock); + if (val < 0 || val > WSA885X_FU21_VOL_STEPS) + return -ERANGE; + + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int wsa885x_stereo_gain_offset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); + long val; + int stereo_vol_db; + + val = ucontrol->value.integer.value[0]; + + if (val < 0 || val > WSA885X_FU21_VOL_STEPS) { + dev_err(component->dev, "%s: Invalid range, Val: %ld\n", __func__, val); + return -EINVAL; + } + + stereo_vol_db = (int)val - 84; + + mutex_lock(&wsa885x->state_lock); + if (wsa885x->stereo_vol_db == stereo_vol_db) { + mutex_unlock(&wsa885x->state_lock); + return 0; + } + + wsa885x_program_stereo_volume(wsa885x, stereo_vol_db, true); + wsa885x->stereo_vol_db = stereo_vol_db; + mutex_unlock(&wsa885x->state_lock); + + return 1; +} + +static int wsa885x_usage_modes_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); + + mutex_lock(&wsa885x->state_lock); + if (wsa885x->usage_mode > WSA885X_USAGE_MODE_MAX) { + mutex_unlock(&wsa885x->state_lock); + return -ERANGE; + } + + ucontrol->value.integer.value[0] = wsa885x->usage_mode; + mutex_unlock(&wsa885x->state_lock); + + return 0; +} + +static int wsa885x_usage_modes_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); + u32 val = ucontrol->value.integer.value[0]; + + if (val > WSA885X_USAGE_MODE_MAX) + return -EINVAL; + + mutex_lock(&wsa885x->state_lock); + if (wsa885x->usage_mode == val) { + mutex_unlock(&wsa885x->state_lock); + return 0; + } + + wsa885x->usage_mode = val; + mutex_unlock(&wsa885x->state_lock); + + return 1; +} + +static int wsa885x_rx_slot_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); + u32 mask; + + mutex_lock(&wsa885x->state_lock); + mask = wsa885x->rx_slot_mask; + mutex_unlock(&wsa885x->state_lock); + if (!wsa885x_is_valid_rx_slot_mask(mask)) + return -ERANGE; + + ucontrol->value.integer.value[0] = mask; + + return 0; +} + +static int wsa885x_rx_slot_mask_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wsa885x_priv *wsa885x = snd_soc_component_get_drvdata(component); + u32 mask = ucontrol->value.integer.value[0]; + + if (!wsa885x_is_valid_rx_slot_mask(mask)) + return -EINVAL; + + mutex_lock(&wsa885x->state_lock); + if (wsa885x->rx_slot_mask == mask) { + mutex_unlock(&wsa885x->state_lock); + return 0; + } + + wsa885x->rx_slot_mask = mask; + mutex_unlock(&wsa885x->state_lock); + + return 1; +} + +static const struct snd_kcontrol_new wsa885x_snd_controls[] = { + SOC_SINGLE_EXT("Usage Mode", SND_SOC_NOPM, 0, WSA885X_USAGE_MODE_MAX, 0, + wsa885x_usage_modes_get, + wsa885x_usage_modes_put), + + SOC_SINGLE_EXT_TLV("Speaker Volume", SND_SOC_NOPM, + 0, WSA885X_FU21_VOL_STEPS, 0, + wsa885x_stereo_gain_offset_get, + wsa885x_stereo_gain_offset_put, + wsa885x_fu21_digital_gain), + + SOC_SINGLE_EXT("Rx Slot Mask", SND_SOC_NOPM, 0, 3, 0, + wsa885x_rx_slot_mask_get, + wsa885x_rx_slot_mask_put), +}; + +static const struct snd_soc_component_driver wsa885x_component = { + .name = "wsa885x", + .probe = wsa885x_component_probe, + .controls = wsa885x_snd_controls, + .num_controls = ARRAY_SIZE(wsa885x_snd_controls), +}; + +static irqreturn_t wsa885x_handle_irq(int irq_idx, void *data) +{ + struct wsa885x_priv *wsa885x = data; + + if (irq_idx < 0 || irq_idx >= WSA885X_IRQ_MAX) + return IRQ_NONE; + + switch (irq_idx) { + case WSA885X_IRQ_INT_SAF2WAR: + case WSA885X_IRQ_INT_WAR2SAF: + case WSA885X_IRQ_INT_DISABLE: + case WSA885X_IRQ_INT_INTR_GPIO1_PIN: + case WSA885X_IRQ_INT_INTR_GPIO2_PIN: + case WSA885X_IRQ_INT_PA0_OCP: + case WSA885X_IRQ_INT_PA1_OCP: + case WSA885X_IRQ_INT_CLIP0: + case WSA885X_IRQ_INT_CLIP1: + case WSA885X_IRQ_INT_CLK_WD: + case WSA885X_IRQ_INT_BOP: + case WSA885X_IRQ_INT_UVLO: + case WSA885X_IRQ_INT_PCM_DATA0_DC: + case WSA885X_IRQ_INT_PCM_DATA1_DC: + case WSA885X_IRQ_INT_PLL_UNLOCKED: + case WSA885X_IRQ_INT_PROT_MODE_CHANGE: + case WSA885X_IRQ_INT_PB_CLOCK_VALID: + case WSA885X_IRQ_INT_SENSE_CLOCK_VALID: + break; + case WSA885X_IRQ_INT_PCM_DATA0_WD: + case WSA885X_IRQ_INT_PCM_DATA1_WD: + if (irq_idx == WSA885X_IRQ_INT_PCM_DATA0_WD) + wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD0_CTL1, + WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); + else + wsa885x_toggle_irq_bit(wsa885x, WSA885X_DIG_CTRL0_PCM_DATA_WD1_CTL1, + WSA885X_PCM_DATA_WD_CTL1_PCM_DATA_WD_EN_MASK); + break; + case WSA885X_IRQ_INT_PA0_FSM_ERR: + case WSA885X_IRQ_INT_PA1_FSM_ERR: + case WSA885X_IRQ_INT_MAIN_FSM_ERR: + if (irq_idx == WSA885X_IRQ_INT_MAIN_FSM_ERR) { + wsa885x_pulse_irq_bit(wsa885x, WSA885X_DIG_CTRL0_POWER_FSM_CTL0, + WSA885X_POWER_FSM_CTL0_CLEAR_ERROR_MASK); + } else if (irq_idx == WSA885X_IRQ_INT_PA0_FSM_ERR) { + wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA0_FSM_CTL0, + WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); + } else if (irq_idx == WSA885X_IRQ_INT_PA1_FSM_ERR) { + wsa885x_pulse_irq_bit(wsa885x, WSA885X_PA1_FSM_CTL0, + WSA885X_PA_FSM_CTL0_CLEAR_ERROR_MASK); + } + break; + default: + break; + } + + return IRQ_HANDLED; +} + +static irqreturn_t wsa885x_interrupt_handler(int irq, void *data) +{ + static const unsigned int status_reg[WSA885X_NUM_REGS] = { + WSA885X_INTR_STATUS0, + WSA885X_INTR_STATUS0 + 1, + WSA885X_INTR_STATUS0 + 2, + }; + static const unsigned int clear_reg[WSA885X_NUM_REGS] = { + WSA885X_INTR_CLEAR0, + WSA885X_INTR_CLEAR0 + 1, + WSA885X_INTR_CLEAR0 + 2, + }; + unsigned int status[WSA885X_NUM_REGS] = { 0 }; + struct wsa885x_priv *wsa885x = data; + irqreturn_t handled = IRQ_NONE; + irqreturn_t irq_ret; + int i, bit, ret, irq_num; + + for (i = 0; i < WSA885X_NUM_REGS; i++) { + ret = regmap_read(wsa885x->regmap, status_reg[i], &status[i]); + if (ret) { + dev_err(wsa885x->dev, + "Failed to read status_reg[%d] (0x%x): %d\n", + i, status_reg[i], ret); + status[i] = 0; + continue; + } + } + + for (i = 0; i < WSA885X_NUM_REGS; i++) { + for (bit = 0; bit < 8; bit++) { + if (status[i] & BIT(bit)) { + irq_num = i * 8 + bit; + regmap_write(wsa885x->regmap, clear_reg[i], BIT(bit)); + regmap_write(wsa885x->regmap, clear_reg[i], 0); + if (irq_num >= WSA885X_IRQ_MAX) { + dev_warn_ratelimited(wsa885x->dev, + "Unexpected IRQ bit %d (reg %d)\n", + bit, i); + handled = IRQ_HANDLED; + continue; + } + irq_ret = wsa885x_handle_irq(irq_num, wsa885x); + if (irq_ret == IRQ_HANDLED) + handled = IRQ_HANDLED; + } + } + } + return handled; +} + +static int wsa885x_register_irq(struct wsa885x_priv *wsa885x) +{ + if (!wsa885x->client->irq) + return dev_err_probe(wsa885x->dev, -EINVAL, + "IRQ is not configured\n"); + + return devm_request_threaded_irq(wsa885x->dev, wsa885x->client->irq, NULL, + wsa885x_interrupt_handler, + IRQF_ONESHOT, + dev_name(wsa885x->dev), wsa885x); +} + +static int wsa885x_probe(struct i2c_client *client) +{ + struct wsa885x_priv *wsa885x; + const struct snd_soc_component_driver *component_driver = &wsa885x_component; + const char *battery_config; + unsigned int i; + int ret; + struct device *dev = &client->dev; + + wsa885x = devm_kzalloc(dev, sizeof(*wsa885x), GFP_KERNEL); + if (!wsa885x) + return -ENOMEM; + + wsa885x->client = client; + wsa885x->dev = dev; + wsa885x->stereo_vol_db = -84; + wsa885x->rx_slot_mask = WSA885X_CHANNEL_STEREO; + mutex_init(&wsa885x->state_lock); + wsa885x->regmap = devm_regmap_init_i2c(client, &wsa885x_regmap_cfg); + + if (IS_ERR(wsa885x->regmap)) + return PTR_ERR(wsa885x->regmap); + + ret = device_property_read_string(dev, "qcom,battery-config", + &battery_config); + if (ret) { + wsa885x->batt_conf = WSA885X_BATT_1S; + } else if (!strcmp(battery_config, "1s")) { + wsa885x->batt_conf = WSA885X_BATT_1S; + } else if (!strcmp(battery_config, "2s")) { + wsa885x->batt_conf = WSA885X_BATT_2S; + } else { + return dev_err_probe(dev, -EINVAL, + "Invalid battery config %s (expected 1s or 2s)\n", + battery_config); + } + + for (i = 0; i < ARRAY_SIZE(wsa885x_supply_name); i++) { + ret = devm_regulator_get_enable(dev, wsa885x_supply_name[i]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to enable regulator %s\n", + wsa885x_supply_name[i]); + } + + ret = wsa885x_get_reset(dev, wsa885x); + if (ret) + return ret; + + wsa885x_reset_deassert(wsa885x); + usleep_range(5000, 5500); + + ret = devm_add_action_or_reset(dev, wsa885x_reset_assert, wsa885x); + if (ret) + return dev_err_probe(dev, ret, "devm_add_action_or_reset failed\n"); + + i2c_set_clientdata(client, wsa885x); + + ret = wsa885x_register_irq(wsa885x); + if (ret) + return dev_err_probe(dev, ret, "wsa885x irq registration failed\n"); + + ret = devm_snd_soc_register_component(dev, component_driver, + wsa885x_dai, + ARRAY_SIZE(wsa885x_dai)); + if (ret) + return dev_err_probe(dev, ret, "Codec component registration failed\n"); + + return 0; +} + +static const struct of_device_id wsa885x_dt_match[] = { + { + .compatible = "qcom,wsa8855", + }, + {} +}; +MODULE_DEVICE_TABLE(of, wsa885x_dt_match); + +static const struct i2c_device_id wsa885x_id[] = { + { + .name = "wsa885x", + .driver_data = 0, + }, + {} +}; +MODULE_DEVICE_TABLE(i2c, wsa885x_id); + +static struct i2c_driver wsa885x_driver = { + .driver = { + .name = "wsa885x", + .of_match_table = wsa885x_dt_match, + }, + .probe = wsa885x_probe, + .id_table = wsa885x_id, +}; + +module_i2c_driver(wsa885x_driver); + +MODULE_DESCRIPTION("ASoC WSA885X Stereo Smart PA Codec Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index e6e24f3b99222..71cd253d32564 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -71,6 +71,16 @@ config SND_SOC_QCOM_COMMON config SND_SOC_QCOM_SDW tristate +config SND_SOC_QCOM_QAIF + tristate "Qualcomm QAIF audio interface support" + depends on COMMON_CLK + select REGMAP_MMIO + help + Say Y or M to enable the Qualcomm Audio Interface (QAIF) driver + for the Shikra audio platform. QAIF is a DMA-based audio + subsystem that moves PCM data between memory and external serial + audio interfaces (AIF) and internal codec interfaces (CIF). + config SND_SOC_QDSP6_COMMON tristate @@ -101,6 +111,7 @@ config SND_SOC_QDSP6_ASM_DAI config SND_SOC_QDSP6_APM_DAI tristate + select QCOM_SCM select SND_SOC_COMPRESS config SND_SOC_QDSP6_APM_LPASS_DAI diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index 985ce2ae286ba..cf50b7a24bcfd 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -47,3 +47,6 @@ obj-$(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) += snd-soc-qcom-offload-utils.o #DSP lib obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ + +snd-soc-qcom-qaif-y := qaif-cpu.o qaif-platform.o qaif-shikra.o +obj-$(CONFIG_SND_SOC_QCOM_QAIF) += snd-soc-qcom-qaif.o diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index edc4611691f73..46de0abfbbfca 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -3,6 +3,7 @@ // Copyright (c) 2018, The Linux Foundation. All rights reserved. #include +#include #include #include #include @@ -23,6 +24,161 @@ static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { SND_SOC_DAPM_SPK("DP7 Jack", NULL), }; +static struct device_node *qcom_snd_get_link_node(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct of_phandle_args args; + int ret; + + if (!card->dev || !card->dev->of_node) + return NULL; + + for_each_available_child_of_node_scoped(card->dev->of_node, np) { + struct device_node *cpu_np __free(device_node) = + of_get_child_by_name(np, "cpu"); + + if (!cpu_np) + continue; + + ret = of_parse_phandle_with_args(cpu_np, "sound-dai", "#sound-dai-cells", 0, + &args); + if (ret) + continue; + + if (args.np == rtd->dai_link->cpus[0].of_node && + args.args_count == 1 && args.args[0] == cpu_dai->id) { + of_node_put(args.np); + return of_node_get(np); + } + + of_node_put(args.np); + } + + return NULL; +} + +static int qcom_snd_parse_tdm_slot(struct device_node *np, + struct qcom_snd_tdm_slot_cfg *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); + + return snd_soc_of_parse_tdm_slot(np, &cfg->tx_mask, &cfg->rx_mask, + &cfg->slots, &cfg->slot_width); +} + +static int qcom_snd_normalize_tdm_slots(struct qcom_snd_tdm_slot_cfg *cpu_cfg, + struct qcom_snd_tdm_slot_cfg *codec_cfg) +{ + unsigned int slots; + unsigned int slot_width; + + if (cpu_cfg->slots && codec_cfg->slots && cpu_cfg->slots != codec_cfg->slots) + return -EINVAL; + + if (cpu_cfg->slot_width && codec_cfg->slot_width && + cpu_cfg->slot_width != codec_cfg->slot_width) + return -EINVAL; + + slots = cpu_cfg->slots ?: codec_cfg->slots; + if (!slots) + return 0; + + slot_width = cpu_cfg->slot_width ?: codec_cfg->slot_width; + if (!slot_width) + return -EINVAL; + + cpu_cfg->slots = slots; + codec_cfg->slots = slots; + cpu_cfg->slot_width = slot_width; + codec_cfg->slot_width = slot_width; + + return 0; +} + +static int qcom_snd_parse_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, + struct qcom_snd_tdm_slot_cfg *cpu_cfg, + struct qcom_snd_tdm_slot_cfg *codec_cfg) +{ + struct device_node *link_np __free(device_node) = qcom_snd_get_link_node(rtd); + int ret; + + if (!link_np) + return -ENOENT; + + struct device_node *cpu_np __free(device_node) = + of_get_child_by_name(link_np, "cpu"); + struct device_node *codec_np __free(device_node) = + of_get_child_by_name(link_np, "codec"); + if (!cpu_np || !codec_np) + return -ENOENT; + + ret = qcom_snd_parse_tdm_slot(cpu_np, cpu_cfg); + if (ret) + return ret; + + return qcom_snd_parse_tdm_slot(codec_np, codec_cfg); +} + +int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, + struct qcom_snd_tdm_slot_cfg *cpu_cfg, + struct qcom_snd_tdm_slot_cfg *codec_cfg) +{ + int ret; + + ret = qcom_snd_parse_dai_tdm_slots(rtd, cpu_cfg, codec_cfg); + if (ret) + return ret; + + return qcom_snd_normalize_tdm_slots(cpu_cfg, codec_cfg); +} +EXPORT_SYMBOL_GPL(qcom_snd_get_dai_tdm_slots); + +int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, + const struct qcom_snd_tdm_slot_cfg *cpu_cfg, + const struct qcom_snd_tdm_slot_cfg *codec_cfg) +{ + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai; + int i; + int ret; + + if (!cpu_cfg->slots) + return 0; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, cpu_cfg->tx_mask, cpu_cfg->rx_mask, + cpu_cfg->slots, cpu_cfg->slot_width); + if (ret) + return ret; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, + codec_cfg->tx_mask, + codec_cfg->rx_mask, + codec_cfg->slots, + codec_cfg->slot_width); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots_cfg); + +int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd) +{ + struct qcom_snd_tdm_slot_cfg cpu_cfg; + struct qcom_snd_tdm_slot_cfg codec_cfg; + int ret; + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) + return ret == -ENOENT ? 0 : ret; + + return qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); +} +EXPORT_SYMBOL_GPL(qcom_snd_apply_dai_tdm_slots); + int qcom_snd_parse_of(struct snd_soc_card *card) { struct device *dev = card->dev; @@ -215,6 +371,7 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, } switch (cpu_dai->id) { + case QAIF_CDC_DMA_RX0: case TX_CODEC_DMA_TX_0: case TX_CODEC_DMA_TX_1: case TX_CODEC_DMA_TX_2: diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h index ee6662885593c..64107b54645dd 100644 --- a/sound/soc/qcom/common.h +++ b/sound/soc/qcom/common.h @@ -5,11 +5,26 @@ #define __QCOM_SND_COMMON_H__ #include +#include #include -#define LPASS_MAX_PORT (SENARY_MI2S_TX + 1) +#define LPASS_MAX_PORT (QAIF_CDC_DMA_VA_TX9 + 1) + +struct qcom_snd_tdm_slot_cfg { + unsigned int tx_mask; + unsigned int rx_mask; + unsigned int slots; + unsigned int slot_width; +}; int qcom_snd_parse_of(struct snd_soc_card *card); +int qcom_snd_get_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd, + struct qcom_snd_tdm_slot_cfg *cpu_cfg, + struct qcom_snd_tdm_slot_cfg *codec_cfg); +int qcom_snd_apply_dai_tdm_slots_cfg(struct snd_soc_pcm_runtime *rtd, + const struct qcom_snd_tdm_slot_cfg *cpu_cfg, + const struct qcom_snd_tdm_slot_cfg *codec_cfg); +int qcom_snd_apply_dai_tdm_slots(struct snd_soc_pcm_runtime *rtd); int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, struct snd_soc_jack *jack, bool *jack_setup); int qcom_snd_dp_jack_setup(struct snd_soc_pcm_runtime *rtd, diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c index 242bc16da36da..61efa45376cd8 100644 --- a/sound/soc/qcom/lpass-cpu.c +++ b/sound/soc/qcom/lpass-cpu.c @@ -458,30 +458,9 @@ const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops2 = { }; EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops2); -static int asoc_qcom_of_xlate_dai_name(struct snd_soc_component *component, - const struct of_phandle_args *args, - const char **dai_name) -{ - struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); - const struct lpass_variant *variant = drvdata->variant; - int id = args->args[0]; - int ret = -EINVAL; - int i; - - for (i = 0; i < variant->num_dai; i++) { - if (variant->dai_driver[i].id == id) { - *dai_name = variant->dai_driver[i].name; - ret = 0; - break; - } - } - - return ret; -} - static const struct snd_soc_component_driver lpass_cpu_comp_driver = { .name = "lpass-cpu", - .of_xlate_dai_name = asoc_qcom_of_xlate_dai_name, + .of_xlate_dai_name = snd_soc_of_xlate_dai_name, .legacy_dai_naming = 1, }; diff --git a/sound/soc/qcom/qaif-cpu.c b/sound/soc/qcom/qaif-cpu.c new file mode 100644 index 0000000000000..384562dbd56a8 --- /dev/null +++ b/sound/soc/qcom/qaif-cpu.c @@ -0,0 +1,877 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-cpu.c -- ALSA SoC CPU-Platform DAI driver for QTi QAIF + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qaif-reg.h" +#include "qaif.h" + +/* CIF INTF CFG register bit positions */ +#define QAIF_CIF_INTF_16BIT_UNPACK_BIT BIT(0) +#define QAIF_CIF_INTF_DYNCLK_BIT BIT(2) +#define QAIF_CIF_INTF_FS_SEL_MASK GENMASK(7, 4) +#define QAIF_CIF_INTF_ACTIVE_CH_MASK GENMASK(27, 12) +#define QAIF_CIF_INTF_ACTIVE_CH_SHIFT 12 + +/* AIF CTL register (QAIF_AUD_INTF_CTL_REG) bit positions */ +#define QAIF_AIF_CTL_ENABLE_BIT BIT(0) +#define QAIF_AIF_CTL_ENABLE_TX_BIT BIT(4) +#define QAIF_AIF_CTL_ENABLE_RX_BIT BIT(8) + +/* AIF SYNC CFG register (QAIF_AUD_INTF_SYNC_CFG_REG) bit positions */ +#define QAIF_AIF_SYNC_SRC_BIT BIT(0) +#define QAIF_AIF_SYNC_MODE_MASK GENMASK(5, 4) +#define QAIF_AIF_SYNC_MODE_SHIFT 4 +#define QAIF_AIF_SYNC_DELAY_MASK GENMASK(9, 8) +#define QAIF_AIF_SYNC_DELAY_SHIFT 8 +#define QAIF_AIF_SYNC_INV_BIT BIT(12) + +/* SYNC_MODE field values */ +#define QAIF_AIF_SYNC_MODE_SHORT 0 +#define QAIF_AIF_SYNC_MODE_ONE_SLOT 1 +#define QAIF_AIF_SYNC_MODE_LONG 2 + +/* SYNC_DELAY field values */ +#define QAIF_AIF_SYNC_DELAY_SAME 0 +#define QAIF_AIF_SYNC_DELAY_ONE 1 + +/* AIF BIT WIDTH CFG register (QAIF_AUD_INTF_BIT_WIDTH_CFG_REG) bit positions */ +#define QAIF_AIF_SLOT_WIDTH_TX_MASK GENMASK(4, 0) +#define QAIF_AIF_SLOT_WIDTH_RX_MASK GENMASK(12, 8) +#define QAIF_AIF_SLOT_WIDTH_RX_SHIFT 8 +#define QAIF_AIF_SAMPLE_WIDTH_TX_MASK GENMASK(20, 16) +#define QAIF_AIF_SAMPLE_WIDTH_TX_SHIFT 16 +#define QAIF_AIF_SAMPLE_WIDTH_RX_MASK GENMASK(28, 24) +#define QAIF_AIF_SAMPLE_WIDTH_RX_SHIFT 24 + +/* + * slot_width is programmed 0-based into the 5-bit SLOT_WIDTH field and the + * frame length (slots * slot_width) 0-based into the 10-bit BITS_PER_LANE + * field, so these are the largest values the registers can represent. + */ +#define QAIF_AIF_SLOT_WIDTH_MAX 32 +#define QAIF_AIF_FRAME_BITS_MAX 1024 + +/* AIF FRAME CFG register (QAIF_AUD_INTF_FRAME_CFG_REG) bit positions */ +#define QAIF_AIF_BITS_PER_LANE_MASK GENMASK(9, 0) + +/* AIF LANE CFG register (QAIF_AUD_INTF_LANE_CFG_REG) bit positions */ +#define QAIF_AIF_LANE_DIR_MASK GENMASK(7, 0) +#define QAIF_AIF_LANE_EN_MASK GENMASK(15, 8) +#define QAIF_AIF_LANE_EN_SHIFT 8 +#define QAIF_AIF_CTRL_DATA_OE_BIT BIT(16) +#define QAIF_AIF_LOOPBACK_EN_BIT BIT(31) + +/* AIF MI2S CFG register (QAIF_AUD_INTF_MI2S_CFG_REG) bit positions */ +#define QAIF_AIF_MONO_MODE_TX_BIT BIT(0) +#define QAIF_AIF_MONO_MODE_RX_BIT BIT(1) + +/* AIF CFG register (QAIF_AUD_INTF_CFG_REG) bit positions */ +#define QAIF_AIF_FULL_CYCLE_EN_BIT BIT(0) + +static inline u32 qaif_cif_intf_reg(const struct qaif_variant *v, + int idx, unsigned int dai_id) +{ + if (dai_id >= QAIF_CDC_DMA_RX0 && dai_id <= QAIF_CDC_DMA_RX9) + return QAIF_CDC_RDDMA_INTF_CFG_REG(v, idx); + return QAIF_CDC_WRDMA_INTF_CFG_REG(v, idx); +} + +static int qaif_cif_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = dai->driver->id; + unsigned int regval; + int idx; + + switch (params_channels(params)) { + case 1: + regval = QAIF_CIF_DMA_INTF_ONE_CHANNEL; + break; + case 2: + regval = QAIF_CIF_DMA_INTF_TWO_CHANNEL; + break; + case 4: + regval = QAIF_CIF_DMA_INTF_FOUR_CHANNEL; + break; + case 6: + regval = QAIF_CIF_DMA_INTF_SIX_CHANNEL; + break; + case 8: + regval = QAIF_CIF_DMA_INTF_EIGHT_CHANNEL; + break; + default: + dev_dbg(rtd->dev, "unsupported channel count %u\n", + params_channels(params)); + return -EINVAL; + } + + idx = v->get_dma_idx(dai_id); + if (idx < 0) { + dev_err(rtd->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + return regmap_update_bits(drvdata->audio_qaif_map, + qaif_cif_intf_reg(v, idx, dai_id), + QAIF_CIF_INTF_ACTIVE_CH_MASK, + regval << QAIF_CIF_INTF_ACTIVE_CH_SHIFT); +} + +static int qaif_cif_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = dai->driver->id; + struct regmap *map = drvdata->audio_qaif_map; + u32 reg; + int idx, ret; + + idx = v->get_dma_idx(dai_id); + if (idx < 0) { + dev_err(rtd->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + reg = qaif_cif_intf_reg(v, idx, dai_id); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_update_bits(map, reg, + QAIF_CIF_INTF_DYNCLK_BIT, + QAIF_CIF_INTF_DYNCLK_BIT); + if (ret) + return ret; + ret = regmap_update_bits(map, reg, QAIF_CIF_INTF_FS_SEL_MASK, 0); + if (ret) + return ret; + return regmap_update_bits(map, reg, + QAIF_CIF_INTF_16BIT_UNPACK_BIT, + QAIF_CIF_INTF_16BIT_UNPACK_BIT); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(map, reg, QAIF_CIF_INTF_DYNCLK_BIT, 0); + if (ret) + return ret; + return regmap_update_bits(map, reg, + QAIF_CIF_INTF_16BIT_UNPACK_BIT, 0); + default: + dev_err(rtd->dev, "invalid trigger cmd %d\n", cmd); + return -EINVAL; + } +} + +static int qaif_cif_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_prepare_enable(drvdata->aud_dma_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->aud_dma_mem_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma_mem clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_clk); + return ret; + } + return 0; +} + +static void qaif_cif_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); +} + +/* + * asoc_qcom_qaif_cif_dai_ops - DAI ops for QAIF CIF (CDC DMA) paths + * + * Provides startup, shutdown, hw_params and trigger callbacks for the + * Codec Interface DMA (CIF) DAI. startup/shutdown enable and disable the + * audio DMA clocks. Used by Shikra machine drivers for Bolero CDC DMA + * RX, TX and VA TX paths. + */ +const struct snd_soc_dai_ops asoc_qcom_qaif_cif_dai_ops = { + .startup = qaif_cif_daiops_startup, + .shutdown = qaif_cif_daiops_shutdown, + .hw_params = qaif_cif_daiops_hw_params, + .trigger = qaif_cif_daiops_trigger, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_cif_dai_ops); + +static int qaif_aif_cpu_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + ret = clk_prepare_enable(drvdata->aud_dma_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->aud_dma_mem_clk); + if (ret) { + dev_err(dai->dev, "error enabling aud_dma_mem clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_clk); + return ret; + } + + ret = clk_prepare_enable(drvdata->mi2s_bit_clk[idx]); + if (ret) { + dev_err(dai->dev, "error enabling mi2s bit clk: %d\n", ret); + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); + } + return ret; +} + +static void qaif_aif_cpu_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + const struct qaif_aif_config *cfg; + unsigned int enable_bit; + int idx = v->get_dma_idx(dai->driver->id); + int ret; + + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + if (cfg->loopback_en) + enable_bit = QAIF_AIF_CTL_ENABLE_BIT; + else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + enable_bit = QAIF_AIF_CTL_ENABLE_TX_BIT; + else + enable_bit = QAIF_AIF_CTL_ENABLE_RX_BIT; + + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, 0); + if (ret) + dev_err(dai->dev, "error clearing AIF enable bit: %d\n", ret); + + clk_disable_unprepare(drvdata->mi2s_bit_clk[idx]); + clk_disable_unprepare(drvdata->aud_dma_mem_clk); + clk_disable_unprepare(drvdata->aud_dma_clk); +} + +static int qaif_aif_cpu_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + const struct qaif_aif_config *cfg; + struct regmap *map = drvdata->audio_qaif_map; + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int slot_width; + int bitwidth, idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "invalid bit width given: %d\n", bitwidth); + return bitwidth; + } + + /* + * The sync, lane and frame configuration and the MI2S bit clock are + * shared by both directions of an AIF. Concurrent playback and + * capture on the same interface are therefore expected to use a + * compatible format, rate and slot configuration. + */ + ret = regmap_update_bits(map, QAIF_AUD_INTF_SYNC_CFG_REG(idx), + QAIF_AIF_SYNC_SRC_BIT | + QAIF_AIF_SYNC_MODE_MASK | + QAIF_AIF_SYNC_DELAY_MASK | + QAIF_AIF_SYNC_INV_BIT, + (cfg->sync_src ? QAIF_AIF_SYNC_SRC_BIT : 0) | + (cfg->sync_mode << QAIF_AIF_SYNC_MODE_SHIFT) | + (cfg->sync_delay << QAIF_AIF_SYNC_DELAY_SHIFT) | + (cfg->invert_sync ? QAIF_AIF_SYNC_INV_BIT : 0)); + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_LANE_CFG_REG(idx), + QAIF_AIF_LANE_DIR_MASK | + QAIF_AIF_LANE_EN_MASK | + QAIF_AIF_CTRL_DATA_OE_BIT | + QAIF_AIF_LOOPBACK_EN_BIT, + cfg->lane_dir_mask | + (cfg->lane_en_mask << QAIF_AIF_LANE_EN_SHIFT) | + (cfg->ctrl_data_oe ? QAIF_AIF_CTRL_DATA_OE_BIT : 0) | + (cfg->loopback_en ? QAIF_AIF_LOOPBACK_EN_BIT : 0)); + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_CFG_REG(idx), + QAIF_AIF_FULL_CYCLE_EN_BIT, + cfg->full_cycle_en ? QAIF_AIF_FULL_CYCLE_EN_BIT : 0); + if (ret) + return ret; + + if (qaif_is_mi2s_port(dai->driver->id) && !cfg->slot_width) { + slot_width = bitwidth; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + drvdata->aif_intf_cfg[idx].slot_en_tx_mask = + GENMASK(channels - 1, 0); + else + drvdata->aif_intf_cfg[idx].slot_en_rx_mask = + GENMASK(channels - 1, 0); + drvdata->aif_intf_cfg[idx].slot_num = QAIF_MI2S_SLOTS; + } else { + slot_width = cfg->slot_width; + if (!slot_width || !cfg->slot_num) { + dev_err(dai->dev, "invalid TDM slot config: width=%u num=%u\n", + slot_width, cfg->slot_num); + return -EINVAL; + } + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = regmap_update_bits(map, QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(idx), + QAIF_AIF_SLOT_WIDTH_TX_MASK | + QAIF_AIF_SAMPLE_WIDTH_TX_MASK, + QAIF_AIF_SLOT_WIDTH(slot_width) | + (QAIF_AIF_SAMPLE_WIDTH(bitwidth) << + QAIF_AIF_SAMPLE_WIDTH_TX_SHIFT)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_AUD_INTF_ACTV_SLOT_EN_TX_REG(idx), + cfg->slot_en_tx_mask); + if (ret) + return ret; + ret = regmap_update_bits(map, QAIF_AUD_INTF_MI2S_CFG_REG(idx), + QAIF_AIF_MONO_MODE_TX_BIT, + (channels < 2) ? QAIF_AIF_MONO_MODE_TX_BIT : 0); + } else { + ret = regmap_update_bits(map, QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(idx), + QAIF_AIF_SLOT_WIDTH_RX_MASK | + QAIF_AIF_SAMPLE_WIDTH_RX_MASK, + (QAIF_AIF_SLOT_WIDTH(slot_width) << + QAIF_AIF_SLOT_WIDTH_RX_SHIFT) | + (QAIF_AIF_SAMPLE_WIDTH(bitwidth) << + QAIF_AIF_SAMPLE_WIDTH_RX_SHIFT)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_AUD_INTF_ACTV_SLOT_EN_RX_REG(idx), + cfg->slot_en_rx_mask); + if (ret) + return ret; + ret = regmap_update_bits(map, QAIF_AUD_INTF_MI2S_CFG_REG(idx), + QAIF_AIF_MONO_MODE_RX_BIT, + (channels < 2) ? QAIF_AIF_MONO_MODE_RX_BIT : 0); + } + if (ret) + return ret; + + ret = regmap_update_bits(map, QAIF_AUD_INTF_FRAME_CFG_REG(idx), + QAIF_AIF_BITS_PER_LANE_MASK, + (slot_width * cfg->slot_num) - 1); + if (ret) + return ret; + + return 0; +} + +static int qaif_aif_cpu_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + const struct qaif_aif_config *cfg; + unsigned int enable_bit; + int idx, ret; + + idx = v->get_dma_idx(dai->driver->id); + if (idx < 0) { + dev_err(dai->dev, "Invalid DMA index: %d\n", idx); + return -EINVAL; + } + + cfg = &drvdata->aif_intf_cfg[idx]; + if (cfg->loopback_en) + enable_bit = QAIF_AIF_CTL_ENABLE_BIT; + else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + enable_bit = QAIF_AIF_CTL_ENABLE_TX_BIT; + else + enable_bit = QAIF_AIF_CTL_ENABLE_RX_BIT; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, enable_bit); + if (ret) + dev_err(dai->dev, "error setting AIF enable bit: %d\n", ret); + return ret; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(drvdata->audio_qaif_map, + QAIF_AUD_INTF_CTL_REG(idx), enable_bit, 0); + if (ret) + dev_err(dai->dev, "error clearing AIF enable bit: %d\n", ret); + return ret; + default: + dev_err(dai->dev, "invalid trigger cmd %d\n", cmd); + return -EINVAL; + } +} + +static int qaif_aif_cpu_daiops_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, int slots, + int slot_width) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + struct qaif_aif_config *cfg; + + if (idx < 0) + return -EINVAL; + + if (slots <= 0 || slot_width <= 0) + return -EINVAL; + + if (slot_width > QAIF_AIF_SLOT_WIDTH_MAX || + slots * slot_width > QAIF_AIF_FRAME_BITS_MAX) + return -EINVAL; + + cfg = &drvdata->aif_intf_cfg[idx]; + cfg->slot_num = slots; + cfg->slot_width = slot_width; + cfg->slot_en_tx_mask = tx_mask; + cfg->slot_en_rx_mask = rx_mask; + + return 0; +} + +static int qaif_aif_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, + int dir) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + + if (idx < 0) + return -EINVAL; + + /* + * The bit clock is prepared and enabled for the lifetime of the + * stream by startup()/shutdown(); only the rate is set here. + */ + return clk_set_rate(drvdata->mi2s_bit_clk[idx], freq); +} + +static int qaif_aif_cpu_daiops_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct qaif_drv_data *drvdata = snd_soc_dai_get_drvdata(dai); + const struct qaif_variant *v = drvdata->variant; + int idx = v->get_dma_idx(dai->driver->id); + struct qaif_aif_config *cfg; + + if (idx < 0) + return -EINVAL; + + cfg = &drvdata->aif_intf_cfg[idx]; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_LONG; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + break; + case SND_SOC_DAIFMT_DSP_A: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_ONE_SLOT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + break; + case SND_SOC_DAIFMT_DSP_B: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_SHORT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_SAME; + break; + default: + dev_err(dai->dev, "unsupported format 0x%x\n", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + cfg->sync_src = 1; + break; + case SND_SOC_DAIFMT_CBC_CFC: + cfg->sync_src = 0; + break; + default: + dev_err(dai->dev, "unsupported clock provider mode\n"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + cfg->invert_sync = (fmt & SND_SOC_DAIFMT_INV_MASK) == + SND_SOC_DAIFMT_NB_IF; + break; + default: + dev_err(dai->dev, "bit clock inversion is not supported\n"); + return -EINVAL; + } + + return 0; +} + +/* + * asoc_qcom_qaif_aif_cpu_dai_ops - DAI ops for QAIF AIF (MI2S/TDM/PCM) paths + * + * Provides startup, shutdown, hw_params and trigger callbacks + * for the Unified Audio Interface (AIF) DAI. Used by Shikra machine + * drivers for MI2S, TDM and PCM serial audio paths. + */ +const struct snd_soc_dai_ops asoc_qcom_qaif_aif_cpu_dai_ops = { + .set_fmt = qaif_aif_cpu_daiops_set_fmt, + .set_tdm_slot = qaif_aif_cpu_daiops_set_tdm_slot, + .set_sysclk = qaif_aif_cpu_daiops_set_sysclk, + .startup = qaif_aif_cpu_daiops_startup, + .shutdown = qaif_aif_cpu_daiops_shutdown, + .hw_params = qaif_aif_cpu_daiops_hw_params, + .trigger = qaif_aif_cpu_daiops_trigger, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_aif_cpu_dai_ops); + +static const struct snd_soc_component_driver qaif_cpu_comp_driver = { + .name = "qaif-cpu", + .of_xlate_dai_name = snd_soc_of_xlate_dai_name, +}; + +static const struct regmap_config audio_qaif_regmap_config = { + .name = "audio_qaif_cpu", + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .cache_type = REGCACHE_NONE, +}; + +static int of_qaif_parse_aif_intf_cfg(struct device *dev, + struct qaif_drv_data *data) +{ + const struct qaif_variant *v = data->variant; + struct device_node *np = dev->of_node; + struct device_node *intf_np; + struct qaif_aif_config *cfg; + int ret; + u32 dai_id; + int intf_idx; + int num_interfaces = 0; + u32 val_buf[QAIF_MAX_LANES]; + int j, n; + unsigned long configured_intf_mask = 0; + + for_each_child_of_node(np, intf_np) { + if (!of_property_present(intf_np, "reg")) + continue; + + if (num_interfaces >= QAIF_MAX_AIF_CFG_CNT) { + dev_warn(dev, "Too many AIF interfaces, limiting to %d\n", + QAIF_MAX_AIF_CFG_CNT); + of_node_put(intf_np); + break; + } + + ret = of_property_read_u32(intf_np, "reg", &dai_id); + if (ret) { + dev_err(dev, "Missing reg for interface %pOFn\n", intf_np); + of_node_put(intf_np); + return ret; + } + + intf_idx = v->get_dma_idx(dai_id); + if (intf_idx < 0) { + dev_err(dev, "Invalid DAI ID %d for interface %pOFn\n", + dai_id, intf_np); + of_node_put(intf_np); + return -EINVAL; + } + if (intf_idx >= ARRAY_SIZE(data->aif_intf_cfg)) { + dev_err(dev, "DAI ID %d maps to out-of-range intf_idx %d\n", + dai_id, intf_idx); + of_node_put(intf_np); + return -EINVAL; + } + + if (configured_intf_mask & BIT(intf_idx)) { + dev_err(dev, "Duplicate reg %d for interface %pOFn\n", + dai_id, intf_np); + of_node_put(intf_np); + return -EINVAL; + } + configured_intf_mask |= BIT(intf_idx); + + cfg = &data->aif_intf_cfg[intf_idx]; + if (v->aif_full_cycle_en) + cfg->full_cycle_en = v->aif_full_cycle_en[intf_idx]; + if (v->aif_ctrl_data_oe) + cfg->ctrl_data_oe = v->aif_ctrl_data_oe[intf_idx]; + if (v->aif_loopback_en) + cfg->loopback_en = v->aif_loopback_en[intf_idx]; + + switch (snd_soc_daifmt_parse_format(intf_np, NULL) & + SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_LONG; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + cfg->invert_sync = true; + break; + case SND_SOC_DAIFMT_DSP_A: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_ONE_SLOT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_ONE; + break; + case SND_SOC_DAIFMT_DSP_B: + cfg->sync_mode = QAIF_AIF_SYNC_MODE_SHORT; + cfg->sync_delay = QAIF_AIF_SYNC_DELAY_SAME; + break; + default: + break; + } + + if (!qaif_is_mi2s_port(dai_id)) { + ret = of_property_read_u32(intf_np, "dai-tdm-slot-width", + &cfg->slot_width); + if (ret || !cfg->slot_width) { + dev_err(dev, "Missing/invalid dai-tdm-slot-width for %pOFn\n", + intf_np); + of_node_put(intf_np); + return ret ? ret : -EINVAL; + } + ret = of_property_read_u32(intf_np, "dai-tdm-slot-num", + &cfg->slot_num); + if (ret || !cfg->slot_num) { + dev_err(dev, "Missing/invalid dai-tdm-slot-num for %pOFn\n", + intf_np); + of_node_put(intf_np); + return ret ? ret : -EINVAL; + } + snd_soc_of_get_slot_mask(intf_np, "dai-tdm-slot-rx-mask", + &cfg->slot_en_rx_mask); + snd_soc_of_get_slot_mask(intf_np, "dai-tdm-slot-tx-mask", + &cfg->slot_en_tx_mask); + if (!cfg->slot_en_rx_mask && !cfg->slot_en_tx_mask) { + dev_err(dev, "No active TDM slots for %pOFn\n", + intf_np); + of_node_put(intf_np); + return -EINVAL; + } + } + + n = of_property_read_variable_u32_array(intf_np, + "qcom,qaif-aif-lane-map", + val_buf, 1, QAIF_MAX_LANES); + if (n < 0) { + dev_err(dev, "Missing qcom,qaif-aif-lane-map for %pOFn\n", + intf_np); + of_node_put(intf_np); + return n; + } + for (j = 0; j < n; j++) { + if (val_buf[j] > 1) { + dev_err(dev, "Invalid lane-map value %u for %pOFn\n", + val_buf[j], intf_np); + of_node_put(intf_np); + return -EINVAL; + } + cfg->lane_en_mask |= BIT(j); + if (val_buf[j]) + cfg->lane_dir_mask |= BIT(j); + } + + num_interfaces++; + } + + return 0; +} + +/** + * asoc_qcom_qaif_cpu_platform_probe - Probe the QAIF CPU and platform driver + * @pdev: Platform device + * + * Initialises the QAIF regmap, parses DT, sets up clocks and registers + * the CPU DAI component and PCM platform. + * + * Return: 0 on success, negative error code on failure. + */ +int asoc_qcom_qaif_cpu_platform_probe(struct platform_device *pdev) +{ + struct regmap_config regmap_cfg = audio_qaif_regmap_config; + struct qaif_drv_data *drvdata; + struct resource *res; + const struct qaif_variant *variant; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + int ret, i, dai_id, idx; + + drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return dev_err_probe(dev, -ENOMEM, "Failed to allocate driver data\n"); + platform_set_drvdata(pdev, drvdata); + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) + return dev_err_probe(dev, -EINVAL, "No matching device data\n"); + + drvdata->variant = match->data; + variant = drvdata->variant; + + if (!variant->get_dma_idx) + return dev_err_probe(dev, -EINVAL, + "variant is missing get_dma_idx callback\n"); + + ret = of_qaif_parse_aif_intf_cfg(dev, drvdata); + if (ret) + return dev_err_probe(dev, ret, "Failed to parse aif interfaces\n"); + + drvdata->audio_qaif = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(drvdata->audio_qaif)) + return dev_err_probe(dev, PTR_ERR(drvdata->audio_qaif), + "Failed to ioremap QAIF registers\n"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return dev_err_probe(dev, -EINVAL, "Failed to get MMIO resource\n"); + + regmap_cfg.max_register = resource_size(res) - regmap_cfg.reg_stride; + drvdata->audio_qaif_map = devm_regmap_init_mmio(dev, drvdata->audio_qaif, + ®map_cfg); + if (IS_ERR(drvdata->audio_qaif_map)) + return dev_err_probe(dev, PTR_ERR(drvdata->audio_qaif_map), + "Failed to init regmap\n"); + + drvdata->aud_dma_clk = devm_clk_get(dev, "aud_dma"); + if (IS_ERR(drvdata->aud_dma_clk)) + return dev_err_probe(dev, PTR_ERR(drvdata->aud_dma_clk), + "Failed to get aud_dma clock\n"); + + drvdata->aud_dma_mem_clk = devm_clk_get(dev, "aud_dma_mem"); + if (IS_ERR(drvdata->aud_dma_mem_clk)) + return dev_err_probe(dev, PTR_ERR(drvdata->aud_dma_mem_clk), + "Failed to get aud_dma_mem clock\n"); + + for (i = 0; i < variant->num_dai; i++) { + dai_id = variant->dai_driver[i].id; + if (qaif_is_cif_dma_port(dai_id)) + continue; + idx = variant->get_dma_idx(dai_id); + if (idx < 0) + continue; + + drvdata->mi2s_bit_clk[idx] = + devm_clk_get(dev, variant->dai_bit_clk_names[idx]); + if (IS_ERR(drvdata->mi2s_bit_clk[idx])) + return dev_err_probe(dev, PTR_ERR(drvdata->mi2s_bit_clk[idx]), + "Failed to get bit clock %d\n", idx); + } + + drvdata->num_clks = variant->num_clks; + drvdata->clks = devm_kcalloc(dev, drvdata->num_clks, + sizeof(*drvdata->clks), GFP_KERNEL); + if (!drvdata->clks) + return dev_err_probe(dev, -ENOMEM, "Failed to allocate clocks\n"); + + for (i = 0; i < drvdata->num_clks; i++) + drvdata->clks[i].id = variant->clk_name[i]; + + ret = devm_clk_bulk_get(dev, drvdata->num_clks, drvdata->clks); + if (ret) + return dev_err_probe(dev, ret, "Failed to get clocks\n"); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + ret = devm_snd_soc_register_component(dev, &qaif_cpu_comp_driver, + variant->dai_driver, variant->num_dai); + if (ret) + return dev_err_probe(dev, ret, "error registering cpu driver\n"); + + return asoc_qcom_qaif_platform_register(pdev); +} +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_cpu_platform_probe); + +static int asoc_qcom_qaif_runtime_suspend(struct device *dev) +{ + struct qaif_drv_data *drvdata = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks); + return 0; +} + +static int asoc_qcom_qaif_runtime_resume(struct device *dev) +{ + struct qaif_drv_data *drvdata = dev_get_drvdata(dev); + + return clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks); +} + +EXPORT_GPL_DEV_PM_OPS(asoc_qcom_qaif_pm_ops) = { + RUNTIME_PM_OPS(asoc_qcom_qaif_runtime_suspend, + asoc_qcom_qaif_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) +}; + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) CPU DAI driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qaif-platform.c b/sound/soc/qcom/qaif-platform.c new file mode 100644 index 0000000000000..2e5dc5b081cd1 --- /dev/null +++ b/sound/soc/qcom/qaif-platform.c @@ -0,0 +1,935 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-platform.c -- ALSA SoC PCM platform driver for the Qualcomm Audio Interface (QAIF) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qaif-reg.h" +#include "qaif.h" + +#define DRV_NAME "qaif-platform" + +/* period: 5ms @ 48kHz/1ch/S16 min, 20ms @ 48kHz/8ch/S32 max */ +#define QAIF_PLATFORM_PERIOD_BYTES_MIN (48 * 5 * 1 * 2) /* 480 B */ +#define QAIF_PLATFORM_PERIOD_BYTES_MAX (48 * 20 * 8 * 4) /* 30720 B */ +#define QAIF_PLATFORM_PERIODS_MIN 2 +#define QAIF_PLATFORM_PERIODS_MAX 4 +/* QAIF_PLATFORM_PERIODS_MAX * QAIF_PLATFORM_PERIOD_BYTES_MAX = 122880 B */ +#define QAIF_PLATFORM_BUFFER_BYTES_MAX \ + (QAIF_PLATFORM_PERIODS_MAX * QAIF_PLATFORM_PERIOD_BYTES_MAX) + +/* DMA CFG register bit positions */ +#define QAIF_DMACFG_SHRAM_WM_MASK GENMASK(11, 0) +#define QAIF_DMACFG_BURST4_BIT BIT(18) +#define QAIF_DMACFG_DYNCLK_BIT BIT(24) + +/* DMA CTL register bit positions */ +#define QAIF_DMACTL_ENABLE_BIT BIT(0) +#define QAIF_ALL_CLIENTS_MASK (QAIF_BITMASK_AIF_RDDMA_WRDMA | \ + QAIF_BITMASK_CIF_RDDMA_WRDMA) + +static const struct snd_pcm_hardware qaif_platform_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = QAIF_PLATFORM_BUFFER_BYTES_MAX, + .period_bytes_min = QAIF_PLATFORM_PERIOD_BYTES_MIN, + .period_bytes_max = QAIF_PLATFORM_PERIOD_BYTES_MAX, + .periods_min = QAIF_PLATFORM_PERIODS_MIN, + .periods_max = QAIF_PLATFORM_PERIODS_MAX, + .fifo_size = 0, +}; + +static int qaif_map_ee_resource(struct qaif_drv_data *drvdata) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + int ret; + + ret = regmap_write(map, QAIF_EE_RDDMA_MAP_REG(v), GENMASK(v->num_rddma - 1, 0)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_EE_WRDMA_MAP_REG(v), GENMASK(v->num_wrdma - 1, 0)); + if (ret) + return ret; + if (v->num_intf > 0) { + ret = regmap_write(map, QAIF_EE_INTF_MAP_REG(v), GENMASK(v->num_intf - 1, 0)); + if (ret) + return ret; + } + ret = regmap_write(map, QAIF_EE_CODEC_RDDMA_MAP_REG(v), + GENMASK(v->num_codec_rddma - 1, 0)); + if (ret) + return ret; + return regmap_write(map, QAIF_EE_CODEC_WRDMA_MAP_REG(v), + GENMASK(v->num_codec_wrdma - 1, 0)); +} + +static int qaif_map_dma_path(struct qaif_drv_data *drvdata) +{ + struct regmap *map = drvdata->audio_qaif_map; + int ret; + + if (drvdata->variant->qxm_type != QAIF_QXM0) { + dev_err(regmap_get_device(map), + "only QXM0 is supported, qxm_type=%d\n", + drvdata->variant->qxm_type); + return -EINVAL; + } + + ret = regmap_write(map, QAIF_RDDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + ret = regmap_write(map, QAIF_WRDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_RDDMA_MAP_QXM, QAIF_QXM0); + if (ret) + return ret; + return regmap_write(map, QAIF_CDC_WRDMA_MAP_QXM, QAIF_QXM0); +} + +static int qaif_config_shram(struct qaif_drv_data *drvdata) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + u32 start_addr, shram_len; + int ret, i; + + if (v->qxm_type != QAIF_QXM0) { + dev_err(regmap_get_device(map), + "only QXM0 is supported, qxm_type=%d\n", + v->qxm_type); + return -EINVAL; + } + + start_addr = v->rddma_shram_start_addr[QAIF_AIF_DMA]; + shram_len = v->rddma_shram_len; + for (i = 0; i < v->num_rddma; i++) { + ret = regmap_write(map, QAIF_RDDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_RDDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->wrdma_shram_start_addr[QAIF_AIF_DMA]; + shram_len = v->wrdma_shram_len; + for (i = 0; i < v->num_wrdma; i++) { + ret = regmap_write(map, QAIF_WRDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_WRDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->rddma_shram_start_addr[QAIF_CIF_DMA]; + shram_len = v->rddma_shram_len; + for (i = 0; i < v->num_codec_rddma; i++) { + ret = regmap_write(map, QAIF_CDC_RDDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_RDDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + + start_addr = v->wrdma_shram_start_addr[QAIF_CIF_DMA]; + shram_len = v->wrdma_shram_len; + for (i = 0; i < v->num_codec_wrdma; i++) { + ret = regmap_write(map, QAIF_CDC_WRDMA_QXM0_SHRAM_ST_ADDR(i), + start_addr + (shram_len * i)); + if (ret) + return ret; + ret = regmap_write(map, QAIF_CDC_WRDMA_QXM0_SHRAM_LEN(i), shram_len); + if (ret) + return ret; + } + return 0; +} + +static int qaif_init(struct snd_soc_component *component) +{ + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + int ret; + + if (drvdata->qaif_hw_configured) + return 0; + + ret = qaif_config_shram(drvdata); + if (ret) { + dev_err(component->dev, "failed to config shram: %d\n", ret); + return ret; + } + + ret = qaif_map_ee_resource(drvdata); + if (ret) { + dev_err(component->dev, "failed to map EE resources: %d\n", ret); + return ret; + } + + ret = qaif_map_dma_path(drvdata); + if (ret) + return ret; + + drvdata->qaif_hw_configured = true; + return 0; +} + +static int qaif_platform_pcmops_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + unsigned int dai_id = cpu_dai->driver->id; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct qaif_dma_mem_info *dma_mem_info; + struct qaif_pcm_data *data; + int ret, stream_dma_idx, dma_reg_idx, dir = substream->stream; + + dma_reg_idx = v->get_dma_idx(dai_id); + if (dma_reg_idx < 0) { + dev_err(component->dev, "invalid DMA register index for DAI %u: %d\n", + dai_id, dma_reg_idx); + return dma_reg_idx; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + runtime->private_data = data; + + buf->dev.dev = component->dev; + buf->private_data = NULL; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + + dma_mem_info = kzalloc(sizeof(*dma_mem_info), GFP_KERNEL); + if (!dma_mem_info) { + ret = -ENOMEM; + goto err_free_data; + } + dma_mem_info->alloc_size = qaif_platform_hardware.buffer_bytes_max; + dma_mem_info->vaddr = dma_alloc_coherent(component->dev, + dma_mem_info->alloc_size, + &dma_mem_info->dma_addr, + GFP_KERNEL); + if (!dma_mem_info->vaddr) { + ret = -ENOMEM; + goto err_free_mem_info; + } + + mutex_lock(&drvdata->stream_lock); + if (!v->alloc_stream_dma_idx) { + ret = -EINVAL; + goto err_unlock; + } + stream_dma_idx = v->alloc_stream_dma_idx(drvdata, dir, dai_id); + if (stream_dma_idx < 0) { + ret = stream_dma_idx; + goto err_unlock; + } + mutex_unlock(&drvdata->stream_lock); + + data->stream_dma_idx = stream_dma_idx; + data->dma_reg_idx = dma_reg_idx; + + buf->bytes = qaif_platform_hardware.buffer_bytes_max; + buf->addr = dma_mem_info->dma_addr; + buf->area = (unsigned char *)dma_mem_info->vaddr; + + if (qaif_is_cif_dma_port(dai_id)) { + WRITE_ONCE(drvdata->cif_substream[stream_dma_idx], substream); + drvdata->cif_dma_heap[stream_dma_idx] = dma_mem_info; + } else { + WRITE_ONCE(drvdata->aif_substream[stream_dma_idx], substream); + drvdata->aif_dma_heap[stream_dma_idx] = dma_mem_info; + } + + snd_soc_set_runtime_hwparams(substream, &qaif_platform_hardware); + runtime->dma_bytes = qaif_platform_hardware.buffer_bytes_max; + snd_pcm_set_runtime_buffer(substream, buf); + + /* + * The DMA buffer/period length registers are programmed in 64-bit + * (8-byte) words, so constrain buffer and period sizes to that step + * to keep the ALSA and hardware sizes identical. + */ + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + BIT(QAIF_DMA_BYTES_TO_WORDS_SHIFT)); + if (ret >= 0) + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + BIT(QAIF_DMA_BYTES_TO_WORDS_SHIFT)); + if (ret >= 0) + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(soc_runtime->dev, "setting constraints failed: %d\n", ret); + if (qaif_is_cif_dma_port(dai_id)) { + WRITE_ONCE(drvdata->cif_substream[stream_dma_idx], NULL); + drvdata->cif_dma_heap[stream_dma_idx] = NULL; + } else { + WRITE_ONCE(drvdata->aif_substream[stream_dma_idx], NULL); + drvdata->aif_dma_heap[stream_dma_idx] = NULL; + } + mutex_lock(&drvdata->stream_lock); + if (v->free_stream_dma_idx) + v->free_stream_dma_idx(drvdata, stream_dma_idx, dai_id); + mutex_unlock(&drvdata->stream_lock); + goto err_free_dma; + } + return 0; + +err_unlock: + mutex_unlock(&drvdata->stream_lock); +err_free_dma: + dma_free_coherent(component->dev, dma_mem_info->alloc_size, + dma_mem_info->vaddr, dma_mem_info->dma_addr); +err_free_mem_info: + kfree(dma_mem_info); +err_free_data: + kfree(data); + return ret; +} + +static int qaif_platform_pcmops_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + struct qaif_dma_mem_info *dma_mem_info; + unsigned int dai_id = cpu_dai->driver->id; + + if (qaif_is_cif_dma_port(dai_id)) { + WRITE_ONCE(drvdata->cif_substream[data->stream_dma_idx], NULL); + dma_mem_info = drvdata->cif_dma_heap[data->stream_dma_idx]; + drvdata->cif_dma_heap[data->stream_dma_idx] = NULL; + } else { + WRITE_ONCE(drvdata->aif_substream[data->stream_dma_idx], NULL); + dma_mem_info = drvdata->aif_dma_heap[data->stream_dma_idx]; + drvdata->aif_dma_heap[data->stream_dma_idx] = NULL; + } + + mutex_lock(&drvdata->stream_lock); + if (v->free_stream_dma_idx) + v->free_stream_dma_idx(drvdata, data->stream_dma_idx, dai_id); + mutex_unlock(&drvdata->stream_lock); + + if (dma_mem_info) { + dma_free_coherent(component->dev, dma_mem_info->alloc_size, + dma_mem_info->vaddr, dma_mem_info->dma_addr); + kfree(dma_mem_info); + } + + snd_pcm_set_runtime_buffer(substream, NULL); + substream->runtime->private_data = NULL; + + kfree(data); + return 0; +} + +static int qaif_platform_pcmops_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret; + + mutex_lock(&drvdata->stream_lock); + ret = qaif_init(component); + mutex_unlock(&drvdata->stream_lock); + if (ret) { + dev_err(soc_runtime->dev, "qaif_init failed: %d\n", ret); + return ret; + } + + ret = regmap_update_bits(drvdata->audio_qaif_map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_BURST4_BIT | QAIF_DMACFG_SHRAM_WM_MASK, + QAIF_DMACFG_BURST4_BIT | QAIF_DMACTL_WM_5); + if (ret) + dev_err(soc_runtime->dev, "error updating DMA CFG: %d\n", ret); + return ret; +} + +static int qaif_platform_pcmops_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret; + + ret = regmap_update_bits(drvdata->audio_qaif_map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, 0); + if (ret) { + dev_err(soc_runtime->dev, "error disabling DMA: %d\n", ret); + return ret; + } + + ret = regmap_write(drvdata->audio_qaif_map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), 0); + if (ret) + dev_err(soc_runtime->dev, "error clearing DMA CFG: %d\n", ret); + return ret; +} + +static int qaif_platform_pcmops_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + struct qaif_pcm_data *data = runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + int idx = data->dma_reg_idx; + int ret, dir = substream->stream; + + ret = clk_set_rate(drvdata->aud_dma_clk, QAIF_DMA_CLOCK_FREQ); + if (ret) { + dev_err(soc_runtime->dev, "error setting aud_dma_clk rate: %d\n", ret); + return ret; + } + ret = clk_set_rate(drvdata->aud_dma_mem_clk, QAIF_DMA_CLOCK_FREQ); + if (ret) { + dev_err(soc_runtime->dev, "error setting aud_dma_mem_clk rate: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_sid_map_reg(dir, dai_id), drvdata->smmu_csid_bits); + if (ret) { + dev_err(soc_runtime->dev, "error writing to SID MAP reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmabase_reg(v, idx, dir, dai_id), + lower_32_bits(runtime->dma_addr)); + if (ret) { + dev_err(soc_runtime->dev, "error writing to dma base reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmabuff_reg(v, idx, dir, dai_id), + (snd_pcm_lib_buffer_bytes(substream) >> + QAIF_DMA_BYTES_TO_WORDS_SHIFT) - 1); + if (ret) { + dev_err(soc_runtime->dev, "error writing to dma buf reg: %d\n", ret); + return ret; + } + + ret = regmap_write(map, qaif_dmaper_len_reg(v, idx, dir, dai_id), + (snd_pcm_lib_period_bytes(substream) >> + QAIF_DMA_BYTES_TO_WORDS_SHIFT) - 1); + if (ret) + dev_err(soc_runtime->dev, "error writing to dma period reg: %d\n", ret); + return ret; +} + +static snd_pcm_uframes_t qaif_platform_pcmops_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + unsigned int base_addr, curr_addr; + int idx = data->dma_reg_idx; + int ret, dir = substream->stream; + + ret = regmap_read(map, qaif_dmabase_reg(v, idx, dir, dai_id), &base_addr); + if (ret) { + dev_err(soc_runtime->dev, "error reading from rdmabase reg: %d\n", ret); + return SNDRV_PCM_POS_XRUN; + } + + ret = regmap_read(map, qaif_dmacurr_reg(v, idx, dir, dai_id), &curr_addr); + if (ret) { + dev_err(soc_runtime->dev, "error reading from rdmacurr reg: %d\n", ret); + return SNDRV_PCM_POS_XRUN; + } + + return bytes_to_frames(substream->runtime, + (curr_addr - base_addr) % + snd_pcm_lib_buffer_bytes(substream)); +} + +static int qaif_platform_pcmops_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(component->dev, vma, + runtime->dma_area, runtime->dma_addr, + runtime->dma_bytes); +} + +static int qaif_platform_copy(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *buf, unsigned long bytes) +{ + struct snd_pcm_runtime *rt = substream->runtime; + size_t buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + size_t channel_bytes = buffer_bytes / rt->channels; + unsigned long offset; + void *dma_buf; + size_t copied; + + if (channel < 0 || channel >= rt->channels) + return -EINVAL; + + /* Absolute byte offset into the configured (not the max) buffer. */ + offset = pos + channel * channel_bytes; + if (offset > buffer_bytes || bytes > buffer_bytes - offset) + return -EINVAL; + + dma_buf = rt->dma_area + offset; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + copied = copy_from_iter(dma_buf, bytes, buf); + else + copied = copy_to_iter(dma_buf, bytes, buf); + + return (copied != bytes) ? -EFAULT : 0; +} + +static int qaif_platform_irq_op(struct qaif_drv_data *drvdata, int dir, + enum qaif_irq_type irq_type, int idx, + enum qaif_irq_op op) +{ + const struct qaif_variant *v = drvdata->variant; + struct regmap *map = drvdata->audio_qaif_map; + unsigned int bit = BIT(idx); + unsigned int val = op == QAIF_IRQ_ENABLE ? bit : 0; + u32 per_reg, err_reg, xrun_reg; + int ret; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + per_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_PERIOD_IRQ_EN_REG(v, irq_type); + xrun_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_UF_IRQ_EN_REG(v, irq_type); + err_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_RDDMA_ERR_RSP_IRQ_EN_REG(v, irq_type); + } else { + per_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_PERIOD_IRQ_EN_REG(v, irq_type); + xrun_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_OF_IRQ_EN_REG(v, irq_type); + err_reg = op == QAIF_IRQ_CLEAR ? QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, irq_type) + : QAIF_EE_WRDMA_ERR_RSP_IRQ_EN_REG(v, irq_type); + } + + if (op == QAIF_IRQ_CLEAR) { + ret = regmap_write(map, per_reg, bit); + if (ret) + return ret; + ret = regmap_write(map, xrun_reg, bit); + if (ret) + return ret; + return regmap_write(map, err_reg, bit); + } + + ret = regmap_write_bits(map, per_reg, bit, val); + if (ret) + return ret; + ret = regmap_write_bits(map, xrun_reg, bit, val); + if (ret) + return ret; + return regmap_write_bits(map, err_reg, bit, val); +} + +static int qaif_platform_pcmops_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + const struct qaif_variant *v = drvdata->variant; + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + enum qaif_irq_type irq_type = qaif_is_cif_dma_port(dai_id) ? QAIF_CIF_IRQ : QAIF_AIF_IRQ; + struct regmap *map = drvdata->audio_qaif_map; + int idx = data->dma_reg_idx; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_CLEAR); + if (ret) { + dev_err(soc_runtime->dev, "error clearing irq regs: %d\n", ret); + return ret; + } + ret = regmap_update_bits(map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_DYNCLK_BIT, QAIF_DMACFG_DYNCLK_BIT); + if (ret) { + dev_err(soc_runtime->dev, "error enabling dma_dynclk: %d\n", ret); + return ret; + } + ret = regmap_update_bits(map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, QAIF_DMACTL_ENABLE_BIT); + if (ret) { + dev_err(soc_runtime->dev, "error enabling DMA: %d\n", ret); + return ret; + } + ret = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_ENABLE); + if (ret) + dev_err(soc_runtime->dev, "error enabling irq regs: %d\n", ret); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_update_bits(map, + qaif_dmactl_reg(v, idx, substream->stream, dai_id), + QAIF_DMACTL_ENABLE_BIT, 0); + if (ret) { + dev_err(soc_runtime->dev, "error disabling DMA: %d\n", ret); + return ret; + } + ret = regmap_update_bits(map, + qaif_dmacfg_reg(v, idx, substream->stream, dai_id), + QAIF_DMACFG_DYNCLK_BIT, 0); + if (ret) + dev_err(soc_runtime->dev, "error disabling dma_dynclk: %d\n", ret); + ret = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_DISABLE); + if (ret) + dev_err(soc_runtime->dev, "error disabling irq regs: %d\n", ret); + break; + default: + return -EINVAL; + } + return ret; +} + +static int qaif_platform_pcmops_sync_stop(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(soc_runtime, 0); + struct qaif_drv_data *drvdata = snd_soc_component_get_drvdata(component); + struct qaif_pcm_data *data = substream->runtime->private_data; + unsigned int dai_id = cpu_dai->driver->id; + enum qaif_irq_type irq_type = qaif_is_cif_dma_port(dai_id) ? QAIF_CIF_IRQ : QAIF_AIF_IRQ; + int idx = data->dma_reg_idx; + int ret; + + synchronize_irq(drvdata->audio_qaif_irq); + ret = qaif_platform_irq_op(drvdata, substream->stream, irq_type, + idx, QAIF_IRQ_CLEAR); + if (ret) + dev_err(soc_runtime->dev, "error clearing irq regs: %d\n", ret); + return ret; +} + +static irqreturn_t qaif_process_dma_irq(struct qaif_drv_data *drvdata, + u32 stat_reg_addr, + u32 clr_reg_addr, + enum qaif_irq_type irq_type, + enum qaif_dma_dir dma_type, + enum qaif_irq irq, + struct snd_pcm_substream **substream) +{ + const struct qaif_variant *v = drvdata->variant; + unsigned int reg = 0; + int dma_idx, stream_dma_idx, rv, num_dma, stream_offset, array_size; + u32 mask; + struct snd_pcm_substream *ss; + + stream_offset = (dma_type == QAIF_DMA_WRDMA) ? + ((irq_type == QAIF_AIF_IRQ) ? v->wrdma_start : v->codec_wrdma_start) : 0; + num_dma = (dma_type == QAIF_DMA_WRDMA) ? + ((irq_type == QAIF_AIF_IRQ) ? v->num_wrdma : v->num_codec_wrdma) : + ((irq_type == QAIF_AIF_IRQ) ? v->num_rddma : v->num_codec_rddma); + if (!num_dma) + return IRQ_NONE; + + array_size = (irq_type == QAIF_AIF_IRQ) ? + QAIF_MAX_AIF_DMA_IDX : QAIF_MAX_CIF_DMA_IDX; + if (stream_offset + num_dma > array_size) { + dev_err(regmap_get_device(drvdata->audio_qaif_map), + "DMA index range %d+%d exceeds substream table %d\n", + stream_offset, num_dma, array_size); + return IRQ_NONE; + } + mask = GENMASK(num_dma - 1, 0); + + rv = regmap_read(drvdata->audio_qaif_map, stat_reg_addr, ®); + if (rv) { + dev_err_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "error reading stat reg 0x%x: %d\n", stat_reg_addr, rv); + return IRQ_NONE; + } + + if (!(reg & mask)) { + dev_warn_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "spurious IRQ: stat reg 0x%x status 0x%x mask 0x%x\n", + stat_reg_addr, reg, mask); + return IRQ_NONE; + } + + if (regmap_write(drvdata->audio_qaif_map, clr_reg_addr, reg & mask)) + return IRQ_NONE; + + for (dma_idx = 0; dma_idx < num_dma; dma_idx++) { + stream_dma_idx = dma_idx + stream_offset; + ss = READ_ONCE(substream[stream_dma_idx]); + if (!(reg & BIT(dma_idx)) || !ss) + continue; + switch (irq) { + case QAIF_IRQ_PERIOD: + snd_pcm_period_elapsed(ss); + break; + case QAIF_IRQ_OVERFLOW: + case QAIF_IRQ_UNDERFLOW: + dev_warn_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "QAIF DMA xRun\n"); + snd_pcm_stop_xrun(ss); + break; + case QAIF_IRQ_ERROR: + snd_pcm_stop_xrun(ss); + dev_err_ratelimited(regmap_get_device(drvdata->audio_qaif_map), + "QAIF bus error\n"); + break; + } + } + return IRQ_HANDLED; +} + +static irqreturn_t qaif_aif_irq_handler(struct qaif_drv_data *drvdata, u32 status) +{ + const struct qaif_variant *v = drvdata->variant; + struct snd_pcm_substream **ss = drvdata->aif_substream; + irqreturn_t ret = IRQ_NONE; + + if (status & QAIF_SUMMARY_BITMASK_AIF_PERIOD_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_PERIOD_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_OVERFLOW_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_OVERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_UNDERFLOW_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_UNDERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_ERROR, ss); + if (status & QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_AIF_IRQ), + QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_AIF_IRQ), + QAIF_AIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_ERROR, ss); + return ret; +} + +static irqreturn_t qaif_cif_irq_handler(struct qaif_drv_data *drvdata, u32 status) +{ + const struct qaif_variant *v = drvdata->variant; + struct snd_pcm_substream **ss = drvdata->cif_substream; + irqreturn_t ret = IRQ_NONE; + + if (status & QAIF_SUMMARY_BITMASK_CIF_PERIOD_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_PERIOD_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_PERIOD, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_OVERFLOW_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_OVERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_UNDERFLOW_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_UNDERFLOW, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_RDDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_RDDMA, QAIF_IRQ_ERROR, ss); + if (status & QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_WRDMA) + ret |= qaif_process_dma_irq(drvdata, + QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, QAIF_CIF_IRQ), + QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, QAIF_CIF_IRQ), + QAIF_CIF_IRQ, QAIF_DMA_WRDMA, QAIF_IRQ_ERROR, ss); + return ret; +} + +static const struct qaif_irq_map qaif_irq_clients[] = { + { QAIF_CLIENT_ID_AIF_DMA, QAIF_BITMASK_AIF_RDDMA_WRDMA, qaif_aif_irq_handler }, + { QAIF_CLIENT_ID_CIF_DMA, QAIF_BITMASK_CIF_RDDMA_WRDMA, qaif_cif_irq_handler }, +}; + +static irqreturn_t asoc_platform_qaif_irq(int irq, void *data) +{ + struct qaif_drv_data *drvdata = data; + const struct qaif_variant *v = drvdata->variant; + u32 summary_irq_status; + int rv, client; + irqreturn_t ret = IRQ_NONE; + + rv = regmap_read(drvdata->audio_qaif_map, QAIF_SUMMARY_IRQSTAT_REG(v), + &summary_irq_status); + if (rv) { + dev_err(regmap_get_device(drvdata->audio_qaif_map), + "error reading from irqstat reg: %d\n", rv); + return IRQ_NONE; + } + + if (!(summary_irq_status & QAIF_ALL_CLIENTS_MASK)) + return IRQ_NONE; + + for (client = 0; client < ARRAY_SIZE(qaif_irq_clients); client++) { + if (summary_irq_status & qaif_irq_clients[client].mask) + ret |= qaif_irq_clients[client].client_irq_handler(drvdata, + summary_irq_status); + } + return ret; +} + +static const struct snd_soc_component_driver qaif_component_driver = { + .name = DRV_NAME, + .open = qaif_platform_pcmops_open, + .close = qaif_platform_pcmops_close, + .hw_params = qaif_platform_pcmops_hw_params, + .hw_free = qaif_platform_pcmops_hw_free, + .prepare = qaif_platform_pcmops_prepare, + .trigger = qaif_platform_pcmops_trigger, + .sync_stop = qaif_platform_pcmops_sync_stop, + .pointer = qaif_platform_pcmops_pointer, + .mmap = qaif_platform_pcmops_mmap, + .copy = qaif_platform_copy, +}; + +int asoc_qcom_qaif_platform_register(struct platform_device *pdev) +{ + struct qaif_drv_data *drvdata = platform_get_drvdata(pdev); + struct of_phandle_args iommu_spec; + u32 sid; + int ret; + + if (!drvdata || !drvdata->variant) + return dev_err_probe(&pdev->dev, -EINVAL, + "Invalid drvdata or variant\n"); + + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, + "iommus", 2, 0, + &iommu_spec); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to parse iommus property\n"); + sid = iommu_spec.args[0]; + of_node_put(iommu_spec.np); + drvdata->smmu_csid_bits = sid & QAIF_CSID_MASK; + + /* + * The DMA base register only holds the low 32 bits of the buffer + * address, so the coherent buffer must come from the low 32-bit + * address space. + */ + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to set 32-bit DMA mask\n"); + + drvdata->audio_qaif_irq = platform_get_irq(pdev, 0); + if (drvdata->audio_qaif_irq < 0) + return dev_err_probe(&pdev->dev, drvdata->audio_qaif_irq, + "Failed to get IRQ\n"); + + drvdata->qaif_hw_configured = false; + mutex_init(&drvdata->stream_lock); + + ret = devm_request_irq(&pdev->dev, drvdata->audio_qaif_irq, + asoc_platform_qaif_irq, 0, + "qaif-irq-audio-core", drvdata); + if (ret) + return dev_err_probe(&pdev->dev, ret, "irq request failed\n"); + + ret = devm_snd_soc_register_component(&pdev->dev, &qaif_component_driver, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register platform component\n"); + return 0; +} +EXPORT_SYMBOL_GPL(asoc_qcom_qaif_platform_register); + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) PCM platform driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qaif-reg.h b/sound/soc/qcom/qaif-reg.h new file mode 100644 index 0000000000000..fc968d584487f --- /dev/null +++ b/sound/soc/qcom/qaif-reg.h @@ -0,0 +1,458 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-reg.h -- QAIF ALSA SoC CPU-Platform DAI driver register header + */ +#ifndef SND_SOC_QCOM_QAIF_REG_H +#define SND_SOC_QCOM_QAIF_REG_H + +#include "qaif.h" + +#define QAIF_SUMMARY_IRQSTAT_REG(v) (0x19188 + (0x1000 * ((v)->ee))) + +/* Core HW info */ +#define QAIF_HW_VERSION_REG (0x0000) +#define QAIF_HW_INFO_REG (0x0004) +#define QAIF_HW_INFO2_REG (0x0008) + +/* Interface lane and channel info */ +#define QAIF_AUD_INTF_LANE_INFO_REG (0x0020) +#define QAIF_AUD_INTF_LANE_INFO2_REG (0x0024) +#define QAIF_CDC_TX_INTF_CH_INFO_REG(n) (0x0028 + (0x4 * (n))) +#define QAIF_CDC_RX_INTF_CH_INFO_REG(n) (0x0068 + (0x4 * (n))) +#define QAIF_QXM1_SHRAM_LENGTH_INFO_REG (0x0088) +#define QAIF_QXM0_SHRAM_LENGTH_INFO_REG (0x008C) +#define QAIF_NUM_AUD_INTF_TO_RAIL_INFO_REG (0x0090) + +/* Debug/control and status */ +#define QAIF_DEBUG_CTL_REG (0x0200) +#define QAIF_WRDMA_LOOPBACK_EN_REG (0x0204) +#define QAIF_WRDMA_LOOPBACK_SEL_REG (0x0208) +#define QAIF_SHRAM_DYNAMIC_CLK_GATING_EN_REG (0x0300) +#define QAIF_AXI_STATUS_REG (0x0304) +#define QAIF_QSB_DYNAMIC_CLK_GATING_EN_REG (0x0308) +#define QAIF_START_STOP_CTRL_BYPASS_EN_REG (0x030C) +#define QAIF_QXM0_AXI_ATTR_CFG_REG (0x040C) + +/* QXM request/grant debug */ +#define QAIF_QXM0_AUD_WR_REQ_GNT_DBG_STAT_REG (0x0500) +#define QAIF_QXM1_AUD_WR_REQ_GNT_DBG_STAT_REG (0x0504) +#define QAIF_QXM0_CODEC_RX_WR_REQ_DBG_STAT_REG (0x0508) +#define QAIF_QXM0_CODEC_RX_WR_GNT_DBG_STAT_REG (0x050C) +#define QAIF_QXM1_CODEC_RX_WR_REQ_DBG_STAT_REG (0x0510) +#define QAIF_QXM1_CODEC_RX_WR_GNT_DBG_STAT_REG (0x0514) +#define QAIF_QXM0_AUD_RD_REQ_GNT_DBG_STAT_REG (0x0518) +#define QAIF_QXM1_AUD_RD_REQ_GNT_DBG_STAT_REG (0x051C) +#define QAIF_QXM0_CODEC_TX_RD_REQ_DBG_STAT_REG (0x0520) +#define QAIF_QXM0_CODEC_TX_RD_GNT_DBG_STAT_REG (0x0524) +#define QAIF_QXM1_CODEC_TX_RD_REQ_DBG_STAT_REG (0x0528) +#define QAIF_QXM1_CODEC_TX_RD_GNT_DBG_STAT_REG (0x052C) +#define QAIF_QXM0_EXT_RDDMA_RD_REQ_GNT_DBG_STAT_REG (0x0530) +#define QAIF_QXM1_EXT_RDDMA_RD_REQ_GNT_DBG_STAT_REG (0x0534) + +/* QSB transaction debug */ +#define QAIF_QSB_AUD_WR_TXN_DBG_STAT_REG (0x0538) +#define QAIF_QSB_CODEC_RX_WR_TXN_ERR_DBG_STAT_REG (0x053C) +#define QAIF_QSB_CODEC_RX_WR_TXN_OKAY_DBG_STAT_REG (0x0540) +#define QAIF_QSB_AUD_ADDR_SENT_DBG_STAT_REG (0x0544) +#define QAIF_QSB_CODEC_TX_RD_ADDR_SENT_DBG_STAT_REG (0x0548) +#define QAIF_QSB_EXT_RDDMA_RD_ADDR_SENT_DBG_STAT_REG (0x054C) +#define QAIF_QSB_CODEC_RX_WR_ADDR_SENT_DBG_STAT_REG (0x0550) +#define QAIF_QSB_AUD_RD_TXN_DBG_STAT_REG (0x0554) +#define QAIF_QSB_CODEC_TX_RD_TXN_ERR_DBG_STAT_REG (0x0558) +#define QAIF_QSB_CODEC_TX_RD_TXN_RCVD_DBG_STAT_REG (0x055C) +#define QAIF_QSB_EXT_RDDMA_RD_TXN_DBG_STAT_REG (0x0560) +#define QAIF_QSB_MISC_DBG_STATUS_REG (0x0564) + +/* Global spare and HWE */ +#define QAIF_GLOBAL_SPARE_IN_REG (0x0B00) +#define QAIF_GLOBAL_SPARE_OUT_REG (0x0B04) +#define QAIF_HWE_CFG_REG (0x0B08) + +/* SID maps */ +#define QAIF_WRDMA_SID_MAP_REG (0x1B00) +#define QAIF_CDC_WRDMA_SID_MAP_REG (0x1B40) +#define QAIF_RDDMA_SID_MAP_REG (0x1C00) +#define QAIF_CDC_RDDMA_SID_MAP_REG (0x1C40) + +/* EE overlap interrupts */ +#define QAIF_EE_OVERLAP_IRQ_EN_REG (0x1D00) +#define QAIF_EE_OVERLAP_IRQ_RAW_STATUS_REG (0x1D04) +#define QAIF_EE_OVERLAP_IRQ_CLEAR_REG (0x1D08) +#define QAIF_EE_OVERLAP_IRQ_FORCE_REG (0x1D0C) + +/* + * EE (Execution Engine) assignment and map registers. + * + * The EE offset (v->ee) is an address/index offset relative to the + * platform-defined QAIF/EE base and lets the hardware route the AIF + * operation to the correct EE context. It is hardware/platform-specific and + * must match the mapping defined for the selected QAIF AIF/lane in the + * hardware programming guide. It is not a runtime data offset and must not + * be changed based on the stream configuration. + */ +#define QAIF_EE_RDDMA_ASSIGNMENT_REG(v) (0x19148 + (0x1000 * ((v)->ee))) +#define QAIF_EE_WRDMA_ASSIGNMENT_REG(v) (0x19150 + (0x1000 * ((v)->ee))) +#define QAIF_EE_INTF_ASSIGNMENT_REG(v) (0x19158 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_RDDMA_ASSIGN_REG(v) (0x19308 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_WRDMA_ASSIGN_REG(v) (0x19318 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RDDMA_MAP_REG(v) (0x1920 + (0x1000 * ((v)->ee))) +#define QAIF_EE_WRDMA_MAP_REG(v) (0x1940 + (0x1000 * ((v)->ee))) +#define QAIF_EE_INTF_MAP_REG(v) (0x1960 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_RDDMA_MAP_REG(v) (0x1980 + (0x1000 * ((v)->ee))) +#define QAIF_EE_CODEC_WRDMA_MAP_REG(v) (0x1A00 + (0x1000 * ((v)->ee))) + +/* EE rate-detection and VFR interrupts */ +#define QAIF_EE_RATE_DET_IRQ_EN_REG(v) (0x190F0 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_STAT_REG(v) (0x190F4 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_RAW_STAT_REG(v) (0x190F8 + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_CLEAR_REG(v) (0x190FC + (0x1000 * ((v)->ee))) +#define QAIF_EE_RATE_DET_IRQ_FORCE_REG(v) (0x19100 + (0x1000 * ((v)->ee))) + +#define QAIF_EE_VFR_IRQ_EN_REG(v) (0x19104 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_STATUS_REG(v) (0x19108 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_RAW_STATUS_REG(v) (0x1910C + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_CLEAR_REG(v) (0x19110 + (0x1000 * ((v)->ee))) +#define QAIF_EE_VFR_IRQ_FORCE_REG(v) (0x19114 + (0x1000 * ((v)->ee))) + +/* EE AUD_INTF underflow/overflow interrupts */ +#define QAIF_EE_AUD_INTF_UF_IRQ_EN_REG(v) (0x19160 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_STAT_REG(v) (0x19164 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_RAW_STAT_REG(v) (0x19168 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_CLEAR_REG(v) (0x1916C + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_UF_IRQ_FORCE_REG(v) (0x19170 + (0x1000 * ((v)->ee))) + +#define QAIF_EE_AUD_INTF_OF_IRQ_EN_REG(v) (0x19174 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_STAT_REG(v) (0x19178 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_RAW_STAT_REG(v) (0x1917C + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_CLEAR_REG(v) (0x19180 + (0x1000 * ((v)->ee))) +#define QAIF_EE_AUD_INTF_OF_IRQ_FORCE_REG(v) (0x19184 + (0x1000 * ((v)->ee))) + +/* EE L2 Period IRQ mux selection */ +#define QAIF_EE_L2_PERIOD_IRQ_0_3_MUX_SEL_REG(v) (0x19F00 + (0x1000 * ((v)->ee))) +#define QAIF_EE_L2_PERIOD_IRQ_4_7_MUX_SEL_REG(v) (0x19F04 + (0x1000 * ((v)->ee))) + +/* AUD_INTF block (per interface, stride 0x1000 starting at 0x4000) */ +#define QAIF_AUD_INTF_REG_ADDR(offset, i) (0x4000 + (offset) + (0x1000 * (i))) + +#define QAIF_AUD_INTF_CTL_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0000, (i)) +#define QAIF_AUD_INTF_SYNC_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0004, (i)) +#define QAIF_AUD_INTF_BIT_WIDTH_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0008, (i)) +#define QAIF_AUD_INTF_FRAME_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x000C, (i)) +#define QAIF_AUD_INTF_ACTV_SLOT_EN_TX_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0010, (i)) +#define QAIF_AUD_INTF_ACTV_SLOT_EN_RX_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0030, (i)) +#define QAIF_AUD_INTF_LANE_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0050, (i)) +#define QAIF_AUD_INTF_MI2S_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0054, (i)) +#define QAIF_AUD_INTF_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0058, (i)) +#define QAIF_AUD_INTF_CHAR_CTL_REG(i) QAIF_AUD_INTF_REG_ADDR(0x005C, (i)) +#define QAIF_AUD_INTF_CHAR_CFG_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0060, (i)) +#define QAIF_AUD_INTF_CHAR_DATA_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0064, (i)) +#define QAIF_AUD_INTF_CHAR_DATA_EXT_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0068, (i)) +#define QAIF_AUD_INTF_CHAR_SYNC_REG(i) QAIF_AUD_INTF_REG_ADDR(0x006C, (i)) +#define QAIF_AUD_INTF_INIT_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF0, (i)) +#define QAIF_AUD_INTF_TX_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF4, (i)) +#define QAIF_AUD_INTF_RX_DBG_STATUS_REG(i) QAIF_AUD_INTF_REG_ADDR(0x0FF8, (i)) + +/* RATE_DET block (per detector, stride 0x1000 starting at 0x1E000) */ +#define QAIF_RATE_DET_REG_ADDR(offset, det) (0x1E000 + (offset) + (0x1000 * (det))) + +#define QAIF_RATE_DET_CONFIG_REG(det) QAIF_RATE_DET_REG_ADDR(0x0000, (det)) +#define QAIF_RATE_DET_TARGET1_CONF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0004, (det)) +#define QAIF_RATE_DET_TARGET2_CONF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0008, (det)) +#define QAIF_RATE_DET_BIN_REG(det) QAIF_RATE_DET_REG_ADDR(0x000C, (det)) +#define QAIF_RATE_DET_STC_DIFF_REG(det) QAIF_RATE_DET_REG_ADDR(0x0010, (det)) +#define QAIF_RATE_DET_SEL_REG(det) QAIF_RATE_DET_REG_ADDR(0x0014, (det)) +#define QAIF_RATE_DET_TIMEOUT_CFG_REG(det) QAIF_RATE_DET_REG_ADDR(0x0018, (det)) + +#define QAIF_WRDMA_MAP_QXM (0x1000) +#define QAIF_CDC_WRDMA_MAP_QXM (0x1004) +#define QAIF_RDDMA_MAP_QXM (0x1010) +#define QAIF_CDC_RDDMA_MAP_QXM (0x1014) +#define QAIF_RDDMA_QXM1_SHRAM_ST_ADDR(i) (0x1100 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM1_SHRAM_ST_ADDR(i) (0x1140 + (0x4 * (i))) +#define QAIF_RDDMA_QXM0_SHRAM_ST_ADDR(i) (0x1200 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM0_SHRAM_ST_ADDR(i) (0x1240 + (0x4 * (i))) +#define QAIF_RDDMA_QXM1_SHRAM_LEN(i) (0x1300 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM1_SHRAM_LEN(i) (0x1340 + (0x4 * (i))) +#define QAIF_RDDMA_QXM0_SHRAM_LEN(i) (0x1400 + (0x4 * (i))) +#define QAIF_CDC_RDDMA_QXM0_SHRAM_LEN(i) (0x1440 + (0x4 * (i))) +#define QAIF_WRDMA_QXM1_SHRAM_ST_ADDR(i) (0x1500 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM1_SHRAM_ST_ADDR(i) (0x1540 + (0x4 * (i))) +#define QAIF_WRDMA_QXM0_SHRAM_ST_ADDR(i) (0x1600 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM0_SHRAM_ST_ADDR(i) (0x1640 + (0x4 * (i))) +#define QAIF_WRDMA_QXM1_SHRAM_LEN(i) (0x1700 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM1_SHRAM_LEN(i) (0x1740 + (0x4 * (i))) +#define QAIF_WRDMA_QXM0_SHRAM_LEN(i) (0x1800 + (0x4 * (i))) +#define QAIF_CDC_WRDMA_QXM0_SHRAM_LEN(i) (0x1840 + (0x4 * (i))) + +/* RDDMA */ +static inline u32 qaif_rddma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->rddma_reg_base + offset + v->rddma_stride * i; +} + +#define QAIF_RDDMA_CTL_REG(v, i) qaif_rddma_reg_addr(v, 0x00, (i)) +#define QAIF_RDDMA_CFG_REG(v, i) qaif_rddma_reg_addr(v, 0x04, (i)) +#define QAIF_RDDMA_BASE_ADDR_REG(v, i) qaif_rddma_reg_addr(v, 0x08, (i)) +#define QAIF_RDDMA_BUFF_LEN_REG(v, i) qaif_rddma_reg_addr(v, 0x10, (i)) +#define QAIF_RDDMA_CURR_ADDR_REG(v, i) qaif_rddma_reg_addr(v, 0x14, (i)) +#define QAIF_RDDMA_PERIOD_LEN_REG(v, i) qaif_rddma_reg_addr(v, 0x1C, (i)) +#define QAIF_RDDMA_PERIOD_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x20, (i)) +#define QAIF_RDDMA_SHRAM_WORDCNT_REG(v, i) qaif_rddma_reg_addr(v, 0x24, (i)) +#define QAIF_RDDMA_FRAME_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0x28, (i)) +#define QAIF_RDDMA_FRAME_STATUS_EXTN_REG(v, i) qaif_rddma_reg_addr(v, 0x2C, (i)) +#define QAIF_RDDMA_FRAME_STATUS_CLR_REG(v, i) qaif_rddma_reg_addr(v, 0x30, (i)) +#define QAIF_RDDMA_SET_BUFF_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x34, (i)) +#define QAIF_RDDMA_SET_PERIOD_CNT_REG(v, i) qaif_rddma_reg_addr(v, 0x38, (i)) +#define QAIF_RDDMA_STC_LSB_REG(v, i) qaif_rddma_reg_addr(v, 0x3C, (i)) +#define QAIF_RDDMA_STC_MSB_REG(v, i) qaif_rddma_reg_addr(v, 0x40, (i)) +#define QAIF_RDDMA_PERIOD_DET_STAT_REG(v, i) qaif_rddma_reg_addr(v, 0x44, (i)) +#define QAIF_RDDMA_PERIOD_DET_CLR_REG(v, i) qaif_rddma_reg_addr(v, 0x48, (i)) +#define QAIF_RDDMA_FORMAT_ERR_REG(v, i) qaif_rddma_reg_addr(v, 0x4C, (i)) +#define QAIF_RDDMA_AHB_BYPASS_REG(v, i) qaif_rddma_reg_addr(v, 0x50, (i)) +#define QAIF_RDDMA_SHUTDOWN_STAT_REG(v, i) qaif_rddma_reg_addr(v, 0x54, (i)) +#define QAIF_RDDMA_PADDING_CFG_REG(v, i) qaif_rddma_reg_addr(v, 0x58, (i)) +#define QAIF_RDDMA_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0x60, (i)) +#define QAIF_RDDMA_DBG_STATUS_REG(v, i) qaif_rddma_reg_addr(v, 0xFF0, (i)) + +static inline u32 qaif_codec_rddma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->codec_rddma_reg_base + offset + v->codec_rddma_stride * i; +} + +#define QAIF_CDC_RDDMA_CTL_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x00, (i)) +#define QAIF_CDC_RDDMA_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x04, (i)) +#define QAIF_CDC_RDDMA_BASE_ADDR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x08, (i)) +#define QAIF_CDC_RDDMA_BUFF_LEN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x10, (i)) +#define QAIF_CDC_RDDMA_CURR_ADDR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x14, (i)) +#define QAIF_CDC_RDDMA_PERIOD_LEN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x1C, (i)) +#define QAIF_CDC_RDDMA_PERIOD_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x20, (i)) +#define QAIF_CDC_RDDMA_SHRAM_WORDCNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x24, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x28, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_EXTN_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x2C, (i)) +#define QAIF_CDC_RDDMA_FRAME_STAT_CLR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x30, (i)) +#define QAIF_CDC_RDDMA_SET_BUFF_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x34, (i)) +#define QAIF_CDC_RDDMA_SET_PERIOD_CNT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x38, (i)) +#define QAIF_CDC_RDDMA_STC_LSB_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x3C, (i)) +#define QAIF_CDC_RDDMA_STC_MSB_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x40, (i)) +#define QAIF_CDC_RDDMA_PERIOD_DET_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x44, (i)) +#define QAIF_CDC_RDDMA_PERIOD_DET_CLR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x48, (i)) +#define QAIF_CDC_RDDMA_FORMAT_ERR_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x4C, (i)) +#define QAIF_CDC_RDDMA_AHB_BYPASS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x50, (i)) +#define QAIF_CDC_RDDMA_SHUTDOWN_STAT_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x54, (i)) +#define QAIF_CDC_RDDMA_PADDING_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x58, (i)) +#define QAIF_CDC_RDDMA_INTF_CFG_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x5C, (i)) +#define QAIF_CDC_RDDMA_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0x60, (i)) +#define QAIF_CDC_RDDMA_DBG_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0xFF0, (i)) +#define QAIF_CDC_RDDMA_INTF_DBG_STATUS_REG(v, i) qaif_codec_rddma_reg_addr(v, 0xFF4, (i)) + +/* WRDMA */ +static inline u32 qaif_wrdma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->wrdma_reg_base + offset + v->wrdma_stride * i; +} + +#define QAIF_WRDMA_CTL_REG(v, i) qaif_wrdma_reg_addr(v, 0x00, (i)) +#define QAIF_WRDMA_CFG_REG(v, i) qaif_wrdma_reg_addr(v, 0x04, (i)) +#define QAIF_WRDMA_BASE_ADDR_REG(v, i) qaif_wrdma_reg_addr(v, 0x08, (i)) +#define QAIF_WRDMA_BUFF_LEN_REG(v, i) qaif_wrdma_reg_addr(v, 0x10, (i)) +#define QAIF_WRDMA_CURR_ADDR_REG(v, i) qaif_wrdma_reg_addr(v, 0x14, (i)) +#define QAIF_WRDMA_PERIOD_LEN_REG(v, i) qaif_wrdma_reg_addr(v, 0x1C, (i)) +#define QAIF_WRDMA_PERIOD_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x20, (i)) +#define QAIF_WRDMA_SHRAM_WORDCNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x24, (i)) +#define QAIF_WRDMA_FRAME_STATUS_REG(v, i) qaif_wrdma_reg_addr(v, 0x28, (i)) +#define QAIF_WRDMA_FRAME_STATUS_EXTN_REG(v, i) qaif_wrdma_reg_addr(v, 0x2C, (i)) +#define QAIF_WRDMA_FRAME_STATUS_CLR_REG(v, i) qaif_wrdma_reg_addr(v, 0x30, (i)) +#define QAIF_WRDMA_SET_BUFF_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x34, (i)) +#define QAIF_WRDMA_SET_PERIOD_CNT_REG(v, i) qaif_wrdma_reg_addr(v, 0x38, (i)) +#define QAIF_WRDMA_STC_LSB_REG(v, i) qaif_wrdma_reg_addr(v, 0x3C, (i)) +#define QAIF_WRDMA_STC_MSB_REG(v, i) qaif_wrdma_reg_addr(v, 0x40, (i)) +#define QAIF_WRDMA_PERIOD_DET_STAT_REG(v, i) qaif_wrdma_reg_addr(v, 0x44, (i)) +#define QAIF_WRDMA_PERIOD_DET_CLR_REG(v, i) qaif_wrdma_reg_addr(v, 0x48, (i)) +#define QAIF_WRDMA_FORMAT_ERR_REG(v, i) qaif_wrdma_reg_addr(v, 0x4C, (i)) +#define QAIF_WRDMA_AHB_BYPASS_REG(v, i) qaif_wrdma_reg_addr(v, 0x50, (i)) +#define QAIF_WRDMA_SHUTDOWN_STAT_REG(v, i) qaif_wrdma_reg_addr(v, 0x54, (i)) +#define QAIF_WRDMA_DBG_STATUS_REG(v, i) qaif_wrdma_reg_addr(v, 0xFF0, (i)) + +static inline u32 qaif_codec_wrdma_reg_addr(const struct qaif_variant *v, + u32 offset, u32 i) +{ + return v->codec_wrdma_reg_base + offset + v->codec_wrdma_stride * i; +} + +#define QAIF_CDC_WRDMA_CTL_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x00, (i)) +#define QAIF_CDC_WRDMA_CFG_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x04, (i)) +#define QAIF_CDC_WRDMA_BASE_ADDR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x08, (i)) +#define QAIF_CDC_WRDMA_BUFF_LEN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x10, (i)) +#define QAIF_CDC_WRDMA_CURR_ADDR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x14, (i)) +#define QAIF_CDC_WRDMA_PERIOD_LEN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x1C, (i)) +#define QAIF_CDC_WRDMA_PERIOD_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x20, (i)) +#define QAIF_CDC_WRDMA_SHRAM_WORDCNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x24, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x28, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_EXTN_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x2C, (i)) +#define QAIF_CDC_WRDMA_FRAME_STAT_CLR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x30, (i)) +#define QAIF_CDC_WRDMA_SET_BUFF_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x34, (i)) +#define QAIF_CDC_WRDMA_SET_PERIOD_CNT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x38, (i)) +#define QAIF_CDC_WRDMA_STC_LSB_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x3C, (i)) +#define QAIF_CDC_WRDMA_STC_MSB_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x40, (i)) +#define QAIF_CDC_WRDMA_PERIOD_DET_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x44, (i)) +#define QAIF_CDC_WRDMA_PERIOD_DET_CLR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x48, (i)) +#define QAIF_CDC_WRDMA_FORMAT_ERR_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x4C, (i)) +#define QAIF_CDC_WRDMA_AHB_BYPASS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x50, (i)) +#define QAIF_CDC_WRDMA_SHUTDOWN_STAT_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x54, (i)) +#define QAIF_CDC_WRDMA_INTF_CFG_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0x58, (i)) +#define QAIF_CDC_WRDMA_DBG_STATUS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0xFF0, (i)) +#define QAIF_CDC_WRDMA_INTF_DBG_STATUS_REG(v, i) qaif_codec_wrdma_reg_addr(v, 0xFF4, (i)) + +static inline u32 qaif_rddma_irq_reg_addr(const struct qaif_variant *v, + enum qaif_irq_type dma_type, + u32 offset) +{ + if (dma_type == QAIF_AIF_IRQ) + return v->rddma_irq_reg_base + offset + + v->rddma_irq_stride * v->ee; + return v->codec_rddma_irq_reg_base + offset + + v->codec_rddma_irq_stride * v->ee; +} + +/* RDDMA Period Interrupts */ +#define QAIF_EE_RDDMA_PERIOD_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x00) +#define QAIF_EE_RDDMA_PERIOD_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x08) +#define QAIF_EE_RDDMA_PERIOD_IRQ_RAW_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x10) +#define QAIF_EE_RDDMA_PERIOD_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x18) +#define QAIF_EE_RDDMA_PERIOD_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x20) +/* RDDMA Underflow Interrupts */ +#define QAIF_EE_RDDMA_UF_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x28) +#define QAIF_EE_RDDMA_UF_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x30) +#define QAIF_EE_RDDMA_UF_IRQ_RAW_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x38) +#define QAIF_EE_RDDMA_UF_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x40) +#define QAIF_EE_RDDMA_UF_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x48) +/* RDDMA Error Response Interrupts */ +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_EN_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x50) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_STAT_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x58) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_RAW_STAT(v, i) qaif_rddma_irq_reg_addr(v, i, 0x60) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_CLR_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x68) +#define QAIF_EE_RDDMA_ERR_RSP_IRQ_FORCE_REG(v, i) qaif_rddma_irq_reg_addr(v, i, 0x70) + +static inline u32 qaif_wrdma_irq_reg_addr(const struct qaif_variant *v, + enum qaif_irq_type dma_type, + u32 offset) +{ + if (dma_type == QAIF_AIF_IRQ) + return v->wrdma_irq_reg_base + offset + + v->wrdma_irq_stride * v->ee; + return v->codec_wrdma_irq_reg_base + offset + + v->codec_wrdma_irq_stride * v->ee; +} + +/* WRDMA Period Interrupts */ +#define QAIF_EE_WRDMA_PERIOD_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x00) +#define QAIF_EE_WRDMA_PERIOD_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x08) +#define QAIF_EE_WRDMA_PERIOD_IRQ_RAW_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x10) +#define QAIF_EE_WRDMA_PERIOD_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x18) +#define QAIF_EE_WRDMA_PERIOD_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x20) +/* WRDMA Overflow Interrupts */ +#define QAIF_EE_WRDMA_OF_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x28) +#define QAIF_EE_WRDMA_OF_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x30) +#define QAIF_EE_WRDMA_OF_IRQ_RAW_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x38) +#define QAIF_EE_WRDMA_OF_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x40) +#define QAIF_EE_WRDMA_OF_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x48) +/* WRDMA Error Response Interrupts */ +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_EN_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x50) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_STAT_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x58) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_RAW_STAT(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x60) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_CLR_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x68) +#define QAIF_EE_WRDMA_ERR_RSP_IRQ_FORCE_REG(v, i) qaif_wrdma_irq_reg_addr(v, i, 0x70) + +static inline u32 qaif_dmacfg_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_dma_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CFG_REG(v, i) : + QAIF_CDC_WRDMA_CFG_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CFG_REG(v, i) : QAIF_WRDMA_CFG_REG(v, i); +} + +static inline u32 qaif_dmactl_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_dma_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CTL_REG(v, i) : + QAIF_CDC_WRDMA_CTL_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CTL_REG(v, i) : QAIF_WRDMA_CTL_REG(v, i); +} + +static inline u32 qaif_dmabuff_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_dma_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_BUFF_LEN_REG(v, i) : + QAIF_CDC_WRDMA_BUFF_LEN_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_BUFF_LEN_REG(v, i) : QAIF_WRDMA_BUFF_LEN_REG(v, i); +} + +static inline u32 qaif_dmacurr_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_dma_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_CURR_ADDR_REG(v, i) : + QAIF_CDC_WRDMA_CURR_ADDR_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_CURR_ADDR_REG(v, i) : QAIF_WRDMA_CURR_ADDR_REG(v, i); +} + +static inline u32 qaif_dmaper_cnt_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_dma_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_PERIOD_CNT_REG(v, i) : + QAIF_CDC_WRDMA_PERIOD_CNT_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_PERIOD_CNT_REG(v, i) : QAIF_WRDMA_PERIOD_CNT_REG(v, i); +} + +static inline u32 qaif_dmaper_len_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_dma_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_PERIOD_LEN_REG(v, i) : + QAIF_CDC_WRDMA_PERIOD_LEN_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_PERIOD_LEN_REG(v, i) : QAIF_WRDMA_PERIOD_LEN_REG(v, i); +} + +static inline u32 qaif_dmabase_reg(const struct qaif_variant *v, + u32 i, int dir, unsigned int dai_id) +{ + if (qaif_is_cif_dma_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_BASE_ADDR_REG(v, i) : + QAIF_CDC_WRDMA_BASE_ADDR_REG(v, i); + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_BASE_ADDR_REG(v, i) : QAIF_WRDMA_BASE_ADDR_REG(v, i); +} + +static inline u32 qaif_sid_map_reg(int dir, unsigned int dai_id) +{ + if (qaif_is_cif_dma_port(dai_id)) + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_CDC_RDDMA_SID_MAP_REG : + QAIF_CDC_WRDMA_SID_MAP_REG; + return dir == SNDRV_PCM_STREAM_PLAYBACK ? + QAIF_RDDMA_SID_MAP_REG : QAIF_WRDMA_SID_MAP_REG; +} + +#endif /* SND_SOC_QCOM_QAIF_REG_H */ diff --git a/sound/soc/qcom/qaif-shikra.c b/sound/soc/qcom/qaif-shikra.c new file mode 100644 index 0000000000000..484ffaa5b3042 --- /dev/null +++ b/sound/soc/qcom/qaif-shikra.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * qaif-shikra.c -- ALSA SoC CPU-Platform DAI driver for QTi QAIF + */ + +#include +#include +#include +#include +#include "qaif.h" + +static const struct qaif_dmaidx_dai_map shikra_mi2s_dma_dai_map[] = { + { QAIF_MI2S_AIF0, QAIF_DMA_IDX0 }, + { QAIF_MI2S_AIF1, QAIF_DMA_IDX1 }, + { QAIF_MI2S_AIF2, QAIF_DMA_IDX2 }, + { QAIF_MI2S_AIF3, QAIF_DMA_IDX3 }, +}; + +static const struct qaif_dmaidx_dai_map shikra_tdm_dma_dai_map[] = { + { QAIF_TDM_AIF0, QAIF_DMA_IDX0 }, + { QAIF_TDM_AIF1, QAIF_DMA_IDX1 }, + { QAIF_TDM_AIF2, QAIF_DMA_IDX2 }, + { QAIF_TDM_AIF3, QAIF_DMA_IDX3 }, +}; + +static const struct qaif_dmaidx_dai_map shikra_cif_rx_dma_dai_map[] = { + { QAIF_CDC_DMA_RX0, QAIF_DMA_IDX0 }, + { QAIF_CDC_DMA_RX1, QAIF_DMA_IDX1 }, +}; + +static const struct qaif_dmaidx_dai_map shikra_cif_va_dma_dai_map[] = { + { QAIF_CDC_DMA_VA_TX0, QAIF_DMA_IDX0 }, + { QAIF_CDC_DMA_VA_TX1, QAIF_DMA_IDX1 }, +}; + +static struct snd_soc_dai_driver shikra_qaif_cpu_dai_driver[] = { + { + .id = QAIF_MI2S_AIF0, + .name = "MI2S AIF Zero", + .playback = { + .stream_name = "MI2S AIF Zero Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .stream_name = "MI2S AIF Zero Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = QAIF_MI2S_AIF1, + .name = "MI2S AIF One", + .playback = { + .stream_name = "MI2S AIF One Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .stream_name = "MI2S AIF One Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = QAIF_MI2S_AIF2, + .name = "MI2S AIF Two", + .playback = { + .stream_name = "MI2S AIF Two Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .stream_name = "MI2S AIF Two Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = QAIF_MI2S_AIF3, + .name = "MI2S AIF Three", + .playback = { + .stream_name = "MI2S AIF Three Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .capture = { + .stream_name = "MI2S AIF Three Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = QAIF_TDM_AIF0, + .name = "TDM AIF Zero", + .playback = { + .stream_name = "TDM AIF Zero Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .stream_name = "TDM AIF Zero Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = QAIF_TDM_AIF1, + .name = "TDM AIF One", + .playback = { + .stream_name = "TDM AIF One Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .stream_name = "TDM AIF One Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = QAIF_TDM_AIF2, + .name = "TDM AIF Two", + .playback = { + .stream_name = "TDM AIF Two Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .stream_name = "TDM AIF Two Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = QAIF_TDM_AIF3, + .name = "TDM AIF Three", + .playback = { + .stream_name = "TDM AIF Three Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .stream_name = "TDM AIF Three Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .channels_min = 1, + .channels_max = 8, + }, + .ops = &asoc_qcom_qaif_aif_cpu_dai_ops, + }, { + .id = QAIF_CDC_DMA_RX0, + .name = "CDC DMA RX0", + .playback = { + .stream_name = "WCD Playback0", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = QAIF_CDC_DMA_RX1, + .name = "CDC DMA RX1", + .playback = { + .stream_name = "WCD Playback1", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = QAIF_CDC_DMA_VA_TX0, + .name = "CDC DMA VA_TX0", + .capture = { + .stream_name = "DMIC Capture0", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, { + .id = QAIF_CDC_DMA_VA_TX1, + .name = "CDC DMA VA_TX1", + .capture = { + .stream_name = "DMIC Capture1", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + }, + .ops = &asoc_qcom_qaif_cif_dai_ops, + }, +}; + +static int shikra_qaif_get_dma_idx(unsigned int dai_id) +{ + const struct qaif_dmaidx_dai_map *map; + int i, size; + + switch (dai_id) { + case QAIF_MI2S_AIF0 ... QAIF_MI2S_AIF12: + map = shikra_mi2s_dma_dai_map; + size = ARRAY_SIZE(shikra_mi2s_dma_dai_map); + break; + case QAIF_TDM_AIF0 ... QAIF_TDM_AIF12: + map = shikra_tdm_dma_dai_map; + size = ARRAY_SIZE(shikra_tdm_dma_dai_map); + break; + case QAIF_CDC_DMA_RX0 ... QAIF_CDC_DMA_RX9: + map = shikra_cif_rx_dma_dai_map; + size = ARRAY_SIZE(shikra_cif_rx_dma_dai_map); + break; + case QAIF_CDC_DMA_VA_TX0 ... QAIF_CDC_DMA_VA_TX9: + map = shikra_cif_va_dma_dai_map; + size = ARRAY_SIZE(shikra_cif_va_dma_dai_map); + break; + default: + return -EINVAL; + } + + for (i = 0; i < size; i++) { + if (map[i].dai_id == dai_id) + return map[i].dma_idx; + } + return -EINVAL; +} + +static int shikra_qaif_alloc_stream_dma_idx(struct qaif_drv_data *drvdata, + int direction, + unsigned int dai_id) +{ + const struct qaif_variant *v = drvdata->variant; + int dma_idx, index; + + dma_idx = shikra_qaif_get_dma_idx(dai_id); + if (dma_idx < 0) + return dma_idx; + + switch (dai_id) { + case QAIF_MI2S_AIF0 ... QAIF_MI2S_AIF12: + case QAIF_TDM_AIF0 ... QAIF_TDM_AIF12: + index = (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + dma_idx : v->wrdma_start + dma_idx; + if (index >= QAIF_MAX_AIF_DMA_IDX) + return -EINVAL; + if (test_and_set_bit(index, &drvdata->aif_dma_idx_bit_map)) + return -EBUSY; + break; + case QAIF_CDC_DMA_RX0 ... QAIF_CDC_DMA_RX9: + case QAIF_CDC_DMA_VA_TX0 ... QAIF_CDC_DMA_VA_TX9: + index = (direction == SNDRV_PCM_STREAM_PLAYBACK) ? + dma_idx : v->codec_wrdma_start + dma_idx; + if (index >= QAIF_MAX_CIF_DMA_IDX) + return -EINVAL; + if (test_and_set_bit(index, &drvdata->cif_dma_idx_bit_map)) + return -EBUSY; + break; + default: + return -EINVAL; + } + + return index; +} + +static int shikra_qaif_free_stream_dma_idx(struct qaif_drv_data *drvdata, + int index, + unsigned int dai_id) +{ + if (qaif_is_aif_port(dai_id)) + clear_bit(index, &drvdata->aif_dma_idx_bit_map); + else + clear_bit(index, &drvdata->cif_dma_idx_bit_map); + return 0; +} + +static const struct qaif_variant shikra_qaif_data = { + .ee = 0, + + .num_rddma = 4, + .num_wrdma = 4, + .wrdma_start = 4, + + .num_codec_rddma = 4, + .num_codec_wrdma = 4, + .codec_wrdma_start = 4, + .num_intf = 4, + + .rddma_reg_base = 0x8000, + .rddma_stride = 0x1000, + .codec_rddma_reg_base = 0xc000, + .codec_rddma_stride = 0x1000, + + .wrdma_reg_base = 0x11000, + .wrdma_stride = 0x1000, + .codec_wrdma_reg_base = 0x15000, + .codec_wrdma_stride = 0x1000, + + .rddma_irq_reg_base = 0x19000, + .rddma_irq_stride = 0x1000, + .codec_rddma_irq_reg_base = 0x191A0, + .codec_rddma_irq_stride = 0x1000, + + .wrdma_irq_reg_base = 0x19078, + .wrdma_irq_stride = 0x1000, + .codec_wrdma_irq_reg_base = 0x19290, + .codec_wrdma_irq_stride = 0x1000, + + .qxm_type = QAIF_QXM0, + .rd_len = 512, + .rddma_shram_len = 64, + .rddma_shram_start_addr = {0, 256}, + .wr_len = 512, + .wrdma_shram_len = 64, + .wrdma_shram_start_addr = {0, 256}, + + .clk_name = (const char * const []) { + "lpass_config", + "lpass_core_axim", + "bus", + }, + .num_clks = 3, + + .dai_driver = shikra_qaif_cpu_dai_driver, + .num_dai = ARRAY_SIZE(shikra_qaif_cpu_dai_driver), + + .dai_bit_clk_names = (const char * const []) { + "aif_if0_ibit", + "aif_if1_ibit", + "aif_if2_ibit", + "aif_if3_ibit", + }, + .aif_full_cycle_en = (const bool []) { false, false, false, false }, + .aif_ctrl_data_oe = (const bool []) { false, false, false, false }, + .aif_loopback_en = (const bool []) { false, false, false, false }, + .alloc_stream_dma_idx = shikra_qaif_alloc_stream_dma_idx, + .free_stream_dma_idx = shikra_qaif_free_stream_dma_idx, + .get_dma_idx = shikra_qaif_get_dma_idx, +}; + +static const struct of_device_id shikra_qaif_cpu_device_id[] = { + { .compatible = "qcom,shikra-qaif-cpu", .data = &shikra_qaif_data }, + {} +}; +MODULE_DEVICE_TABLE(of, shikra_qaif_cpu_device_id); + +static struct platform_driver shikra_qaif_cpu_platform_driver = { + .driver = { + .name = "shikra-qaif-cpu", + .of_match_table = shikra_qaif_cpu_device_id, + .pm = pm_ptr(&asoc_qcom_qaif_pm_ops), + }, + .probe = asoc_qcom_qaif_cpu_platform_probe, +}; +module_platform_driver(shikra_qaif_cpu_platform_driver); + +MODULE_DESCRIPTION("Qualcomm Audio Interface (QAIF) Shikra variant driver"); +MODULE_AUTHOR("Harendra Gautam "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/qcom/qaif.h b/sound/soc/qcom/qaif.h new file mode 100644 index 0000000000000..c8d170684d052 --- /dev/null +++ b/sound/soc/qcom/qaif.h @@ -0,0 +1,302 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef SND_SOC_QCOM_QAIF_H +#define SND_SOC_QCOM_QAIF_H + +#include +#include +#include +#include +#include +#include +#include +#include + +struct snd_pcm_substream; +struct snd_soc_dai_driver; +struct snd_soc_dai_ops; +struct platform_device; + +#define QAIF_CIF_DMA_INTF_ONE_CHANNEL 0x01 +#define QAIF_CIF_DMA_INTF_TWO_CHANNEL 0x03 +#define QAIF_CIF_DMA_INTF_FOUR_CHANNEL 0x0f +#define QAIF_CIF_DMA_INTF_SIX_CHANNEL 0x3f +#define QAIF_CIF_DMA_INTF_EIGHT_CHANNEL 0xff + +#define QAIF_AIF_SLOT_WIDTH(bits) ((bits) - 1) +#define QAIF_AIF_SAMPLE_WIDTH(bits) ((bits) - 1) + +/* Watermark level 5 encoded as N-1: hardware field value = watermark - 1 */ +#define QAIF_DMACTL_WM_5 4 + +#define QAIF_DMA_BYTES_TO_WORDS_SHIFT 3 +#define QAIF_DMA_CLOCK_FREQ 38400000 + +#define QAIF_MAX_AIF_CFG_CNT 4 +#define QAIF_MAX_LANES 8 + +#define QAIF_MAX_MI2S_PORTS 8 +#define QAIF_MI2S_SLOTS 2 +#define QAIF_MAX_AIF_DMA_IDX 8 +#define QAIF_MAX_CIF_DMA_IDX 8 + +/* SID map register CSID field (WRDMA_SID/RDDMA_SID), bits [4:0] */ +#define QAIF_CSID_MASK GENMASK(4, 0) + +static inline bool qaif_is_cif_dma_port(int dai_id) +{ + if (dai_id >= QAIF_CDC_DMA_RX0 && dai_id <= QAIF_CDC_DMA_VA_TX9) + return true; + return false; +} + +static inline bool qaif_is_mi2s_port(int dai_id) +{ + return dai_id >= QAIF_MI2S_AIF0 && dai_id <= QAIF_MI2S_AIF12; +} + +static inline bool qaif_is_tdm_port(int dai_id) +{ + return dai_id >= QAIF_TDM_AIF0 && dai_id <= QAIF_TDM_AIF12; +} + +static inline bool qaif_is_aif_port(int dai_id) +{ + return qaif_is_mi2s_port(dai_id) || qaif_is_tdm_port(dai_id); +} + +enum qxm_sel { + QAIF_QXM0 = 0, + QAIF_QXM1 = 1, +}; + +enum qaif_irq_type { + QAIF_AIF_IRQ = 0, + QAIF_CIF_IRQ = 1, + QAIF_IRQ_TYPE_MAX, +}; + +enum qaif_dma_type { + QAIF_AIF_DMA = 0, + QAIF_CIF_DMA = 1, +}; + +struct qaif_aif_config { + u32 sync_mode; + u32 sync_src; + bool invert_sync; + u32 sync_delay; + u32 slot_width; + u32 slot_en_rx_mask; + u32 slot_en_tx_mask; + bool loopback_en; + bool ctrl_data_oe; + u32 lane_en_mask; + u32 lane_dir_mask; + bool full_cycle_en; + u32 slot_num; +}; + +struct qaif_pcm_data { + /* Flat slot in aif_substream[]/aif_dma_heap[] (or cif_*) used to track + * this stream. Direction-encoded: playback uses the raw DMA channel + * number (0..num_rddma-1), capture adds wrdma_start so playback and + * capture of the same DAI occupy distinct slots. + * Set by alloc_stream_dma_idx(); bounded by QAIF_MAX_AIF/CIF_DMA_IDX. + */ + int stream_dma_idx; + /* Hardware channel number (0-based within its DMA bank) passed to + * register address helpers (e.g. QAIF_RDDMA_CFG_REG(v, chan)). + * Direction-agnostic: the register helpers select RDDMA or WRDMA bank + * based on stream direction, so both directions of the same DAI share + * the same value. Set by get_dma_idx(). + */ + int dma_reg_idx; +}; + +/* Named 0-based DMA channel indices used as the dma_idx of a + * qaif_dmaidx_dai_map entry, so board tables reference channels by name. + */ +enum qaif_dma_idx { + QAIF_DMA_IDX0 = 0, + QAIF_DMA_IDX1, + QAIF_DMA_IDX2, + QAIF_DMA_IDX3, + QAIF_DMA_IDX4, + QAIF_DMA_IDX5, + QAIF_DMA_IDX6, + QAIF_DMA_IDX7, +}; + +/* Associates a DAI ID with its physical DMA channel index on a given + * board/variant. @dai_id is the DAI to match; @dma_idx (a qaif_dma_idx value) + * is the 0-based hardware channel that get_dma_idx() returns for it. + */ +struct qaif_dmaidx_dai_map { + unsigned int dai_id; + int dma_idx; +}; + +struct qaif_dma_mem_info { + dma_addr_t dma_addr; + size_t alloc_size; + void *vaddr; +}; + +struct qaif_drv_data { + struct clk *mi2s_bit_clk[QAIF_MAX_MI2S_PORTS]; + struct clk_bulk_data *clks; + int num_clks; + struct clk *aud_dma_clk; + struct clk *aud_dma_mem_clk; + void __iomem *audio_qaif; + struct regmap *audio_qaif_map; + int audio_qaif_irq; + struct mutex stream_lock; /* serializes stream open/close */ + bool qaif_hw_configured; + const struct qaif_variant *variant; + struct qaif_aif_config aif_intf_cfg[QAIF_MAX_AIF_CFG_CNT]; + unsigned long aif_dma_idx_bit_map; + unsigned long cif_dma_idx_bit_map; + struct snd_pcm_substream *aif_substream[QAIF_MAX_AIF_DMA_IDX]; + struct snd_pcm_substream *cif_substream[QAIF_MAX_CIF_DMA_IDX]; + u32 smmu_csid_bits; /* SMMU stream ID bits from DT iommus cell */ + struct qaif_dma_mem_info *aif_dma_heap[QAIF_MAX_AIF_DMA_IDX]; + struct qaif_dma_mem_info *cif_dma_heap[QAIF_MAX_CIF_DMA_IDX]; +}; + +enum qaif_summary_irq_bitmask { + QAIF_SUMMARY_BITMASK_AIF_PERIOD_RDDMA = BIT(0), + QAIF_SUMMARY_BITMASK_AIF_UNDERFLOW_RDDMA = BIT(1), + QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_RDDMA = BIT(2), + QAIF_SUMMARY_BITMASK_AIF_PERIOD_WRDMA = BIT(3), + QAIF_SUMMARY_BITMASK_AIF_OVERFLOW_WRDMA = BIT(4), + QAIF_SUMMARY_BITMASK_AIF_ERR_RSP_WRDMA = BIT(5), + QAIF_SUMMARY_BITMASK_CIF_PERIOD_RDDMA = BIT(18), + QAIF_SUMMARY_BITMASK_CIF_UNDERFLOW_RDDMA = BIT(19), + QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_RDDMA = BIT(20), + QAIF_SUMMARY_BITMASK_CIF_PERIOD_WRDMA = BIT(24), + QAIF_SUMMARY_BITMASK_CIF_OVERFLOW_WRDMA = BIT(25), + QAIF_SUMMARY_BITMASK_CIF_ERR_RSP_WRDMA = BIT(26), +}; + +enum qaif_client_status_register_bitmask_info { + QAIF_BITMASK_AIF_RDDMA_WRDMA = GENMASK(5, 0), + QAIF_BITMASK_CIF_RDDMA_WRDMA = GENMASK(26, 18), +}; + +struct qaif_irq_map { + u32 client_id; + u32 mask; + /* + * Called from the hard IRQ handler (asoc_platform_qaif_irq()) with + * @status holding the summary interrupt status. Must not sleep. + */ + irqreturn_t (*client_irq_handler)(struct qaif_drv_data *drvdata, u32 status); +}; + +enum qaif_dma_dir { + QAIF_DMA_RDDMA = 0, + QAIF_DMA_WRDMA = 1, +}; + +enum qaif_irq { + QAIF_IRQ_PERIOD = 0, + QAIF_IRQ_OVERFLOW, + QAIF_IRQ_UNDERFLOW, + QAIF_IRQ_ERROR, +}; + +enum qaif_irq_op { + QAIF_IRQ_CLEAR = 0, + QAIF_IRQ_ENABLE, + QAIF_IRQ_DISABLE, +}; + +enum qaif_client_info { + QAIF_CLIENT_ID_AIF_DMA = 0, + QAIF_CLIENT_ID_CIF_DMA, +}; + +struct qaif_variant { + u32 ee; /* Hardware EE index assigned to this driver instance */ + + u32 num_rddma; + u32 num_wrdma; + u32 wrdma_start; + u32 num_codec_rddma; + u32 num_codec_wrdma; + u32 codec_wrdma_start; + u32 num_intf; + + u32 rddma_reg_base; + u32 rddma_stride; + u32 codec_rddma_reg_base; + u32 codec_rddma_stride; + u32 wrdma_reg_base; + u32 wrdma_stride; + u32 codec_wrdma_reg_base; + u32 codec_wrdma_stride; + + u32 rddma_irq_reg_base; + u32 rddma_irq_stride; + u32 codec_rddma_irq_reg_base; + u32 codec_rddma_irq_stride; + u32 wrdma_irq_reg_base; + u32 wrdma_irq_stride; + u32 codec_wrdma_irq_reg_base; + u32 codec_wrdma_irq_stride; + + /* QSB master port selector; only QAIF_QXM0 is supported today */ + enum qxm_sel qxm_type; + /* All *_len values below are SHRAM sizes in 64-bit words */ + u32 rd_len; + u32 rddma_shram_len; + /* SHRAM start offsets (in 64-bit words), indexed by enum qaif_dma_type */ + u32 rddma_shram_start_addr[2]; + u32 wr_len; + u32 wrdma_shram_len; + u32 wrdma_shram_start_addr[2]; + + const char * const *clk_name; + int num_clks; + struct snd_soc_dai_driver *dai_driver; + int num_dai; + const char * const *dai_bit_clk_names; + /* + * Optional per-AIF configuration tables, indexed by AIF interface + * index (0..num_intf-1). NULL if the variant does not use them. + */ + const bool *aif_full_cycle_en; + const bool *aif_ctrl_data_oe; + const bool *aif_loopback_en; + + /* Returns a direction-encoded flat slot index for use in + * aif_substream[]/cif_substream[] and the dma_idx_bit_map. + * Playback returns the raw channel number; capture adds wrdma_start (or + * codec_wrdma_start) so both directions of the same DAI get distinct + * slots. Sets the corresponding bit atomically; returns -EBUSY if + * already open, -EINVAL if @dai_id or the computed index is out of range. + */ + int (*alloc_stream_dma_idx)(struct qaif_drv_data *data, int direction, + unsigned int dai_id); + /* Clears the bit set by alloc_stream_dma_idx. */ + int (*free_stream_dma_idx)(struct qaif_drv_data *data, int chan, + unsigned int dai_id); + /* Returns the hardware channel number (0-based within its DMA bank) + * for @dai_id, direction-agnostic. Used as dma_reg_idx. + */ + int (*get_dma_idx)(unsigned int dai_id); +}; + +extern const struct snd_soc_dai_ops asoc_qcom_qaif_cif_dai_ops; +extern const struct snd_soc_dai_ops asoc_qcom_qaif_aif_cpu_dai_ops; + +int asoc_qcom_qaif_cpu_platform_probe(struct platform_device *pdev); +int asoc_qcom_qaif_platform_register(struct platform_device *pdev); +extern const struct dev_pm_ops asoc_qcom_qaif_pm_ops; + +#endif /* SND_SOC_QCOM_QAIF_H */ diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index e6e9eb2e85aa1..0350afb5995a0 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -152,6 +152,13 @@ struct apm_i2s_module_intf_cfg { #define APM_I2S_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_i2s_module_intf_cfg), 8) +struct apm_audio_if_module_intf_cfg { + struct apm_module_param_data param_data; + struct param_id_audio_if_intf_cfg cfg; +} __packed; + +#define APM_AUDIO_IF_INTF_CFG_PSIZE ALIGN(sizeof(struct apm_audio_if_module_intf_cfg), 8) + struct apm_module_hw_ep_mf_cfg { struct apm_module_param_data param_data; struct param_id_hw_ep_mf mf; @@ -168,6 +175,13 @@ struct apm_module_frame_size_factor_cfg { #define APM_FS_CFG_PSIZE ALIGN(sizeof(struct apm_module_frame_size_factor_cfg), 8) +struct apm_module_hw_ep_frame_duration_cfg { + struct apm_module_param_data param_data; + struct param_id_hw_ep_frame_duration frame_duration; +} __packed; + +#define APM_HW_EP_FRAME_DURATION_PSIZE ALIGN(sizeof(struct apm_module_hw_ep_frame_duration_cfg), 8) + struct apm_module_hw_ep_power_mode_cfg { struct apm_module_param_data param_data; struct param_id_hw_ep_power_mode_cfg power_mode; @@ -579,10 +593,10 @@ EXPORT_SYMBOL_GPL(audioreach_alloc_graph_pkt); int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode) + struct gpr_pkt *pkt, uint32_t rsp_opcode) { - const struct gpr_hdr *hdr = &pkt->hdr; + struct gpr_hdr *hdr = &pkt->hdr; int rc; mutex_lock(cmd_lock); @@ -622,10 +636,12 @@ int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, } EXPORT_SYMBOL_GPL(audioreach_send_cmd_sync); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return audioreach_send_cmd_sync(graph->dev, NULL, &graph->result, &graph->lock, graph->port, &graph->cmd_wait, pkt, rsp_opcode); } @@ -970,6 +986,8 @@ int audioreach_compr_set_param(struct q6apm_graph *graph, if (rc) return rc; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(audioreach_compr_set_param); @@ -1042,6 +1060,81 @@ static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, return q6apm_send_cmd_sync(graph->apm, pkt, 0); } +static int audioreach_audio_if_set_media_format(struct q6apm_graph *graph, + const struct audioreach_module *module, + const struct audioreach_module_config *cfg) +{ + struct apm_module_hw_ep_frame_duration_cfg *fd_cfg; + struct apm_module_param_data *param_data; + struct apm_audio_if_module_intf_cfg *intf_cfg; + struct apm_module_hw_ep_mf_cfg *hw_cfg; + int ic_sz = APM_AUDIO_IF_INTF_CFG_PSIZE; + int ep_sz = APM_HW_EP_CFG_PSIZE; + int fd_sz = APM_HW_EP_FRAME_DURATION_PSIZE; + int size = ic_sz + ep_sz + fd_sz; + u32 slot_mask = cfg->slot_mask ? cfg->slot_mask : module->slot_mask; + u16 nslots_per_frame = cfg->nslots_per_frame ? + (u16)cfg->nslots_per_frame : module->nslots_per_frame; + u16 slot_width = cfg->slot_width ? (u16)cfg->slot_width : module->slot_width; + void *p; + + struct gpr_pkt *pkt __free(kfree) = audioreach_alloc_apm_cmd_pkt(size, APM_CMD_SET_CFG, 0); + if (IS_ERR(pkt)) + return PTR_ERR(pkt); + + p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + intf_cfg = p; + + param_data = &intf_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_AUDIO_IF_INTF_CFG; + param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; + intf_cfg->cfg.qaif_type = module->qaif_type; + intf_cfg->cfg.intf_idx = (u16)module->hw_interface_idx; + intf_cfg->cfg.intf_mode = module->intf_mode; + intf_cfg->cfg.ctrl_data_out_enable = module->ctrl_data_out_enable; + intf_cfg->cfg.active_slot_mask = slot_mask; + intf_cfg->cfg.nslots_per_frame = nslots_per_frame; + intf_cfg->cfg.slot_width = slot_width; + intf_cfg->cfg.active_lane_mask = module->active_lane_mask; + intf_cfg->cfg.frame_sync_rate = module->frame_sync_rate; + intf_cfg->cfg.frame_sync_src = module->sync_src; + intf_cfg->cfg.frame_sync_mode = module->sync_mode; + intf_cfg->cfg.invert_frame_sync_pulse = module->ctrl_invert_sync_pulse; + intf_cfg->cfg.frame_sync_data_delay = module->ctrl_sync_data_delay; + intf_cfg->cfg.bit_clk_type = module->bit_clk_type; + intf_cfg->cfg.inv_int_bit_clk = module->inv_int_bit_clk; + intf_cfg->cfg.inv_ext_bit_clk = module->inv_ext_bit_clk; + + p += ic_sz; + hw_cfg = p; + param_data = &hw_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_MF_CFG; + param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; + + hw_cfg->mf.sample_rate = cfg->sample_rate; + hw_cfg->mf.bit_width = cfg->bit_width; + hw_cfg->mf.num_channels = cfg->num_channels; + hw_cfg->mf.data_format = module->data_format; + + p += ep_sz; + fd_cfg = p; + param_data = &fd_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_FRAME_DURATION; + param_data->param_size = fd_sz - APM_MODULE_PARAM_DATA_SIZE; + fd_cfg->frame_duration.frame_duration_in_us = AUDIO_IF_FRAME_DURATION_US; + fd_cfg->frame_duration.allow_frame_duration_normalization = AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE; + fd_cfg->frame_duration.min_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MIN_US; + fd_cfg->frame_duration.max_normalized_frame_dur_us = AUDIO_IF_FRAME_DURATION_MAX_US; + + return q6apm_send_cmd_sync(graph->apm, pkt, 0); +} + static int audioreach_logging_set_media_format(struct q6apm_graph *graph, const struct audioreach_module *module) { @@ -1411,6 +1504,10 @@ int audioreach_set_media_format(struct q6apm_graph *graph, if (!rc) rc = audioreach_module_enable(graph, module, true); break; + case MODULE_ID_AUDIO_IF_SOURCE: + case MODULE_ID_AUDIO_IF_SINK: + rc = audioreach_audio_if_set_media_format(graph, module, cfg); + break; default: rc = 0; @@ -1489,6 +1586,8 @@ int audioreach_shared_memory_send_eos(struct q6apm_graph *graph) eos->policy = WR_SH_MEM_EP_EOS_POLICY_LAST; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(audioreach_shared_memory_send_eos); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 62a2fd79bbcb9..587f14ce71d23 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -36,6 +36,8 @@ struct q6apm_graph; #define MODULE_ID_SPEAKER_PROTECTION 0x070010E2 #define MODULE_ID_SPEAKER_PROTECTION_VI 0x070010E3 #define MODULE_ID_OPUS_DEC 0x07001174 +#define MODULE_ID_AUDIO_IF_SINK 0x0700117C +#define MODULE_ID_AUDIO_IF_SOURCE 0x0700117D #define APM_CMD_GET_SPF_STATE 0x01001021 #define APM_CMD_RSP_GET_SPF_STATE 0x02001007 @@ -544,6 +546,74 @@ struct param_id_i2s_intf_cfg { #define PORT_ID_I2S_OUPUT 1 #define I2S_STACK_SIZE 2048 +#define PARAM_ID_AUDIO_IF_INTF_CFG 0x08001B11 + +/* + * struct param_id_audio_if_intf_cfg - Audio interface configuration + * @qaif_type: Audio interface type (e.g. QAIF, QAIF_VA) + * @intf_idx: Interface instance index + * @intf_mode: Interface operating mode (TDM/PCM/I2S) + * @ctrl_data_out_enable: Enable sharing of data-out signal with other masters + * @active_slot_mask: Bitmask indicating active slots + * @nslots_per_frame: Number of slots per audio frame + * @slot_width: Width of each slot in bits + * @active_lane_mask: Bitmask of active data lanes + * @frame_sync_rate: Frame sync rate in Hz + * @frame_sync_src: Frame sync source selection + * @frame_sync_mode: Frame sync mode configuration + * @invert_frame_sync_pulse: Invert frame sync polarity when set + * @frame_sync_data_delay: Data delay from frame sync in bit clocks + * @bit_clk_type: Bit clock type (internal / external) + * @inv_int_bit_clk: Invert internal bit clock when set + * @inv_ext_bit_clk: Invert external bit clock when set + * + * This structure defines configuration parameters for the Qualcomm + * Audio Interface (QAIF) block. It is used to program interface + * characteristics such as slot configuration, clocking and frame + * synchronization behaviour. + */ +struct param_id_audio_if_intf_cfg { + uint16_t qaif_type; + uint16_t intf_idx; + uint16_t intf_mode; + uint16_t ctrl_data_out_enable; + uint32_t active_slot_mask; + uint16_t nslots_per_frame; + uint16_t slot_width; + uint32_t active_lane_mask; + uint32_t frame_sync_rate; + uint16_t frame_sync_src; + uint16_t frame_sync_mode; + uint16_t invert_frame_sync_pulse; + uint16_t frame_sync_data_delay; + uint16_t bit_clk_type; + uint8_t inv_int_bit_clk; + uint8_t inv_ext_bit_clk; +} __packed; + +#define PARAM_ID_HW_EP_FRAME_DURATION 0x08001B2F +#define AUDIO_IF_FRAME_DURATION_US 1000 +#define AUDIO_IF_FRAME_DURATION_NORMALIZATION_ENABLE 1 +#define AUDIO_IF_FRAME_DURATION_MIN_US 1 +#define AUDIO_IF_FRAME_DURATION_MAX_US 100000 + +/** + * struct param_id_hw_ep_frame_duration - Hardware endpoint frame duration + * @frame_duration_in_us: Frame duration in microseconds. + * @allow_frame_duration_normalization: Permit SPF to normalize frame duration. + * @min_normalized_frame_dur_us: Minimum normalized frame duration in microseconds. + * @max_normalized_frame_dur_us: Maximum normalized frame duration in microseconds. + * + * This structure configures the frame duration for the Audio IF hardware + * endpoint and, when enabled, the allowed normalization range. + */ +struct param_id_hw_ep_frame_duration { + uint32_t frame_duration_in_us; + uint32_t allow_frame_duration_normalization; + uint32_t min_normalized_frame_dur_us; + uint32_t max_normalized_frame_dur_us; +} __packed; + #define PARAM_ID_DISPLAY_PORT_INTF_CFG 0x08001154 struct param_id_display_port_intf_cfg { @@ -877,6 +947,23 @@ struct audioreach_module { uint32_t data_format; uint32_t hw_interface_type; + /* Audio IF module (TDM/PCM/I2S) */ + u32 slot_mask; + u32 active_lane_mask; + u32 frame_sync_rate; + u16 qaif_type; + u16 sync_src; + u16 ctrl_data_out_enable; + u16 nslots_per_frame; + u16 slot_width; + u16 intf_mode; + u16 sync_mode; + u16 ctrl_invert_sync_pulse; + u16 ctrl_sync_data_delay; + u16 bit_clk_type; + u8 inv_int_bit_clk; + u8 inv_ext_bit_clk; + /* PCM module specific */ uint32_t interleave_type; @@ -907,19 +994,27 @@ struct audioreach_module_config { u32 channel_allocation; u32 sd_line_mask; int fmt; + u32 slot_mask; + u16 nslots_per_frame; + u16 slot_width; struct snd_codec codec; u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; }; /* Packet Allocation routines */ -void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, uint32_t - token); +static inline u16 audioreach_gpr_dest_domain(gpr_device_t *gdev) +{ + return gdev && gdev->domain_id ? gdev->domain_id : GPR_DOMAIN_ID_ADSP; +} + +void *audioreach_alloc_apm_cmd_pkt(int pkt_size, uint32_t opcode, + uint32_t token); void audioreach_set_default_channel_mapping(u8 *ch_map, int num_channels); void *audioreach_alloc_cmd_pkt(int payload_size, uint32_t opcode, uint32_t token, uint32_t src_port, uint32_t dest_port); void *audioreach_alloc_apm_pkt(int pkt_size, uint32_t opcode, uint32_t token, - uint32_t src_port); + uint32_t src_port); void *audioreach_alloc_pkt(int payload_size, uint32_t opcode, uint32_t token, uint32_t src_port, uint32_t dest_port); @@ -930,10 +1025,13 @@ int audioreach_tplg_init(struct snd_soc_component *component); /* Module specific */ void audioreach_graph_free_buf(struct q6apm_graph *graph); -int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, struct gpr_ibasic_rsp_result_t *result, - struct mutex *cmd_lock, gpr_port_t *port, wait_queue_head_t *cmd_wait, - const struct gpr_pkt *pkt, uint32_t rsp_opcode); -int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, const struct gpr_pkt *pkt, +int audioreach_send_cmd_sync(struct device *dev, gpr_device_t *gdev, + struct gpr_ibasic_rsp_result_t *result, + struct mutex *cmd_lock, gpr_port_t *port, + wait_queue_head_t *cmd_wait, + struct gpr_pkt *pkt, uint32_t rsp_opcode); +int audioreach_graph_send_cmd_sync(struct q6apm_graph *graph, + struct gpr_pkt *pkt, uint32_t rsp_opcode); int audioreach_set_media_format(struct q6apm_graph *graph, const struct audioreach_module *module, diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index bf1f872a09f45..02c8b832e1a0e 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -1,20 +1,22 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (c) 2021, Linaro Limited -#include +#include #include +#include +#include #include #include +#include #include #include -#include -#include #include -#include #include #include -#include +#include #include +#include +#include #include "q6apm.h" #define DRV_NAME "q6apm-dai" @@ -34,6 +36,9 @@ #define COMPR_PLAYBACK_MAX_NUM_FRAGMENTS (16 * 4) #define COMPR_PLAYBACK_MIN_FRAGMENT_SIZE (8 * 1024) #define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4) +#define Q6APM_MAX_VMIDS 2 +#define Q6APM_MAX_CARVEOUTS 2 +#define Q6APM_POOL_MAX_STREAMS 8 #define SID_MASK_DEFAULT 0xF static const struct snd_compr_codec_caps q6apm_compr_caps = { @@ -57,6 +62,13 @@ enum stream_state { Q6APM_STREAM_RUNNING, }; +struct q6apm_scm_region { + phys_addr_t addr; + size_t size; + u64 src_perms; + bool assigned; +}; + struct q6apm_dai_rtd { struct snd_pcm_substream *substream; struct snd_compr_stream *cstream; @@ -84,9 +96,123 @@ struct q6apm_dai_rtd { }; struct q6apm_dai_data { + struct device *dev; long long sid; + int num_vmids; + u32 vmids[Q6APM_MAX_VMIDS]; + bool use_scm_assign; + bool has_reserved_mem; + size_t reserved_buf_size; + struct q6apm_scm_region carveout_regions[Q6APM_MAX_CARVEOUTS]; + int num_carveouts; }; +static int q6apm_dai_scm_assign(struct q6apm_scm_region *region, + const struct q6apm_dai_data *pdata) +{ + struct qcom_scm_vmperm *dst; + int dst_count = 0; + int ret, i; + + if (region->assigned) + return 0; + + dst = kcalloc(pdata->num_vmids + 1, sizeof(*dst), GFP_KERNEL); + if (!dst) + return -ENOMEM; + + dst[dst_count].vmid = QCOM_SCM_VMID_HLOS; + dst[dst_count].perm = QCOM_SCM_PERM_RW; + dst_count++; + + for (i = 0; i < pdata->num_vmids; i++) { + if (WARN_ON_ONCE(pdata->vmids[i] == QCOM_SCM_VMID_HLOS)) + continue; + dst[dst_count].vmid = pdata->vmids[i]; + dst[dst_count].perm = QCOM_SCM_PERM_RW; + dst_count++; + } + + if (dst_count == 1) { + kfree(dst); + return 0; + } + + ret = qcom_scm_assign_mem(region->addr, region->size, + ®ion->src_perms, dst, dst_count); + kfree(dst); + if (!ret) + region->assigned = true; + + return ret; +} + +static void q6apm_dai_scm_unassign(struct q6apm_scm_region *region, + const struct q6apm_dai_data *pdata) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + int ret; + + if (!region->assigned) + return; + + ret = qcom_scm_assign_mem(region->addr, region->size, + ®ion->src_perms, &hlos, 1); + if (ret) { + /* + * A failed reclaim leaves the buffer owned by the DSP VMIDs + * instead of HLOS: it must not be reused. Warn loudly and keep + * it marked assigned so it is never handed back to the pool. + */ + dev_err(pdata->dev, + "SCM unassign failed for %pa (size %zu): %d\n", + ®ion->addr, region->size, ret); + return; + } + + region->assigned = false; + region->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS); +} + +static void q6apm_dai_unassign_all(void *data) +{ + struct q6apm_dai_data *pdata = data; + int i; + + for (i = 0; i < pdata->num_carveouts; i++) + q6apm_dai_scm_unassign(&pdata->carveout_regions[i], pdata); +} + +/* + * Reclaim and drop the runtime carveout tracking the buffer at @addr. + * Only per-PCM buffers (allocated in pcm_new when there is no reserved + * memory pool) are removed this way, so tearing one PCM down never + * disturbs regions still owned by other live PCMs. + */ +static void q6apm_dai_scm_unassign_addr(struct q6apm_dai_data *pdata, + phys_addr_t addr) +{ + int i; + + for (i = 0; i < pdata->num_carveouts; i++) { + if (pdata->carveout_regions[i].addr != addr) + continue; + + q6apm_dai_scm_unassign(&pdata->carveout_regions[i], pdata); + if (pdata->carveout_regions[i].assigned) + return; /* reclaim failed: keep tracking, warned already */ + + /* swap-remove the (now HLOS-owned) slot */ + pdata->num_carveouts--; + pdata->carveout_regions[i] = + pdata->carveout_regions[pdata->num_carveouts]; + return; + } +} + static const struct snd_pcm_hardware q6apm_dai_hardware_capture = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | @@ -216,7 +342,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, { struct snd_pcm_runtime *runtime = substream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; - struct audioreach_module_config cfg; + struct audioreach_module_config cfg = {}; struct device *dev = component->dev; struct q6apm_dai_data *pdata; int ret; @@ -409,8 +535,11 @@ static int q6apm_dai_open(struct snd_soc_component *component, } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + size_t buf_max = pdata->has_reserved_mem ? + pdata->reserved_buf_size : BUFFER_BYTES_MAX; + ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, - BUFFER_BYTES_MIN, BUFFER_BYTES_MAX); + BUFFER_BYTES_MIN, buf_max); if (ret < 0) { dev_err(dev, "constraint for buffer bytes min max ret = %d\n", ret); goto err; @@ -431,17 +560,18 @@ static int q6apm_dai_open(struct snd_soc_component *component, } runtime->private_data = prtd; - runtime->dma_bytes = BUFFER_BYTES_MAX; if (pdata->sid < 0) prtd->phys = substream->dma_buffer.addr; else prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); if (q6apm_is_graph_in_push_pull_mode(prtd->graph)) { + size_t buf_sz = pdata->has_reserved_mem ? + pdata->reserved_buf_size : BUFFER_BYTES_MAX; void *pos_buffer; - prtd->pos_phys = prtd->phys + BUFFER_BYTES_MAX; - pos_buffer = (void *)(substream->dma_buffer.area + BUFFER_BYTES_MAX); + prtd->pos_phys = prtd->phys + buf_sz; + pos_buffer = (void *)(substream->dma_buffer.area + buf_sz); prtd->pos_buffer = (struct sh_mem_pull_push_mode_position_buffer *)(pos_buffer); } @@ -529,6 +659,9 @@ static int q6apm_dai_hw_params(struct snd_soc_component *component, return 0; } +static void q6apm_dai_memory_unmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream); + static int q6apm_dai_memory_map(struct snd_soc_component *component, struct snd_pcm_substream *substream, int graph_id, bool is_push_pull) @@ -549,15 +682,21 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, else phys = substream->dma_buffer.addr | (pdata->sid << 32); - ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, BUFFER_BYTES_MAX); + ret = q6apm_map_memory_fixed_region(dev, graph_id, phys, + pdata->has_reserved_mem ? + pdata->reserved_buf_size : + BUFFER_BYTES_MAX); if (ret < 0) dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", ret); if (is_push_pull) { + size_t buf_sz = pdata->has_reserved_mem ? + pdata->reserved_buf_size : BUFFER_BYTES_MAX; + if (pdata->sid < 0) - phys = substream->dma_buffer.addr + BUFFER_BYTES_MAX; + phys = substream->dma_buffer.addr + buf_sz; else - phys = (substream->dma_buffer.addr + BUFFER_BYTES_MAX) | (pdata->sid << 32); + phys = (substream->dma_buffer.addr + buf_sz) | (pdata->sid << 32); ret = q6apm_map_pos_buffer(dev, graph_id, phys, POS_BUFFER_BYTES); if (ret < 0) @@ -572,6 +711,7 @@ static int q6apm_dai_memory_map(struct snd_soc_component *component, static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct q6apm_dai_data *pdata = snd_soc_component_get_drvdata(component); struct snd_pcm *pcm = rtd->pcm; /* * Allocate one extra page as a workaround for a DSP bug where 32-bit @@ -583,15 +723,17 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc bool is_push_pull; struct snd_pcm_substream *substream = NULL; + if (!pdata) + return -EINVAL; + graph_id = cpu_dai->driver->id; /* Note: DSP backend dais are uni-directional ONLY(either playback or capture) */ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; - else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) + else if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; - if (substream) { is_push_pull = q6apm_is_graph_in_push_pull_mode_from_id(component->dev, graph_id, @@ -599,13 +741,46 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc if (is_push_pull) size += POS_BUFFER_BYTES; - ret = snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, component->dev, size); + if (pdata->has_reserved_mem) + ret = snd_pcm_set_managed_buffer_all(pcm, + SNDRV_DMA_TYPE_DEV, component->dev, + pdata->reserved_buf_size, + pdata->reserved_buf_size); + else + ret = snd_pcm_set_fixed_buffer_all(pcm, + SNDRV_DMA_TYPE_DEV, component->dev, + size); if (ret) return ret; ret = q6apm_dai_memory_map(component, substream, graph_id, is_push_pull); if (ret) return ret; + + if (pdata->use_scm_assign && !pdata->has_reserved_mem) { + struct q6apm_scm_region *r; + + if (pdata->num_carveouts >= Q6APM_MAX_CARVEOUTS) { + dev_err(component->dev, + "too many SCM carveouts (max %d)\n", + Q6APM_MAX_CARVEOUTS); + q6apm_dai_memory_unmap(component, substream); + return -ENOSPC; + } + + r = &pdata->carveout_regions[pdata->num_carveouts]; + r->addr = substream->dma_buffer.addr; + r->size = ALIGN(size, PAGE_SIZE); + r->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS); + ret = q6apm_dai_scm_assign(r, pdata); + if (ret) { + dev_err(component->dev, + "SCM assign DMA buffer failed: %d\n", ret); + q6apm_dai_memory_unmap(component, substream); + return ret; + } + pdata->num_carveouts++; + } } return 0; @@ -635,15 +810,29 @@ static void q6apm_dai_memory_unmap(struct snd_soc_component *component, static void q6apm_dai_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm) { + struct q6apm_dai_data *pdata = snd_soc_component_get_drvdata(component); struct snd_pcm_substream *substream; + int i; - substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; - if (substream) - q6apm_dai_memory_unmap(component, substream); + if (!pdata) + return; + + /* + * Reverse of pcm_new: unmap the buffer from the DSP first, then + * reclaim only this PCM's carveout(s) back to HLOS. Regions owned + * by other live PCMs, and the reserved-memory pool, are untouched. + */ + for (i = 0; i < SNDRV_PCM_STREAM_LAST + 1; i++) { + substream = pcm->streams[i].substream; + if (!substream) + continue; - substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; - if (substream) q6apm_dai_memory_unmap(component, substream); + + if (pdata->use_scm_assign && !pdata->has_reserved_mem) + q6apm_dai_scm_unassign_addr(pdata, + substream->dma_buffer.addr); + } } static int q6apm_dai_compr_open(struct snd_soc_component *component, @@ -814,7 +1003,7 @@ static int q6apm_dai_compr_set_params(struct snd_soc_component *component, struct snd_compr_runtime *runtime = stream->runtime; struct q6apm_dai_rtd *prtd = runtime->private_data; struct q6apm_dai_data *pdata; - struct audioreach_module_config cfg; + struct audioreach_module_config cfg = {}; struct snd_codec *codec = ¶ms->codec; int dir = stream->direction; int ret; @@ -1017,23 +1206,161 @@ static const struct snd_soc_component_driver q6apm_fe_dai_component = { .remove_order = SND_SOC_COMP_ORDER_EARLY, }; +static void q6apm_dai_reserved_mem_release(void *data) +{ + of_reserved_mem_device_release(data); +} + static int q6apm_dai_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; struct q6apm_dai_data *pdata; struct of_phandle_args args; - int rc; + int rc, vmids, i; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; + pdata->dev = dev; + rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); - if (rc < 0) + if (rc < 0) { pdata->sid = -1; - else + } else { pdata->sid = args.args[0] & SID_MASK_DEFAULT; + of_node_put(args.np); + } + + vmids = of_property_count_u32_elems(node, "qcom,vmids"); + if (vmids == -EINVAL) { + /* no qcom,vmids: SCM assignment not used on this target */ + pdata->use_scm_assign = false; + } else if (vmids < 0) { + return vmids; + } else if (vmids == 0 || vmids > Q6APM_MAX_VMIDS) { + dev_err(dev, "qcom,vmids: invalid count %d (need 1..%d)\n", + vmids, Q6APM_MAX_VMIDS); + return -EINVAL; + } else if (pdata->sid >= 0) { + /* + * iommus (SMMU translation) and qcom,vmids (SCM VMID + * assignment) are mutually exclusive buffer-protection + * schemes; the binding forbids both, reject them here too. + */ + dev_err(dev, "qcom,vmids and iommus are mutually exclusive\n"); + return -EINVAL; + } + + if (vmids > 0) { + rc = of_property_read_u32_array(node, "qcom,vmids", + pdata->vmids, vmids); + if (rc) + return rc; + + for (i = 0; i < vmids; i++) { + if (pdata->vmids[i] == QCOM_SCM_VMID_HLOS) { + dev_err(dev, + "qcom,vmids must not include HLOS\n"); + return -EINVAL; + } + } + pdata->num_vmids = vmids; + pdata->use_scm_assign = true; + } + + if (pdata->use_scm_assign) { + struct device_node *mem_node; + struct reserved_mem *rmem; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + mem_node = of_parse_phandle(node, "memory-region", 0); + if (mem_node) { + rmem = of_reserved_mem_lookup(mem_node); + of_node_put(mem_node); + if (rmem) { + struct q6apm_scm_region *r; + + r = &pdata->carveout_regions[pdata->num_carveouts++]; + r->addr = rmem->base; + r->size = ALIGN(rmem->size, PAGE_SIZE); + r->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS); + } + } + + mem_node = of_parse_phandle(node, "memory-region", 1); + if (mem_node) { + rmem = of_reserved_mem_lookup(mem_node); + of_node_put(mem_node); + if (rmem) { + struct q6apm_scm_region *r; + size_t per_stream; + + /* + * The data-path carveout is a shared DMA pool + * from which all PCM substreams pre-allocate. + * Divide evenly so concurrent streams fit, + * subtracting per-stream position-buffer + * overhead. Reject a pool too small to give + * each stream a usable buffer (unsigned + * arithmetic would otherwise wrap). + */ + per_stream = rmem->size / Q6APM_POOL_MAX_STREAMS; + if (per_stream <= POS_BUFFER_BYTES) { + dev_err(dev, + "reserved-memory pool too small: %llu bytes\n", + (u64)rmem->size); + return -EINVAL; + } + + rc = of_reserved_mem_device_init_by_idx(dev, node, 1); + if (rc) { + dev_err(dev, + "reserved-memory pool init failed: %d\n", + rc); + return rc; + } + rc = devm_add_action_or_reset(dev, + q6apm_dai_reserved_mem_release, + dev); + if (rc) + return rc; + + /* never exceed the per-substream buffer cap */ + pdata->reserved_buf_size = + min_t(size_t, + per_stream - POS_BUFFER_BYTES, + BUFFER_BYTES_MAX); + pdata->has_reserved_mem = true; + + r = &pdata->carveout_regions[pdata->num_carveouts++]; + r->addr = rmem->base; + r->size = ALIGN(rmem->size, PAGE_SIZE); + r->src_perms = BIT_ULL(QCOM_SCM_VMID_HLOS); + } + } + + if (pdata->num_carveouts) { + for (i = 0; i < pdata->num_carveouts; i++) { + rc = q6apm_dai_scm_assign(&pdata->carveout_regions[i], + pdata); + if (rc) { + dev_err(dev, + "SCM assign carveout[%d] failed: %d\n", + i, rc); + return rc; + } + } + rc = devm_add_action_or_reset(dev, + q6apm_dai_unassign_all, + pdata); + if (rc) + return rc; + } + } dev_set_drvdata(dev, pdata); diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 006b283484d9e..e204fd59e5129 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -2,10 +2,12 @@ // Copyright (c) 2021, Linaro Limited #include +#include #include #include #include #include +#include #include #include #include @@ -15,15 +17,54 @@ #include "q6dsp-common.h" #include "audioreach.h" #include "q6apm.h" +#include "q6prm.h" #define AUDIOREACH_BE_PCM_BASE 16 +struct q6apm_dai_priv_data { + struct clk *mclk; + struct clk *bclk; + bool mclk_enabled, bclk_enabled; +}; + struct q6apm_lpass_dai_data { struct q6apm_graph *graph[APM_PORT_MAX]; bool is_port_started[APM_PORT_MAX]; struct audioreach_module_config module_config[APM_PORT_MAX]; + struct q6apm_dai_priv_data priv[APM_PORT_MAX]; }; +static void q6apm_lpass_dai_disable_clocks(struct q6apm_lpass_dai_data *dai_data, int id) +{ + if (dai_data->priv[id].mclk_enabled) { + clk_disable_unprepare(dai_data->priv[id].mclk); + dai_data->priv[id].mclk_enabled = false; + } + + if (dai_data->priv[id].bclk_enabled) { + clk_disable_unprepare(dai_data->priv[id].bclk); + dai_data->priv[id].bclk_enabled = false; + } +} + +static void q6apm_lpass_dai_put_clocks(struct q6apm_lpass_dai_data *dai_data) +{ + int i; + + for (i = 0; i < APM_PORT_MAX; i++) { + q6apm_lpass_dai_disable_clocks(dai_data, i); + + if (dai_data->priv[i].mclk) { + clk_put(dai_data->priv[i].mclk); + dai_data->priv[i].mclk = NULL; + } + if (dai_data->priv[i].bclk) { + clk_put(dai_data->priv[i].bclk); + dai_data->priv[i].bclk = NULL; + } + } +} + static int q6dma_set_channel_map(struct snd_soc_dai *dai, unsigned int tx_num, const unsigned int *tx_ch_mask, @@ -251,6 +292,62 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s return 0; } +static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + return q6apm_lpass_dai_startup(substream, dai); +} + +static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + + q6apm_lpass_dai_shutdown(substream, dai); + q6apm_lpass_dai_disable_clocks(dai_data, dai->id); +} + +static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct clk *sysclk = NULL; + bool *enabled = NULL; + int ret = 0; + + switch (clk_id) { + case LPAIF_MI2S_MCLK: + sysclk = dai_data->priv[dai->id].mclk; + enabled = &dai_data->priv[dai->id].mclk_enabled; + break; + case LPAIF_MI2S_BCLK: + sysclk = dai_data->priv[dai->id].bclk; + enabled = &dai_data->priv[dai->id].bclk_enabled; + break; + default: + return -EINVAL; + } + + if (sysclk) { + ret = clk_set_rate(sysclk, freq); + if (ret) { + dev_err(dai->dev, "Error, Unable to set rate (%d) for sysclk %d\n", + freq, clk_id); + return ret; + } + + if (*enabled) + return 0; + + ret = clk_prepare_enable(sysclk); + if (ret) { + dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id); + return ret; + } + + *enabled = true; + } + + return ret; +} + static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); @@ -261,6 +358,53 @@ static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } +static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; + unsigned int cap_mask, slot_mask; + + if (slot_width != 16 && slot_width != 32) { + dev_err(dai->dev, "%s: invalid slot_width %d\n", __func__, slot_width); + return -EINVAL; + } + + switch (slots) { + case 2: + case 4: + case 8: + case 16: + cap_mask = GENMASK(slots - 1, 0); + break; + default: + dev_err(dai->dev, "%s: invalid slots %d\n", __func__, slots); + return -EINVAL; + } + + switch (dai->id) { + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + slot_mask = (dai->id & 0x1) ? tx_mask : rx_mask; + if (slot_mask & ~cap_mask) { + dev_err(dai->dev, "%s: invalid slot mask 0x%x for %d slots\n", + __func__, slot_mask, slots); + return -EINVAL; + } + + cfg->nslots_per_frame = slots; + cfg->slot_width = slot_width; + cfg->slot_mask = slot_mask; + break; + default: + dev_err(dai->dev, "%s: invalid dai id 0x%x\n", __func__, dai->id); + return -EINVAL; + } + + return 0; +} + static const struct snd_soc_dai_ops q6dma_ops = { .prepare = q6apm_lpass_dai_prepare, .startup = q6apm_lpass_dai_startup, @@ -272,11 +416,12 @@ static const struct snd_soc_dai_ops q6dma_ops = { static const struct snd_soc_dai_ops q6i2s_ops = { .prepare = q6apm_lpass_dai_prepare, - .startup = q6apm_lpass_dai_startup, - .shutdown = q6apm_lpass_dai_shutdown, + .startup = q6i2s_dai_startup, + .shutdown = q6i2s_lpass_dai_shutdown, .set_channel_map = q6dma_set_channel_map, .hw_params = q6dma_hw_params, .set_fmt = q6i2s_set_fmt, + .set_sysclk = q6i2s_set_sysclk, .trigger = q6apm_lpass_dai_trigger, }; @@ -289,6 +434,17 @@ static const struct snd_soc_dai_ops q6hdmi_ops = { .trigger = q6apm_lpass_dai_trigger, }; +static const struct snd_soc_dai_ops q6tdm_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, + .shutdown = q6i2s_lpass_dai_shutdown, + .set_tdm_slot = q6tdm_set_tdm_slot, + .hw_params = q6dma_hw_params, + .set_fmt = q6i2s_set_fmt, + .set_sysclk = q6i2s_set_sysclk, + .trigger = q6apm_lpass_dai_trigger, +}; + static const struct snd_soc_component_driver q6apm_lpass_dai_component = { .name = "q6apm-be-dai-component", .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, @@ -297,6 +453,65 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = { .remove_order = SND_SOC_COMP_ORDER_FIRST, }; +static int of_q6apm_parse_dai_data(struct device *dev, + struct q6apm_lpass_dai_data *data) +{ + int ret; + + for_each_child_of_node_scoped(dev->of_node, node) { + struct q6apm_dai_priv_data *priv; + int id; + + ret = of_property_read_u32(node, "reg", &id); + if (ret || id < 0 || id >= APM_PORT_MAX) { + dev_err(dev, "valid dai id not found:%d\n", ret); + continue; + } + + switch (id) { + /* MI2S specific properties */ + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + priv = &data->priv[id]; + priv->mclk = of_clk_get_by_name(node, "mclk"); + if (IS_ERR(priv->mclk)) { + int err = PTR_ERR(priv->mclk); + + priv->mclk = NULL; + if (err == -EPROBE_DEFER) { + q6apm_lpass_dai_put_clocks(data); + return dev_err_probe(dev, err, + "unable to get mi2s mclk\n"); + } + } + + priv->bclk = of_clk_get_by_name(node, "bclk"); + if (IS_ERR(priv->bclk)) { + int err = PTR_ERR(priv->bclk); + + priv->bclk = NULL; + if (err == -EPROBE_DEFER) { + q6apm_lpass_dai_put_clocks(data); + return dev_err_probe(dev, err, + "unable to get mi2s bclk\n"); + } + } + break; + default: + break; + } + } + + return 0; +} + +static void q6apm_lpass_dai_clocks_action(void *data) +{ + q6apm_lpass_dai_put_clocks(data); +} + static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) { struct q6dsp_audio_port_dai_driver_config cfg; @@ -304,17 +519,26 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) struct snd_soc_dai_driver *dais; struct device *dev = &pdev->dev; int num_dais; + int ret; dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); if (!dai_data) return -ENOMEM; dev_set_drvdata(dev, dai_data); + ret = of_q6apm_parse_dai_data(dev, dai_data); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, q6apm_lpass_dai_clocks_action, dai_data); + if (ret) + return ret; memset(&cfg, 0, sizeof(cfg)); cfg.q6i2s_ops = &q6i2s_ops; cfg.q6dma_ops = &q6dma_ops; cfg.q6hdmi_ops = &q6hdmi_ops; + cfg.q6tdm_ops = &q6tdm_ops; dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index 641d6d2432299..e5563014fa5be 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -29,11 +29,13 @@ struct apm_graph_mgmt_cmd { static struct q6apm *g_apm; -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { gpr_device_t *gdev = apm->gdev; + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(gdev); + return audioreach_send_cmd_sync(&gdev->dev, gdev, &apm->result, &apm->lock, NULL, &apm->wait, pkt, rsp_opcode); } @@ -502,6 +504,8 @@ int q6apm_write_async(struct q6apm_graph *graph, uint32_t len, uint32_t msw_ts, mutex_unlock(&graph->lock); + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(q6apm_write_async); @@ -536,6 +540,8 @@ int q6apm_read(struct q6apm_graph *graph) mutex_unlock(&graph->lock); + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(graph->apm->gdev); + return gpr_send_port_pkt(graph->port, pkt); } EXPORT_SYMBOL_GPL(q6apm_read); diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 5cb51ca491dc4..9092359ccf909 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -147,7 +147,7 @@ int q6apm_alloc_fragments(struct q6apm_graph *graph, int q6apm_free_fragments(struct q6apm_graph *graph, unsigned int dir); int q6apm_unmap_memory_fixed_region(struct device *dev, unsigned int graph_id); /* Helpers */ -int q6apm_send_cmd_sync(struct q6apm *apm, const struct gpr_pkt *pkt, +int q6apm_send_cmd_sync(struct q6apm *apm, struct gpr_pkt *pkt, uint32_t rsp_opcode); /* Callback for graph specific */ diff --git a/sound/soc/qcom/qdsp6/q6prm-clocks.c b/sound/soc/qcom/qdsp6/q6prm-clocks.c index 4c574b48ab004..a938163e5c1cf 100644 --- a/sound/soc/qcom/qdsp6/q6prm-clocks.c +++ b/sound/soc/qcom/qdsp6/q6prm-clocks.c @@ -42,6 +42,11 @@ static const struct q6dsp_clk_init q6prm_clks[] = { Q6PRM_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT), Q6PRM_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT), Q6PRM_CLK(LPASS_CLK_ID_QUI_MI2S_OSR), + Q6PRM_CLK(LPASS_CLK_ID_MCLK_1), + Q6PRM_CLK(LPASS_CLK_ID_MCLK_2), + Q6PRM_CLK(LPASS_CLK_ID_MCLK_3), + Q6PRM_CLK(LPASS_CLK_ID_MCLK_4), + Q6PRM_CLK(LPASS_CLK_ID_MCLK_5), Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_MCLK), Q6PRM_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK), Q6PRM_CLK(LPASS_CLK_ID_VA_CORE_MCLK), @@ -59,6 +64,34 @@ static const struct q6dsp_clk_init q6prm_clks[] = { Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_MCLK), Q6PRM_CLK(LPASS_CLK_ID_WSA2_CORE_TX_2X_MCLK), Q6PRM_CLK(LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF0_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF1_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF2_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF3_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF4_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF5_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF6_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF7_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF8_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF9_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF10_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF11_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_QAIF_IF12_EBIT), + Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_IBIT), + Q6PRM_CLK(LPASS_CLK_ID_VA_QAIF_IF0_EBIT), Q6DSP_VOTE_CLK(LPASS_HW_MACRO_VOTE, Q6PRM_HW_CORE_ID_LPASS, "LPASS_HW_MACRO"), Q6DSP_VOTE_CLK(LPASS_HW_DCODEC_VOTE, Q6PRM_HW_CORE_ID_DCODEC, diff --git a/sound/soc/qcom/qdsp6/q6prm.c b/sound/soc/qcom/qdsp6/q6prm.c index 04892fb4423fe..f93383078eb18 100644 --- a/sound/soc/qcom/qdsp6/q6prm.c +++ b/sound/soc/qcom/qdsp6/q6prm.c @@ -51,6 +51,8 @@ struct prm_cmd_release_rsc { static int q6prm_send_cmd_sync(struct q6prm *prm, struct gpr_pkt *pkt, uint32_t rsp_opcode) { + pkt->hdr.dest_domain = audioreach_gpr_dest_domain(prm->gdev); + return audioreach_send_cmd_sync(prm->dev, prm->gdev, &prm->result, &prm->lock, NULL, &prm->wait, pkt, rsp_opcode); } diff --git a/sound/soc/qcom/qdsp6/q6prm.h b/sound/soc/qcom/qdsp6/q6prm.h index a988a32086fe1..b42c207125e62 100644 --- a/sound/soc/qcom/qdsp6/q6prm.h +++ b/sound/soc/qcom/qdsp6/q6prm.h @@ -3,6 +3,9 @@ #ifndef __Q6PRM_H__ #define __Q6PRM_H__ +#define LPAIF_MI2S_MCLK 1 +#define LPAIF_MI2S_BCLK 2 + /* Clock ID for Primary I2S IBIT */ #define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 /* Clock ID for Primary I2S EBIT */ @@ -52,6 +55,17 @@ /* Clock ID for QUINARY MI2S OSR CLK */ #define Q6PRM_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 +/* Clock ID for MCLK1 */ +#define Q6PRM_LPASS_CLK_ID_MCLK_1 0x300 +/* Clock ID for MCLK2 */ +#define Q6PRM_LPASS_CLK_ID_MCLK_2 0x301 +/* Clock ID for MCLK3 */ +#define Q6PRM_LPASS_CLK_ID_MCLK_3 0x302 +/* Clock ID for MCLK4 */ +#define Q6PRM_LPASS_CLK_ID_MCLK_4 0x303 +/* Clock ID for MCLK5 */ +#define Q6PRM_LPASS_CLK_ID_MCLK_5 0x304 + #define Q6PRM_LPASS_CLK_ID_WSA_CORE_MCLK 0x305 #define Q6PRM_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x306 @@ -83,6 +97,35 @@ /* Clock ID for RX CORE MCLK2 2X MCLK */ #define Q6PRM_LPASS_CLK_ID_RX_CORE_MCLK2_2X_MCLK 0x318 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_IBIT 0x500 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF0_EBIT 0x501 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_IBIT 0x502 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF1_EBIT 0x503 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_IBIT 0x504 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF2_EBIT 0x505 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_IBIT 0x506 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF3_EBIT 0x507 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_IBIT 0x508 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF4_EBIT 0x509 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_IBIT 0x50A +#define Q6PRM_LPASS_CLK_ID_QAIF_IF5_EBIT 0x50B +#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_IBIT 0x50C +#define Q6PRM_LPASS_CLK_ID_QAIF_IF6_EBIT 0x50D +#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_IBIT 0x50E +#define Q6PRM_LPASS_CLK_ID_QAIF_IF7_EBIT 0x50F +#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_IBIT 0x510 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF8_EBIT 0x511 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_IBIT 0x512 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF9_EBIT 0x513 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_IBIT 0x514 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF10_EBIT 0x515 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_IBIT 0x516 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF11_EBIT 0x517 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_IBIT 0x518 +#define Q6PRM_LPASS_CLK_ID_QAIF_IF12_EBIT 0x519 +#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_IBIT 0x550 +#define Q6PRM_LPASS_CLK_ID_VA_QAIF_IF0_EBIT 0x551 + #define Q6PRM_LPASS_CLK_SRC_INTERNAL 1 #define Q6PRM_LPASS_CLK_ROOT_DEFAULT 0 #define Q6PRM_HW_CORE_ID_LPASS 1 diff --git a/sound/soc/qcom/qdsp6/topology.c b/sound/soc/qcom/qdsp6/topology.c index 1f69fba6de26d..54661bcb006c9 100644 --- a/sound/soc/qcom/qdsp6/topology.c +++ b/sound/soc/qcom/qdsp6/topology.c @@ -753,6 +753,103 @@ static int audioreach_widget_i2s_module_load(struct audioreach_module *mod, return 0; } +static int audioreach_widget_audio_if_module_load(struct audioreach_module *mod, + const struct snd_soc_tplg_vendor_array *mod_array) +{ + const struct snd_soc_tplg_vendor_value_elem *mod_elem; + int tkn_count = 0; + u32 val; + + mod_elem = mod_array->value; + + while (tkn_count < le32_to_cpu(mod_array->num_elems)) { + val = le32_to_cpu(mod_elem->value); + switch (le32_to_cpu(mod_elem->token)) { + case AR_TKN_U32_MODULE_HW_IF_IDX: + mod->hw_interface_idx = val; + break; + case AR_TKN_U32_MODULE_FMT_DATA: + mod->data_format = val; + break; + case AR_TKN_U16_MODULE_SYNC_SRC: + if (val > U16_MAX) + return -EINVAL; + mod->sync_src = (u16)val; + break; + case AR_TKN_U16_MODULE_CTRL_DATA_OUT_ENABLE: + if (val > U16_MAX) + return -EINVAL; + mod->ctrl_data_out_enable = (u16)val; + break; + case AR_TKN_U32_MODULE_SLOT_MASK: + mod->slot_mask = val; + break; + case AR_TKN_U16_MODULE_NSLOTS_PER_FRAME: + if (val > U16_MAX) + return -EINVAL; + mod->nslots_per_frame = (u16)val; + break; + case AR_TKN_U16_MODULE_SLOT_WIDTH: + if (val > U16_MAX) + return -EINVAL; + mod->slot_width = (u16)val; + break; + case AR_TKN_U16_MODULE_INTF_MODE: + if (val > U16_MAX) + return -EINVAL; + mod->intf_mode = (u16)val; + break; + case AR_TKN_U16_MODULE_SYNC_MODE: + if (val > U16_MAX) + return -EINVAL; + mod->sync_mode = (u16)val; + break; + case AR_TKN_U16_MODULE_CTRL_INVERT_SYNC_PULSE: + if (val > U16_MAX) + return -EINVAL; + mod->ctrl_invert_sync_pulse = (u16)val; + break; + case AR_TKN_U16_MODULE_CTRL_SYNC_DATA_DELAY: + if (val > U16_MAX) + return -EINVAL; + mod->ctrl_sync_data_delay = (u16)val; + break; + case AR_TKN_U16_MODULE_QAIF_TYPE: + if (val > U16_MAX) + return -EINVAL; + mod->qaif_type = (u16)val; + break; + case AR_TKN_U32_MODULE_ACTIVE_LANE_MASK: + mod->active_lane_mask = val; + break; + case AR_TKN_U32_MODULE_FRAME_SYNC_RATE: + mod->frame_sync_rate = val; + break; + case AR_TKN_U16_MODULE_BIT_CLK_TYPE: + if (val > U16_MAX) + return -EINVAL; + mod->bit_clk_type = (u16)val; + break; + case AR_TKN_U8_MODULE_INV_INT_BIT_CLK: + if (val > U8_MAX) + return -EINVAL; + mod->inv_int_bit_clk = (u8)val; + break; + case AR_TKN_U8_MODULE_INV_EXT_BIT_CLK: + if (val > U8_MAX) + return -EINVAL; + mod->inv_ext_bit_clk = (u8)val; + break; + default: + break; + } + tkn_count++; + mod_elem++; + } + + return 0; +} + static int audioreach_widget_dp_module_load(struct audioreach_module *mod, const struct snd_soc_tplg_vendor_array *mod_array) { @@ -806,6 +903,12 @@ static int audioreach_widget_load_buffer(struct snd_soc_component *component, case MODULE_ID_I2S_SOURCE: audioreach_widget_i2s_module_load(mod, mod_array); break; + case MODULE_ID_AUDIO_IF_SINK: + case MODULE_ID_AUDIO_IF_SOURCE: + ret = audioreach_widget_audio_if_module_load(mod, mod_array); + if (ret) + return ret; + break; case MODULE_ID_DISPLAY_PORT_SINK: audioreach_widget_dp_module_load(mod, mod_array); break; diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c index 98b15a527e37e..21f185eba32b8 100644 --- a/sound/soc/qcom/sc8280xp.c +++ b/sound/soc/qcom/sc8280xp.c @@ -2,6 +2,7 @@ // Copyright (c) 2022, Linaro Limited #include +#include #include #include #include @@ -12,17 +13,168 @@ #include #include #include "qdsp6/q6afe.h" +#include "qdsp6/q6apm.h" +#include "qdsp6/q6prm.h" #include "common.h" #include "sdw.h" +#define I2S_MCLKFS 256 + +#define I2S_MCLK_RATE(rate) \ + ((rate) * (I2S_MCLKFS)) +#define I2S_BIT_RATE(rate, channels, format) \ + ((rate) * (channels) * (format)) + +static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("DP0 Jack", NULL), + SND_SOC_DAPM_SPK("DP1 Jack", NULL), + SND_SOC_DAPM_SPK("DP2 Jack", NULL), + SND_SOC_DAPM_SPK("DP3 Jack", NULL), + SND_SOC_DAPM_SPK("DP4 Jack", NULL), + SND_SOC_DAPM_SPK("DP5 Jack", NULL), + SND_SOC_DAPM_SPK("DP6 Jack", NULL), + SND_SOC_DAPM_SPK("DP7 Jack", NULL), +}; + +static struct snd_soc_dapm_widget shikra_cqm_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_widget shikra_iqs_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_kcontrol_new shikra_iqs_controls[] = { + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Int Mic"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +struct qcom_snd_soc_common { + const char *driver_name; + const struct snd_soc_dapm_widget *dapm_widgets; + int num_dapm_widgets; + const struct snd_soc_dapm_route *dapm_routes; + int num_dapm_routes; + const struct snd_kcontrol_new *controls; + int num_controls; + unsigned int codec_dai_fmt; + bool codec_sysclk_set; + bool mi2s_mclk_enable; + bool mi2s_bclk_enable; + bool wcd_jack; + bool dsp_bypass; +}; + struct sc8280xp_snd_data { bool stream_prepared[AFE_PORT_MAX]; struct snd_soc_card *card; struct snd_soc_jack jack; struct snd_soc_jack dp_jack[8]; + const struct qcom_snd_soc_common *priv; bool jack_setup; }; +static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params) +{ + int rate = params_rate(params); + + switch (rate) { + case 11025: + case 44100: + case 88200: + return I2S_MCLK_RATE(44100); + default: + break; + } + + return I2S_MCLK_RATE(rate); +} + +static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params) +{ + return I2S_BIT_RATE(params_rate(params), + params_channels(params), + snd_pcm_format_width(params_format(params))); +} + +static int sc8280xp_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai; + struct qcom_snd_tdm_slot_cfg cpu_cfg; + struct qcom_snd_tdm_slot_cfg codec_cfg; + int bclk_freq; + int ret; + int i; + + ret = qcom_snd_get_dai_tdm_slots(rtd, &cpu_cfg, &codec_cfg); + if (ret) + return ret == -ENOENT ? 0 : ret; + + if (!cpu_cfg.slots) + return 0; + + if (data->priv->dsp_bypass) + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_BP_FP); + else + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); + if (ret && ret != -ENOTSUPP) + return ret; + + if (data->priv->codec_dai_fmt) { + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_fmt(codec_dai, + data->priv->codec_dai_fmt); + if (ret && ret != -ENOTSUPP) + return ret; + } + } + + ret = qcom_snd_apply_dai_tdm_slots_cfg(rtd, &cpu_cfg, &codec_cfg); + if (ret) + return ret; + + bclk_freq = snd_soc_tdm_params_to_bclk(params, cpu_cfg.slot_width, cpu_cfg.slots, 1); + if (bclk_freq <= 0) + return -EINVAL; + + if (data->priv->mi2s_bclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, LPAIF_MI2S_BCLK, bclk_freq, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set cpu sysclk: %d\n", + __func__, ret); + return ret; + } + } + + if (data->priv->codec_sysclk_set) { + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, bclk_freq, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) { + dev_err(rtd->dev, "%s: failed to set codec sysclk on %s: %d\n", + __func__, codec_dai->name, ret); + return ret; + } + } + } + + return 0; +} + static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) { struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); @@ -32,10 +184,6 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) int dp_pcm_id = 0; switch (cpu_dai->id) { - case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX: - case QUINARY_MI2S_RX...QUINARY_MI2S_TX: - snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); - break; case WSA_CODEC_DMA_RX_0: case WSA_CODEC_DMA_RX_1: /* @@ -64,7 +212,10 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd) if (dp_jack) return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id); - return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + if (data->priv->wcd_jack) + return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); + + return 0; } static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, @@ -96,6 +247,65 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, return 0; } +static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + int mclk_freq = sc8280xp_get_mclk_freq(params); + int bclk_freq = sc8280xp_get_bclk_freq(params); + int ret; + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + case QUINARY_MI2S_RX ... QUINARY_MI2S_TX: + case SENARY_MI2S_RX ... SENARY_MI2S_TX: + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP); + if (ret && ret != -ENOTSUPP) + return ret; + + if (data->priv->codec_dai_fmt) { + ret = snd_soc_dai_set_fmt(codec_dai, + data->priv->codec_dai_fmt); + if (ret && ret != -ENOTSUPP) + return ret; + } + + if (data->priv->mi2s_mclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, + LPAIF_MI2S_MCLK, mclk_freq, + SND_SOC_CLOCK_OUT); + if (ret) + return ret; + } + + if (data->priv->mi2s_bclk_enable) { + ret = snd_soc_dai_set_sysclk(cpu_dai, + LPAIF_MI2S_BCLK, bclk_freq, + SND_SOC_CLOCK_OUT); + if (ret) + return ret; + } + + if (data->priv->codec_sysclk_set) { + ret = snd_soc_dai_set_sysclk(codec_dai, + 0, mclk_freq, + SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) + return ret; + } + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + return sc8280xp_tdm_hw_params(substream, params); + default: + break; + } + + return 0; +} + static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); @@ -117,6 +327,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream) static const struct snd_soc_ops sc8280xp_be_ops = { .startup = qcom_snd_sdw_startup, .shutdown = qcom_snd_sdw_shutdown, + .hw_params = sc8280xp_snd_hw_params, .hw_free = sc8280xp_snd_hw_free, .prepare = sc8280xp_snd_prepare, }; @@ -127,7 +338,7 @@ static void sc8280xp_add_be_ops(struct snd_soc_card *card) int i; for_each_card_prelinks(card, i, link) { - if (link->no_pcm == 1) { + if (link->no_pcm == 1 || link->num_codecs > 0) { link->init = sc8280xp_snd_init; link->be_hw_params_fixup = sc8280xp_be_hw_params_fixup; link->ops = &sc8280xp_be_ops; @@ -145,38 +356,162 @@ static int sc8280xp_platform_probe(struct platform_device *pdev) card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!card) return -ENOMEM; - card->owner = THIS_MODULE; + /* Allocate the private data */ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; + data->priv = of_device_get_match_data(dev); + if (!data->priv) + return -ENODEV; + + card->owner = THIS_MODULE; card->dev = dev; dev_set_drvdata(dev, card); snd_soc_card_set_drvdata(card, data); + card->dapm_widgets = data->priv->dapm_widgets; + card->num_dapm_widgets = data->priv->num_dapm_widgets; + card->dapm_routes = data->priv->dapm_routes; + card->num_dapm_routes = data->priv->num_dapm_routes; + card->controls = data->priv->controls; + card->num_controls = data->priv->num_controls; + ret = qcom_snd_parse_of(card); if (ret) return ret; - card->driver_name = of_device_get_match_data(dev); + card->driver_name = data->priv->driver_name; sc8280xp_add_be_ops(card); return devm_snd_soc_register_card(dev, card); } +static const struct qcom_snd_soc_common eliza_priv_data = { + .driver_name = "eliza", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common kaanapali_priv_data = { + .driver_name = "kaanapali", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common qcs9100_priv_data = { + .driver_name = "sa8775p", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), +}; + +static const struct qcom_snd_soc_common qcs615_priv_data = { + .driver_name = "qcs615", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), +}; + +static const struct qcom_snd_soc_common qcm6490_priv_data = { + .driver_name = "qcm6490", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common qcs6490_priv_data = { + .driver_name = "qcs6490", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common qcs8275_priv_data = { + .driver_name = "qcs8300", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), +}; + +static const struct qcom_snd_soc_common sc8280xp_priv_data = { + .driver_name = "sc8280xp", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common shikra_cqm_priv_data = { + .driver_name = "shikra", + .dapm_widgets = shikra_cqm_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(shikra_cqm_dapm_widgets), + .dsp_bypass = true, +}; + +static const struct qcom_snd_soc_common shikra_cqs_priv_data = { + .driver_name = "shikra", + .dapm_widgets = shikra_cqm_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(shikra_cqm_dapm_widgets), + .mi2s_bclk_enable = true, + .codec_sysclk_set = true, +}; + +static const struct qcom_snd_soc_common shikra_iqs_priv_data = { + .driver_name = "shikra", + .dapm_widgets = shikra_iqs_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(shikra_iqs_dapm_widgets), + .controls = shikra_iqs_controls, + .num_controls = ARRAY_SIZE(shikra_iqs_controls), + .codec_dai_fmt = SND_SOC_DAIFMT_CBC_CFC | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S, + .codec_sysclk_set = true, + .mi2s_bclk_enable = true, +}; + +static const struct qcom_snd_soc_common sm8450_priv_data = { + .driver_name = "sm8450", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common sm8550_priv_data = { + .driver_name = "sm8550", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common sm8650_priv_data = { + .driver_name = "sm8650", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + +static const struct qcom_snd_soc_common sm8750_priv_data = { + .driver_name = "sm8750", + .dapm_widgets = sc8280xp_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets), + .wcd_jack = true, +}; + static const struct of_device_id snd_sc8280xp_dt_match[] = { - {.compatible = "qcom,eliza-sndcard", "eliza"}, - {.compatible = "qcom,kaanapali-sndcard", "kaanapali"}, - {.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"}, - {.compatible = "qcom,qcs615-sndcard", "qcs615"}, - {.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"}, - {.compatible = "qcom,qcs8275-sndcard", "qcs8300"}, - {.compatible = "qcom,qcs9075-sndcard", "sa8775p"}, - {.compatible = "qcom,qcs9100-sndcard", "sa8775p"}, - {.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"}, - {.compatible = "qcom,sm8450-sndcard", "sm8450"}, - {.compatible = "qcom,sm8550-sndcard", "sm8550"}, - {.compatible = "qcom,sm8650-sndcard", "sm8650"}, - {.compatible = "qcom,sm8750-sndcard", "sm8750"}, + { .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data }, + { .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data }, + { .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data }, + { .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data }, + { .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data }, + { .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data }, + { .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data }, + { .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data }, + { .compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data }, + { .compatible = "qcom,shikra-cqm-sndcard", .data = &shikra_cqm_priv_data}, + { .compatible = "qcom,shikra-cqs-sndcard", .data = &shikra_cqs_priv_data}, + { .compatible = "qcom,shikra-iqs-sndcard", .data = &shikra_iqs_priv_data}, + { .compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data }, + { .compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data }, + { .compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data }, + { .compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data }, {} }; diff --git a/sound/soc/qcom/sdw.c b/sound/soc/qcom/sdw.c index 6576b47a4c8c3..0be743cec68e7 100644 --- a/sound/soc/qcom/sdw.c +++ b/sound/soc/qcom/sdw.c @@ -4,6 +4,7 @@ #include #include +#include #include #include #include "sdw.h" @@ -41,6 +42,8 @@ static bool qcom_snd_is_sdw_dai(int id) switch (id) { case LPASS_CDC_DMA_TX3: case LPASS_CDC_DMA_RX0: + case QAIF_CDC_DMA_VA_TX0: + case QAIF_CDC_DMA_RX0: return true; default: break; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 44f9bb4473f55..129405deeda69 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3045,6 +3045,39 @@ int snd_soc_of_parse_tdm_slot(struct device_node *np, } EXPORT_SYMBOL_GPL(snd_soc_of_parse_tdm_slot); +/** + * snd_soc_of_xlate_dai_name - Resolve a sound-dai phandle argument to a + * DAI name by searching the component DAI list. + * @component: ASoC component to search. + * @args: Phandle arguments from the sound-dai property; args[0] is the + * DAI ID. + * @dai_name: Output pointer set to the matched DAI name on success. + * + * Return: 0 on success, -EINVAL if args_count != 1 or no match is found. + */ +int snd_soc_of_xlate_dai_name(struct snd_soc_component *component, + const struct of_phandle_args *args, + const char **dai_name) +{ + struct snd_soc_dai *dai; + int id; + + if (args->args_count != 1) + return -EINVAL; + + id = args->args[0]; + + for_each_component_dais(component, dai) { + if (dai->driver->id == id) { + *dai_name = snd_soc_dai_name_get(dai); + return 0; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_of_xlate_dai_name); + void snd_soc_dlc_use_cpu_as_platform(struct snd_soc_dai_link_component *platforms, struct snd_soc_dai_link_component *cpus) {