[alsa-devel] [PATCH v3 00/25] ASoC: qcom: Add support to QDSP based Audio
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patchset aims to provide a basic version of QCOM DSP based audio support which is available in downstream andriod kernels. This patchset support audio playback on HDMI-RX, MI2S, SLIMBus and will add support to other features as we move on.
QDSP has both static and dynamic modules. static modules like AFE (Audio FrontEnd), ADM (Audio Device Manager), ASM(Audio Stream Manager) and CORE to provide this audio services. All these services use APR (Asynchronous Packet Router) protocol via smd/glink transport to communicate with Application processor. More details on each module is availble in there respective patch.
This patchset is tested on DB820c, with HDMI audio playback, MI2S on DB410c on top of mainline, Also tested SLIMBus analog audio using wcd9355 with an additional patches.
Here is my test branch incase somone want to try these patches https://git.linaro.org/people/srinivas.kandagatla/linux.git/log/?h=v4.16-rc1...
Here is block diagram to give a quick overview of the components
+---------+ +---------+ +---------+ | q6asm | |q6routing| | q6afe | | fedai | <------> | mixers | <-----> | bedai | +---------+ +---------+ +---------+ ^ ^ ^ | | | | +------------------+----------------+ | | | | | | v v v v v +---------+ +---------+ +---------+ | q6ASM | | q6ADM | | q6AFE | +---------+ +---------+ +---------+ ^ ^ ^ ^ | | | CPU Side | ------+---------------------+-------------------+-------- | | | | |APR(smd/glink) | | | | | +------------------+----------------+ | | | | | | +-----+--+-----------------------------------+--+------- | | | | | QDSP Side | v v v v v v +---------+ +---------+ +---------+ | ASM | <------> | ADM | <-----> | AFE | +---------+ +---------+ +---------+ ^ | +-------------------+ | ---------------------------+-------------------------- | Audio I/O | v v +--------------------------------------------------+ | Audio devices | | CODEC | HDMI-TX | PCM | SLIMBUS | I2S |MI2S |...| | | +--------------------------------------------------+
Changes since v2 (https://lwn.net/Articles/741472/) - fixed locking issues with ASM and APR. - Added support to MI2S and SLIMBus ports - moved dma device to apr. - added dt bindings for ASM, ADM, AFE. - Fixed dt bindings for msm8996 sound card. - added compatible strings to ASM, ADM, AFE modules. - Cleaned up common code as suggested in review. - converted sound card driver to proper apr client driver. - Fixed various issues spotted by Banajit and Kasam. - Tested on DB410c, DB820c for both analog and digital playbacks. - Exported q6core functions as suggested by Rohit. - Fixed various issues with dsp carsh/recover usecase. - Added multiple ASM streams as requested in review.
Srinivas Kandagatla (25): dt-bindings: soc: qcom: Add bindings for APR bus soc: qcom: add support to APR bus driver ASoC: qcom: qdsp6: Add common qdsp6 helper functions dt-bindings: sound: qcom: Add bindings for q6afe ASoC: qcom: qdsp6: Add support to Q6AFE dt-bindings: sound: qcom: Add bindings for q6adm ASoC: qcom: qdsp6: Add support to Q6ADM dt-bindings: sound: qcom: Add bindings for q6asm ASoC: qcom: qdsp6: Add support to Q6ASM ASoC: qcom: q6asm: Add support to memory map and unmap ASoC: qcom: q6asm: add support to audio stream apis ASoC: qcom: qdsp6: Add support to Q6CORE ASoC: qcom: qdsp6: Add support to q6routing driver ASoC: qcom: qdsp6: Add support to q6afe dai driver ASoC: qcom: qdsp6: Add support to q6asm dai driver ASoC: qcom: q6afe: add SLIMBus port Support ASoC: qcom: q6afe-dai: add support to slim afe dais ASoC: qcom: q6routing: add support to all SLIMBus Mixers ASoC: qcom: q6afe: add support to MI2S ports ASoC: qcom: q6afe: add support to MI2S sysclks ASoC: qcom: q6afe-dai: add support to 4 MI2S ports ASoC: qcom: q6routing: add support to MI2S Mixers dt-bindings: sound: qcom: Add devicetree bindings for apq8096 ASoC: qcom: apq8096: Add db820c machine driver arm64: dts: msm8996: db820c: Add sound card support
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 83 ++ .../devicetree/bindings/sound/qcom,apq8096.txt | 89 ++ .../devicetree/bindings/sound/qcom,q6adm.txt | 31 + .../devicetree/bindings/sound/qcom,q6afe.txt | 38 + .../devicetree/bindings/sound/qcom,q6asm.txt | 38 + arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi | 44 +- arch/arm64/boot/dts/qcom/msm8996.dtsi | 62 ++ drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 381 +++++++ include/dt-bindings/soc/qcom,apr.h | 27 + include/dt-bindings/sound/qcom,q6afe.h | 33 + include/dt-bindings/sound/qcom,q6asm.h | 22 + include/linux/mod_devicetable.h | 11 + include/linux/soc/qcom/apr.h | 131 +++ sound/soc/qcom/Kconfig | 42 + sound/soc/qcom/Makefile | 5 + sound/soc/qcom/apq8096.c | 173 ++++ sound/soc/qcom/qdsp6/Makefile | 5 + sound/soc/qcom/qdsp6/q6adm.c | 634 ++++++++++++ sound/soc/qcom/qdsp6/q6adm.h | 29 + sound/soc/qcom/qdsp6/q6afe-dai.c | 645 ++++++++++++ sound/soc/qcom/qdsp6/q6afe.c | 875 ++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 74 ++ sound/soc/qcom/qdsp6/q6asm-dai.c | 621 ++++++++++++ sound/soc/qcom/qdsp6/q6asm.c | 1063 ++++++++++++++++++++ sound/soc/qcom/qdsp6/q6asm.h | 66 ++ sound/soc/qcom/qdsp6/q6core.c | 235 +++++ sound/soc/qcom/qdsp6/q6core.h | 9 + sound/soc/qcom/qdsp6/q6dsp-common.c | 67 ++ sound/soc/qcom/qdsp6/q6dsp-common.h | 24 + sound/soc/qcom/qdsp6/q6dsp-errno.h | 97 ++ sound/soc/qcom/qdsp6/q6routing.c | 758 ++++++++++++++ sound/soc/qcom/qdsp6/q6routing.h | 9 + 34 files changed, 6430 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,apq8096.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,q6adm.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,q6afe.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,q6asm.txt create mode 100644 drivers/soc/qcom/apr.c create mode 100644 include/dt-bindings/soc/qcom,apr.h create mode 100644 include/dt-bindings/sound/qcom,q6afe.h create mode 100644 include/dt-bindings/sound/qcom,q6asm.h create mode 100644 include/linux/soc/qcom/apr.h create mode 100644 sound/soc/qcom/apq8096.c create mode 100644 sound/soc/qcom/qdsp6/Makefile create mode 100644 sound/soc/qcom/qdsp6/q6adm.c create mode 100644 sound/soc/qcom/qdsp6/q6adm.h create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c create mode 100644 sound/soc/qcom/qdsp6/q6afe.c create mode 100644 sound/soc/qcom/qdsp6/q6afe.h create mode 100644 sound/soc/qcom/qdsp6/q6asm-dai.c create mode 100644 sound/soc/qcom/qdsp6/q6asm.c create mode 100644 sound/soc/qcom/qdsp6/q6asm.h create mode 100644 sound/soc/qcom/qdsp6/q6core.c create mode 100644 sound/soc/qcom/qdsp6/q6core.h create mode 100644 sound/soc/qcom/qdsp6/q6dsp-common.c create mode 100644 sound/soc/qcom/qdsp6/q6dsp-common.h create mode 100644 sound/soc/qcom/qdsp6/q6dsp-errno.h create mode 100644 sound/soc/qcom/qdsp6/q6routing.c create mode 100644 sound/soc/qcom/qdsp6/q6routing.h
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR (Asynchronous Packet Router) bus driver. This bus is used for communicating with DSP which provides audio and various other services to cpu.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- .../devicetree/bindings/soc/qcom/qcom,apr.txt | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt new file mode 100644 index 000000000000..1b95fbfed348 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,83 @@ +Qualcomm APR (Asynchronous Packet Router) binding + +This binding describes the Qualcomm APR. APR is a IPC protocol for +communication between Application processor and QDSP. APR is mainly +used for audio/voice services on the QDSP. + +- compatible: + Usage: required + Value type: <stringlist> + Definition: must be "qcom,apr-v<VERSION-NUMBER>", example "qcom,apr-v2" + +- qcom,apr-dest-domain-id + Usage: required + Value type: <prop-encoded-array> + Definition: Destination processor ID. + Possible values are : + 1 - APR simulator + 2 - PC + 3 - MODEM + 4 - ADSP + 5 - APPS + 6 - MODEM2 + 7 - APPS2 + += APR SERVICES +Each subnode of the APR node can represent service tied to this apr. The name +of the nodes are not important. The properties of these nodes are defined +by the individual bindings for the specific service +- but must contain the following property: + +- qcom,apr-svc-id + Usage: required + Value type: <prop-encoded-array> + Definition: APR Service ID, used for matching the service. + Possible values are : + 3 - DSP Core Service + 4 - Audio Front End Service. + 5 - Voice Stream Manager Service. + 6 - Voice processing manager. + 7 - Audio Stream Manager Service. + 8 - Audio Device Manager Service. + 9 - Multimode voice manager. + 10 - Core voice stream. + 11 - Core voice processor. + 12 - Ultrasound stream manager. + 13 - Listen stream manager. + +- qcom,apr-svc-name + Usage: required + Value type: <stringlist> + Definition: User readable name of a APR service. + += APR DEVICES: +Each subnode of the APR node can represent devices tied to this apr, like +sound-card. The properties of these nodes are defined by the individual +bindings for the specific device. + += EXAMPLE +The following example represents a QDSP based sound card on a MSM8996 device +which uses apr as communication between Apps and QDSP. + + apr { + compatible = "qcom,apr-v2"; + qcom,smd-channels = "apr_audio_svc"; + qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>; + + q6core { + compatible = "qcom,q6core"; + qcom,apr-svc-name = "CORE"; + qcom,apr-svc-id = <APR_SVC_ADSP_CORE>; + }; + + q6afe { + compatible = "qcom,q6afe"; + qcom,apr-svc-name = "AFE"; + qcom,apr-svc-id = <APR_SVC_AFE>; + }; + + audio { + compatible = "qcom,msm8996-snd-card"; + ... + }; + };
On Tue, Feb 13, 2018 at 04:58:13PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR (Asynchronous Packet Router) bus driver. This bus is used for communicating with DSP which provides audio and various other services to cpu.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt new file mode 100644 index 000000000000..1b95fbfed348 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,83 @@ +Qualcomm APR (Asynchronous Packet Router) binding
+This binding describes the Qualcomm APR. APR is a IPC protocol for +communication between Application processor and QDSP. APR is mainly +used for audio/voice services on the QDSP.
+- compatible:
- Usage: required
- Value type: <stringlist>
- Definition: must be "qcom,apr-v<VERSION-NUMBER>", example "qcom,apr-v2"
+- qcom,apr-dest-domain-id
- Usage: required
- Value type: <prop-encoded-array>
- Definition: Destination processor ID.
- Possible values are :
1 - APR simulator
2 - PC
3 - MODEM
4 - ADSP
5 - APPS
6 - MODEM2
7 - APPS2
+= APR SERVICES +Each subnode of the APR node can represent service tied to this apr. The name +of the nodes are not important. The properties of these nodes are defined +by the individual bindings for the specific service +- but must contain the following property:
+- qcom,apr-svc-id
- Usage: required
- Value type: <prop-encoded-array>
- Definition: APR Service ID, used for matching the service.
- Possible values are :
3 - DSP Core Service
4 - Audio Front End Service.
5 - Voice Stream Manager Service.
6 - Voice processing manager.
7 - Audio Stream Manager Service.
8 - Audio Device Manager Service.
9 - Multimode voice manager.
10 - Core voice stream.
11 - Core voice processor.
12 - Ultrasound stream manager.
13 - Listen stream manager.
+- qcom,apr-svc-name
- Usage: required
- Value type: <stringlist>
- Definition: User readable name of a APR service.
+= APR DEVICES: +Each subnode of the APR node can represent devices tied to this apr, like +sound-card. The properties of these nodes are defined by the individual +bindings for the specific device.
It's not a good design generally to mix different types of nodes at one level.
+= EXAMPLE +The following example represents a QDSP based sound card on a MSM8996 device +which uses apr as communication between Apps and QDSP.
- apr {
compatible = "qcom,apr-v2";
qcom,smd-channels = "apr_audio_svc";
qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
q6core {
compatible = "qcom,q6core";
qcom,apr-svc-name = "CORE";
qcom,apr-svc-id = <APR_SVC_ADSP_CORE>;
};
q6afe {
compatible = "qcom,q6afe";
qcom,apr-svc-name = "AFE";
qcom,apr-svc-id = <APR_SVC_AFE>;
};
audio {
compatible = "qcom,msm8996-snd-card";
...
};
- };
-- 2.15.1
Thanks for the review,
On 13/02/18 23:12, Rob Herring wrote:
On Tue, Feb 13, 2018 at 04:58:13PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR (Asynchronous Packet Router) bus driver. This bus is used for communicating with DSP which provides audio and various other services to cpu.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt new file mode 100644 index 000000000000..1b95fbfed348 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,83 @@ +Qualcomm APR (Asynchronous Packet Router) binding
+This binding describes the Qualcomm APR. APR is a IPC protocol for +communication between Application processor and QDSP. APR is mainly +used for audio/voice services on the QDSP.
+- compatible:
- Usage: required
- Value type: <stringlist>
- Definition: must be "qcom,apr-v<VERSION-NUMBER>", example "qcom,apr-v2"
+- qcom,apr-dest-domain-id
- Usage: required
- Value type: <prop-encoded-array>
- Definition: Destination processor ID.
- Possible values are :
1 - APR simulator
2 - PC
3 - MODEM
4 - ADSP
5 - APPS
6 - MODEM2
7 - APPS2
+= APR SERVICES +Each subnode of the APR node can represent service tied to this apr. The name +of the nodes are not important. The properties of these nodes are defined +by the individual bindings for the specific service +- but must contain the following property:
...
+= APR DEVICES: +Each subnode of the APR node can represent devices tied to this apr, like +sound-card. The properties of these nodes are defined by the individual +bindings for the specific device.
It's not a good design generally to mix different types of nodes at one level.
I agree, may be I can split the services and devices into different subnodes like below, which should avoid mixing different types of nodes.
Does this sound good to you?
apr { compatible = "qcom,apr-v2"; qcom,smd-channels = "apr_audio_svc"; qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
apr-services { q6core { qcom,apr-svc-name = "CORE"; qcom,apr-svc-id = <APR_SVC_ADSP_CORE>; compatible = "qcom,q6core"; };
q6afe: q6afe { compatible = "qcom,q6afe"; qcom,apr-svc-name = "AFE"; qcom,apr-svc-id = <APR_SVC_AFE>; #sound-dai-cells = <1>; };
q6asm: q6asm { compatible = "qcom,q6asm"; qcom,apr-svc-name = "ASM"; qcom,apr-svc-id = <APR_SVC_ASM>; #sound-dai-cells = <1>; };
q6adm: q6adm { compatible = "qcom,q6adm"; qcom,apr-svc-name = "ADM"; qcom,apr-svc-id = <APR_SVC_ADM>; #sound-dai-cells = <0>; }; };
apr-devices { audio { compatible = "qcom,msm8996-snd-card"; ... }; }; };
+= EXAMPLE +The following example represents a QDSP based sound card on a MSM8996 device +which uses apr as communication between Apps and QDSP.
- apr {
compatible = "qcom,apr-v2";
qcom,smd-channels = "apr_audio_svc";
qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
q6core {
compatible = "qcom,q6core";
qcom,apr-svc-name = "CORE";
qcom,apr-svc-id = <APR_SVC_ADSP_CORE>;
};
q6afe {
compatible = "qcom,q6afe";
qcom,apr-svc-name = "AFE";
qcom,apr-svc-id = <APR_SVC_AFE>;
};
audio {
compatible = "qcom,msm8996-snd-card";
...
};
- };
-- 2.15.1
On Wed, Feb 14, 2018 at 09:13:23AM +0000, Srinivas Kandagatla wrote:
Thanks for the review,
On 13/02/18 23:12, Rob Herring wrote:
On Tue, Feb 13, 2018 at 04:58:13PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR (Asynchronous Packet Router) bus driver. This bus is used for communicating with DSP which provides audio and various other services to cpu.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt new file mode 100644 index 000000000000..1b95fbfed348 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,83 @@ +Qualcomm APR (Asynchronous Packet Router) binding
+This binding describes the Qualcomm APR. APR is a IPC protocol for +communication between Application processor and QDSP. APR is mainly +used for audio/voice services on the QDSP.
+- compatible:
- Usage: required
- Value type: <stringlist>
- Definition: must be "qcom,apr-v<VERSION-NUMBER>", example "qcom,apr-v2"
+- qcom,apr-dest-domain-id
- Usage: required
- Value type: <prop-encoded-array>
- Definition: Destination processor ID.
- Possible values are :
1 - APR simulator
2 - PC
3 - MODEM
4 - ADSP
5 - APPS
6 - MODEM2
7 - APPS2
+= APR SERVICES +Each subnode of the APR node can represent service tied to this apr. The name +of the nodes are not important. The properties of these nodes are defined +by the individual bindings for the specific service +- but must contain the following property:
...
+= APR DEVICES: +Each subnode of the APR node can represent devices tied to this apr, like +sound-card. The properties of these nodes are defined by the individual +bindings for the specific device.
It's not a good design generally to mix different types of nodes at one level.
I agree, may be I can split the services and devices into different subnodes like below, which should avoid mixing different types of nodes.
Does this sound good to you?
Seems your original example wasn't so complete...
I don't see why you need all these nodes in the first place. For a single SoC, how much does all this vary?
apr { compatible = "qcom,apr-v2"; qcom,smd-channels = "apr_audio_svc"; qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
apr-services { q6core { qcom,apr-svc-name = "CORE"; qcom,apr-svc-id = <APR_SVC_ADSP_CORE>; compatible = "qcom,q6core"; }; q6afe: q6afe { compatible = "qcom,q6afe"; qcom,apr-svc-name = "AFE"; qcom,apr-svc-id = <APR_SVC_AFE>; #sound-dai-cells = <1>; }; q6asm: q6asm { compatible = "qcom,q6asm"; qcom,apr-svc-name = "ASM"; qcom,apr-svc-id = <APR_SVC_ASM>; #sound-dai-cells = <1>; }; q6adm: q6adm { compatible = "qcom,q6adm"; qcom,apr-svc-name = "ADM"; qcom,apr-svc-id = <APR_SVC_ADM>; #sound-dai-cells = <0>; };
All these DAI nodes could be a single node and the cell value be the svc-id?
}; apr-devices { audio { compatible = "qcom,msm8996-snd-card";
This is a pseudo device. Why not put it at the top level like other sound cards?
... }; };
};
+= EXAMPLE +The following example represents a QDSP based sound card on a MSM8996 device +which uses apr as communication between Apps and QDSP.
- apr {
compatible = "qcom,apr-v2";
qcom,smd-channels = "apr_audio_svc";
qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
q6core {
compatible = "qcom,q6core";
qcom,apr-svc-name = "CORE";
qcom,apr-svc-id = <APR_SVC_ADSP_CORE>;
};
q6afe {
compatible = "qcom,q6afe";
qcom,apr-svc-name = "AFE";
qcom,apr-svc-id = <APR_SVC_AFE>;
};
audio {
compatible = "qcom,msm8996-snd-card";
...
};
- };
-- 2.15.1
Thanks for your review comments,
On 18/02/18 23:04, Rob Herring wrote:
On Wed, Feb 14, 2018 at 09:13:23AM +0000, Srinivas Kandagatla wrote:
Thanks for the review,
On 13/02/18 23:12, Rob Herring wrote:
On Tue, Feb 13, 2018 at 04:58:13PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR (Asynchronous Packet Router) bus driver. This bus is used for communicating with DSP which provides audio and various other services to cpu.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt new file mode 100644 index 000000000000..1b95fbfed348 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,83 @@ +Qualcomm APR (Asynchronous Packet Router) binding
+This binding describes the Qualcomm APR. APR is a IPC protocol for +communication between Application processor and QDSP. APR is mainly +used for audio/voice services on the QDSP.
+- compatible:
- Usage: required
- Value type: <stringlist>
- Definition: must be "qcom,apr-v<VERSION-NUMBER>", example "qcom,apr-v2"
+- qcom,apr-dest-domain-id
- Usage: required
- Value type: <prop-encoded-array>
- Definition: Destination processor ID.
- Possible values are :
1 - APR simulator
2 - PC
3 - MODEM
4 - ADSP
5 - APPS
6 - MODEM2
7 - APPS2
+= APR SERVICES +Each subnode of the APR node can represent service tied to this apr. The name +of the nodes are not important. The properties of these nodes are defined +by the individual bindings for the specific service +- but must contain the following property:
...
+= APR DEVICES: +Each subnode of the APR node can represent devices tied to this apr, like +sound-card. The properties of these nodes are defined by the individual +bindings for the specific device.
It's not a good design generally to mix different types of nodes at one level.
I agree, may be I can split the services and devices into different subnodes like below, which should avoid mixing different types of nodes.
Does this sound good to you?
Seems your original example wasn't so complete...
Yep, I will fix it in next version.
I don't see why you need all these nodes in the first place. For a single SoC, how much does all this vary?
It might not vary for a given SoC, but It does vary across the SoCs. Also the versions of each service are independent to each other.
apr { compatible = "qcom,apr-v2"; qcom,smd-channels = "apr_audio_svc"; qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
apr-services { q6core { qcom,apr-svc-name = "CORE"; qcom,apr-svc-id = <APR_SVC_ADSP_CORE>; compatible = "qcom,q6core"; }; q6afe: q6afe { compatible = "qcom,q6afe"; qcom,apr-svc-name = "AFE"; qcom,apr-svc-id = <APR_SVC_AFE>; #sound-dai-cells = <1>; }; q6asm: q6asm { compatible = "qcom,q6asm"; qcom,apr-svc-name = "ASM"; qcom,apr-svc-id = <APR_SVC_ASM>; #sound-dai-cells = <1>; }; q6adm: q6adm { compatible = "qcom,q6adm"; qcom,apr-svc-name = "ADM"; qcom,apr-svc-id = <APR_SVC_ADM>; #sound-dai-cells = <0>; };
All these DAI nodes could be a single node and the cell value be the svc-id?
No, DAI's here are both backends and frontends, and some of the services like core, USM are not DAI's
Are you also saying that we should have a single driver entity for all these services?
}; apr-devices { audio { compatible = "qcom,msm8996-snd-card";
This is a pseudo device. Why not put it at the top level like other sound cards?
APR bus depends on the state of DSP services, which can go off if the DSP crashes or DSP is stopped. If we remove this sound card out of apr bus then the sound card dependency on apr bus is totally lost.
Main purpose of having sound card under this bus is that the sound card should register/unregister depending up the apr channel presence/absence respectively.
thanks, srini
... }; };
};
+= EXAMPLE +The following example represents a QDSP based sound card on a MSM8996 device +which uses apr as communication between Apps and QDSP.
- apr {
compatible = "qcom,apr-v2";
qcom,smd-channels = "apr_audio_svc";
qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
q6core {
compatible = "qcom,q6core";
qcom,apr-svc-name = "CORE";
qcom,apr-svc-id = <APR_SVC_ADSP_CORE>;
};
q6afe {
compatible = "qcom,q6afe";
qcom,apr-svc-name = "AFE";
qcom,apr-svc-id = <APR_SVC_AFE>;
};
audio {
compatible = "qcom,msm8996-snd-card";
...
};
- };
-- 2.15.1
On Tue, Feb 20, 2018 at 3:33 AM, Srinivas Kandagatla srinivas.kandagatla@linaro.org wrote:
Thanks for your review comments,
On 18/02/18 23:04, Rob Herring wrote:
On Wed, Feb 14, 2018 at 09:13:23AM +0000, Srinivas Kandagatla wrote:
Thanks for the review,
On 13/02/18 23:12, Rob Herring wrote:
On Tue, Feb 13, 2018 at 04:58:13PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR (Asynchronous Packet Router) bus driver. This bus is used for communicating with DSP which provides audio and various other services to cpu.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt new file mode 100644 index 000000000000..1b95fbfed348 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,83 @@ +Qualcomm APR (Asynchronous Packet Router) binding
+This binding describes the Qualcomm APR. APR is a IPC protocol for +communication between Application processor and QDSP. APR is mainly +used for audio/voice services on the QDSP.
+- compatible:
Usage: required
Value type: <stringlist>
Definition: must be "qcom,apr-v<VERSION-NUMBER>", example
"qcom,apr-v2"
+- qcom,apr-dest-domain-id
Usage: required
Value type: <prop-encoded-array>
Definition: Destination processor ID.
Possible values are :
1 - APR simulator
2 - PC
3 - MODEM
4 - ADSP
5 - APPS
6 - MODEM2
7 - APPS2
+= APR SERVICES +Each subnode of the APR node can represent service tied to this apr. The name +of the nodes are not important. The properties of these nodes are defined +by the individual bindings for the specific service +- but must contain the following property:
...
+= APR DEVICES: +Each subnode of the APR node can represent devices tied to this apr, like +sound-card. The properties of these nodes are defined by the individual +bindings for the specific device.
It's not a good design generally to mix different types of nodes at one level.
I agree, may be I can split the services and devices into different subnodes like below, which should avoid mixing different types of nodes.
Does this sound good to you?
Seems your original example wasn't so complete...
Yep, I will fix it in next version.
I don't see why you need all these nodes in the first place. For a single SoC, how much does all this vary?
It might not vary for a given SoC, but It does vary across the SoCs. Also the versions of each service are independent to each other.
Not sure I follow the last statement. Meaning firmware updates change the services?
I don't see any versioning of services here.
apr { compatible = "qcom,apr-v2"; qcom,smd-channels = "apr_audio_svc"; qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
apr-services { q6core { qcom,apr-svc-name = "CORE"; qcom,apr-svc-id = <APR_SVC_ADSP_CORE>; compatible = "qcom,q6core"; }; q6afe: q6afe { compatible = "qcom,q6afe"; qcom,apr-svc-name = "AFE"; qcom,apr-svc-id = <APR_SVC_AFE>; #sound-dai-cells = <1>; }; q6asm: q6asm { compatible = "qcom,q6asm"; qcom,apr-svc-name = "ASM"; qcom,apr-svc-id = <APR_SVC_ASM>; #sound-dai-cells = <1>; }; q6adm: q6adm { compatible = "qcom,q6adm"; qcom,apr-svc-name = "ADM"; qcom,apr-svc-id = <APR_SVC_ADM>; #sound-dai-cells = <0>; };
All these DAI nodes could be a single node and the cell value be the svc-id?
No, DAI's here are both backends and frontends, and some of the services like core, USM are not DAI's
Are you also saying that we should have a single driver entity for all these services?
DT nodes do not equate driver entities. A driver can instantiate other drivers.
Am I saying a single DT node for this? Yes, perhaps.
}; apr-devices { audio { compatible = "qcom,msm8996-snd-card";
This is a pseudo device. Why not put it at the top level like other sound cards?
APR bus depends on the state of DSP services, which can go off if the DSP crashes or DSP is stopped. If we remove this sound card out of apr bus then the sound card dependency on apr bus is totally lost.
Main purpose of having sound card under this bus is that the sound card should register/unregister depending up the apr channel presence/absence respectively.
Okay, that seems sensible.
Rob
On 22/02/18 00:14, Rob Herring wrote:
On Tue, Feb 20, 2018 at 3:33 AM, Srinivas Kandagatla srinivas.kandagatla@linaro.org wrote:
Thanks for your review comments,
On 18/02/18 23:04, Rob Herring wrote:
On Wed, Feb 14, 2018 at 09:13:23AM +0000, Srinivas Kandagatla wrote:
Thanks for the review,
On 13/02/18 23:12, Rob Herring wrote:
On Tue, Feb 13, 2018 at 04:58:13PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR (Asynchronous Packet Router) bus driver. This bus is used for communicating with DSP which provides audio and various other services to cpu.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 83
++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt new file mode 100644 index 000000000000..1b95fbfed348 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,83 @@ +Qualcomm APR (Asynchronous Packet Router) binding
+This binding describes the Qualcomm APR. APR is a IPC protocol for +communication between Application processor and QDSP. APR is mainly +used for audio/voice services on the QDSP.
...
It's not a good design generally to mix different types of nodes at one level.
I agree, may be I can split the services and devices into different subnodes like below, which should avoid mixing different types of nodes.
Does this sound good to you?
Seems your original example wasn't so complete...
Yep, I will fix it in next version.
I don't see why you need all these nodes in the first place. For a single SoC, how much does all this vary?
It might not vary for a given SoC, but It does vary across the SoCs. Also the versions of each service are independent to each other.
Not sure I follow the last statement. Meaning firmware updates change the services?
Sorry for not being clear, so the services like AFE, ASM, ADM have different version numbers for a given SoC/firmware.
Not 100% sure if firmware updates would change the service version number, even if it does, it can be queried dynamically on new B family SoCs, and on older A Family SoCs I have not seen the firmware updates in last 4 years.
I don't see any versioning of services here.
Yes, Plan is that it will be part of the compatible string in cases where version query is not supported on older QCOM A family SoCs. On B Family SoCs we can query the version dynamically.
apr { compatible = "qcom,apr-v2"; qcom,smd-channels = "apr_audio_svc"; qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
apr-services { q6core { qcom,apr-svc-name = "CORE"; qcom,apr-svc-id = <APR_SVC_ADSP_CORE>; compatible = "qcom,q6core"; }; q6afe: q6afe { compatible = "qcom,q6afe"; qcom,apr-svc-name = "AFE"; qcom,apr-svc-id = <APR_SVC_AFE>; #sound-dai-cells = <1>; }; q6asm: q6asm { compatible = "qcom,q6asm"; qcom,apr-svc-name = "ASM"; qcom,apr-svc-id = <APR_SVC_ASM>; #sound-dai-cells = <1>; }; q6adm: q6adm { compatible = "qcom,q6adm"; qcom,apr-svc-name = "ADM"; qcom,apr-svc-id = <APR_SVC_ADM>; #sound-dai-cells = <0>; };
All these DAI nodes could be a single node and the cell value be the svc-id?
So we will have 2 cell values, one representing the apr service and other the dai.
No, DAI's here are both backends and frontends, and some of the services like core, USM are not DAI's
Are you also saying that we should have a single driver entity for all these services?
DT nodes do not equate driver entities. A driver can instantiate other drivers.
Am I saying a single DT node for this? Yes, perhaps.
Yes, I will give that a go.
So we will endup having something like this in DT for one frontend and backend dailink:
apr { compatible = "qcom,apr-v2"; qcom,smd-channels = "apr_audio_svc"; qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>
q6audio: audiosvc { compatible = "qcom,q6audio-svc"; qcom,apr-svcs = <APR_SVC_CORE APR_SVC_AFE APR_SVC_ASM>; qcom,apr-svc-names = "CORE", "AFE", "ASM"; };
sndcard { compatible = "qcom,msm8996-snd-card" qcom,model = "DB820c"; qcom,audio-routing = "RX_BIAS", "MCLK"; fe@1 { is-fe; link-name = "MultiMedia1"; cpu { sound-dai = <&q6audio APR_SVC_ASM MM1> }; platform { sound-dai = <&q6audio APR_SVC_ASM MM1>; }; };
be@1 { link-name = "HDMI Playback"; cpu { sound-dai = <&q6audio APR_SVC_AFE HDMI>; };
platform { sound-dai = <&q6audio ARP_SVC_ADM>; };
codec { sound-dai = <&hdmi 0>; }; }; }; };
Thanks, Srini
On 22/02/18 10:03, Srinivas Kandagatla wrote:
Also the versions of each service are independent to each other.
Not sure I follow the last statement. Meaning firmware updates change the services?
Sorry for not being clear, so the services like AFE, ASM, ADM have different version numbers for a given SoC/firmware.
Not 100% sure if firmware updates would change the service version number, even if it does, it can be queried dynamically on new B family SoCs, and on older A Family SoCs I have not seen the firmware updates in last 4 years.
I don't see any versioning of services here.
Yes, Plan is that it will be part of the compatible string in cases where version query is not supported on older QCOM A family SoCs. On B Family SoCs we can query the version dynamically.
apr { compatible = "qcom,apr-v2"; qcom,smd-channels = "apr_audio_svc"; qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>;
apr-services { q6core { qcom,apr-svc-name = "CORE"; qcom,apr-svc-id = <APR_SVC_ADSP_CORE>; compatible = "qcom,q6core"; }; q6afe: q6afe { compatible = "qcom,q6afe"; qcom,apr-svc-name = "AFE"; qcom,apr-svc-id = <APR_SVC_AFE>; #sound-dai-cells = <1>; }; q6asm: q6asm { compatible = "qcom,q6asm"; qcom,apr-svc-name = "ASM"; qcom,apr-svc-id = <APR_SVC_ASM>; #sound-dai-cells = <1>; }; q6adm: q6adm {, "external-sleep" compatible = "qcom,q6adm"; qcom,apr-svc-name = "ADM"; qcom,apr-svc-id = <APR_SVC_ADM>; #sound-dai-cells = <0>; };
All these DAI nodes could be a single node and the cell value be the svc-id?
So we will have 2 cell values, one representing the apr service and other the dai.
No, DAI's here are both backends and frontends, and some of the services like core, USM are not DAI's
Are you also saying that we should have a single driver entity for all these services?
DT nodes do not equate driver entities. A driver can instantiate other drivers.
Am I saying a single DT node for this? Yes, perhaps.
Yes, I will give that a go.
I did try your suggestion of making audio services into a single node and it ended up more messy and non scalable.
Mainly because q6afe node has more than one interface type of backend dais (child nodes).
Each of this backend dai may need board specific setting ex: MI2S case where board could wire up specific tx and rx lines from 4 possible lines for that board. Also other type of dais also have some interface specific properties.
MI2S example: apr { ... q6afe: q6afe { compatible = "qcom,q6afe"; qcom,apr-svc-name = "AFE"; qcom,apr-svc-id = <APR_SVC_AFE>; pinctrl-0 = <&ext_sec_tlmm_lines_act>; pinctrl-names = "default"; #sound-dai-cells = <1>;
mi2s_prim_rx_dai@34{ reg = <34>; rx-lines = <0>; ... };
mi2s_prim_tx_dai@35{ reg = <35>; tx-lines = <1 2> ... }; ... }; };
Here is block diagram to give a quick overview of the components
+---------+ +---------+ +---------+ | q6asm | |q6routing| | q6afe | | fedai | <------> | mixers | <-----> | bedai | +---------+ +---------+ +---------+ ^ ^ ^ | | | | +------------------+----------------+ | | | | | | v v v v v +---------+ +---------+ +---------+ | q6ASM | | q6ADM | | q6AFE | +---------+ +---------+ +---------+ ^ ^ ^ ^ | | | CPU Side | ------+---------------------+-------------------+-------- | | | |APR |APR |APR | | | | +------------------+----------------+ | | | | | | +-----+--+-----------------------------------+--+------- | | | | | QDSP Side | v v v v v v +---------+ +---------+ +---------+ | ASM | <------> | ADM | <-----> | AFE | +---------+ +---------+ +---------+ ^ | +-------------------+ | ---------------------------+-------------------------- | Audio I/O | v v +--------------------------------------------------+ | Audio devices | | CODEC | HDMI-TX | PCM | SLIMBUS | I2S |MI2S |...| | | +--------------------------------------------------+
thanks, srini
On Thu, Feb 22, 2018 at 10:03:42AM +0000, Srinivas Kandagatla wrote:
On 22/02/18 00:14, Rob Herring wrote:
Am I saying a single DT node for this? Yes, perhaps.
Yes, I will give that a go.
So we will endup having something like this in DT for one frontend and backend dailink:
Let's not start encoding DPCM into DT, DPCM is very much an implemntation detail of the current stack which we're gradually pushing towards replacing with something better. What we want to be doing is just treating components inside the SoC the same as components in a CODEC, the routing within a SoC being the same as in a CODEC and similarly for externally connected devices.
fe@1 { is-fe; link-name = "MultiMedia1";
In particular having the concept of "front end" in the DT is *very* implementation specific. We should just be describing the connections that exist in the system.
Thanks for the review,
On 01/03/18 20:34, Mark Brown wrote:
On Thu, Feb 22, 2018 at 10:03:42AM +0000, Srinivas Kandagatla wrote:
On 22/02/18 00:14, Rob Herring wrote:
Am I saying a single DT node for this? Yes, perhaps.
Yes, I will give that a go.
So we will endup having something like this in DT for one frontend and backend dailink:
Let's not start encoding DPCM into DT, DPCM is very much an implemntation detail of the current stack which we're gradually pushing towards replacing with something better. What we want to be doing is just treating components inside the SoC the same as components in a CODEC, the routing within a SoC being the same as in a CODEC and similarly for externally connected devices.
fe@1 { is-fe; link-name = "MultiMedia1";
In particular having the concept of "front end" in the DT is *very* implementation specific. We should just be describing the connections that exist in the system.
Yep, it makes sense. is-fe flag will be removed in next version, we can determine this based on codec dai, so from DT side FE or BE should not be any different.
Thanks, srini
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support toi APR bus (Asynchronous Packet Router) driver. ARP driver is made as a bus driver so that the apr devices can added removed more dynamically depending on the state of the services on the dsp. APR is used for communication between application processor and QDSP to use services on QDSP like Audio and others.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 381 +++++++++++++++++++++++++++++++++++++ include/dt-bindings/soc/qcom,apr.h | 27 +++ include/linux/mod_devicetable.h | 11 ++ include/linux/soc/qcom/apr.h | 131 +++++++++++++ 6 files changed, 560 insertions(+) create mode 100644 drivers/soc/qcom/apr.c create mode 100644 include/dt-bindings/soc/qcom,apr.h create mode 100644 include/linux/soc/qcom/apr.h
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index e050eb83341d..a1273e3f9eb5 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -107,4 +107,13 @@ config QCOM_WCNSS_CTRL Client driver for the WCNSS_CTRL SMD channel, used to download nv firmware to a newly booted WCNSS chip.
+config QCOM_APR + tristate "Qualcomm APR Bus (Asynchronous Packet Router)" + depends on ARCH_QCOM + depends on RPMSG + help + Enable APR IPC protocol support between + application processor and QDSP6. APR is + used by audio driver to configure QDSP6 + ASM, ADM and AFE modules. endmenu diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 4e91e2a7c24c..92f9c8630291 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o obj-$(CONFIG_ARCH_MSM8996) += kryo-l2-accessors.o +obj-$(CONFIG_QCOM_APR) += apr.o diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c new file mode 100644 index 000000000000..4db41c218851 --- /dev/null +++ b/drivers/soc/qcom/apr.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/of_device.h> +#include <linux/soc/qcom/apr.h> +#include <linux/rpmsg.h> +#include <linux/of.h> + +struct apr { + struct rpmsg_endpoint *ch; + struct device *dev; + spinlock_t svcs_lock; + struct list_head svcs; + int dest_domain_id; +}; + +/** + * apr_send_pkt() - Send a apr message from apr device + * + * @adev: Pointer to previously registered apr device. + * @buf: Pointer to buffer to send + * + * Return: Will be an negative on packet size on success. + */ +int apr_send_pkt(struct apr_device *adev, void *buf) +{ + struct apr *apr = dev_get_drvdata(adev->dev.parent); + struct apr_hdr *hdr; + unsigned long flags; + int ret; + + spin_lock_irqsave(&adev->lock, flags); + + hdr = (struct apr_hdr *)buf; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->src_svc = adev->svc_id; + hdr->dest_domain = adev->domain_id; + hdr->dest_svc = adev->svc_id; + + ret = rpmsg_send(apr->ch, buf, hdr->pkt_size); + if (ret) { + dev_err(&adev->dev, "Unable to send APR pkt %d\n", + hdr->pkt_size); + } else { + ret = hdr->pkt_size; + } + + spin_unlock_irqrestore(&adev->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(apr_send_pkt); + +static void apr_dev_release(struct device *dev) +{ + struct apr_device *adev = to_apr_device(dev); + + kfree(adev); +} + +static int apr_callback(struct rpmsg_device *rpdev, void *buf, + int len, void *priv, u32 addr) +{ + struct apr *apr = dev_get_drvdata(&rpdev->dev); + struct apr_client_message data; + struct apr_device *p, *c_svc = NULL; + struct apr_driver *adrv = NULL; + struct apr_hdr *hdr; + uint16_t hdr_size; + uint16_t msg_type; + uint16_t ver; + uint16_t svc; + + if (len <= APR_HDR_SIZE) { + dev_err(apr->dev, "APR: Improper apr pkt received:%p %d\n", + buf, len); + return -EINVAL; + } + + hdr = buf; + ver = APR_HDR_FIELD_VER(hdr->hdr_field); + if (ver > APR_PKT_VER + 1) + return -EINVAL; + + hdr_size = APR_HDR_FIELD_SIZE_BYTES(hdr->hdr_field); + if (hdr_size < APR_HDR_SIZE) { + dev_err(apr->dev, "APR: Wrong hdr size:%d\n", hdr_size); + return -EINVAL; + } + + if (hdr->pkt_size < APR_HDR_SIZE) { + dev_err(apr->dev, "APR: Wrong paket size\n"); + return -EINVAL; + } + + msg_type = APR_HDR_FIELD_MT(hdr->hdr_field); + if (msg_type >= APR_MSG_TYPE_MAX && msg_type != APR_BASIC_RSP_RESULT) { + dev_err(apr->dev, "APR: Wrong message type: %d\n", msg_type); + return -EINVAL; + } + + if (hdr->src_domain >= APR_DOMAIN_MAX || + hdr->dest_domain >= APR_DOMAIN_MAX || + hdr->src_svc >= APR_SVC_MAX || + hdr->dest_svc >= APR_SVC_MAX) { + dev_err(apr->dev, "APR: Wrong APR header\n"); + return -EINVAL; + } + + svc = hdr->dest_svc; + spin_lock(&apr->svcs_lock); + list_for_each_entry(p, &apr->svcs, node) { + if (svc == p->svc_id) { + c_svc = p; + if (c_svc->dev.driver) + adrv = to_apr_driver(c_svc->dev.driver); + break; + } + } + spin_unlock(&apr->svcs_lock); + + if (!adrv) { + dev_err(apr->dev, "APR: service is not registered\n"); + return -EINVAL; + } + + data.payload_size = hdr->pkt_size - hdr_size; + data.opcode = hdr->opcode; + data.src_port = hdr->src_port; + data.dest_port = hdr->dest_port; + data.token = hdr->token; + data.msg_type = msg_type; + + if (data.payload_size > 0) + data.payload = buf + hdr_size; + + adrv->callback(c_svc, &data); + + return 0; +} + +static int apr_device_match(struct device *dev, struct device_driver *drv) +{ + struct apr_device *adev = to_apr_device(dev); + struct apr_driver *adrv = to_apr_driver(drv); + const struct apr_device_id *id = adrv->id_table; + + /* Attempt an OF style match first */ + if (of_driver_match_device(dev, drv)) + return 1; + + if (!id) + return 0; + + while (id->domain_id != 0 || id->svc_id != 0) { + if (id->domain_id == adev->domain_id && + id->svc_id == adev->svc_id) + return 1; + id++; + } + + return 0; +} + +static int apr_device_probe(struct device *dev) +{ + struct apr_device *adev = to_apr_device(dev); + struct apr_driver *adrv = to_apr_driver(dev->driver); + + return adrv->probe(adev); +} + +static int apr_device_remove(struct device *dev) +{ + struct apr_device *adev = to_apr_device(dev); + struct apr_driver *adrv; + struct apr *apr = dev_get_drvdata(adev->dev.parent); + + if (dev->driver) { + adrv = to_apr_driver(dev->driver); + if (adrv->remove) + adrv->remove(adev); + spin_lock(&apr->svcs_lock); + list_del(&adev->node); + spin_unlock(&apr->svcs_lock); + } + + return 0; +} + +struct bus_type aprbus_type = { + .name = "aprbus", + .match = apr_device_match, + .probe = apr_device_probe, + .remove = apr_device_remove, + .force_dma = true, +}; +EXPORT_SYMBOL_GPL(aprbus_type); + +static int apr_add_device(struct device *dev, struct device_node *np, + const struct apr_device_id *id, bool is_svc) +{ + struct apr *apr = dev_get_drvdata(dev); + struct apr_device *adev = NULL; + + adev = kzalloc(sizeof(*adev), GFP_KERNEL); + if (!adev) + return -ENOMEM; + + spin_lock_init(&adev->lock); + + if (is_svc) { + adev->svc_id = id->svc_id; + adev->domain_id = id->domain_id; + adev->version = id->svc_version; + dev_set_name(&adev->dev, "aprsvc:%s:%x:%x", id->name, + id->domain_id, id->svc_id); + } else { + dev_set_name(&adev->dev, "%s:%s", dev_name(dev), np->name); + } + + adev->dev.bus = &aprbus_type; + adev->dev.parent = dev; + adev->dev.of_node = np; + adev->dev.release = apr_dev_release; + adev->dev.driver = NULL; + + spin_lock(&apr->svcs_lock); + list_add_tail(&adev->node, &apr->svcs); + spin_unlock(&apr->svcs_lock); + + dev_info(dev, "Adding APR dev: %s\n", dev_name(&adev->dev)); + + return device_register(&adev->dev); +} + +static void of_register_apr_devices(struct device *dev) +{ + struct apr *apr = dev_get_drvdata(dev); + struct device_node *node; + + for_each_child_of_node(dev->of_node, node) { + struct apr_device_id id = {0}; + const char *svc_name; + bool is_svc = false; + + if (of_find_property(node, "qcom,apr-svc-id", NULL) && + of_find_property(node, "qcom,apr-svc-name", NULL)) { + /* svc node */ + of_property_read_u32(node, "qcom,apr-svc-id", + &id.svc_id); + of_property_read_string(node, "qcom,apr-svc-name", + &svc_name); + id.domain_id = apr->dest_domain_id; + + memcpy(id.name, svc_name, strlen(svc_name) + 1); + is_svc = true; + } + + if (apr_add_device(dev, node, &id, is_svc)) + dev_err(dev, "Failed to add arp %s svc\n", svc_name); + } +} + +static int apr_probe(struct rpmsg_device *rpdev) +{ + struct device *dev = &rpdev->dev; + struct apr *apr; + int ret; + + apr = devm_kzalloc(dev, sizeof(*apr), GFP_KERNEL); + if (!apr) + return -ENOMEM; + + ret = of_property_read_u32(dev->of_node, "qcom,apr-dest-domain-id", + &apr->dest_domain_id); + if (ret) { + dev_err(dev, "APR Domain ID not specified in DT\n"); + return ret; + } + + dev_set_drvdata(dev, apr); + apr->ch = rpdev->ept; + apr->dev = dev; + INIT_LIST_HEAD(&apr->svcs); + + of_register_apr_devices(dev); + + return 0; +} + +static int apr_remove_device(struct device *dev, void *null) +{ + struct apr_device *adev = to_apr_device(dev); + + device_unregister(&adev->dev); + + return 0; +} + +static void apr_remove(struct rpmsg_device *rpdev) +{ + device_for_each_child(&rpdev->dev, NULL, apr_remove_device); +} + +/* + * __apr_driver_register() - Client driver registration with aprbus + * + * @drv:Client driver to be associated with client-device. + * @owner: owning module/driver + * + * This API will register the client driver with the aprbus + * It is called from the driver's module-init function. + */ +int __apr_driver_register(struct apr_driver *drv, struct module *owner) +{ + drv->driver.bus = &aprbus_type; + drv->driver.owner = owner; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__apr_driver_register); + +/* + * apr_driver_unregister() - Undo effect of apr_driver_register + * + * @drv: Client driver to be unregistered + */ +void apr_driver_unregister(struct apr_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(apr_driver_unregister); + +static const struct of_device_id apr_of_match[] = { + { .compatible = "qcom,apr"}, + { .compatible = "qcom,apr-v2"}, + {} +}; + +static struct rpmsg_driver apr_driver = { + .probe = apr_probe, + .remove = apr_remove, + .callback = apr_callback, + .drv = { + .name = "qcom,apr", + .of_match_table = apr_of_match, + }, +}; + +static int __init apr_init(void) +{ + int ret; + + ret = bus_register(&aprbus_type); + if (!ret) + ret = register_rpmsg_driver(&apr_driver); + + return ret; +} + +static void __exit apr_exit(void) +{ + bus_unregister(&aprbus_type); + unregister_rpmsg_driver(&apr_driver); +} + +subsys_initcall(apr_init); +module_exit(apr_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm APR Bus"); diff --git a/include/dt-bindings/soc/qcom,apr.h b/include/dt-bindings/soc/qcom,apr.h new file mode 100644 index 000000000000..fb162ac4ca0e --- /dev/null +++ b/include/dt-bindings/soc/qcom,apr.h @@ -0,0 +1,27 @@ +#ifndef __DT_BINDINGS_QCOM_APR_H +#define __DT_BINDINGS_QCOM_APR_H + +/* Domain IDs */ +#define APR_DOMAIN_SIM 0x1 +#define APR_DOMAIN_PC 0x2 +#define APR_DOMAIN_MODEM 0x3 +#define APR_DOMAIN_ADSP 0x4 +#define APR_DOMAIN_APPS 0x5 +#define APR_DOMAIN_MAX 0x6 + +/* ADSP service IDs */ +#define APR_SVC_ADSP_CORE 0x3 +#define APR_SVC_AFE 0x4 +#define APR_SVC_VSM 0x5 +#define APR_SVC_VPM 0x6 +#define APR_SVC_ASM 0x7 +#define APR_SVC_ADM 0x8 +#define APR_SVC_ADSP_MVM 0x09 +#define APR_SVC_ADSP_CVS 0x0A +#define APR_SVC_ADSP_CVP 0x0B +#define APR_SVC_USM 0x0C +#define APR_SVC_LSM 0x0D +#define APR_SVC_VIDC 0x16 +#define APR_SVC_MAX 0x17 + +#endif /* __DT_BINDINGS_QCOM_APR_H */ diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 48fb2b43c35a..c05c89b5a99e 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -471,6 +471,17 @@ struct slim_device_id { kernel_ulong_t driver_data; };
+#define APR_NAME_SIZE 32 +#define APR_MODULE_PREFIX "apr:" + +struct apr_device_id { + char name[APR_NAME_SIZE]; + __u32 domain_id; + __u32 svc_id; + __u32 svc_version; + kernel_ulong_t driver_data; /* Data private to the driver */ +}; + #define SPMI_NAME_SIZE 32 #define SPMI_MODULE_PREFIX "spmi:"
diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h new file mode 100644 index 000000000000..17bf0773e146 --- /dev/null +++ b/include/linux/soc/qcom/apr.h @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#ifndef __QCOM_APR_H_ +#define __QCOM_APR_H_ + +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/mod_devicetable.h> +#include <dt-bindings/soc/qcom,apr.h> + +#define APR_HDR_LEN(hdr_len) ((hdr_len)/4) + +/* + * HEADER field + * version:0:3 + * header_size : 4:7 + * message_type : 8:9 + * reserved: 10:15 + */ +#define APR_HDR_FIELD(msg_type, hdr_len, ver)\ + (((msg_type & 0x3) << 8) | ((hdr_len & 0xF) << 4) | (ver & 0xF)) + +#define APR_HDR_SIZE sizeof(struct apr_hdr) +#define APR_SEQ_CMD_HDR_FIELD APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(APR_HDR_SIZE), \ + APR_PKT_VER) +/* Version */ +#define APR_PKT_VER 0x0 + +/* Command and Response Types */ +#define APR_MSG_TYPE_EVENT 0x0 +#define APR_MSG_TYPE_CMD_RSP 0x1 +#define APR_MSG_TYPE_SEQ_CMD 0x2 +#define APR_MSG_TYPE_NSEQ_CMD 0x3 +#define APR_MSG_TYPE_MAX 0x04 + +/* APR Basic Response Message */ +#define APR_BASIC_RSP_RESULT 0x000110E8 +#define APR_RSP_ACCEPTED 0x000100BE + +struct aprv2_ibasic_rsp_result_t { + uint32_t opcode; + uint32_t status; +}; + +/* hdr field Ver [0:3], Size [4:7], Message type [8:10] */ +#define APR_HDR_FIELD_VER(h) (h & 0x000F) +#define APR_HDR_FIELD_SIZE(h) ((h & 0x00F0) >> 4) +#define APR_HDR_FIELD_SIZE_BYTES(h) (((h & 0x00F0) >> 4) * 4) +#define APR_HDR_FIELD_MT(h) ((h & 0x0300) >> 8) + +struct apr_hdr { + uint16_t hdr_field; + uint16_t pkt_size; + uint8_t src_svc; + uint8_t src_domain; + uint16_t src_port; + uint8_t dest_svc; + uint8_t dest_domain; + uint16_t dest_port; + uint32_t token; + uint32_t opcode; +}; + +struct apr_client_message { + uint16_t payload_size; + uint16_t hdr_len; + uint16_t msg_type; + uint16_t src; + uint16_t dest_svc; + uint16_t src_port; + uint16_t dest_port; + uint32_t token; + uint32_t opcode; + void *payload; +}; + +/* Bits 0 to 15 -- Minor version, Bits 16 to 31 -- Major version */ +#define APR_SVC_MAJOR_VERSION(v) ((v >> 16) & 0xFF) +#define APR_SVC_MINOR_VERSION(v) (v & 0xFF) + +struct apr_device { + struct device dev; + uint16_t svc_id; + uint16_t domain_id; + uint16_t version; + spinlock_t lock; + struct list_head node; +}; + +#define to_apr_device(d) container_of(d, struct apr_device, dev) + +struct apr_driver { + int (*probe)(struct apr_device *sl); + int (*remove)(struct apr_device *sl); + int (*callback)(struct apr_device *a, + struct apr_client_message *d); + struct device_driver driver; + const struct apr_device_id *id_table; +}; + +#define to_apr_driver(d) container_of(d, struct apr_driver, driver) + +/* + * use a macro to avoid include chaining to get THIS_MODULE + */ +#define apr_driver_register(drv) __apr_driver_register(drv, THIS_MODULE) + +int __apr_driver_register(struct apr_driver *drv, struct module *owner); +void apr_driver_unregister(struct apr_driver *drv); + +/** + * module_apr_driver() - Helper macro for registering a aprbus driver + * @__aprbus_driver: aprbus_driver struct + * + * Helper macro for aprbus drivers which do not do anything special in + * module init/exit. This eliminates a lot of boilerplate. Each module + * may only use this macro once, and calling it replaces module_init() + * and module_exit() + */ +#define module_apr_driver(__apr_driver) \ + module_driver(__apr_driver, apr_driver_register, \ + apr_driver_unregister) + +int apr_send_pkt(struct apr_device *adev, void *buf); + +#endif /* __QCOM_APR_H_ */
On Tue, Feb 13, 2018 at 04:58:14PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support toi APR bus (Asynchronous Packet Router) driver. ARP driver is made as a bus driver so that the apr devices can added removed more dynamically depending on the state of the services on the dsp. APR is used for communication between application processor and QDSP to use services on QDSP like Audio and others.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 381 +++++++++++++++++++++++++++++++++++++ include/dt-bindings/soc/qcom,apr.h | 27 +++
This belongs in the binding patch.
include/linux/mod_devicetable.h | 11 ++ include/linux/soc/qcom/apr.h | 131 +++++++++++++ 6 files changed, 560 insertions(+) create mode 100644 drivers/soc/qcom/apr.c create mode 100644 include/dt-bindings/soc/qcom,apr.h create mode 100644 include/linux/soc/qcom/apr.h
diff --git a/include/dt-bindings/soc/qcom,apr.h b/include/dt-bindings/soc/qcom,apr.h new file mode 100644 index 000000000000..fb162ac4ca0e --- /dev/null +++ b/include/dt-bindings/soc/qcom,apr.h @@ -0,0 +1,27 @@
SPDX tag
+#ifndef __DT_BINDINGS_QCOM_APR_H +#define __DT_BINDINGS_QCOM_APR_H
+/* Domain IDs */ +#define APR_DOMAIN_SIM 0x1 +#define APR_DOMAIN_PC 0x2 +#define APR_DOMAIN_MODEM 0x3 +#define APR_DOMAIN_ADSP 0x4 +#define APR_DOMAIN_APPS 0x5 +#define APR_DOMAIN_MAX 0x6
+/* ADSP service IDs */ +#define APR_SVC_ADSP_CORE 0x3 +#define APR_SVC_AFE 0x4 +#define APR_SVC_VSM 0x5 +#define APR_SVC_VPM 0x6 +#define APR_SVC_ASM 0x7 +#define APR_SVC_ADM 0x8 +#define APR_SVC_ADSP_MVM 0x09 +#define APR_SVC_ADSP_CVS 0x0A +#define APR_SVC_ADSP_CVP 0x0B +#define APR_SVC_USM 0x0C +#define APR_SVC_LSM 0x0D +#define APR_SVC_VIDC 0x16 +#define APR_SVC_MAX 0x17
+#endif /* __DT_BINDINGS_QCOM_APR_H */
Thanks for review comments,
On 19/02/18 03:08, Rob Herring wrote:
On Tue, Feb 13, 2018 at 04:58:14PM +0000,srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatlasrinivas.kandagatla@linaro.org
This patch adds support toi APR bus (Asynchronous Packet Router) driver. ARP driver is made as a bus driver so that the apr devices can added removed more dynamically depending on the state of the services on the dsp. APR is used for communication between application processor and QDSP to use services on QDSP like Audio and others.
Signed-off-by: Srinivas Kandagatlasrinivas.kandagatla@linaro.org
drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 381 +++++++++++++++++++++++++++++++++++++ include/dt-bindings/soc/qcom,apr.h | 27 +++
This belongs in the binding patch.
I agree, will fix it in next version.
include/linux/mod_devicetable.h | 11 ++ include/linux/soc/qcom/apr.h | 131 +++++++++++++ 6 files changed, 560 insertions(+) create mode 100644 drivers/soc/qcom/apr.c create mode 100644 include/dt-bindings/soc/qcom,apr.h create mode 100644 include/linux/soc/qcom/apr.h
diff --git a/include/dt-bindings/soc/qcom,apr.h b/include/dt-bindings/soc/qcom,apr.h new file mode 100644 index 000000000000..fb162ac4ca0e --- /dev/null +++ b/include/dt-bindings/soc/qcom,apr.h @@ -0,0 +1,27 @@
SPDX tag
Thanks for reminding this.. Will fix it in next spin.
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds some common helper functions like translating dsp error to linux error codes and channel mappings etc.
These functions are used in all the following qdsp6 drivers.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 14 ++++++ sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6dsp-common.c | 67 +++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6dsp-common.h | 24 +++++++++ sound/soc/qcom/qdsp6/q6dsp-errno.h | 97 +++++++++++++++++++++++++++++++++++++ 5 files changed, 203 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/Makefile create mode 100644 sound/soc/qcom/qdsp6/q6dsp-common.c create mode 100644 sound/soc/qcom/qdsp6/q6dsp-common.h create mode 100644 sound/soc/qcom/qdsp6/q6dsp-errno.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 8ec9a074b38b..b01f347b427d 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -43,3 +43,17 @@ config SND_SOC_APQ8016_SBC Support for Qualcomm Technologies LPASS audio block in APQ8016 SOC-based systems. Say Y if you want to use audio devices on MI2S. + +config SND_SOC_QDSP6_COMMON + tristate + default n + +config SND_SOC_QDSP6 + tristate "SoC ALSA audio driver for QDSP6" + depends on QCOM_APR && HAS_DMA + select SND_SOC_QDSP6_COMMON + help + To add support for MSM QDSP6 Soc Audio. + This will enable sound soc platform specific + audio drivers. This includes q6asm, q6adm, + q6afe interfaces to DSP using apr. diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile new file mode 100644 index 000000000000..accebdb49306 --- /dev/null +++ b/sound/soc/qcom/qdsp6/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.c b/sound/soc/qcom/qdsp6/q6dsp-common.c new file mode 100644 index 000000000000..3fe5b7942c4c --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-common.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ +#include "q6dsp-common.h" +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/errno.h> + +int q6dsp_map_channels(u8 ch_map[PCM_FORMAT_MAX_NUM_CHANNEL], int ch) +{ + memset(ch_map, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + switch (ch) { + case 1: + ch_map[0] = PCM_CHANNEL_FC; + break; + case 2: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + break; + case 3: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_FC; + break; + case 4: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LS; + ch_map[3] = PCM_CHANNEL_RS; + break; + case 5: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_FC; + ch_map[3] = PCM_CHANNEL_LS; + ch_map[4] = PCM_CHANNEL_RS; + break; + case 6: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LFE; + ch_map[3] = PCM_CHANNEL_FC; + ch_map[4] = PCM_CHANNEL_LS; + ch_map[5] = PCM_CHANNEL_RS; + break; + case 8: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LFE; + ch_map[3] = PCM_CHANNEL_FC; + ch_map[4] = PCM_CHANNEL_LS; + ch_map[5] = PCM_CHANNEL_RS; + ch_map[6] = PCM_CHANNEL_LB; + ch_map[7] = PCM_CHANNEL_RB; + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6dsp_map_channels); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.h b/sound/soc/qcom/qdsp6/q6dsp-common.h new file mode 100644 index 000000000000..32386f4a6432 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-common.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __Q6DSP_COMMON_H__ +#define __Q6DSP_COMMON_H__ + +#include <linux/kernel.h> + +#define PCM_FORMAT_MAX_NUM_CHANNEL 8 +#define PCM_CHANNEL_NULL 0 + +#define PCM_CHANNEL_FL 1 /* Front left channel. */ +#define PCM_CHANNEL_FR 2 /* Front right channel. */ +#define PCM_CHANNEL_FC 3 /* Front center channel. */ +#define PCM_CHANNEL_LS 4 /* Left surround channel. */ +#define PCM_CHANNEL_RS 5 /* Right surround channel. */ +#define PCM_CHANNEL_LFE 6 /* Low frequency effect channel. */ +#define PCM_CHANNEL_CS 7 /* Center surround channel; Rear center ch */ +#define PCM_CHANNEL_LB 8 /* Left back channel; Rear left channel. */ +#define PCM_CHANNEL_RB 9 /* Right back channel; Rear right channel. */ +#define PCM_CHANNELS 10 /* Top surround channel. */ + +int q6dsp_map_channels(u8 ch_map[PCM_FORMAT_MAX_NUM_CHANNEL], int ch); + +#endif /* __Q6DSP_COMMON_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6dsp-errno.h b/sound/soc/qcom/qdsp6/q6dsp-errno.h new file mode 100644 index 000000000000..763c19bb4a13 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-errno.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __Q6DSP_ERR_NO_H__ +#define __Q6DSP_ERR_NO_H__ +#include <linux/kernel.h> + +/* Success. The operation completed with no errors. */ +#define ADSP_EOK 0x00000000 +/* General failure. */ +#define ADSP_EFAILED 0x00000001 +/* Bad operation parameter. */ +#define ADSP_EBADPARAM 0x00000002 +/* Unsupported routine or operation. */ +#define ADSP_EUNSUPPORTED 0x00000003 +/* Unsupported version. */ +#define ADSP_EVERSION 0x00000004 +/* Unexpected problem encountered. */ +#define ADSP_EUNEXPECTED 0x00000005 +/* Unhandled problem occurred. */ +#define ADSP_EPANIC 0x00000006 +/* Unable to allocate resource. */ +#define ADSP_ENORESOURCE 0x00000007 +/* Invalid handle. */ +#define ADSP_EHANDLE 0x00000008 +/* Operation is already processed. */ +#define ADSP_EALREADY 0x00000009 +/* Operation is not ready to be processed. */ +#define ADSP_ENOTREADY 0x0000000A +/* Operation is pending completion. */ +#define ADSP_EPENDING 0x0000000B +/* Operation could not be accepted or processed. */ +#define ADSP_EBUSY 0x0000000C +/* Operation aborted due to an error. */ +#define ADSP_EABORTED 0x0000000D +/* Operation preempted by a higher priority. */ +#define ADSP_EPREEMPTED 0x0000000E +/* Operation requests intervention to complete. */ +#define ADSP_ECONTINUE 0x0000000F +/* Operation requests immediate intervention to complete. */ +#define ADSP_EIMMEDIATE 0x00000010 +/* Operation is not implemented. */ +#define ADSP_ENOTIMPL 0x00000011 +/* Operation needs more data or resources. */ +#define ADSP_ENEEDMORE 0x00000012 +/* Operation does not have memory. */ +#define ADSP_ENOMEMORY 0x00000014 +/* Item does not exist. */ +#define ADSP_ENOTEXIST 0x00000015 +/* Max count for adsp error code sent to HLOS*/ + +struct q6dsp_err_code { + int lnx_err_code; + char *adsp_err_str; +}; + +static struct q6dsp_err_code q6dsp_err_codes[] = { + [ADSP_EFAILED] = { -ENOTRECOVERABLE, "ADSP_EFAILED"}, + [ADSP_EBADPARAM] = { -EINVAL, "ADSP_EBADPARAM"}, + [ADSP_EUNSUPPORTED] = { -ENOSYS, "ADSP_EUNSUPPORTED"}, + [ADSP_EVERSION] = { -ENOPROTOOPT, "ADSP_EVERSION"}, + [ADSP_EUNEXPECTED] = { -ENOTRECOVERABLE, "ADSP_EUNEXPECTED"}, + [ADSP_EPANIC] = { -ENOTRECOVERABLE, "ADSP_EPANIC"}, + [ADSP_ENORESOURCE] = { -ENOSPC, "ADSP_ENORESOURCE"}, + [ADSP_EHANDLE] = { -EBADR, "ADSP_EHANDLE"}, + [ADSP_EALREADY] = { -EALREADY, "ADSP_EALREADY"}, + [ADSP_ENOTREADY] = { -EPERM, "ADSP_ENOTREADY"}, + [ADSP_EPENDING] = { -EINPROGRESS, "ADSP_EPENDING"}, + [ADSP_EBUSY] = { -EBUSY, "ADSP_EBUSY"}, + [ADSP_EABORTED] = { -ECANCELED, "ADSP_EABORTED"}, + [ADSP_EPREEMPTED] = { -EAGAIN, "ADSP_EPREEMPTED"}, + [ADSP_ECONTINUE] = { -EAGAIN, "ADSP_ECONTINUE"}, + [ADSP_EIMMEDIATE] = { -EAGAIN, "ADSP_EIMMEDIATE"}, + [ADSP_ENOTIMPL] = { -EAGAIN, "ADSP_ENOTIMPL"}, + [ADSP_ENEEDMORE] = { -ENODATA, "ADSP_ENEEDMORE"}, + [ADSP_ENOMEMORY] = { -EINVAL, "ADSP_ENOMEMORY"}, + [ADSP_ENOTEXIST] = { -ENOENT, "ADSP_ENOTEXIST"}, +}; + +static inline int q6dsp_errno(u32 error) +{ + int ret = -EINVAL; + + if (error <= ARRAY_SIZE(q6dsp_err_codes)) + ret = q6dsp_err_codes[error].lnx_err_code; + + return ret; +} + +static inline char *q6dsp_strerror(u32 error) +{ + if (error <= ARRAY_SIZE(q6dsp_err_codes)) + return q6dsp_err_codes[error].adsp_err_str; + + return "ADSP_ERR_MAX"; +} + +#endif /*__Q6DSP_ERR_NO_H__ */
On Tue, Feb 13, 2018 at 04:58:15PM +0000, srinivas.kandagatla@linaro.org wrote:
+config SND_SOC_QDSP6_COMMON
- tristate
- default n
Ah, the other default n that had snuck in was actually in an earlier patch in the series! I'm fairly sure this has come up with earlier drivers you've submitted...
+int q6dsp_map_channels(u8 ch_map[PCM_FORMAT_MAX_NUM_CHANNEL], int ch) +{
- memset(ch_map, 0, PCM_FORMAT_MAX_NUM_CHANNEL);
- switch (ch) {
- case 1:
ch_map[0] = PCM_CHANNEL_FC;
break;
- case 2:
ch_map[0] = PCM_CHANNEL_FL;
ch_map[1] = PCM_CHANNEL_FR;
break;
That's some really funky indentation there...
+static inline int q6dsp_errno(u32 error) +{
- int ret = -EINVAL;
- if (error <= ARRAY_SIZE(q6dsp_err_codes))
ret = q6dsp_err_codes[error].lnx_err_code;
- return ret;
+}
Is the error handling such a hot path that we need to make the lookup functions (and the data table with the lookups) static inlines in a header or could we just move the functions and the data into a C file? Especially given the string lookups for the errors (which are really nice to have) it seems wasteful.
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- .../devicetree/bindings/sound/qcom,q6afe.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,q6afe.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,q6afe.txt b/Documentation/devicetree/bindings/sound/qcom,q6afe.txt new file mode 100644 index 000000000000..4b389124275c --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,q6afe.txt @@ -0,0 +1,38 @@ +Qualcomm Audio Front End (Q6AFE) binding + +AFE is one of the APR audio service on Q6DSP +Please refer to qcom,apr.txt for details of the coommon apr service bindings +used by the apr service device. + +- but must contain the following property: + +- compatible: + Usage: required + Value type: <stringlist> + Definition: must be "qcom,afe-v<MAJOR-NUMBER>.<MINOR-NUMBER>". + example "qcom,afe-v2.0" + +- qcom,apr-svc-id + Usage: required + Value type: <prop-encoded-array> + Definition: Must be 4 for Audio Front End Service. + +- qcom,apr-svc-name + Usage: required + Value type: <stringlist> + Definition: Must be "AFE" + +- #sound-dai-cells + Usage: required + Value type: <prop-encoded-array> + Definition: Must be 1 + + += EXAMPLE + +q6afe { + compatible = "qcom,q6afe"; + qcom,apr-svc-name = "AFE"; + qcom,apr-svc-id = <APR_SVC_AFE>; + #sound-dai-cells = <1>; +};
On Tue, Feb 13, 2018 at 04:58:16PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
Please submit patches using subject lines reflecting the style for the subsystem. This makes it easier for people to identify relevant patches. Look at what existing commits in the area you're changing are doing and make sure your subject lines visually resemble what they're doing.
+- but must contain the following property:
+- compatible: +- qcom,apr-svc-id +- qcom,apr-svc-name +- #sound-dai-cells
Property or properties? It's a bit surprising that both the ID and the name are mandatory, does the name actually get used for non-diagnostic reasons?
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to Q6AFE (Audio Front End) module on Q6DSP.
AFE module sits right at the other end of cpu where the codec/audio devices are connected.
AFE provides abstraced interfaces to both hardware and virtual devices. Each AFE tx/rx port can be configured to connect to one of the hardware devices like codec, hdmi, slimbus, i2s and so on. AFE services include starting, stopping, and if needed, any configurations of the ports.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- include/dt-bindings/sound/qcom,q6afe.h | 9 + sound/soc/qcom/Kconfig | 5 + sound/soc/qcom/Makefile | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6afe.c | 520 +++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 37 +++ 6 files changed, 577 insertions(+) create mode 100644 include/dt-bindings/sound/qcom,q6afe.h create mode 100644 sound/soc/qcom/qdsp6/q6afe.c create mode 100644 sound/soc/qcom/qdsp6/q6afe.h
diff --git a/include/dt-bindings/sound/qcom,q6afe.h b/include/dt-bindings/sound/qcom,q6afe.h new file mode 100644 index 000000000000..b4d82cccdc86 --- /dev/null +++ b/include/dt-bindings/sound/qcom,q6afe.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef __DT_BINDINGS_Q6_AFE_H__ +#define __DT_BINDINGS_Q6_AFE_H__ + +/* Audio Front End (AFE) Ports */ +#define AFE_PORT_HDMI_RX 8 + +#endif /* __DT_BINDINGS_Q6_AFE_H__ */ + diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index b01f347b427d..caeaf8b1b561 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -48,10 +48,15 @@ config SND_SOC_QDSP6_COMMON tristate default n
+config SND_SOC_QDSP6_AFE + tristate + default n + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" depends on QCOM_APR && HAS_DMA select SND_SOC_QDSP6_COMMON + select SND_SOC_QDSP6_AFE help To add support for MSM QDSP6 Soc Audio. This will enable sound soc platform specific diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index d5280355c24f..748f5e891dcf 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -13,6 +13,11 @@ obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o # Machine snd-soc-storm-objs := storm.o snd-soc-apq8016-sbc-objs := apq8016_sbc.o +snd-soc-msm8996-objs := apq8096.o
obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o +obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-msm8996.o + +#DSP lib +obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index accebdb49306..9ec951e0833b 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c new file mode 100644 index 000000000000..0a5af06bb50e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/uaccess.h> +#include <linux/wait.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <linux/soc/qcom/apr.h> +#include "q6dsp-errno.h" +#include "q6afe.h" + +/* AFE CMDs */ +#define AFE_PORT_CMD_DEVICE_START 0x000100E5 +#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6 +#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF +#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106 +#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210 +#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C + +/* Port IDs */ +#define AFE_API_VERSION_HDMI_CONFIG 0x1 +#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E +#define TIMEOUT_MS 1000 +#define AFE_CMD_RESP_AVAIL 0 +#define AFE_CMD_RESP_NONE 1 + +struct q6afe { + struct apr_device *apr; + struct device *dev; + int state; + int status; + + struct mutex lock; + struct list_head port_list; + spinlock_t port_list_lock; + struct list_head node; + void *dai_data; +}; + +struct afe_port_cmd_device_start { + struct apr_hdr hdr; + u16 port_id; + u16 reserved; +} __packed; + +struct afe_port_cmd_device_stop { + struct apr_hdr hdr; + u16 port_id; + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0.*/ +} __packed; + +struct afe_port_param_data_v2 { + u32 module_id; + u32 param_id; + u16 param_size; + u16 reserved; +} __packed; + +struct afe_port_cmd_set_param_v2 { + u16 port_id; + u16 payload_size; + u32 payload_address_lsw; + u32 payload_address_msw; + u32 mem_map_handle; +} __packed; + +struct afe_param_id_hdmi_multi_chan_audio_cfg { + u32 hdmi_cfg_minor_version; + u16 datatype; + u16 channel_allocation; + u32 sample_rate; + u16 bit_width; + u16 reserved; +} __packed; + +union afe_port_config { + struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; +} __packed; + +struct q6afe_port { + wait_queue_head_t wait; + union afe_port_config port_cfg; + int token; + int id; + int cfg_type; + struct q6afe *afe; + struct list_head node; +}; + +struct afe_audioif_config_command { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + union afe_port_config port; +} __packed; + +struct afe_port_map { + int port_id; + int token; + int is_rx; + int is_dig_pcm; +}; + +/* Port map of index vs real hw port ids */ +static struct afe_port_map port_maps[AFE_PORT_MAX] = { + [AFE_PORT_HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX, + AFE_PORT_HDMI_RX, 1, 1}, +}; + +static struct q6afe_port *afe_find_port(struct q6afe *afe, int token) +{ + struct q6afe_port *p = NULL; + + spin_lock(&afe->port_list_lock); + list_for_each_entry(p, &afe->port_list, node) + if (p->token == token) + break; + + spin_unlock(&afe->port_list_lock); + return p; +} + +static int afe_callback(struct apr_device *adev, + struct apr_client_message *data) +{ + struct q6afe *afe = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *res; + struct q6afe_port *port; + + if (!data->payload_size) + return 0; + + res = data->payload; + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (res->status) { + afe->status = res->status; + dev_err(afe->dev, "cmd = 0x%x returned error = 0x%x\n", + res->opcode, res->status); + } + + switch (res->opcode) { + case AFE_PORT_CMD_SET_PARAM_V2: + case AFE_PORT_CMD_DEVICE_STOP: + case AFE_PORT_CMD_DEVICE_START: + afe->state = AFE_CMD_RESP_AVAIL; + port = afe_find_port(afe, data->token); + if (port) + wake_up(&port->wait); + + break; + default: + dev_err(afe->dev, "Unknown cmd 0x%x\n", res->opcode); + break; + } + } + + return 0; +} +/** + * q6afe_get_port_id() - Get port id from a given port index + * + * @index: port index + * + * Return: Will be an negative on error or valid port_id on success + */ +int q6afe_get_port_id(int index) +{ + if (index < 0 || index > AFE_PORT_MAX) + return -EINVAL; + + return port_maps[index].port_id; +} +EXPORT_SYMBOL_GPL(q6afe_get_port_id); + +static int afe_apr_send_pkt(struct q6afe *afe, void *data, + wait_queue_head_t *wait) +{ + int ret; + + mutex_lock(&afe->lock); + afe->status = 0; + afe->state = AFE_CMD_RESP_NONE; + + ret = apr_send_pkt(afe->apr, data); + if (ret < 0) { + dev_err(afe->dev, "packet not transmitted\n"); + ret = -EINVAL; + goto err; + } + + ret = wait_event_timeout(*wait, (afe->state == AFE_CMD_RESP_AVAIL), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + ret = -ETIMEDOUT; + } else if (afe->status > 0) { + dev_err(afe->dev, "DSP returned error[%s]\n", + q6dsp_strerror(afe->status)); + ret = q6dsp_errno(afe->status); + } else { + ret = 0; + } + +err: + mutex_unlock(&afe->lock); + + return ret; +} + +static int afe_send_cmd_port_start(struct q6afe_port *port) +{ + u16 port_id = port->id; + struct afe_port_cmd_device_start start; + struct q6afe *afe = port->afe; + int ret; + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = port->token; + start.hdr.opcode = AFE_PORT_CMD_DEVICE_START; + start.port_id = port_id; + + ret = afe_apr_send_pkt(afe, &start, &port->wait); + if (ret) + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + + return ret; +} + +static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data, + int param_id, int psize) +{ + struct apr_hdr *hdr; + struct afe_port_cmd_set_param_v2 *param; + struct afe_port_param_data_v2 *pdata; + struct q6afe *afe = port->afe; + u16 port_id = port->id; + int ret; + + hdr = data; + param = data + sizeof(*hdr); + pdata = data + sizeof(*hdr) + sizeof(*param); + + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + hdr->pkt_size = sizeof(*hdr) + sizeof(*param) + + sizeof(*pdata) + psize; + hdr->src_port = 0; + hdr->dest_port = 0; + hdr->token = port->token; + hdr->opcode = AFE_PORT_CMD_SET_PARAM_V2; + param->port_id = port_id; + param->payload_size = sizeof(*pdata) + psize; + param->payload_address_lsw = 0x00; + param->payload_address_msw = 0x00; + param->mem_map_handle = 0x00; + pdata->module_id = AFE_MODULE_AUDIO_DEV_INTERFACE; + pdata->param_id = param_id; + pdata->param_size = psize; + + ret = afe_apr_send_pkt(afe, data, &port->wait); + if (ret) + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + + + return ret; +} + +static int afe_port_start(struct q6afe_port *port, + union afe_port_config *afe_config) +{ + struct afe_audioif_config_command config = {0,}; + struct q6afe *afe = port->afe; + int port_id = port->id; + int ret, param_id = port->cfg_type; + + config.port = *afe_config; + + ret = q6afe_port_set_param_v2(port, &config, param_id, + sizeof(*afe_config)); + if (ret) { + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + return ret; + } + return afe_send_cmd_port_start(port); +} + +/** + * q6afe_port_stop() - Stop a afe port + * + * @port: Instance of port to stop + * + * Return: Will be an negative on packet size on success. + */ +int q6afe_port_stop(struct q6afe_port *port) +{ + int port_id = port->id; + struct afe_port_cmd_device_stop stop; + struct q6afe *afe = port->afe; + int ret = 0; + int index = 0; + + port_id = port->id; + index = port->token; + if (index < 0 || index > AFE_PORT_MAX) { + dev_err(afe->dev, "AFE port index[%d] invalid!\n", index); + return -EINVAL; + } + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = index; + stop.hdr.opcode = AFE_PORT_CMD_DEVICE_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + ret = afe_apr_send_pkt(afe, &stop, &port->wait); + if (ret) + dev_err(afe->dev, "AFE close failed %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(q6afe_port_stop); + +/** + * q6afe_set_dai_data() - set dai private data + * + * @dev: Pointer to afe device. + * @data: dai private data + * + */ +void q6afe_set_dai_data(struct device *dev, void *data) +{ + struct q6afe *afe = dev_get_drvdata(dev); + + afe->dai_data = data; +} +EXPORT_SYMBOL_GPL(q6afe_set_dai_data); + +/** + * q6afe_get_dai_data() - get dai private data + * + * @dev: Pointer to afe device. + * + * Return: pointer to dai private data + */ +void *q6afe_get_dai_data(struct device *dev) +{ + struct q6afe *afe = dev_get_drvdata(dev); + + return afe->dai_data; +} +EXPORT_SYMBOL_GPL(q6afe_get_dai_data); + +/** + * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. + * + * @port: Instance of afe port + * @cfg: HDMI configuration for the afe port + * + */ +void q6afe_hdmi_port_prepare(struct q6afe_port *port, + struct q6afe_hdmi_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->hdmi_multi_ch.hdmi_cfg_minor_version = + AFE_API_VERSION_HDMI_CONFIG; + pcfg->hdmi_multi_ch.datatype = cfg->datatype; + pcfg->hdmi_multi_ch.channel_allocation = cfg->channel_allocation; + pcfg->hdmi_multi_ch.sample_rate = cfg->sample_rate; + pcfg->hdmi_multi_ch.bit_width = cfg->bit_width; +} +EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare); + +/** + * q6afe_port_start() - Start a afe port + * + * @port: Instance of port to start + * + * Return: Will be an negative on packet size on success. + */ +int q6afe_port_start(struct q6afe_port *port) +{ + return afe_port_start(port, &port->port_cfg); +} +EXPORT_SYMBOL_GPL(q6afe_port_start); + +/** + * q6afe_port_get_from_id() - Get port instance from a port id + * + * @dev: Pointer to afe child device. + * @id: port id + * + * Return: Will be an error pointer on error or a valid afe port + * on success. + */ +struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) +{ + int port_id; + struct q6afe *afe = dev_get_drvdata(dev); + struct q6afe_port *port; + int cfg_type; + + if (id < 0 || id > AFE_PORT_MAX) { + dev_err(dev, "AFE port token[%d] invalid!\n", id); + return ERR_PTR(-EINVAL); + } + + port_id = port_maps[id].port_id; + + switch (port_id) { + case AFE_PORT_ID_MULTICHAN_HDMI_RX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + default: + dev_err(dev, "Invalid port id 0x%x\n", port_id); + return ERR_PTR(-EINVAL); + } + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + init_waitqueue_head(&port->wait); + + port->token = id; + port->id = port_id; + port->afe = afe; + port->cfg_type = cfg_type; + + spin_lock(&afe->port_list_lock); + list_add_tail(&port->node, &afe->port_list); + spin_unlock(&afe->port_list_lock); + + return port; + +} +EXPORT_SYMBOL_GPL(q6afe_port_get_from_id); + +/** + * q6afe_port_put() - Release port reference + * + * @port: Instance of port to put + */ +void q6afe_port_put(struct q6afe_port *port) +{ + struct q6afe *afe = port->afe; + + spin_lock(&afe->port_list_lock); + list_del(&port->node); + spin_unlock(&afe->port_list_lock); +} +EXPORT_SYMBOL_GPL(q6afe_port_put); + +static int q6afev2_probe(struct apr_device *adev) +{ + struct q6afe *afe; + struct device *dev = &adev->dev; + + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + + afe->apr = adev; + mutex_init(&afe->lock); + afe->dev = dev; + INIT_LIST_HEAD(&afe->port_list); + spin_lock_init(&afe->port_list_lock); + + dev_set_drvdata(dev, afe); + + return q6afe_dai_dev_probe(dev); +} + +static int q6afev2_remove(struct apr_device *adev) +{ + return q6afe_dai_dev_remove(&adev->dev); +} + +static const struct of_device_id q6afe_device_id[] = { + { .compatible = "qcom,q6afe" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6afe_device_id); + +static struct apr_driver qcom_q6afe_driver = { + .probe = q6afev2_probe, + .remove = q6afev2_remove, + .callback = afe_callback, + .driver = { + .name = "qcom-q6afe", + .of_match_table = of_match_ptr(q6afe_device_id), + + }, +}; + +module_apr_driver(qcom_q6afe_driver); +MODULE_DESCRIPTION("Q6 Audio Front End"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h new file mode 100644 index 000000000000..43df524f01bb --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __Q6AFE_H__ +#define __Q6AFE_H__ + +#include <dt-bindings/sound/qcom,q6afe.h> + +#define AFE_PORT_MAX 9 + +#define MSM_AFE_PORT_TYPE_RX 0 +#define MSM_AFE_PORT_TYPE_TX 1 +#define AFE_MAX_PORTS AFE_PORT_MAX + +struct q6afe_hdmi_cfg { + u16 datatype; + u16 channel_allocation; + u32 sample_rate; + u16 bit_width; +}; + +struct q6afe_port_config { + struct q6afe_hdmi_cfg hdmi; +}; + +struct q6afe_port; +void q6afe_set_dai_data(struct device *dev, void *data); +void *q6afe_get_dai_data(struct device *dev); + +struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id); +int q6afe_port_start(struct q6afe_port *port); +int q6afe_port_stop(struct q6afe_port *port); +void q6afe_port_put(struct q6afe_port *port); +int q6afe_get_port_id(int index); +void q6afe_hdmi_port_prepare(struct q6afe_port *port, + struct q6afe_hdmi_cfg *cfg); + +#endif /* __Q6AFE_H__ */
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to Q6AFE (Audio Front End) module on Q6DSP.
AFE module sits right at the other end of cpu where the codec/audio devices are connected.
AFE provides abstraced interfaces to both hardware and virtual devices. Each AFE tx/rx port can be configured to connect to one of the hardware devices like codec, hdmi, slimbus, i2s and so on. AFE services include starting, stopping, and if needed, any configurations of the ports.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
include/dt-bindings/sound/qcom,q6afe.h | 9 + sound/soc/qcom/Kconfig | 5 + sound/soc/qcom/Makefile | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6afe.c | 520 +++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 37 +++ 6 files changed, 577 insertions(+) create mode 100644 include/dt-bindings/sound/qcom,q6afe.h create mode 100644 sound/soc/qcom/qdsp6/q6afe.c create mode 100644 sound/soc/qcom/qdsp6/q6afe.h
diff --git a/include/dt-bindings/sound/qcom,q6afe.h b/include/dt-bindings/sound/qcom,q6afe.h new file mode 100644 index 000000000000..b4d82cccdc86 --- /dev/null +++ b/include/dt-bindings/sound/qcom,q6afe.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef __DT_BINDINGS_Q6_AFE_H__ +#define __DT_BINDINGS_Q6_AFE_H__
+/* Audio Front End (AFE) Ports */ +#define AFE_PORT_HDMI_RX 8
+#endif /* __DT_BINDINGS_Q6_AFE_H__ */
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index b01f347b427d..caeaf8b1b561 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -48,10 +48,15 @@ config SND_SOC_QDSP6_COMMON tristate default n
+config SND_SOC_QDSP6_AFE
- tristate
- default n
- config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" depends on QCOM_APR && HAS_DMA select SND_SOC_QDSP6_COMMON
- select SND_SOC_QDSP6_AFE help To add support for MSM QDSP6 Soc Audio. This will enable sound soc platform specific
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index d5280355c24f..748f5e891dcf 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -13,6 +13,11 @@ obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o # Machine snd-soc-storm-objs := storm.o snd-soc-apq8016-sbc-objs := apq8016_sbc.o +snd-soc-msm8996-objs := apq8096.o
This needs to be moved out of this patch
obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o +obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-msm8996.o
+#DSP lib +obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index accebdb49306..9ec951e0833b 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c new file mode 100644 index 000000000000..0a5af06bb50e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (c) 2011-2017, The Linux Foundation
- Copyright (c) 2018, Linaro Limited
- */
+#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/uaccess.h> +#include <linux/wait.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <linux/soc/qcom/apr.h> +#include "q6dsp-errno.h" +#include "q6afe.h"
+/* AFE CMDs */ +#define AFE_PORT_CMD_DEVICE_START 0x000100E5 +#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6 +#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF +#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106 +#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210 +#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C
+/* Port IDs */ +#define AFE_API_VERSION_HDMI_CONFIG 0x1 +#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E +#define TIMEOUT_MS 1000 +#define AFE_CMD_RESP_AVAIL 0 +#define AFE_CMD_RESP_NONE 1
+struct q6afe {
- struct apr_device *apr;
- struct device *dev;
- int state;
- int status;
- struct mutex lock;
- struct list_head port_list;
- spinlock_t port_list_lock;
- struct list_head node;
- void *dai_data;
+};
+struct afe_port_cmd_device_start {
- struct apr_hdr hdr;
- u16 port_id;
- u16 reserved;
+} __packed;
+struct afe_port_cmd_device_stop {
- struct apr_hdr hdr;
- u16 port_id;
- u16 reserved;
+/* Reserved for 32-bit alignment. This field must be set to 0.*/ +} __packed;
+struct afe_port_param_data_v2 {
- u32 module_id;
- u32 param_id;
- u16 param_size;
- u16 reserved;
+} __packed;
+struct afe_port_cmd_set_param_v2 {
- u16 port_id;
- u16 payload_size;
- u32 payload_address_lsw;
- u32 payload_address_msw;
- u32 mem_map_handle;
+} __packed;
+struct afe_param_id_hdmi_multi_chan_audio_cfg {
- u32 hdmi_cfg_minor_version;
- u16 datatype;
- u16 channel_allocation;
- u32 sample_rate;
- u16 bit_width;
- u16 reserved;
+} __packed;
+union afe_port_config {
- struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch;
+} __packed;
+struct q6afe_port {
- wait_queue_head_t wait;
- union afe_port_config port_cfg;
- int token;
- int id;
- int cfg_type;
- struct q6afe *afe;
- struct list_head node;
+};
+struct afe_audioif_config_command {
- struct apr_hdr hdr;
- struct afe_port_cmd_set_param_v2 param;
- struct afe_port_param_data_v2 pdata;
- union afe_port_config port;
+} __packed;
+struct afe_port_map {
- int port_id;
- int token;
- int is_rx;
- int is_dig_pcm;
+};
+/* Port map of index vs real hw port ids */ +static struct afe_port_map port_maps[AFE_PORT_MAX] = {
- [AFE_PORT_HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX,
AFE_PORT_HDMI_RX, 1, 1},
+};
+static struct q6afe_port *afe_find_port(struct q6afe *afe, int token) +{
- struct q6afe_port *p = NULL;
- spin_lock(&afe->port_list_lock);
- list_for_each_entry(p, &afe->port_list, node)
if (p->token == token)
break;
- spin_unlock(&afe->port_list_lock);
- return p;
+}
+static int afe_callback(struct apr_device *adev,
struct apr_client_message *data)
+{
- struct q6afe *afe = dev_get_drvdata(&adev->dev);
- struct aprv2_ibasic_rsp_result_t *res;
- struct q6afe_port *port;
- if (!data->payload_size)
return 0;
- res = data->payload;
- if (data->opcode == APR_BASIC_RSP_RESULT) {
if (res->status) {
afe->status = res->status;
dev_err(afe->dev, "cmd = 0x%x returned error = 0x%x\n",
res->opcode, res->status);
}
switch (res->opcode) {
case AFE_PORT_CMD_SET_PARAM_V2:
case AFE_PORT_CMD_DEVICE_STOP:
case AFE_PORT_CMD_DEVICE_START:
afe->state = AFE_CMD_RESP_AVAIL;
port = afe_find_port(afe, data->token);
if (port)
wake_up(&port->wait);
break;
default:
dev_err(afe->dev, "Unknown cmd 0x%x\n", res->opcode);
break;
}
- }
- return 0;
+} +/**
- q6afe_get_port_id() - Get port id from a given port index
- @index: port index
- Return: Will be an negative on error or valid port_id on success
- */
+int q6afe_get_port_id(int index) +{
- if (index < 0 || index > AFE_PORT_MAX)
return -EINVAL;
- return port_maps[index].port_id;
+} +EXPORT_SYMBOL_GPL(q6afe_get_port_id);
+static int afe_apr_send_pkt(struct q6afe *afe, void *data,
wait_queue_head_t *wait)
+{
- int ret;
- mutex_lock(&afe->lock);
- afe->status = 0;
- afe->state = AFE_CMD_RESP_NONE;
- ret = apr_send_pkt(afe->apr, data);
- if (ret < 0) {
dev_err(afe->dev, "packet not transmitted\n");
ret = -EINVAL;
goto err;
- }
- ret = wait_event_timeout(*wait, (afe->state == AFE_CMD_RESP_AVAIL),
msecs_to_jiffies(TIMEOUT_MS));
- if (!ret) {
ret = -ETIMEDOUT;
- } else if (afe->status > 0) {
dev_err(afe->dev, "DSP returned error[%s]\n",
q6dsp_strerror(afe->status));
ret = q6dsp_errno(afe->status);
- } else {
ret = 0;
- }
+err:
- mutex_unlock(&afe->lock);
- return ret;
+}
+static int afe_send_cmd_port_start(struct q6afe_port *port) +{
- u16 port_id = port->id;
- struct afe_port_cmd_device_start start;
- struct q6afe *afe = port->afe;
- int ret;
- start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE),
APR_PKT_VER);
- start.hdr.pkt_size = sizeof(start);
- start.hdr.src_port = 0;
- start.hdr.dest_port = 0;
- start.hdr.token = port->token;
- start.hdr.opcode = AFE_PORT_CMD_DEVICE_START;
- start.port_id = port_id;
- ret = afe_apr_send_pkt(afe, &start, &port->wait);
- if (ret)
dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
port_id, ret);
- return ret;
+}
+static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data,
int param_id, int psize)
+{
- struct apr_hdr *hdr;
- struct afe_port_cmd_set_param_v2 *param;
- struct afe_port_param_data_v2 *pdata;
- struct q6afe *afe = port->afe;
- u16 port_id = port->id;
- int ret;
- hdr = data;
- param = data + sizeof(*hdr);
- pdata = data + sizeof(*hdr) + sizeof(*param);
- hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE),
APR_PKT_VER);
- hdr->pkt_size = sizeof(*hdr) + sizeof(*param) +
sizeof(*pdata) + psize;
- hdr->src_port = 0;
- hdr->dest_port = 0;
- hdr->token = port->token;
- hdr->opcode = AFE_PORT_CMD_SET_PARAM_V2;
- param->port_id = port_id;
- param->payload_size = sizeof(*pdata) + psize;
- param->payload_address_lsw = 0x00;
- param->payload_address_msw = 0x00;
- param->mem_map_handle = 0x00;
- pdata->module_id = AFE_MODULE_AUDIO_DEV_INTERFACE;
- pdata->param_id = param_id;
- pdata->param_size = psize;
- ret = afe_apr_send_pkt(afe, data, &port->wait);
- if (ret)
dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
port_id, ret);
- return ret;
+}
+static int afe_port_start(struct q6afe_port *port,
union afe_port_config *afe_config)
+{
- struct afe_audioif_config_command config = {0,};
- struct q6afe *afe = port->afe;
- int port_id = port->id;
- int ret, param_id = port->cfg_type;
- config.port = *afe_config;
- ret = q6afe_port_set_param_v2(port, &config, param_id,
sizeof(*afe_config));
- if (ret) {
dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
port_id, ret);
return ret;
- }
- return afe_send_cmd_port_start(port);
+}
+/**
- q6afe_port_stop() - Stop a afe port
- @port: Instance of port to stop
- Return: Will be an negative on packet size on success.
- */
+int q6afe_port_stop(struct q6afe_port *port) +{
- int port_id = port->id;
- struct afe_port_cmd_device_stop stop;
- struct q6afe *afe = port->afe;
- int ret = 0;
- int index = 0;
- port_id = port->id;
- index = port->token;
- if (index < 0 || index > AFE_PORT_MAX) {
dev_err(afe->dev, "AFE port index[%d] invalid!\n", index);
return -EINVAL;
- }
- stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE),
APR_PKT_VER);
- stop.hdr.pkt_size = sizeof(stop);
- stop.hdr.src_port = 0;
- stop.hdr.dest_port = 0;
- stop.hdr.token = index;
- stop.hdr.opcode = AFE_PORT_CMD_DEVICE_STOP;
- stop.port_id = port_id;
- stop.reserved = 0;
- ret = afe_apr_send_pkt(afe, &stop, &port->wait);
- if (ret)
dev_err(afe->dev, "AFE close failed %d\n", ret);
- return ret;
+} +EXPORT_SYMBOL_GPL(q6afe_port_stop);
+/**
- q6afe_set_dai_data() - set dai private data
- @dev: Pointer to afe device.
- @data: dai private data
- */
+void q6afe_set_dai_data(struct device *dev, void *data) +{
- struct q6afe *afe = dev_get_drvdata(dev);
- afe->dai_data = data;
+} +EXPORT_SYMBOL_GPL(q6afe_set_dai_data);
+/**
- q6afe_get_dai_data() - get dai private data
- @dev: Pointer to afe device.
- Return: pointer to dai private data
- */
+void *q6afe_get_dai_data(struct device *dev) +{
- struct q6afe *afe = dev_get_drvdata(dev);
- return afe->dai_data;
+} +EXPORT_SYMBOL_GPL(q6afe_get_dai_data);
+/**
- q6afe_hdmi_port_prepare() - Prepare hdmi afe port.
- @port: Instance of afe port
- @cfg: HDMI configuration for the afe port
- */
+void q6afe_hdmi_port_prepare(struct q6afe_port *port,
struct q6afe_hdmi_cfg *cfg)
+{
- union afe_port_config *pcfg = &port->port_cfg;
- pcfg->hdmi_multi_ch.hdmi_cfg_minor_version =
AFE_API_VERSION_HDMI_CONFIG;
- pcfg->hdmi_multi_ch.datatype = cfg->datatype;
- pcfg->hdmi_multi_ch.channel_allocation = cfg->channel_allocation;
- pcfg->hdmi_multi_ch.sample_rate = cfg->sample_rate;
- pcfg->hdmi_multi_ch.bit_width = cfg->bit_width;
+} +EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare);
+/**
- q6afe_port_start() - Start a afe port
- @port: Instance of port to start
- Return: Will be an negative on packet size on success.
- */
+int q6afe_port_start(struct q6afe_port *port) +{
- return afe_port_start(port, &port->port_cfg);
+} +EXPORT_SYMBOL_GPL(q6afe_port_start);
+/**
- q6afe_port_get_from_id() - Get port instance from a port id
- @dev: Pointer to afe child device.
- @id: port id
- Return: Will be an error pointer on error or a valid afe port
- on success.
- */
+struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) +{
- int port_id;
- struct q6afe *afe = dev_get_drvdata(dev);
- struct q6afe_port *port;
- int cfg_type;
- if (id < 0 || id > AFE_PORT_MAX) {
dev_err(dev, "AFE port token[%d] invalid!\n", id);
return ERR_PTR(-EINVAL);
- }
- port_id = port_maps[id].port_id;
- switch (port_id) {
- case AFE_PORT_ID_MULTICHAN_HDMI_RX:
cfg_type = AFE_PARAM_ID_HDMI_CONFIG;
break;
- default:
dev_err(dev, "Invalid port id 0x%x\n", port_id);
return ERR_PTR(-EINVAL);
- }
- port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
- if (!port)
return ERR_PTR(-ENOMEM);
- init_waitqueue_head(&port->wait);
- port->token = id;
- port->id = port_id;
- port->afe = afe;
- port->cfg_type = cfg_type;
- spin_lock(&afe->port_list_lock);
- list_add_tail(&port->node, &afe->port_list);
- spin_unlock(&afe->port_list_lock);
- return port;
+} +EXPORT_SYMBOL_GPL(q6afe_port_get_from_id);
+/**
- q6afe_port_put() - Release port reference
- @port: Instance of port to put
- */
+void q6afe_port_put(struct q6afe_port *port) +{
- struct q6afe *afe = port->afe;
- spin_lock(&afe->port_list_lock);
- list_del(&port->node);
- spin_unlock(&afe->port_list_lock);
+} +EXPORT_SYMBOL_GPL(q6afe_port_put);
+static int q6afev2_probe(struct apr_device *adev) +{
- struct q6afe *afe;
- struct device *dev = &adev->dev;
- afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
- if (!afe)
return -ENOMEM;
- afe->apr = adev;
- mutex_init(&afe->lock);
- afe->dev = dev;
- INIT_LIST_HEAD(&afe->port_list);
- spin_lock_init(&afe->port_list_lock);
- dev_set_drvdata(dev, afe);
- return q6afe_dai_dev_probe(dev);
+}
+static int q6afev2_remove(struct apr_device *adev) +{
- return q6afe_dai_dev_remove(&adev->dev);
+}
+static const struct of_device_id q6afe_device_id[] = {
- { .compatible = "qcom,q6afe" },
- {},
+}; +MODULE_DEVICE_TABLE(of, q6afe_device_id);
+static struct apr_driver qcom_q6afe_driver = {
- .probe = q6afev2_probe,
- .remove = q6afev2_remove,
- .callback = afe_callback,
- .driver = {
.name = "qcom-q6afe",
.of_match_table = of_match_ptr(q6afe_device_id),
- },
+};
+module_apr_driver(qcom_q6afe_driver); +MODULE_DESCRIPTION("Q6 Audio Front End"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h new file mode 100644 index 000000000000..43df524f01bb --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0
+#ifndef __Q6AFE_H__ +#define __Q6AFE_H__
+#include <dt-bindings/sound/qcom,q6afe.h>
+#define AFE_PORT_MAX 9
+#define MSM_AFE_PORT_TYPE_RX 0 +#define MSM_AFE_PORT_TYPE_TX 1 +#define AFE_MAX_PORTS AFE_PORT_MAX
+struct q6afe_hdmi_cfg {
- u16 datatype;
- u16 channel_allocation;
- u32 sample_rate;
- u16 bit_width;
+};
+struct q6afe_port_config {
- struct q6afe_hdmi_cfg hdmi;
+};
+struct q6afe_port; +void q6afe_set_dai_data(struct device *dev, void *data); +void *q6afe_get_dai_data(struct device *dev);
+struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id); +int q6afe_port_start(struct q6afe_port *port); +int q6afe_port_stop(struct q6afe_port *port); +void q6afe_port_put(struct q6afe_port *port); +int q6afe_get_port_id(int index); +void q6afe_hdmi_port_prepare(struct q6afe_port *port,
struct q6afe_hdmi_cfg *cfg);
+#endif /* __Q6AFE_H__ */
Thanks for your review comments
On 19/02/18 10:30, Rohit Kumar wrote:
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile index d5280355c24f..748f5e891dcf 100644 --- a/sound/soc/qcom/Makefile +++ b/sound/soc/qcom/Makefile @@ -13,6 +13,11 @@ obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o # Machine snd-soc-storm-objs := storm.o snd-soc-apq8016-sbc-objs := apq8016_sbc.o +snd-soc-msm8996-objs := apq8096.o
This needs to be moved out of this patch
Will fix it in next version.
obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o +obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-msm8996.o
+#DSP lib
On Mon, Feb 19, 2018 at 04:00:32PM +0530, Rohit Kumar wrote:
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to Q6AFE (Audio Front End) module on Q6DSP.
AFE module sits right at the other end of cpu where the codec/audio devices are connected.
Please delete unneeded context from mails when replying. Doing this makes it much easier to find your reply in the message, helping ensure it won't be missed by people scrolling through the irrelevant quoted material.
On Tue, Feb 13, 2018 at 04:58:17PM +0000, srinivas.kandagatla@linaro.org wrote:
+// SPDX-License-Identifier: GPL-2.0 +#ifndef __DT_BINDINGS_Q6_AFE_H__ +#define __DT_BINDINGS_Q6_AFE_H__
+/* Audio Front End (AFE) Ports */ +#define AFE_PORT_HDMI_RX 8
+#endif /* __DT_BINDINGS_Q6_AFE_H__ */
Please use a C++ comment here as well for consistency, it looks more intentional.
+config SND_SOC_QDSP6_AFE
- tristate
- default n
n is the default anyway, no need to specify it (I know some uses already slipped through here but it'd be better to fix those).
index 000000000000..0a5af06bb50e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (c) 2011-2017, The Linux Foundation
- Copyright (c) 2018, Linaro Limited
- */
Same here with the comment, just make the whole comment a C++ comment rather than having one comment using both styles. Similar things apply elsewhere.
+/* Port map of index vs real hw port ids */ +static struct afe_port_map port_maps[AFE_PORT_MAX] = {
- [AFE_PORT_HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX,
AFE_PORT_HDMI_RX, 1, 1},
+};
Is this not device specific in any way? It looks likely to be.
+static struct q6afe_port *afe_find_port(struct q6afe *afe, int token) +{
- struct q6afe_port *p = NULL;
- spin_lock(&afe->port_list_lock);
- list_for_each_entry(p, &afe->port_list, node)
if (p->token == token)
break;
- spin_unlock(&afe->port_list_lock);
Why do we need to lock the port list, what are we protecting it against? We don't write here which suggests either there's some kind of race condition in this lookup or the lock is not doing anything.
+static int afe_callback(struct apr_device *adev,
struct apr_client_message *data)
+{
- struct q6afe *afe = dev_get_drvdata(&adev->dev);
- struct aprv2_ibasic_rsp_result_t *res;
- struct q6afe_port *port;
- if (!data->payload_size)
return 0;
- res = data->payload;
- if (data->opcode == APR_BASIC_RSP_RESULT) {
if (res->status) {
Shouldn't we use a switch statement here in case we want to handle something else?
switch (res->opcode) {
case AFE_PORT_CMD_SET_PARAM_V2:
case AFE_PORT_CMD_DEVICE_STOP:
case AFE_PORT_CMD_DEVICE_START:
afe->state = AFE_CMD_RESP_AVAIL;
port = afe_find_port(afe, data->token);
if (port)
wake_up(&port->wait);
No locking here? It seems a bit odd that the AFE global state is being set to say there's a response available but we wake a specific port.
- ret = apr_send_pkt(afe->apr, data);
- if (ret < 0) {
dev_err(afe->dev, "packet not transmitted\n");
ret = -EINVAL;
goto err;
Why squash the error code here? We don't even print it.
- }
- ret = wait_event_timeout(*wait, (afe->state == AFE_CMD_RESP_AVAIL),
msecs_to_jiffies(TIMEOUT_MS));
- if (!ret) {
ret = -ETIMEDOUT;
- } else if (afe->status > 0) {
dev_err(afe->dev, "DSP returned error[%s]\n",
q6dsp_strerror(afe->status));
ret = q6dsp_errno(afe->status);
If we time out here and the DSP delivers a response later it looks like we'll get data corruption - the DSP will happily deliver the response into shared state.
+static int afe_port_start(struct q6afe_port *port,
union afe_port_config *afe_config)
+{
- struct afe_audioif_config_command config = {0,};
- struct q6afe *afe = port->afe;
- int port_id = port->id;
- int ret, param_id = port->cfg_type;
- config.port = *afe_config;
- ret = q6afe_port_set_param_v2(port, &config, param_id,
sizeof(*afe_config));
- if (ret) {
dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
port_id, ret);
return ret;
- }
- return afe_send_cmd_port_start(port);
Why not just inline this function here? It appears to have only this user.
- int index = 0;
We set index to 0...
- port_id = port->id;
- index = port->token;
...the unconditionally overwrite it?
+/**
- q6afe_port_start() - Start a afe port
- @port: Instance of port to start
- Return: Will be an negative on packet size on success.
- */
+int q6afe_port_start(struct q6afe_port *port) +{
- return afe_port_start(port, &port->port_cfg);
+} +EXPORT_SYMBOL_GPL(q6afe_port_start);
This is the third level of wrapper for the port start command in this file. Do we *really* need all these wrappers?
+struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) +{
- port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
- if (!port)
return ERR_PTR(-ENOMEM);
The _port_get_ function is allocating data but...
+/**
- q6afe_port_put() - Release port reference
- @port: Instance of port to put
- */
+void q6afe_port_put(struct q6afe_port *port) +{
- struct q6afe *afe = port->afe;
- spin_lock(&afe->port_list_lock);
- list_del(&port->node);
- spin_unlock(&afe->port_list_lock);
+} +EXPORT_SYMBOL_GPL(q6afe_port_put);
...the _port_put() function isn't freeing it. That seems leaky. I'm also a bit worried about this being a spinlock that we're using for allocation, and not even spin_lock_irqsave(). Presumably this is protecting against interrupts otherwise we'd not need a spinlock but for that to work we'd need the _irqsave()?
Thanks for the review comments,
On 01/03/18 20:59, Mark Brown wrote:
On Tue, Feb 13, 2018 at 04:58:17PM +0000, srinivas.kandagatla@linaro.org wrote:
+// SPDX-License-Identifier: GPL-2.0 +#ifndef __DT_BINDINGS_Q6_AFE_H__ +#define __DT_BINDINGS_Q6_AFE_H__
+/* Audio Front End (AFE) Ports */ +#define AFE_PORT_HDMI_RX 8
+#endif /* __DT_BINDINGS_Q6_AFE_H__ */
Please use a C++ comment here as well for consistency, it looks more intentional.
Yep, I will fix such occurrences in next version.
+config SND_SOC_QDSP6_AFE
- tristate
- default n
n is the default anyway, no need to specify it (I know some uses already slipped through here but it'd be better to fix those).
index 000000000000..0a5af06bb50e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (c) 2011-2017, The Linux Foundation
- Copyright (c) 2018, Linaro Limited
- */
Yep, I will fix this, I think I picked this up variant while this was still being discussed. I will fix all such occurrences.
Same here with the comment, just make the whole comment a C++ comment rather than having one comment using both styles. Similar things apply elsewhere.
+/* Port map of index vs real hw port ids */ +static struct afe_port_map port_maps[AFE_PORT_MAX] = {
- [AFE_PORT_HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX,
AFE_PORT_HDMI_RX, 1, 1},
+};
Is this not device specific in any way? It looks likely to be.
It is specific to Audio firmware build. AFAIK, DSP port IDs are consistent across a given range of AVS firmware builds. So far I have seen them not change in any of the B family SoCs. However on older A family SOCs these are very different numbers. Which is where ADSP version info would help select correct map.
+static struct q6afe_port *afe_find_port(struct q6afe *afe, int token) +{
- struct q6afe_port *p = NULL;
- spin_lock(&afe->port_list_lock);
- list_for_each_entry(p, &afe->port_list, node)
if (p->token == token)
break;
- spin_unlock(&afe->port_list_lock);
Why do we need to lock the port list, what are we protecting it against?
This is just to protect the list from anyone deleting this.
Its very rare but the use case could be somelike the adsp is up and we are in the middle of finding a port and then adsp went down or crashed we would delete an entry in the list.
We don't write here which suggests either there's some kind of race condition in this lookup or the lock is not doing anything.
+static int afe_callback(struct apr_device *adev,
struct apr_client_message *data)
+{
- struct q6afe *afe = dev_get_drvdata(&adev->dev);
- struct aprv2_ibasic_rsp_result_t *res;
- struct q6afe_port *port;
- if (!data->payload_size)
return 0;
- res = data->payload;
- if (data->opcode == APR_BASIC_RSP_RESULT) {
if (res->status) {
Shouldn't we use a switch statement here in case we want to handle something else?
Yep, I will fix this.
switch (res->opcode) {
case AFE_PORT_CMD_SET_PARAM_V2:
case AFE_PORT_CMD_DEVICE_STOP:
case AFE_PORT_CMD_DEVICE_START:
afe->state = AFE_CMD_RESP_AVAIL;
port = afe_find_port(afe, data->token);
if (port)
wake_up(&port->wait);
No locking here? It seems a bit odd that the AFE global state is being set to say there's a response available but we wake a specific port.
This callback is invoked in interrupt context and sends are serialized at afe level.
Yep, It does not make sense to have a global state, I will fix this by making this state specific to port.
- ret = apr_send_pkt(afe->apr, data);
- if (ret < 0) {
dev_err(afe->dev, "packet not transmitted\n");
ret = -EINVAL;
goto err;
Why squash the error code here? We don't even print it.
Will fix this in next version.
- }
- ret = wait_event_timeout(*wait, (afe->state == AFE_CMD_RESP_AVAIL),
msecs_to_jiffies(TIMEOUT_MS));
- if (!ret) {
ret = -ETIMEDOUT;
- } else if (afe->status > 0) {
dev_err(afe->dev, "DSP returned error[%s]\n",
q6dsp_strerror(afe->status));
ret = q6dsp_errno(afe->status);
If we time out here and the DSP delivers a response later it looks like we'll get data corruption - the DSP will happily deliver the response into shared state.
If I make this state specific to port, we could handle this case much cleanly.
+static int afe_port_start(struct q6afe_port *port,
union afe_port_config *afe_config)
+{
- struct afe_audioif_config_command config = {0,};
- struct q6afe *afe = port->afe;
- int port_id = port->id;
- int ret, param_id = port->cfg_type;
- config.port = *afe_config;
- ret = q6afe_port_set_param_v2(port, &config, param_id,
sizeof(*afe_config));
- if (ret) {
dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
port_id, ret);
return ret;
- }
- return afe_send_cmd_port_start(port);
Why not just inline this function here? It appears to have only this user.
Will do that.
- int index = 0;
We set index to 0...
does not make sense, will remove this initialization.
- port_id = port->id;
- index = port->token;
...the unconditionally overwrite it?
+/**
- q6afe_port_start() - Start a afe port
- @port: Instance of port to start
- Return: Will be an negative on packet size on success.
- */
+int q6afe_port_start(struct q6afe_port *port) +{
- return afe_port_start(port, &port->port_cfg);
+} +EXPORT_SYMBOL_GPL(q6afe_port_start);
This is the third level of wrapper for the port start command in this file. Do we *really* need all these wrappers?
Intention here is that we have plans to support different version of ADSP, on A family this command is different, so having this wrapper would help tackle this use-case.
+struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) +{
- port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
- if (!port)
return ERR_PTR(-ENOMEM);
The _port_get_ function is allocating data but...
+/**
- q6afe_port_put() - Release port reference
- @port: Instance of port to put
- */
+void q6afe_port_put(struct q6afe_port *port) +{
- struct q6afe *afe = port->afe;
- spin_lock(&afe->port_list_lock);
- list_del(&port->node);
- spin_unlock(&afe->port_list_lock);
+} +EXPORT_SYMBOL_GPL(q6afe_port_put);
...the _port_put() function isn't freeing it. That seems leaky. I'm
Yes, I can probably free it here instead of depending on device core to do that.
also a bit worried about this being a spinlock that we're using for allocation, and not even spin_lock_irqsave(). Presumably this is protecting against interrupts otherwise we'd not need a spinlock but for that to work we'd need the _irqsave()?
Yes, I need to use _irqsave/restore variant here, Will fix that in next version,
Thanks, Srini
On Fri, Mar 02, 2018 at 01:13:17PM +0000, Srinivas Kandagatla wrote:
On 01/03/18 20:59, Mark Brown wrote:
On Tue, Feb 13, 2018 at 04:58:17PM +0000, srinivas.kandagatla@linaro.org wrote:
+static struct afe_port_map port_maps[AFE_PORT_MAX] = {
- [AFE_PORT_HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX,
AFE_PORT_HDMI_RX, 1, 1},
+};
Is this not device specific in any way? It looks likely to be.
It is specific to Audio firmware build. AFAIK, DSP port IDs are consistent across a given range of AVS firmware builds. So far I have seen them not change in any of the B family SoCs. However on older A family SOCs these are very different numbers. Which is where ADSP version info would help select correct map.
Can we have some documentation of this in the code please?
+static struct q6afe_port *afe_find_port(struct q6afe *afe, int token) +{
- struct q6afe_port *p = NULL;
- spin_lock(&afe->port_list_lock);
- list_for_each_entry(p, &afe->port_list, node)
if (p->token == token)
break;
- spin_unlock(&afe->port_list_lock);
Why do we need to lock the port list, what are we protecting it against?
This is just to protect the list from anyone deleting this.
Its very rare but the use case could be somelike the adsp is up and we are in the middle of finding a port and then adsp went down or crashed we would delete an entry in the list.
If that's what we're protecting against then this also needs to do something to ensure that the port we looked up doesn't get deallocated before or while we look at it.
+int q6afe_port_start(struct q6afe_port *port) +{
- return afe_port_start(port, &port->port_cfg);
+} +EXPORT_SYMBOL_GPL(q6afe_port_start);
This is the third level of wrapper for the port start command in this file. Do we *really* need all these wrappers?
Intention here is that we have plans to support different version of ADSP, on A family this command is different, so having this wrapper would help tackle this use-case.
Why not just take out the level of wrapper for now then add it in when there's actually an abstraction in there? The code might end up looking different anyway.
Thanks for the review comments,
On 02/03/18 17:54, Mark Brown wrote:
On Fri, Mar 02, 2018 at 01:13:17PM +0000, Srinivas Kandagatla wrote:
On 01/03/18 20:59, Mark Brown wrote:
On Tue, Feb 13, 2018 at 04:58:17PM +0000, srinivas.kandagatla@linaro.org wrote:
+static struct afe_port_map port_maps[AFE_PORT_MAX] = {
- [AFE_PORT_HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX,
AFE_PORT_HDMI_RX, 1, 1},
+};
Is this not device specific in any way? It looks likely to be.
It is specific to Audio firmware build. AFAIK, DSP port IDs are consistent across a given range of AVS firmware builds. So far I have seen them not change in any of the B family SoCs. However on older A family SOCs these are very different numbers. Which is where ADSP version info would help select correct map.
Can we have some documentation of this in the code please?
Sure, I will add documentation in next version.
+static struct q6afe_port *afe_find_port(struct q6afe *afe, int token) +{
- struct q6afe_port *p = NULL;
- spin_lock(&afe->port_list_lock);
- list_for_each_entry(p, &afe->port_list, node)
if (p->token == token)
break;
- spin_unlock(&afe->port_list_lock);
Why do we need to lock the port list, what are we protecting it against?
This is just to protect the list from anyone deleting this.
Its very rare but the use case could be somelike the adsp is up and we are in the middle of finding a port and then adsp went down or crashed we would delete an entry in the list.
If that's what we're protecting against then this also needs to do something to ensure that the port we looked up doesn't get deallocated before or while we look at it.
Yes, I will take closer look at all possible paths before sending next version.
+int q6afe_port_start(struct q6afe_port *port) +{
- return afe_port_start(port, &port->port_cfg);
+} +EXPORT_SYMBOL_GPL(q6afe_port_start);
This is the third level of wrapper for the port start command in this file. Do we *really* need all these wrappers?
Intention here is that we have plans to support different version of ADSP, on A family this command is different, so having this wrapper would help tackle this use-case.
Why not just take out the level of wrapper for now then add it in when there's actually an abstraction in there? The code might end up looking different anyway.
Okay, I can do that, will remove extra abstraction layer.
thanks, srini
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- .../devicetree/bindings/sound/qcom,q6adm.txt | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,q6adm.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,q6adm.txt b/Documentation/devicetree/bindings/sound/qcom,q6adm.txt new file mode 100644 index 000000000000..e493dccfe116 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,q6adm.txt @@ -0,0 +1,31 @@ +Qualcomm Audio Device Manager (Q6ADM) binding + +Q6ADM is one of the APR audio service on Q6DSP. +Please refer to qcom,apr.txt for details of the coommon apr service bindings +used by the apr service device. + +- but must contain the following property: + +- compatible: + Usage: required + Value type: <stringlist> + Definition: must be "qcom,adm-v<MAJOR-NUMBER>.<MINOR-NUMBER>". + example "qcom,adm-v2.0" + +- qcom,apr-svc-id + Usage: required + Value type: <prop-encoded-array> + Definition: Must be 8 for Audio Device Manager Service. + +- qcom,apr-svc-name + Usage: required + Value type: <stringlist> + Definition: Must be "ADM" + += EXAMPLE + +q6adm: q6adm { + compatible = "qcom,q6adm-v2.0"; + qcom,apr-svc-name = "ADM"; + qcom,apr-svc-id = <APR_SVC_ADM>; +};
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to Q6ADM (Audio Device Manager) module in q6dsp. ADM performs routing between audio streams and AFE ports. It does Rate matching for streams going to devices driven by different clocks, it handles volume ramping, Mixing with channel and bit-width. ADM creates and destroys dynamic COPP services for device-related audio processing as needed.
This patch adds basic support to ADM.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6adm.c | 634 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6adm.h | 26 ++ 4 files changed, 666 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6adm.c create mode 100644 sound/soc/qcom/qdsp6/q6adm.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index caeaf8b1b561..bf04e1301608 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -52,11 +52,16 @@ config SND_SOC_QDSP6_AFE tristate default n
+config SND_SOC_QDSP6_ADM + tristate + default n + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" depends on QCOM_APR && HAS_DMA select SND_SOC_QDSP6_COMMON select SND_SOC_QDSP6_AFE + select SND_SOC_QDSP6_ADM help To add support for MSM QDSP6 Soc Audio. This will enable sound soc platform specific diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 9ec951e0833b..930944425051 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o +obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c new file mode 100644 index 000000000000..e9c65e05882b --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6adm.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/jiffies.h> +#include <linux/of.h> +#include <linux/wait.h> +#include <linux/soc/qcom/apr.h> +#include <linux/platform_device.h> +#include <sound/asound.h> +#include "q6adm.h" +#include "q6afe.h" +#include "q6dsp-errno.h" +#include "q6dsp-common.h" + +#define ADM_CMD_DEVICE_OPEN_V5 0x00010326 +#define ADM_CMDRSP_DEVICE_OPEN_V5 0x00010329 +#define ADM_CMD_DEVICE_CLOSE_V5 0x00010327 +#define ADM_CMD_MATRIX_MAP_ROUTINGS_V5 0x00010325 + +#define TIMEOUT_MS 1000 +#define RESET_COPP_ID 99 +#define INVALID_COPP_ID 0xFF +/* Definition for a legacy device session. */ +#define ADM_LEGACY_DEVICE_SESSION 0 +#define ADM_MATRIX_ID_AUDIO_RX 0 + +struct copp { + int afe_port; + int copp_idx; + int id; + int refcnt; + int topology; + int mode; + int stat; + int rate; + int bit_width; + int channels; + int app_type; + int acdb_id; + struct mutex lock; + wait_queue_head_t wait; + struct list_head node; + struct q6adm *adm; +}; + +struct q6adm { + struct apr_device *apr; + struct device *dev; + unsigned long copp_bitmap[AFE_MAX_PORTS]; + struct list_head copps_list; + spinlock_t copps_list_lock; + int matrix_map_stat; + struct mutex lock; + wait_queue_head_t matrix_map_wait; + void *routing_data; +}; + +struct adm_cmd_device_open_v5 { + struct apr_hdr hdr; + u16 flags; + u16 mode_of_operation; + u16 endpoint_id_1; + u16 endpoint_id_2; + u32 topology_id; + u16 dev_num_channel; + u16 bit_width; + u32 sample_rate; + u8 dev_channel_mapping[8]; +} __packed; + +struct adm_cmd_matrix_map_routings_v5 { + struct apr_hdr hdr; + u32 matrix_id; + u32 num_sessions; +} __packed; + +struct adm_session_map_node_v5 { + u16 session_id; + u16 num_copps; +} __packed; + +static struct copp *adm_find_copp(struct q6adm *adm, int port_idx, + int copp_idx) +{ + struct copp *c; + + spin_lock(&adm->copps_list_lock); + list_for_each_entry(c, &adm->copps_list, node) { + if ((port_idx == c->afe_port) && (copp_idx == c->copp_idx)) { + spin_unlock(&adm->copps_list_lock); + return c; + } + } + + spin_unlock(&adm->copps_list_lock); + return NULL; + +} + +static int adm_callback(struct apr_device *adev, + struct apr_client_message *data) +{ + struct aprv2_ibasic_rsp_result_t *result = data->payload; + int port_idx, copp_idx; + struct copp *copp; + struct q6adm *adm = dev_get_drvdata(&adev->dev); + + if (!data->payload_size) + return 0; + + copp_idx = (data->token) & 0XFF; + port_idx = ((data->token) >> 16) & 0xFF; + if (port_idx < 0 || port_idx >= AFE_MAX_PORTS) { + dev_err(&adev->dev, "Invalid port idx %d token %d\n", + port_idx, data->token); + return 0; + } + if (copp_idx < 0 || copp_idx >= MAX_COPPS_PER_PORT) { + dev_err(&adev->dev, "Invalid copp idx %d token %d\n", + copp_idx, data->token); + return 0; + } + + switch (data->opcode) { + case APR_BASIC_RSP_RESULT: { + if (result->status != 0) { + dev_err(&adev->dev, "cmd = 0x%x return error = 0x%x\n", + result->opcode, result->status); + } + switch (result->opcode) { + case ADM_CMD_DEVICE_OPEN_V5: + case ADM_CMD_DEVICE_CLOSE_V5: + copp = adm_find_copp(adm, port_idx, copp_idx); + if (IS_ERR_OR_NULL(copp)) + return 0; + + copp->stat = result->status; + wake_up(&copp->wait); + break; + case ADM_CMD_MATRIX_MAP_ROUTINGS_V5: + adm->matrix_map_stat = result->status; + wake_up(&adm->matrix_map_wait); + break; + + default: + dev_err(&adev->dev, "Unknown Cmd: 0x%x\n", + result->opcode); + break; + } + return 0; + } + case ADM_CMDRSP_DEVICE_OPEN_V5: { + struct adm_cmd_rsp_device_open_v5 { + u32 status; + u16 copp_id; + u16 reserved; + } __packed * open = data->payload; + + open = data->payload; + copp = adm_find_copp(adm, port_idx, copp_idx); + if (IS_ERR_OR_NULL(copp)) + return 0; + + if (open->copp_id == INVALID_COPP_ID) { + dev_err(&adev->dev, "Invalid coppid rxed %d\n", + open->copp_id); + copp->stat = ADSP_EBADPARAM; + wake_up(&copp->wait); + break; + } + copp->stat = result->opcode; + copp->id = open->copp_id; + wake_up(&copp->wait); + } + break; + default: + dev_err(&adev->dev, "Unknown cmd:0x%x\n", + data->opcode); + break; + } + + return 0; +} + +static struct copp *adm_alloc_copp(struct q6adm *adm, int port_idx) +{ + struct copp *c; + int idx; + + idx = find_first_zero_bit(&adm->copp_bitmap[port_idx], + MAX_COPPS_PER_PORT); + + if (idx > MAX_COPPS_PER_PORT) + return ERR_PTR(-EBUSY); + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + + set_bit(idx, &adm->copp_bitmap[port_idx]); + c->copp_idx = idx; + c->afe_port = port_idx; + c->adm = adm; + + mutex_init(&c->lock); + init_waitqueue_head(&c->wait); + + spin_lock(&adm->copps_list_lock); + list_add_tail(&c->node, &adm->copps_list); + spin_unlock(&adm->copps_list_lock); + + return c; +} + +static void adm_free_copp(struct q6adm *adm, struct copp *c, int port_idx) +{ + clear_bit(c->copp_idx, &adm->copp_bitmap[port_idx]); + spin_lock(&adm->copps_list_lock); + list_del(&c->node); + spin_unlock(&adm->copps_list_lock); + kfree(c); +} + +static struct copp *adm_find_matching_copp(struct q6adm *adm, + int port_id, int topology, + int mode, int rate, int channel_mode, + int bit_width, int app_type) +{ + struct copp *c; + + spin_lock(&adm->copps_list_lock); + + list_for_each_entry(c, &adm->copps_list, node) { + if ((port_id == c->afe_port) && (topology == c->topology) && + (mode == c->mode) && (rate == c->rate) && + (bit_width == c->bit_width) && (app_type == c->app_type)) { + spin_unlock(&adm->copps_list_lock); + return c; + } + } + spin_unlock(&adm->copps_list_lock); + + c = adm_alloc_copp(adm, port_id); + if (IS_ERR_OR_NULL(c)) + return ERR_CAST(c); + + mutex_lock(&c->lock); + c->refcnt = 0; + c->topology = topology; + c->mode = mode; + c->rate = rate; + c->channels = channel_mode; + c->bit_width = bit_width; + c->app_type = app_type; + mutex_unlock(&c->lock); + + return c; + +} + +static int q6adm_apr_send_copp_pkt(struct q6adm *adm, struct copp *copp, + void *data) +{ + struct device *dev = adm->dev; + int ret; + + mutex_lock(&copp->lock); + copp->stat = -1; + ret = apr_send_pkt(adm->apr, data); + if (ret < 0) { + dev_err(dev, "Failed to send APR packet\n"); + ret = -EINVAL; + goto err; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(copp->wait, copp->stat >= 0, + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + dev_err(dev, "ADM copp cmd timedout\n"); + ret = -EINVAL; + } else if (copp->stat > 0) { + dev_err(dev, "DSP returned error[%s]\n", + q6dsp_strerror(copp->stat)); + ret = q6dsp_errno(copp->stat); + } + +err: + mutex_unlock(&copp->lock); + return ret; +} + +static int q6adm_device_open(struct q6adm *adm, struct copp *copp, int port_id, + int path, int topology, int channel_mode, + int bit_width, int rate) +{ + struct adm_cmd_device_open_v5 open = {0,}; + int afe_port = q6afe_get_port_id(port_id); + int ret; + + open.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + open.hdr.pkt_size = sizeof(open); + open.hdr.src_svc = APR_SVC_ADM; + open.hdr.src_domain = APR_DOMAIN_APPS; + open.hdr.src_port = afe_port; + open.hdr.dest_svc = APR_SVC_ADM; + open.hdr.dest_domain = APR_DOMAIN_ADSP; + open.hdr.dest_port = afe_port; + open.hdr.token = port_id << 16 | copp->copp_idx; + open.hdr.opcode = ADM_CMD_DEVICE_OPEN_V5; + open.flags = ADM_LEGACY_DEVICE_SESSION; + open.mode_of_operation = path; + open.endpoint_id_1 = afe_port; + open.topology_id = topology; + open.dev_num_channel = channel_mode & 0x00FF; + open.bit_width = bit_width; + open.sample_rate = rate; + + ret = q6dsp_map_channels(&open.dev_channel_mapping[0], + channel_mode); + if (ret) + return ret; + + return q6adm_apr_send_copp_pkt(adm, copp, &open); +} + +/** + * q6adm_open() - open adm and grab a free copp + * + * @dev: Pointer to adm child device. + * @port_id: port id + * @path: playback or capture path. + * @rate: rate at which copp is required. + * @channel_mode: channel mode + * @topology: adm topology id + * @perf_mode: performace mode. + * @bit_width: audio sample bit width + * @app_type: Application type. + * @acdb_id: ACDB id + * + * Return: Will be an negative on error or a valid copp index on success. + */ +int q6adm_open(struct device *dev, int port_id, int path, int rate, + int channel_mode, int topology, int perf_mode, + uint16_t bit_width, int app_type, int acdb_id) +{ + struct q6adm *adm = dev_get_drvdata(dev); + struct copp *copp; + int ret = 0; + + if (port_id < 0) { + dev_err(dev, "Invalid port_id 0x%x\n", port_id); + return -EINVAL; + } + + copp = adm_find_matching_copp(adm, port_id, topology, perf_mode, + rate, channel_mode, bit_width, app_type); + + /* Create a COPP if port id are not enabled */ + if (copp->refcnt == 0) { + ret = q6adm_device_open(adm, copp, port_id, path, topology, + channel_mode, bit_width, rate); + if (ret < 0) + return ret; + } + mutex_lock(&copp->lock); + copp->refcnt++; + mutex_unlock(&copp->lock); + + return copp->copp_idx; +} +EXPORT_SYMBOL_GPL(q6adm_open); + +/** + * q6adm_set_routing_data() - set routing private data + * + * @dev: Pointer to adm device. + * @data: routing private data + * + */ +void q6adm_set_routing_data(struct device *dev, void *data) +{ + struct q6adm *adm = dev_get_drvdata(dev); + + adm->routing_data = data; +} +EXPORT_SYMBOL_GPL(q6adm_set_routing_data); + +/** + * q6adm_get_routing_data() - get routing private data + * + * @dev: Pointer to adm device. + * + * Return: pointer to routing private data + */ +void *q6adm_get_routing_data(struct device *dev) +{ + struct q6adm *adm = dev_get_drvdata(dev); + + return adm->routing_data; +} +EXPORT_SYMBOL_GPL(q6adm_get_routing_data); + +/** + * q6adm_matrix_map() - Map asm streams and afe ports using payload + * + * @dev: Pointer to adm child device. + * @path: playback or capture path. + * @payload_map: map between session id and afe ports. + * @perf_mode: Performace mode. + * + * Return: Will be an negative on error or a zero on success. + */ +int q6adm_matrix_map(struct device *dev, int path, + struct route_payload payload_map, int perf_mode) +{ + struct q6adm *adm = dev_get_drvdata(dev); + struct adm_cmd_matrix_map_routings_v5 *route; + struct adm_session_map_node_v5 *node; + uint16_t *copps_list; + int cmd_size, ret, i, copp_idx; + void *matrix_map = NULL; + struct copp *copp; + + /* Assumes port_ids have already been validated during adm_open */ + cmd_size = (sizeof(*route) + + sizeof(*node) + + (sizeof(uint32_t) * payload_map.num_copps)); + matrix_map = kzalloc(cmd_size, GFP_KERNEL); + if (!matrix_map) + return -ENOMEM; + + route = matrix_map; + route->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + route->hdr.pkt_size = cmd_size; + route->hdr.src_svc = 0; + route->hdr.src_domain = APR_DOMAIN_APPS; + route->hdr.dest_svc = APR_SVC_ADM; + route->hdr.dest_domain = APR_DOMAIN_ADSP; + route->hdr.token = 0; + route->hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS_V5; + route->num_sessions = 1; + + switch (path) { + case ADM_PATH_PLAYBACK: + route->matrix_id = ADM_MATRIX_ID_AUDIO_RX; + break; + default: + dev_err(dev, "Wrong path set[%d]\n", path); + + break; + } + + node = matrix_map + sizeof(*route); + node->session_id = payload_map.session_id; + node->num_copps = payload_map.num_copps; + copps_list = matrix_map + sizeof(*route) + sizeof(*node); + + for (i = 0; i < payload_map.num_copps; i++) { + int port_idx = payload_map.port_id[i]; + + if (port_idx < 0) { + dev_err(dev, "Invalid port_id 0x%x\n", + payload_map.port_id[i]); + ret = -EINVAL; + goto fail_cmd; + } + copp_idx = payload_map.copp_idx[i]; + + copp = adm_find_copp(adm, port_idx, copp_idx); + if (IS_ERR_OR_NULL(copp)) { + ret = -EINVAL; + goto fail_cmd; + } + + copps_list[i] = copp->id; + } + + mutex_lock(&adm->lock); + adm->matrix_map_stat = -1; + + ret = apr_send_pkt(adm->apr, matrix_map); + if (ret < 0) { + dev_err(dev, "routing for syream %d failed ret %d\n", + payload_map.session_id, ret); + ret = -EINVAL; + goto fail_cmd; + } + ret = wait_event_timeout(adm->matrix_map_wait, + adm->matrix_map_stat >= 0, + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + dev_err(dev, "routing for syream %d failed\n", + payload_map.session_id); + ret = -ETIMEDOUT; + goto fail_cmd; + } else if (adm->matrix_map_stat > 0) { + dev_err(dev, "DSP returned error[%s]\n", + q6dsp_strerror(adm->matrix_map_stat)); + ret = q6dsp_errno(adm->matrix_map_stat); + goto fail_cmd; + } + +fail_cmd: + mutex_unlock(&adm->lock); + kfree(matrix_map); + return ret; +} +EXPORT_SYMBOL_GPL(q6adm_matrix_map); + +static int q6adm_device_close(struct q6adm *adm, struct copp *copp, + int port_id, int copp_idx) +{ + struct apr_hdr close = {0}; + + close.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + close.pkt_size = sizeof(close); + close.src_svc = APR_SVC_ADM; + close.src_domain = APR_DOMAIN_APPS; + close.src_port = port_id; + close.dest_svc = APR_SVC_ADM; + close.dest_domain = APR_DOMAIN_ADSP; + close.dest_port = copp->id; + close.token = port_id << 16 | copp_idx; + close.opcode = ADM_CMD_DEVICE_CLOSE_V5; + + return q6adm_apr_send_copp_pkt(adm, copp, &close); +} + +/** + * q6adm_close() - Close adm copp + * + * @dev: Pointer to adm child device. + * @port_id: afe port id. + * @perf_mode: perf_mode mode + * @copp_idx: copp index to close + * + * Return: Will be an negative on error or a zero on success. + */ +int q6adm_close(struct device *dev, int port_id, int perf_mode, int copp_idx) +{ + struct q6adm *adm = dev_get_drvdata(dev); + struct copp *copp; + + if (port_id < 0) { + dev_err(dev, "Invalid port_id 0x%x\n", port_id); + return -EINVAL; + } + + if ((copp_idx < 0) || (copp_idx >= MAX_COPPS_PER_PORT)) { + dev_err(dev, "Invalid copp idx: %d\n", copp_idx); + return -EINVAL; + } + + copp = adm_find_copp(adm, port_id, copp_idx); + if (IS_ERR_OR_NULL(copp)) + return -EINVAL; + + mutex_lock(&copp->lock); + copp->refcnt--; + mutex_unlock(&copp->lock); + if (!copp->refcnt) { + int ret = q6adm_device_close(adm, copp, port_id, copp_idx); + + if (ret < 0) + return ret; + + adm_free_copp(adm, copp, port_id); + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6adm_close); + +static int q6adm_probe(struct apr_device *adev) +{ + struct q6adm *adm; + + adm = devm_kzalloc(&adev->dev, sizeof(*adm), GFP_KERNEL); + if (!adm) + return -ENOMEM; + + adm->apr = adev; + dev_set_drvdata(&adev->dev, adm); + adm->dev = &adev->dev; + adm->matrix_map_stat = 0; + mutex_init(&adm->lock); + init_waitqueue_head(&adm->matrix_map_wait); + + INIT_LIST_HEAD(&adm->copps_list); + spin_lock_init(&adm->copps_list_lock); + + return q6pcm_routing_probe(adm->dev); +} + +static int q6adm_exit(struct apr_device *adev) +{ + q6pcm_routing_remove(&adev->dev); + return 0; +} + +static const struct of_device_id q6adm_device_id[] = { + { .compatible = "qcom,q6adm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6adm_device_id); + +static struct apr_driver qcom_q6adm_driver = { + .probe = q6adm_probe, + .remove = q6adm_exit, + .callback = adm_callback, + .driver = { + .name = "qcom-q6adm", + .of_match_table = of_match_ptr(q6adm_device_id), + }, +}; + +module_apr_driver(qcom_q6adm_driver); +MODULE_DESCRIPTION("Q6 Audio Device Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6adm.h b/sound/soc/qcom/qdsp6/q6adm.h new file mode 100644 index 000000000000..8f89210e2fcf --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6adm.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef __Q6_ADM_V2_H__ +#define __Q6_ADM_V2_H__ + +#define ADM_PATH_PLAYBACK 0x1 +#define MAX_COPPS_PER_PORT 8 +#define NULL_COPP_TOPOLOGY 0x00010312 + +/* multiple copp per stream. */ +struct route_payload { + int num_copps; + int session_id; + int copp_idx[MAX_COPPS_PER_PORT]; + int port_id[MAX_COPPS_PER_PORT]; +}; + +void *q6adm_get_routing_data(struct device *dev); +void q6adm_set_routing_data(struct device *dev, void *data); +int q6adm_open(struct device *dev, int port_id, int path, int rate, + int channel_mode, int topology, int perf_mode, + uint16_t bit_width, int app_type, int acdb_id); +int q6adm_close(struct device *dev, int port, int topology, int perf_mode); +int q6adm_matrix_map(struct device *dev, int path, + struct route_payload payload_map, int perf_mode); + +#endif /* __Q6_ADM_V2_H__ */
On Tue, Feb 13, 2018 at 04:58:19PM +0000, srinivas.kandagatla@linaro.org wrote:
+static struct copp *adm_find_copp(struct q6adm *adm, int port_idx,
int copp_idx)
+{
- struct copp *c;
- spin_lock(&adm->copps_list_lock);
- list_for_each_entry(c, &adm->copps_list, node) {
if ((port_idx == c->afe_port) && (copp_idx == c->copp_idx)) {
spin_unlock(&adm->copps_list_lock);
return c;
}
- }
- spin_unlock(&adm->copps_list_lock);
We've again got this use of spinlocks here but no IRQ safety - what exactly is going on with the locking? In general all of the locking in this stuff is raising very serious alarm bells with me, I don't understand what is being protected against what and there's some very obvious bugs. We could probably use some documentation about what the locking is supposed to be doing.
- case ADM_CMDRSP_DEVICE_OPEN_V5: {
copp->id = open->copp_id;
wake_up(&copp->wait);
- }
- break;
- default:
This indentation is confusing.
+static struct copp *adm_find_matching_copp(struct q6adm *adm,
int port_id, int topology,
int mode, int rate, int channel_mode,
int bit_width, int app_type)
+{
- struct copp *c;
- spin_lock(&adm->copps_list_lock);
- list_for_each_entry(c, &adm->copps_list, node) {
if ((port_id == c->afe_port) && (topology == c->topology) &&
(mode == c->mode) && (rate == c->rate) &&
(bit_width == c->bit_width) && (app_type == c->app_type)) {
spin_unlock(&adm->copps_list_lock);
return c;
}
- }
- spin_unlock(&adm->copps_list_lock);
- c = adm_alloc_copp(adm, port_id);
So really this is a find or allocate operation...
- if (IS_ERR_OR_NULL(c))
return ERR_CAST(c);
- mutex_lock(&c->lock);
- c->refcnt = 0;
Why do we need to lock the thing we just allocated but didn't yet initialize, and surely if something can find it before we finished initializing we have a race condition?
- copp = adm_find_matching_copp(adm, port_id, topology, perf_mode,
rate, channel_mode, bit_width, app_type);
- /* Create a COPP if port id are not enabled */
- if (copp->refcnt == 0) {
ret = q6adm_device_open(adm, copp, port_id, path, topology,
channel_mode, bit_width, rate);
if (ret < 0)
return ret;
- }
- mutex_lock(&copp->lock);
- copp->refcnt++;
- mutex_unlock(&copp->lock);
There's an obvious race here between checking the reference count and incrementing it - something might drop a reference before we increment it which would be bad. I'm also not clear when we'd want multiple things using a single COPP.
- mutex_lock(&copp->lock);
- copp->refcnt--;
- mutex_unlock(&copp->lock);
- if (!copp->refcnt) {
This locking is also broken.
Thanks for the review,
On 01/03/18 21:24, Mark Brown wrote:
On Tue, Feb 13, 2018 at 04:58:19PM +0000, srinivas.kandagatla@linaro.org wrote:
+static struct copp *adm_find_copp(struct q6adm *adm, int port_idx,
int copp_idx)
+{
- struct copp *c;
- spin_lock(&adm->copps_list_lock);
- list_for_each_entry(c, &adm->copps_list, node) {
if ((port_idx == c->afe_port) && (copp_idx == c->copp_idx)) {
spin_unlock(&adm->copps_list_lock);
return c;
}
- }
- spin_unlock(&adm->copps_list_lock);
We've again got this use of spinlocks here but no IRQ safety - what exactly is going on with the locking? In general all of the locking in this stuff is raising very serious alarm bells with me, I don't understand what is being protected against what and there's some very obvious bugs. We could probably use some documentation about what the locking is supposed to be doing.
I agree, there are locking issues here, Am revisiting them all before I send a next version.
- case ADM_CMDRSP_DEVICE_OPEN_V5: {
copp->id = open->copp_id;
wake_up(&copp->wait);
- }
- break;
- default:
This indentation is confusing.
I agree, will fix such instances in next version.
thanks, srini
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- .../devicetree/bindings/sound/qcom,q6asm.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,q6asm.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,q6asm.txt b/Documentation/devicetree/bindings/sound/qcom,q6asm.txt new file mode 100644 index 000000000000..5d6a90681796 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,q6asm.txt @@ -0,0 +1,38 @@ +Qualcomm Audio Stream Manager (Q6ASM) binding + +Q6ASM is one of the APR audio service on Q6DSP. +Please refer to qcom,apr.txt for details of the coommon apr service bindings +used by the apr service device. + +- but must contain the following property: + +- compatible: + Usage: required + Value type: <stringlist> + Definition: must be "qcom,asm-v<MAJOR-NUMBER>.<MINOR-NUMBER>". + example "qcom,asm-v2.0" + +- qcom,apr-svc-id + Usage: required + Value type: <prop-encoded-array> + Definition: Must be 7 for Audio Stream Manager Service. + +- qcom,apr-svc-name + Usage: required + Value type: <stringlist> + Definition: Must be "ASM" + +- #sound-dai-cells + Usage: required + Value type: <prop-encoded-array> + Definition: Must be 1 + + += EXAMPLE + +q6asm: q6asm { + compatible = "qcom,q6asm"; + qcom,apr-svc-name = "ASM"; + qcom,apr-svc-id = <APR_SVC_ASM>; + #sound-dai-cells = <1>; +};
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds basic support to Q6 ASM (Audio Stream Manager) module on Q6DSP. ASM supports up to 8 concurrent streams. each stream can be setup as playback/capture. ASM provides top control functions like Pause/flush/resume for playback and record. ASM can Create/destroy encoder, decoder and also provides POPP dynamic services.
This patch adds support to basic features to allow hdmi playback.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6asm.c | 252 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6asm.h | 18 +++ 4 files changed, 276 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6asm.c create mode 100644 sound/soc/qcom/qdsp6/q6asm.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index bf04e1301608..a14d960b8fe4 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -56,12 +56,17 @@ config SND_SOC_QDSP6_ADM tristate default n
+config SND_SOC_QDSP6_ASM + tristate + default n + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" depends on QCOM_APR && HAS_DMA select SND_SOC_QDSP6_COMMON select SND_SOC_QDSP6_AFE select SND_SOC_QDSP6_ADM + select SND_SOC_QDSP6_ASM help To add support for MSM QDSP6 Soc Audio. This will enable sound soc platform specific diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 930944425051..eea962315ab3 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o +obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c new file mode 100644 index 000000000000..768d9b446da9 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/soc/qcom/apr.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include "q6asm.h" +#include "q6dsp-errno.h" +#include "q6dsp-common.h" + +#define ASM_SYNC_IO_MODE 0x0001 +#define ASM_ASYNC_IO_MODE 0x0002 +#define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ +#define ASM_TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */ + +struct audio_client { + int session; + q6asm_cb cb; + int cmd_state; + void *priv; + uint32_t io_mode; + struct apr_device *adev; + struct mutex lock; + wait_queue_head_t cmd_wait; + int perf_mode; + int stream_id; + struct device *dev; +}; + +struct q6asm { + struct apr_device *adev; + int mem_state; + struct device *dev; + wait_queue_head_t mem_wait; + struct mutex session_lock; + struct platform_device *pcmdev; + struct audio_client *session[MAX_SESSIONS + 1]; + void *dai_data; +}; + +static bool q6asm_is_valid_audio_client(struct audio_client *ac) +{ + struct q6asm *a = dev_get_drvdata(ac->dev); + int n; + + if (!ac) + return false; + + for (n = 1; n <= MAX_SESSIONS; n++) { + if (a->session[n] == ac) + return true; + } + + return false; +} + +/** + * q6asm_audio_client_free() - Freee allocated audio client + * + * @ac: audio client to free + */ +void q6asm_audio_client_free(struct audio_client *ac) +{ + struct q6asm *a = dev_get_drvdata(ac->dev); + + mutex_lock(&a->session_lock); + a->session[ac->session] = NULL; + mutex_unlock(&a->session_lock); + kfree(ac); +} +EXPORT_SYMBOL_GPL(q6asm_audio_client_free); + +static struct audio_client *q6asm_get_audio_client(struct q6asm *a, + int session_id) +{ + if ((session_id <= 0) || (session_id > MAX_SESSIONS)) { + dev_err(a->dev, "invalid session: %d\n", session_id); + return NULL; + } + + if (!a->session[session_id]) { + dev_err(a->dev, "session not active: %d\n", session_id); + return NULL; + } + + return a->session[session_id]; +} + +/** + * q6asm_set_dai_data() - set dai private data + * + * @dev: Pointer to asm device. + * @data: dai private data + * + */ +void q6asm_set_dai_data(struct device *dev, void *data) +{ + struct q6asm *a = dev_get_drvdata(dev); + + a->dai_data = data; +} +EXPORT_SYMBOL_GPL(q6asm_set_dai_data); + +/** + * q6asm_get_dai_data() - get dai private data + * + * @dev: Pointer to asm device. + * + * Return: pointer to dai private data + */ +void *q6asm_get_dai_data(struct device *dev) +{ + struct q6asm *a = dev_get_drvdata(dev); + + return a->dai_data; +} +EXPORT_SYMBOL_GPL(q6asm_get_dai_data); + +static int q6asm_srvc_callback(struct apr_device *adev, + struct apr_client_message *data) +{ + struct aprv2_ibasic_rsp_result_t *result; + struct q6asm *a, *q6asm = dev_get_drvdata(&adev->dev); + struct audio_client *ac = NULL; + struct audio_port_data *port; + uint32_t dir = 0; + uint32_t sid = 0; + + result = data->payload; + sid = (data->token >> 8) & 0x0F; + ac = q6asm_get_audio_client(q6asm, sid); + if (!ac) { + dev_err(&adev->dev, "Audio Client not active\n"); + return 0; + } + + if (ac->cb) + ac->cb(data->opcode, data->token, data->payload, ac->priv); + + return 0; +} + +/** + * q6asm_get_session_id() - get session id for audio client + * + * @ac: audio client pointer + * + * Return: Will be an session id of the audio client. + */ +int q6asm_get_session_id(struct audio_client *c) +{ + return c->session; +} +EXPORT_SYMBOL_GPL(q6asm_get_session_id); + +/** + * q6asm_audio_client_alloc() - Allocate a new audio client + * + * @dev: Pointer to asm child device. + * @cb: event callback. + * @priv: private data associated with this client. + * + * Return: Will be an error pointer on error or a valid audio client + * on success. + */ +struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb, + void *priv, int stream_id) +{ + struct q6asm *a = dev_get_drvdata(dev); + struct audio_client *ac; + + if (stream_id + 1 > MAX_SESSIONS) + return ERR_PTR(-EINVAL); + + ac = kzalloc(sizeof(*ac), GFP_KERNEL); + if (!ac) + return ERR_PTR(-ENOMEM); + + mutex_lock(&a->session_lock); + a->session[stream_id + 1] = ac; + mutex_unlock(&a->session_lock); + + ac->session = stream_id + 1; + ac->cb = cb; + ac->dev = dev; + ac->priv = priv; + ac->io_mode = ASM_SYNC_IO_MODE; + ac->perf_mode = LEGACY_PCM_MODE; + /* DSP expects stream id from 1 */ + ac->stream_id = 1; + ac->adev = a->adev; + + init_waitqueue_head(&ac->cmd_wait); + mutex_init(&ac->lock); + ac->cmd_state = 0; + + return ac; +} +EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc); + + +static int q6asm_probe(struct apr_device *adev) +{ + struct q6asm *q6asm; + + q6asm = devm_kzalloc(&adev->dev, sizeof(*q6asm), GFP_KERNEL); + if (!q6asm) + return -ENOMEM; + + q6asm->dev = &adev->dev; + q6asm->adev = adev; + q6asm->mem_state = 0; + init_waitqueue_head(&q6asm->mem_wait); + mutex_init(&q6asm->session_lock); + dev_set_drvdata(&adev->dev, q6asm); + + return q6asm_dai_probe(&adev->dev); +} + +static int q6asm_remove(struct apr_device *adev) +{ + return q6asm_dai_remove(&adev->dev); +} + +static const struct of_device_id q6asm_device_id[] = { + { .compatible = "qcom,q6asm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6asm_device_id); + +static struct apr_driver qcom_q6asm_driver = { + .probe = q6asm_probe, + .remove = q6asm_remove, + .callback = q6asm_srvc_callback, + .driver = { + .name = "qcom-q6asm", + .of_match_table = of_match_ptr(q6asm_device_id), + }, +}; + +module_apr_driver(qcom_q6asm_driver); +MODULE_DESCRIPTION("Q6 Audio Stream Manager driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h new file mode 100644 index 000000000000..b13fe0d2ea61 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef __Q6_ASM_H__ +#define __Q6_ASM_H__ + +#define MAX_SESSIONS 16 + +void q6asm_set_dai_data(struct device *dev, void *data); +void *q6asm_get_dai_data(struct device *dev); + +typedef void (*q6asm_cb) (uint32_t opcode, uint32_t token, + void *payload, void *priv); +struct audio_client; +struct audio_client *q6asm_audio_client_alloc(struct device *dev, + q6asm_cb cb, void *priv, + int session_id); +void q6asm_audio_client_free(struct audio_client *ac); +int q6asm_get_session_id(struct audio_client *ac); +#endif /* __Q6_ASM_H__ */
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to memory map and unmap regions commands in q6asm module.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/q6asm.c | 312 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6asm.h | 5 + 2 files changed, 317 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 768d9b446da9..412275edb15c 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -17,10 +17,47 @@ #include "q6dsp-errno.h" #include "q6dsp-common.h"
+#define ASM_CMD_SHARED_MEM_MAP_REGIONS 0x00010D92 +#define ASM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010D93 +#define ASM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010D94 + #define ASM_SYNC_IO_MODE 0x0001 #define ASM_ASYNC_IO_MODE 0x0002 #define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ #define ASM_TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */ +#define ASM_SHIFT_GAPLESS_MODE_FLAG 31 +#define ADSP_MEMORY_MAP_SHMEM8_4K_POOL 3 + +struct avs_cmd_shared_mem_map_regions { + struct apr_hdr hdr; + u16 mem_pool_id; + u16 num_regions; + u32 property_flag; +} __packed; + +struct avs_shared_map_region_payload { + u32 shm_addr_lsw; + u32 shm_addr_msw; + u32 mem_size_bytes; +} __packed; + +struct avs_cmd_shared_mem_unmap_regions { + struct apr_hdr hdr; + u32 mem_map_handle; +} __packed; + +struct audio_buffer { + phys_addr_t phys; + uint32_t used; + uint32_t size; /* size of buffer */ +}; + +struct audio_port_data { + struct audio_buffer *buf; + uint32_t num_periods; + uint32_t dsp_buf; + uint32_t mem_map_handle; +};
struct audio_client { int session; @@ -30,6 +67,8 @@ struct audio_client { uint32_t io_mode; struct apr_device *adev; struct mutex lock; + /* idx:1 out port, 0: in port */ + struct audio_port_data port[2]; wait_queue_head_t cmd_wait; int perf_mode; int stream_id; @@ -63,6 +102,237 @@ static bool q6asm_is_valid_audio_client(struct audio_client *ac) return false; }
+static inline void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, bool cmd_flg, + uint32_t stream_id) +{ + hdr->hdr_field = APR_SEQ_CMD_HDR_FIELD; + hdr->src_svc = ac->adev->svc_id; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->dest_svc = APR_SVC_ASM; + hdr->dest_domain = APR_DOMAIN_ADSP; + hdr->src_port = ((ac->session << 8) & 0xFF00) | (stream_id); + hdr->dest_port = ((ac->session << 8) & 0xFF00) | (stream_id); + hdr->pkt_size = pkt_size; + if (cmd_flg) + hdr->token = ac->session; +} + +static int q6asm_apr_send_session_pkt(struct q6asm *a, struct audio_client *ac, + void *data) +{ + int rc; + + mutex_lock(&a->session_lock); + a->mem_state = 1; + rc = apr_send_pkt(a->adev, data); + if (rc < 0) + goto err; + + rc = wait_event_timeout(a->mem_wait, (a->mem_state <= 0), 5 * HZ); + if (!rc) { + dev_err(a->dev, "CMD timeout \n"); + rc = -ETIMEDOUT; + } else if (a->mem_state < 0) { + rc = q6dsp_errno(a->mem_state); + } + +err: + mutex_unlock(&a->session_lock); + return rc; +} + +static int __q6asm_memory_unmap(struct audio_client *ac, + phys_addr_t buf_add, int dir) +{ + struct avs_cmd_shared_mem_unmap_regions mem_unmap; + struct q6asm *a = dev_get_drvdata(ac->dev); + int rc; + + if (ac->port[dir].mem_map_handle == 0) { + dev_err(ac->dev, "invalid mem handle\n"); + return -EINVAL; + } + + mem_unmap.hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD; + mem_unmap.hdr.src_port = 0; + mem_unmap.hdr.dest_port = 0; + mem_unmap.hdr.pkt_size = sizeof(mem_unmap); + mem_unmap.hdr.token = ((ac->session << 8) | dir); + + mem_unmap.hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS; + mem_unmap.mem_map_handle = ac->port[dir].mem_map_handle; + + rc = q6asm_apr_send_session_pkt(a, ac, &mem_unmap); + if (rc < 0) + return rc; + + ac->port[dir].mem_map_handle = 0; + + return 0; +} + +/** + * q6asm_unmap_memory_regions() - unmap memory regions in the dsp. + * + * @dir: direction of audio stream + * @ac: audio client instanace + * + * Return: Will be an negative value on failure or zero on success + */ +int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + + mutex_lock(&ac->lock); + port = &ac->port[dir]; + if (!port->buf) { + rc = -EINVAL; + goto err; + } + cnt = port->num_periods - 1; + if (cnt >= 0) { + rc = __q6asm_memory_unmap(ac, port->buf[dir].phys, dir); + if (rc < 0) { + dev_err(ac->dev, "%s: Memory_unmap_regions failed %d\n", + __func__, rc); + goto err; + } + } + + port->num_periods = 0; + kfree(port->buf); + port->buf = NULL; + +err: + mutex_unlock(&ac->lock); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_unmap_memory_regions); + +static int __q6asm_memory_map_regions(struct audio_client *ac, int dir, + size_t period_sz, unsigned int periods, + bool is_contiguous) +{ + struct avs_cmd_shared_mem_map_regions *cmd = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + struct q6asm *a = dev_get_drvdata(ac->dev); + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *mmap_region_cmd = NULL; + uint32_t num_regions, buf_sz; + int rc, i, cmd_size; + + num_regions = is_contiguous ? 1 : periods; + buf_sz = is_contiguous ? (period_sz * periods) : period_sz; + buf_sz = PAGE_ALIGN(buf_sz); + + cmd_size = sizeof(*cmd) + (sizeof(*mregions) * num_regions); + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) + return -ENOMEM; + + cmd = mmap_region_cmd; + + cmd->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD; + cmd->hdr.src_port = 0; + cmd->hdr.dest_port = 0; + cmd->hdr.pkt_size = cmd_size; + cmd->hdr.token = ((ac->session << 8) | dir); + + + cmd->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS; + cmd->mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL; + cmd->num_regions = num_regions; + cmd->property_flag = 0x00; + + mregions = mmap_region_cmd + sizeof(*cmd); + + port = &ac->port[dir]; + + for (i = 0; i < num_regions; i++) { + ab = &port->buf[i]; + mregions->shm_addr_lsw = lower_32_bits(ab->phys); + mregions->shm_addr_msw = upper_32_bits(ab->phys); + mregions->mem_size_bytes = buf_sz; + ++mregions; + } + + rc = q6asm_apr_send_session_pkt(a, ac, mmap_region_cmd); + + kfree(mmap_region_cmd); + + return rc; +} + +/** + * q6asm_map_memory_regions() - map memory regions in the dsp. + * + * @dir: direction of audio stream + * @ac: audio client instanace + * @phys: physcial address that needs mapping. + * @period_sz: audio period size + * @periods: number of periods + * + * Return: Will be an negative value on failure or zero on success + */ +int q6asm_map_memory_regions(unsigned int dir, struct audio_client *ac, + phys_addr_t phys, + size_t period_sz, unsigned int periods) +{ + struct audio_buffer *buf; + int cnt; + int rc; + + mutex_lock(&ac->lock); + + if (ac->port[dir].buf) { + dev_err(ac->dev, "Buffer already allocated\n"); + rc = 0; + goto err; + } + + + buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; + goto err; + } + + + ac->port[dir].buf = buf; + + buf[0].phys = phys; + buf[0].used = !!dir; + buf[0].size = period_sz; + + for (cnt = 1; cnt < periods; cnt++) { + if (period_sz > 0) { + buf[cnt].phys = buf[0].phys + (cnt * period_sz); + buf[cnt].used = dir ^ 1; + buf[cnt].size = period_sz; + } + } + + ac->port[dir].num_periods = periods; + + rc = __q6asm_memory_map_regions(ac, dir, period_sz, periods, 1); + if (rc < 0) { + dev_err(ac->dev, "Memory_map_regions failed\n"); + ac->port[dir].num_periods = 0; + kfree(buf); + ac->port[dir].buf = NULL; + goto err; + } + +err: + mutex_unlock(&ac->lock); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_map_memory_regions); + /** * q6asm_audio_client_free() - Freee allocated audio client * @@ -143,6 +413,48 @@ static int q6asm_srvc_callback(struct apr_device *adev, return 0; }
+ a = dev_get_drvdata(ac->dev); + dir = (data->token & 0x0F); + port = &ac->port[dir]; + + switch (data->opcode) + case APR_BASIC_RSP_RESULT: { + switch (result->opcode) { + case ASM_CMD_SHARED_MEM_MAP_REGIONS: + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + if (result->status != 0) { + dev_err(ac->dev, + "cmd = 0x%x retur err= 0x%x sid:%d\n", + result->opcode, result->status, sid); + a->mem_state = -result->status; + } else { + a->mem_state = 0; + } + + wake_up(&a->mem_wait); + break; + default: + dev_err(&adev->dev, "command[0x%x] not expecting rsp\n", + result->opcode); + break; + } + return 0; + case ASM_CMDRSP_SHARED_MEM_MAP_REGIONS: + a->mem_state = 0; + ac->port[dir].mem_map_handle = result->opcode; + wake_up(&a->mem_wait); + break; + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + a->mem_state = 0; + ac->port[dir].mem_map_handle = 0; + wake_up(&a->mem_wait); + break; + default: + dev_dbg(&adev->dev, "command[0x%x]success [0x%x]\n", + result->opcode, result->status); + break; + } + if (ac->cb) ac->cb(data->opcode, data->token, data->payload, ac->priv);
diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h index b13fe0d2ea61..a4f9fe636b7e 100644 --- a/sound/soc/qcom/qdsp6/q6asm.h +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -15,4 +15,9 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev, int session_id); void q6asm_audio_client_free(struct audio_client *ac); int q6asm_get_session_id(struct audio_client *ac); +int q6asm_map_memory_regions(unsigned int dir, + struct audio_client *ac, + phys_addr_t phys, + size_t bufsz, unsigned int bufcnt); +int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac); #endif /* __Q6_ASM_H__ */
On Tue, Feb 13, 2018 at 04:58:22PM +0000, srinivas.kandagatla@linaro.org wrote:
- num_regions = is_contiguous ? 1 : periods;
- buf_sz = is_contiguous ? (period_sz * periods) : period_sz;
Please write normal if statements, it's much easier to read.
- buf_sz = PAGE_ALIGN(buf_sz);
I don't understand what this is doing, buf_sz is a length not an address so why are we attempting to align it?
Thanks for the review,
On 01/03/18 21:28, Mark Brown wrote:
On Tue, Feb 13, 2018 at 04:58:22PM +0000, srinivas.kandagatla@linaro.org wrote:
- num_regions = is_contiguous ? 1 : periods;
- buf_sz = is_contiguous ? (period_sz * periods) : period_sz;
Please write normal if statements, it's much easier to read.
Sure, will fix it in next version.
- buf_sz = PAGE_ALIGN(buf_sz);
I don't understand what this is doing, buf_sz is a length not an address so why are we attempting to align it?
Yes, this is a requirement form the DSP side that the size is multiple of 4KB. I will fix this properly in next version.
thanks, srini
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to open, write and media format commands in the q6asm module.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- include/dt-bindings/sound/qcom,q6asm.h | 22 ++ sound/soc/qcom/qdsp6/q6asm.c | 503 ++++++++++++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6asm.h | 41 +++ 3 files changed, 564 insertions(+), 2 deletions(-) create mode 100644 include/dt-bindings/sound/qcom,q6asm.h
diff --git a/include/dt-bindings/sound/qcom,q6asm.h b/include/dt-bindings/sound/qcom,q6asm.h new file mode 100644 index 000000000000..4e85bf804cec --- /dev/null +++ b/include/dt-bindings/sound/qcom,q6asm.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef __DT_BINDINGS_Q6_ASM_H__ +#define __DT_BINDINGS_Q6_ASM_H__ + +#define MSM_FRONTEND_DAI_MULTIMEDIA1 0 +#define MSM_FRONTEND_DAI_MULTIMEDIA2 1 +#define MSM_FRONTEND_DAI_MULTIMEDIA3 2 +#define MSM_FRONTEND_DAI_MULTIMEDIA4 3 +#define MSM_FRONTEND_DAI_MULTIMEDIA5 4 +#define MSM_FRONTEND_DAI_MULTIMEDIA6 5 +#define MSM_FRONTEND_DAI_MULTIMEDIA7 6 +#define MSM_FRONTEND_DAI_MULTIMEDIA8 7 +#define MSM_FRONTEND_DAI_MULTIMEDIA9 8 +#define MSM_FRONTEND_DAI_MULTIMEDIA10 9 +#define MSM_FRONTEND_DAI_MULTIMEDIA11 10 +#define MSM_FRONTEND_DAI_MULTIMEDIA12 11 +#define MSM_FRONTEND_DAI_MULTIMEDIA13 12 +#define MSM_FRONTEND_DAI_MULTIMEDIA14 13 +#define MSM_FRONTEND_DAI_MULTIMEDIA15 14 +#define MSM_FRONTEND_DAI_MULTIMEDIA16 15 + +#endif /* __DT_BINDINGS_Q6_ASM_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 412275edb15c..0ee1e30a8d8e 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -10,6 +10,7 @@ #include <linux/soc/qcom/apr.h> #include <linux/device.h> #include <linux/of.h> +#include <uapi/sound/asound.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/mm.h> @@ -17,10 +18,26 @@ #include "q6dsp-errno.h" #include "q6dsp-common.h"
+#define ASM_STREAM_CMD_CLOSE 0x00010BCD +#define ASM_STREAM_CMD_FLUSH 0x00010BCE +#define ASM_SESSION_CMD_PAUSE 0x00010BD3 +#define ASM_DATA_CMD_EOS 0x00010BDB +#define ASM_DEFAULT_POPP_TOPOLOGY 0x00010BE4 +#define ASM_STREAM_CMD_FLUSH_READBUFS 0x00010C09 #define ASM_CMD_SHARED_MEM_MAP_REGIONS 0x00010D92 #define ASM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010D93 #define ASM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010D94 - +#define ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2 0x00010D98 +#define ASM_DATA_EVENT_WRITE_DONE_V2 0x00010D99 +#define ASM_SESSION_CMD_RUN_V2 0x00010DAA +#define ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 0x00010DA5 +#define ASM_DATA_CMD_WRITE_V2 0x00010DAB +#define ASM_SESSION_CMD_SUSPEND 0x00010DEC +#define ASM_STREAM_CMD_OPEN_WRITE_V3 0x00010DB3 + +#define ASM_LEGACY_STREAM_SESSION 0 +#define ASM_END_POINT_DEVICE_MATRIX 0 +#define ASM_DEFAULT_APP_TYPE 0 #define ASM_SYNC_IO_MODE 0x0001 #define ASM_ASYNC_IO_MODE 0x0002 #define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ @@ -46,6 +63,49 @@ struct avs_cmd_shared_mem_unmap_regions { u32 mem_map_handle; } __packed;
+struct asm_data_cmd_media_fmt_update_v2 { + u32 fmt_blk_size; +} __packed; + +struct asm_multi_channel_pcm_fmt_blk_v2 { + struct apr_hdr hdr; + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + u16 num_channels; + u16 bits_per_sample; + u32 sample_rate; + u16 is_signed; + u16 reserved; + u8 channel_mapping[PCM_FORMAT_MAX_NUM_CHANNEL]; +} __packed; + +struct asm_data_cmd_write_v2 { + struct apr_hdr hdr; + u32 buf_addr_lsw; + u32 buf_addr_msw; + u32 mem_map_handle; + u32 buf_size; + u32 seq_id; + u32 timestamp_lsw; + u32 timestamp_msw; + u32 flags; +} __packed; + +struct asm_stream_cmd_open_write_v3 { + struct apr_hdr hdr; + uint32_t mode_flags; + uint16_t sink_endpointype; + uint16_t bits_per_sample; + uint32_t postprocopo_id; + uint32_t dec_fmt_id; +} __packed; + +struct asm_session_cmd_run_v2 { + struct apr_hdr hdr; + u32 flags; + u32 time_lsw; + u32 time_msw; +} __packed; + struct audio_buffer { phys_addr_t phys; uint32_t used; @@ -131,7 +191,7 @@ static int q6asm_apr_send_session_pkt(struct q6asm *a, struct audio_client *ac,
rc = wait_event_timeout(a->mem_wait, (a->mem_state <= 0), 5 * HZ); if (!rc) { - dev_err(a->dev, "CMD timeout \n"); + dev_err(a->dev, "CMD timeout\n"); rc = -ETIMEDOUT; } else if (a->mem_state < 0) { rc = q6dsp_errno(a->mem_state); @@ -395,6 +455,108 @@ void *q6asm_get_dai_data(struct device *dev) } EXPORT_SYMBOL_GPL(q6asm_get_dai_data);
+static int32_t q6asm_stream_callback(struct apr_device *adev, + struct apr_client_message *data, + int session_id) +{ + struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *result; + struct audio_port_data *port; + struct audio_client *ac; + uint32_t token; + uint32_t client_event = 0; + + ac = q6asm_get_audio_client(q6asm, session_id); + if (!ac)/* Audio client might already be freed by now */ + return 0; + + if (!q6asm_is_valid_audio_client(ac)) + return -EINVAL; + + result = data->payload; + + switch (data->opcode) { + case APR_BASIC_RSP_RESULT: + token = data->token; + switch (result->opcode) { + case ASM_SESSION_CMD_PAUSE: + client_event = ASM_CLIENT_EVENT_CMD_PAUSE_DONE; + break; + case ASM_SESSION_CMD_SUSPEND: + client_event = ASM_CLIENT_EVENT_CMD_SUSPEND_DONE; + break; + case ASM_DATA_CMD_EOS: + client_event = ASM_CLIENT_EVENT_CMD_EOS_DONE; + break; + break; + case ASM_STREAM_CMD_FLUSH: + client_event = ASM_CLIENT_EVENT_CMD_FLUSH_DONE; + break; + case ASM_SESSION_CMD_RUN_V2: + client_event = ASM_CLIENT_EVENT_CMD_RUN_DONE; + break; + + case ASM_STREAM_CMD_FLUSH_READBUFS: + if (token != ac->session) { + dev_err(ac->dev, "session invalid\n"); + return -EINVAL; + } + case ASM_STREAM_CMD_CLOSE: + client_event = ASM_CLIENT_EVENT_CMD_CLOSE_DONE; + break; + case ASM_STREAM_CMD_OPEN_WRITE_V3: + case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2: + if (result->status != 0) { + dev_err(ac->dev, + "cmd = 0x%x returned error = 0x%x\n", + result->opcode, result->status); + ac->cmd_state = -result->status; + wake_up(&ac->cmd_wait); + return 0; + } + break; + default: + dev_err(ac->dev, "command[0x%x] not expecting rsp\n", + result->opcode); + break; + } + + if (ac->cmd_state) { + ac->cmd_state = 0; + wake_up(&ac->cmd_wait); + } + if (ac->cb) + ac->cb(client_event, data->token, + data->payload, ac->priv); + + return 0; + + case ASM_DATA_EVENT_WRITE_DONE_V2: + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + + client_event = ASM_CLIENT_EVENT_DATA_WRITE_DONE; + + if (ac->io_mode & ASM_SYNC_IO_MODE) { + phys_addr_t phys = port->buf[data->token].phys; + + if (lower_32_bits(phys) != result->opcode || + upper_32_bits(phys) != result->status) { + dev_err(ac->dev, "Expected addr %pa\n", + &port->buf[data->token].phys); + return -EINVAL; + } + token = data->token; + port->buf[token].used = 1; + } + break; + } + + if (ac->cb) + ac->cb(client_event, data->token, data->payload, ac->priv); + + return 0; +} + static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_message *data) { @@ -404,6 +566,11 @@ static int q6asm_srvc_callback(struct apr_device *adev, struct audio_port_data *port; uint32_t dir = 0; uint32_t sid = 0; + int session_id; + + session_id = (data->dest_port >> 8) & 0xFF; + if (session_id) + return q6asm_stream_callback(adev, data, session_id);
result = data->payload; sid = (data->token >> 8) & 0x0F; @@ -519,6 +686,338 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb, } EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
+static int q6asm_ac_send_cmd_sync(struct audio_client *ac, void *cmd) +{ + int rc; + + mutex_lock(&ac->lock); + ac->cmd_state = 1; + + rc = apr_send_pkt(ac->adev, cmd); + if (rc < 0) + goto err; + + rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state <= 0), 5 * HZ); + if (!rc) { + dev_err(ac->dev, "CMD timeout\n"); + rc = -ETIMEDOUT; + goto err; + } + + if (ac->cmd_state > 0) + rc = q6dsp_errno(ac->cmd_state); + +err: + mutex_unlock(&ac->lock); + return rc; +} + +/** + * q6asm_open_write() - Open audio client for writing + * + * @ac: audio client pointer + * @format: audio sample format + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_open_write(struct audio_client *ac, uint32_t format, + uint16_t bits_per_sample) +{ + struct asm_stream_cmd_open_write_v3 open; + int rc; + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), true, ac->stream_id); + + open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3; + open.mode_flags = 0x00; + open.mode_flags |= ASM_LEGACY_STREAM_SESSION; + + /* source endpoint : matrix */ + open.sink_endpointype = ASM_END_POINT_DEVICE_MATRIX; + open.bits_per_sample = bits_per_sample; + open.postprocopo_id = ASM_DEFAULT_POPP_TOPOLOGY; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.dec_fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + default: + dev_err(ac->dev, "Invalid format 0x%x\n", format); + return -EINVAL; + } + + rc = q6asm_ac_send_cmd_sync(ac, &open); + if (rc < 0) + return rc; + + ac->io_mode |= ASM_TUN_WRITE_IO_MODE; + + return 0; +} +EXPORT_SYMBOL_GPL(q6asm_open_write); + +static int __q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts, bool wait) +{ + struct asm_session_cmd_run_v2 run; + + q6asm_add_hdr(ac, &run.hdr, sizeof(run), true, ac->stream_id); + + run.hdr.opcode = ASM_SESSION_CMD_RUN_V2; + run.flags = flags; + run.time_lsw = lsw_ts; + run.time_msw = msw_ts; + if (wait) + return q6asm_ac_send_cmd_sync(ac, &run); + else + return apr_send_pkt(ac->adev, &run); + +} + +/** + * q6asm_run() - start the audio client + * + * @ac: audio client pointer + * @flags: flags associated with write + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + return __q6asm_run(ac, flags, msw_ts, lsw_ts, true); +} +EXPORT_SYMBOL_GPL(q6asm_run); + +/** + * q6asm_run_nowait() - start the audio client withou blocking + * + * @ac: audio client pointer + * @flags: flags associated with write + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + return __q6asm_run(ac, flags, msw_ts, lsw_ts, false); +} +EXPORT_SYMBOL_GPL(q6asm_run_nowait); + +/** + * q6asm_media_format_block_multi_ch_pcm() - setup pcm configuration + * + * @ac: audio client pointer + * @rate: audio sample rate + * @channels: number of audio channels. + * @use_default_chmap: flag to use default ch map. + * @channel_map: channel map pointer + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels, + u8 channel_map[PCM_FORMAT_MAX_NUM_CHANNEL], + uint16_t bits_per_sample) +{ + struct asm_multi_channel_pcm_fmt_blk_v2 fmt; + u8 *channel_mapping; + int rc; + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), true, ac->stream_id); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt.fmt_blk.fmt_blk_size = sizeof(fmt) - sizeof(fmt.hdr) - + sizeof(fmt.fmt_blk); + fmt.num_channels = channels; + fmt.bits_per_sample = bits_per_sample; + fmt.sample_rate = rate; + fmt.is_signed = 1; + + channel_mapping = fmt.channel_mapping; + + if (channel_map) { + memcpy(channel_mapping, channel_map, + PCM_FORMAT_MAX_NUM_CHANNEL); + } else { + if (q6dsp_map_channels(channel_mapping, channels)) { + dev_err(ac->dev, " map channels failed %d\n", channels); + return -EINVAL; + } + } + + rc = q6asm_ac_send_cmd_sync(ac, &fmt); + if (rc < 0) + goto fail_cmd; + + return 0; +fail_cmd: + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_media_format_block_multi_ch_pcm); + +/** + * q6asm_write_async() - non blocking write + * + * @ac: audio client pointer + * @len: lenght in bytes + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * @flags: flags associated with write + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_write_async(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags) +{ + struct asm_data_cmd_write_v2 write; + struct audio_port_data *port; + struct audio_buffer *ab; + int rc = 0; + + if (!(ac->io_mode & ASM_SYNC_IO_MODE)) + return 0; + + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + q6asm_add_hdr(ac, &write.hdr, sizeof(write), false, + ac->stream_id); + + ab = &port->buf[port->dsp_buf]; + + write.hdr.token = port->dsp_buf; + write.hdr.opcode = ASM_DATA_CMD_WRITE_V2; + write.buf_addr_lsw = lower_32_bits(ab->phys); + write.buf_addr_msw = upper_32_bits(ab->phys); + write.buf_size = len; + write.seq_id = port->dsp_buf; + write.timestamp_lsw = lsw_ts; + write.timestamp_msw = msw_ts; + write.mem_map_handle = + ac->port[SNDRV_PCM_STREAM_PLAYBACK].mem_map_handle; + + if (flags == NO_TIMESTAMP) + write.flags = (flags & 0x800000FF); + else + write.flags = (0x80000000 | flags); + + port->dsp_buf++; + + if (port->dsp_buf >= port->num_periods) + port->dsp_buf = 0; + + rc = apr_send_pkt(ac->adev, &write); + if (rc < 0) + return rc; + + return 0; +} +EXPORT_SYMBOL_GPL(q6asm_write_async); + +static void q6asm_reset_buf_state(struct audio_client *ac) +{ + int cnt = 0; + int loopcnt = 0; + int used; + struct audio_port_data *port = NULL; + + if (!(ac->io_mode & ASM_SYNC_IO_MODE)) + return; + + used = (ac->io_mode & ASM_TUN_WRITE_IO_MODE ? 1 : 0); + mutex_lock(&ac->lock); + for (loopcnt = 0; loopcnt <= SNDRV_PCM_STREAM_CAPTURE; + loopcnt++) { + port = &ac->port[loopcnt]; + cnt = port->num_periods - 1; + port->dsp_buf = 0; + while (cnt >= 0) { + if (!port->buf) + continue; + port->buf[cnt].used = used; + cnt--; + } + } + mutex_unlock(&ac->lock); +} + +static int __q6asm_cmd(struct audio_client *ac, int cmd, bool wait) +{ + int stream_id = ac->stream_id; + struct apr_hdr hdr; + int rc; + + q6asm_add_hdr(ac, &hdr, sizeof(hdr), true, stream_id); + + switch (cmd) { + case CMD_PAUSE: + hdr.opcode = ASM_SESSION_CMD_PAUSE; + break; + case CMD_SUSPEND: + hdr.opcode = ASM_SESSION_CMD_SUSPEND; + break; + case CMD_FLUSH: + hdr.opcode = ASM_STREAM_CMD_FLUSH; + break; + case CMD_OUT_FLUSH: + hdr.opcode = ASM_STREAM_CMD_FLUSH_READBUFS; + break; + case CMD_EOS: + hdr.opcode = ASM_DATA_CMD_EOS; + break; + case CMD_CLOSE: + hdr.opcode = ASM_STREAM_CMD_CLOSE; + break; + default: + return -EINVAL; + } + + if (wait) + rc = q6asm_ac_send_cmd_sync(ac, &hdr); + else + return apr_send_pkt(ac->adev, &hdr); + + if (rc < 0) + return rc; + + if (cmd == CMD_FLUSH) + q6asm_reset_buf_state(ac); + + return 0; +} + +/** + * q6asm_cmd() - run cmd on audio client + * + * @ac: audio client pointer + * @cmd: command to run on audio client. + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_cmd(struct audio_client *ac, int cmd) +{ + return __q6asm_cmd(ac, cmd, true); +} +EXPORT_SYMBOL_GPL(q6asm_cmd); + +/** + * q6asm_cmd_nowait() - non blocking, run cmd on audio client + * + * @ac: audio client pointer + * @cmd: command to run on audio client. + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_cmd_nowait(struct audio_client *ac, int cmd) +{ + return __q6asm_cmd(ac, cmd, false); +} +EXPORT_SYMBOL_GPL(q6asm_cmd_nowait);
static int q6asm_probe(struct apr_device *adev) { diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h index a4f9fe636b7e..b5ef90bb724b 100644 --- a/sound/soc/qcom/qdsp6/q6asm.h +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -1,8 +1,35 @@ // SPDX-License-Identifier: GPL-2.0 #ifndef __Q6_ASM_H__ #define __Q6_ASM_H__ +#include "q6dsp-common.h" +#include <dt-bindings/sound/qcom,q6asm.h> + +/* ASM client callback events */ +#define CMD_PAUSE 0x0001 +#define ASM_CLIENT_EVENT_CMD_PAUSE_DONE 0x1001 +#define CMD_FLUSH 0x0002 +#define ASM_CLIENT_EVENT_CMD_FLUSH_DONE 0x1002 +#define CMD_EOS 0x0003 +#define ASM_CLIENT_EVENT_CMD_EOS_DONE 0x1003 +#define CMD_CLOSE 0x0004 +#define ASM_CLIENT_EVENT_CMD_CLOSE_DONE 0x1004 +#define CMD_OUT_FLUSH 0x0005 +#define ASM_CLIENT_EVENT_CMD_OUT_FLUSH_DONE 0x1005 +#define CMD_SUSPEND 0x0006 +#define ASM_CLIENT_EVENT_CMD_SUSPEND_DONE 0x1006 +#define ASM_CLIENT_EVENT_CMD_RUN_DONE 0x1008 +#define ASM_CLIENT_EVENT_DATA_WRITE_DONE 0x1009 + +enum { + LEGACY_PCM_MODE = 0, + LOW_LATENCY_PCM_MODE, + ULTRA_LOW_LATENCY_PCM_MODE, + ULL_POST_PROCESSING_PCM_MODE, +};
#define MAX_SESSIONS 16 +#define NO_TIMESTAMP 0xFF00 +#define FORMAT_LINEAR_PCM 0x0000
void q6asm_set_dai_data(struct device *dev, void *data); void *q6asm_get_dai_data(struct device *dev); @@ -14,6 +41,20 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb, void *priv, int session_id); void q6asm_audio_client_free(struct audio_client *ac); +int q6asm_write_async(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags); +int q6asm_open_write(struct audio_client *ac, uint32_t format, + uint16_t bits_per_sample); +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels, + u8 channel_map[PCM_FORMAT_MAX_NUM_CHANNEL], + uint16_t bits_per_sample); +int q6asm_run(struct audio_client *ac, uint32_t flags, uint32_t msw_ts, + uint32_t lsw_ts); +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, uint32_t msw_ts, + uint32_t lsw_ts); +int q6asm_cmd(struct audio_client *ac, int cmd); +int q6asm_cmd_nowait(struct audio_client *ac, int cmd); int q6asm_get_session_id(struct audio_client *ac); int q6asm_map_memory_regions(unsigned int dir, struct audio_client *ac,
On Tue, Feb 13, 2018 at 04:58:23PM +0000, srinivas.kandagatla@linaro.org wrote:
uint32_t used; @@ -131,7 +191,7 @@ static int q6asm_apr_send_session_pkt(struct q6asm *a, struct audio_client *ac,
rc = wait_event_timeout(a->mem_wait, (a->mem_state <= 0), 5 * HZ); if (!rc) {
dev_err(a->dev, "CMD timeout \n");
rc = -ETIMEDOUT; } else if (a->mem_state < 0) { rc = q6dsp_errno(a->mem_state);dev_err(a->dev, "CMD timeout\n");
This should be folded into whatever patch is being fixed.
- open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3;
- open.mode_flags = 0x00;
- open.mode_flags |= ASM_LEGACY_STREAM_SESSION;
What is a legacy stream and why are we using it in new code?
Thanks for the review comments.
sorry for delay, for some reason these emails endup in my SPAM folder.
On 01/03/18 21:33, Mark Brown wrote:
On Tue, Feb 13, 2018 at 04:58:23PM +0000, srinivas.kandagatla@linaro.org wrote:
uint32_t used; @@ -131,7 +191,7 @@ static int q6asm_apr_send_session_pkt(struct q6asm *a, struct audio_client *ac,
rc = wait_event_timeout(a->mem_wait, (a->mem_state <= 0), 5 * HZ); if (!rc) {
dev_err(a->dev, "CMD timeout \n");
rc = -ETIMEDOUT; } else if (a->mem_state < 0) { rc = q6dsp_errno(a->mem_state);dev_err(a->dev, "CMD timeout\n");
This should be folded into whatever patch is being fixed.
My Bad, I will fix this in next version.
- open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3;
- open.mode_flags = 0x00;
- open.mode_flags |= ASM_LEGACY_STREAM_SESSION;
What is a legacy stream and why are we using it in new code?
This is basically Ensures backward compatibility to the original behavior of ASM_STREAM_CMD_OPEN_WRITE_V2 command.
I will take a closer look and see if its possible to remove this in the first place.
thanks, srini
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to core apr service, which is used to query status of other static and dynamic services on the dsp.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6core.c | 235 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6core.h | 9 ++ 4 files changed, 250 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6core.c create mode 100644 sound/soc/qcom/qdsp6/q6core.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index a14d960b8fe4..8c2d65e0a28e 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -60,6 +60,10 @@ config SND_SOC_QDSP6_ASM tristate default n
+config SND_SOC_QDSP6_CORE + tristate + default n + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" depends on QCOM_APR && HAS_DMA @@ -67,6 +71,7 @@ config SND_SOC_QDSP6 select SND_SOC_QDSP6_AFE select SND_SOC_QDSP6_ADM select SND_SOC_QDSP6_ASM + select SND_SOC_QDSP6_CORE help To add support for MSM QDSP6 Soc Audio. This will enable sound soc platform specific diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index eea962315ab3..61f089bc0d25 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o +obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6core.c b/sound/soc/qcom/qdsp6/q6core.c new file mode 100644 index 000000000000..d4a3ff409a34 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/of.h> +#include <linux/jiffies.h> +#include <linux/wait.h> +#include <linux/soc/qcom/apr.h> +#include "q6dsp-errno.h" + +#define ADSP_STATE_READY_TIMEOUT_MS 3000 +#define Q6_READY_TIMEOUT_MS 100 +#define AVCS_CMD_ADSP_EVENT_GET_STATE 0x0001290C +#define AVCS_CMDRSP_ADSP_EVENT_GET_STATE 0x0001290D +#define AVCS_GET_VERSIONS 0x00012905 +#define AVCS_GET_VERSIONS_RSP 0x00012906 + +struct avcs_svc_info { + uint32_t service_id; + uint32_t version; +} __packed; + +struct q6core { + struct apr_device *adev; + wait_queue_head_t wait; + uint32_t avcs_state; + bool resp_received; + uint32_t num_services; + struct avcs_svc_info *svcs_info; +}; + +struct q6core *core; + +static int q6core_callback(struct apr_device *adev, + struct apr_client_message *data) +{ + struct q6core *core = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *result; + + result = data->payload; + switch (data->opcode) { + case AVCS_GET_VERSIONS_RSP: + core->num_services = result->status; + + core->svcs_info = kcalloc(core->num_services, + sizeof(*core->svcs_info), + GFP_ATOMIC); + if (!core->svcs_info) + return -ENOMEM; + + /* svc info is after apr result */ + memcpy(core->svcs_info, result + sizeof(*result), + core->num_services * sizeof(*core->svcs_info)); + + core->resp_received = true; + wake_up(&core->wait); + + break; + case AVCS_CMDRSP_ADSP_EVENT_GET_STATE: + core->avcs_state = result->opcode; + + core->resp_received = true; + wake_up(&core->wait); + break; + default: + dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n", + data->opcode); + break; + } + + return 0; +} + +static int q6core_get_svc_versions(struct q6core *core) +{ + struct apr_device *adev = core->adev; + struct apr_hdr hdr = {0}; + int rc; + + hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + hdr.pkt_size = APR_HDR_SIZE; + hdr.opcode = AVCS_GET_VERSIONS; + + rc = apr_send_pkt(adev, &hdr); + if (rc < 0) + return rc; + + rc = wait_event_timeout(core->wait, (core->resp_received), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = false; + return 0; + } + + return rc; +} + +static bool __q6core_is_adsp_ready(struct q6core *core) +{ + struct apr_device *adev = core->adev; + struct apr_hdr hdr = {0}; + int rc; + + hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + hdr.pkt_size = APR_HDR_SIZE; + hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE; + + rc = apr_send_pkt(adev, &hdr); + if (rc < 0) + return false; + + rc = wait_event_timeout(core->wait, (core->resp_received), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = false; + if (core->avcs_state == 0x1) + return true; + } + + return false; +} + +/** + * q6core_get_svc_version() - Get version number of a service. + * + * @svc_id: service id of the service. + * + * Return: Will be a valid version number on success and zero on failure. + * version number returned contains bits 0 to 15 as Minor version number + * Bits 16 to 31 as Major version number + */ +uint32_t q6core_get_svc_version(int svc_id) +{ + struct apr_device *adev; + struct avcs_svc_info *svcs_info; + int i, ret; + + if (!core) + return 0; + + if (!core->svcs_info) { + ret = q6core_get_svc_versions(core); + if (ret) + return ret; + } + + adev = core->adev; + svcs_info = core->svcs_info; + + for (i = 0; i < core->num_services; i++) + if (svcs_info[i].service_id == svc_id) + return svcs_info[i].version; + + return 0; +} +EXPORT_SYMBOL_GPL(q6core_get_svc_version); + +/** + * q6core_is_adsp_ready() - Get status of adsp + * + * Return: Will be an true if adsp is ready and false if not. + */ +bool q6core_is_adsp_ready(void) +{ + unsigned long timeout; + + if (!core) + return false; + + timeout = jiffies + msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS); + for (;;) { + if (__q6core_is_adsp_ready(core)) + return true; + + if (!time_after(timeout, jiffies)) + return false; + } + + return false; +} +EXPORT_SYMBOL_GPL(q6core_is_adsp_ready); + +static int q6core_probe(struct apr_device *adev) +{ + core = kzalloc(sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + dev_set_drvdata(&adev->dev, core); + + core->adev = adev; + init_waitqueue_head(&core->wait); + + return 0; +} + +static int q6core_exit(struct apr_device *adev) +{ + if (core->svcs_info) + kfree(core->svcs_info); + + kfree(core); + core = NULL; + + return 0; +} + +static const struct of_device_id q6core_device_id[] = { + { .compatible = "qcom,q6core" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6core_device_id); + +static struct apr_driver qcom_q6core_driver = { + .probe = q6core_probe, + .remove = q6core_exit, + .callback = q6core_callback, + .driver = { + .name = "qcom-q6core", + .of_match_table = of_match_ptr(q6core_device_id), + }, +}; + +module_apr_driver(qcom_q6core_driver); +MODULE_DESCRIPTION("q6 core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6core.h b/sound/soc/qcom/qdsp6/q6core.h new file mode 100644 index 000000000000..2852fbb7756e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 + +#ifndef __Q6CORE_H__ +#define __Q6CORE_H__ + +bool q6core_is_adsp_ready(void); +uint32_t q6core_get_svc_version(int svc_id); + +#endif /* __Q6CORE_H__ */
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to core apr service, which is used to query status of other static and dynamic services on the dsp.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
sound/soc/qcom/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6core.c | 235 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6core.h | 9 ++ 4 files changed, 250 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6core.c create mode 100644 sound/soc/qcom/qdsp6/q6core.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index a14d960b8fe4..8c2d65e0a28e 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -60,6 +60,10 @@ config SND_SOC_QDSP6_ASM tristate default n
+config SND_SOC_QDSP6_CORE
- tristate
- default n
- config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" depends on QCOM_APR && HAS_DMA
@@ -67,6 +71,7 @@ config SND_SOC_QDSP6 select SND_SOC_QDSP6_AFE select SND_SOC_QDSP6_ADM select SND_SOC_QDSP6_ASM
- select SND_SOC_QDSP6_CORE help To add support for MSM QDSP6 Soc Audio. This will enable sound soc platform specific
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index eea962315ab3..61f089bc0d25 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o +obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6core.c b/sound/soc/qcom/qdsp6/q6core.c new file mode 100644 index 000000000000..d4a3ff409a34 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (c) 2011-2017, The Linux Foundation
- Copyright (c) 2018, Linaro Limited
- */
+#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/of.h> +#include <linux/jiffies.h> +#include <linux/wait.h> +#include <linux/soc/qcom/apr.h> +#include "q6dsp-errno.h"
+#define ADSP_STATE_READY_TIMEOUT_MS 3000 +#define Q6_READY_TIMEOUT_MS 100 +#define AVCS_CMD_ADSP_EVENT_GET_STATE 0x0001290C +#define AVCS_CMDRSP_ADSP_EVENT_GET_STATE 0x0001290D +#define AVCS_GET_VERSIONS 0x00012905 +#define AVCS_GET_VERSIONS_RSP 0x00012906
+struct avcs_svc_info {
- uint32_t service_id;
- uint32_t version;
+} __packed;
+struct q6core {
- struct apr_device *adev;
- wait_queue_head_t wait;
- uint32_t avcs_state;
- bool resp_received;
- uint32_t num_services;
- struct avcs_svc_info *svcs_info;
+};
+struct q6core *core;
+static int q6core_callback(struct apr_device *adev,
struct apr_client_message *data)
+{
- struct q6core *core = dev_get_drvdata(&adev->dev);
- struct aprv2_ibasic_rsp_result_t *result;
- result = data->payload;
- switch (data->opcode) {
- case AVCS_GET_VERSIONS_RSP:
core->num_services = result->status;
core->svcs_info = kcalloc(core->num_services,
sizeof(*core->svcs_info),
GFP_ATOMIC);
if (!core->svcs_info)
return -ENOMEM;
/* svc info is after apr result */
memcpy(core->svcs_info, result + sizeof(*result),
core->num_services * sizeof(*core->svcs_info));
core->resp_received = true;
wake_up(&core->wait);
break;
- case AVCS_CMDRSP_ADSP_EVENT_GET_STATE:
core->avcs_state = result->opcode;
core->resp_received = true;
wake_up(&core->wait);
break;
- default:
dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n",
data->opcode);
break;
- }
- return 0;
+}
+static int q6core_get_svc_versions(struct q6core *core) +{
- struct apr_device *adev = core->adev;
- struct apr_hdr hdr = {0};
- int rc;
- hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
- hdr.pkt_size = APR_HDR_SIZE;
- hdr.opcode = AVCS_GET_VERSIONS;
- rc = apr_send_pkt(adev, &hdr);
- if (rc < 0)
return rc;
- rc = wait_event_timeout(core->wait, (core->resp_received),
msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
- if (rc > 0 && core->resp_received) {
core->resp_received = false;
return 0;
- }
- return rc;
+}
+static bool __q6core_is_adsp_ready(struct q6core *core) +{
- struct apr_device *adev = core->adev;
- struct apr_hdr hdr = {0};
- int rc;
- hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER);
- hdr.pkt_size = APR_HDR_SIZE;
- hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE;
- rc = apr_send_pkt(adev, &hdr);
- if (rc < 0)
return false;
- rc = wait_event_timeout(core->wait, (core->resp_received),
msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
- if (rc > 0 && core->resp_received) {
core->resp_received = false;
if (core->avcs_state == 0x1)
return true;
- }
- return false;
+}
+/**
- q6core_get_svc_version() - Get version number of a service.
- @svc_id: service id of the service.
- Return: Will be a valid version number on success and zero on failure.
- version number returned contains bits 0 to 15 as Minor version number
- Bits 16 to 31 as Major version number
- */
+uint32_t q6core_get_svc_version(int svc_id)
no caller of q6core_get_svc_version() / q6core_is_adsp_ready()
+{
- struct apr_device *adev;
- struct avcs_svc_info *svcs_info;
- int i, ret;
- if (!core)
return 0;
- if (!core->svcs_info) {
ret = q6core_get_svc_versions(core);
if (ret)
return ret;
- }
- adev = core->adev;
- svcs_info = core->svcs_info;
- for (i = 0; i < core->num_services; i++)
if (svcs_info[i].service_id == svc_id)
return svcs_info[i].version;
- return 0;
+} +EXPORT_SYMBOL_GPL(q6core_get_svc_version);
+/**
- q6core_is_adsp_ready() - Get status of adsp
- Return: Will be an true if adsp is ready and false if not.
- */
+bool q6core_is_adsp_ready(void) +{
- unsigned long timeout;
- if (!core)
return false;
- timeout = jiffies + msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS);
- for (;;) {
if (__q6core_is_adsp_ready(core))
return true;
if (!time_after(timeout, jiffies))
return false;
- }
- return false;
+} +EXPORT_SYMBOL_GPL(q6core_is_adsp_ready);
+static int q6core_probe(struct apr_device *adev) +{
- core = kzalloc(sizeof(*core), GFP_KERNEL);
- if (!core)
return -ENOMEM;
- dev_set_drvdata(&adev->dev, core);
- core->adev = adev;
- init_waitqueue_head(&core->wait);
- return 0;
+}
+static int q6core_exit(struct apr_device *adev) +{
- if (core->svcs_info)
kfree(core->svcs_info);
- kfree(core);
- core = NULL;
- return 0;
+}
+static const struct of_device_id q6core_device_id[] = {
- { .compatible = "qcom,q6core" },
- {},
+}; +MODULE_DEVICE_TABLE(of, q6core_device_id);
+static struct apr_driver qcom_q6core_driver = {
- .probe = q6core_probe,
- .remove = q6core_exit,
- .callback = q6core_callback,
- .driver = {
.name = "qcom-q6core",
.of_match_table = of_match_ptr(q6core_device_id),
- },
+};
+module_apr_driver(qcom_q6core_driver); +MODULE_DESCRIPTION("q6 core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6core.h b/sound/soc/qcom/qdsp6/q6core.h new file mode 100644 index 000000000000..2852fbb7756e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0
+#ifndef __Q6CORE_H__ +#define __Q6CORE_H__
+bool q6core_is_adsp_ready(void); +uint32_t q6core_get_svc_version(int svc_id);
+#endif /* __Q6CORE_H__ */
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6 routing driver which configures route between ASM and AFE module using ADM apis.
This driver uses dapm widgets to setup the matrix between AFE ports and ASM streams.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/Makefile | 2 +- sound/soc/qcom/qdsp6/q6adm.h | 3 + sound/soc/qcom/qdsp6/q6routing.c | 355 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6routing.h | 9 + 4 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/qdsp6/q6routing.c create mode 100644 sound/soc/qcom/qdsp6/q6routing.h
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 61f089bc0d25..660afcab98fd 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o -obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o +obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6adm.h b/sound/soc/qcom/qdsp6/q6adm.h index 8f89210e2fcf..270cb9c0133a 100644 --- a/sound/soc/qcom/qdsp6/q6adm.h +++ b/sound/soc/qcom/qdsp6/q6adm.h @@ -14,6 +14,9 @@ struct route_payload { int port_id[MAX_COPPS_PER_PORT]; };
+int q6pcm_routing_probe(struct device *dev); +int q6pcm_routing_remove(struct device *dev); + void *q6adm_get_routing_data(struct device *dev); void q6adm_set_routing_data(struct device *dev, void *data); int q6adm_open(struct device *dev, int port_id, int path, int rate, diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c new file mode 100644 index 000000000000..828243c58569 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2017, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/bitops.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/control.h> +#include <sound/asound.h> +#include <sound/pcm_params.h> +#include "q6afe.h" +#include "q6asm.h" +#include "q6adm.h" +#include "q6routing.h" + +struct session_data { + int state; + int port_id; + int path_type; + int app_type; + int acdb_id; + int sample_rate; + int bits_per_sample; + int channels; + int perf_mode; + int numcopps; + int fedai_id; + unsigned long copp_map; +}; + +struct msm_routing_data { + struct session_data sessions[MAX_SESSIONS]; + struct session_data port_data[AFE_MAX_PORTS]; + struct device *dev; + struct mutex lock; +}; + +static struct msm_routing_data *routing_data; + +/** + * q6routing_stream_open() - Register a new stream for route setup + * + * @fedai_id: Frontend dai id. + * @perf_mode: Performance mode. + * @stream_id: ASM stream id to map. + * @stream_type: Direction of stream + * + * Return: Will be an negative on error or a zero on success. + */ +int q6routing_stream_open(int fedai_id, int perf_mode, + int stream_id, int stream_type) +{ + int j, topology, num_copps = 0; + struct route_payload payload; + int copp_idx; + struct session_data *session, *pdata; + + if (!routing_data) { + pr_err("Routing driver not yet ready\n"); + return -EINVAL; + } + + session = &routing_data->sessions[stream_id - 1]; + pdata = &routing_data->port_data[session->port_id]; + + mutex_lock(&routing_data->lock); + session->fedai_id = fedai_id; + + session->path_type = pdata->path_type; + session->sample_rate = pdata->sample_rate; + session->channels = pdata->channels; + session->bits_per_sample = pdata->bits_per_sample; + + payload.num_copps = 0; /* only RX needs to use payload */ + topology = NULL_COPP_TOPOLOGY; + copp_idx = q6adm_open(routing_data->dev, session->port_id, + session->path_type, session->sample_rate, + session->channels, topology, perf_mode, + session->bits_per_sample, 0, 0); + + if (copp_idx < 0) { + mutex_unlock(&routing_data->lock); + return -EINVAL; + } + + set_bit(copp_idx, &session->copp_map); + + for_each_set_bit(j, &session->copp_map, MAX_COPPS_PER_PORT) { + payload.port_id[num_copps] = session->port_id; + payload.copp_idx[num_copps] = j; + num_copps++; + } + + if (num_copps) { + payload.num_copps = num_copps; + payload.session_id = stream_id; + q6adm_matrix_map(routing_data->dev, session->path_type, + payload, perf_mode); + } + mutex_unlock(&routing_data->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(q6routing_stream_open); + +static struct session_data *get_session_from_id(struct msm_routing_data *data, + int fedai_id) +{ + int i; + + for (i = 0; i < MAX_SESSIONS; i++) { + if (fedai_id == data->sessions[i].fedai_id) + return &data->sessions[i]; + } + + return NULL; +} +/** + * q6routing_stream_close() - Deregister a stream + * + * @fedai_id: Frontend dai id. + * @stream_type: Direction of stream + * + * Return: Will be an negative on error or a zero on success. + */ +void q6routing_stream_close(int fedai_id, int stream_type) +{ + struct session_data *session; + int idx; + + session = get_session_from_id(routing_data, fedai_id); + if (!session) + return; + + for_each_set_bit(idx, &session->copp_map, MAX_COPPS_PER_PORT) + q6adm_close(routing_data->dev, session->port_id, + session->perf_mode, idx); + + session->fedai_id = -1; + session->copp_map = 0; +} +EXPORT_SYMBOL_GPL(q6routing_stream_close); + +static int msm_routing_get_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int session_id = mc->shift; + struct snd_soc_platform *platform = snd_soc_dapm_to_platform(dapm); + struct msm_routing_data *priv = q6adm_get_routing_data(platform->dev); + struct session_data *session = &priv->sessions[session_id]; + + if (session->port_id != -1) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_platform *platform = snd_soc_dapm_to_platform(dapm); + struct msm_routing_data *data = q6adm_get_routing_data(platform->dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_update *update = NULL; + int be_id = mc->reg; + int session_id = mc->shift; + struct session_data *session = &data->sessions[session_id]; + + if (ucontrol->value.integer.value[0]) { + session->port_id = be_id; + snd_soc_dapm_mixer_update_power(dapm, kcontrol, 1, update); + } else { + session->port_id = -1; + snd_soc_dapm_mixer_update_power(dapm, kcontrol, 0, update); + } + + return 1; +} + +static const struct snd_kcontrol_new hdmi_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", AFE_PORT_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, + msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", AFE_PORT_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, + msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", AFE_PORT_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, + msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", AFE_PORT_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, + msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", AFE_PORT_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, + msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", AFE_PORT_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, + msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", AFE_PORT_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, + msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", AFE_PORT_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, + msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + /* Frontend AIF */ + SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL4", "MultiMedia4 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL5", "MultiMedia5 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL6", "MultiMedia6 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL7", "MultiMedia7 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL8", "MultiMedia8 Playback", 0, 0, 0, 0), + + /* Mixer definitions */ + SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0, + hdmi_mixer_controls, + ARRAY_SIZE(hdmi_mixer_controls)), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"HDMI Mixer", "MultiMedia1", "MM_DL1"}, + {"HDMI Mixer", "MultiMedia2", "MM_DL2"}, + {"HDMI Mixer", "MultiMedia3", "MM_DL3"}, + {"HDMI Mixer", "MultiMedia4", "MM_DL4"}, + {"HDMI Mixer", "MultiMedia5", "MM_DL5"}, + {"HDMI Mixer", "MultiMedia6", "MM_DL6"}, + {"HDMI Mixer", "MultiMedia7", "MM_DL7"}, + {"HDMI Mixer", "MultiMedia8", "MM_DL8"}, + {"HDMI_RX", NULL, "HDMI Mixer"}, +}; + +static int routing_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int be_id = rtd->cpu_dai->id; + struct snd_soc_platform *platform = rtd->platform; + struct msm_routing_data *data = q6adm_get_routing_data(platform->dev); + struct session_data *session; + int path_type; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + path_type = ADM_PATH_PLAYBACK; + + if (be_id > AFE_MAX_PORTS) + return -EINVAL; + + session = &data->port_data[be_id]; + + mutex_lock(&data->lock); + + session->path_type = path_type; + session->sample_rate = params_rate(params); + session->channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + session->bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + session->bits_per_sample = 24; + break; + default: + break; + } + + mutex_unlock(&data->lock); + return 0; +} + +static struct snd_pcm_ops q6pcm_routing_ops = { + .hw_params = routing_hw_params, +}; + +static int msm_routing_probe(struct snd_soc_platform *platform) +{ + int i; + + for (i = 0; i < MAX_SESSIONS; i++) + routing_data->sessions[i].port_id = -1; + + return 0; +} + +static struct snd_soc_platform_driver msm_soc_routing_platform = { + .ops = &q6pcm_routing_ops, + .probe = msm_routing_probe, + .component_driver = { + .name = "q6routing-component", + .dapm_widgets = msm_qdsp6_widgets, + .num_dapm_widgets = ARRAY_SIZE(msm_qdsp6_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + }, +}; + +int q6pcm_routing_probe(struct device *dev) +{ + routing_data = kzalloc(sizeof(*routing_data), GFP_KERNEL); + if (!routing_data) + return -ENOMEM; + + routing_data->dev = dev; + + mutex_init(&routing_data->lock); + q6adm_set_routing_data(dev, routing_data); + + return devm_snd_soc_register_platform(dev, + &msm_soc_routing_platform); +} +EXPORT_SYMBOL_GPL(q6pcm_routing_probe); + +int q6pcm_routing_remove(struct device *dev) +{ + kfree(routing_data); + + routing_data = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(q6pcm_routing_remove); +MODULE_DESCRIPTION("Q6 Routing platform"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6routing.h b/sound/soc/qcom/qdsp6/q6routing.h new file mode 100644 index 000000000000..aa41e4f45742 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6routing.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef _Q6_PCM_ROUTING_H +#define _Q6_PCM_ROUTING_H + +int q6routing_stream_open(int fedai_id, int perf_mode, + int stream_id, int stream_type); +void q6routing_stream_close(int fedai_id, int stream_type); + +#endif /*_Q6_PCM_ROUTING_H */
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6afe backend dais driver.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/Makefile | 2 +- sound/soc/qcom/qdsp6/q6afe-dai.c | 280 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 3 + 3 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 660afcab98fd..c7833842b878 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o -obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o q6afe-dai.o obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c new file mode 100644 index 000000000000..f6a618e9db9e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2016, The Linux Foundation + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "q6afe.h" + +struct q6afe_dai_data { + struct q6afe_port *port[AFE_PORT_MAX]; + struct q6afe_port_config port_config[AFE_PORT_MAX]; + bool is_port_started[AFE_PORT_MAX]; +}; + +static int q6hdmi_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct q6afe_dai_data *dai_data = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + + dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype = value; + + return 0; +} + +static int q6hdmi_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct q6afe_dai_data *dai_data = kcontrol->private_data; + + ucontrol->value.integer.value[0] = + dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype; + + return 0; +} + +static const char * const hdmi_format[] = { + "LPCM", + "Compr" +}; + +static const struct soc_enum hdmi_config_enum[] = { + SOC_ENUM_SINGLE_EXT(2, hdmi_format), +}; + +static const struct snd_kcontrol_new q6afe_config_controls[] = { + SOC_ENUM_EXT("HDMI RX Format", hdmi_config_enum[0], + q6hdmi_format_get, + q6hdmi_format_put), +}; + +static int q6hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + int channels = params_channels(params); + struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi; + + hdmi->sample_rate = params_rate(params); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + hdmi->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hdmi->bit_width = 24; + break; + } + + /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/ + switch (channels) { + case 2: + hdmi->channel_allocation = 0; + break; + case 3: + hdmi->channel_allocation = 0x02; + break; + case 4: + hdmi->channel_allocation = 0x06; + break; + case 5: + hdmi->channel_allocation = 0x0A; + break; + case 6: + hdmi->channel_allocation = 0x0B; + break; + case 7: + hdmi->channel_allocation = 0x12; + break; + case 8: + hdmi->channel_allocation = 0x13; + break; + default: + dev_err(dai->dev, "invalid Channels = %u\n", channels); + return -EINVAL; + } + + return 0; +} + +static int q6afe_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + + dai_data->is_port_started[dai->id] = false; + + return 0; +} + +static void q6afe_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + int rc; + + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) + dev_err(dai->dev, "fail to close AFE port\n"); + + dai_data->is_port_started[dai->id] = false; + +} + +static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + int rc; + + if (dai_data->is_port_started[dai->id]) { + /* stop the port and restart with new port config */ + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to close AFE port\n"); + return rc; + } + } + + if (dai->id == AFE_PORT_HDMI_RX) + q6afe_hdmi_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].hdmi); + + rc = q6afe_port_start(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to start AFE port %x\n", dai->id); + return rc; + } + dai_data->is_port_started[dai->id] = true; + + return 0; +} + +static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + {"HDMI Playback", NULL, "HDMI_RX"}, +}; + +static struct snd_soc_dai_ops q6hdmi_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6hdmi_hw_params, + .shutdown = q6afe_dai_shutdown, + .startup = q6afe_dai_startup, +}; + +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + struct snd_soc_dapm_context *dapm; + struct q6afe_port *port; + + dapm = snd_soc_component_get_dapm(dai->component); + + port = q6afe_port_get_from_id(dai->dev, dai->id); + if (IS_ERR(port)) { + dev_err(dai->dev, "Unable to get afe port\n"); + return -EINVAL; + } + dai_data->port[dai->id] = port; + + return 0; +} + +static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + + q6afe_port_put(dai_data->port[dai->id]); + + return 0; +} + +static struct snd_soc_dai_driver q6afe_dais[] = { + { + .playback = { + .stream_name = "HDMI Playback", + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 8, + .rate_max = 192000, + .rate_min = 48000, + }, + .ops = &q6hdmi_ops, + .id = AFE_PORT_HDMI_RX, + .name = "HDMI", + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, +}; + +static int q6afe_of_xlate_dai_name(struct snd_soc_component *component, + struct of_phandle_args *args, + const char **dai_name) +{ + int id = args->args[0]; + int i, ret = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) { + if (q6afe_dais[i].id == id) { + *dai_name = q6afe_dais[i].name; + ret = 0; + break; + } + } + + return ret; +} + +static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + SND_SOC_DAPM_AIF_OUT("HDMI_RX", "HDMI Playback", 0, 0, 0, 0), +}; + +static const struct snd_soc_component_driver q6afe_dai_component = { + .name = "q6afe-dai-component", + .dapm_widgets = q6afe_dai_widgets, + .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets), + .controls = q6afe_config_controls, + .num_controls = ARRAY_SIZE(q6afe_config_controls), + .dapm_routes = q6afe_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes), + .of_xlate_dai_name = q6afe_of_xlate_dai_name, + +}; + +int q6afe_dai_dev_probe(struct device *dev) +{ + int rc = 0; + struct q6afe_dai_data *dai_data; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + rc = -ENOMEM; + + q6afe_set_dai_data(dev, dai_data); + + return devm_snd_soc_register_component(dev, &q6afe_dai_component, + q6afe_dais, ARRAY_SIZE(q6afe_dais)); +} +EXPORT_SYMBOL_GPL(q6afe_dai_dev_probe); + +int q6afe_dai_dev_remove(struct device *dev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(q6afe_dai_dev_remove); +MODULE_DESCRIPTION("Q6 Audio Fronend dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index 43df524f01bb..647ed2d15545 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -26,6 +26,9 @@ struct q6afe_port; void q6afe_set_dai_data(struct device *dev, void *data); void *q6afe_get_dai_data(struct device *dev);
+int q6afe_dai_dev_probe(struct device *dev); +int q6afe_dai_dev_remove(struct device *dev); + struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id); int q6afe_port_start(struct q6afe_port *port); int q6afe_port_stop(struct q6afe_port *port);
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6afe backend dais driver.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
sound/soc/qcom/qdsp6/Makefile | 2 +- sound/soc/qcom/qdsp6/q6afe-dai.c | 280 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 3 + 3 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 660afcab98fd..c7833842b878 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o -obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o q6afe-dai.o
depmod: ERROR: Cycle detected: q6afe -> q6afe_dai -> q6afe need to use like: +qdsp6_afe-objs := q6afe.o q6afe-dai.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += qdsp6_afe.o similarly for asm and adm
obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c new file mode 100644 index 000000000000..f6a618e9db9e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (c) 2011-2016, The Linux Foundation
- Copyright (c) 2018, Linaro Limited
- */
+#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "q6afe.h"
+struct q6afe_dai_data {
- struct q6afe_port *port[AFE_PORT_MAX];
- struct q6afe_port_config port_config[AFE_PORT_MAX];
- bool is_port_started[AFE_PORT_MAX];
+};
+static int q6hdmi_format_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct q6afe_dai_data *dai_data = kcontrol->private_data;
- int value = ucontrol->value.integer.value[0];
- dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype = value;
- return 0;
+}
+static int q6hdmi_format_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct q6afe_dai_data *dai_data = kcontrol->private_data;
- ucontrol->value.integer.value[0] =
dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype;
- return 0;
+}
+static const char * const hdmi_format[] = {
- "LPCM",
- "Compr"
+};
+static const struct soc_enum hdmi_config_enum[] = {
- SOC_ENUM_SINGLE_EXT(2, hdmi_format),
+};
+static const struct snd_kcontrol_new q6afe_config_controls[] = {
- SOC_ENUM_EXT("HDMI RX Format", hdmi_config_enum[0],
q6hdmi_format_get,
q6hdmi_format_put),
+};
+static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- int channels = params_channels(params);
- struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi;
- hdmi->sample_rate = params_rate(params);
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
hdmi->bit_width = 16;
break;
- case SNDRV_PCM_FORMAT_S24_LE:
hdmi->bit_width = 24;
break;
- }
- /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/
- switch (channels) {
- case 2:
hdmi->channel_allocation = 0;
break;
- case 3:
hdmi->channel_allocation = 0x02;
break;
- case 4:
hdmi->channel_allocation = 0x06;
break;
- case 5:
hdmi->channel_allocation = 0x0A;
break;
- case 6:
hdmi->channel_allocation = 0x0B;
break;
- case 7:
hdmi->channel_allocation = 0x12;
break;
- case 8:
hdmi->channel_allocation = 0x13;
break;
- default:
dev_err(dai->dev, "invalid Channels = %u\n", channels);
return -EINVAL;
- }
- return 0;
+}
+static int q6afe_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- dai_data->is_port_started[dai->id] = false;
- return 0;
+}
+static void q6afe_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- int rc;
- rc = q6afe_port_stop(dai_data->port[dai->id]);
- if (rc < 0)
dev_err(dai->dev, "fail to close AFE port\n");
- dai_data->is_port_started[dai->id] = false;
+}
+static int q6afe_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- int rc;
- if (dai_data->is_port_started[dai->id]) {
/* stop the port and restart with new port config */
rc = q6afe_port_stop(dai_data->port[dai->id]);
if (rc < 0) {
dev_err(dai->dev, "fail to close AFE port\n");
return rc;
}
- }
- if (dai->id == AFE_PORT_HDMI_RX)
q6afe_hdmi_port_prepare(dai_data->port[dai->id],
&dai_data->port_config[dai->id].hdmi);
- rc = q6afe_port_start(dai_data->port[dai->id]);
- if (rc < 0) {
dev_err(dai->dev, "fail to start AFE port %x\n", dai->id);
return rc;
- }
- dai_data->is_port_started[dai->id] = true;
- return 0;
+}
+static const struct snd_soc_dapm_route q6afe_dapm_routes[] = {
- {"HDMI Playback", NULL, "HDMI_RX"},
+};
+static struct snd_soc_dai_ops q6hdmi_ops = {
- .prepare = q6afe_dai_prepare,
- .hw_params = q6hdmi_hw_params,
- .shutdown = q6afe_dai_shutdown,
- .startup = q6afe_dai_startup,
+};
+static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- struct snd_soc_dapm_context *dapm;
- struct q6afe_port *port;
- dapm = snd_soc_component_get_dapm(dai->component);
- port = q6afe_port_get_from_id(dai->dev, dai->id);
- if (IS_ERR(port)) {
dev_err(dai->dev, "Unable to get afe port\n");
return -EINVAL;
- }
- dai_data->port[dai->id] = port;
- return 0;
+}
+static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) +{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- q6afe_port_put(dai_data->port[dai->id]);
- return 0;
+}
+static struct snd_soc_dai_driver q6afe_dais[] = {
- {
.playback = {
.stream_name = "HDMI Playback",
.rates = SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE,
.channels_min = 2,
.channels_max = 8,
.rate_max = 192000,
.rate_min = 48000,
},
.ops = &q6hdmi_ops,
.id = AFE_PORT_HDMI_RX,
.name = "HDMI",
.probe = msm_dai_q6_dai_probe,
.remove = msm_dai_q6_dai_remove,
- },
+};
+static int q6afe_of_xlate_dai_name(struct snd_soc_component *component,
struct of_phandle_args *args,
const char **dai_name)
+{
- int id = args->args[0];
- int i, ret = -EINVAL;
- for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) {
if (q6afe_dais[i].id == id) {
*dai_name = q6afe_dais[i].name;
ret = 0;
break;
}
- }
- return ret;
+}
+static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = {
- SND_SOC_DAPM_AIF_OUT("HDMI_RX", "HDMI Playback", 0, 0, 0, 0),
+};
+static const struct snd_soc_component_driver q6afe_dai_component = {
- .name = "q6afe-dai-component",
- .dapm_widgets = q6afe_dai_widgets,
- .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets),
- .controls = q6afe_config_controls,
- .num_controls = ARRAY_SIZE(q6afe_config_controls),
- .dapm_routes = q6afe_dapm_routes,
- .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes),
- .of_xlate_dai_name = q6afe_of_xlate_dai_name,
+};
+int q6afe_dai_dev_probe(struct device *dev) +{
- int rc = 0;
- struct q6afe_dai_data *dai_data;
- dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL);
- if (!dai_data)
rc = -ENOMEM;
- q6afe_set_dai_data(dev, dai_data);
- return devm_snd_soc_register_component(dev, &q6afe_dai_component,
q6afe_dais, ARRAY_SIZE(q6afe_dais));
+} +EXPORT_SYMBOL_GPL(q6afe_dai_dev_probe);
+int q6afe_dai_dev_remove(struct device *dev) +{
- return 0;
+} +EXPORT_SYMBOL_GPL(q6afe_dai_dev_remove); +MODULE_DESCRIPTION("Q6 Audio Fronend dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index 43df524f01bb..647ed2d15545 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -26,6 +26,9 @@ struct q6afe_port; void q6afe_set_dai_data(struct device *dev, void *data); void *q6afe_get_dai_data(struct device *dev);
+int q6afe_dai_dev_probe(struct device *dev); +int q6afe_dai_dev_remove(struct device *dev);
- struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id); int q6afe_port_start(struct q6afe_port *port); int q6afe_port_stop(struct q6afe_port *port);
Thanks for testing this out,
On 19/02/18 10:32, Rohit Kumar wrote:
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 660afcab98fd..c7833842b878 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o -obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o q6afe-dai.o
depmod: ERROR: Cycle detected: q6afe -> q6afe_dai -> q6afe need to use like: +qdsp6_afe-objs := q6afe.o q6afe-dai.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += qdsp6_afe.o similarly for asm and adm
Yep, will fix this in next version,
thanks, srini
obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c new file mode 100644 index 000000000000..f6a618e9db9e
On Tue, Feb 13, 2018 at 04:58:26PM +0000, srinivas.kandagatla@linaro.org wrote:
+static int q6hdmi_format_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct q6afe_dai_data *dai_data = kcontrol->private_data;
- int value = ucontrol->value.integer.value[0];
- dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype = value;
- return 0;
+}
No validation, and do we not need to tell a currently running stream if the format changed on it (or block such changes if they're not going to work, which seems more likely)?
+static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- int channels = params_channels(params);
- struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi;
- hdmi->sample_rate = params_rate(params);
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
hdmi->bit_width = 16;
break;
- case SNDRV_PCM_FORMAT_S24_LE:
hdmi->bit_width = 24;
break;
- }
This silently accepts invalid values.
- /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/
Coding style, spaces around the /* */.
+static int q6afe_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- dai_data->is_port_started[dai->id] = false;
- return 0;
+}
If this is needed it makes me a bit worried that we've got some kind of bug with not shutting things down properly somewhere - what's going on here?
+static void q6afe_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- int rc;
- rc = q6afe_port_stop(dai_data->port[dai->id]);
- if (rc < 0)
dev_err(dai->dev, "fail to close AFE port\n");
Better to print the error code so users have more information to debug the problem.
.stream_name = "HDMI Playback",
.rates = SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_192000,
Indentation again.
+static int q6afe_of_xlate_dai_name(struct snd_soc_component *component,
struct of_phandle_args *args,
const char **dai_name)
+{
- int id = args->args[0];
- int i, ret = -EINVAL;
Coding style, don't mix initialization in with other variable declarations on the same line like this.
+int q6afe_dai_dev_remove(struct device *dev) +{
- return 0;
+}
Remove empty functions, if they can't be removed it's probably not OK for them to be empty either.
Thanks for the review comments,
On 02/03/18 12:50, Mark Brown wrote:
On Tue, Feb 13, 2018 at 04:58:26PM +0000, srinivas.kandagatla@linaro.org wrote:
+static int q6hdmi_format_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct q6afe_dai_data *dai_data = kcontrol->private_data;
- int value = ucontrol->value.integer.value[0];
- dai_data->port_config[AFE_PORT_HDMI_RX].hdmi.datatype = value;
- return 0;
+}
No validation, and do we not need to tell a currently running stream if the format changed on it (or block such changes if they're not going to work, which seems more likely)?
Yes, It would not work if the stream is running. This mixer has to be setup before the stream/port is prepared/started. TBH, I have no means to test Compr format, I should probably remove this control until am able to test this format.
+static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- int channels = params_channels(params);
- struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi;
- hdmi->sample_rate = params_rate(params);
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
hdmi->bit_width = 16;
break;
- case SNDRV_PCM_FORMAT_S24_LE:
hdmi->bit_width = 24;
break;
- }
This silently accepts invalid values.
Yep, I will fix this in next version.
- /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/
Coding style, spaces around the /* */.
Agreed! Will fix it in next version.
+static int q6afe_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- dai_data->is_port_started[dai->id] = false;
- return 0;
+}
If this is needed it makes me a bit worried that we've got some kind of bug with not shutting things down properly somewhere - what's going on here?
This looks over done, we do not need to set this flag in startup, as it would be properly reset in shutdown.
Will remove this function totally as its not required.
+static void q6afe_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
- struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev);
- int rc;
- rc = q6afe_port_stop(dai_data->port[dai->id]);
- if (rc < 0)
dev_err(dai->dev, "fail to close AFE port\n");
Better to print the error code so users have more information to debug the problem.
Yep.
.stream_name = "HDMI Playback",
.rates = SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_192000,
Indentation again.
Will sort it out in next version.
+static int q6afe_of_xlate_dai_name(struct snd_soc_component *component,
struct of_phandle_args *args,
const char **dai_name)
+{
- int id = args->args[0];
- int i, ret = -EINVAL;
Coding style, don't mix initialization in with other variable declarations on the same line like this.
Will fix all such instances in next version.
+int q6afe_dai_dev_remove(struct device *dev) +{
- return 0;
+}
Remove empty functions, if they can't be removed it's probably not OK for them to be empty either.
Sure will do that.
thanks, srini
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6asm dai driver which configures Q6ASM streams to pass pcm data.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/Makefile | 2 +- sound/soc/qcom/qdsp6/q6asm-dai.c | 621 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6asm.h | 2 + 3 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/qdsp6/q6asm-dai.c
diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index c7833842b878..5f2d54d573f0 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o q6afe-dai.o obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o q6routing.o -obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o +obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o q6asm-dai.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c new file mode 100644 index 000000000000..7c5e94b2ced4 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2011-2016, The Linux Foundation + * Copyright (c) 2017, Linaro Limited + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <asm/dma.h> +#include <linux/dma-mapping.h> +#include <linux/of_device.h> +#include <sound/pcm_params.h> +#include "q6asm.h" +#include "q6routing.h" +#include "q6dsp-errno.h" + +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 65536 +#define PLAYBACK_MIN_PERIOD_SIZE 128 + +enum stream_state { + Q6ASM_STREAM_IDLE = 0, + Q6ASM_STREAM_STOPPED, + Q6ASM_STREAM_RUNNING, +}; + +struct q6asm_dai_rtd { + struct snd_pcm_substream *substream; + phys_addr_t phys; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int periods; + uint16_t bits_per_sample; + uint16_t source; /* Encoding source bit mask */ + struct audio_client *audio_client; + uint16_t session_id; + enum stream_state state; +}; + +struct q6asm_dai_data { + long long int sid; +}; + +static struct snd_pcm_hardware q6asm_dai_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = (PLAYBACK_MAX_NUM_PERIODS * + PLAYBACK_MAX_PERIOD_SIZE), + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + 88200, 96000, 176400, 192000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6asm_dai_rtd *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + + switch (opcode) { + case ASM_CLIENT_EVENT_CMD_RUN_DONE: + q6asm_write_async(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + break; + case ASM_CLIENT_EVENT_CMD_EOS_DONE: + prtd->state = Q6ASM_STREAM_STOPPED; + break; + case ASM_CLIENT_EVENT_DATA_WRITE_DONE: { + prtd->pcm_irq_pos += prtd->pcm_count; + snd_pcm_period_elapsed(substream); + if (prtd->state == Q6ASM_STREAM_RUNNING) + q6asm_write_async(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + + break; + } + default: + break; + } +} + +static int q6asm_dai_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct q6asm_dai_rtd *prtd = runtime->private_data; + struct q6asm_dai_data *pdata; + int ret; + + pdata = q6asm_get_dai_data(soc_prtd->platform->dev); + if (!pdata) + return -EINVAL; + + if (!prtd || !prtd->audio_client) { + pr_err("%s: private data null or audio client freed\n", + __func__); + return -EINVAL; + } + + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + if (prtd->state) { + /* clear the previous setup if any */ + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_unmap_memory_regions(substream->stream, + prtd->audio_client); + q6routing_stream_close(soc_prtd->dai_link->id, + SNDRV_PCM_STREAM_PLAYBACK); + } + + ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client, + prtd->phys, + (prtd->pcm_size / prtd->periods), + prtd->periods); + + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed rc = %d\n", + ret); + return -ENOMEM; + } + + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM, + prtd->bits_per_sample); + if (ret < 0) { + pr_err("%s: q6asm_open_write failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + prtd->audio_client = NULL; + return -ENOMEM; + } + + prtd->session_id = q6asm_get_session_id(prtd->audio_client); + ret = q6routing_stream_open(soc_prtd->dai_link->id, LEGACY_PCM_MODE, + prtd->session_id, substream->stream); + if (ret) { + pr_err("%s: stream reg failed ret:%d\n", __func__, ret); + return ret; + } + + ret = q6asm_media_format_block_multi_ch_pcm( + prtd->audio_client, runtime->rate, + runtime->channels, NULL, + prtd->bits_per_sample); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + + prtd->state = Q6ASM_STREAM_RUNNING; + + return 0; +} + +static int q6asm_dai_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + prtd->state = Q6ASM_STREAM_STOPPED; + ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6asm_dai_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct snd_soc_dai *cpu_dai = soc_prtd->cpu_dai; + + struct q6asm_dai_rtd *prtd; + struct q6asm_dai_data *pdata; + struct device *dev = soc_prtd->platform->dev; + int ret = 0; + int stream_id; + + stream_id = cpu_dai->driver->id; + + pdata = q6asm_get_dai_data(dev); + if (!pdata) { + pr_err("Platform data not found ..\n"); + return -EINVAL; + } + + prtd = kzalloc(sizeof(struct q6asm_dai_rtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc(dev, + (q6asm_cb)event_handler, prtd, stream_id); + if (!prtd->audio_client) { + pr_info("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + +// prtd->audio_client->dev = dev; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = q6asm_dai_hardware_playback; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_integer failed\n"); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE, + PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE); + if (ret < 0) { + pr_err("constraint for buffer bytes min max ret = %d\n", + ret); + } + } + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (ret < 0) { + pr_err("constraint for period bytes step ret = %d\n", + ret); + } + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (ret < 0) { + pr_err("constraint for buffer bytes step ret = %d\n", + ret); + } + + runtime->private_data = prtd; + + snd_soc_set_runtime_hwparams(substream, &q6asm_dai_hardware_playback); + + runtime->dma_bytes = q6asm_dai_hardware_playback.buffer_bytes_max; + + + if (pdata->sid < 0) + prtd->phys = substream->dma_buffer.addr; + else + prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int q6asm_dai_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + if (prtd->audio_client) { + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_unmap_memory_regions(substream->stream, + prtd->audio_client); + q6asm_audio_client_free(prtd->audio_client); + } + q6routing_stream_close(soc_prtd->dai_link->id, + SNDRV_PCM_STREAM_PLAYBACK); + kfree(prtd); + return 0; +} + +static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int q6asm_dai_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct device *dev = soc_prtd->platform->dev->parent; + + return dma_mmap_coherent(dev, vma, + runtime->dma_area, runtime->dma_addr, + runtime->dma_bytes); +} + +static int q6asm_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + prtd->pcm_size = params_buffer_bytes(params); + prtd->periods = params_periods(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + prtd->bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + prtd->bits_per_sample = 24; + break; + } + + return 0; +} + +static struct snd_pcm_ops q6asm_dai_ops = { + .open = q6asm_dai_open, + .hw_params = q6asm_dai_hw_params, + .close = q6asm_dai_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = q6asm_dai_prepare, + .trigger = q6asm_dai_trigger, + .pointer = q6asm_dai_pointer, + .mmap = q6asm_dai_mmap, +}; + +static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm_substream *substream; + struct of_phandle_args args; + struct device_node *node; + struct q6asm_dai_data *pdata; + struct snd_pcm *pcm = rtd->pcm; + struct device *dev; + int size, ret; + + dev = rtd->platform->dev->parent; + node = dev->of_node; + pdata = q6asm_get_dai_data(rtd->platform->dev); + + ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); + if (ret < 0) + pdata->sid = -1; + else + pdata->sid = args.args[0]; + + + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + size = q6asm_dai_hardware_playback.buffer_bytes_max; + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, + &substream->dma_buffer); + if (ret) + dev_err(dev, "Cannot allocate buffer(s)\n"); + + return ret; +} + +static void q6asm_dai_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) { + substream = pcm->streams[i].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +static struct snd_soc_platform_driver q6asm_soc_platform = { + .ops = &q6asm_dai_ops, + .pcm_new = q6asm_dai_pcm_new, + .pcm_free = q6asm_dai_pcm_free, + +}; + +static const struct snd_soc_dapm_route afe_pcm_routes[] = { + {"MM_DL1", NULL, "MultiMedia1 Playback" }, + {"MM_DL2", NULL, "MultiMedia2 Playback" }, + {"MM_DL3", NULL, "MultiMedia3 Playback" }, + {"MM_DL4", NULL, "MultiMedia4 Playback" }, + {"MM_DL5", NULL, "MultiMedia5 Playback" }, + {"MM_DL6", NULL, "MultiMedia6 Playback" }, + {"MM_DL7", NULL, "MultiMedia7 Playback" }, + +}; + +static int fe_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_dapm_context *dapm; + + dapm = snd_soc_component_get_dapm(dai->component); + snd_soc_dapm_add_routes(dapm, afe_pcm_routes, + ARRAY_SIZE(afe_pcm_routes)); + + return 0; +} + +static const struct snd_soc_component_driver q6asm_fe_dai_component = { + .name = "q6asm-fe-dai", +}; + +static struct snd_soc_dai_driver q6asm_fe_dais[] = { + { + .playback = { + .stream_name = "MultiMedia1 Playback", + .rates = (SNDRV_PCM_RATE_8000_192000| + SNDRV_PCM_RATE_KNOT), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "MultiMedia1", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA1, + }, + { + .playback = { + .stream_name = "MultiMedia2 Playback", + .rates = (SNDRV_PCM_RATE_8000_192000| + SNDRV_PCM_RATE_KNOT), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "MultiMedia2", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, + { + .playback = { + .stream_name = "MultiMedia3 Playback", + .rates = (SNDRV_PCM_RATE_8000_192000| + SNDRV_PCM_RATE_KNOT), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "MultiMedia3", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA3, + }, + { + .playback = { + .stream_name = "MultiMedia4 Playback", + .rates = (SNDRV_PCM_RATE_8000_192000| + SNDRV_PCM_RATE_KNOT), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "MultiMedia4", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA4, + }, + { + .playback = { + .stream_name = "MultiMedia5 Playback", + .rates = (SNDRV_PCM_RATE_8000_192000| + SNDRV_PCM_RATE_KNOT), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "MultiMedia5", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA5, + }, + { + .playback = { + .stream_name = "MultiMedia6 Playback", + .rates = (SNDRV_PCM_RATE_8000_192000| + SNDRV_PCM_RATE_KNOT), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "MultiMedia6", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA6, + }, + { + .playback = { + .stream_name = "MultiMedia7 Playback", + .rates = (SNDRV_PCM_RATE_8000_192000| + SNDRV_PCM_RATE_KNOT), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "MultiMedia7", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA7, + }, + { + .playback = { + .stream_name = "MultiMedia8 Playback", + .rates = (SNDRV_PCM_RATE_8000_192000| + SNDRV_PCM_RATE_KNOT), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "MultiMedia8", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA8, + }, +}; + +int q6asm_dai_probe(struct device *dev) +{ + struct q6asm_dai_data *pdata; + int rc; + + pdata = devm_kzalloc(dev, sizeof(struct q6asm_dai_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + + q6asm_set_dai_data(dev, pdata); + + rc = devm_snd_soc_register_platform(dev, &q6asm_soc_platform); + if (rc) { + dev_err(dev, "err_dai_platform\n"); + return rc; + } + + return devm_snd_soc_register_component(dev, &q6asm_fe_dai_component, + q6asm_fe_dais, + ARRAY_SIZE(q6asm_fe_dais)); +} +EXPORT_SYMBOL_GPL(q6asm_dai_probe); + +int q6asm_dai_remove(struct device *dev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(q6asm_dai_remove); + +MODULE_DESCRIPTION("Q6ASM dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h index b5ef90bb724b..6595695f06c3 100644 --- a/sound/soc/qcom/qdsp6/q6asm.h +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -33,6 +33,8 @@ enum {
void q6asm_set_dai_data(struct device *dev, void *data); void *q6asm_get_dai_data(struct device *dev); +int q6asm_dai_probe(struct device *dev); +int q6asm_dai_remove(struct device *dev);
typedef void (*q6asm_cb) (uint32_t opcode, uint32_t token, void *payload, void *priv);
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6asm dai driver which configures Q6ASM streams to pass pcm data.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
[..]
diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c new file mode 100644 index 000000000000..7c5e94b2ced4 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (c) 2011-2016, The Linux Foundation
- Copyright (c) 2017, Linaro Limited
- */
+#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <asm/dma.h>
[..]
+static struct snd_pcm_hw_constraint_list constraints_sample_rates = {
- .count = ARRAY_SIZE(supported_sample_rates),
- .list = supported_sample_rates,
- .mask = 0,
+};
+static void event_handler(uint32_t opcode, uint32_t token,
uint32_t *payload, void *priv)
+{
- struct q6asm_dai_rtd *prtd = priv;
- struct snd_pcm_substream *substream = prtd->substream;
- switch (opcode) {
- case ASM_CLIENT_EVENT_CMD_RUN_DONE:
Need to add support for V2 version of opcodes
q6asm_write_async(prtd->audio_client,
prtd->pcm_count, 0, 0, NO_TIMESTAMP);
break;
- case ASM_CLIENT_EVENT_CMD_EOS_DONE:
prtd->state = Q6ASM_STREAM_STOPPED;
break;
- case ASM_CLIENT_EVENT_DATA_WRITE_DONE: {
prtd->pcm
[..]
+static int q6asm_dai_trigger(struct snd_pcm_substream *substream, int cmd) +{
- int ret = 0;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct q6asm_dai_rtd *prtd = runtime->private_data;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
break;
below two cases can be combined with START if no change
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
break;
- case SNDRV_PCM_TRIGGER_STOP:
prtd->state = Q6ASM_STREAM_STOPPED;
ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);
break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);
break;
- default:
ret = -EINVAL;
break;
- }
- return ret;
+}
+static int q6asm_dai_open(struct snd_pcm_substream *substream) +{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = soc_prtd->cpu_dai;
- struct q6asm_dai_rtd *prtd;
- struct q6asm_dai_data *pdata;
- struct device *dev = soc_prtd->platform->dev;
- int ret = 0;
- int stream_id;
- stream_id = cpu_dai->driver->id;
- pdata = q6asm_get_dai_data(dev);
- if (!pdata) {
pr_err("Platform data not found ..\n");
return -EINVAL;
- }
- prtd = kzalloc(sizeof(struct q6asm_dai_rtd), GFP_KERNEL);
- if (prtd == NULL)
return -ENOMEM;
- prtd->substream = substream;
- prtd->audio_client = q6asm_audio_client_alloc(dev,
(q6asm_cb)event_handler, prtd, stream_id);
- if (!prtd->audio_client) {
pr_info("%s: Could not allocate memory\n", __func__);
kfree(prtd);
return -ENOMEM;
- }
+// prtd->audio_client->dev = dev;
cleanup this
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = q6asm_dai_hardware_playback;
- ret = snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_sample_rates);
[..]
+static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd) +{
- struct snd_pcm_substream *substream;
- struct of_phandle_args args;
- struct device_node *node;
- struct q6asm_dai_data *pdata;
- struct snd_pcm *pcm = rtd->pcm;
- struct device *dev;
- int size, ret;
- dev = rtd->platform->dev->parent;
- node = dev->of_node;
- pdata = q6asm_get_dai_data(rtd->platform->dev);
- ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
- if (ret < 0)
pdata->sid = -1;
- else
pdata->sid = args.args[0];
iommus for sdm845 is 16bit value. we need to have sid_mask which is 0x1 in sdm845. We need to mask sid with 0x1 to get proper sid. pdata->sid &= 0x1;
- substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
- size = q6asm_dai_hardware_playback.buffer_bytes_max;
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
&substream->dma_buffer);
- if (ret)
dev_err(dev, "Cannot allocate buffer(s)\n");
- return ret;
+}
Thanks for your review Rohit,
On 21/02/18 11:14, Rohit Kumar wrote:
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6asm dai driver which configures Q6ASM streams to pass pcm data.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
[..]
diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c new file mode 100644 index 000000000000..7c5e94b2ced4 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- Copyright (c) 2011-2016, The Linux Foundation
- Copyright (c) 2017, Linaro Limited
- */
+#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <asm/dma.h>
[..]
+static struct snd_pcm_hw_constraint_list constraints_sample_rates = {
- .count = ARRAY_SIZE(supported_sample_rates),
- .list = supported_sample_rates,
- .mask = 0,
+};
+static void event_handler(uint32_t opcode, uint32_t token,
uint32_t *payload, void *priv)
+{
- struct q6asm_dai_rtd *prtd = priv;
- struct snd_pcm_substream *substream = prtd->substream;
- switch (opcode) {
- case ASM_CLIENT_EVENT_CMD_RUN_DONE:
Need to add support for V2 version of opcodes
Makes sense, I will add them.
q6asm_write_async(prtd->audio_client,
prtd->pcm_count, 0, 0, NO_TIMESTAMP);
break;
- case ASM_CLIENT_EVENT_CMD_EOS_DONE:
prtd->state = Q6ASM_STREAM_STOPPED;
break;
- case ASM_CLIENT_EVENT_DATA_WRITE_DONE: {
prtd->pcm
[..]
+static int q6asm_dai_trigger(struct snd_pcm_substream *substream, int cmd) +{
- int ret = 0;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct q6asm_dai_rtd *prtd = runtime->private_data;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
break;
below two cases can be combined with START if no change
Yep, I will do that in next version.
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0);
break;
- case SNDRV_PCM_TRIGGER_STOP:
prtd->state = Q6ASM_STREAM_STOPPED;
ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS);
break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE);
break;
- default:
ret = -EINVAL;
break;
- }
- return ret;
+}
+static int q6asm_dai_open(struct snd_pcm_substream *substream) +{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_pcm_runtime *soc_prtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = soc_prtd->cpu_dai;
- struct q6asm_dai_rtd *prtd;
- struct q6asm_dai_data *pdata;
- struct device *dev = soc_prtd->platform->dev;
- int ret = 0;
- int stream_id;
- stream_id = cpu_dai->driver->id;
- pdata = q6asm_get_dai_data(dev);
- if (!pdata) {
pr_err("Platform data not found ..\n");
return -EINVAL;
- }
- prtd = kzalloc(sizeof(struct q6asm_dai_rtd), GFP_KERNEL);
- if (prtd == NULL)
return -ENOMEM;
- prtd->substream = substream;
- prtd->audio_client = q6asm_audio_client_alloc(dev,
(q6asm_cb)event_handler, prtd, stream_id);
- if (!prtd->audio_client) {
pr_info("%s: Could not allocate memory\n", __func__);
kfree(prtd);
return -ENOMEM;
- }
+// prtd->audio_client->dev = dev;
cleanup this
Sure!
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = q6asm_dai_hardware_playback;
- ret = snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
&constraints_sample_rates);
[..]
+static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd) +{
- struct snd_pcm_substream *substream;
- struct of_phandle_args args;
- struct device_node *node;
- struct q6asm_dai_data *pdata;
- struct snd_pcm *pcm = rtd->pcm;
- struct device *dev;
- int size, ret;
- dev = rtd->platform->dev->parent;
- node = dev->of_node;
- pdata = q6asm_get_dai_data(rtd->platform->dev);
- ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
- if (ret < 0)
pdata->sid = -1;
- else
pdata->sid = args.args[0];
iommus for sdm845 is 16bit value. we need to have sid_mask which is 0x1 in sdm845. We need to mask sid with 0x1 to get proper sid. pdata->sid &= 0x1;
Okay, I will take closer look at sdm845 and other socs, and make it more generic in next version.
- substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
- size = q6asm_dai_hardware_playback.buffer_bytes_max;
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size,
&substream->dma_buffer);
- if (ret)
dev_err(dev, "Cannot allocate buffer(s)\n");
- return ret;
+}
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to 6 SLIMBus AFE ports, which are used as backend dais.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- include/dt-bindings/sound/qcom,q6afe.h | 14 ++++ sound/soc/qcom/qdsp6/q6afe.c | 129 +++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 16 +++- 3 files changed, 158 insertions(+), 1 deletion(-)
diff --git a/include/dt-bindings/sound/qcom,q6afe.h b/include/dt-bindings/sound/qcom,q6afe.h index b4d82cccdc86..e9004ee39f72 100644 --- a/include/dt-bindings/sound/qcom,q6afe.h +++ b/include/dt-bindings/sound/qcom,q6afe.h @@ -4,6 +4,20 @@
/* Audio Front End (AFE) Ports */ #define AFE_PORT_HDMI_RX 8 +#define SLIMBUS_0_RX 15 +#define SLIMBUS_0_TX 16 +#define SLIMBUS_1_RX 17 +#define SLIMBUS_1_TX 18 +#define SLIMBUS_2_RX 19 +#define SLIMBUS_2_TX 20 +#define SLIMBUS_3_RX 21 +#define SLIMBUS_3_TX 22 +#define SLIMBUS_4_RX 23 +#define SLIMBUS_4_TX 24 +#define SLIMBUS_5_RX 25 +#define SLIMBUS_5_TX 26 +#define SLIMBUS_6_RX 45 +#define SLIMBUS_6_TX 46
#endif /* __DT_BINDINGS_Q6_AFE_H__ */
diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index 0a5af06bb50e..637390f5421e 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -25,9 +25,45 @@ #define AFE_PARAM_ID_HDMI_CONFIG 0x00010210 #define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C
+#define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235 + +#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212 + /* Port IDs */ #define AFE_API_VERSION_HDMI_CONFIG 0x1 #define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E + +#define AFE_API_VERSION_SLIMBUS_CONFIG 0x1 + +/* SLIMbus Rx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000 +/* SLIMbus Tx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001 +/* SLIMbus Rx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002 +/* SLIMbus Tx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003 +/* SLIMbus Rx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004 +/* SLIMbus Tx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005 +/* SLIMbus Rx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006 +/* SLIMbus Tx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007 +/* SLIMbus Rx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008 +/* SLIMbus Tx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009 +/* SLIMbus Rx port on channel 5. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX 0x400a +/* SLIMbus Tx port on channel 5. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX 0x400b +/* SLIMbus Rx port on channel 6. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX 0x400c +/* SLIMbus Tx port on channel 6. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX 0x400d + #define TIMEOUT_MS 1000 #define AFE_CMD_RESP_AVAIL 0 #define AFE_CMD_RESP_NONE 1 @@ -82,8 +118,53 @@ struct afe_param_id_hdmi_multi_chan_audio_cfg { u16 reserved; } __packed;
+struct afe_param_id_slimbus_cfg { + u32 sb_cfg_minor_version; +/* Minor version used for tracking the version of the SLIMBUS + * configuration interface. + * Supported values: #AFE_API_VERSION_SLIMBUS_CONFIG + */ + + u16 slimbus_dev_id; +/* SLIMbus hardware device ID, which is required to handle + * multiple SLIMbus hardware blocks. + * Supported values: - #AFE_SLIMBUS_DEVICE_1 - #AFE_SLIMBUS_DEVICE_2 + */ + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + u16 data_format; +/* Data format supported by the SLIMbus hardware. The default is + * 0 (#AFE_SB_DATA_FORMAT_NOT_INDICATED), which indicates the + * hardware does not perform any format conversions before the data + * transfer. + */ + u16 num_channels; +/* Number of channels. + * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT + */ + u8 shared_ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; +/* Mapping of shared channel IDs (128 to 255) to which the + * master port is to be connected. + * Shared_channel_mapping[i] represents the shared channel assigned + * for audio channel i in multichannel audio data. + */ + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - #AFE_PORT_SAMPLE_RATE_192K + */ +} __packed; + + union afe_port_config { struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; + struct afe_param_id_slimbus_cfg slim_cfg; } __packed;
struct q6afe_port { @@ -114,6 +195,20 @@ struct afe_port_map { static struct afe_port_map port_maps[AFE_PORT_MAX] = { [AFE_PORT_HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX, AFE_PORT_HDMI_RX, 1, 1}, + [SLIMBUS_0_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX, + SLIMBUS_0_RX, 1, 1}, + [SLIMBUS_1_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX, + SLIMBUS_1_RX, 1, 1}, + [SLIMBUS_2_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX, + SLIMBUS_2_RX, 1, 1}, + [SLIMBUS_3_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX, + SLIMBUS_3_RX, 1, 1}, + [SLIMBUS_4_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX, + SLIMBUS_4_RX, 1, 1}, + [SLIMBUS_5_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX, + SLIMBUS_5_RX, 1, 1}, + [SLIMBUS_6_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX, + SLIMBUS_6_RX, 1, 1}, };
static struct q6afe_port *afe_find_port(struct q6afe *afe, int token) @@ -372,6 +467,31 @@ void *q6afe_get_dai_data(struct device *dev) } EXPORT_SYMBOL_GPL(q6afe_get_dai_data);
+/** + * q6afe_slim_port_prepare() - Prepare slim afe port. + * + * @port: Instance of afe port + * @cfg: SLIM configuration for the afe port + * + */ +void q6afe_slim_port_prepare(struct q6afe_port *port, + struct q6afe_slim_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->slim_cfg.sb_cfg_minor_version = AFE_API_VERSION_SLIMBUS_CONFIG; + pcfg->slim_cfg.sample_rate = cfg->sample_rate; + pcfg->slim_cfg.bit_width = cfg->bit_width; + pcfg->slim_cfg.num_channels = cfg->num_channels; + pcfg->slim_cfg.data_format = cfg->data_format; + pcfg->slim_cfg.shared_ch_mapping[0] = cfg->ch_mapping[0]; + pcfg->slim_cfg.shared_ch_mapping[1] = cfg->ch_mapping[1]; + pcfg->slim_cfg.shared_ch_mapping[2] = cfg->ch_mapping[2]; + pcfg->slim_cfg.shared_ch_mapping[3] = cfg->ch_mapping[3]; + +} +EXPORT_SYMBOL_GPL(q6afe_slim_port_prepare); + /** * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. * @@ -433,6 +553,15 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) case AFE_PORT_ID_MULTICHAN_HDMI_RX: cfg_type = AFE_PARAM_ID_HDMI_CONFIG; break; + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX: + cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG; + break; default: dev_err(dev, "Invalid port id 0x%x\n", port_id); return ERR_PTR(-EINVAL); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index 647ed2d15545..aeacf1f2c9a9 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -5,12 +5,15 @@
#include <dt-bindings/sound/qcom,q6afe.h>
-#define AFE_PORT_MAX 9 +#define AFE_PORT_MAX 48
#define MSM_AFE_PORT_TYPE_RX 0 #define MSM_AFE_PORT_TYPE_TX 1 #define AFE_MAX_PORTS AFE_PORT_MAX
+#define AFE_MAX_CHAN_COUNT 8 +#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8 + struct q6afe_hdmi_cfg { u16 datatype; u16 channel_allocation; @@ -18,8 +21,17 @@ struct q6afe_hdmi_cfg { u16 bit_width; };
+struct q6afe_slim_cfg { + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 num_channels; + u8 ch_mapping[AFE_MAX_CHAN_COUNT]; +}; + struct q6afe_port_config { struct q6afe_hdmi_cfg hdmi; + struct q6afe_slim_cfg slim; };
struct q6afe_port; @@ -36,5 +48,7 @@ void q6afe_port_put(struct q6afe_port *port); int q6afe_get_port_id(int index); void q6afe_hdmi_port_prepare(struct q6afe_port *port, struct q6afe_hdmi_cfg *cfg); +void q6afe_slim_port_prepare(struct q6afe_port *port, + struct q6afe_slim_cfg *cfg);
#endif /* __Q6AFE_H__ */
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to SLIMBus AFE backend dais.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/q6afe-dai.c | 211 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c index f6a618e9db9e..39badb701be0 100644 --- a/sound/soc/qcom/qdsp6/q6afe-dai.c +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -59,6 +59,37 @@ static const struct snd_kcontrol_new q6afe_config_controls[] = { q6hdmi_format_put), };
+static int q6slim_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + struct q6afe_slim_cfg *slim = &dai_data->port_config[dai->id].slim; + + slim->num_channels = params_channels(params); + slim->sample_rate = params_rate(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_SPECIAL: + slim->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + slim->bit_width = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + slim->bit_width = 32; + break; + default: + pr_err("%s: format %d\n", + __func__, params_format(params)); + return -EINVAL; + } + + return 0; +} + static int q6hdmi_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -150,6 +181,9 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, if (dai->id == AFE_PORT_HDMI_RX) q6afe_hdmi_port_prepare(dai_data->port[dai->id], &dai_data->port_config[dai->id].hdmi); + else if (dai->id >= SLIMBUS_0_RX && dai->id <= SLIMBUS_6_TX) + q6afe_slim_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].slim);
rc = q6afe_port_start(dai_data->port[dai->id]); if (rc < 0) { @@ -161,8 +195,44 @@ static int q6afe_dai_prepare(struct snd_pcm_substream *substream, return 0; }
+static int q6slim_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + struct q6afe_port_config *pcfg = &dai_data->port_config[dai->id]; + int i; + + if (!rx_slot) { + pr_err("%s: rx slot not found\n", __func__); + return -EINVAL; + } + + for (i = 0; i < rx_num; i++) { + pcfg->slim.ch_mapping[i] = rx_slot[i]; + pr_debug("%s: find number of channels[%d] ch[%d]\n", + __func__, i, rx_slot[i]); + } + + pcfg->slim.num_channels = rx_num; + + pr_debug("%s: SLIMBUS_%d_RX cnt[%d] ch[%d %d]\n", __func__, + (dai->id - SLIMBUS_0_RX) / 2, rx_num, + pcfg->slim.ch_mapping[0], + pcfg->slim.ch_mapping[1]); + + return 0; +} + static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { {"HDMI Playback", NULL, "HDMI_RX"}, + {"Slimbus1 Playback", NULL, "SLIMBUS_1_RX"}, + {"Slimbus2 Playback", NULL, "SLIMBUS_2_RX"}, + {"Slimbus3 Playback", NULL, "SLIMBUS_3_RX"}, + {"Slimbus4 Playback", NULL, "SLIMBUS_4_RX"}, + {"Slimbus5 Playback", NULL, "SLIMBUS_5_RX"}, + {"Slimbus6 Playback", NULL, "SLIMBUS_6_RX"}, + };
static struct snd_soc_dai_ops q6hdmi_ops = { @@ -172,6 +242,14 @@ static struct snd_soc_dai_ops q6hdmi_ops = { .startup = q6afe_dai_startup, };
+static struct snd_soc_dai_ops q6slim_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6slim_hw_params, + .shutdown = q6afe_dai_shutdown, + .startup = q6afe_dai_startup, + .set_channel_map = q6slim_set_channel_map, +}; + static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) { struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); @@ -218,6 +296,132 @@ static struct snd_soc_dai_driver q6afe_dais[] = { .name = "HDMI", .probe = msm_dai_q6_dai_probe, .remove = msm_dai_q6_dai_remove, + }, { + .name = "SLIMBUS_0_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_0_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .playback = { + .stream_name = "Slimbus Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus1 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_1_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_1_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Slimbus2 Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_2_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_2_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Slimbus3 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_3_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_3_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Slimbus4 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_4_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_4_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Slimbus5 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_5_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_5_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Slimbus6 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000 | SNDRV_PCM_RATE_44100, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .ops = &q6slim_ops, + .name = "SLIMBUS_6_RX", + .id = SLIMBUS_6_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, }, };
@@ -241,6 +445,13 @@ static int q6afe_of_xlate_dai_name(struct snd_soc_component *component,
static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { SND_SOC_DAPM_AIF_OUT("HDMI_RX", "HDMI Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_RX", "Slimbus Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_RX", "Slimbus1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_2_RX", "Slimbus2 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_3_RX", "Slimbus3 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_RX", "Slimbus4 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_5_RX", "Slimbus5 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_6_RX", "Slimbus6 Playback", 0, 0, 0, 0), };
static const struct snd_soc_component_driver q6afe_dai_component = {
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to SLIMBus related mixers to control mux between ASM stream and AFE port.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/q6routing.c | 261 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index 828243c58569..8c9b5d899ba7 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -232,6 +232,180 @@ static const struct snd_kcontrol_new hdmi_mixer_controls[] = { msm_routing_put_audio_mixer), };
+static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_2_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_3_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_4_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_5_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_6_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { /* Frontend AIF */ SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, 0, 0, 0), @@ -247,6 +421,28 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0, hdmi_mixer_controls, ARRAY_SIZE(hdmi_mixer_controls)), + + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_rx_mixer_controls, + ARRAY_SIZE(slimbus_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_1_rx_mixer_controls, + ARRAY_SIZE(slimbus_1_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_2_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_2_rx_mixer_controls, + ARRAY_SIZE(slimbus_2_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_3_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_3_rx_mixer_controls, + ARRAY_SIZE(slimbus_3_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_4_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_4_rx_mixer_controls, + ARRAY_SIZE(slimbus_4_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_5_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_5_rx_mixer_controls, + ARRAY_SIZE(slimbus_5_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_6_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_6_rx_mixer_controls, + ARRAY_SIZE(slimbus_6_rx_mixer_controls)), };
static const struct snd_soc_dapm_route intercon[] = { @@ -259,6 +455,71 @@ static const struct snd_soc_dapm_route intercon[] = { {"HDMI Mixer", "MultiMedia7", "MM_DL7"}, {"HDMI Mixer", "MultiMedia8", "MM_DL8"}, {"HDMI_RX", NULL, "HDMI Mixer"}, + + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Audio Mixer"}, + + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Audio Mixer"}, + + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_2_RX", NULL, "SLIMBUS_2_RX Audio Mixer"}, + + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_3_RX", NULL, "SLIMBUS_3_RX Audio Mixer"}, + + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_4_RX", NULL, "SLIMBUS_4_RX Audio Mixer"}, + + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_5_RX", NULL, "SLIMBUS_5_RX Audio Mixer"}, + + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_6_RX", NULL, "SLIMBUS_6_RX Audio Mixer"}, };
static int routing_hw_params(struct snd_pcm_substream *substream,
The patch
ASoC: qdsp6: q6routing: Add support to all SLIMBus Mixers
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 794fe039381c334381bea8cfdb595b2c5088627e Mon Sep 17 00:00:00 2001
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org Date: Fri, 18 May 2018 13:56:05 +0100 Subject: [PATCH] ASoC: qdsp6: q6routing: Add support to all SLIMBus Mixers
This patch adds support to SLIMBus related mixers to control mux between ASM stream and AFE port.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org Reviewed-and-tested-by: Rohit kumar rohitkr@codeaurora.org Reviewed-by: Banajit Goswami bgoswami@codeaurora.org Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/qcom/qdsp6/q6routing.c | 261 +++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index 1d7a4088a435..43086913060a 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -241,6 +241,180 @@ static const struct snd_kcontrol_new hdmi_mixer_controls[] = { msm_routing_put_audio_mixer), };
+static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_1_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_2_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_2_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_3_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_3_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_4_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_4_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_5_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_5_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_6_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SLIMBUS_6_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { /* Frontend AIF */ SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, 0, 0, 0), @@ -264,6 +438,28 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0, hdmi_mixer_controls, ARRAY_SIZE(hdmi_mixer_controls)), + + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_rx_mixer_controls, + ARRAY_SIZE(slimbus_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_1_rx_mixer_controls, + ARRAY_SIZE(slimbus_1_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_2_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_2_rx_mixer_controls, + ARRAY_SIZE(slimbus_2_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_3_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_3_rx_mixer_controls, + ARRAY_SIZE(slimbus_3_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_4_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_4_rx_mixer_controls, + ARRAY_SIZE(slimbus_4_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_5_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_5_rx_mixer_controls, + ARRAY_SIZE(slimbus_5_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_6_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_6_rx_mixer_controls, + ARRAY_SIZE(slimbus_6_rx_mixer_controls)), };
static const struct snd_soc_dapm_route intercon[] = { @@ -276,6 +472,71 @@ static const struct snd_soc_dapm_route intercon[] = { {"HDMI Mixer", "MultiMedia7", "MM_DL7"}, {"HDMI Mixer", "MultiMedia8", "MM_DL8"}, {"HDMI_RX", NULL, "HDMI Mixer"}, + + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Audio Mixer"}, + + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_1_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_1_RX", NULL, "SLIMBUS_1_RX Audio Mixer"}, + + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_2_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_2_RX", NULL, "SLIMBUS_2_RX Audio Mixer"}, + + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_3_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_3_RX", NULL, "SLIMBUS_3_RX Audio Mixer"}, + + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_4_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_4_RX", NULL, "SLIMBUS_4_RX Audio Mixer"}, + + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_5_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_5_RX", NULL, "SLIMBUS_5_RX Audio Mixer"}, + + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SLIMBUS_6_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"SLIMBUS_6_RX", NULL, "SLIMBUS_6_RX Audio Mixer"}, };
static int routing_hw_params(struct snd_pcm_substream *substream,
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- include/dt-bindings/sound/qcom,q6afe.h | 10 +++ sound/soc/qcom/qdsp6/q6afe.c | 111 +++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 10 +++ 3 files changed, 131 insertions(+)
diff --git a/include/dt-bindings/sound/qcom,q6afe.h b/include/dt-bindings/sound/qcom,q6afe.h index e9004ee39f72..3cd862262369 100644 --- a/include/dt-bindings/sound/qcom,q6afe.h +++ b/include/dt-bindings/sound/qcom,q6afe.h @@ -16,6 +16,16 @@ #define SLIMBUS_4_TX 24 #define SLIMBUS_5_RX 25 #define SLIMBUS_5_TX 26 +#define QUATERNARY_MI2S_RX 34 +#define QUATERNARY_MI2S_TX 35 +#define SECONDARY_MI2S_RX 36 +#define SECONDARY_MI2S_TX 37 +#define TERTIARY_MI2S_RX 38 +#define TERTIARY_MI2S_TX 39 +#define PRIMARY_MI2S_RX 40 +#define PRIMARY_MI2S_TX 41 +#define SECONDARY_PCM_RX 42 +#define SECONDARY_PCM_TX 43 #define SLIMBUS_6_RX 45 #define SLIMBUS_6_TX 46
diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index 637390f5421e..c04caea5481c 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -14,6 +14,10 @@ #include <linux/of.h> #include <linux/delay.h> #include <linux/soc/qcom/apr.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> #include "q6dsp-errno.h" #include "q6afe.h"
@@ -28,6 +32,24 @@ #define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235
#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212 +#define AFE_PARAM_ID_I2S_CONFIG 0x0001020D + +/* I2S config specific */ +#define AFE_API_VERSION_I2S_CONFIG 0x1 +#define AFE_PORT_I2S_SD0 0x1 +#define AFE_PORT_I2S_SD1 0x2 +#define AFE_PORT_I2S_SD2 0x3 +#define AFE_PORT_I2S_SD3 0x4 +#define AFE_PORT_I2S_QUAD01 0x5 +#define AFE_PORT_I2S_QUAD23 0x6 +#define AFE_PORT_I2S_6CHS 0x7 +#define AFE_PORT_I2S_8CHS 0x8 +#define AFE_PORT_I2S_MONO 0x0 +#define AFE_PORT_I2S_STEREO 0x1 +#define AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL 0x0 +#define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1 +#define AFE_LINEAR_PCM_DATA 0x0 +
/* Port IDs */ #define AFE_API_VERSION_HDMI_CONFIG 0x1 @@ -63,6 +85,14 @@ #define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX 0x400c /* SLIMbus Tx port on channel 6. */ #define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX 0x400d +#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000 +#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001 +#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002 +#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003 +#define AFE_PORT_ID_TERTIARY_MI2S_RX 0x1004 +#define AFE_PORT_ID_TERTIARY_MI2S_TX 0x1005 +#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006 +#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007
#define TIMEOUT_MS 1000 #define AFE_CMD_RESP_AVAIL 0 @@ -161,10 +191,21 @@ struct afe_param_id_slimbus_cfg { */ } __packed;
+struct afe_param_id_i2s_cfg { + u32 i2s_cfg_minor_version; + u16 bit_width; + u16 channel_mode; + u16 mono_stereo; + u16 ws_src; + u32 sample_rate; + u16 data_format; + u16 reserved; +} __packed;
union afe_port_config { struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; struct afe_param_id_slimbus_cfg slim_cfg; + struct afe_param_id_i2s_cfg i2s_cfg; } __packed;
struct q6afe_port { @@ -207,6 +248,14 @@ static struct afe_port_map port_maps[AFE_PORT_MAX] = { SLIMBUS_4_RX, 1, 1}, [SLIMBUS_5_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX, SLIMBUS_5_RX, 1, 1}, + [QUATERNARY_MI2S_RX] = { AFE_PORT_ID_QUATERNARY_MI2S_RX, + QUATERNARY_MI2S_RX, 1, 1}, + [SECONDARY_MI2S_RX] = { AFE_PORT_ID_SECONDARY_MI2S_RX, + SECONDARY_MI2S_RX, 1, 1}, + [TERTIARY_MI2S_RX] = { AFE_PORT_ID_TERTIARY_MI2S_RX, + TERTIARY_MI2S_RX, 1, 1}, + [PRIMARY_MI2S_RX] = { AFE_PORT_ID_PRIMARY_MI2S_RX, + PRIMARY_MI2S_RX, 1, 1}, [SLIMBUS_6_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX, SLIMBUS_6_RX, 1, 1}, }; @@ -513,6 +562,61 @@ void q6afe_hdmi_port_prepare(struct q6afe_port *port, } EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare);
+/** + * q6afe_i2s_port_prepare() - Prepare i2s afe port. + * + * @port: Instance of afe port + * @cfg: I2S configuration for the afe port + * + */ +void q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->i2s_cfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + pcfg->i2s_cfg.sample_rate = cfg->sample_rate; + pcfg->i2s_cfg.bit_width = cfg->bit_width; + pcfg->i2s_cfg.data_format = AFE_LINEAR_PCM_DATA; + + switch (cfg->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* CPU is slave */ + pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL; + break; + default: + break; + } + + switch (cfg->num_channels) { + case 1: + pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_MONO; + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0; + break; + case 2: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0; + pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_STEREO; + break; + case 3: + case 4: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD01; + break; + case 5: + case 6: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_6CHS; + break; + case 7: + case 8: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_8CHS; + break; + default: + break; + } +} +EXPORT_SYMBOL_GPL(q6afe_i2s_port_prepare); + /** * q6afe_port_start() - Start a afe port * @@ -562,6 +666,13 @@ struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX: cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG; break; + + case AFE_PORT_ID_PRIMARY_MI2S_RX: + case AFE_PORT_ID_SECONDARY_MI2S_RX: + case AFE_PORT_ID_TERTIARY_MI2S_RX: + case AFE_PORT_ID_QUATERNARY_MI2S_RX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; default: dev_err(dev, "Invalid port id 0x%x\n", port_id); return ERR_PTR(-EINVAL); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index aeacf1f2c9a9..9114d68a79cb 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -29,9 +29,18 @@ struct q6afe_slim_cfg { u8 ch_mapping[AFE_MAX_CHAN_COUNT]; };
+struct q6afe_i2s_cfg { + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 num_channels; + int fmt; +}; + struct q6afe_port_config { struct q6afe_hdmi_cfg hdmi; struct q6afe_slim_cfg slim; + struct q6afe_i2s_cfg i2s_cfg; };
struct q6afe_port; @@ -50,5 +59,6 @@ void q6afe_hdmi_port_prepare(struct q6afe_port *port, struct q6afe_hdmi_cfg *cfg); void q6afe_slim_port_prepare(struct q6afe_port *port, struct q6afe_slim_cfg *cfg); +void q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg);
#endif /* __Q6AFE_H__ */
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
include/dt-bindings/sound/qcom,q6afe.h | 10 +++ sound/soc/qcom/qdsp6/q6afe.c | 111 +++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 10 +++ 3 files changed, 131 insertions(+)
diff --git a/include/dt-bindings/sound/qcom,q6afe.h b/include/dt-bindings/sound/qcom,q6afe.h index e9004ee39f72..3cd862262369 100644 --- a/include/dt-bindings/sound/qcom,q6afe.h +++ b/include/dt-bindings/sound/qcom,q6afe.h @@ -16,6 +16,16 @@ #define SLIMBUS_4_TX 24 #define SLIMBUS_5_RX 25 #define SLIMBUS_5_TX 26 +#define QUATERNARY_MI2S_RX 34 +#define QUATERNARY_MI2S_TX 35 +#define SECONDARY_MI2S_RX 36 +#define SECONDARY_MI2S_TX 37 +#define TERTIARY_MI2S_RX 38 +#define TERTIARY_MI2S_TX 39 +#define PRIMARY_MI2S_RX 40 +#define PRIMARY_MI2S_TX 41
Can we assign ids to Primary, secondary, tertiary and quaternary MI2S ports in sequence starting with Primary.
+#define SECONDARY_PCM_RX 42 +#define SECONDARY_PCM_TX 43
Why only SECONDARY_PCM_RX ? This is not required for MI2S right?
#define SLIMBUS_6_RX 45 #define SLIMBUS_6_TX 46
[..]
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to LPASS Bit clock, LPASS Digital core clock and OSR clock. These clocks are required for both MI2S and PCM setup.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/q6afe.c | 115 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 10 ++++ 2 files changed, 125 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index c04caea5481c..ea6fa3848007 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -31,6 +31,9 @@
#define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235
+#define AFE_PARAM_ID_LPAIF_CLK_CONFIG 0x00010238 +#define AFE_PARAM_ID_INTERNAL_DIGITAL_CDC_CLK_CONFIG 0x00010239 + #define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212 #define AFE_PARAM_ID_I2S_CONFIG 0x0001020D
@@ -94,6 +97,11 @@ #define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006 #define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007
+#define Q6AFE_LPASS_MODE_CLK1_VALID 1 +#define Q6AFE_LPASS_MODE_CLK2_VALID 2 +#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1 +#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0 + #define TIMEOUT_MS 1000 #define AFE_CMD_RESP_AVAIL 0 #define AFE_CMD_RESP_NONE 1 @@ -191,6 +199,23 @@ struct afe_param_id_slimbus_cfg { */ } __packed;
+struct afe_clk_cfg { + u32 i2s_cfg_minor_version; + u32 clk_val1; + u32 clk_val2; + u16 clk_src; + u16 clk_root; + u16 clk_set_mode; + u16 reserved; +} __packed; + +struct afe_digital_clk_cfg { + u32 i2s_cfg_minor_version; + u32 clk_val; + u16 clk_root; + u16 reserved; +} __packed; + struct afe_param_id_i2s_cfg { u32 i2s_cfg_minor_version; u16 bit_width; @@ -208,6 +233,21 @@ union afe_port_config { struct afe_param_id_i2s_cfg i2s_cfg; } __packed;
+struct afe_lpass_clk_config_command { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + struct afe_clk_cfg clk_cfg; +} __packed; + +struct afe_lpass_digital_clk_config_command { + struct apr_hdr hdr; + struct afe_port_cmd_set_param_v2 param; + struct afe_port_param_data_v2 pdata; + struct afe_digital_clk_cfg clk_cfg; +} __packed; + +/* This param id is used to configure internal clk */ struct q6afe_port { wait_queue_head_t wait; union afe_port_config port_cfg; @@ -425,6 +465,81 @@ static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data, return ret; }
+static int q6afe_set_lpass_clock(struct q6afe_port *port, + struct afe_clk_cfg *cfg) +{ + struct afe_lpass_clk_config_command clk_cfg = {0}; + int param_id = AFE_PARAM_ID_LPAIF_CLK_CONFIG; + struct q6afe *afe = port->afe; + + if (!cfg) { + dev_err(afe->dev, "clock cfg is NULL\n"); + return -EINVAL; + } + + clk_cfg.clk_cfg = *cfg; + + return q6afe_port_set_param_v2(port, &clk_cfg, param_id, sizeof(*cfg)); +} + +static int q6afe_set_digital_codec_core_clock(struct q6afe_port *port, + struct afe_digital_clk_cfg *cfg) +{ + struct afe_lpass_digital_clk_config_command clk_cfg = {0}; + int param_id = AFE_PARAM_ID_INTERNAL_DIGITAL_CDC_CLK_CONFIG; + struct q6afe *afe = port->afe; + + if (!cfg) { + dev_err(afe->dev, "clock cfg is NULL\n"); + return -EINVAL; + } + + clk_cfg.clk_cfg = *cfg; + + return q6afe_port_set_param_v2(port, &clk_cfg, param_id, sizeof(*cfg)); +} + +int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, + int clk_src, int clk_root, + unsigned int freq, int dir) +{ + struct afe_clk_cfg ccfg = {0,}; + struct afe_digital_clk_cfg dcfg = {0,}; + int ret; + + switch (clk_id) { + case LPAIF_DIG_CLK: + dcfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + dcfg.clk_val = freq; + dcfg.clk_root = clk_root; + ret = q6afe_set_digital_codec_core_clock(port, &dcfg); + break; + case LPAIF_BIT_CLK: + ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + ccfg.clk_val1 = freq; + ccfg.clk_src = clk_src; + ccfg.clk_root = clk_root; + ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK1_VALID; + ret = q6afe_set_lpass_clock(port, &ccfg); + break; + + case LPAIF_OSR_CLK: + ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + ccfg.clk_val2 = freq; + ccfg.clk_src = clk_src; + ccfg.clk_root = clk_root; + ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK2_VALID; + ret = q6afe_set_lpass_clock(port, &ccfg); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(q6afe_port_set_sysclk); + static int afe_port_start(struct q6afe_port *port, union afe_port_config *afe_config) { diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index 9114d68a79cb..c469f6de38d2 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -14,6 +14,13 @@ #define AFE_MAX_CHAN_COUNT 8 #define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8
+#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1 +#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0 + +#define LPAIF_DIG_CLK 1 +#define LPAIF_BIT_CLK 2 +#define LPAIF_OSR_CLK 3 + struct q6afe_hdmi_cfg { u16 datatype; u16 channel_allocation; @@ -61,4 +68,7 @@ void q6afe_slim_port_prepare(struct q6afe_port *port, struct q6afe_slim_cfg *cfg); void q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg);
+int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, + int clk_src, int clk_root, + unsigned int freq, int dir); #endif /* __Q6AFE_H__ */
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to 4 MI2S dais supported on LPASS IP.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/q6afe-dai.c | 154 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c index 39badb701be0..b6db1ae654e8 100644 --- a/sound/soc/qcom/qdsp6/q6afe-dai.c +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -139,6 +139,31 @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream, return 0; }
+static int q6i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg; + + + i2s->sample_rate = params_rate(params); + i2s->bit_width = params_width(params); + i2s->num_channels = params_channels(params); + + return 0; +} + +static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg; + + i2s->fmt = fmt; + + return 0; +} + static int q6afe_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -163,6 +188,34 @@ static void q6afe_dai_shutdown(struct snd_pcm_substream *substream,
}
+static int q6afe_mi2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + int rc; + + if (dai_data->is_port_started[dai->id]) { + /* stop the port and restart with new port config */ + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to close AFE port\n"); + return rc; + } + } + + q6afe_i2s_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].i2s_cfg); + + rc = q6afe_port_start(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to start AFE port %x\n", dai->id); + return rc; + } + dai_data->is_port_started[dai->id] = true; + + return 0; +} + static int q6afe_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -224,6 +277,26 @@ static int q6slim_set_channel_map(struct snd_soc_dai *dai, return 0; }
+static int q6afe_mi2s_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct q6afe_dai_data *dai_data = q6afe_get_dai_data(dai->dev); + struct q6afe_port *port = dai_data->port[dai->id]; + + switch (clk_id) { + case LPAIF_DIG_CLK: + return q6afe_port_set_sysclk(port, clk_id, 0, 5, freq, dir); + case LPAIF_BIT_CLK: + case LPAIF_OSR_CLK: + return q6afe_port_set_sysclk(port, clk_id, + Q6AFE_LPASS_CLK_SRC_INTERNAL, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, + freq, dir); + } + + return 0; +} + static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { {"HDMI Playback", NULL, "HDMI_RX"}, {"Slimbus1 Playback", NULL, "SLIMBUS_1_RX"}, @@ -233,6 +306,10 @@ static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { {"Slimbus5 Playback", NULL, "SLIMBUS_5_RX"}, {"Slimbus6 Playback", NULL, "SLIMBUS_6_RX"},
+ {"Primary MI2S Playback", NULL, "PRI_MI2S_RX"}, + {"Secondary MI2S Playback", NULL, "SEC_MI2S_RX"}, + {"Tertiary MI2S Playback", NULL, "TERT_MI2S_RX"}, + {"Quaternary MI2S Playback", NULL, "QUAT_MI2S_RX"}, };
static struct snd_soc_dai_ops q6hdmi_ops = { @@ -242,6 +319,15 @@ static struct snd_soc_dai_ops q6hdmi_ops = { .startup = q6afe_dai_startup, };
+static struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6afe_mi2s_prepare, + .hw_params = q6i2s_hw_params, + .set_fmt = q6i2s_set_fmt, + .shutdown = q6afe_dai_shutdown, + .startup = q6afe_dai_startup, + .set_sysclk = q6afe_mi2s_set_sysclk, +}; + static struct snd_soc_dai_ops q6slim_ops = { .prepare = q6afe_dai_prepare, .hw_params = q6slim_hw_params, @@ -422,6 +508,63 @@ static struct snd_soc_dai_driver q6afe_dais[] = { .id = SLIMBUS_6_RX, .probe = msm_dai_q6_dai_probe, .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Primary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = PRIMARY_MI2S_RX, + .name = "PRI_MI2S_RX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Secondary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "SEC_MI2S_RX", + .id = SECONDARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Tertiary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "TERT_MI2S_RX", + .id = TERTIARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Quaternary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "QUAT_MI2S_RX", + .id = QUATERNARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, }, };
@@ -452,6 +595,17 @@ static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_RX", "Slimbus4 Playback", 0, 0, 0, 0), SND_SOC_DAPM_AIF_OUT("SLIMBUS_5_RX", "Slimbus5 Playback", 0, 0, 0, 0), SND_SOC_DAPM_AIF_OUT("SLIMBUS_6_RX", "Slimbus6 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_MI2S_RX", "Quaternary MI2S Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_MI2S_RX", "Tertiary MI2S Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_MI2S_RX", "Secondary MI2S Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_MI2S_RX_SD1", + "Secondary MI2S Playback SD1", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRI_MI2S_RX", "Primary MI2S Playback", + 0, 0, 0, 0), };
static const struct snd_soc_component_driver q6afe_dai_component = {
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add support to MI2S mixers required to select path between ASM stream and AFE ports.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/qdsp6/q6routing.c | 142 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+)
diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index 8c9b5d899ba7..8ffcc49a79a1 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -232,6 +232,102 @@ static const struct snd_kcontrol_new hdmi_mixer_controls[] = { msm_routing_put_audio_mixer), };
+static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", PRIMARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", PRIMARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", PRIMARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", PRIMARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", PRIMARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", PRIMARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", PRIMARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", PRIMARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new secondary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", SECONDARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", SECONDARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", SECONDARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", SECONDARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", SECONDARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", SECONDARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", SECONDARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", SECONDARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", QUATERNARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", QUATERNARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", QUATERNARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", QUATERNARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia5", QUATERNARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia6", QUATERNARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia7", QUATERNARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia8", QUATERNARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", TERTIARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", TERTIARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", TERTIARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia4", TERTIARY_MI2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = { SOC_SINGLE_EXT("MultiMedia1", SLIMBUS_0_RX, MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, @@ -443,6 +539,18 @@ static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { SND_SOC_DAPM_MIXER("SLIMBUS_6_RX Audio Mixer", SND_SOC_NOPM, 0, 0, slimbus_6_rx_mixer_controls, ARRAY_SIZE(slimbus_6_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("PRI_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + primary_mi2s_rx_mixer_controls, + ARRAY_SIZE(primary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + secondary_mi2s_rx_mixer_controls, + ARRAY_SIZE(secondary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + quaternary_mi2s_rx_mixer_controls, + ARRAY_SIZE(quaternary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + tertiary_mi2s_rx_mixer_controls, + ARRAY_SIZE(tertiary_mi2s_rx_mixer_controls)), };
static const struct snd_soc_dapm_route intercon[] = { @@ -520,6 +628,40 @@ static const struct snd_soc_dapm_route intercon[] = { {"SLIMBUS_6_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, {"SLIMBUS_6_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, {"SLIMBUS_6_RX", NULL, "SLIMBUS_6_RX Audio Mixer"}, + + {"QUAT_MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"QUAT_MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"QUAT_MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"QUAT_MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"QUAT_MI2S_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"QUAT_MI2S_RX Audio Mixer", "MultiMedia6", "MM_DL6"}, + {"QUAT_MI2S_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"QUAT_MI2S_RX Audio Mixer", "MultiMedia8", "MM_DL8"}, + {"QUAT_MI2S_RX", NULL, "QUAT_MI2S_RX Audio Mixer"}, + + {"TERT_MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"TERT_MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"TERT_MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"TERT_MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"TERT_MI2S_RX", NULL, "TERT_MI2S_RX Audio Mixer"}, + + {"SEC_MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SEC_MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SEC_MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SEC_MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"SEC_MI2S_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"SEC_MI2S_RX Audio Mixer", "MultiMedia6", "MM_DL5"}, + {"SEC_MI2S_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"SEC_MI2S_RX Audio Mixer", "MultiMedia8", "MM_DL7"}, + {"SEC_MI2S_RX", NULL, "SEC_MI2S_RX Audio Mixer"}, + + {"PRI_MI2S_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"PRI_MI2S_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"PRI_MI2S_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"PRI_MI2S_RX Audio Mixer", "MultiMedia4", "MM_DL4"}, + {"PRI_MI2S_RX Audio Mixer", "MultiMedia5", "MM_DL5"}, + {"PRI_MI2S_RX Audio Mixer", "MultiMedia7", "MM_DL7"}, + {"PRI_MI2S_RX", NULL, "PRI_MI2S_RX Audio Mixer"}, };
static int routing_hw_params(struct snd_pcm_substream *substream,
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
Add devicetree bindings documentation file for Qualcomm apq8096 sound card.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- .../devicetree/bindings/sound/qcom,apq8096.txt | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/qcom,apq8096.txt
diff --git a/Documentation/devicetree/bindings/sound/qcom,apq8096.txt b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt new file mode 100644 index 000000000000..58cd506477c0 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt @@ -0,0 +1,89 @@ +* Qualcomm Technologies APQ8096 ASoC sound card driver + +This binding describes the APQ8096 sound card, which uses qdsp for audio. + +- compatible: + Usage: required + Value type: <stringlist> + Definition: must be "qcom,apq8096-sndcard" + +- qcom,audio-routing: + Usage: Optional + Value type: <stringlist> + Definition: A list of the connections between audio components. + Each entry is a pair of strings, the first being the + connection's sink, the second being the connection's + source. Valid names could be power supplies, MicBias + of codec and the jacks on the board: + += FRONTEND and BACKEND dailinks +Each subnode of sndcard represents either frontend or backend dailink, +and subnodes of each backend/frontend dailinks would be +cpu/codec/platform dais. + +- link-name: + Usage: required + Value type: <string> + Definition: User friendly name for dai link + +- is-fe: + Usage: optional + Value type: <bool> + Definition: present if the dailink is frontend + + += CPU, PLATFORM, CODEC dais subnodes +- cpu: + Usage: required + Value type: <subnode> + Definition: cpu dai sub-node + +- codec: + Usage: required + Value type: <subnode> + Definition: codec dai sub-node + +- platform: + Usage: opional + Value type: <subnode> + Definition: platform dai sub-node + +- sound-dai: + Usage: required + Value type: <phandle> + Definition: dai phandle/s and port of CPU/CODEC/PLATFORM node. + +Example: + +audio { + compatible = "qcom,apq8096-sndcard"; + qcom,model = "DB820c"; + qcom,audio-routing = + "RX_BIAS", "MCLK"; + + fedai1 { + is-fe; + link-name = "MultiMedia1 Playback"; + cpu { + sound-dai = <&q6asm MSM_FRONTEND_DAI_MULTIMEDIA1>; + }; + platform { + sound-dai = <&q6asm MSM_FRONTEND_DAI_MULTIMEDIA1>; + }; + }; + + bedai1 { + link-name = "HDMI Playback"; + cpu { + sound-dai = <&q6afe AFE_PORT_HDMI_RX>; + }; + + platform { + sound-dai = <&q6adm>; + }; + + codec { + sound-dai = <&hdmi 0>; + }; + }; +};
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to DB820c machine driver.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 8 +++ sound/soc/qcom/apq8096.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 sound/soc/qcom/apq8096.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 8c2d65e0a28e..fa4b575c086c 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -77,3 +77,11 @@ config SND_SOC_QDSP6 This will enable sound soc platform specific audio drivers. This includes q6asm, q6adm, q6afe interfaces to DSP using apr. + +config SND_SOC_MSM8996 + tristate "SoC Machine driver for MSM8996 and APQ8096 boards" + depends on QCOM_APR + select SND_SOC_QDSP6V2 + default n + help + To add support for SoC audio on MSM8996 and APQ8096 boards diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c new file mode 100644 index 000000000000..b12d2398557a --- /dev/null +++ b/sound/soc/qcom/apq8096.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, Linaro Limited + */ + +#include <linux/soc/qcom/apr.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> + +static int msm8996_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + return 0; +} + +static int apq8096_sbc_parse_of(struct snd_soc_card *card) +{ + struct device *dev = card->dev; + struct snd_soc_dai_link *link; + struct device_node *np, *codec, *platform, *cpu, *node = dev->of_node; + int ret, num_links; + bool is_fe; + + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) + dev_err(dev, "Error parsing card name: %d\n", ret); + + if (of_property_read_bool(dev->of_node, "qcom,audio-routing")) + ret = snd_soc_of_parse_audio_routing(card, + "qcom,audio-routing"); + + /* Populate links */ + num_links = of_get_child_count(node); + + dev_info(dev, "Found %d child audio dai links..\n", num_links); + /* Allocate the private data and the DAI link array */ + card->dai_link = devm_kzalloc(dev, sizeof(*link) * num_links, + GFP_KERNEL); + if (!card->dai_link) + return -ENOMEM; + + card->num_links = num_links; + + link = card->dai_link; + + for_each_child_of_node(node, np) { + is_fe = false; + if (of_property_read_bool(np, "is-fe")) + is_fe = true; + + if (is_fe) { + /* BE is dummy */ + link->codec_of_node = NULL; + link->codec_dai_name = "snd-soc-dummy-dai"; + link->codec_name = "snd-soc-dummy"; + + /* FE settings */ + link->dynamic = 1; + link->dpcm_playback = 1; + + } else { + link->no_pcm = 1; + link->dpcm_playback = 1; + link->ignore_suspend = 1; + link->ignore_pmdown_time = 1; + link->be_hw_params_fixup = msm8996_be_hw_params_fixup; + } + + cpu = of_get_child_by_name(np, "cpu"); + platform = of_get_child_by_name(np, "platform"); + codec = of_get_child_by_name(np, "codec"); + + if (!cpu) { + dev_err(dev, "Can't find cpu DT node\n"); + return -EINVAL; + } + + link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); + if (!link->cpu_of_node) { + dev_err(card->dev, "error getting cpu phandle\n"); + return -EINVAL; + } + + link->platform_of_node = of_parse_phandle(platform, + "sound-dai", 0); + if (!link->platform_of_node) { + dev_err(card->dev, "error getting platform phandle\n"); + return -EINVAL; + } + + ret = snd_soc_of_get_dai_name(cpu, &link->cpu_dai_name); + if (ret) { + dev_err(card->dev, "error getting cpu dai name\n"); + return ret; + } + + if (codec) { + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + + if (ret < 0) { + dev_err(card->dev, "error getting codec dai name\n"); + return ret; + } + } + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { + dev_err(card->dev, "error getting codec dai_link name\n"); + return ret; + } + + link->stream_name = link->name; + link++; + } + + return ret; +} + +static int msm_snd_apq8096_probe(struct apr_device *adev) +{ + int ret; + struct snd_soc_card *card; + + card = devm_kzalloc(&adev->dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = &adev->dev; + + ret = apq8096_sbc_parse_of(card); + if (ret) { + dev_err(&adev->dev, "Error parsing OF data\n"); + return ret; + } + + ret = devm_snd_soc_register_card(&adev->dev, card); + if (ret) + dev_err(&adev->dev, "sound card register failed (%d)!\n", ret); + else + dev_err(&adev->dev, "sound card register Sucessfull\n"); + + return ret; +} + +static const struct of_device_id msm_snd_apq8096_dt_match[] = { + {.compatible = "qcom,apq8096-sndcard"}, + {} +}; + +static struct apr_driver msm_snd_apq8096_driver = { + .probe = msm_snd_apq8096_probe, + .driver = { + .name = "msm-snd-apq8096", + .owner = THIS_MODULE, + .of_match_table = msm_snd_apq8096_dt_match, + }, +}; +module_apr_driver(msm_snd_apq8096_driver); +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("APQ8096 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2");
On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to DB820c machine driver.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
sound/soc/qcom/Kconfig | 8 +++ sound/soc/qcom/apq8096.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 sound/soc/qcom/apq8096.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 8c2d65e0a28e..fa4b575c086c 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -77,3 +77,11 @@ config SND_SOC_QDSP6 This will enable sound soc platform specific audio drivers. This includes q6asm, q6adm, q6afe interfaces to DSP using apr.
+config SND_SOC_MSM8996
- tristate "SoC Machine driver for MSM8996 and APQ8096 boards"
- depends on QCOM_APR
- select SND_SOC_QDSP6V2
should be select SND_SOC_QDSP6, V2 is not defined
- default n
- help
To add support for SoC audio on MSM8996 and APQ8096 boards
Thanks for the review,
On 22/02/18 11:00, Rohit Kumar wrote:
+config SND_SOC_MSM8996
- tristate "SoC Machine driver for MSM8996 and APQ8096 boards"
- depends on QCOM_APR
- select SND_SOC_QDSP6V2
should be select SND_SOC_QDSP6, V2 is not defined
I will fix this in next version.
--srini
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds hdmi sound card support to db820c via qdsp.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi | 44 +++++++++++++++++++- arch/arm64/boot/dts/qcom/msm8996.dtsi | 62 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi index 9769053957af..6f6f21501ee9 100644 --- a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi +++ b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi @@ -18,7 +18,8 @@ #include "apq8096-db820c-pmic-pins.dtsi" #include <dt-bindings/input/input.h> #include <dt-bindings/gpio/gpio.h> - +#include <dt-bindings/sound/qcom,q6afe.h> +#include <dt-bindings/sound/qcom,q6asm.h> / { aliases { serial0 = &blsp2_uart1; @@ -186,6 +187,7 @@
core-vdda-supply = <&pm8994_l12>; core-vcc-supply = <&pm8994_s4>; + #sound-dai-cells = <1>; }; }; }; @@ -360,4 +362,44 @@ }; }; }; + adsp-pil { + power-domains = <&gcc HLOS1_VOTE_LPASS_ADSP_GDSC>; + smd-edge { + apr { + iommus = <&lpass_q6_smmu 1>; + audio { + compatible = "qcom,apq8096-sndcard"; + qcom,model = "DB820c"; + qcom,audio-routing = + "RX_BIAS", "MCLK"; + + fe@1 { + is-fe; + link-name = "MultiMedia1 Playback"; + cpu { + sound-dai = <&q6asm MSM_FRONTEND_DAI_MULTIMEDIA1>; + }; + platform { + sound-dai = <&q6asm MSM_FRONTEND_DAI_MULTIMEDIA1>; + }; + }; + + be@1 { + link-name = "HDMI Playback"; + cpu { + sound-dai = <&q6afe AFE_PORT_HDMI_RX>; + }; + + platform { + sound-dai = <&q6adm>; + }; + + codec { + sound-dai = <&hdmi 0>; + }; + }; + }; + }; + }; + }; }; diff --git a/arch/arm64/boot/dts/qcom/msm8996.dtsi b/arch/arm64/boot/dts/qcom/msm8996.dtsi index c93bbae645bd..75cb055b0c55 100644 --- a/arch/arm64/boot/dts/qcom/msm8996.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi @@ -14,6 +14,7 @@ #include <dt-bindings/clock/qcom,gcc-msm8996.h> #include <dt-bindings/clock/qcom,mmcc-msm8996.h> #include <dt-bindings/clock/qcom,rpmcc.h> +#include <dt-bindings/soc/qcom,apr.h>
/ { model = "Qualcomm Technologies, Inc. MSM8996"; @@ -1287,6 +1288,34 @@ "ref_clk"; }; }; + + lpass_q6_smmu: arm,smmu-lpass_q6@1600000 { + compatible = "qcom,msm8996-smmu-v2"; + reg = <0x1600000 0x20000>; + #iommu-cells = <1>; + power-domains = <&gcc HLOS1_VOTE_LPASS_CORE_GDSC>; + + #global-interrupts = <1>; + interrupts = <GIC_SPI 404 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 226 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 393 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 394 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 395 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 396 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 397 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 398 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 399 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 400 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 401 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 402 IRQ_TYPE_LEVEL_HIGH>, + <GIC_SPI 403 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&gcc GCC_HLOS1_VOTE_LPASS_CORE_SMMU_CLK>, + <&gcc GCC_HLOS1_VOTE_LPASS_ADSP_SMMU_CLK>; + clock-names = "iface", "bus"; + status = "okay"; + }; + };
adsp-pil { @@ -1315,6 +1344,39 @@ qcom,ipc = <&apcs 16 8>; qcom,smd-edge = <1>; qcom,remote-pid = <2>; + apr { + compatible = "qcom,apr-v2"; + qcom,smd-channels = "apr_audio_svc"; + qcom,apr-dest-domain-id = <APR_DOMAIN_ADSP>; + + q6core { + qcom,apr-svc-name = "CORE"; + qcom,apr-svc-id = <APR_SVC_ADSP_CORE>; + compatible = "qcom,q6core"; + }; + + q6afe: q6afe { + compatible = "qcom,q6afe"; + qcom,apr-svc-name = "AFE"; + qcom,apr-svc-id = <APR_SVC_AFE>; + #sound-dai-cells = <1>; + }; + + q6asm: q6asm { + compatible = "qcom,q6asm"; + qcom,apr-svc-name = "ASM"; + qcom,apr-svc-id = <APR_SVC_ASM>; + #sound-dai-cells = <1>; + }; + + q6adm: q6adm { + compatible = "qcom,q6adm"; + qcom,apr-svc-name = "ADM"; + qcom,apr-svc-id = <APR_SVC_ADM>; + #sound-dai-cells = <0>; + }; + + }; }; };
participants (5)
-
Mark Brown
-
Rob Herring
-
Rohit Kumar
-
Srinivas Kandagatla
-
srinivas.kandagatla@linaro.org