[alsa-devel] [RESEND PATCH v2 00/15] ASoC: qcom: Add support to QDSP6 based audio
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patchset aims to provide a very basic version of QCOM DSP based audio support which is available in downstream andriod kernels. This patchset only support Digital audio based for HDMI-RX 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 on top of mainline, also tested slimbus analog audio using wcd9355 with an additional small patch.
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 RFC: - Converted APR driver to proper bus driver - Added API version info to apr devices - Added q6core driver. - Fixed various issues spotted by Banajit and Kasam. - Added kernel doc to exported symbols. - renamed qdsp6v2 to qdsp6 as api version info can be used from apr device. - removed unnecessary dt bindings for apr devices.
Srinivas Kandagatla (15): 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 ASoC: qcom: qdsp6: Add support to Q6AFE ASoC: qcom: qdsp6: Add support to Q6ADM 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 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 | 28 + .../devicetree/bindings/sound/qcom,apq8096.txt | 22 + arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi | 5 + arch/arm64/boot/dts/qcom/msm8996.dtsi | 33 + drivers/soc/qcom/Kconfig | 8 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 395 +++++++ include/linux/mod_devicetable.h | 13 + include/linux/soc/qcom/apr.h | 174 +++ sound/soc/qcom/Kconfig | 51 + sound/soc/qcom/Makefile | 5 + sound/soc/qcom/apq8096.c | 124 +++ sound/soc/qcom/qdsp6/Makefile | 7 + sound/soc/qcom/qdsp6/common.h | 225 ++++ sound/soc/qcom/qdsp6/q6adm.c | 602 +++++++++++ sound/soc/qcom/qdsp6/q6adm.h | 24 + sound/soc/qcom/qdsp6/q6afe-dai.c | 241 +++++ sound/soc/qcom/qdsp6/q6afe.c | 503 +++++++++ sound/soc/qcom/qdsp6/q6afe.h | 30 + sound/soc/qcom/qdsp6/q6asm-dai.c | 534 ++++++++++ sound/soc/qcom/qdsp6/q6asm.c | 1119 ++++++++++++++++++++ sound/soc/qcom/qdsp6/q6asm.h | 61 ++ sound/soc/qcom/qdsp6/q6core.c | 227 ++++ sound/soc/qcom/qdsp6/q6routing.c | 386 +++++++ sound/soc/qcom/qdsp6/q6routing.h | 9 + 25 files changed, 4827 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt create mode 100644 Documentation/devicetree/bindings/sound/qcom,apq8096.txt create mode 100644 drivers/soc/qcom/apr.c 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/common.h 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/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 bus driver
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- .../devicetree/bindings/soc/qcom/qcom,apr.txt | 28 ++++++++++++++++++++++ 1 file changed, 28 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..4e93213ae98d --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,28 @@ +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-<SOC-NAME>" example: "qcom,apr-msm8996" + + +- qcom,smd-channel: + Usage: required + Value type: <string> + Definition: standard SMD property specifying the SMD channel used for + communication with the APR on QDSP. + Should be "apr_audio_svc". + Described in soc/qcom/qcom,smd.txt + += 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-msm8996"; + qcom,smd-channels = "apr_audio_svc"; + };
On Thu, Dec 14, 2017 at 05:33:48PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR bus driver
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 28 ++++++++++++++++++++++ 1 file changed, 28 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..4e93213ae98d --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,28 @@ +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-<SOC-NAME>" example: "qcom,apr-msm8996"
<soc>-apr is the more standard order. With that,
Reviewed-by: Rob Herring robh@kernel.org
On 16/12/17 17:27, Rob Herring wrote:
On Thu, Dec 14, 2017 at 05:33:48PM +0000, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR bus driver
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 28 ++++++++++++++++++++++ 1 file changed, 28 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..4e93213ae98d --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,28 @@ +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-<SOC-NAME>" example: "qcom,apr-msm8996"
<soc>-apr is the more standard order. With that,
Yes, it makes sense, will do that in next version.
Reviewed-by: Rob Herring robh@kernel.org
Thanks for reviewed-by tag.
Rgrds, Srini
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR bus driver
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 28 ++++++++++++++++++++++ 1 file changed, 28 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..4e93213ae98d --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,28 @@ +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-<SOC-NAME>" example: "qcom,apr-msm8996"
This is not the only apr on msm8996 and some platform seems to have 3-4 aprs. I'm therefor hesitant to use the compatible to pick the static list of services available on the particular firmware.
If this scheme is followed we at least would need to rename this qcom,msm8996-apr-audio-svc
But I think it would be preferable to go with qcom,apr-v2 and then list the static services as child nodes.
+- qcom,smd-channel:
- Usage: required
- Value type: <string>
- Definition: standard SMD property specifying the SMD channel used for
communication with the APR on QDSP.
Should be "apr_audio_svc".
This is not the only APR channel, but for apr itself this doesn't matter and might as well be qcom,glink-channels; so perhaps we can omit this from this document?
Described in soc/qcom/qcom,smd.txt
+= 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-msm8996";
qcom,smd-channels = "apr_audio_svc";
- };
Regards, Bjorn
Thanks for review comments!
On 03/01/18 00:35, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch add dt bindings for Qualcomm APR bus driver
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
.../devicetree/bindings/soc/qcom/qcom,apr.txt | 28 ++++++++++++++++++++++ 1 file changed, 28 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..4e93213ae98d --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,28 @@ +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-<SOC-NAME>" example: "qcom,apr-msm8996"
This is not the only apr on msm8996 and some platform seems to have 3-4 aprs. I'm therefor hesitant to use the compatible to pick the static list of services available on the particular firmware.
If this scheme is followed we at least would need to rename this qcom,msm8996-apr-audio-svc
But I think it would be preferable to go with qcom,apr-v2 and then list the static services as child nodes.
Am okay doing it, Only issue I have is the dsp state. If the dsp AVCS is not ready yet then we would be chatting on the service without the services being up on remote side? Chances of this seems to be slim but we need to confirm this before we make a call on this.
+- qcom,smd-channel:
- Usage: required
- Value type: <string>
- Definition: standard SMD property specifying the SMD channel used for
communication with the APR on QDSP.
Should be "apr_audio_svc".
This is not the only APR channel, but for apr itself this doesn't matter and might as well be qcom,glink-channels; so perhaps we can omit this from this document?
Yes, I agree with you. will remove this in next version.
Described in soc/qcom/qcom,smd.txt
+= 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-msm8996";
qcom,smd-channels = "apr_audio_svc";
- };
Regards, Bjorn
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 | 8 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 395 ++++++++++++++++++++++++++++++++++++++++ include/linux/mod_devicetable.h | 13 ++ include/linux/soc/qcom/apr.h | 174 ++++++++++++++++++ 5 files changed, 591 insertions(+) create mode 100644 drivers/soc/qcom/apr.c create mode 100644 include/linux/soc/qcom/apr.h
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index b81374bb6713..1daa39925dd4 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -97,4 +97,12 @@ 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 (RPMSG_QCOM_SMD || RPMSG_QCOM_GLINK_RPM) + 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 40c56f67e94a..9daba7e6d20f 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.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..c6f3aa7a375b --- /dev/null +++ b/drivers/soc/qcom/apr.c @@ -0,0 +1,395 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, 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_data { + int (*get_data_src)(struct apr_hdr *hdr); + int dest_id; +}; + +struct apr { + struct rpmsg_endpoint *ch; + struct device *dev; + spinlock_t svcs_lock; + struct list_head svcs; + int dest_id; + const struct apr_data *data; +}; + +/* Static CORE service on the ADSP */ +static const struct apr_device_id core_svc_device_id = + ADSP_AUDIO_APR_DEV("CORE", APR_SVC_ADSP_CORE); + +/** + * 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, uint32_t *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 qcom_rpmsg_q6_callback(struct rpmsg_device *rpdev, void *buf, + int len, void *priv, u32 addr) +{ + struct apr *apr = dev_get_drvdata(&rpdev->dev); + struct apr_client_data 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 src; + uint16_t svc; + + if (!buf || 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; + src = apr->data->get_data_src(hdr); + if (src == APR_DEST_MAX) + return -EINVAL; + + 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 = src; + 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 = (char *)hdr + hdr_size; + + adrv->callback(c_svc, &data); + + return 0; +} + +static const struct apr_device_id *apr_match(const struct apr_device_id *id, + const struct apr_device *adev) +{ + while (id->domain_id != 0 || id->svc_id != 0) { + if (id->domain_id == adev->domain_id && + id->svc_id == adev->svc_id && + id->client_id == adev->client_id) + return id; + id++; + } + return NULL; +} + +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); + + return !!apr_match(adrv->id_table, adev); +} + +static int apr_device_probe(struct device *dev) +{ + struct apr_device *adev; + struct apr_driver *adrv; + int ret = 0; + + adev = to_apr_device(dev); + adrv = to_apr_driver(dev->driver); + + ret = adrv->probe(adev); + + return ret; +} + +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, +}; +EXPORT_SYMBOL_GPL(aprbus_type); + +/** + * apr_add_device() - Add a new apr device + * + * @dev: Pointer to apr device. + * @id: Pointer to apr device id to add. + * + * Return: Will be an negative on error or a zero on success. + */ +int apr_add_device(struct device *dev, const struct apr_device_id *id) +{ + struct apr *apr = dev_get_drvdata(dev); + struct apr_device *adev = NULL; + + if (!apr) + return -EINVAL; + + adev = kzalloc(sizeof(*adev), GFP_KERNEL); + if (!adev) + return -ENOMEM; + + spin_lock_init(&adev->lock); + + adev->svc_id = id->svc_id; + adev->dest_id = apr->dest_id; + adev->client_id = id->client_id; + adev->domain_id = id->domain_id; + adev->version = id->svc_version; + + adev->dev.bus = &aprbus_type; + adev->dev.parent = dev; + adev->dev.release = apr_dev_release; + adev->dev.driver = NULL; + + dev_set_name(&adev->dev, "apr:%s:%x:%x:%x", id->name, id->domain_id, + id->svc_id, id->client_id); + + spin_lock(&apr->svcs_lock); + list_add_tail(&adev->node, &apr->svcs); + spin_unlock(&apr->svcs_lock); + + return device_register(&adev->dev); +} +EXPORT_SYMBOL_GPL(apr_add_device); + +static int qcom_rpmsg_q6_probe(struct rpmsg_device *rpdev) +{ + struct device *dev = &rpdev->dev; + struct apr *apr; + + apr = devm_kzalloc(dev, sizeof(*apr), GFP_KERNEL); + if (!apr) + return -ENOMEM; + + apr->data = of_device_get_match_data(dev); + if (!apr->data) + return -ENODEV; + + apr->dest_id = apr->data->dest_id; + dev_set_drvdata(dev, apr); + apr->ch = rpdev->ept; + apr->dev = dev; + INIT_LIST_HEAD(&apr->svcs); + + /* register core service */ + apr_add_device(dev, &core_svc_device_id); + + 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 qcom_rpmsg_q6_remove(struct rpmsg_device *rpdev) +{ + device_for_each_child(&rpdev->dev, NULL, apr_remove_device); +} + +static int apr_v2_get_data_src(struct apr_hdr *hdr) +{ + if (hdr->src_domain == APR_DOMAIN_MODEM) + return APR_DEST_MODEM; + else if (hdr->src_domain == APR_DOMAIN_ADSP) + return APR_DEST_QDSP6; + + return APR_DEST_MAX; +} + +/* + * __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) +{ + /* ID table is mandatory to match the devices to probe */ + if (!drv->id_table) + return -EINVAL; + + 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 apr_data apr_v2_data = { + .get_data_src = apr_v2_get_data_src, + .dest_id = APR_DEST_QDSP6, +}; + +static const struct of_device_id qcom_rpmsg_q6_of_match[] = { + { .compatible = "qcom,apr-msm8996", .data = &apr_v2_data}, + {} +}; + +static struct rpmsg_driver qcom_rpmsg_q6_driver = { + .probe = qcom_rpmsg_q6_probe, + .remove = qcom_rpmsg_q6_remove, + .callback = qcom_rpmsg_q6_callback, + .drv = { + .name = "qcom_rpmsg_q6", + .owner = THIS_MODULE, + .of_match_table = qcom_rpmsg_q6_of_match, + }, +}; + +static int __init apr_init(void) +{ + int ret; + + ret = register_rpmsg_driver(&qcom_rpmsg_q6_driver); + if (!ret) + return bus_register(&aprbus_type); + + return ret; +} + +static void __exit apr_exit(void) +{ + bus_unregister(&aprbus_type); + unregister_rpmsg_driver(&qcom_rpmsg_q6_driver); +} + +subsys_initcall(apr_init); +module_exit(apr_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm APR Bus"); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index abb6dc2ebbf8..068d215c3be6 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -452,6 +452,19 @@ struct spi_device_id { kernel_ulong_t driver_data; /* Data private to the driver */ };
+ +#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 client_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..8620289c34ab --- /dev/null +++ b/include/linux/soc/qcom/apr.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, Linaro Limited +*/ + +#ifndef __APR_H_ +#define __APR_H_ + +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/mod_devicetable.h> +/* APR Client IDs */ +#define APR_CLIENT_AUDIO 0x0 +#define APR_CLIENT_VOICE 0x1 +#define APR_CLIENT_MAX 0x2 + +#define APR_DL_SMD 0 +#define APR_DL_MAX 1 + +#define APR_DEST_MODEM 0 +#define APR_DEST_QDSP6 1 +#define APR_DEST_MAX 2 +#define APR_MAX_BUF 8192 + +#define APR_HDR_LEN(hdr_len) ((hdr_len)/4) +#define APR_PKT_SIZE(hdr_len, payload_len) ((hdr_len) + (payload_len)) +#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 + +/* 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_TEST_CLIENT 0x2 +#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 + +/* Modem Service IDs */ +#define APR_SVC_MVS 0x3 +#define APR_SVC_MVM 0x4 +#define APR_SVC_CVS 0x5 +#define APR_SVC_CVP 0x6 +#define APR_SVC_SRD 0x7 + +/* APR Port IDs */ +#define APR_MAX_PORTS 0x80 +#define APR_NAME_MAX 0x40 +#define RESET_EVENTS 0x000130D7 + +/* 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_data { + 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; +}; + +#define ADSP_AUDIO_APR_DEV(name, id) \ + {name, APR_DOMAIN_ADSP, id, APR_CLIENT_AUDIO} + +/* 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 dest_id; + uint16_t client_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_data *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); +int apr_add_device(struct device *dev, const struct apr_device_id *id); + +/** + * 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, uint32_t *buf); + +#endif /* __APR_H_ */
On Thu 14 Dec 09:33 PST 2017, 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 | 8 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 395 ++++++++++++++++++++++++++++++++++++++++ include/linux/mod_devicetable.h | 13 ++ include/linux/soc/qcom/apr.h | 174 ++++++++++++++++++ 5 files changed, 591 insertions(+) create mode 100644 drivers/soc/qcom/apr.c create mode 100644 include/linux/soc/qcom/apr.h
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index b81374bb6713..1daa39925dd4 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -97,4 +97,12 @@ 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 (RPMSG_QCOM_SMD || RPMSG_QCOM_GLINK_RPM)
The actual dependency you have is RPMSG. Specifying the individual transports here causes additional restrictions in how you can mix and match modules vs builtin (e.g. glink being builtin to get regulators early and smd built as a module forces apr to be built as a module)
PS, the glink config you're depending on is RPMSG_QCOM_GLINK_SMEM
- 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 40c56f67e94a..9daba7e6d20f 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.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..c6f3aa7a375b --- /dev/null +++ b/drivers/soc/qcom/apr.c @@ -0,0 +1,395 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, Linaro Limited +*/
For some tooling reason the SPDX line should be // commented, i.e this should be:
// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2011-2016, The Linux Foundation * Copyright (c) 2017, 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_data {
- int (*get_data_src)(struct apr_hdr *hdr);
- int dest_id;
+};
+struct apr {
- struct rpmsg_endpoint *ch;
- struct device *dev;
- spinlock_t svcs_lock;
- struct list_head svcs;
- int dest_id;
- const struct apr_data *data;
+};
+/* Static CORE service on the ADSP */ +static const struct apr_device_id core_svc_device_id =
ADSP_AUDIO_APR_DEV("CORE", APR_SVC_ADSP_CORE);
How does this work out when we want to use APR for the modem services?
+/**
- 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, uint32_t *buf)
So buf here is a struct apr_hdr followed by arbitrary data. Passing a reference to an arbitrary chunk of data should be done with a void *. But you could turn struct apr_hdr into struct apr_packet by adding a flexible array member at the end of the struct and having this function take that data type. This would make the prototype self-documenting.
I do, however, not fancy the asymmetry of the send/callback interface exposed to the client. One takes the raw packet, as it sits in the transport and the other creates an abstract representation of the same.
Can't you in the callback verify the data and then just pass the same object up to the client?
+{
- 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 qcom_rpmsg_q6_callback(struct rpmsg_device *rpdev, void *buf,
int len, void *priv, u32 addr)
+{
- struct apr *apr = dev_get_drvdata(&rpdev->dev);
- struct apr_client_data 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 src;
- uint16_t svc;
- if (!buf || len <= APR_HDR_SIZE) {
rpmsg should never call you with buf = NULL, so no reason to check for that.
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;
- src = apr->data->get_data_src(hdr);
Couldn't we just use src_domain here instead of maintaining this mapping table in the driver?
Also, looking at the send function, isn't this src just c_svc->svc_id?
- if (src == APR_DEST_MAX)
return -EINVAL;
- 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;
}
- }
Keep your services in a idr, keyed by svc_id. This will make the search O(log(n)), but more importantly it would replace this loop with a single idr_find().
- 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 = src;
- 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 = (char *)hdr + hdr_size;
Using buf + hdr_size probably makes even more sense, and saves you from the typecast.
- adrv->callback(c_svc, &data);
- return 0;
+}
+static const struct apr_device_id *apr_match(const struct apr_device_id *id,
const struct apr_device *adev)
+{
- while (id->domain_id != 0 || id->svc_id != 0) {
if (id->domain_id == adev->domain_id &&
id->svc_id == adev->svc_id &&
id->client_id == adev->client_id)
Is the client_id significant here? As far as I can tell it's a identifier of the local "function". Are there multiple client drivers that would "attach" to the same remote service?
return id;
id++;
- }
- return NULL;
+}
+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);
- return !!apr_match(adrv->id_table, adev);
If this remain the only caller of apr_match() you could just inline the loop here.
+}
+static int apr_device_probe(struct device *dev) +{
- struct apr_device *adev;
- struct apr_driver *adrv;
You don't indent things elsewhere.
- int ret = 0;
- adev = to_apr_device(dev);
- adrv = to_apr_driver(dev->driver);
- ret = adrv->probe(adev);
Drop ret and just return adrv->probe()
- return ret;
+}
+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,
+}; +EXPORT_SYMBOL_GPL(aprbus_type);
+/**
- apr_add_device() - Add a new apr device
- @dev: Pointer to apr device.
- @id: Pointer to apr device id to add.
- Return: Will be an negative on error or a zero on success.
- */
+int apr_add_device(struct device *dev, const struct apr_device_id *id) +{
- struct apr *apr = dev_get_drvdata(dev);
It's not obvious which dev this is, but it has to be the rpmsg device or this would dereference the drvdata of the wrong type and things would be very bad.
How about making this more explicit by just taking a struct apr * as first argument, and then provide a helper for clients to acquire the struct apr * from the parent dev, if this is needed.
- struct apr_device *adev = NULL;
- if (!apr)
return -EINVAL;
- adev = kzalloc(sizeof(*adev), GFP_KERNEL);
- if (!adev)
return -ENOMEM;
- spin_lock_init(&adev->lock);
- adev->svc_id = id->svc_id;
- adev->dest_id = apr->dest_id;
- adev->client_id = id->client_id;
- adev->domain_id = id->domain_id;
Wouldn't this always be the domain of the apr? (or we're adding a device to the wrong apr)
- adev->version = id->svc_version;
- adev->dev.bus = &aprbus_type;
- adev->dev.parent = dev;
- adev->dev.release = apr_dev_release;
- adev->dev.driver = NULL;
- dev_set_name(&adev->dev, "apr:%s:%x:%x:%x", id->name, id->domain_id,
id->svc_id, id->client_id);
- spin_lock(&apr->svcs_lock);
- list_add_tail(&adev->node, &apr->svcs);
- spin_unlock(&apr->svcs_lock);
This causes svcs to contain entries related to devices that has not been matched and probed yet (e.g. implementation is in a kernel module not loaded). I think you should move this to apr_device_probe().
- return device_register(&adev->dev);
+} +EXPORT_SYMBOL_GPL(apr_add_device);
+static int qcom_rpmsg_q6_probe(struct rpmsg_device *rpdev) +{
- struct device *dev = &rpdev->dev;
- struct apr *apr;
- apr = devm_kzalloc(dev, sizeof(*apr), GFP_KERNEL);
- if (!apr)
return -ENOMEM;
- apr->data = of_device_get_match_data(dev);
- if (!apr->data)
return -ENODEV;
- apr->dest_id = apr->data->dest_id;
- dev_set_drvdata(dev, apr);
- apr->ch = rpdev->ept;
- apr->dev = dev;
- INIT_LIST_HEAD(&apr->svcs);
- /* register core service */
- apr_add_device(dev, &core_svc_device_id);
- 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 qcom_rpmsg_q6_remove(struct rpmsg_device *rpdev) +{
- device_for_each_child(&rpdev->dev, NULL, apr_remove_device);
+}
+static int apr_v2_get_data_src(struct apr_hdr *hdr) +{
- if (hdr->src_domain == APR_DOMAIN_MODEM)
return APR_DEST_MODEM;
- else if (hdr->src_domain == APR_DOMAIN_ADSP)
return APR_DEST_QDSP6;
- return APR_DEST_MAX;
The idiomatic return value here would be -EINVAL.
+}
+/*
- __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) +{
- /* ID table is mandatory to match the devices to probe */
- if (!drv->id_table)
return -EINVAL;
- 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 apr_data apr_v2_data = {
- .get_data_src = apr_v2_get_data_src,
- .dest_id = APR_DEST_QDSP6,
+};
+static const struct of_device_id qcom_rpmsg_q6_of_match[] = {
- { .compatible = "qcom,apr-msm8996", .data = &apr_v2_data},
The dest_id of apr_v2_data and the use of "q6" in the name indicates that this is the "msm8996 adsp apr", what's your plan to support other apr remotes?
How about making the domain id a property of the apr in DT and then just list the clients as nodes with svc_id, svc_version and client_id? You can still match by device_id (compatible can be optional), but that would allow you to describe either the adsp or modem apr link, of any platform (of that apr version), with the same compatible.
- {}
+};
+static struct rpmsg_driver qcom_rpmsg_q6_driver = {
- .probe = qcom_rpmsg_q6_probe,
- .remove = qcom_rpmsg_q6_remove,
- .callback = qcom_rpmsg_q6_callback,
- .drv = {
.name = "qcom_rpmsg_q6",
.owner = THIS_MODULE,
Drop the owner.
.of_match_table = qcom_rpmsg_q6_of_match,
},
Indentation.
+};
+static int __init apr_init(void) +{
- int ret;
- ret = register_rpmsg_driver(&qcom_rpmsg_q6_driver);
- if (!ret)
return bus_register(&aprbus_type);
Register the bus first, then the rpmsg driver.
- return ret;
+}
+static void __exit apr_exit(void) +{
- bus_unregister(&aprbus_type);
- unregister_rpmsg_driver(&qcom_rpmsg_q6_driver);
+}
+subsys_initcall(apr_init); +module_exit(apr_exit);
+MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm APR Bus"); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index abb6dc2ebbf8..068d215c3be6 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -452,6 +452,19 @@ struct spi_device_id { kernel_ulong_t driver_data; /* Data private to the driver */ };
One newline is enough.
+#define APR_NAME_SIZE 32 +#define APR_MODULE_PREFIX "apr:"
+struct apr_device_id {
- char name[APR_NAME_SIZE];
Does this name has any meaning? As far as I can see we're matching on the other parameters and use the name only to name the device.
- __u32 domain_id;
- __u32 svc_id;
- __u32 client_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..8620289c34ab --- /dev/null +++ b/include/linux/soc/qcom/apr.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, Linaro Limited +*/
+#ifndef __APR_H_ +#define __APR_H_
+#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/mod_devicetable.h> +/* APR Client IDs */ +#define APR_CLIENT_AUDIO 0x0 +#define APR_CLIENT_VOICE 0x1 +#define APR_CLIENT_MAX 0x2
+#define APR_DL_SMD 0 +#define APR_DL_MAX 1
Unused.
+#define APR_DEST_MODEM 0 +#define APR_DEST_QDSP6 1 +#define APR_DEST_MAX 2 +#define APR_MAX_BUF 8192
+#define APR_HDR_LEN(hdr_len) ((hdr_len)/4) +#define APR_PKT_SIZE(hdr_len, payload_len) ((hdr_len) + (payload_len)) +#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)
So for the tx path these macros are to be used by the client and for the rx path they are to be used by the apr driver. Better make the api symmetrical.
One possible path is to use an sk_buf for the tx-path and reserve space for the header, then pass the abstract version of the packet and let the apr driver fill out the header.
+/* 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
+/* 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_TEST_CLIENT 0x2 +#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
+/* Modem Service IDs */ +#define APR_SVC_MVS 0x3 +#define APR_SVC_MVM 0x4 +#define APR_SVC_CVS 0x5 +#define APR_SVC_CVP 0x6 +#define APR_SVC_SRD 0x7
+/* APR Port IDs */ +#define APR_MAX_PORTS 0x80 +#define APR_NAME_MAX 0x40 +#define RESET_EVENTS 0x000130D7
+/* 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_data {
Perhaps name this apr_client_message or similar, to indicate that this is not a per-client thing, but a description of a message/packet.
- 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;
+};
Regards, Bjorn
Thank Bjorn for the Review.
On 01/01/18 23:29, Bjorn Andersson wrote:
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index b81374bb6713..1daa39925dd4 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -97,4 +97,12 @@ 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 (RPMSG_QCOM_SMD || RPMSG_QCOM_GLINK_RPM)
The actual dependency you have is RPMSG. Specifying the individual transports here causes additional restrictions in how you can mix and match modules vs builtin (e.g. glink being builtin to get regulators early and smd built as a module forces apr to be built as a module)
I agree, Will fix this in next version.
+++ b/drivers/soc/qcom/apr.c @@ -0,0 +1,395 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, Linaro Limited +*/
For some tooling reason the SPDX line should be // commented, i.e this should be:
// SPDX-License-Identifier: GPL-2.0 /*
- Copyright (c) 2011-2016, The Linux Foundation
- Copyright (c) 2017, Linaro Limited
*/
Yep, this was also considered for next version.
...
+/* Static CORE service on the ADSP */ +static const struct apr_device_id core_svc_device_id =
ADSP_AUDIO_APR_DEV("CORE", APR_SVC_ADSP_CORE);
How does this work out when we want to use APR for the modem services?
So the plan is, If the modem service can be queried using AVCS core commands then it would be added dynamically from q6core driver else we can add is as static service into this list.
+/**
- 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, uint32_t *buf)
So buf here is a struct apr_hdr followed by arbitrary data. Passing a reference to an arbitrary chunk of data should be done with a void *. But you could turn struct apr_hdr into struct apr_packet by adding a flexible array member at the end of the struct and having this function take that data type. This would make the prototype self-documenting.
I do, however, not fancy the asymmetry of the send/callback interface exposed to the client. One takes the raw packet, as it sits in the transport and the other creates an abstract representation of the same.
Can't you in the callback verify the data and then just pass the same object up to the client?
I can try it and see how it looks.
+{
...
+static int qcom_rpmsg_q6_callback(struct rpmsg_device *rpdev, void *buf,
int len, void *priv, u32 addr)
+{
- struct apr *apr = dev_get_drvdata(&rpdev->dev);
- struct apr_client_data 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 src;
- uint16_t svc;
- if (!buf || len <= APR_HDR_SIZE) {
rpmsg should never call you with buf = NULL, so no reason to check for that.
Yep!
dev_err(apr->dev, "APR: Improper apr pkt received:%p %d\n",
buf, len);
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;
- src = apr->data->get_data_src(hdr);
Couldn't we just use src_domain here instead of maintaining this mapping table in the driver?
Yes, we can do that too, I will give that a go.
Also, looking at the send function, isn't this src just c_svc->svc_id?
src here represents mapping between domain and dest id. It is not same as svc_id.
- if (src == APR_DEST_MAX)
return -EINVAL;
- 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;
}
- }
Keep your services in a idr, keyed by svc_id. This will make the search O(log(n)), but more importantly it would replace this loop with a single idr_find().
I will try it and see how it looks!
- 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 = src;
- 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 = (char *)hdr + hdr_size;
Using buf + hdr_size probably makes even more sense, and saves you from the typecast.
Agree!
- adrv->callback(c_svc, &data);
- return 0;
+}
+static const struct apr_device_id *apr_match(const struct apr_device_id *id,
const struct apr_device *adev)
+{
- while (id->domain_id != 0 || id->svc_id != 0) {
if (id->domain_id == adev->domain_id &&
id->svc_id == adev->svc_id &&
id->client_id == adev->client_id)
Is the client_id significant here? As far as I can tell it's a identifier of the local "function". Are there multiple client drivers that would "attach" to the same remote service?
No. svc id is unique per client id, so it redundant check as you said.
return id;
id++;
- }
- return NULL;
+}
+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);
- return !!apr_match(adrv->id_table, adev);
If this remain the only caller of apr_match() you could just inline the loop here.
Makes sense.
+}
+static int apr_device_probe(struct device *dev) +{
- struct apr_device *adev;
- struct apr_driver *adrv;
You don't indent things elsewhere.
Yep.
- int ret = 0;
- adev = to_apr_device(dev);
- adrv = to_apr_driver(dev->driver);
- ret = adrv->probe(adev);
Drop ret and just return adrv->probe()
yep
- return ret;
+}
...
+/**
- apr_add_device() - Add a new apr device
- @dev: Pointer to apr device.
- @id: Pointer to apr device id to add.
- Return: Will be an negative on error or a zero on success.
- */
+int apr_add_device(struct device *dev, const struct apr_device_id *id) +{
- struct apr *apr = dev_get_drvdata(dev);
It's not obvious which dev this is, but it has to be the rpmsg device or this would dereference the drvdata of the wrong type and things would be very bad.
How about making this more explicit by just taking a struct apr * as first argument, and then provide a helper for clients to acquire the struct apr * from the parent dev, if this is needed.
Yep, that makes it much cleaner, will do it in next version.
- struct apr_device *adev = NULL;
- if (!apr)
return -EINVAL;
- adev = kzalloc(sizeof(*adev), GFP_KERNEL);
- if (!adev)
return -ENOMEM;
- spin_lock_init(&adev->lock);
- adev->svc_id = id->svc_id;
- adev->dest_id = apr->dest_id;
- adev->client_id = id->client_id;
- adev->domain_id = id->domain_id;
Wouldn't this always be the domain of the apr? (or we're adding a device to the wrong apr)
Yes, it is.
- adev->version = id->svc_version;
- adev->dev.bus = &aprbus_type;
- adev->dev.parent = dev;
- adev->dev.release = apr_dev_release;
- adev->dev.driver = NULL;
- dev_set_name(&adev->dev, "apr:%s:%x:%x:%x", id->name, id->domain_id,
id->svc_id, id->client_id);
- spin_lock(&apr->svcs_lock);
- list_add_tail(&adev->node, &apr->svcs);
- spin_unlock(&apr->svcs_lock);
This causes svcs to contain entries related to devices that has not been matched and probed yet (e.g. implementation is in a kernel module not loaded). I think you should move this to apr_device_probe().
Yep!
- return device_register(&adev->dev);
+} +EXPORT_SYMBOL_GPL(apr_add_device);
...
+static int apr_v2_get_data_src(struct apr_hdr *hdr) +{
- if (hdr->src_domain == APR_DOMAIN_MODEM)
return APR_DEST_MODEM;
- else if (hdr->src_domain == APR_DOMAIN_ADSP)
return APR_DEST_QDSP6;
- return APR_DEST_MAX;
The idiomatic return value here would be -EINVAL.
yep!, I try it this would also change logic at caller side.
+}
+/*
+static const struct apr_data apr_v2_data = {
- .get_data_src = apr_v2_get_data_src,
- .dest_id = APR_DEST_QDSP6,
+};
+static const struct of_device_id qcom_rpmsg_q6_of_match[] = {
- { .compatible = "qcom,apr-msm8996", .data = &apr_v2_data},
The dest_id of apr_v2_data and the use of "q6" in the name indicates that this is the "msm8996 adsp apr", what's your plan to support other apr remotes?
We would need one more entry for modem case, like db410c cases, Will add this as we move on.
How about making the domain id a property of the apr in DT and then just list the clients as nodes with svc_id, svc_version and client_id? You can still match by device_id (compatible can be optional), but that would allow you to describe either the adsp or modem apr link, of any platform (of that apr version), with the same compatible.
This will work too!
Current design I had in mind was to get the q6core service up and this can query what AVCS static services are available on remote side and then add apr devices.
If we go with dt approch we might not need q6core driver, but on the other hand we need to be aware of svc major and minor version, which might be specific to the dsp firmware running on. Also we might endup talking on services without querying the dsp state.
May be we should do some offline disussion on this!
- {}
+};
+static struct rpmsg_driver qcom_rpmsg_q6_driver = {
- .probe = qcom_rpmsg_q6_probe,
- .remove = qcom_rpmsg_q6_remove,
- .callback = qcom_rpmsg_q6_callback,
- .drv = {
.name = "qcom_rpmsg_q6",
.owner = THIS_MODULE,
Drop the owner.
Yep!
.of_match_table = qcom_rpmsg_q6_of_match,
},
Indentation.
Sure!
+};
+static int __init apr_init(void) +{
- int ret;
- ret = register_rpmsg_driver(&qcom_rpmsg_q6_driver);
- if (!ret)
return bus_register(&aprbus_type);
Register the bus first, then the rpmsg driver.
yep!
- return ret;
+}
...
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index abb6dc2ebbf8..068d215c3be6 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -452,6 +452,19 @@ struct spi_device_id { kernel_ulong_t driver_data; /* Data private to the driver */ };
One newline is enough.
yep
+#define APR_NAME_SIZE 32 +#define APR_MODULE_PREFIX "apr:"
+struct apr_device_id {
- char name[APR_NAME_SIZE];
Does this name has any meaning? As far as I can see we're matching on the other parameters and use the name only to name the device.
No, it just to give the device a usefull name.
- __u32 domain_id;
- __u32 svc_id;
- __u32 client_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..8620289c34ab --- /dev/null +++ b/include/linux/soc/qcom/apr.h
...
+#define APR_DL_SMD 0 +#define APR_DL_MAX 1
Unused.
will remove it.
...
+#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)
So for the tx path these macros are to be used by the client and for the rx path they are to be used by the apr driver. Better make the api symmetrical.
Will try that in next version.
One possible path is to use an sk_buf for the tx-path and reserve space for the header, then pass the abstract version of the packet and let the apr driver fill out the header.
In some cases like q6asm the apr header port numbers are much more specific to the service and they change depening on stream and session ids within the service.
+struct apr_client_data {
Perhaps name this apr_client_message or similar, to indicate that this is not a per-client thing, but a description of a message/packet.
make sense, Will change it to apr_client_message.
- uint16_t payload_size;
...
- void *payload;
+};
Regards, Bjorn
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/qdsp6/common.h | 225 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/common.h
diff --git a/sound/soc/qcom/qdsp6/common.h b/sound/soc/qcom/qdsp6/common.h new file mode 100644 index 000000000000..cbd6e009ef4b --- /dev/null +++ b/sound/soc/qcom/qdsp6/common.h @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6DSP_COMMON_H__ +#define __Q6DSP_COMMON_H__ + +/* ERROR CODES */ +/* 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*/ +#define ADSP_ERR_MAX (ADSP_ENOTEXIST + 1) +/* Operation is finished. */ +#define ADSP_ETERMINATED 0x00011174 + +/* ERROR STRING */ +/* Success. The operation completed with no errors. */ +#define ADSP_EOK_STR "ADSP_EOK" +/* General failure. */ +#define ADSP_EFAILED_STR "ADSP_EFAILED" +/* Bad operation parameter. */ +#define ADSP_EBADPARAM_STR "ADSP_EBADPARAM" +/* Unsupported routine or operation. */ +#define ADSP_EUNSUPPORTED_STR "ADSP_EUNSUPPORTED" +/* Unsupported version. */ +#define ADSP_EVERSION_STR "ADSP_EVERSION" +/* Unexpected problem encountered. */ +#define ADSP_EUNEXPECTED_STR "ADSP_EUNEXPECTED" +/* Unhandled problem occurred. */ +#define ADSP_EPANIC_STR "ADSP_EPANIC" +/* Unable to allocate resource. */ +#define ADSP_ENORESOURCE_STR "ADSP_ENORESOURCE" +/* Invalid handle. */ +#define ADSP_EHANDLE_STR "ADSP_EHANDLE" +/* Operation is already processed. */ +#define ADSP_EALREADY_STR "ADSP_EALREADY" +/* Operation is not ready to be processed. */ +#define ADSP_ENOTREADY_STR "ADSP_ENOTREADY" +/* Operation is pending completion. */ +#define ADSP_EPENDING_STR "ADSP_EPENDING" +/* Operation could not be accepted or processed. */ +#define ADSP_EBUSY_STR "ADSP_EBUSY" +/* Operation aborted due to an error. */ +#define ADSP_EABORTED_STR "ADSP_EABORTED" +/* Operation preempted by a higher priority. */ +#define ADSP_EPREEMPTED_STR "ADSP_EPREEMPTED" +/* Operation requests intervention to complete. */ +#define ADSP_ECONTINUE_STR "ADSP_ECONTINUE" +/* Operation requests immediate intervention to complete. */ +#define ADSP_EIMMEDIATE_STR "ADSP_EIMMEDIATE" +/* Operation is not implemented. */ +#define ADSP_ENOTIMPL_STR "ADSP_ENOTIMPL" +/* Operation needs more data or resources. */ +#define ADSP_ENEEDMORE_STR "ADSP_ENEEDMORE" +/* Operation does not have memory. */ +#define ADSP_ENOMEMORY_STR "ADSP_ENOMEMORY" +/* Item does not exist. */ +#define ADSP_ENOTEXIST_STR "ADSP_ENOTEXIST" +/* Unexpected error code. */ +#define ADSP_ERR_MAX_STR "ADSP_ERR_MAX" + +#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. */ + +static inline int q6dsp_map_channels(u8 *ch_map, int ch) +{ + memset(ch_map, 0, PCM_FORMAT_MAX_NUM_CHANNEL); + + if (ch == 1) { + ch_map[0] = PCM_CHANNEL_FC; + } else if (ch == 2) { + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + } else if (ch == 3) { + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_FC; + } else if (ch == 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; + } else if (ch == 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; + } else if (ch == 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; + } else if (ch == 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; + } else { + return -EINVAL; + } + + return 0; +} + +enum { + LEGACY_PCM_MODE = 0, + LOW_LATENCY_PCM_MODE, + ULTRA_LOW_LATENCY_PCM_MODE, + ULL_POST_PROCESSING_PCM_MODE, +}; + +struct adsp_err_code { + int lnx_err_code; + char *adsp_err_str; +}; + +static struct adsp_err_code adsp_err_code_info[ADSP_ERR_MAX+1] = { + { 0, ADSP_EOK_STR}, + { -ENOTRECOVERABLE, ADSP_EFAILED_STR}, + { -EINVAL, ADSP_EBADPARAM_STR}, + { -ENOSYS, ADSP_EUNSUPPORTED_STR}, + { -ENOPROTOOPT, ADSP_EVERSION_STR}, + { -ENOTRECOVERABLE, ADSP_EUNEXPECTED_STR}, + { -ENOTRECOVERABLE, ADSP_EPANIC_STR}, + { -ENOSPC, ADSP_ENORESOURCE_STR}, + { -EBADR, ADSP_EHANDLE_STR}, + { -EALREADY, ADSP_EALREADY_STR}, + { -EPERM, ADSP_ENOTREADY_STR}, + { -EINPROGRESS, ADSP_EPENDING_STR}, + { -EBUSY, ADSP_EBUSY_STR}, + { -ECANCELED, ADSP_EABORTED_STR}, + { -EAGAIN, ADSP_EPREEMPTED_STR}, + { -EAGAIN, ADSP_ECONTINUE_STR}, + { -EAGAIN, ADSP_EIMMEDIATE_STR}, + { -EAGAIN, ADSP_ENOTIMPL_STR}, + { -ENODATA, ADSP_ENEEDMORE_STR}, + { -EADV, ADSP_ERR_MAX_STR}, + { -ENOMEM, ADSP_ENOMEMORY_STR}, + { -ENODEV, ADSP_ENOTEXIST_STR}, + { -EADV, ADSP_ERR_MAX_STR}, +}; + +static inline int adsp_err_get_lnx_err_code(u32 adsp_error) +{ + if (adsp_error > ADSP_ERR_MAX) + return adsp_err_code_info[ADSP_ERR_MAX].lnx_err_code; + else + return adsp_err_code_info[adsp_error].lnx_err_code; +} + +static inline char *adsp_err_get_err_str(u32 adsp_error) +{ + if (adsp_error > ADSP_ERR_MAX) + return adsp_err_code_info[ADSP_ERR_MAX].adsp_err_str; + else + return adsp_err_code_info[adsp_error].adsp_err_str; +} + +#endif /* __Q6DSP_COMMON_H__ */
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
+static inline int q6dsp_map_channels(u8 *ch_map, int ch) +{
- memset(ch_map, 0, PCM_FORMAT_MAX_NUM_CHANNEL);
This implies that ch_map is always an array of PCM_FORMAT_MAX_NUM_CHANNEL elements. As such it would be better to express this in the prototype; i.e u8 ch_map[PCM_FORMAT_MAX_NUM_CHANNEL]
- if (ch == 1) {
This is a switch statement.
ch_map[0] = PCM_CHANNEL_FC;
- } else if (ch == 2) {
[..]
+struct adsp_err_code {
- int lnx_err_code;
Indentation, and these could be given more succinct names.
- char *adsp_err_str;
+};
+static struct adsp_err_code adsp_err_code_info[ADSP_ERR_MAX+1] = {
- { 0, ADSP_EOK_STR},
- { -ENOTRECOVERABLE, ADSP_EFAILED_STR},
- { -EINVAL, ADSP_EBADPARAM_STR},
- { -ENOSYS, ADSP_EUNSUPPORTED_STR},
- { -ENOPROTOOPT, ADSP_EVERSION_STR},
- { -ENOTRECOVERABLE, ADSP_EUNEXPECTED_STR},
- { -ENOTRECOVERABLE, ADSP_EPANIC_STR},
- { -ENOSPC, ADSP_ENORESOURCE_STR},
- { -EBADR, ADSP_EHANDLE_STR},
- { -EALREADY, ADSP_EALREADY_STR},
- { -EPERM, ADSP_ENOTREADY_STR},
- { -EINPROGRESS, ADSP_EPENDING_STR},
- { -EBUSY, ADSP_EBUSY_STR},
- { -ECANCELED, ADSP_EABORTED_STR},
- { -EAGAIN, ADSP_EPREEMPTED_STR},
- { -EAGAIN, ADSP_ECONTINUE_STR},
- { -EAGAIN, ADSP_EIMMEDIATE_STR},
- { -EAGAIN, ADSP_ENOTIMPL_STR},
- { -ENODATA, ADSP_ENEEDMORE_STR},
- { -EADV, ADSP_ERR_MAX_STR},
This, element 0x13, is not listed among the defined errors. Is this a placeholder?
How about making this even more descriptive by using the format
[ADSP_EBADPARAM] = { -EINVAL, ADSP_EBADPARAM_STR },
That way the mapping table is self-describing.
And you can use ARRAY_SIZE() instead of specifying the fixed size of ADSP_ERR_MAX + 1...
- { -ENOMEM, ADSP_ENOMEMORY_STR},
- { -ENODEV, ADSP_ENOTEXIST_STR},
- { -EADV, ADSP_ERR_MAX_STR},
"Advertise error"?
+};
+static inline int adsp_err_get_lnx_err_code(u32 adsp_error)
Can this be made internal to some c-file? So that any third party deals only with linux error codes?
How about renaming this q6dsp_errno()?
+{
- if (adsp_error > ADSP_ERR_MAX)
return adsp_err_code_info[ADSP_ERR_MAX].lnx_err_code;
- else
return adsp_err_code_info[adsp_error].lnx_err_code;
I think this would look better if you assign a local variable and have a single return. And just hard code the "invalid error code" errno, rather than looking up ADSP_ERR_MAX in the list.
+}
+static inline char *adsp_err_get_err_str(u32 adsp_error)
q6dsp_strerror(), to match strerror(3)?
+{
- if (adsp_error > ADSP_ERR_MAX)
return adsp_err_code_info[ADSP_ERR_MAX].adsp_err_str;
- else
return adsp_err_code_info[adsp_error].adsp_err_str;
And I do think that, as with strerror, this should return a human readable error, not the stringified define.
+}
I'm puzzled to why these helper functions lives in a header file, at least some aspects of this would better be hidden...
Regards, Bjorn
Thanks for the review comments,
On 02/01/18 00:19, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
+static inline int q6dsp_map_channels(u8 *ch_map, int ch) +{
- memset(ch_map, 0, PCM_FORMAT_MAX_NUM_CHANNEL);
This implies that ch_map is always an array of PCM_FORMAT_MAX_NUM_CHANNEL elements. As such it would be better to express this in the prototype; i.e u8 ch_map[PCM_FORMAT_MAX_NUM_CHANNEL]
Yep, Will do that.
- if (ch == 1) {
This is a switch statement.
Yes, makes more sense.
ch_map[0] = PCM_CHANNEL_FC;
- } else if (ch == 2) {
[..]
+struct adsp_err_code {
- int lnx_err_code;
Indentation, and these could be given more succinct names.
- char *adsp_err_str;
+};
+static struct adsp_err_code adsp_err_code_info[ADSP_ERR_MAX+1] = {
- { 0, ADSP_EOK_STR},
- { -ENOTRECOVERABLE, ADSP_EFAILED_STR},
- { -EINVAL, ADSP_EBADPARAM_STR},
- { -ENOSYS, ADSP_EUNSUPPORTED_STR},
- { -ENOPROTOOPT, ADSP_EVERSION_STR},
- { -ENOTRECOVERABLE, ADSP_EUNEXPECTED_STR},
- { -ENOTRECOVERABLE, ADSP_EPANIC_STR},
- { -ENOSPC, ADSP_ENORESOURCE_STR},
- { -EBADR, ADSP_EHANDLE_STR},
- { -EALREADY, ADSP_EALREADY_STR},
- { -EPERM, ADSP_ENOTREADY_STR},
- { -EINPROGRESS, ADSP_EPENDING_STR},
- { -EBUSY, ADSP_EBUSY_STR},
- { -ECANCELED, ADSP_EABORTED_STR},
- { -EAGAIN, ADSP_EPREEMPTED_STR},
- { -EAGAIN, ADSP_ECONTINUE_STR},
- { -EAGAIN, ADSP_EIMMEDIATE_STR},
- { -EAGAIN, ADSP_ENOTIMPL_STR},
- { -ENODATA, ADSP_ENEEDMORE_STR},
- { -EADV, ADSP_ERR_MAX_STR},
This, element 0x13, is not listed among the defined errors. Is this a placeholder?
How about making this even more descriptive by using the format
[ADSP_EBADPARAM] = { -EINVAL, ADSP_EBADPARAM_STR },
That way the mapping table is self-describing.
And you can use ARRAY_SIZE() instead of specifying the fixed size of ADSP_ERR_MAX + 1...
Will give that a try!
- { -ENOMEM, ADSP_ENOMEMORY_STR},
- { -ENODEV, ADSP_ENOTEXIST_STR},
- { -EADV, ADSP_ERR_MAX_STR},
"Advertise error"?
No, downstream seems to define any unexpected error as -EADV, am not sure if this correct, probably we should change this to be more sensible one.
+};
+static inline int adsp_err_get_lnx_err_code(u32 adsp_error)
Can this be made internal to some c-file? So that any third party deals only with linux error codes?
How about renaming this q6dsp_errno()?
yep will do that.
+{
- if (adsp_error > ADSP_ERR_MAX)
return adsp_err_code_info[ADSP_ERR_MAX].lnx_err_code;
- else
return adsp_err_code_info[adsp_error].lnx_err_code;
I think this would look better if you assign a local variable and have a single return. And just hard code the "invalid error code" errno, rather than looking up ADSP_ERR_MAX in the list.
+}
+static inline char *adsp_err_get_err_str(u32 adsp_error)
q6dsp_strerror(), to match strerror(3)?
yep!
+{
- if (adsp_error > ADSP_ERR_MAX)
return adsp_err_code_info[ADSP_ERR_MAX].adsp_err_str;
- else
return adsp_err_code_info[adsp_error].adsp_err_str;
And I do think that, as with strerror, this should return a human readable error, not the stringified define.
okay!
+}
I'm puzzled to why these helper functions lives in a header file, at least some aspects of this would better be hidden...
Will try to improve on this in next version.
Regards, Bjorn
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6 AFE (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 --- sound/soc/qcom/Kconfig | 13 ++ sound/soc/qcom/Makefile | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6afe.c | 503 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6afe.h | 30 +++ 5 files changed, 552 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/Makefile create mode 100644 sound/soc/qcom/qdsp6/q6afe.c create mode 100644 sound/soc/qcom/qdsp6/q6afe.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 8ec9a074b38b..1db92069a6a0 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -43,3 +43,16 @@ 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_AFE + tristate + default n + +config SND_SOC_QDSP6 + tristate "SoC ALSA audio driver for QDSP6" + select SND_SOC_QDSP6_AFE + 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/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 new file mode 100644 index 000000000000..313e65f571db --- /dev/null +++ b/sound/soc/qcom/qdsp6/Makefile @@ -0,0 +1 @@ +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..a6a782e6f17d --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -0,0 +1,503 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, 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/platform_device.h> +#include <linux/delay.h> +#include <linux/soc/qcom/apr.h> +#include "common.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 q6afev2 { + void *apr; + struct device *dev; + int state; + int status; + struct platform_device *daidev; + + struct mutex afe_cmd_lock; + struct list_head port_list; + spinlock_t port_list_lock; + struct list_head node; +}; + +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; + union { + struct q6afev2 *v2; + } 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; +}; + +/* 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}, +}; + +static struct q6afe_port *afe_find_port(struct q6afev2 *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_data *data) +{ + struct q6afev2 *afe = dev_get_drvdata(&adev->dev);//priv; + struct q6afe_port *port; + + if (!data) { + dev_err(afe->dev, "%s: Invalid param data\n", __func__); + return -EINVAL; + } + + if (data->payload_size) { + uint32_t *payload = data->payload; + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (payload[1] != 0) { + afe->status = payload[1]; + dev_err(afe->dev, + "cmd = 0x%x returned error = 0x%x\n", + payload[0], payload[1]); + } + switch (payload[0]) { + 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", + payload[0]); + 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 q6afev2 *afe, void *data, + wait_queue_head_t *wait) +{ + int ret; + + if (wait) + afe->state = AFE_CMD_RESP_NONE; + + afe->status = 0; + ret = apr_send_pkt(afe->apr, data); + if (ret > 0) { + if (wait) { + 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", + adsp_err_get_err_str(afe->status)); + ret = adsp_err_get_lnx_err_code(afe->status); + } else { + ret = 0; + } + } else { + ret = 0; + } + } else { + dev_err(afe->dev, "packet not transmitted\n"); + ret = -EINVAL; + } + + 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 q6afev2 *afe = port->afe.v2; + int ret, index; + + index = port->token; + 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 = index; + 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 afe_port_start(struct q6afe_port *port, + union afe_port_config *afe_config) +{ + struct afe_audioif_config_command config; + struct q6afev2 *afe = port->afe.v2; + int ret = 0; + int port_id = port->id; + int cfg_type; + int index = 0; + + if (!afe_config) { + dev_err(afe->dev, "Error, no configuration data\n"); + ret = -EINVAL; + return ret; + } + + index = port->token; + + mutex_lock(&afe->afe_cmd_lock); + /* Also send the topology id here: */ + config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + config.hdr.pkt_size = sizeof(config); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + config.hdr.token = index; + + cfg_type = port->cfg_type; + config.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + config.param.port_id = port_id; + config.param.payload_size = sizeof(config) - sizeof(struct apr_hdr) - + sizeof(config.param); + config.param.payload_address_lsw = 0x00; + config.param.payload_address_msw = 0x00; + config.param.mem_map_handle = 0x00; + config.pdata.module_id = AFE_MODULE_AUDIO_DEV_INTERFACE; + config.pdata.param_id = cfg_type; + config.pdata.param_size = sizeof(config.port); + + config.port = *afe_config; + + ret = afe_apr_send_pkt(afe, &config, &port->wait); + if (ret) { + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + goto fail_cmd; + } + + ret = afe_send_cmd_port_start(port); + +fail_cmd: + mutex_unlock(&afe->afe_cmd_lock); + return ret; +} + +/** + * 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 q6afev2 *afe = port->afe.v2; + 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_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 q6afev2 *afe = dev_get_drvdata(dev->parent); + struct q6afe_port *port; + int token; + int cfg_type; + + if (!afe) { + dev_err(dev, "Unable to find instance of afe service\n"); + return ERR_PTR(-ENOENT); + } + + token = id; + if (token < 0 || token > AFE_PORT_MAX) { + dev_err(dev, "AFE port token[%d] invalid!\n", token); + return ERR_PTR(-EINVAL); + } + + port_id = port_maps[id].port_id; + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + init_waitqueue_head(&port->wait); + + port->token = token; + port->id = port_id; + + port->afe.v2 = afe; + 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->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 q6afev2 *afe = port->afe.v2; + + 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 q6afev2 *afe; + struct device *dev = &adev->dev; + + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + + afe->apr = adev; + mutex_init(&afe->afe_cmd_lock); + afe->dev = dev; + INIT_LIST_HEAD(&afe->port_list); + spin_lock_init(&afe->port_list_lock); + + dev_set_drvdata(dev, afe); + + afe->daidev = platform_device_register_data(&adev->dev, "q6afe_dai", + -1, NULL, 0); + return 0; +} + +static int q6afev2_remove(struct apr_device *adev) +{ + struct q6afev2 *afe = dev_get_drvdata(&adev->dev); + + platform_device_unregister(afe->daidev); + + return 0; +} + +static const struct apr_device_id q6asm_id[] = { + {"Q6AFE", APR_DOMAIN_ADSP, APR_SVC_AFE, APR_CLIENT_AUDIO}, + {} +}; + +static struct apr_driver qcom_q6afe_driver = { + .probe = q6afev2_probe, + .remove = q6afev2_remove, + .callback = afe_callback, + .id_table = q6asm_id, + .driver = { + .name = "qcom-q6afe", + }, +}; + +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..c04541b94a47 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6AFE_H__ +#define __Q6AFE_H__ + +/* Audio Front End (AFE) Ports */ +#define AFE_PORT_HDMI_RX 8 +#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; + +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 Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
[..]
+config SND_SOC_QDSP6_AFE
- tristate
- default n
Do you see a particular benefit of having one kernel module per function? Why not just compile them all into the same q6dsp.ko?
+config SND_SOC_QDSP6
- tristate "SoC ALSA audio driver for QDSP6"
- select SND_SOC_QDSP6_AFE
- 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.
[..]
+struct q6afev2 {
- void *apr;
apr has a type, even if it's definition is hidden you should use the proper type here.
- struct device *dev;
- int state;
- int status;
- struct platform_device *daidev;
- struct mutex afe_cmd_lock;
You shouldn't need to include afe_ in this name.
- struct list_head port_list;
- spinlock_t port_list_lock;
- struct list_head node;
+};
[..]
+/* 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,
Looks like you have an extra tab here, consider breaking the 80 char "rule" to not have to wrap these.
AFE_PORT_HDMI_RX, 1},
+};
+static struct q6afe_port *afe_find_port(struct q6afev2 *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;
+}
Make port_list an idr and you can just use idr_find() instead of rolling your own search function.
+static int afe_callback(struct apr_device *adev, struct apr_client_data *data) +{
- struct q6afev2 *afe = dev_get_drvdata(&adev->dev);//priv;
This is perfectly fine, no need to extend the interface with a priv (so drop the comment).
- struct q6afe_port *port;
- if (!data) {
dev_err(afe->dev, "%s: Invalid param data\n", __func__);
return -EINVAL;
- }
Just define on in the apr layer that data will never be NULL, that will save you 4 lines of code in every apr client.
- if (data->payload_size) {
uint32_t *payload = data->payload;
So the payload is 2 ints, where the first is a command and the second is the status of it. This you can express in a simple struct to make the code even easier on the eye.
if (data->opcode == APR_BASIC_RSP_RESULT) {
if (payload[1] != 0) {
afe->status = payload[1];
dev_err(afe->dev,
"cmd = 0x%x returned error = 0x%x\n",
payload[0], payload[1]);
}
switch (payload[0]) {
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",
payload[0]);
If you flip the check for payload_size to return early if there isn't a payload then you can reduce the indentation level one step and probably doesn't have to wrap this line.
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 q6afev2 *afe, void *data,
wait_queue_head_t *wait)
Rather than conditionally passing the wait, split this function in afe_send_sync(*afe, *data, wait) and afe_send_async(*afe, *data).
+{
- int ret;
- if (wait)
afe->state = AFE_CMD_RESP_NONE;
- afe->status = 0;
- ret = apr_send_pkt(afe->apr, data);
- if (ret > 0) {
Check ret < 0 and return here, this saves you one indentation level in the following chunk.
If you then check !wait and return early you can reduce another level.
if (wait) {
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",
adsp_err_get_err_str(afe->status));
ret = adsp_err_get_lnx_err_code(afe->status);
} else {
ret = 0;
}
} else {
ret = 0;
}
- } else {
dev_err(afe->dev, "packet not transmitted\n");
ret = -EINVAL;
- }
- 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 q6afev2 *afe = port->afe.v2;
- int ret, index;
- index = port->token;
- 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 = index;
Just put port->token here, saves you a local variable.
- 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 afe_port_start(struct q6afe_port *port,
union afe_port_config *afe_config)
+{
- struct afe_audioif_config_command config;
- struct q6afev2 *afe = port->afe.v2;
- int ret = 0;
- int port_id = port->id;
- int cfg_type;
- int index = 0;
- if (!afe_config) {
Looking at the one caller of this function, afe_config can't be NULL, so no need for this error handling.
dev_err(afe->dev, "Error, no configuration data\n");
ret = -EINVAL;
return ret;
- }
- index = port->token;
- mutex_lock(&afe->afe_cmd_lock);
- /* Also send the topology id here: */
- config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE),
APR_PKT_VER);
- config.hdr.pkt_size = sizeof(config);
- config.hdr.src_port = 0;
- config.hdr.dest_port = 0;
- config.hdr.token = index;
- cfg_type = port->cfg_type;
- config.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2;
- config.param.port_id = port_id;
- config.param.payload_size = sizeof(config) - sizeof(struct apr_hdr) -
sizeof(config.param);
- config.param.payload_address_lsw = 0x00;
- config.param.payload_address_msw = 0x00;
- config.param.mem_map_handle = 0x00;
- config.pdata.module_id = AFE_MODULE_AUDIO_DEV_INTERFACE;
- config.pdata.param_id = cfg_type;
- config.pdata.param_size = sizeof(config.port);
This looks like a good candidate for a afe_port_set_param() function.
- config.port = *afe_config;
- ret = afe_apr_send_pkt(afe, &config, &port->wait);
- if (ret) {
dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
port_id, ret);
goto fail_cmd;
- }
- ret = afe_send_cmd_port_start(port);
+fail_cmd:
- mutex_unlock(&afe->afe_cmd_lock);
- return ret;
+}
[..]
+/**
- 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)
Will there be any other getter? Otherwise you can just call this q6afe_port_get().
+{
- int port_id;
- struct q6afev2 *afe = dev_get_drvdata(dev->parent);
- struct q6afe_port *port;
- int token;
- int cfg_type;
- if (!afe) {
dev_err(dev, "Unable to find instance of afe service\n");
return ERR_PTR(-ENOENT);
- }
As this comes from the passed dev this check will catch bugs withing this driver, but if the client accidentally passes the wrong dev it's likely that you don't catch it here anyways. Consider dropping the check.
- token = id;
- if (token < 0 || token > AFE_PORT_MAX) {
dev_err(dev, "AFE port token[%d] invalid!\n", token);
return ERR_PTR(-EINVAL);
- }
- port_id = port_maps[id].port_id;
- port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
- if (!port)
return ERR_PTR(-ENOMEM);
- init_waitqueue_head(&port->wait);
- port->token = token;
- port->id = port_id;
- port->afe.v2 = afe;
- switch (port_id) {
How about moving this switch statement above the allocation?
- 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->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);
Regards, Bjorn
Thanks for the comments!
On 02/01/18 00:45, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
[..]
+config SND_SOC_QDSP6_AFE
- tristate
- default n
Do you see a particular benefit of having one kernel module per function? Why not just compile them all into the same q6dsp.ko?
No, I do not see any benefit in doing so, I can try to make it as single module in next version.
+config SND_SOC_QDSP6
- tristate "SoC ALSA audio driver for QDSP6"
- select SND_SOC_QDSP6_AFE
- 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.
[..]
+struct q6afev2 {
- void *apr;
apr has a type, even if it's definition is hidden you should use the proper type here.
I agree.
- struct device *dev;
- int state;
- int status;
- struct platform_device *daidev;
- struct mutex afe_cmd_lock;
You shouldn't need to include afe_ in this name.
make sense!
- struct list_head port_list;
- spinlock_t port_list_lock;
- struct list_head node;
+};
[..]
+/* 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,
Looks like you have an extra tab here, consider breaking the 80 char "rule" to not have to wrap these.
yep!
AFE_PORT_HDMI_RX, 1},
+};
+static struct q6afe_port *afe_find_port(struct q6afev2 *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;
+}
Make port_list an idr and you can just use idr_find() instead of rolling your own search function.
Will give that a go.
+static int afe_callback(struct apr_device *adev, struct apr_client_data *data) +{
- struct q6afev2 *afe = dev_get_drvdata(&adev->dev);//priv;
This is perfectly fine, no need to extend the interface with a priv (so drop the comment).
I think it was a leftover, will clean such instances.
- struct q6afe_port *port;
- if (!data) {
dev_err(afe->dev, "%s: Invalid param data\n", __func__);
return -EINVAL;
- }
Just define on in the apr layer that data will never be NULL, that will save you 4 lines of code in every apr client.
I agree!
- if (data->payload_size) {
uint32_t *payload = data->payload;
So the payload is 2 ints, where the first is a command and the second is the status of it. This you can express in a simple struct to make the code even easier on the eye.
Will do that, if it make code more readable.
if (data->opcode == APR_BASIC_RSP_RESULT) {
if (payload[1] != 0) {
afe->status = payload[1];
dev_err(afe->dev,
"cmd = 0x%x returned error = 0x%x\n",
payload[0], payload[1]);
}
switch (payload[0]) {
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",
payload[0]);
If you flip the check for payload_size to return early if there isn't a payload then you can reduce the indentation level one step and probably doesn't have to wrap this line.
yep make sense!
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 q6afev2 *afe, void *data,
wait_queue_head_t *wait)
Rather than conditionally passing the wait, split this function in afe_send_sync(*afe, *data, wait) and afe_send_async(*afe, *data).
Will do that across other modules too.
+{
- int ret;
- if (wait)
afe->state = AFE_CMD_RESP_NONE;
- afe->status = 0;
- ret = apr_send_pkt(afe->apr, data);
- if (ret > 0) {
Check ret < 0 and return here, this saves you one indentation level in the following chunk.
If you then check !wait and return early you can reduce another level.
okay!
if (wait) {
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",
adsp_err_get_err_str(afe->status));
ret = adsp_err_get_lnx_err_code(afe->status);
} else {
ret = 0;
}
} else {
ret = 0;
}
- } else {
dev_err(afe->dev, "packet not transmitted\n");
ret = -EINVAL;
- }
- 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 q6afev2 *afe = port->afe.v2;
- int ret, index;
- index = port->token;
- 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 = index;
Just put port->token here, saves you a local variable.
yep!
- 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 afe_port_start(struct q6afe_port *port,
union afe_port_config *afe_config)
+{
- struct afe_audioif_config_command config;
- struct q6afev2 *afe = port->afe.v2;
- int ret = 0;
- int port_id = port->id;
- int cfg_type;
- int index = 0;
- if (!afe_config) {
Looking at the one caller of this function, afe_config can't be NULL, so no need for this error handling.
okay.
dev_err(afe->dev, "Error, no configuration data\n");
ret = -EINVAL;
return ret;
- }
- index = port->token;
- mutex_lock(&afe->afe_cmd_lock);
- /* Also send the topology id here: */
- config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
APR_HDR_LEN(APR_HDR_SIZE),
APR_PKT_VER);
- config.hdr.pkt_size = sizeof(config);
- config.hdr.src_port = 0;
- config.hdr.dest_port = 0;
- config.hdr.token = index;
- cfg_type = port->cfg_type;
- config.hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2;
- config.param.port_id = port_id;
- config.param.payload_size = sizeof(config) - sizeof(struct apr_hdr) -
sizeof(config.param);
- config.param.payload_address_lsw = 0x00;
- config.param.payload_address_msw = 0x00;
- config.param.mem_map_handle = 0x00;
- config.pdata.module_id = AFE_MODULE_AUDIO_DEV_INTERFACE;
- config.pdata.param_id = cfg_type;
- config.pdata.param_size = sizeof(config.port);
This looks like a good candidate for a afe_port_set_param() function.
makes sense.
- config.port = *afe_config;
- ret = afe_apr_send_pkt(afe, &config, &port->wait);
- if (ret) {
dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
port_id, ret);
goto fail_cmd;
- }
- ret = afe_send_cmd_port_start(port);
+fail_cmd:
- mutex_unlock(&afe->afe_cmd_lock);
- return ret;
+}
[..]
+/**
- 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)
Will there be any other getter? Otherwise you can just call this q6afe_port_get().
There is one more get, which is basically lookup from index to port number.
+{
- int port_id;
- struct q6afev2 *afe = dev_get_drvdata(dev->parent);
- struct q6afe_port *port;
- int token;
- int cfg_type;
- if (!afe) {
dev_err(dev, "Unable to find instance of afe service\n");
return ERR_PTR(-ENOENT);
- }
As this comes from the passed dev this check will catch bugs withing this driver, but if the client accidentally passes the wrong dev it's likely that you don't catch it here anyways. Consider dropping the check.
yes!
- token = id;
- if (token < 0 || token > AFE_PORT_MAX) {
dev_err(dev, "AFE port token[%d] invalid!\n", token);
return ERR_PTR(-EINVAL);
- }
- port_id = port_maps[id].port_id;
- port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
- if (!port)
return ERR_PTR(-ENOMEM);
- init_waitqueue_head(&port->wait);
- port->token = token;
- port->id = port_id;
- port->afe.v2 = afe;
- switch (port_id) {
How about moving this switch statement above the allocation?
Yes, this can be done!
- 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->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);
Regards, Bjorn
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6 ADM (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 | 602 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6adm.h | 24 ++ 4 files changed, 632 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 1db92069a6a0..a307880dc992 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -48,9 +48,14 @@ 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" 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 313e65f571db..052813ea7062 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1 +1,2 @@ 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..b9f79a198ea4 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6adm.c @@ -0,0 +1,602 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, 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/wait.h> +#include <linux/soc/qcom/apr.h> +#include <linux/platform_device.h> +#include <sound/asound.h> +#include "q6adm.h" +#include "q6afe.h" +#include "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 cnt; + int topology; + int mode; + int stat; + int rate; + int bit_width; + int channels; + int app_type; + int acdb_id; + 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 platform_device *routing_dev; + + wait_queue_head_t matrix_map_wait; +}; + +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 struct copp *adm_find_matching_copp(struct q6adm *adm, + int port_idx, int topology, + int mode, int rate, + 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_idx == 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); + + return NULL; + +} + +static int adm_callback(struct apr_device *adev, struct apr_client_data *data) +{ + uint32_t *payload; + int port_idx, copp_idx; + struct copp *copp; + struct q6adm *adm = dev_get_drvdata(&adev->dev); + + payload = data->payload; + + if (data->payload_size) { + 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; + } + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (payload[1] != 0) { + dev_err(&adev->dev, "cmd = 0x%x returned error = 0x%x\n", + payload[0], payload[1]); + } + switch (payload[0]) { + 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 = payload[1]; + wake_up(&copp->wait); + break; + case ADM_CMD_MATRIX_MAP_ROUTINGS_V5: + adm->matrix_map_stat = payload[1]; + wake_up(&adm->matrix_map_wait); + break; + + default: + dev_err(&adev->dev, "Unknown Cmd: 0x%x\n", + payload[0]); + break; + } + return 0; + } + + switch (data->opcode) { + 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 = payload[0]; + copp->id = open->copp_id; + pr_debug("%s: coppid rxed=%d\n", __func__, + 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); + + set_bit(idx, &adm->copp_bitmap[port_idx]); + + c = devm_kzalloc(adm->dev, sizeof(*c), GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + c->copp_idx = idx; + c->afe_port = port_idx; + c->adm = adm; + + 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); +} +/** + * q6adm_open() - open adm to get hold of 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 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 open; + int ret = 0; + int port_idx, flags; + int tmp_port = q6afe_get_port_id(port_id); + struct copp *copp; + struct q6adm *adm = dev_get_drvdata(dev->parent); + + port_idx = port_id; + if (port_idx < 0) { + dev_err(dev, "Invalid port_id 0x%x\n", port_id); + return -EINVAL; + } + + flags = ADM_LEGACY_DEVICE_SESSION; + copp = adm_find_matching_copp(adm, port_idx, topology, perf_mode, + rate, bit_width, app_type); + + if (!copp) { + copp = adm_alloc_copp(adm, port_idx); + if (IS_ERR_OR_NULL(copp)) + return PTR_ERR(copp); + + copp->cnt = 0; + copp->topology = topology; + copp->mode = perf_mode; + copp->rate = rate; + copp->channels = channel_mode; + copp->bit_width = bit_width; + copp->app_type = app_type; + } + + /* Create a COPP if port id are not enabled */ + if (copp->cnt == 0) { + + 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 = tmp_port; + open.hdr.dest_svc = APR_SVC_ADM; + open.hdr.dest_domain = APR_DOMAIN_ADSP; + open.hdr.dest_port = tmp_port; + open.hdr.token = port_idx << 16 | copp->copp_idx; + open.hdr.opcode = ADM_CMD_DEVICE_OPEN_V5; + open.flags = flags; + open.mode_of_operation = path; + open.endpoint_id_1 = tmp_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; + + copp->stat = -1; + ret = apr_send_pkt(adm->apr, (uint32_t *)&open); + if (ret < 0) { + dev_err(dev, "port_id: 0x%x for[0x%x] failed %d\n", + tmp_port, port_id, ret); + return -EINVAL; + } + /* 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 timedout port_id: 0x%x for [0x%x]\n", + tmp_port, port_id); + return -EINVAL; + } else if (copp->stat > 0) { + dev_err(dev, "DSP returned error[%s]\n", + adsp_err_get_err_str(copp->stat)); + return adsp_err_get_lnx_err_code(copp->stat); + } + } + copp->cnt++; + return copp->copp_idx; +} +EXPORT_SYMBOL_GPL(q6adm_open); +/** + * 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 adm_cmd_matrix_map_routings_v5 { + struct apr_hdr hdr; + u32 matrix_id; + u32 num_sessions; + } __packed * route; + + struct adm_session_map_node_v5 { + u16 session_id; + u16 num_copps; + } __packed * node; + struct q6adm *adm = dev_get_drvdata(dev->parent); + uint16_t *copps_list; + int cmd_size = 0; + int ret = 0, i = 0; + void *payload = NULL; + void *matrix_map = NULL; + int port_idx, copp_idx; + 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 = (struct adm_cmd_matrix_map_routings_v5 *)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.src_port = 0; /* Ignored */ + route->hdr.dest_svc = APR_SVC_ADM; + route->hdr.dest_domain = APR_DOMAIN_ADSP; + route->hdr.dest_port = 0; /* Ignored */ + 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; + } + + payload = ((u8 *) matrix_map + sizeof(*route)); + node = (struct adm_session_map_node_v5 *)payload; + + node->session_id = payload_map.session_id; + node->num_copps = payload_map.num_copps; + payload = (u8 *) node + sizeof(*node); + copps_list = (uint16_t *) payload; + + for (i = 0; i < payload_map.num_copps; i++) { + 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]); + return -EINVAL; + } + copp_idx = payload_map.copp_idx[i]; + + copp = adm_find_copp(adm, port_idx, copp_idx); + if (IS_ERR_OR_NULL(copp)) + return -EINVAL; + + copps_list[i] = copp->id; + } + + adm->matrix_map_stat = -1; + + ret = apr_send_pkt(adm->apr, (uint32_t *) 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 = -EINVAL; + goto fail_cmd; + } else if (adm->matrix_map_stat > 0) { + dev_err(dev, "DSP returned error[%s]\n", + adsp_err_get_err_str(adm->matrix_map_stat)); + ret = adsp_err_get_lnx_err_code(adm->matrix_map_stat); + goto fail_cmd; + } + +fail_cmd: + kfree(matrix_map); + return ret; +} +EXPORT_SYMBOL_GPL(q6adm_matrix_map); + +static void adm_reset_copp(struct copp *c) +{ + c->id = RESET_COPP_ID; + c->cnt = 0; + c->topology = 0; + c->mode = 0; + c->stat = -1; + c->rate = 0; + c->channels = 0; + c->bit_width = 0; + c->app_type = 0; +} +/** + * 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 apr_hdr close; + struct copp *copp; + + int ret = 0, port_idx; + int copp_id = RESET_COPP_ID; + struct q6adm *adm = dev_get_drvdata(dev->parent); + + port_idx = port_id; + if (port_idx < 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; + + copp->cnt--; + if (!copp->cnt) { + copp_id = copp->id; + + 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_idx << 16 | copp_idx; + close.opcode = ADM_CMD_DEVICE_CLOSE_V5; + + ret = apr_send_pkt(adm->apr, (uint32_t *) &close); + if (ret < 0) { + dev_err(dev, "ADM close failed %d\n", ret); + return -EINVAL; + } + + ret = wait_event_timeout(copp->wait, copp->stat >= 0, + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + dev_err(dev, "ADM cmd Route timedout for port 0x%x\n", + port_id); + return -EINVAL; + } else if (copp->stat > 0) { + dev_err(dev, "DSP returned error[%s]\n", + adsp_err_get_err_str(copp->stat)); + return adsp_err_get_lnx_err_code(copp->stat); + } + + adm_reset_copp(copp); + 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; + init_waitqueue_head(&adm->matrix_map_wait); + + INIT_LIST_HEAD(&adm->copps_list); + spin_lock_init(&adm->copps_list_lock); + + adm->routing_dev = platform_device_register_data(&adev->dev, + "q6routing", + -1, NULL, 0); + + + return 0; +} + +static int q6adm_exit(struct apr_device *adev) +{ + struct q6adm *adm = dev_get_drvdata(&adev->dev); + + platform_device_unregister(adm->routing_dev); + + return 0; +} + +static const struct apr_device_id adm_id[] = { + {"Q6ADM", APR_DOMAIN_ADSP, APR_SVC_ADM, APR_CLIENT_AUDIO}, + {} +}; + +static struct apr_driver qcom_q6adm_driver = { + .probe = q6adm_probe, + .remove = q6adm_exit, + .callback = adm_callback, + .id_table = adm_id, + .driver = { + .name = "qcom-q6adm", + }, +}; + +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..aa7b3ba4360b --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6adm.h @@ -0,0 +1,24 @@ +/* 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]; +}; + +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 Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6 ADM (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
lower case "Rate"
different clocks, it handles volume ramping, Mixing with channel
and "Mixing"
and bit-width. ADM creates and destroys dynamic COPP services for device-related audio processing as needed.
What's a "copp"?
This patch adds basic support to ADM.
Wouldn't s/to/for/ be better?
[..]
+struct copp {
- int afe_port;
- int copp_idx;
- int id;
- int cnt;
Please rename this "refcnt" to match other kernel code.
- int topology;
- int mode;
- int stat;
- int rate;
- int bit_width;
- int channels;
- int app_type;
- int acdb_id;
- 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 platform_device *routing_dev;
- wait_queue_head_t matrix_map_wait;
+};
+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 struct copp *adm_find_matching_copp(struct q6adm *adm,
int port_idx, int topology,
int mode, int rate,
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_idx == 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);
- return NULL;
+}
+static int adm_callback(struct apr_device *adev, struct apr_client_data *data) +{
- uint32_t *payload;
- int port_idx, copp_idx;
- struct copp *copp;
- struct q6adm *adm = dev_get_drvdata(&adev->dev);
- payload = data->payload;
- if (data->payload_size) {
Bail if you don't have a payload and save yourself one indentation level.
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;
}
if (data->opcode == APR_BASIC_RSP_RESULT) {
This is a case in the following switch statement.
if (payload[1] != 0) {
dev_err(&adev->dev, "cmd = 0x%x returned error = 0x%x\n",
payload[0], payload[1]);
This would again benefit from a small struct...
}
switch (payload[0]) {
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 = payload[1];
wake_up(&copp->wait);
break;
case ADM_CMD_MATRIX_MAP_ROUTINGS_V5:
adm->matrix_map_stat = payload[1];
wake_up(&adm->matrix_map_wait);
break;
default:
dev_err(&adev->dev, "Unknown Cmd: 0x%x\n",
payload[0]);
break;
}
return 0;
}
switch (data->opcode) {
case ADM_CMDRSP_DEVICE_OPEN_V5:{
Perhaps it would be cleaner to break these out in separate functions?
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 = payload[0];
copp->id = open->copp_id;
pr_debug("%s: coppid rxed=%d\n", __func__,
You have a dev, use dev_dbg()
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);
- set_bit(idx, &adm->copp_bitmap[port_idx]);
- c = devm_kzalloc(adm->dev, sizeof(*c), GFP_KERNEL);
- if (!c)
Set the bit after doing the allocation and you don't need to clear it here.
return ERR_PTR(-ENOMEM);
- c->copp_idx = idx;
- c->afe_port = port_idx;
- c->adm = adm;
- 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);
This function clear the copp_bitmap, so after recycling this once c->copp_idx will be "dangling".
This seems to put the copp in a state where it is invalid and as the copp is "reset" i don't think adm_find_matching_copp() will actually find this again, ever...
So shouldn't you free the copp here too - rather than relying on devm doing that at some later point in time?
+} +/**
- q6adm_open() - open adm to get hold of free copp
"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 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 open;
- int ret = 0;
- int port_idx, flags;
- int tmp_port = q6afe_get_port_id(port_id);
- struct copp *copp;
- struct q6adm *adm = dev_get_drvdata(dev->parent);
- port_idx = port_id;
I'm not seeing the reason for having two different variables with the same value.
- if (port_idx < 0) {
dev_err(dev, "Invalid port_id 0x%x\n", port_id);
return -EINVAL;
- }
- flags = ADM_LEGACY_DEVICE_SESSION;
Just put ADM_LEGACY_DEVICE_SESSION in the assignment below.
- copp = adm_find_matching_copp(adm, port_idx, topology, perf_mode,
rate, bit_width, app_type);
- if (!copp) {
copp = adm_alloc_copp(adm, port_idx);
if (IS_ERR_OR_NULL(copp))
return PTR_ERR(copp);
copp->cnt = 0;
copp->topology = topology;
copp->mode = perf_mode;
copp->rate = rate;
copp->channels = channel_mode;
copp->bit_width = bit_width;
copp->app_type = app_type;
- }
I would suggest that you make adm_find_matching_copp() allocate the new copp if none is found.
- /* Create a COPP if port id are not enabled */
- if (copp->cnt == 0) {
Doesn't this scheme require some locking? What about concurrent close()?
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 = tmp_port;
open.hdr.dest_svc = APR_SVC_ADM;
open.hdr.dest_domain = APR_DOMAIN_ADSP;
open.hdr.dest_port = tmp_port;
open.hdr.token = port_idx << 16 | copp->copp_idx;
open.hdr.opcode = ADM_CMD_DEVICE_OPEN_V5;
open.flags = flags;
open.mode_of_operation = path;
open.endpoint_id_1 = tmp_port;
open.topology_id = topology;
open.dev_num_channel = channel_mode & 0x00FF;
open.bit_width = bit_width;
open.sample_rate = rate;
This looks like a q6adm_device_open() helper function.
ret = q6dsp_map_channels(&open.dev_channel_mapping[0],
channel_mode);
if (ret)
return ret;
copp->stat = -1;
ret = apr_send_pkt(adm->apr, (uint32_t *)&open);
if (ret < 0) {
dev_err(dev, "port_id: 0x%x for[0x%x] failed %d\n",
tmp_port, port_id, ret);
return -EINVAL;
}
/* 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 timedout port_id: 0x%x for [0x%x]\n",
tmp_port, port_id);
return -EINVAL;
} else if (copp->stat > 0) {
dev_err(dev, "DSP returned error[%s]\n",
adsp_err_get_err_str(copp->stat));
return adsp_err_get_lnx_err_code(copp->stat);
}
- }
- copp->cnt++;
- return copp->copp_idx;
+} +EXPORT_SYMBOL_GPL(q6adm_open); +/**
- 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 adm_cmd_matrix_map_routings_v5 {
struct apr_hdr hdr;
u32 matrix_id;
u32 num_sessions;
- } __packed * route;
- struct adm_session_map_node_v5 {
u16 session_id;
u16 num_copps;
- } __packed * node;
- struct q6adm *adm = dev_get_drvdata(dev->parent);
- uint16_t *copps_list;
- int cmd_size = 0;
- int ret = 0, i = 0;
- void *payload = NULL;
- void *matrix_map = NULL;
- int port_idx, copp_idx;
- 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 = (struct adm_cmd_matrix_map_routings_v5 *)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.src_port = 0; /* Ignored */
Omit the ignored members instead.
- route->hdr.dest_svc = APR_SVC_ADM;
- route->hdr.dest_domain = APR_DOMAIN_ADSP;
- route->hdr.dest_port = 0; /* Ignored */
- 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;
- }
- payload = ((u8 *) matrix_map + sizeof(*route));
matrix_map is a void *, so no need to cast it to u8 * to calculate the payload address.
- node = (struct adm_session_map_node_v5 *)payload;
payload is void *, so no need to typecast here. And for that matter, I'm not sure about the benefits of having this intermediate "payload" variable, just assign node directly.
- node->session_id = payload_map.session_id;
- node->num_copps = payload_map.num_copps;
- payload = (u8 *) node + sizeof(*node);
- copps_list = (uint16_t *) payload;
As with node above, drop the temporary variable and drop the type casts.
- for (i = 0; i < payload_map.num_copps; i++) {
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]);
Leaking matrix_map.
return -EINVAL;
}
copp_idx = payload_map.copp_idx[i];
copp = adm_find_copp(adm, port_idx, copp_idx);
if (IS_ERR_OR_NULL(copp))
Dito.
return -EINVAL;
copps_list[i] = copp->id;
- }
- adm->matrix_map_stat = -1;
- ret = apr_send_pkt(adm->apr, (uint32_t *) 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 = -EINVAL;
goto fail_cmd;
- } else if (adm->matrix_map_stat > 0) {
dev_err(dev, "DSP returned error[%s]\n",
adsp_err_get_err_str(adm->matrix_map_stat));
ret = adsp_err_get_lnx_err_code(adm->matrix_map_stat);
goto fail_cmd;
- }
+fail_cmd:
- kfree(matrix_map);
- return ret;
+} +EXPORT_SYMBOL_GPL(q6adm_matrix_map);
+static void adm_reset_copp(struct copp *c)
As far as I can see this will decommission the copp, so I don't think there is a point in updating any of this and then keep it around?
+{
- c->id = RESET_COPP_ID;
- c->cnt = 0;
- c->topology = 0;
- c->mode = 0;
- c->stat = -1;
- c->rate = 0;
- c->channels = 0;
- c->bit_width = 0;
- c->app_type = 0;
+} +/**
- 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 apr_hdr close;
- struct copp *copp;
- int ret = 0, port_idx;
- int copp_id = RESET_COPP_ID;
- struct q6adm *adm = dev_get_drvdata(dev->parent);
- port_idx = port_id;
- if (port_idx < 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;
- copp->cnt--;
- if (!copp->cnt) {
Again, this needs some locking.
copp_id = copp->id;
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_idx << 16 | copp_idx;
close.opcode = ADM_CMD_DEVICE_CLOSE_V5;
Split this out into a separate helper function.
ret = apr_send_pkt(adm->apr, (uint32_t *) &close);
if (ret < 0) {
dev_err(dev, "ADM close failed %d\n", ret);
return -EINVAL;
}
ret = wait_event_timeout(copp->wait, copp->stat >= 0,
msecs_to_jiffies(TIMEOUT_MS));
if (!ret) {
dev_err(dev, "ADM cmd Route timedout for port 0x%x\n",
port_id);
return -EINVAL;
} else if (copp->stat > 0) {
dev_err(dev, "DSP returned error[%s]\n",
adsp_err_get_err_str(copp->stat));
return adsp_err_get_lnx_err_code(copp->stat);
}
adm_reset_copp(copp);
adm_free_copp(adm, copp, port_id);
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(q6adm_close);
[..]
+static struct apr_driver qcom_q6adm_driver = {
- .probe = q6adm_probe,
- .remove = q6adm_exit,
- .callback = adm_callback,
- .id_table = adm_id,
- .driver = {
.name = "qcom-q6adm",
},
Indentation.
Regards, Bjorn
Thanks for your review comments.
On 02/01/18 01:50, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6 ADM (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
lower case "Rate"
different clocks, it handles volume ramping, Mixing with channel
and "Mixing"
and bit-width. ADM creates and destroys dynamic COPP services for device-related audio processing as needed.
What's a "copp"?
Common post processing.
This patch adds basic support to ADM.
Wouldn't s/to/for/ be better?
Yes!
[..]
+struct copp {
- int afe_port;
- int copp_idx;
- int id;
- int cnt;
Please rename this "refcnt" to match other kernel code.
yep.
- int topology;
- int mode;
[...]
+};
[...]
+static int adm_callback(struct apr_device *adev, struct apr_client_data *data) +{
- uint32_t *payload;
- int port_idx, copp_idx;
- struct copp *copp;
- struct q6adm *adm = dev_get_drvdata(&adev->dev);
- payload = data->payload;
- if (data->payload_size) {
Bail if you don't have a payload and save yourself one indentation level.
yep!
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;
}
if (data->opcode == APR_BASIC_RSP_RESULT) {
This is a case in the following switch statement.
if (payload[1] != 0) {
dev_err(&adev->dev, "cmd = 0x%x returned error = 0x%x\n",
payload[0], payload[1]);
This would again benefit from a small struct...
}
switch (payload[0]) {
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 = payload[1];
wake_up(&copp->wait);
break;
case ADM_CMD_MATRIX_MAP_ROUTINGS_V5:
adm->matrix_map_stat = payload[1];
wake_up(&adm->matrix_map_wait);
break;
default:
dev_err(&adev->dev, "Unknown Cmd: 0x%x\n",
payload[0]);
break;
}
return 0;
}
switch (data->opcode) {
case ADM_CMDRSP_DEVICE_OPEN_V5:{
Perhaps it would be cleaner to break these out in separate functions?
will do!
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 = payload[0];
copp->id = open->copp_id;
pr_debug("%s: coppid rxed=%d\n", __func__,
You have a dev, use dev_dbg()
I think this was a leftover from previous versions, I will fix such occurrences.
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);
- set_bit(idx, &adm->copp_bitmap[port_idx]);
- c = devm_kzalloc(adm->dev, sizeof(*c), GFP_KERNEL);
- if (!c)
Set the bit after doing the allocation and you don't need to clear it here.
Makes sense.
return ERR_PTR(-ENOMEM);
- c->copp_idx = idx;
- c->afe_port = port_idx;
- c->adm = adm;
- 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);
This function clear the copp_bitmap, so after recycling this once c->copp_idx will be "dangling".
This seems to put the copp in a state where it is invalid and as the copp is "reset" i don't think adm_find_matching_copp() will actually find this again, ever...
So shouldn't you free the copp here too - rather than relying on devm doing that at some later point in time?
Yes I agree.
+} +/**
- q6adm_open() - open adm to get hold of free copp
"open adm and grab a free copp"?
yep.
- @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 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 open;
- int ret = 0;
- int port_idx, flags;
- int tmp_port = q6afe_get_port_id(port_id);
- struct copp *copp;
- struct q6adm *adm = dev_get_drvdata(dev->parent);
- port_idx = port_id;
I'm not seeing the reason for having two different variables with the same value.
not sure why it ended up like this, will fix it.
- if (port_idx < 0) {
dev_err(dev, "Invalid port_id 0x%x\n", port_id);
return -EINVAL;
- }
- flags = ADM_LEGACY_DEVICE_SESSION;
Just put ADM_LEGACY_DEVICE_SESSION in the assignment below.
yep!
- copp = adm_find_matching_copp(adm, port_idx, topology, perf_mode,
rate, bit_width, app_type);
- if (!copp) {
copp = adm_alloc_copp(adm, port_idx);
if (IS_ERR_OR_NULL(copp))
return PTR_ERR(copp);
copp->cnt = 0;
copp->topology = topology;
copp->mode = perf_mode;
copp->rate = rate;
copp->channels = channel_mode;
copp->bit_width = bit_width;
copp->app_type = app_type;
- }
I would suggest that you make adm_find_matching_copp() allocate the new copp if none is found.
will have a go and see!
- /* Create a COPP if port id are not enabled */
- if (copp->cnt == 0) {
Doesn't this scheme require some locking? What about concurrent close()?
yes, it will be issue if we do concurrent closes(), will revisit locking on this.
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 = tmp_port;
open.hdr.dest_svc = APR_SVC_ADM;
open.hdr.dest_domain = APR_DOMAIN_ADSP;
open.hdr.dest_port = tmp_port;
open.hdr.token = port_idx << 16 | copp->copp_idx;
open.hdr.opcode = ADM_CMD_DEVICE_OPEN_V5;
open.flags = flags;
open.mode_of_operation = path;
open.endpoint_id_1 = tmp_port;
open.topology_id = topology;
open.dev_num_channel = channel_mode & 0x00FF;
open.bit_width = bit_width;
open.sample_rate = rate;
This looks like a q6adm_device_open() helper function.
yep!
ret = q6dsp_map_channels(&open.dev_channel_mapping[0],
channel_mode);
if (ret)
return ret;
copp->stat = -1;
ret = apr_send_pkt(adm->apr, (uint32_t *)&open);
if (ret < 0) {
dev_err(dev, "port_id: 0x%x for[0x%x] failed %d\n",
tmp_port, port_id, ret);
return -EINVAL;
}
/* 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 timedout port_id: 0x%x for [0x%x]\n",
tmp_port, port_id);
return -EINVAL;
} else if (copp->stat > 0) {
dev_err(dev, "DSP returned error[%s]\n",
adsp_err_get_err_str(copp->stat));
return adsp_err_get_lnx_err_code(copp->stat);
}
- }
- copp->cnt++;
- return copp->copp_idx;
+} +EXPORT_SYMBOL_GPL(q6adm_open); +/**
- 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 adm_cmd_matrix_map_routings_v5 {
struct apr_hdr hdr;
u32 matrix_id;
u32 num_sessions;
- } __packed * route;
- struct adm_session_map_node_v5 {
u16 session_id;
u16 num_copps;
- } __packed * node;
- struct q6adm *adm = dev_get_drvdata(dev->parent);
- uint16_t *copps_list;
- int cmd_size = 0;
- int ret = 0, i = 0;
- void *payload = NULL;
- void *matrix_map = NULL;
- int port_idx, copp_idx;
- 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 = (struct adm_cmd_matrix_map_routings_v5 *)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.src_port = 0; /* Ignored */
Omit the ignored members instead.
yep!
- route->hdr.dest_svc = APR_SVC_ADM;
- route->hdr.dest_domain = APR_DOMAIN_ADSP;
- route->hdr.dest_port = 0; /* Ignored */
- 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;
- }
- payload = ((u8 *) matrix_map + sizeof(*route));
matrix_map is a void *, so no need to cast it to u8 * to calculate the payload address.
yep.
- node = (struct adm_session_map_node_v5 *)payload;
payload is void *, so no need to typecast here. And for that matter, I'm not sure about the benefits of having this intermediate "payload" variable, just assign node directly.
- node->session_id = payload_map.session_id;
- node->num_copps = payload_map.num_copps;
- payload = (u8 *) node + sizeof(*node);
- copps_list = (uint16_t *) payload;
As with node above, drop the temporary variable and drop the type casts.
- for (i = 0; i < payload_map.num_copps; i++) {
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]);
Leaking matrix_map.
yep!
return -EINVAL;
}
copp_idx = payload_map.copp_idx[i];
copp = adm_find_copp(adm, port_idx, copp_idx);
if (IS_ERR_OR_NULL(copp))
Dito.
return -EINVAL;
copps_list[i] = copp->id;
- }
- adm->matrix_map_stat = -1;
- ret = apr_send_pkt(adm->apr, (uint32_t *) 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 = -EINVAL;
goto fail_cmd;
- } else if (adm->matrix_map_stat > 0) {
dev_err(dev, "DSP returned error[%s]\n",
adsp_err_get_err_str(adm->matrix_map_stat));
ret = adsp_err_get_lnx_err_code(adm->matrix_map_stat);
goto fail_cmd;
- }
+fail_cmd:
- kfree(matrix_map);
- return ret;
+} +EXPORT_SYMBOL_GPL(q6adm_matrix_map);
+static void adm_reset_copp(struct copp *c)
As far as I can see this will decommission the copp, so I don't think there is a point in updating any of this and then keep it around?
yes, if we free it as suggested above we can get rid of this totally.
+{
- c->id = RESET_COPP_ID;
- c->cnt = 0;
- c->topology = 0;
- c->mode = 0;
- c->stat = -1;
- c->rate = 0;
- c->channels = 0;
- c->bit_width = 0;
- c->app_type = 0;
+} +/**
- 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 apr_hdr close;
- struct copp *copp;
- int ret = 0, port_idx;
- int copp_id = RESET_COPP_ID;
- struct q6adm *adm = dev_get_drvdata(dev->parent);
- port_idx = port_id;
- if (port_idx < 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;
- copp->cnt--;
- if (!copp->cnt) {
Again, this needs some locking.
Yes, this needs some protection, i will revisit this part.
copp_id = copp->id;
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_idx << 16 | copp_idx;
close.opcode = ADM_CMD_DEVICE_CLOSE_V5;
Split this out into a separate helper function.
yep!
ret = apr_send_pkt(adm->apr, (uint32_t *) &close);
if (ret < 0) {
dev_err(dev, "ADM close failed %d\n", ret);
return -EINVAL;
}
ret = wait_event_timeout(copp->wait, copp->stat >= 0,
msecs_to_jiffies(TIMEOUT_MS));
if (!ret) {
dev_err(dev, "ADM cmd Route timedout for port 0x%x\n",
port_id);
return -EINVAL;
} else if (copp->stat > 0) {
dev_err(dev, "DSP returned error[%s]\n",
adsp_err_get_err_str(copp->stat));
return adsp_err_get_lnx_err_code(copp->stat);
}
adm_reset_copp(copp);
adm_free_copp(adm, copp, port_id);
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(q6adm_close);
[..]
+static struct apr_driver qcom_q6adm_driver = {
- .probe = q6adm_probe,
- .remove = q6adm_exit,
- .callback = adm_callback,
- .id_table = adm_id,
- .driver = {
.name = "qcom-q6adm",
},
Indentation.
yep.
Regards, Bjorn
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 | 250 ++++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6asm.h | 14 +++ 4 files changed, 270 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 a307880dc992..7ebdb879a8a3 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -52,10 +52,15 @@ 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" 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 052813ea7062..49dd3ccab27b 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,2 +1,3 @@ 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..9cc583afef4d --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -0,0 +1,250 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, 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/platform_device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include "q6asm.h" +#include "common.h" + +#define TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ +#define SYNC_IO_MODE 0x0001 +#define ASYNC_IO_MODE 0x0002 + +struct audio_client { + int session; + app_cb cb; + int cmd_state; + void *priv; + uint32_t io_mode; + uint64_t time_stamp; + struct apr_device *adev; + struct mutex cmd_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]; +}; + +static int q6asm_session_alloc(struct audio_client *ac, struct q6asm *a) +{ + int n = -EINVAL; + + mutex_lock(&a->session_lock); + for (n = 1; n <= MAX_SESSIONS; n++) { + if (!a->session[n]) { + a->session[n] = ac; + break; + } + } + mutex_unlock(&a->session_lock); + + return n; +} + +static bool q6asm_is_valid_audio_client(struct audio_client *ac) +{ + struct q6asm *a = dev_get_drvdata(ac->dev->parent); + int n; + + for (n = 1; n <= MAX_SESSIONS; n++) { + if (a->session[n] == ac) + return 1; + } + + return 0; +} + +static void q6asm_session_free(struct audio_client *ac) +{ + struct q6asm *a = dev_get_drvdata(ac->dev->parent); + + if (!a) + return; + + mutex_lock(&a->session_lock); + a->session[ac->session] = 0; + ac->session = 0; + ac->perf_mode = LEGACY_PCM_MODE; + mutex_unlock(&a->session_lock); +} + +/** + * q6asm_audio_client_free() - Freee allocated audio client + * + * @ac: audio client to free + */ +void q6asm_audio_client_free(struct audio_client *ac) +{ + q6asm_session_free(ac); + 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); + goto err; + } + + if (!a->session[session_id]) { + dev_err(a->dev, "session not active: %d\n", session_id); + goto err; + } + return a->session[session_id]; +err: + return NULL; +} + +static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data *data) +{ + struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + struct audio_client *ac = NULL; + uint32_t sid = 0; + uint32_t *payload; + + if (!data) { + dev_err(&adev->dev, "%s: Invalid CB\n", __func__); + return 0; + } + + payload = 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, + app_cb cb, void *priv) +{ + struct q6asm *a = dev_get_drvdata(dev->parent); + struct audio_client *ac; + int n; + + ac = kzalloc(sizeof(struct audio_client), GFP_KERNEL); + if (!ac) + return NULL; + + n = q6asm_session_alloc(ac, a); + if (n <= 0) { + dev_err(dev, "ASM Session alloc fail n=%d\n", n); + kfree(ac); + return NULL; + } + + ac->session = n; + ac->cb = cb; + ac->dev = dev; + ac->priv = priv; + ac->io_mode = 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->cmd_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); + + q6asm->pcmdev = platform_device_register_data(&adev->dev, + "q6asm_dai", -1, NULL, 0); + + return 0; +} + +static int q6asm_remove(struct apr_device *adev) +{ + struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + + platform_device_unregister(q6asm->pcmdev); + + return 0; +} + +static const struct apr_device_id q6asm_id[] = { + {"Q6ASM", APR_DOMAIN_ADSP, APR_SVC_ASM, APR_CLIENT_AUDIO}, + {} +}; + +static struct apr_driver qcom_q6asm_driver = { + .probe = q6asm_probe, + .remove = q6asm_remove, + .callback = q6asm_srvc_callback, + .id_table = q6asm_id, + .driver = { + .name = "qcom-q6asm", + }, +}; + +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..7a8a9039fd89 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6_ASM_H__ +#define __Q6_ASM_H__ + +#define MAX_SESSIONS 16 + +typedef void (*app_cb) (uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv); +struct audio_client; +struct audio_client *q6asm_audio_client_alloc(struct device *dev, + app_cb cb, void *priv); +void q6asm_audio_client_free(struct audio_client *ac); +int q6asm_get_session_id(struct audio_client *ac); +#endif /* __Q6_ASM_H__ */
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
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.
"...streams, each one setup as either playback or capture".
or "each" need to be capitalized.
ASM provides top control functions like Pause/flush/resume for playback and record. ASM can Create/destroy encoder,
lower case p and c
decoder and also provides POPP dynamic services.
Please describe what POPP is.
[..]
+struct audio_client {
- int session;
- app_cb cb;
- int cmd_state;
- void *priv;
- uint32_t io_mode;
- uint64_t time_stamp;
Unused.
- struct apr_device *adev;
- struct mutex cmd_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];
+};
+static int q6asm_session_alloc(struct audio_client *ac, struct q6asm *a)
Move the allocation of ac into this function, and return the newly allocated ac - that way the name of this function makes more sense.
+{
- int n = -EINVAL;
You're returning MAX_SESSIONS if no free sessions are found, but are checking for <= 0 in the caller.
- mutex_lock(&a->session_lock);
- for (n = 1; n <= MAX_SESSIONS; n++) {
Is there an external reason for session 0 not being considered?
if (!a->session[n]) {
a->session[n] = ac;
break;
}
- }
If you make session an idr this function would become idr_alloc(1, MAX_SESSIONS + 1).
- mutex_unlock(&a->session_lock);
- return n;
+}
+static bool q6asm_is_valid_audio_client(struct audio_client *ac) +{
- struct q6asm *a = dev_get_drvdata(ac->dev->parent);
- int n;
- for (n = 1; n <= MAX_SESSIONS; n++) {
if (a->session[n] == ac)
return 1;
"true"
- }
- return 0;
"false"
+}
+static void q6asm_session_free(struct audio_client *ac) +{
- struct q6asm *a = dev_get_drvdata(ac->dev->parent);
- if (!a)
return;
- mutex_lock(&a->session_lock);
- a->session[ac->session] = 0;
- ac->session = 0;
- ac->perf_mode = LEGACY_PCM_MODE;
No need to update ac->*, as you kfree ac as soon as you return from here.
- mutex_unlock(&a->session_lock);
+}
+/**
- q6asm_audio_client_free() - Freee allocated audio client
- @ac: audio client to free
- */
+void q6asm_audio_client_free(struct audio_client *ac) +{
- q6asm_session_free(ac);
Inline q6asm_session_free() here.
- 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);
goto err;
Just return NULL here instead.
- }
- if (!a->session[session_id]) {
dev_err(a->dev, "session not active: %d\n", session_id);
goto err;
Dito
- }
But this is another place where an idr would be preferable, as both these cases would be covered with a call to idr_find()
- return a->session[session_id];
+err:
- return NULL;
+}
+static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data *data) +{
- struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
- struct audio_client *ac = NULL;
- uint32_t sid = 0;
This is 4 bits, so just use int.
- uint32_t *payload;
payload is unused.
- if (!data) {
dev_err(&adev->dev, "%s: Invalid CB\n", __func__);
return 0;
- }
Again, define the apr to never invoke the callback with data = NULL
- payload = 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,
app_cb cb, void *priv)
+{
- struct q6asm *a = dev_get_drvdata(dev->parent);
- struct audio_client *ac;
- int n;
- ac = kzalloc(sizeof(struct audio_client), GFP_KERNEL);
sizeof(*ac)
- if (!ac)
return NULL;
- n = q6asm_session_alloc(ac, a);
As stated above, moving the kzalloc into q6asm_session_alloc() would clean the code up here, as you only need to deal with one possible error case here.
- if (n <= 0) {
dev_err(dev, "ASM Session alloc fail n=%d\n", n);
kfree(ac);
return NULL;
Per the kerneldoc I expect an ERR_PTR(n) here.
- }
- ac->session = n;
- ac->cb = cb;
- ac->dev = dev;
- ac->priv = priv;
- ac->io_mode = 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->cmd_lock);
- ac->cmd_state = 0;
- return ac;
+} +EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
Extra newline.
+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);
- q6asm->pcmdev = platform_device_register_data(&adev->dev,
"q6asm_dai", -1, NULL, 0);
- return 0;
+}
+static int q6asm_remove(struct apr_device *adev) +{
- struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
- platform_device_unregister(q6asm->pcmdev);
- return 0;
+}
+static const struct apr_device_id q6asm_id[] = {
- {"Q6ASM", APR_DOMAIN_ADSP, APR_SVC_ASM, APR_CLIENT_AUDIO},
- {}
+};
+static struct apr_driver qcom_q6asm_driver = {
- .probe = q6asm_probe,
- .remove = q6asm_remove,
- .callback = q6asm_srvc_callback,
- .id_table = q6asm_id,
- .driver = {
.name = "qcom-q6asm",
},
Indentation
+};
+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..7a8a9039fd89 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6_ASM_H__ +#define __Q6_ASM_H__
+#define MAX_SESSIONS 16
+typedef void (*app_cb) (uint32_t opcode, uint32_t token,
uint32_t *payload, void *priv);
This name of a type is too generic.
And make payload void *, unless the payload really really is an unstructured uint32_t array.
Regards, Bjorn
On 02/01/18 04:43, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
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.
"...streams, each one setup as either playback or capture".
or "each" need to be capitalized.
ASM provides top control functions like Pause/flush/resume for playback and record. ASM can Create/destroy encoder,
lower case p and c
decoder and also provides POPP dynamic services.
Please describe what POPP is.
Yep, will fix the commit log as suggested.
[..]
+struct audio_client {
- int session;
- app_cb cb;
- int cmd_state;
- void *priv;
- uint32_t io_mode;
- uint64_t time_stamp;
Unused.
will remove this in next version.
- struct apr_device *adev;
- struct mutex cmd_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];
+};
+static int q6asm_session_alloc(struct audio_client *ac, struct q6asm *a)
Move the allocation of ac into this function, and return the newly allocated ac - that way the name of this function makes more sense.
will try that, it should cleanup some code.
+{
- int n = -EINVAL;
You're returning MAX_SESSIONS if no free sessions are found, but are checking for <= 0 in the caller.
I will make sure that its checked correctly and i will also update the kernel doc to reflect this.
- mutex_lock(&a->session_lock);
- for (n = 1; n <= MAX_SESSIONS; n++) {
Is there an external reason for session 0 not being considered?
Yes, session 0 is reserved.
if (!a->session[n]) {
a->session[n] = ac;
break;
}
- }
If you make session an idr this function would become idr_alloc(1, MAX_SESSIONS + 1).
will try idr and see how it looks.
- mutex_unlock(&a->session_lock);
- return n;
+}
+static bool q6asm_is_valid_audio_client(struct audio_client *ac) +{
- struct q6asm *a = dev_get_drvdata(ac->dev->parent);
- int n;
- for (n = 1; n <= MAX_SESSIONS; n++) {
if (a->session[n] == ac)
return 1;
"true"
thanks, will fix these.
- }
- return 0;
"false"
+}
+static void q6asm_session_free(struct audio_client *ac) +{
- struct q6asm *a = dev_get_drvdata(ac->dev->parent);
- if (!a)
return;
- mutex_lock(&a->session_lock);
- a->session[ac->session] = 0;
- ac->session = 0;
- ac->perf_mode = LEGACY_PCM_MODE;
No need to update ac->*, as you kfree ac as soon as you return from here.
yep.
- mutex_unlock(&a->session_lock);
+}
+/**
- q6asm_audio_client_free() - Freee allocated audio client
- @ac: audio client to free
- */
+void q6asm_audio_client_free(struct audio_client *ac) +{
- q6asm_session_free(ac);
Inline q6asm_session_free() here.
makes sense here.
- 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);
goto err;
Just return NULL here instead.
yep.
- }
- if (!a->session[session_id]) {
dev_err(a->dev, "session not active: %d\n", session_id);
goto err;
Dito
- }
But this is another place where an idr would be preferable, as both these cases would be covered with a call to idr_find()
- return a->session[session_id];
+err:
- return NULL;
+}
+static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data *data) +{
- struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
- struct audio_client *ac = NULL;
- uint32_t sid = 0;
This is 4 bits, so just use int.
makes sense.
- uint32_t *payload;
payload is unused.
will remove this in next version.
- if (!data) {
dev_err(&adev->dev, "%s: Invalid CB\n", __func__);
return 0;
- }
Again, define the apr to never invoke the callback with data = NULL
yep.
- payload = 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_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,
app_cb cb, void *priv)
+{
- struct q6asm *a = dev_get_drvdata(dev->parent);
- struct audio_client *ac;
- int n;
- ac = kzalloc(sizeof(struct audio_client), GFP_KERNEL);
sizeof(*ac)
Yep.
- if (!ac)
return NULL;
- n = q6asm_session_alloc(ac, a);
As stated above, moving the kzalloc into q6asm_session_alloc() would clean the code up here, as you only need to deal with one possible error case here.
Will give it a go and see.
- if (n <= 0) {
dev_err(dev, "ASM Session alloc fail n=%d\n", n);
kfree(ac);
return NULL;
Per the kerneldoc I expect an ERR_PTR(n) here.
yep.
- }
- ac->session = n;
- ac->cb = cb;
- ac->dev = dev;
- ac->priv = priv;
- ac->io_mode = 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->cmd_lock);
- ac->cmd_state = 0;
- return ac;
+} +EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
Extra newline.
yep, will fix it.
[...]
+static struct apr_driver qcom_q6asm_driver = {
- .probe = q6asm_probe,
- .remove = q6asm_remove,
- .callback = q6asm_srvc_callback,
- .id_table = q6asm_id,
- .driver = {
.name = "qcom-q6asm",
},
Indentation
yep.
+};
+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..7a8a9039fd89 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6_ASM_H__ +#define __Q6_ASM_H__
+#define MAX_SESSIONS 16
+typedef void (*app_cb) (uint32_t opcode, uint32_t token,
uint32_t *payload, void *priv);
This name of a type is too generic.
And make payload void *, unless the payload really really is an unstructured uint32_t array.
will do that as suggested.
Regards, Bjorn
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 | 343 ++++++++++++++++++++++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6asm.h | 5 + 2 files changed, 347 insertions(+), 1 deletion(-)
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 9cc583afef4d..4be92441f524 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -14,9 +14,46 @@ #include "q6asm.h" #include "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 TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ #define SYNC_IO_MODE 0x0001 #define ASYNC_IO_MODE 0x0002 +#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 { + dma_addr_t phys; + uint32_t used; + uint32_t size; /* size of buffer */ +}; + +struct audio_port_data { + struct audio_buffer *buf; + uint32_t max_buf_cnt; + uint32_t dsp_buf; + uint32_t mem_map_handle; +};
struct audio_client { int session; @@ -27,6 +64,8 @@ struct audio_client { uint64_t time_stamp; struct apr_device *adev; struct mutex cmd_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; @@ -86,6 +125,260 @@ static void q6asm_session_free(struct audio_client *ac) mutex_unlock(&a->session_lock); }
+static inline void q6asm_add_mmaphdr(struct audio_client *ac, + struct apr_hdr *hdr, u32 pkt_size, + bool cmd_flg, u32 token) +{ + hdr->hdr_field = APR_SEQ_CMD_HDR_FIELD; + hdr->src_port = 0; + hdr->dest_port = 0; + hdr->pkt_size = pkt_size; + if (cmd_flg) + hdr->token = token; +} + +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_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->parent); + int rc; + + if (!a) + return -ENODEV; + + q6asm_add_mmaphdr(ac, &mem_unmap.hdr, sizeof(mem_unmap), true, + ((ac->session << 8) | dir)); + a->mem_state = -1; + + mem_unmap.hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS; + mem_unmap.mem_map_handle = ac->port[dir].mem_map_handle; + + if (mem_unmap.mem_map_handle == 0) { + dev_err(ac->dev, "invalid mem handle\n"); + return -EINVAL; + } + + rc = apr_send_pkt(a->adev, (uint32_t *) &mem_unmap); + if (rc < 0) + return rc; + + rc = wait_event_timeout(a->mem_wait, (a->mem_state >= 0), + 5 * HZ); + if (!rc) { + dev_err(ac->dev, "CMD timeout for memory_unmap 0x%x\n", + mem_unmap.mem_map_handle); + return -ETIMEDOUT; + } else if (a->mem_state > 0) { + return adsp_err_get_lnx_err_code(a->mem_state); + } + 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->cmd_lock); + port = &ac->port[dir]; + if (!port->buf) { + mutex_unlock(&ac->cmd_lock); + return 0; + } + cnt = port->max_buf_cnt - 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); + mutex_unlock(&ac->cmd_lock); + return rc; + } + } + + port->max_buf_cnt = 0; + kfree(port->buf); + port->buf = NULL; + mutex_unlock(&ac->cmd_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(q6asm_unmap_memory_regions); + +static int __q6asm_memory_map_regions(struct audio_client *ac, int dir, + uint32_t period_sz, uint32_t periods, + bool is_contiguous) +{ + struct avs_cmd_shared_mem_map_regions *mmap_regions = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + struct q6asm *a = dev_get_drvdata(ac->dev->parent); + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + uint32_t num_regions; + uint32_t buf_sz; + + if (!a) + return -ENODEV; + num_regions = is_contiguous ? 1 : periods; + buf_sz = is_contiguous ? (period_sz * periods) : period_sz; + buf_sz = PAGE_ALIGN(buf_sz); + + cmd_size = sizeof(*mmap_regions) + (sizeof(*mregions) * num_regions); + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) + return -ENOMEM; + + mmap_regions = (struct avs_cmd_shared_mem_map_regions *)mmap_region_cmd; + q6asm_add_mmaphdr(ac, &mmap_regions->hdr, cmd_size, true, + ((ac->session << 8) | dir)); + a->mem_state = -1; + + mmap_regions->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS; + mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL; + mmap_regions->num_regions = num_regions; + mmap_regions->property_flag = 0x00; + + payload = ((u8 *) mmap_region_cmd + + sizeof(struct avs_cmd_shared_mem_map_regions)); + + mregions = (struct avs_shared_map_region_payload *)payload; + + ac->port[dir].mem_map_handle = 0; + 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 = apr_send_pkt(a->adev, (uint32_t *) mmap_region_cmd); + if (rc < 0) + goto fail_cmd; + + rc = wait_event_timeout(a->mem_wait, (a->mem_state >= 0), + 5 * HZ); + if (!rc) { + dev_err(ac->dev, "timeout. waited for memory_map\n"); + rc = -ETIMEDOUT; + goto fail_cmd; + } + + if (a->mem_state > 0) { + rc = adsp_err_get_lnx_err_code(a->mem_state); + goto fail_cmd; + } + rc = 0; +fail_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, + dma_addr_t phys, + unsigned int period_sz, unsigned int periods) +{ + struct audio_buffer *buf; + int cnt; + int rc; + + if (ac->port[dir].buf) { + dev_err(ac->dev, "Buffer already allocated\n"); + return 0; + } + + mutex_lock(&ac->cmd_lock); + + buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_KERNEL); + if (!buf) { + mutex_unlock(&ac->cmd_lock); + return -ENOMEM; + } + + + ac->port[dir].buf = buf; + + buf[0].phys = phys; + buf[0].used = dir ^ 1; + buf[0].size = period_sz; + cnt = 1; + while (cnt < periods) { + if (period_sz > 0) { + buf[cnt].phys = buf[0].phys + (cnt * period_sz); + buf[cnt].used = dir ^ 1; + buf[cnt].size = period_sz; + } + cnt++; + } + + ac->port[dir].max_buf_cnt = periods; + mutex_unlock(&ac->cmd_lock); + + rc = __q6asm_memory_map_regions(ac, dir, period_sz, periods, 1); + if (rc < 0) { + dev_err(ac->dev, + "CMD Memory_map_regions failed %d for size %d\n", rc, + period_sz); + + + ac->port[dir].max_buf_cnt = 0; + kfree(buf); + ac->port[dir].buf = NULL; + + return rc; + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6asm_map_memory_regions); + /** * q6asm_audio_client_free() - Freee allocated audio client * @@ -117,8 +410,10 @@ static struct audio_client *q6asm_get_audio_client(struct q6asm *a,
static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data *data) { - struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + 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; uint32_t *payload;
@@ -135,6 +430,52 @@ static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data * return 0; }
+ a = dev_get_drvdata(ac->dev->parent); + if (data->opcode == APR_BASIC_RSP_RESULT) { + switch (payload[0]) { + case ASM_CMD_SHARED_MEM_MAP_REGIONS: + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + if (payload[1] != 0) { + dev_err(ac->dev, + "cmd = 0x%x returned error = 0x%x sid:%d\n", + payload[0], payload[1], sid); + a->mem_state = payload[1]; + } else { + a->mem_state = 0; + } + + wake_up(&a->mem_wait); + break; + default: + dev_err(&adev->dev, "command[0x%x] not expecting rsp\n", + payload[0]); + break; + } + return 0; + } + + dir = (data->token & 0x0F); + port = &ac->port[dir]; + + switch (data->opcode) { + case ASM_CMDRSP_SHARED_MEM_MAP_REGIONS:{ + a->mem_state = 0; + ac->port[dir].mem_map_handle = payload[0]; + 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", + payload[0], payload[1]); + break; + } if (ac->cb) ac->cb(data->opcode, data->token, data->payload, ac->priv); return 0; diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h index 7a8a9039fd89..e1409c368600 100644 --- a/sound/soc/qcom/qdsp6/q6asm.h +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -11,4 +11,9 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev, app_cb cb, void *priv); 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, + dma_addr_t phys, + unsigned int bufsz, unsigned int bufcnt); +int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac); #endif /* __Q6_ASM_H__ */
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
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 | 343 ++++++++++++++++++++++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6asm.h | 5 + 2 files changed, 347 insertions(+), 1 deletion(-)
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 9cc583afef4d..4be92441f524 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -14,9 +14,46 @@ #include "q6asm.h" #include "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 TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ #define SYNC_IO_MODE 0x0001 #define ASYNC_IO_MODE 0x0002 +#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 {
- dma_addr_t phys;
- uint32_t used;
- uint32_t size; /* size of buffer */
+};
+struct audio_port_data {
- struct audio_buffer *buf;
- uint32_t max_buf_cnt;
This seems to denote the number of audio_buffers in the buf array, so I'm not sure about the meaning of "max_".
- uint32_t dsp_buf;
- uint32_t mem_map_handle;
+};
struct audio_client { int session; @@ -27,6 +64,8 @@ struct audio_client { uint64_t time_stamp; struct apr_device *adev; struct mutex cmd_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;
@@ -86,6 +125,260 @@ static void q6asm_session_free(struct audio_client *ac) mutex_unlock(&a->session_lock); }
+static inline void q6asm_add_mmaphdr(struct audio_client *ac,
struct apr_hdr *hdr, u32 pkt_size,
bool cmd_flg, u32 token)
cmd_flg is true in both callers, so this function ends up simply assigning hdr_field, pkt_size and token. Inlining these assignments would make for cleaner call sites as well.
+{
- hdr->hdr_field = APR_SEQ_CMD_HDR_FIELD;
- hdr->src_port = 0;
- hdr->dest_port = 0;
- hdr->pkt_size = pkt_size;
- if (cmd_flg)
hdr->token = token;
+}
+static inline void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr,
This is unused.
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_memory_unmap(struct audio_client *ac,
phys_addr_t buf_add, int dir)
+{
- struct avs_cmd_shared_mem_unmap_regions mem_unmap;
If you name this "cmd" you will declutter below code a bit.
- struct q6asm *a = dev_get_drvdata(ac->dev->parent);
- int rc;
- if (!a)
return -ENODEV;
Does this NULL check add any real value?
- q6asm_add_mmaphdr(ac, &mem_unmap.hdr, sizeof(mem_unmap), true,
((ac->session << 8) | dir));
- a->mem_state = -1;
- mem_unmap.hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS;
- mem_unmap.mem_map_handle = ac->port[dir].mem_map_handle;
- if (mem_unmap.mem_map_handle == 0) {
Start the function by checking for !ac->port[dir].mem_map_handle
dev_err(ac->dev, "invalid mem handle\n");
return -EINVAL;
- }
- rc = apr_send_pkt(a->adev, (uint32_t *) &mem_unmap);
- if (rc < 0)
return rc;
- rc = wait_event_timeout(a->mem_wait, (a->mem_state >= 0),
5 * HZ);
- if (!rc) {
dev_err(ac->dev, "CMD timeout for memory_unmap 0x%x\n",
mem_unmap.mem_map_handle);
return -ETIMEDOUT;
- } else if (a->mem_state > 0) {
return adsp_err_get_lnx_err_code(a->mem_state);
- }
- ac->port[dir].mem_map_handle = 0;
Does all errors indicate that the region is left mapped?
- 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->cmd_lock);
- port = &ac->port[dir];
- if (!port->buf) {
mutex_unlock(&ac->cmd_lock);
return 0;
Put a label right before the mutex_unlock below and return rc instead of 0, then you can replace these two lines with "goto unlock"
- }
- cnt = port->max_buf_cnt - 1;
What if we mapped 1 period? Why shouldn't we enter the unmap path?
- 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);
Most of the code paths through __q6asm_memory_unmap() will print an error, make this consistent and print the warning once.
mutex_unlock(&ac->cmd_lock);
return rc;
Same here.
}
- }
- port->max_buf_cnt = 0;
- kfree(port->buf);
- port->buf = NULL;
- mutex_unlock(&ac->cmd_lock);
I think however that if you rearrange this function somewhat you can start off by doing:
mutex_lock(&ac->cmd_lock); port = &ac->port[dir];
bufs = port->buf; cnt = port->max_buf_cnt;
port->max_buf_cnt = 0; port->buf = NULL; mutex_unlock(&ac->cmd_lock);
And then you can do the rest without locks.
- return 0;
+} +EXPORT_SYMBOL_GPL(q6asm_unmap_memory_regions);
+static int __q6asm_memory_map_regions(struct audio_client *ac, int dir,
uint32_t period_sz, uint32_t periods,
period_sz is typical size_t material.
bool is_contiguous)
+{
- struct avs_cmd_shared_mem_map_regions *mmap_regions = NULL;
Calling this "cmd" would declutter the function.
- struct avs_shared_map_region_payload *mregions = NULL;
- struct q6asm *a = dev_get_drvdata(ac->dev->parent);
- struct audio_port_data *port = NULL;
- struct audio_buffer *ab = NULL;
- void *mmap_region_cmd = NULL;
No need to initialize this.
Also, this really is a avs_cmd_shared_mem_map_regions with some extra data at the end, not a void *.
- void *payload = NULL;
- int rc = 0;
- int i = 0;
- int cmd_size = 0;
Most of these can be left uninitialized.
- uint32_t num_regions;
- uint32_t buf_sz;
- if (!a)
return -ENODEV;
- num_regions = is_contiguous ? 1 : periods;
- buf_sz = is_contiguous ? (period_sz * periods) : period_sz;
- buf_sz = PAGE_ALIGN(buf_sz);
- cmd_size = sizeof(*mmap_regions) + (sizeof(*mregions) * num_regions);
- mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL);
- if (!mmap_region_cmd)
return -ENOMEM;
- mmap_regions = (struct avs_cmd_shared_mem_map_regions *)mmap_region_cmd;
- q6asm_add_mmaphdr(ac, &mmap_regions->hdr, cmd_size, true,
((ac->session << 8) | dir));
- a->mem_state = -1;
- mmap_regions->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS;
- mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL;
- mmap_regions->num_regions = num_regions;
- mmap_regions->property_flag = 0x00;
- payload = ((u8 *) mmap_region_cmd +
sizeof(struct avs_cmd_shared_mem_map_regions));
mmap_region_cmd is void *, so no need to type cast.
- mregions = (struct avs_shared_map_region_payload *)payload;
Payload is void *, so no need to type cast. But there's also no benefit of having "payload", as this line can be written as:
mregions = mmap_region_cmd + sizeof(*mmap_regions);
But adding a flexible array member to the avs_cmd_shared_mem_map_regions struct would make things even clearer, in particular you would be able to read the struct definition and see that there's a payload following the command.
- ac->port[dir].mem_map_handle = 0;
Isn't this already 0?
- 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;
Here you're dereferencing port->buf without holding cmd_lock.
++mregions;
- }
- rc = apr_send_pkt(a->adev, (uint32_t *) mmap_region_cmd);
- if (rc < 0)
goto fail_cmd;
- rc = wait_event_timeout(a->mem_wait, (a->mem_state >= 0),
5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout. waited for memory_map\n");
rc = -ETIMEDOUT;
goto fail_cmd;
- }
- if (a->mem_state > 0) {
rc = adsp_err_get_lnx_err_code(a->mem_state);
goto fail_cmd;
- }
- rc = 0;
+fail_cmd:
- kfree(mmap_region_cmd);
- return rc;
+}
+/**
- q6asm_map_memory_regions() - map memory regions in the dsp.
- @dir: direction of audio stream
This sounds boolean, perhaps worth mentioning here if true means rx or tx.
And it's idiomatic to have such a parameter later in the list, it would probably be more natural to read the call sight if the order was:
q6asm_map_memory_regions(ac, phys, periods, size, true);
- @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,
dma_addr_t phys,
unsigned int period_sz, unsigned int periods)
period_sz could with benefit be of type size_t.
+{
- struct audio_buffer *buf;
- int cnt;
- int rc;
- if (ac->port[dir].buf) {
dev_err(ac->dev, "Buffer already allocated\n");
return 0;
- }
- mutex_lock(&ac->cmd_lock);
I believe this lock should cover above check.
- buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_KERNEL);
Loose a few of those parenthesis and use *buf in the sizeof.
- if (!buf) {
mutex_unlock(&ac->cmd_lock);
return -ENOMEM;
- }
- ac->port[dir].buf = buf;
- buf[0].phys = phys;
- buf[0].used = dir ^ 1;
Why would this be called "used", and it's probably cleaner to just use !!dir.
- buf[0].size = period_sz;
- cnt = 1;
- while (cnt < periods) {
cnt goes from 1 to periods and is incremented 1 each step, this would be more succinct as a for loop.
if (period_sz > 0) {
buf[cnt].phys = buf[0].phys + (cnt * period_sz);
buf[cnt].used = dir ^ 1;
buf[cnt].size = period_sz;
}
cnt++;
- }
- ac->port[dir].max_buf_cnt = periods;
- mutex_unlock(&ac->cmd_lock);
The only possible purpose of taking cmd_lock here is to protect ac->port[dir].buf, but
- rc = __q6asm_memory_map_regions(ac, dir, period_sz, periods, 1);
The last parameter should be "true".
- if (rc < 0) {
dev_err(ac->dev,
"CMD Memory_map_regions failed %d for size %d\n", rc,
period_sz);
ac->port[dir].max_buf_cnt = 0;
kfree(buf);
ac->port[dir].buf = NULL;
These operations are done without holding cmd_lock.
return rc;
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(q6asm_map_memory_regions);
/**
- q6asm_audio_client_free() - Freee allocated audio client
@@ -117,8 +410,10 @@ static struct audio_client *q6asm_get_audio_client(struct q6asm *a,
static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data *data) {
- struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
- 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; uint32_t *payload;
@@ -135,6 +430,52 @@ static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data * return 0; }
- a = dev_get_drvdata(ac->dev->parent);
- if (data->opcode == APR_BASIC_RSP_RESULT) {
This is a case in below switch statement.
switch (payload[0]) {
case ASM_CMD_SHARED_MEM_MAP_REGIONS:
case ASM_CMD_SHARED_MEM_UNMAP_REGIONS:
if (payload[1] != 0) {
dev_err(ac->dev,
"cmd = 0x%x returned error = 0x%x sid:%d\n",
payload[0], payload[1], sid);
a->mem_state = payload[1];
} else {
a->mem_state = 0;
Just assign a->mem_state = payload[1] outside the conditional, as it will be the same value.
}
wake_up(&a->mem_wait);
break;
default:
dev_err(&adev->dev, "command[0x%x] not expecting rsp\n",
payload[0]);
break;
}
return 0;
- }
Regards, Bjorn
Thanks for your review comments.
On 02/01/18 05:48, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
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 | 343 ++++++++++++++++++++++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6asm.h | 5 + 2 files changed, 347 insertions(+), 1 deletion(-)
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 9cc583afef4d..4be92441f524 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c
[...]
+};
+struct audio_port_data {
- struct audio_buffer *buf;
- uint32_t max_buf_cnt;
This seems to denote the number of audio_buffers in the buf array, so I'm not sure about the meaning of "max_
I can rename it to buf_cnt if it makes it more readable.
- uint32_t dsp_buf;
- uint32_t mem_map_handle;
+};
struct audio_client { int session; @@ -27,6 +64,8 @@ struct audio_client { uint64_t time_stamp; struct apr_device *adev; struct mutex cmd_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;
@@ -86,6 +125,260 @@ static void q6asm_session_free(struct audio_client *ac) mutex_unlock(&a->session_lock); }
+static inline void q6asm_add_mmaphdr(struct audio_client *ac,
struct apr_hdr *hdr, u32 pkt_size,
bool cmd_flg, u32 token)
cmd_flg is true in both callers, so this function ends up simply assigning hdr_field, pkt_size and token. Inlining these assignments would make for cleaner call sites as well.
yep, will try that.
+{
- hdr->hdr_field = APR_SEQ_CMD_HDR_FIELD;
- hdr->src_port = 0;
- hdr->dest_port = 0;
- hdr->pkt_size = pkt_size;
- if (cmd_flg)
hdr->token = token;
+}
+static inline void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr,
This is unused.
This should actually go into the next patch.
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_memory_unmap(struct audio_client *ac,
phys_addr_t buf_add, int dir)
+{
- struct avs_cmd_shared_mem_unmap_regions mem_unmap;
If you name this "cmd" you will declutter below code a bit.
yep!
- struct q6asm *a = dev_get_drvdata(ac->dev->parent);
- int rc;
- if (!a)
return -ENODEV;
Does this NULL check add any real value?
- q6asm_add_mmaphdr(ac, &mem_unmap.hdr, sizeof(mem_unmap), true,
((ac->session << 8) | dir));
- a->mem_state = -1;
- mem_unmap.hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS;
- mem_unmap.mem_map_handle = ac->port[dir].mem_map_handle;
- if (mem_unmap.mem_map_handle == 0) {
Start the function by checking for !ac->port[dir].mem_map_handle
yes!
dev_err(ac->dev, "invalid mem handle\n");
return -EINVAL;
- }
- rc = apr_send_pkt(a->adev, (uint32_t *) &mem_unmap);
- if (rc < 0)
return rc;
- rc = wait_event_timeout(a->mem_wait, (a->mem_state >= 0),
5 * HZ);
- if (!rc) {
dev_err(ac->dev, "CMD timeout for memory_unmap 0x%x\n",
mem_unmap.mem_map_handle);
return -ETIMEDOUT;
- } else if (a->mem_state > 0) {
return adsp_err_get_lnx_err_code(a->mem_state);
- }
- ac->port[dir].mem_map_handle = 0;
Does all errors indicate that the region is left mapped?
No, caller should check the return value of this to verify that its mapped or not.
- 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->cmd_lock);
- port = &ac->port[dir];
- if (!port->buf) {
mutex_unlock(&ac->cmd_lock);
return 0;
Put a label right before the mutex_unlock below and return rc instead of 0, then you can replace these two lines with "goto unlock"
- }
- cnt = port->max_buf_cnt - 1;
What if we mapped 1 period? Why shouldn't we enter the unmap path?
It would enter into unmap path, as cnt would be 0 for 1 period.
- 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);
Most of the code paths through __q6asm_memory_unmap() will print an error, make this consistent and print the warning once.
okay.
mutex_unlock(&ac->cmd_lock);
return rc;
Same here.
}
- }
- port->max_buf_cnt = 0;
- kfree(port->buf);
- port->buf = NULL;
- mutex_unlock(&ac->cmd_lock);
I think however that if you rearrange this function somewhat you can start off by doing:
mutex_lock(&ac->cmd_lock); port = &ac->port[dir];
bufs = port->buf; cnt = port->max_buf_cnt;
port->max_buf_cnt = 0; port->buf = NULL; mutex_unlock(&ac->cmd_lock);
And then you can do the rest without locks.
will give that a go.
- return 0;
+} +EXPORT_SYMBOL_GPL(q6asm_unmap_memory_regions);
+static int __q6asm_memory_map_regions(struct audio_client *ac, int dir,
uint32_t period_sz, uint32_t periods,
period_sz is typical size_t material.
yep.
bool is_contiguous)
+{
- struct avs_cmd_shared_mem_map_regions *mmap_regions = NULL;
Calling this "cmd" would declutter the function.
- struct avs_shared_map_region_payload *mregions = NULL;
- struct q6asm *a = dev_get_drvdata(ac->dev->parent);
- struct audio_port_data *port = NULL;
- struct audio_buffer *ab = NULL;
- void *mmap_region_cmd = NULL;
No need to initialize this.
yes, I agree.
Also, this really is a avs_cmd_shared_mem_map_regions with some extra data at the end, not a void *.
- void *payload = NULL;
- int rc = 0;
- int i = 0;
- int cmd_size = 0;
Most of these can be left uninitialized.
- uint32_t num_regions;
- uint32_t buf_sz;
- if (!a)
return -ENODEV;
- num_regions = is_contiguous ? 1 : periods;
- buf_sz = is_contiguous ? (period_sz * periods) : period_sz;
- buf_sz = PAGE_ALIGN(buf_sz);
- cmd_size = sizeof(*mmap_regions) + (sizeof(*mregions) * num_regions);
- mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL);
- if (!mmap_region_cmd)
return -ENOMEM;
- mmap_regions = (struct avs_cmd_shared_mem_map_regions *)mmap_region_cmd;
- q6asm_add_mmaphdr(ac, &mmap_regions->hdr, cmd_size, true,
((ac->session << 8) | dir));
- a->mem_state = -1;
- mmap_regions->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS;
- mmap_regions->mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL;
- mmap_regions->num_regions = num_regions;
- mmap_regions->property_flag = 0x00;
- payload = ((u8 *) mmap_region_cmd +
sizeof(struct avs_cmd_shared_mem_map_regions));
mmap_region_cmd is void *, so no need to type cast.
yep.
- mregions = (struct avs_shared_map_region_payload *)payload;
Payload is void *, so no need to type cast. But there's also no benefit of having "payload", as this line can be written as:
mregions = mmap_region_cmd + sizeof(*mmap_regions);
But adding a flexible array member to the avs_cmd_shared_mem_map_regions struct would make things even clearer, in particular you would be able to read the struct definition and see that there's a payload following the command.
- ac->port[dir].mem_map_handle = 0;
Isn't this already 0?
- 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;
Here you're dereferencing port->buf without holding cmd_lock.
yep, will fix that in next version.
++mregions;
- }
- rc = apr_send_pkt(a->adev, (uint32_t *) mmap_region_cmd);
- if (rc < 0)
goto fail_cmd;
- rc = wait_event_timeout(a->mem_wait, (a->mem_state >= 0),
5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout. waited for memory_map\n");
rc = -ETIMEDOUT;
goto fail_cmd;
- }
- if (a->mem_state > 0) {
rc = adsp_err_get_lnx_err_code(a->mem_state);
goto fail_cmd;
- }
- rc = 0;
+fail_cmd:
- kfree(mmap_region_cmd);
- return rc;
+}
+/**
- q6asm_map_memory_regions() - map memory regions in the dsp.
- @dir: direction of audio stream
This sounds boolean, perhaps worth mentioning here if true means rx or tx.
I will add a note in doc about this.
And it's idiomatic to have such a parameter later in the list, it would probably be more natural to read the call sight if the order was:
q6asm_map_memory_regions(ac, phys, periods, size, true);
- @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,
dma_addr_t phys,
unsigned int period_sz, unsigned int periods)
period_sz could with benefit be of type size_t.
yep.
+{
- struct audio_buffer *buf;
- int cnt;
- int rc;
- if (ac->port[dir].buf) {
dev_err(ac->dev, "Buffer already allocated\n");
return 0;
- }
- mutex_lock(&ac->cmd_lock);
I believe this lock should cover above check.
yep.
- buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_KERNEL);
Loose a few of those parenthesis and use *buf in the sizeof.
yes
- if (!buf) {
mutex_unlock(&ac->cmd_lock);
return -ENOMEM;
- }
- ac->port[dir].buf = buf;
- buf[0].phys = phys;
- buf[0].used = dir ^ 1;
Why would this be called "used", and it's probably cleaner to just use !!dir.
We can get rid of this, it looks like leftover from old code.
- buf[0].size = period_sz;
- cnt = 1;
- while (cnt < periods) {
cnt goes from 1 to periods and is incremented 1 each step, this would be more succinct as a for loop.
yep!
if (period_sz > 0) {
buf[cnt].phys = buf[0].phys + (cnt * period_sz);
buf[cnt].used = dir ^ 1;
buf[cnt].size = period_sz;
}
cnt++;
- }
- ac->port[dir].max_buf_cnt = periods;
- mutex_unlock(&ac->cmd_lock);
The only possible purpose of taking cmd_lock here is to protect ac->port[dir].buf, but
- rc = __q6asm_memory_map_regions(ac, dir, period_sz, periods, 1);
The last parameter should be "true".
yes.
- if (rc < 0) {
dev_err(ac->dev,
"CMD Memory_map_regions failed %d for size %d\n", rc,
period_sz);
ac->port[dir].max_buf_cnt = 0;
kfree(buf);
ac->port[dir].buf = NULL;
These operations are done without holding cmd_lock.
I will revisit such instances where the buf is not protected.
return rc;
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(q6asm_map_memory_regions);
- /**
- q6asm_audio_client_free() - Freee allocated audio client
@@ -117,8 +410,10 @@ static struct audio_client *q6asm_get_audio_client(struct q6asm *a,
static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data *data) {
- struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
- 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; uint32_t *payload;
@@ -135,6 +430,52 @@ static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data * return 0; }
- a = dev_get_drvdata(ac->dev->parent);
- if (data->opcode == APR_BASIC_RSP_RESULT) {
This is a case in below switch statement.
sure.
switch (payload[0]) {
case ASM_CMD_SHARED_MEM_MAP_REGIONS:
case ASM_CMD_SHARED_MEM_UNMAP_REGIONS:
if (payload[1] != 0) {
dev_err(ac->dev,
"cmd = 0x%x returned error = 0x%x sid:%d\n",
payload[0], payload[1], sid);
a->mem_state = payload[1];
} else {
a->mem_state = 0;
Just assign a->mem_state = payload[1] outside the conditional, as it will be the same value.
I agree, will fix such instances.
}
wake_up(&a->mem_wait);
break;
default:
dev_err(&adev->dev, "command[0x%x] not expecting rsp\n",
payload[0]);
break;
}
return 0;
- }
Regards, Bjorn
On Wed 03 Jan 08:26 PST 2018, Srinivas Kandagatla wrote:
Thanks for your review comments.
On 02/01/18 05:48, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
[..]
+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->cmd_lock);
- port = &ac->port[dir];
- if (!port->buf) {
mutex_unlock(&ac->cmd_lock);
return 0;
Put a label right before the mutex_unlock below and return rc instead of 0, then you can replace these two lines with "goto unlock"
- }
- cnt = port->max_buf_cnt - 1;
What if we mapped 1 period? Why shouldn't we enter the unmap path?
It would enter into unmap path, as cnt would be 0 for 1 period.
You're right, I missed the = in the comparison, but I don't see a reason to subtract 1. It seems like the max_buf_cnt might have been used differently in the past?
I suggest that you drop the - 1 and change the comparison to cnt > 0, if nothing else to not confuse me if I read this code again ;)
- if (cnt >= 0) {
[..]
+int q6asm_map_memory_regions(unsigned int dir, struct audio_client *ac,
dma_addr_t phys,
unsigned int period_sz, unsigned int periods)
[..]
ac->port[dir].max_buf_cnt = 0;
kfree(buf);
ac->port[dir].buf = NULL;
These operations are done without holding cmd_lock.
I will revisit such instances where the buf is not protected.
NB. I got the impression that cmd_lock was actually the port_lock in most places.
Regards, Bjorn
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 --- sound/soc/qcom/qdsp6/q6asm.c | 530 ++++++++++++++++++++++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6asm.h | 42 ++++ 2 files changed, 571 insertions(+), 1 deletion(-)
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 4be92441f524..dabd6509ef99 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -8,16 +8,34 @@ #include <linux/soc/qcom/apr.h> #include <linux/device.h> #include <linux/platform_device.h> +#include <uapi/sound/asound.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/mm.h> #include "q6asm.h" #include "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 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 DEFAULT_APP_TYPE 0 +#define TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */ #define TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ #define SYNC_IO_MODE 0x0001 #define ASYNC_IO_MODE 0x0002 @@ -42,6 +60,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[8]; +} __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 { dma_addr_t phys; uint32_t used; @@ -408,6 +469,111 @@ static struct audio_client *q6asm_get_audio_client(struct q6asm *a, return NULL; }
+static int32_t q6asm_callback(struct apr_device *adev, + struct apr_client_data *data, int session_id) +{ + struct audio_client *ac;// = (struct audio_client *)priv; + uint32_t token; + uint32_t *payload; + uint32_t wakeup_flag = 1; + uint32_t client_event = 0; + struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + + if (data == NULL) + return -EINVAL; + + ac = q6asm_get_audio_client(q6asm, session_id); + if (!q6asm_is_valid_audio_client(ac)) + return -EINVAL; + + payload = data->payload; + + if (data->opcode == APR_BASIC_RSP_RESULT) { + token = data->token; + switch (payload[0]) { + 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 (payload[1] != 0) { + dev_err(ac->dev, + "cmd = 0x%x returned error = 0x%x\n", + payload[0], payload[1]); + if (wakeup_flag) { + ac->cmd_state = payload[1]; + wake_up(&ac->cmd_wait); + } + return 0; + } + break; + default: + dev_err(ac->dev, "command[0x%x] not expecting rsp\n", + payload[0]); + break; + } + + if (ac->cmd_state && wakeup_flag) { + ac->cmd_state = 0; + wake_up(&ac->cmd_wait); + } + if (ac->cb) + ac->cb(client_event, data->token, + data->payload, ac->priv); + + return 0; + } + + switch (data->opcode) { + case ASM_DATA_EVENT_WRITE_DONE_V2:{ + struct audio_port_data *port = + &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + + client_event = ASM_CLIENT_EVENT_DATA_WRITE_DONE; + + if (ac->io_mode & SYNC_IO_MODE) { + dma_addr_t phys = port->buf[data->token].phys; + + if (lower_32_bits(phys) != payload[0] || + upper_32_bits(phys) != payload[1]) { + 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_data *data) { struct q6asm *a, *q6asm = dev_get_drvdata(&adev->dev); @@ -415,12 +581,16 @@ static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data * struct audio_port_data *port; uint32_t dir = 0; uint32_t sid = 0; + int dest_port; uint32_t *payload;
if (!data) { dev_err(&adev->dev, "%s: Invalid CB\n", __func__); return 0; } + dest_port = (data->dest_port >> 8) & 0xFF; + if (dest_port) + return q6asm_callback(adev, data, dest_port);
payload = data->payload; sid = (data->token >> 8) & 0x0F; @@ -540,6 +710,364 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev, } EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
+static int __q6asm_open_write(struct audio_client *ac, uint32_t format, + uint16_t bits_per_sample, uint32_t stream_id, + bool is_gapless_mode) +{ + struct asm_stream_cmd_open_write_v3 open; + int rc; + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), true, stream_id); + ac->cmd_state = -1; + + open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3; + open.mode_flags = 0x00; + open.mode_flags |= ASM_LEGACY_STREAM_SESSION; + if (is_gapless_mode) + open.mode_flags |= 1 << ASM_SHIFT_GAPLESS_MODE_FLAG; + + /* source endpoint : matrix */ + open.sink_endpointype = ASM_END_POINT_DEVICE_MATRIX; + open.bits_per_sample = bits_per_sample; + open.postprocopo_id = 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 = apr_send_pkt(ac->adev, (uint32_t *) &open); + if (rc < 0) + return rc; + + rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ); + if (!rc) { + dev_err(ac->dev, "timeout on open write\n"); + return -ETIMEDOUT; + } + + if (ac->cmd_state > 0) + return adsp_err_get_lnx_err_code(ac->cmd_state); + + ac->io_mode |= TUN_WRITE_IO_MODE; + + return 0; +} + +/** + * 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) +{ + return __q6asm_open_write(ac, format, bits_per_sample, + ac->stream_id, false); +} +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; + int rc; + + q6asm_add_hdr(ac, &run.hdr, sizeof(run), true, ac->stream_id); + ac->cmd_state = -1; + + run.hdr.opcode = ASM_SESSION_CMD_RUN_V2; + run.flags = flags; + run.time_lsw = lsw_ts; + run.time_msw = msw_ts; + + rc = apr_send_pkt(ac->adev, (uint32_t *) &run); + if (rc < 0) + return rc; + + if (wait) { + rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), + 5 * HZ); + if (!rc) { + dev_err(ac->dev, "timeout on run cmd\n"); + return -ETIMEDOUT; + } + if (ac->cmd_state > 0) + return adsp_err_get_lnx_err_code(ac->cmd_state); + } + + return 0; +} + +/** + * 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, + bool use_default_chmap, + char *channel_map, + uint16_t bits_per_sample) +{ + struct asm_multi_channel_pcm_fmt_blk_v2 fmt; + u8 *channel_mapping; + int rc = 0; + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), true, ac->stream_id); + ac->cmd_state = -1; + + 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 (use_default_chmap) { + if (q6dsp_map_channels(channel_mapping, channels)) { + dev_err(ac->dev, " map channels failed %d\n", channels); + return -EINVAL; + } + } else { + memcpy(channel_mapping, channel_map, + PCM_FORMAT_MAX_NUM_CHANNEL); + } + + rc = apr_send_pkt(ac->adev, (uint32_t *) &fmt); + if (rc < 0) + goto fail_cmd; + + rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ); + if (!rc) { + dev_err(ac->dev, "timeout on format update\n"); + return -ETIMEDOUT; + } + if (ac->cmd_state > 0) + return adsp_err_get_lnx_err_code(ac->cmd_state); + + return 0; +fail_cmd: + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_media_format_block_multi_ch_pcm); + +/** + * q6asm_write_nolock() - 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_nolock(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 dsp_buf = 0; + int rc = 0; + + if (ac->io_mode & SYNC_IO_MODE) { + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + q6asm_add_hdr(ac, &write.hdr, sizeof(write), false, + ac->stream_id); + + dsp_buf = port->dsp_buf; + ab = &port->buf[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->max_buf_cnt) + port->dsp_buf = 0; + + rc = apr_send_pkt(ac->adev, (uint32_t *) &write); + if (rc < 0) + return rc; + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6asm_write_nolock); + +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 & SYNC_IO_MODE) { + used = (ac->io_mode & TUN_WRITE_IO_MODE ? 1 : 0); + mutex_lock(&ac->cmd_lock); + for (loopcnt = 0; loopcnt <= SNDRV_PCM_STREAM_CAPTURE; + loopcnt++) { + port = &ac->port[loopcnt]; + cnt = port->max_buf_cnt - 1; + port->dsp_buf = 0; + while (cnt >= 0) { + if (!port->buf) + continue; + port->buf[cnt].used = used; + cnt--; + } + } + mutex_unlock(&ac->cmd_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); + ac->cmd_state = -1; + 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; + ac->cmd_state = 0; + break; + case CMD_CLOSE: + hdr.opcode = ASM_STREAM_CMD_CLOSE; + break; + default: + return -EINVAL; + } + + rc = apr_send_pkt(ac->adev, (uint32_t *) &hdr); + if (rc < 0) + return rc; + + if (!wait) + return 0; + + rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ); + if (!rc) { + dev_err(ac->dev, "timeout response for opcode[0x%x]\n", + hdr.opcode); + return -ETIMEDOUT; + } + if (ac->cmd_state > 0) + return adsp_err_get_lnx_err_code(ac->cmd_state); + + 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 e1409c368600..b4896059da79 100644 --- a/sound/soc/qcom/qdsp6/q6asm.h +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -2,7 +2,34 @@ #ifndef __Q6_ASM_H__ #define __Q6_ASM_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 + +#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 MAX_SESSIONS 16 +#define NO_TIMESTAMP 0xFF00 +#define FORMAT_LINEAR_PCM 0x0000
typedef void (*app_cb) (uint32_t opcode, uint32_t token, uint32_t *payload, void *priv); @@ -10,6 +37,21 @@ struct audio_client; struct audio_client *q6asm_audio_client_alloc(struct device *dev, app_cb cb, void *priv); void q6asm_audio_client_free(struct audio_client *ac); +int q6asm_write_nolock(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, + bool use_default_chmap, + char *channel_map, + 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 Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
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
sound/soc/qcom/qdsp6/q6asm.c | 530 ++++++++++++++++++++++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6asm.h | 42 ++++ 2 files changed, 571 insertions(+), 1 deletion(-)
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 4be92441f524..dabd6509ef99 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -8,16 +8,34 @@ #include <linux/soc/qcom/apr.h> #include <linux/device.h> #include <linux/platform_device.h> +#include <uapi/sound/asound.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/mm.h> #include "q6asm.h" #include "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 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 DEFAULT_APP_TYPE 0 +#define TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */ #define TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ #define SYNC_IO_MODE 0x0001 #define ASYNC_IO_MODE 0x0002
Probably prettier to reorder these and make them Q6ASM_IO_MODE_xyz
[..]
+static int32_t q6asm_callback(struct apr_device *adev,
This callback is an extracted part of q6asm_srvc_callback(), can it be given a more descriptive name?
struct apr_client_data *data, int session_id)
+{
- struct audio_client *ac;// = (struct audio_client *)priv;
- uint32_t token;
- uint32_t *payload;
- uint32_t wakeup_flag = 1;
- uint32_t client_event = 0;
- struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
- if (data == NULL)
return -EINVAL;
- ac = q6asm_get_audio_client(q6asm, session_id);
- if (!q6asm_is_valid_audio_client(ac))
return -EINVAL;
- payload = data->payload;
- if (data->opcode == APR_BASIC_RSP_RESULT) {
Move this into the switch.
token = data->token;
switch (payload[0]) {
This is again that common response struct.
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 (payload[1] != 0) {
dev_err(ac->dev,
"cmd = 0x%x returned error = 0x%x\n",
payload[0], payload[1]);
if (wakeup_flag) {
ac->cmd_state = payload[1];
wake_up(&ac->cmd_wait);
}
return 0;
}
break;
default:
dev_err(ac->dev, "command[0x%x] not expecting rsp\n",
payload[0]);
break;
}
if (ac->cmd_state && wakeup_flag) {
ac->cmd_state = 0;
wake_up(&ac->cmd_wait);
}
if (ac->cb)
ac->cb(client_event, data->token,
data->payload, ac->priv);
return 0;
- }
- switch (data->opcode) {
- case ASM_DATA_EVENT_WRITE_DONE_V2:{
struct audio_port_data *port =
&ac->port[SNDRV_PCM_STREAM_PLAYBACK];
client_event = ASM_CLIENT_EVENT_DATA_WRITE_DONE;
if (ac->io_mode & SYNC_IO_MODE) {
dma_addr_t phys = port->buf[data->token].phys;
if (lower_32_bits(phys) != payload[0] ||
upper_32_bits(phys) != payload[1]) {
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_data *data) { struct q6asm *a, *q6asm = dev_get_drvdata(&adev->dev); @@ -415,12 +581,16 @@ static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data * struct audio_port_data *port; uint32_t dir = 0; uint32_t sid = 0;
int dest_port; uint32_t *payload;
if (!data) { dev_err(&adev->dev, "%s: Invalid CB\n", __func__); return 0; }
dest_port = (data->dest_port >> 8) & 0xFF;
if (dest_port)
return q6asm_callback(adev, data, dest_port);
You call dest_port "session_id" above, this seems to be a better name for this variable.
payload = data->payload; sid = (data->token >> 8) & 0x0F; @@ -540,6 +710,364 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev, } EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
+static int __q6asm_open_write(struct audio_client *ac, uint32_t format,
uint16_t bits_per_sample, uint32_t stream_id,
bool is_gapless_mode)
+{
- struct asm_stream_cmd_open_write_v3 open;
- int rc;
- q6asm_add_hdr(ac, &open.hdr, sizeof(open), true, stream_id);
- ac->cmd_state = -1;
- open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3;
- open.mode_flags = 0x00;
- open.mode_flags |= ASM_LEGACY_STREAM_SESSION;
- if (is_gapless_mode)
This is hard coded as false.
open.mode_flags |= 1 << ASM_SHIFT_GAPLESS_MODE_FLAG;
- /* source endpoint : matrix */
- open.sink_endpointype = ASM_END_POINT_DEVICE_MATRIX;
- open.bits_per_sample = bits_per_sample;
- open.postprocopo_id = 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 = apr_send_pkt(ac->adev, (uint32_t *) &open);
- if (rc < 0)
return rc;
- rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout on open write\n");
return -ETIMEDOUT;
- }
Almost every time you apr_send_pkt() you have this wait with timeout, can this send/wait/return be wrapped in a helper function to reduce the duplication?
Creating a q6asm_send_sync() and q6asm_send_async() pair with this logic should help quite a bit.
- if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- ac->io_mode |= TUN_WRITE_IO_MODE;
- return 0;
+}
+/**
- 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)
+{
- return __q6asm_open_write(ac, format, bits_per_sample,
I don't see a particular reason for not inlining this, is there one coming later in the series?
ac->stream_id, false);
+} +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;
- int rc;
- q6asm_add_hdr(ac, &run.hdr, sizeof(run), true, ac->stream_id);
- ac->cmd_state = -1;
- run.hdr.opcode = ASM_SESSION_CMD_RUN_V2;
- run.flags = flags;
- run.time_lsw = lsw_ts;
- run.time_msw = msw_ts;
- rc = apr_send_pkt(ac->adev, (uint32_t *) &run);
- if (rc < 0)
return rc;
- if (wait) {
Rather than having half of the function conditional I would recommend inlining this function in the two callers.
In particular if you can come up with a helper function for the send/wait/handle-error case.
rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0),
5 * HZ);
if (!rc) {
dev_err(ac->dev, "timeout on run cmd\n");
return -ETIMEDOUT;
}
if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- }
- return 0;
+}
+/**
- 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,
bool use_default_chmap,
char *channel_map,
This should be u8 channel_map[PCM_FORMAT_MAX_NUM_CHANNEL], possibly char. Unless you, as I suggest below, want to be able to represent use_default_chmap = false, by setting this to NULL.
uint16_t bits_per_sample)
+{
- struct asm_multi_channel_pcm_fmt_blk_v2 fmt;
- u8 *channel_mapping;
- int rc = 0;
Unnecessary initialization.
- q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), true, ac->stream_id);
- ac->cmd_state = -1;
- 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 (use_default_chmap) {
Passing NULL as channel_map would probably be a nicer way to say this, instead of having a separate bool.
if (q6dsp_map_channels(channel_mapping, channels)) {
dev_err(ac->dev, " map channels failed %d\n", channels);
return -EINVAL;
}
- } else {
memcpy(channel_mapping, channel_map,
PCM_FORMAT_MAX_NUM_CHANNEL);
- }
- rc = apr_send_pkt(ac->adev, (uint32_t *) &fmt);
- if (rc < 0)
goto fail_cmd;
- rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout on format update\n");
return -ETIMEDOUT;
- }
- if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- return 0;
+fail_cmd:
- return rc;
+} +EXPORT_SYMBOL_GPL(q6asm_media_format_block_multi_ch_pcm);
+/**
- q6asm_write_nolock() - 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_nolock(struct audio_client *ac, uint32_t len, uint32_t msw_ts,
uint32_t lsw_ts, uint32_t flags)
q6asm_write_async() is probably a better name, nolock indicates some relationship to mutual exclusions...
+{
- struct asm_data_cmd_write_v2 write;
- struct audio_port_data *port;
- struct audio_buffer *ab;
- int dsp_buf = 0;
- int rc = 0;
- if (ac->io_mode & SYNC_IO_MODE) {
Bail early if this isn't true, to save you the indentation level.
port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK];
q6asm_add_hdr(ac, &write.hdr, sizeof(write), false,
ac->stream_id);
dsp_buf = port->dsp_buf;
ab = &port->buf[dsp_buf];
So we're just unconditionally telling the remote side about the next buf in our ring buffer. Do we need to ensure that this is available/ready?
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);
Fill in the constant and this becomes
if flags == 0xff00: write.flags = 0xff00 & 0x800000ff;
Or in other words: if flags == 0xff00: write.flags = 0;
else
write.flags = (0x80000000 | flags);
Drop the parenthesis and flip the |. It would be nice to have a define or a comment indicating what BIT(31) is...
port->dsp_buf++;
if (port->dsp_buf >= port->max_buf_cnt)
port->dsp_buf = 0;
rc = apr_send_pkt(ac->adev, (uint32_t *) &write);
if (rc < 0)
return rc;
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(q6asm_write_nolock);
+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 & SYNC_IO_MODE) {
used = (ac->io_mode & TUN_WRITE_IO_MODE ? 1 : 0);
mutex_lock(&ac->cmd_lock);
for (loopcnt = 0; loopcnt <= SNDRV_PCM_STREAM_CAPTURE;
loopcnt++) {
port = &ac->port[loopcnt];
cnt = port->max_buf_cnt - 1;
port->dsp_buf = 0;
while (cnt >= 0) {
if (!port->buf)
continue;
port->buf[cnt].used = used;
cnt--;
}
}
mutex_unlock(&ac->cmd_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);
- ac->cmd_state = -1;
Resetting cmd_state relates to the send, don't mix it with building the packet.
- 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;
ac->cmd_state = 0;
break;
- case CMD_CLOSE:
hdr.opcode = ASM_STREAM_CMD_CLOSE;
break;
- default:
return -EINVAL;
- }
- rc = apr_send_pkt(ac->adev, (uint32_t *) &hdr);
- if (rc < 0)
return rc;
- if (!wait)
return 0;
I've asked you to split the others into _sync() vs _async() operations.
One particular concern I have is that I don't see any mutual exclusion protecting the cmd_state and a call with !wait will overwrite the existing value, which might be unexpected.
- rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout response for opcode[0x%x]\n",
hdr.opcode);
return -ETIMEDOUT;
- }
- if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- if (cmd == CMD_FLUSH)
q6asm_reset_buf_state(ac);
- return 0;
+}
[..]
diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h index e1409c368600..b4896059da79 100644 --- a/sound/soc/qcom/qdsp6/q6asm.h +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -2,7 +2,34 @@ #ifndef __Q6_ASM_H__ #define __Q6_ASM_H__
+/* ASM client callback events */ +#define CMD_PAUSE 0x0001
These defines has rather generic names...
[..]
+#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 MAX_SESSIONS 16 +#define NO_TIMESTAMP 0xFF00 +#define FORMAT_LINEAR_PCM 0x0000
Ditto.
Regards, Bjorn
Thanks for your comments.
On 02/01/18 20:08, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
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
sound/soc/qcom/qdsp6/q6asm.c | 530 ++++++++++++++++++++++++++++++++++++++++++- sound/soc/qcom/qdsp6/q6asm.h | 42 ++++ 2 files changed, 571 insertions(+), 1 deletion(-)
diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c index 4be92441f524..dabd6509ef99 100644 --- a/sound/soc/qcom/qdsp6/q6asm.c +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -8,16 +8,34 @@ #include <linux/soc/qcom/apr.h> #include <linux/device.h> #include <linux/platform_device.h> +#include <uapi/sound/asound.h> #include <linux/delay.h> #include <linux/slab.h> #include <linux/mm.h> #include "q6asm.h" #include "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 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 DEFAULT_APP_TYPE 0 +#define TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */ #define TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ #define SYNC_IO_MODE 0x0001 #define ASYNC_IO_MODE 0x0002
Probably prettier to reorder these and make them Q6ASM_IO_MODE_xyz
Sure I will try that.
[..]
+static int32_t q6asm_callback(struct apr_device *adev,
This callback is an extracted part of q6asm_srvc_callback(), can it be given a more descriptive name?
May be q6asm_stream_callback/q6asm_session_callback() should be better.
struct apr_client_data *data, int session_id)
+{
- struct audio_client *ac;// = (struct audio_client *)priv;
- uint32_t token;
- uint32_t *payload;
- uint32_t wakeup_flag = 1;
- uint32_t client_event = 0;
- struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
- if (data == NULL)
return -EINVAL;
- ac = q6asm_get_audio_client(q6asm, session_id);
- if (!q6asm_is_valid_audio_client(ac))
return -EINVAL;
- payload = data->payload;
- if (data->opcode == APR_BASIC_RSP_RESULT) {
Move this into the switch.
Yep, will cleanup these instances.
token = data->token;
switch (payload[0]) {
This is again that common response struct.
yep!
[...]
- return 0;
+}
- static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data *data) { struct q6asm *a, *q6asm = dev_get_drvdata(&adev->dev);
@@ -415,12 +581,16 @@ static int q6asm_srvc_callback(struct apr_device *adev, struct apr_client_data * struct audio_port_data *port; uint32_t dir = 0; uint32_t sid = 0;
int dest_port; uint32_t *payload;
if (!data) { dev_err(&adev->dev, "%s: Invalid CB\n", __func__); return 0; }
dest_port = (data->dest_port >> 8) & 0xFF;
if (dest_port)
return q6asm_callback(adev, data, dest_port);
You call dest_port "session_id" above, this seems to be a better name for this variable.
yes
payload = data->payload; sid = (data->token >> 8) & 0x0F; @@ -540,6 +710,364 @@ struct audio_client *q6asm_audio_client_alloc(struct device *dev, } EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
+static int __q6asm_open_write(struct audio_client *ac, uint32_t format,
uint16_t bits_per_sample, uint32_t stream_id,
bool is_gapless_mode)
+{
- struct asm_stream_cmd_open_write_v3 open;
- int rc;
- q6asm_add_hdr(ac, &open.hdr, sizeof(open), true, stream_id);
- ac->cmd_state = -1;
- open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3;
- open.mode_flags = 0x00;
- open.mode_flags |= ASM_LEGACY_STREAM_SESSION;
- if (is_gapless_mode)
This is hard coded as false.
Will clean this up.
open.mode_flags |= 1 << ASM_SHIFT_GAPLESS_MODE_FLAG;
- /* source endpoint : matrix */
- open.sink_endpointype = ASM_END_POINT_DEVICE_MATRIX;
- open.bits_per_sample = bits_per_sample;
- open.postprocopo_id = 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 = apr_send_pkt(ac->adev, (uint32_t *) &open);
- if (rc < 0)
return rc;
- rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout on open write\n");
return -ETIMEDOUT;
- }
Almost every time you apr_send_pkt() you have this wait with timeout, can this send/wait/return be wrapped in a helper function to reduce the duplication?
Creating a q6asm_send_sync() and q6asm_send_async() pair with this logic should help quite a bit.
will do that with all the apr drivers.
- if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- ac->io_mode |= TUN_WRITE_IO_MODE;
- return 0;
+}
+/**
- 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)
+{
- return __q6asm_open_write(ac, format, bits_per_sample,
I don't see a particular reason for not inlining this, is there one coming later in the series?
No, will clean it up.
ac->stream_id, false);
+} +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;
- int rc;
- q6asm_add_hdr(ac, &run.hdr, sizeof(run), true, ac->stream_id);
- ac->cmd_state = -1;
- run.hdr.opcode = ASM_SESSION_CMD_RUN_V2;
- run.flags = flags;
- run.time_lsw = lsw_ts;
- run.time_msw = msw_ts;
- rc = apr_send_pkt(ac->adev, (uint32_t *) &run);
- if (rc < 0)
return rc;
- if (wait) {
Rather than having half of the function conditional I would recommend inlining this function in the two callers.
In particular if you can come up with a helper function for the send/wait/handle-error case.
sure.
rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0),
5 * HZ);
if (!rc) {
dev_err(ac->dev, "timeout on run cmd\n");
return -ETIMEDOUT;
}
if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- }
- return 0;
+}
+/**
- 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,
bool use_default_chmap,
char *channel_map,
This should be u8 channel_map[PCM_FORMAT_MAX_NUM_CHANNEL], possibly char. Unless you, as I suggest below, want to be able to represent use_default_chmap = false, by setting this to NULL.
uint16_t bits_per_sample)
+{
- struct asm_multi_channel_pcm_fmt_blk_v2 fmt;
- u8 *channel_mapping;
- int rc = 0;
Unnecessary initialization.
yep.
- q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), true, ac->stream_id);
- ac->cmd_state = -1;
- 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 (use_default_chmap) {
Passing NULL as channel_map would probably be a nicer way to say this, instead of having a separate bool.
I will give it a go and see.
if (q6dsp_map_channels(channel_mapping, channels)) {
dev_err(ac->dev, " map channels failed %d\n", channels);
return -EINVAL;
}
- } else {
memcpy(channel_mapping, channel_map,
PCM_FORMAT_MAX_NUM_CHANNEL);
- }
- rc = apr_send_pkt(ac->adev, (uint32_t *) &fmt);
- if (rc < 0)
goto fail_cmd;
- rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout on format update\n");
return -ETIMEDOUT;
- }
- if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- return 0;
+fail_cmd:
- return rc;
+} +EXPORT_SYMBOL_GPL(q6asm_media_format_block_multi_ch_pcm);
+/**
- q6asm_write_nolock() - 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_nolock(struct audio_client *ac, uint32_t len, uint32_t msw_ts,
uint32_t lsw_ts, uint32_t flags)
q6asm_write_async() is probably a better name, nolock indicates some relationship to mutual exclusions...
yep.
+{
- struct asm_data_cmd_write_v2 write;
- struct audio_port_data *port;
- struct audio_buffer *ab;
- int dsp_buf = 0;
- int rc = 0;
- if (ac->io_mode & SYNC_IO_MODE) {
Bail early if this isn't true, to save you the indentation level.
yep.
port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK];
q6asm_add_hdr(ac, &write.hdr, sizeof(write), false,
ac->stream_id);
dsp_buf = port->dsp_buf;
ab = &port->buf[dsp_buf];
So we're just unconditionally telling the remote side about the next buf in our ring buffer. Do we need to ensure that this is available/ready?
This is already synchronized at the top layer in q6asm_dai driver.
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);
Fill in the constant and this becomes
if flags == 0xff00: write.flags = 0xff00 & 0x800000ff;
Or in other words: if flags == 0xff00: write.flags = 0;
else
write.flags = (0x80000000 | flags);
Drop the parenthesis and flip the |. It would be nice to have a define or a comment indicating what BIT(31) is...
sure, I will make add more information here on the flag and also cleanup as suggested.
port->dsp_buf++;
if (port->dsp_buf >= port->max_buf_cnt)
port->dsp_buf = 0;
rc = apr_send_pkt(ac->adev, (uint32_t *) &write);
if (rc < 0)
return rc;
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(q6asm_write_nolock);
[...]
+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);
- ac->cmd_state = -1;
Resetting cmd_state relates to the send, don't mix it with building the packet.
Sure.
- 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;
ac->cmd_state = 0;
break;
- case CMD_CLOSE:
hdr.opcode = ASM_STREAM_CMD_CLOSE;
break;
- default:
return -EINVAL;
- }
- rc = apr_send_pkt(ac->adev, (uint32_t *) &hdr);
- if (rc < 0)
return rc;
- if (!wait)
return 0;
I've asked you to split the others into _sync() vs _async() operations.
One particular concern I have is that I don't see any mutual exclusion protecting the cmd_state and a call with !wait will overwrite the existing value, which might be unexpected.
yes, this will be issue, we could move setting cmd_state to here.
Also I will revisit _sync() function to make sure that these are sequenced correctly and async are not touching the cmd_state.
- rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout response for opcode[0x%x]\n",
hdr.opcode);
return -ETIMEDOUT;
- }
- if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- if (cmd == CMD_FLUSH)
q6asm_reset_buf_state(ac);
- return 0;
+}
[..]
diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h index e1409c368600..b4896059da79 100644 --- a/sound/soc/qcom/qdsp6/q6asm.h +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -2,7 +2,34 @@ #ifndef __Q6_ASM_H__ #define __Q6_ASM_H__
+/* ASM client callback events */ +#define CMD_PAUSE 0x0001
These defines has rather generic names...
I can prefix them with Q6ASM to make it much more specific to Q6ASM service.
[..]
+#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 MAX_SESSIONS 16
+#define NO_TIMESTAMP 0xFF00 +#define FORMAT_LINEAR_PCM 0x0000
Ditto.
Regards, Bjorn
On 12/14/2017 11:03 PM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to open, write and media format commands in the q6asm module.
[..]
+static int32_t q6asm_callback(struct apr_device *adev,
struct apr_client_data *data, int session_id)
+{
- struct audio_client *ac;// = (struct audio_client *)priv;
- uint32_t token;
- uint32_t *payload;
- uint32_t wakeup_flag = 1;
- uint32_t client_event = 0;
- struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
- if (data == NULL)
return -EINVAL;
- ac = q6asm_get_audio_client(q6asm, session_id);
- if (!q6asm_is_valid_audio_client(ac))
return -EINVAL;
ac could get freed by q6asm_audio_client_free during the execution of q6asm_callback as they are running in different thread. Add synchronization.
- payload = data->payload;
- if (data->opcode == APR_BASIC_RSP_RESULT) {
token = data->token;
switch (payload[0]) {
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 (payload[1] != 0) {
dev_err(ac->dev,
"cmd = 0x%x returned error = 0x%x\n",
payload[0], payload[1]);
if (wakeup_flag) {
ac->cmd_state = payload[1];
wake_up(&ac->cmd_wait);
}
return 0;
}
break;
default:
dev_err(ac->dev, "command[0x%x] not expecting rsp\n",
payload[0]);
break;
}
if (ac->cmd_state && wakeup_flag) {
ac->cmd_state = 0;
wake_up(&ac->cmd_wait);
}
if (ac->cb)
ac->cb(client_event, data->token,
data->payload, ac->priv);
return 0;
- }
- switch (data->opcode) {
- case ASM_DATA_EVENT_WRITE_DONE_V2:{
struct audio_port_data *port =
&ac->port[SNDRV_PCM_STREAM_PLAYBACK];
client_event = ASM_CLIENT_EVENT_DATA_WRITE_DONE;
if (ac->io_mode & SYNC_IO_MODE) {
dma_addr_t phys = port->buf[data->token].phys;
if (lower_32_bits(phys) != payload[0] ||
upper_32_bits(phys) != payload[1]) {
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;
+}
[..]
+/**
- 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,
bool use_default_chmap,
char *channel_map,
uint16_t bits_per_sample)
+{
- struct asm_multi_channel_pcm_fmt_blk_v2 fmt;
asm_multi_channel_pcm_fmt_blk_v4 is now being used in latest adsp. Better to add adsp version based support to handle different struct
- u8 *channel_mapping;
- int rc = 0;
- q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), true, ac->stream_id);
- ac->cmd_state = -1;
- 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 (use_default_chmap) {
if (q6dsp_map_channels(channel_mapping, channels)) {
dev_err(ac->dev, " map channels failed %d\n", channels);
return -EINVAL;
}
- } else {
memcpy(channel_mapping, channel_map,
PCM_FORMAT_MAX_NUM_CHANNEL);
- }
- rc = apr_send_pkt(ac->adev, (uint32_t *) &fmt);
- if (rc < 0)
goto fail_cmd;
- rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout on format update\n");
return -ETIMEDOUT;
- }
- if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- return 0;
+fail_cmd:
- return rc;
+} +EXPORT_SYMBOL_GPL(q6asm_media_format_block_multi_ch_pcm);
+/**
- q6asm_write_nolock() - 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_nolock(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 dsp_buf = 0;
- int rc = 0;
- if (ac->io_mode & SYNC_IO_MODE) {
port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK];
q6asm_add_hdr(ac, &write.hdr, sizeof(write), false,
ac->stream_id);
dsp_buf = port->dsp_buf;
ab = &port->buf[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->max_buf_cnt)
port->dsp_buf = 0;
rc = apr_send_pkt(ac->adev, (uint32_t *) &write);
if (rc < 0)
return rc;
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(q6asm_write_nolock);
+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 & SYNC_IO_MODE) {
used = (ac->io_mode & TUN_WRITE_IO_MODE ? 1 : 0);
mutex_lock(&ac->cmd_lock);
for (loopcnt = 0; loopcnt <= SNDRV_PCM_STREAM_CAPTURE;
loopcnt++) {
port = &ac->port[loopcnt];
cnt = port->max_buf_cnt - 1;
port->dsp_buf = 0;
while (cnt >= 0) {
if (!port->buf)
continue;
port->buf[cnt].used = used;
cnt--;
}
}
mutex_unlock(&ac->cmd_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);
- ac->cmd_state = -1;
- 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;
ac->cmd_state = 0;
break;
- case CMD_CLOSE:
hdr.opcode = ASM_STREAM_CMD_CLOSE;
break;
- default:
return -EINVAL;
- }
- rc = apr_send_pkt(ac->adev, (uint32_t *) &hdr);
- if (rc < 0)
return rc;
- if (!wait)
return 0;
- rc = wait_event_timeout(ac->cmd_wait, (ac->cmd_state >= 0), 5 * HZ);
- if (!rc) {
dev_err(ac->dev, "timeout response for opcode[0x%x]\n",
hdr.opcode);
return -ETIMEDOUT;
- }
- if (ac->cmd_state > 0)
return adsp_err_get_lnx_err_code(ac->cmd_state);
- 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 e1409c368600..b4896059da79 100644 --- a/sound/soc/qcom/qdsp6/q6asm.h +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -2,7 +2,34 @@ #ifndef __Q6_ASM_H__ #define __Q6_ASM_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
+#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 MAX_SESSIONS 16
+#define NO_TIMESTAMP 0xFF00 +#define FORMAT_LINEAR_PCM 0x0000
typedef void (*app_cb) (uint32_t opcode, uint32_t token, uint32_t *payload, void *priv); @@ -10,6 +37,21 @@ struct audio_client; struct audio_client *q6asm_audio_client_alloc(struct device *dev, app_cb cb, void *priv); void q6asm_audio_client_free(struct audio_client *ac); +int q6asm_write_nolock(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,
bool use_default_chmap,
char *channel_map,
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,
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 | 227 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6core.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 7ebdb879a8a3..121b9c957024 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -56,11 +56,16 @@ 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" 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 49dd3ccab27b..ad7f10691e54 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,3 +1,4 @@ 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..d4e2dbc62489 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -0,0 +1,227 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2017, Linaro Limited +*/ +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/jiffies.h> +#include <linux/wait.h> +#include <linux/soc/qcom/apr.h> +#include <linux/platform_device.h> +#include <sound/asound.h> +#include "common.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; + int resp_received; + uint32_t num_services; + struct avcs_svc_info *svcs_info; +}; + +static struct apr_device_id static_services[] = { + ADSP_AUDIO_APR_DEV("AFE", APR_SVC_AFE), + ADSP_AUDIO_APR_DEV("ASM", APR_SVC_ASM), + ADSP_AUDIO_APR_DEV("ADM", APR_SVC_ADM), + ADSP_AUDIO_APR_DEV("TEST", APR_SVC_TEST_CLIENT), + ADSP_AUDIO_APR_DEV("MVM", APR_SVC_ADSP_MVM), + ADSP_AUDIO_APR_DEV("CVS", APR_SVC_ADSP_CVS), + ADSP_AUDIO_APR_DEV("CVP", APR_SVC_ADSP_CVP), + ADSP_AUDIO_APR_DEV("USM", APR_SVC_USM), + ADSP_AUDIO_APR_DEV("VIDC", APR_SVC_VIDC), + ADSP_AUDIO_APR_DEV("LSM", APR_SVC_LSM), +}; + +static int core_callback(struct apr_device *adev, struct apr_client_data *data) +{ + struct q6core *core = dev_get_drvdata(&adev->dev); + uint32_t *payload; + + switch (data->opcode) { + case AVCS_GET_VERSIONS_RSP: + payload = data->payload; + core->num_services = payload[1]; + + if (!core->svcs_info) + core->svcs_info = kcalloc(core->num_services, + sizeof(*core->svcs_info), + GFP_ATOMIC); + if (!core->svcs_info) + return -ENOMEM; + + /* svc info is after 8 bytes */ + memcpy(core->svcs_info, payload + 2, + core->num_services * sizeof(*core->svcs_info)); + + core->resp_received = 1; + wake_up(&core->wait); + + break; + case AVCS_CMDRSP_ADSP_EVENT_GET_STATE: + payload = data->payload; + core->avcs_state = payload[0]; + + core->resp_received = 1; + wake_up(&core->wait); + break; + default: + dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n", + data->opcode); + break; + } + + return 0; +} + +void q6core_add_service(struct device *dev, uint32_t svc_id, uint32_t version) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(static_services); i++) { + if (static_services[i].svc_id == svc_id) { + static_services[i].svc_version = version; + apr_add_device(dev->parent, &static_services[i]); + dev_info(dev, + "Adding SVC: %s: id 0x%x API Ver 0x%x:0x%x\n", + static_services[i].name, svc_id, + APR_SVC_MAJOR_VERSION(version), + APR_SVC_MINOR_VERSION(version)); + } + } +} + +static void q6core_add_static_services(struct q6core *core) +{ + int i; + struct apr_device *adev = core->adev; + struct avcs_svc_info *svcs_info = core->svcs_info; + + for (i = 0; i < core->num_services; i++) + q6core_add_service(&adev->dev, svcs_info[i].service_id, + svcs_info[i].version); +} + +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_PKT_SIZE(APR_HDR_SIZE, 0); + hdr.opcode = AVCS_GET_VERSIONS; + + rc = apr_send_pkt(adev, (uint32_t *)&hdr); + if (rc < 0) + return rc; + + rc = wait_event_timeout(core->wait, (core->resp_received == 1), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = 0; + 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_PKT_SIZE(APR_HDR_SIZE, 0); + hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE; + + rc = apr_send_pkt(adev, (uint32_t *)&hdr); + if (rc < 0) + return false; + + rc = wait_event_timeout(core->wait, (core->resp_received == 1), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = 0; + if (core->avcs_state == 0x1) + return true; + } + + return false; +} + +static int q6core_probe(struct apr_device *adev) +{ + struct q6core *core; + unsigned long timeout = jiffies + + msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS); + int ret = 0; + + core = devm_kzalloc(&adev->dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + dev_set_drvdata(&adev->dev, core); + + core->adev = adev; + init_waitqueue_head(&core->wait); + + do { + if (!q6core_is_adsp_ready(core)) { + dev_info(&adev->dev, "ADSP Audio isn't ready\n"); + } else { + dev_info(&adev->dev, "ADSP Audio is ready\n"); + + ret = q6core_get_svc_versions(core); + if (!ret) + q6core_add_static_services(core); + + break; + } + } while (time_after(timeout, jiffies)); + + return ret; +} + +static int q6core_exit(struct apr_device *adev) +{ + return 0; +} + +static const struct apr_device_id core_id[] = { + {"Q6CORE", APR_DOMAIN_ADSP, APR_SVC_ADSP_CORE, APR_CLIENT_AUDIO}, + { }, +}; + +static struct apr_driver qcom_q6core_driver = { + .probe = q6core_probe, + .remove = q6core_exit, + .callback = core_callback, + .id_table = core_id, + .driver = { + .name = "qcom-q6core", + }, +}; + +module_apr_driver(qcom_q6core_driver); + +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("q6 core"); +MODULE_LICENSE("GPL v2");
On Thu 14 Dec 09:33 PST 2017, 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 | 227 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6core.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 7ebdb879a8a3..121b9c957024 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -56,11 +56,16 @@ 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" 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 49dd3ccab27b..ad7f10691e54 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,3 +1,4 @@ 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..d4e2dbc62489 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -0,0 +1,227 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2017, Linaro Limited +*/ +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/jiffies.h> +#include <linux/wait.h> +#include <linux/soc/qcom/apr.h> +#include <linux/platform_device.h> +#include <sound/asound.h> +#include "common.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;
- int resp_received;
bool
- uint32_t num_services;
- struct avcs_svc_info *svcs_info;
+};
+static struct apr_device_id static_services[] = {
- ADSP_AUDIO_APR_DEV("AFE", APR_SVC_AFE),
- ADSP_AUDIO_APR_DEV("ASM", APR_SVC_ASM),
- ADSP_AUDIO_APR_DEV("ADM", APR_SVC_ADM),
- ADSP_AUDIO_APR_DEV("TEST", APR_SVC_TEST_CLIENT),
- ADSP_AUDIO_APR_DEV("MVM", APR_SVC_ADSP_MVM),
- ADSP_AUDIO_APR_DEV("CVS", APR_SVC_ADSP_CVS),
- ADSP_AUDIO_APR_DEV("CVP", APR_SVC_ADSP_CVP),
- ADSP_AUDIO_APR_DEV("USM", APR_SVC_USM),
- ADSP_AUDIO_APR_DEV("VIDC", APR_SVC_VIDC),
- ADSP_AUDIO_APR_DEV("LSM", APR_SVC_LSM),
+};
+static int core_callback(struct apr_device *adev, struct apr_client_data *data) +{
- struct q6core *core = dev_get_drvdata(&adev->dev);
- uint32_t *payload;
- switch (data->opcode) {
- case AVCS_GET_VERSIONS_RSP:
payload = data->payload;
core->num_services = payload[1];
Describe the payload in a struct (with flexible array member for the svcs_info list).
if (!core->svcs_info)
core->svcs_info = kcalloc(core->num_services,
sizeof(*core->svcs_info),
GFP_ATOMIC);
if (!core->svcs_info)
return -ENOMEM;
If we receive this twice with different num_services for some reason the memcpy might trash the heap.
But as this is the get_version response and we're only going to issue that once you should remove the check for !core->svcs_info above.
And don't forget to free svcs_info once you have added your services.
/* svc info is after 8 bytes */
memcpy(core->svcs_info, payload + 2,
core->num_services * sizeof(*core->svcs_info));
core->resp_received = 1;
wake_up(&core->wait);
break;
- case AVCS_CMDRSP_ADSP_EVENT_GET_STATE:
payload = data->payload;
core->avcs_state = payload[0];
core->resp_received = 1;
wake_up(&core->wait);
break;
- default:
dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n",
data->opcode);
break;
- }
- return 0;
+}
+void q6core_add_service(struct device *dev, uint32_t svc_id, uint32_t version) +{
- int i;
- for (i = 0; i < ARRAY_SIZE(static_services); i++) {
if (static_services[i].svc_id == svc_id) {
static_services[i].svc_version = version;
apr_add_device(dev->parent, &static_services[i]);
dev_info(dev,
Please don't spam the logs, dev_dbg() should be enough. And as apr_add_device() returns you can find the devices registered in /sys
"Adding SVC: %s: id 0x%x API Ver 0x%x:0x%x\n",
static_services[i].name, svc_id,
APR_SVC_MAJOR_VERSION(version),
APR_SVC_MINOR_VERSION(version));
}
- }
+}
+static void q6core_add_static_services(struct q6core *core)
The name of this function is deceiving, it doesn't really add the static services. It adds devices for the services that we've been informed exists, by the other side - using the static list of services.
Per the comment on a previous patch I don't think the "name" in apr_device_id provides any real value and in this case if forces you to perform a lookup using this table.
If you drop the name, you can loop over the list of service ids returned from the remote and just register them with a hard coded domain id (based on apr instance?) and client_id. You don't need the lookup table.
+{
- int i;
- struct apr_device *adev = core->adev;
- struct avcs_svc_info *svcs_info = core->svcs_info;
- for (i = 0; i < core->num_services; i++)
q6core_add_service(&adev->dev, svcs_info[i].service_id,
svcs_info[i].version);
+}
+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_PKT_SIZE(APR_HDR_SIZE, 0);
- hdr.opcode = AVCS_GET_VERSIONS;
- rc = apr_send_pkt(adev, (uint32_t *)&hdr);
- if (rc < 0)
return rc;
- rc = wait_event_timeout(core->wait, (core->resp_received == 1),
msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
The wait and resp_received could favourably be expressed as a completion instead, as all we care about is that this happened once.
- if (rc > 0 && core->resp_received) {
core->resp_received = 0;
return 0;
- }
It wasn't obvious at first sight that this is the success case and the return rc below was the error case...
- return rc;
And this will actually be 0 if core->resp_received has not become 1 at the timeout.
+}
+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_PKT_SIZE(APR_HDR_SIZE, 0);
- hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE;
- rc = apr_send_pkt(adev, (uint32_t *)&hdr);
- if (rc < 0)
return false;
- rc = wait_event_timeout(core->wait, (core->resp_received == 1),
msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
I think this too would be nicer to describe with a completion.
Currently it's possible to ask is the dsp is ready, time out and ask again, just to receive the first ack and continue. The service request sleep might then wake up on this previous ack.
If you describe this by two different completions for the two waits you avoid any such race conditions occurring.
- if (rc > 0 && core->resp_received) {
core->resp_received = 0;
if (core->avcs_state == 0x1)
return true;
- }
- return false;
+}
+static int q6core_probe(struct apr_device *adev) +{
- struct q6core *core;
- unsigned long timeout = jiffies +
msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS);
- int ret = 0;
- core = devm_kzalloc(&adev->dev, sizeof(*core), GFP_KERNEL);
- if (!core)
return -ENOMEM;
- dev_set_drvdata(&adev->dev, core);
- core->adev = adev;
- init_waitqueue_head(&core->wait);
- do {
if (!q6core_is_adsp_ready(core)) {
dev_info(&adev->dev, "ADSP Audio isn't ready\n");
} else {
dev_info(&adev->dev, "ADSP Audio is ready\n");
ret = q6core_get_svc_versions(core);
if (!ret)
q6core_add_static_services(core);
break;
}
- } while (time_after(timeout, jiffies));
This would be much better rewritten as:
for (;;) { if (q6core_is_adsp_ready(core)) break;
if (time_after(timeout, jiffies)) return -ETIMEDOUT; }
ret = q6core_get_svc_versions(core); if (ret) return ret;
q6core_add_static_services(core);
- return ret;
+}
+static int q6core_exit(struct apr_device *adev) +{
- return 0;
+}
+static const struct apr_device_id core_id[] = {
- {"Q6CORE", APR_DOMAIN_ADSP, APR_SVC_ADSP_CORE, APR_CLIENT_AUDIO},
- { },
+};
+static struct apr_driver qcom_q6core_driver = {
- .probe = q6core_probe,
- .remove = q6core_exit,
- .callback = core_callback,
- .id_table = core_id,
- .driver = {
.name = "qcom-q6core",
},
Indentation.
+};
+module_apr_driver(qcom_q6core_driver);
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("q6 core");
+MODULE_LICENSE("GPL v2");
2.15.0
Thanks for the review.
On 02/01/18 22:15, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, 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 | 227 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6core.c
+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..d4e2dbc62489
+struct q6core {
- struct apr_device *adev;
- wait_queue_head_t wait;
- uint32_t avcs_state;
- int resp_received;
bool
yep.
- uint32_t num_services;
- struct avcs_svc_info *svcs_info;
+};
+static int core_callback(struct apr_device *adev, struct apr_client_data *data) +{
- struct q6core *core = dev_get_drvdata(&adev->dev);
- uint32_t *payload;
- switch (data->opcode) {
- case AVCS_GET_VERSIONS_RSP:
payload = data->payload;
core->num_services = payload[1];
Describe the payload in a struct (with flexible array member for the svcs_info list).
sure!
if (!core->svcs_info)
core->svcs_info = kcalloc(core->num_services,
sizeof(*core->svcs_info),
GFP_ATOMIC);
if (!core->svcs_info)
return -ENOMEM;
If we receive this twice with different num_services for some reason the memcpy might trash the heap.
But as this is the get_version response and we're only going to issue that once you should remove the check for !core->svcs_info above.
yes, I agree
And don't forget to free svcs_info once you have added your services.
yep.
/* svc info is after 8 bytes */
memcpy(core->svcs_info, payload + 2,
core->num_services * sizeof(*core->svcs_info));
core->resp_received = 1;
wake_up(&core->wait);
break;
- case AVCS_CMDRSP_ADSP_EVENT_GET_STATE:
payload = data->payload;
core->avcs_state = payload[0];
core->resp_received = 1;
wake_up(&core->wait);
break;
- default:
dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n",
data->opcode);
break;
- }
- return 0;
+}
+void q6core_add_service(struct device *dev, uint32_t svc_id, uint32_t version) +{
- int i;
- for (i = 0; i < ARRAY_SIZE(static_services); i++) {
if (static_services[i].svc_id == svc_id) {
static_services[i].svc_version = version;
apr_add_device(dev->parent, &static_services[i]);
dev_info(dev,
Please don't spam the logs, dev_dbg() should be enough. And as apr_add_device() returns you can find the devices registered in /sys
sure!
"Adding SVC: %s: id 0x%x API Ver 0x%x:0x%x\n",
static_services[i].name, svc_id,
APR_SVC_MAJOR_VERSION(version),
APR_SVC_MINOR_VERSION(version));
}
- }
+}
+static void q6core_add_static_services(struct q6core *core)
The name of this function is deceiving, it doesn't really add the static services. It adds devices for the services that we've been informed exists, by the other side - using the static list of services.
Per the comment on a previous patch I don't think the "name" in apr_device_id provides any real value and in this case if forces you to perform a lookup using this table.
If you drop the name, you can loop over the list of service ids returned from the remote and just register them with a hard coded domain id (based on apr instance?) and client_id. You don't need the lookup table.
yes, correct.
+{
- int i;
- struct apr_device *adev = core->adev;
- struct avcs_svc_info *svcs_info = core->svcs_info;
- for (i = 0; i < core->num_services; i++)
q6core_add_service(&adev->dev, svcs_info[i].service_id,
svcs_info[i].version);
+}
+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_PKT_SIZE(APR_HDR_SIZE, 0);
- hdr.opcode = AVCS_GET_VERSIONS;
- rc = apr_send_pkt(adev, (uint32_t *)&hdr);
- if (rc < 0)
return rc;
- rc = wait_event_timeout(core->wait, (core->resp_received == 1),
msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
The wait and resp_received could favourably be expressed as a completion instead, as all we care about is that this happened once.
will give that a try.
- if (rc > 0 && core->resp_received) {
core->resp_received = 0;
return 0;
- }
It wasn't obvious at first sight that this is the success case and the return rc below was the error case...
okay, I can add some comments here to help.
- return rc;
And this will actually be 0 if core->resp_received has not become 1 at the timeout.
yep, will return an error value here.
+}
+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_PKT_SIZE(APR_HDR_SIZE, 0);
- hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE;
- rc = apr_send_pkt(adev, (uint32_t *)&hdr);
- if (rc < 0)
return false;
- rc = wait_event_timeout(core->wait, (core->resp_received == 1),
msecs_to_jiffies(Q6_READY_TIMEOUT_MS));
I think this too would be nicer to describe with a completion.
Currently it's possible to ask is the dsp is ready, time out and ask again, just to receive the first ack and continue. The service request sleep might then wake up on this previous ack.
If you describe this by two different completions for the two waits you avoid any such race conditions occurring.
sure i will make a note of it when I try completions.
- if (rc > 0 && core->resp_received) {
core->resp_received = 0;
if (core->avcs_state == 0x1)
return true;
- }
- return false;
+}
+static int q6core_probe(struct apr_device *adev) +{
- struct q6core *core;
- unsigned long timeout = jiffies +
msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS);
- int ret = 0;
- core = devm_kzalloc(&adev->dev, sizeof(*core), GFP_KERNEL);
- if (!core)
return -ENOMEM;
- dev_set_drvdata(&adev->dev, core);
- core->adev = adev;
- init_waitqueue_head(&core->wait);
- do {
if (!q6core_is_adsp_ready(core)) {
dev_info(&adev->dev, "ADSP Audio isn't ready\n");
} else {
dev_info(&adev->dev, "ADSP Audio is ready\n");
ret = q6core_get_svc_versions(core);
if (!ret)
q6core_add_static_services(core);
break;
}
- } while (time_after(timeout, jiffies));
This would be much better rewritten as:
for (;;) { if (q6core_is_adsp_ready(core)) break;
if (time_after(timeout, jiffies)) return -ETIMEDOUT; }
ret = q6core_get_svc_versions(core); if (ret) return ret;
q6core_add_static_services(core);
Sure.
- return ret;
+}
+static struct apr_driver qcom_q6core_driver = {
- .probe = q6core_probe,
- .remove = q6core_exit,
- .callback = core_callback,
- .id_table = core_id,
- .driver = {
.name = "qcom-q6core",
},
Indentation.
Sure.
On 12/14/2017 11:03 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 | 227 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6core.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 7ebdb879a8a3..121b9c957024 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -56,11 +56,16 @@ 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" 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 49dd3ccab27b..ad7f10691e54 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -1,3 +1,4 @@ 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..d4e2dbc62489 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -0,0 +1,227 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2017, Linaro Limited +*/ +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/jiffies.h>
[..]
- dev_set_drvdata(&adev->dev, core);
- core->adev = adev;
- init_waitqueue_head(&core->wait);
- do {
if (!q6core_is_adsp_ready(core)) {
dev_info(&adev->dev, "ADSP Audio isn't ready\n");
} else {
dev_info(&adev->dev, "ADSP Audio is ready\n");
If q6core_is_adsp_ready() return failure, then we should not call and ADSP API.
ret = q6core_get_svc_versions(core);
if (!ret)
q6core_add_static_services(core);
break;
}
- } while (time_after(timeout, jiffies));
I think we should defer probe if q6core_is_adsp_ready() returns failure and timeouts.
- return ret;
+}
+static int q6core_exit(struct apr_device *adev) +{
- return 0;
+}
+static const struct apr_device_id core_id[] = {
- {"Q6CORE", APR_DOMAIN_ADSP, APR_SVC_ADSP_CORE, APR_CLIENT_AUDIO},
- { },
+};
+static struct apr_driver qcom_q6core_driver = {
- .probe = q6core_probe,
- .remove = q6core_exit,
- .callback = core_callback,
- .id_table = core_id,
- .driver = {
.name = "qcom-q6core",
},
+};
+module_apr_driver(qcom_q6core_driver);
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("q6 core"); +MODULE_LICENSE("GPL v2");
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/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6routing.c | 386 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6routing.h | 9 + 4 files changed, 401 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6routing.c create mode 100644 sound/soc/qcom/qdsp6/q6routing.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 121b9c957024..dd8fb0cde614 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -60,12 +60,17 @@ config SND_SOC_QDSP6_CORE tristate default n
+config SND_SOC_QDSP6_ROUTING + tristate + default n + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" select SND_SOC_QDSP6_AFE select SND_SOC_QDSP6_ADM select SND_SOC_QDSP6_ASM select SND_SOC_QDSP6_CORE + select SND_SOC_QDSP6_ROUTING 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 ad7f10691e54..c1ad060a2341 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -2,3 +2,4 @@ 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 +obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c new file mode 100644 index 000000000000..f5f12d61a1ee --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -0,0 +1,386 @@ +/* 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/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 format; + int perf_mode; + int numcopps; + int fedai_id; + unsigned long copp_map; +}; + +struct msm_routing_data { + struct session_data sessions[MAX_SESSIONS]; + struct device *dev; + struct mutex lock; +}; + +static struct msm_routing_data *routing_data; + +/** + * q6routing_reg_phy_stream() - Register a new stream for route setup + * + * @fedai_id: Frontend dai id. + * @perf_mode: Performace 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_reg_phy_stream(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; + + if (!routing_data) { + pr_err("Routing driver not yet ready\n"); + return -EINVAL; + } + + session = &routing_data->sessions[stream_id - 1]; + mutex_lock(&routing_data->lock); + session->fedai_id = fedai_id; + 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) || (copp_idx >= MAX_COPPS_PER_PORT)) { + mutex_unlock(&routing_data->lock); + return -EINVAL; + } + + set_bit(copp_idx, &session->copp_map); + for (j = 0; j < MAX_COPPS_PER_PORT; j++) { + unsigned long copp = session->copp_map; + + if (test_bit(j, &copp)) { + 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_reg_phy_stream); + +static struct session_data *routing_get_session(struct msm_routing_data *data, + int port_id, int port_type) +{ + int i; + + for (i = 0; i < MAX_SESSIONS; i++) + if (port_id == data->sessions[i].port_id) + return &data->sessions[i]; + + return NULL; +} + +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_dereg_phy_stream() - 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_dereg_phy_stream(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_dereg_phy_stream); + +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 = snd_soc_platform_get_drvdata(platform); + 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 = snd_soc_platform_get_drvdata(platform); + 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 */ + /* Widget name equals to Front-End DAI name<Need confirmation>, + * Stream name must contains substring of front-end dai name + */ + 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", NULL, "HDMI Mixer"}, + {"HDMI-RX", NULL, "HDMI"}, +}; + +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 = snd_soc_platform_get_drvdata(platform); + struct session_data *session; + int port_id, port_type, path_type, bits_per_sample; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + path_type = ADM_PATH_PLAYBACK; + port_type = MSM_AFE_PORT_TYPE_RX; + } + + port_id = be_id; + + session = routing_get_session(data, port_id, port_type); + + if (!session) { + pr_err("No session matrix setup yet..\n"); + return -EINVAL; + } + + mutex_lock(&data->lock); + + session->path_type = path_type; + session->sample_rate = params_rate(params); + session->channels = params_channels(params); + session->format = params_format(params); + + if (session->format == SNDRV_PCM_FORMAT_S16_LE) + session->bits_per_sample = 16; + else if (session->format == SNDRV_PCM_FORMAT_S24_LE) + bits_per_sample = 24; + + mutex_unlock(&data->lock); + return 0; +} + +static int routing_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int routing_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_pcm_ops q6pcm_routing_ops = { + .hw_params = routing_hw_params, + .close = routing_close, + .prepare = routing_prepare, +}; + +/* Not used but frame seems to require it */ +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; + + snd_soc_platform_set_drvdata(platform, routing_data); + + return 0; +} + +static struct snd_soc_platform_driver msm_soc_routing_platform = { + .ops = &q6pcm_routing_ops, + .probe = msm_routing_probe, + .component_driver = { + .dapm_widgets = msm_qdsp6_widgets, + .num_dapm_widgets = ARRAY_SIZE(msm_qdsp6_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + }, +}; + +static int q6pcm_routing_probe(struct platform_device *pdev) +{ + + routing_data = devm_kzalloc(&pdev->dev, + sizeof(*routing_data), GFP_KERNEL); + if (!routing_data) + return -ENOMEM; + + routing_data->dev = &pdev->dev; + + mutex_init(&routing_data->lock); + dev_set_drvdata(&pdev->dev, routing_data); + + return devm_snd_soc_register_platform(&pdev->dev, + &msm_soc_routing_platform); +} + +static int q6pcm_routing_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver q6pcm_routing_driver = { + .driver = { + .name = "q6routing", + .owner = THIS_MODULE, + }, + .probe = q6pcm_routing_probe, + .remove = q6pcm_routing_remove, +}; + +module_platform_driver(q6pcm_routing_driver); + +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..7f0feb196acc --- /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_reg_phy_stream(int fedai_id, int perf_mode, + int stream_id, int stream_type); +void q6routing_dereg_phy_stream(int fedai_id, int stream_type); + +#endif /*_Q6_PCM_ROUTING_H */
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
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.
Why is this a separate driver from the q6adm?
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
sound/soc/qcom/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6routing.c | 386 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6routing.h | 9 + 4 files changed, 401 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6routing.c create mode 100644 sound/soc/qcom/qdsp6/q6routing.h
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 121b9c957024..dd8fb0cde614 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -60,12 +60,17 @@ config SND_SOC_QDSP6_CORE tristate default n
+config SND_SOC_QDSP6_ROUTING
- tristate
- default n
config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" select SND_SOC_QDSP6_AFE select SND_SOC_QDSP6_ADM select SND_SOC_QDSP6_ASM select SND_SOC_QDSP6_CORE
- select SND_SOC_QDSP6_ROUTING 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 ad7f10691e54..c1ad060a2341 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -2,3 +2,4 @@ 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 +obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c new file mode 100644 index 000000000000..f5f12d61a1ee --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -0,0 +1,386 @@ +/* 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/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 format;
- int perf_mode;
- int numcopps;
- int fedai_id;
- unsigned long copp_map;
+};
+struct msm_routing_data {
- struct session_data sessions[MAX_SESSIONS];
- struct device *dev;
- struct mutex lock;
+};
+static struct msm_routing_data *routing_data;
+/**
- q6routing_reg_phy_stream() - Register a new stream for route setup
- @fedai_id: Frontend dai id.
- @perf_mode: Performace mode.
"Performance"
- @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_reg_phy_stream(int fedai_id, int perf_mode,
q6routing_stream_open() ?
int stream_id, int stream_type)
+{
- int j, topology, num_copps = 0;
- struct route_payload payload;
- int copp_idx;
- struct session_data *session;
- if (!routing_data) {
pr_err("Routing driver not yet ready\n");
return -EINVAL;
- }
- session = &routing_data->sessions[stream_id - 1];
- mutex_lock(&routing_data->lock);
- session->fedai_id = fedai_id;
- 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) || (copp_idx >= MAX_COPPS_PER_PORT)) {
Make q6adm_open() not return >= MAX_COPPS_PER_PORT.
And drop the extra parenthesis.
mutex_unlock(&routing_data->lock);
return -EINVAL;
- }
- set_bit(copp_idx, &session->copp_map);
- for (j = 0; j < MAX_COPPS_PER_PORT; j++) {
Use for_each_set_bit()
unsigned long copp = session->copp_map;
if (test_bit(j, &copp)) {
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_reg_phy_stream);
+static struct session_data *routing_get_session(struct msm_routing_data *data,
int port_id, int port_type)
port_type is ignored
+{
- int i;
- for (i = 0; i < MAX_SESSIONS; i++)
if (port_id == data->sessions[i].port_id)
return &data->sessions[i];
- return NULL;
+}
+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_dereg_phy_stream() - 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_dereg_phy_stream(int fedai_id, int stream_type)
q6routing_stream_close()?
+{
- 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_dereg_phy_stream);
+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 = snd_soc_platform_get_drvdata(platform);
- 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 = snd_soc_platform_get_drvdata(platform);
- 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 */
- /* Widget name equals to Front-End DAI name<Need confirmation>,
Perhaps this should be confirmed and the comment updated?
* Stream name must contains substring of front-end dai name
*/
- 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", NULL, "HDMI Mixer"},
- {"HDMI-RX", NULL, "HDMI"},
+};
+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 = snd_soc_platform_get_drvdata(platform);
- struct session_data *session;
- int port_id, port_type, path_type, bits_per_sample;
bits_per_sample is likely unused.
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
path_type = ADM_PATH_PLAYBACK;
port_type = MSM_AFE_PORT_TYPE_RX;
- }
- port_id = be_id;
Why alias this variable?
- session = routing_get_session(data, port_id, port_type);
- if (!session) {
pr_err("No session matrix setup yet..\n");
return -EINVAL;
- }
- mutex_lock(&data->lock);
- session->path_type = path_type;
- session->sample_rate = params_rate(params);
- session->channels = params_channels(params);
- session->format = params_format(params);
- if (session->format == SNDRV_PCM_FORMAT_S16_LE)
session->bits_per_sample = 16;
- else if (session->format == SNDRV_PCM_FORMAT_S24_LE)
bits_per_sample = 24;
session-> ?
Perhaps switch on params_format(params) instead? And fail in the default case.
- mutex_unlock(&data->lock);
- return 0;
+}
+static int routing_close(struct snd_pcm_substream *substream) +{
- return 0;
+}
+static int routing_prepare(struct snd_pcm_substream *substream) +{
- return 0;
+}
+static struct snd_pcm_ops q6pcm_routing_ops = {
- .hw_params = routing_hw_params,
- .close = routing_close,
- .prepare = routing_prepare,
+};
+/* Not used but frame seems to require it */
Remove comment?
+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;
- snd_soc_platform_set_drvdata(platform, routing_data);
- return 0;
+}
+static struct snd_soc_platform_driver msm_soc_routing_platform = {
- .ops = &q6pcm_routing_ops,
- .probe = msm_routing_probe,
- .component_driver = {
.dapm_widgets = msm_qdsp6_widgets,
.num_dapm_widgets = ARRAY_SIZE(msm_qdsp6_widgets),
.dapm_routes = intercon,
.num_dapm_routes = ARRAY_SIZE(intercon),
},
+};
+static int q6pcm_routing_probe(struct platform_device *pdev) +{
- routing_data = devm_kzalloc(&pdev->dev,
sizeof(*routing_data), GFP_KERNEL);
- if (!routing_data)
return -ENOMEM;
- routing_data->dev = &pdev->dev;
- mutex_init(&routing_data->lock);
- dev_set_drvdata(&pdev->dev, routing_data);
- return devm_snd_soc_register_platform(&pdev->dev,
&msm_soc_routing_platform);
+}
+static int q6pcm_routing_remove(struct platform_device *pdev) +{
As you return here routing_data will be freed. The early check in q6routing_reg_phy_stream() seems to indicate that this driver can be called even though the routing device isn't available.
So you probably want to clear that variable, at least.
- return 0;
+}
+static struct platform_driver q6pcm_routing_driver = {
- .driver = {
.name = "q6routing",
.owner = THIS_MODULE,
Drop .owner
},
- .probe = q6pcm_routing_probe,
- .remove = q6pcm_routing_remove,
+};
+module_platform_driver(q6pcm_routing_driver);
+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..7f0feb196acc --- /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_reg_phy_stream(int fedai_id, int perf_mode,
int stream_id, int stream_type);
+void q6routing_dereg_phy_stream(int fedai_id, int stream_type);
+#endif /*_Q6_PCM_ROUTING_H */
2.15.0
On 02/01/18 23:00, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
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.
Why is this a separate driver from the q6adm?
This is actually exposing the mixer controls for routing streams on to different audio sink/sources using a matrix.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
sound/soc/qcom/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6routing.c | 386 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6routing.h | 9 + 4 files changed, 401 insertions(+) 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/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c
+/**
- q6routing_reg_phy_stream() - Register a new stream for route setup
- @fedai_id: Frontend dai id.
- @perf_mode: Performace mode.
"Performance"
yep.
- @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_reg_phy_stream(int fedai_id, int perf_mode,
q6routing_stream_open() ?
sure if it helps reading.
int stream_id, int stream_type)
+{
- int j, topology, num_copps = 0;
- struct route_payload payload;
- int copp_idx;
- struct session_data *session;
- if (!routing_data) {
pr_err("Routing driver not yet ready\n");
return -EINVAL;
- }
- session = &routing_data->sessions[stream_id - 1];
- mutex_lock(&routing_data->lock);
- session->fedai_id = fedai_id;
- 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) || (copp_idx >= MAX_COPPS_PER_PORT)) {
Make q6adm_open() not return >= MAX_COPPS_PER_PORT.
And drop the extra parenthesis.
mutex_unlock(&routing_data->lock);
return -EINVAL;
- }
- set_bit(copp_idx, &session->copp_map);
- for (j = 0; j < MAX_COPPS_PER_PORT; j++) {
Use for_each_set_bit()
unsigned long copp = session->copp_map;
if (test_bit(j, &copp)) {
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_reg_phy_stream);
+static struct session_data *routing_get_session(struct msm_routing_data *data,
int port_id, int port_type)
port_type is ignored
It will be used once we add capture support.
+{
- int i;
- for (i = 0; i < MAX_SESSIONS; i++)
if (port_id == data->sessions[i].port_id)
return &data->sessions[i];
- return NULL;
+}
+/**
- q6routing_dereg_phy_stream() - 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_dereg_phy_stream(int fedai_id, int stream_type)
q6routing_stream_close()?
will rename it.
+{
- 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_dereg_phy_stream);
+static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = {
- /* Frontend AIF */
- /* Widget name equals to Front-End DAI name<Need confirmation>,
Perhaps this should be confirmed and the comment updated?
Some leftover from old code...
* Stream name must contains substring of front-end dai name
*/
- 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 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 = snd_soc_platform_get_drvdata(platform);
- struct session_data *session;
- int port_id, port_type, path_type, bits_per_sample;
bits_per_sample is likely unused.
yep.
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
path_type = ADM_PATH_PLAYBACK;
port_type = MSM_AFE_PORT_TYPE_RX;
- }
- port_id = be_id;
Why alias this variable?
will fix this in next version.
- session = routing_get_session(data, port_id, port_type);
- if (!session) {
pr_err("No session matrix setup yet..\n");
return -EINVAL;
- }
- mutex_lock(&data->lock);
- session->path_type = path_type;
- session->sample_rate = params_rate(params);
- session->channels = params_channels(params);
- session->format = params_format(params);
- if (session->format == SNDRV_PCM_FORMAT_S16_LE)
session->bits_per_sample = 16;
- else if (session->format == SNDRV_PCM_FORMAT_S24_LE)
bits_per_sample = 24;
session-> ?
I agree, will fix it.
Perhaps switch on params_format(params) instead? And fail in the default case.
will give that a go.
- mutex_unlock(&data->lock);
- return 0;
+}
+static struct snd_pcm_ops q6pcm_routing_ops = {
- .hw_params = routing_hw_params,
- .close = routing_close,
- .prepare = routing_prepare,
+};
+/* Not used but frame seems to require it */
Remove comment?
looks like leftover.. will remove it.
[...]
+static int q6pcm_routing_remove(struct platform_device *pdev) +{
As you return here routing_data will be freed. The early check in q6routing_reg_phy_stream() seems to indicate that this driver can be called even though the routing device isn't available.
So you probably want to clear that variable, at least.
sure, will fix this in next version.
- return 0;
+}
+static struct platform_driver q6pcm_routing_driver = {
- .driver = {
.name = "q6routing",
.owner = THIS_MODULE,
Drop .owner
yep.
},
- .probe = q6pcm_routing_probe,
- .remove = q6pcm_routing_remove,
+};
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/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6afe-dai.c | 241 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index dd8fb0cde614..003ce182691c 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -64,6 +64,10 @@ config SND_SOC_QDSP6_ROUTING tristate default n
+config SND_SOC_QDSP6_AFE_DAI + tristate + default n + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" select SND_SOC_QDSP6_AFE @@ -71,6 +75,7 @@ config SND_SOC_QDSP6 select SND_SOC_QDSP6_ASM select SND_SOC_QDSP6_CORE select SND_SOC_QDSP6_ROUTING + select SND_SOC_QDSP6_AFE_DAI 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 c1ad060a2341..bd8bd02bf09e 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.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..e9865c684bcb --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -0,0 +1,241 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, 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 q6hdmi_dai_data { + struct q6afe_port *port; + struct q6afe_hdmi_cfg port_config; + bool is_port_started; +}; + +static int q6hdmi_format_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct q6hdmi_dai_data *dai_data = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + + dai_data->port_config.datatype = value; + + return 0; +} + +static int q6hdmi_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct q6hdmi_dai_data *dai_data = kcontrol->private_data; + + ucontrol->value.integer.value[0] = + dai_data->port_config.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 hdmi_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 q6hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + int channels = params_channels(params); + + dai_data->port_config.sample_rate = params_rate(params); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + dai_data->port_config.bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + dai_data->port_config.bit_width = 24; + break; + } + + /*refer to HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4*/ + switch (channels) { + case 2: + dai_data->port_config.channel_allocation = 0; + break; + case 3: + dai_data->port_config.channel_allocation = 0x02; + break; + case 4: + dai_data->port_config.channel_allocation = 0x06; + break; + case 5: + dai_data->port_config.channel_allocation = 0x0A; + break; + case 6: + dai_data->port_config.channel_allocation = 0x0B; + break; + case 7: + dai_data->port_config.channel_allocation = 0x12; + break; + case 8: + dai_data->port_config.channel_allocation = 0x13; + break; + default: + dev_err(dai->dev, "invalid Channels = %u\n", channels); + return -EINVAL; + } + + return 0; +} + +static int q6hdmi_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->is_port_started = false; + + return 0; +} + +static void q6hdmi_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc; + + rc = q6afe_port_stop(dai_data->port); + if (rc < 0) + dev_err(dai->dev, "fail to close AFE port\n"); + + dai_data->is_port_started = false; + +} + +static int q6hdmi_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6hdmi_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc; + + if (dai_data->is_port_started) { + /* stop the port and restart with new port config */ + rc = q6afe_port_stop(dai_data->port); + if (rc < 0) { + dev_err(dai->dev, "fail to close AFE port\n"); + return rc; + } + } + + q6afe_hdmi_port_prepare(dai_data->port, &dai_data->port_config); + rc = q6afe_port_start(dai_data->port); + if (rc < 0) { + dev_err(dai->dev, "fail to start AFE port %x\n", dai->id); + return rc; + } + dai_data->is_port_started = true; + + return 0; +} + +static const struct snd_soc_dapm_route hdmi_dapm_routes[] = { + {"HDMI Playback", NULL, "HDMI"}, +}; + +static struct snd_soc_dai_ops q6hdmi_ops = { + .prepare = q6hdmi_prepare, + .hw_params = q6hdmi_hw_params, + .shutdown = q6hdmi_shutdown, + .startup = q6hdmi_startup, +}; + +static struct snd_soc_dai_driver q6afe_dai_hdmi_rx = { + .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", +}; + +static const struct snd_soc_dapm_widget hdmi_dapm_widgets[] = { + SND_SOC_DAPM_AIF_OUT("HDMI", "HDMI Playback", 0, 0, 0, 0), + SND_SOC_DAPM_OUTPUT("HDMI-RX"), +}; + +static const struct snd_soc_component_driver msm_dai_hdmi_q6_component = { + .name = "msm-dai-q6-hdmi", + .dapm_widgets = hdmi_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(hdmi_dapm_widgets), + .controls = hdmi_config_controls, + .num_controls = ARRAY_SIZE(hdmi_config_controls), + .dapm_routes = hdmi_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(hdmi_dapm_routes), +}; + +static int q6afe_dai_dev_probe(struct platform_device *pdev) +{ + struct q6hdmi_dai_data *dai_data; + int rc = 0; + struct q6afe_port *port; + + dai_data = devm_kzalloc(&pdev->dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + rc = -ENOMEM; + + port = q6afe_port_get_from_id(&pdev->dev, AFE_PORT_HDMI_RX); + if (IS_ERR(port)) { + dev_err(&pdev->dev, "Unable to get afe port\n"); + return -EPROBE_DEFER; + } + dai_data->port = port; + dev_set_drvdata(&pdev->dev, dai_data); + + return devm_snd_soc_register_component(&pdev->dev, + &msm_dai_hdmi_q6_component, + &q6afe_dai_hdmi_rx, 1); +} + +static int q6afe_dai_dev_remove(struct platform_device *pdev) +{ + struct q6hdmi_dai_data *dai_data = dev_get_drvdata(&pdev->dev); + + q6afe_port_put(dai_data->port); + + return 0; +} + +static struct platform_driver q6afe_dai_driver = { + .probe = q6afe_dai_dev_probe, + .remove = q6afe_dai_dev_remove, + .driver = { + .name = "q6afe_dai", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(q6afe_dai_driver);
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6afe backend dais driver.
Isn't the list of backend DAIs platform-dependent?
[..]
+static const struct snd_soc_dapm_widget hdmi_dapm_widgets[] = {
- SND_SOC_DAPM_AIF_OUT("HDMI", "HDMI Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_OUTPUT("HDMI-RX"),
+};
+static const struct snd_soc_component_driver msm_dai_hdmi_q6_component = {
How will this look beyond HDMI? I'm having issues mapping this to downstream.
- .name = "msm-dai-q6-hdmi",
- .dapm_widgets = hdmi_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(hdmi_dapm_widgets),
- .controls = hdmi_config_controls,
- .num_controls = ARRAY_SIZE(hdmi_config_controls),
- .dapm_routes = hdmi_dapm_routes,
- .num_dapm_routes = ARRAY_SIZE(hdmi_dapm_routes),
+};
Regards, Bjorn
On 02/01/18 23:28, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6afe backend dais driver.
Isn't the list of backend DAIs platform-dependent?
dai links and connections between backend and front ends are platform dependent.
[..]
+static const struct snd_soc_dapm_widget hdmi_dapm_widgets[] = {
- SND_SOC_DAPM_AIF_OUT("HDMI", "HDMI Playback", 0, 0, 0, 0),
- SND_SOC_DAPM_OUTPUT("HDMI-RX"),
+};
+static const struct snd_soc_component_driver msm_dai_hdmi_q6_component = {
How will this look beyond HDMI? I'm having issues mapping this to downstream.
ex: For slimbus dais, we would have more entries of "struct snd_soc_dai_driver" in this file.
Basically these are the dais that are exposed by the dsp firmware.
Depending on the actually platform some of these dais would be setup accordingly.
- .name = "msm-dai-q6-hdmi",
- .dapm_widgets = hdmi_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(hdmi_dapm_widgets),
- .controls = hdmi_config_controls,
- .num_controls = ARRAY_SIZE(hdmi_config_controls),
- .dapm_routes = hdmi_dapm_routes,
- .num_dapm_routes = ARRAY_SIZE(hdmi_dapm_routes),
+};
Regards, Bjorn
On 12/14/2017 11:03 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/Kconfig | 5 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6afe-dai.c | 241 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6afe-dai.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index dd8fb0cde614..003ce182691c 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -64,6 +64,10 @@ config SND_SOC_QDSP6_ROUTING tristate default n
+config SND_SOC_QDSP6_AFE_DAI
- tristate
- default n
- config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" select SND_SOC_QDSP6_AFE
@@ -71,6 +75,7 @@ config SND_SOC_QDSP6 select SND_SOC_QDSP6_ASM select SND_SOC_QDSP6_CORE select SND_SOC_QDSP6_ROUTING
- select SND_SOC_QDSP6_AFE_DAI 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 c1ad060a2341..bd8bd02bf09e 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.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..e9865c684bcb --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -0,0 +1,241 @@ +/* SPDX-License-Identifier: GPL-2.0 +* Copyright (c) 2011-2016, The Linux Foundation +* Copyright (c) 2017, 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 q6hdmi_dai_data {
- struct q6afe_port *port;
- struct q6afe_hdmi_cfg port_config;
- bool is_port_started;
+};
+static int q6hdmi_format_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct q6hdmi_dai_data *dai_data = kcontrol->private_data;
- int value = ucontrol->value.integer.value[0];
- dai_data->port_config.datatype = value;
....
+static struct platform_driver q6afe_dai_driver = {
- .probe = q6afe_dai_dev_probe,
- .remove = q6afe_dai_dev_remove,
- .driver = {
.name = "q6afe_dai",
.owner = THIS_MODULE,
- },
+};
+module_platform_driver(q6afe_dai_driver);
MODULE_LICENSE missing.
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to q6asm dai driver which configures Q6ASM streams to pass pcm data. Currently the driver only exposes 2 playback streams for hdmi playback support, it can be easily extended to add all 8 streams.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 6 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6asm-dai.c | 534 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 541 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6asm-dai.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 003ce182691c..ecd1e4ba834d 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -68,6 +68,11 @@ config SND_SOC_QDSP6_AFE_DAI tristate default n
+config SND_SOC_QDSP6_ASM_DAI + tristate + default n + + config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" select SND_SOC_QDSP6_AFE @@ -76,6 +81,7 @@ config SND_SOC_QDSP6 select SND_SOC_QDSP6_CORE select SND_SOC_QDSP6_ROUTING select SND_SOC_QDSP6_AFE_DAI + select SND_SOC_QDSP6_ASM_DAI 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 bd8bd02bf09e..03576a442fb5 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o +obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.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..709c5de230fa --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -0,0 +1,534 @@ +/* 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 "common.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 { + IDLE = 0, + STOPPED, + RUNNING, +}; + +struct q6asm_dai_rtd { + struct snd_pcm_substream *substream; + dma_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; + bool set_channel_map; + char channel_map[8]; +}; + +struct q6asm_dai_data { + u64 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_nolock(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + break; + case ASM_CLIENT_EVENT_CMD_EOS_DONE: + prtd->state = STOPPED; + break; + case ASM_CLIENT_EVENT_DATA_WRITE_DONE: { + prtd->pcm_irq_pos += prtd->pcm_count; + snd_pcm_period_elapsed(substream); + if (prtd->state == RUNNING) + q6asm_write_nolock(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 = dev_get_drvdata(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_dereg_phy_stream(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_reg_phy_stream(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, !prtd->set_channel_map, + prtd->channel_map, prtd->bits_per_sample); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + + prtd->state = 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 = 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 q6asm_dai_rtd *prtd; + struct q6asm_dai_data *pdata; + struct device *dev = soc_prtd->platform->dev; + int ret = 0; + + pdata = dev_get_drvdata(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, + (app_cb)event_handler, prtd); + 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); + } + + prtd->set_channel_map = false; + 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_dereg_phy_stream(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 snd_card *card = soc_prtd->card->snd_card; + + return dma_mmap_coherent(card->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 *pcm = rtd->pcm; + struct snd_pcm_substream *substream; + struct snd_card *card = rtd->card->snd_card; + struct device *dev = card->dev; + struct device_node *node = dev->of_node; + struct q6asm_dai_data *pdata = dev_get_drvdata(rtd->platform->dev); + struct of_phandle_args args; + + int size, ret = 0; + + 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; + } + + 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" }, + +}; + +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 = "MM_DL1", + .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 = "MM_DL2", + .probe = fe_dai_probe, + .id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, +}; + +static int q6asm_dai_probe(struct platform_device *pdev) +{ + struct q6asm_dai_data *pdata; + struct device *dev = &pdev->dev; + int rc; + + pdata = devm_kzalloc(dev, sizeof(struct q6asm_dai_data), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + + dev_set_drvdata(dev, pdata); + + rc = snd_soc_register_platform(dev, &q6asm_soc_platform); + if (rc) { + dev_err(&pdev->dev, "err_dai_platform\n"); + return rc; + } + + rc = snd_soc_register_component(dev, &q6asm_fe_dai_component, + q6asm_fe_dais, + ARRAY_SIZE(q6asm_fe_dais)); + if (rc) + dev_err(dev, "err_dai_component\n"); + + return rc; + +} + +static int q6asm_dai_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver q6asm_dai_driver = { + .driver = { + .name = "q6asm_dai", + .owner = THIS_MODULE, + }, + .probe = q6asm_dai_probe, + .remove = q6asm_dai_remove, +}; + +module_platform_driver(q6asm_dai_driver); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2");
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
[..]
+enum stream_state {
- IDLE = 0,
- STOPPED,
- RUNNING,
These are too generic.
+};
+struct q6asm_dai_rtd {
- struct snd_pcm_substream *substream;
- dma_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;
- bool set_channel_map;
- char channel_map[8];
There's a define for this 8
+};
+struct q6asm_dai_data {
- u64 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,
If you just put the numbers here, instead of using the PLAYBACK_ defines, it's possible to grok the values of this struct without having to jump to the defines for each one.
- .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 = {
This is unreferenced.
- .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_nolock(prtd->audio_client,
prtd->pcm_count, 0, 0, NO_TIMESTAMP);
break;
- case ASM_CLIENT_EVENT_CMD_EOS_DONE:
prtd->state = STOPPED;
break;
- case ASM_CLIENT_EVENT_DATA_WRITE_DONE: {
prtd->pcm_irq_pos += prtd->pcm_count;
snd_pcm_period_elapsed(substream);
if (prtd->state == RUNNING)
q6asm_write_nolock(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 = dev_get_drvdata(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_dereg_phy_stream(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;
Do you need to roll back the q6asm_map_memory_regions?
return -ENOMEM;
- }
- prtd->session_id = q6asm_get_session_id(prtd->audio_client);
- ret = q6routing_reg_phy_stream(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, !prtd->set_channel_map,
prtd->channel_map, prtd->bits_per_sample);
set_channel_map and channel_map aren't referenced elsewhere. If this isn't used consider removing it for now.
- if (ret < 0)
pr_info("%s: CMD Format block failed\n", __func__);
- prtd->state = RUNNING;
- return 0;
+}
[..]
+static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd) +{
- struct snd_pcm *pcm = rtd->pcm;
- struct snd_pcm_substream *substream;
- struct snd_card *card = rtd->card->snd_card;
- struct device *dev = card->dev;
- struct device_node *node = dev->of_node;
- struct q6asm_dai_data *pdata = dev_get_drvdata(rtd->platform->dev);
- struct of_phandle_args args;
- int size, ret = 0;
- ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
- if (ret < 0)
pdata->sid = -1;
- else
pdata->sid = args.args[0];
Is this really how you're supposed to deal with the iommu?
- 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;
Just fall through.
- }
- return ret;
+}
[..]
+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 = "MM_DL1",
.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,
I presume the listed frontend DAIs needs to match the firmware of the DSP (and features of hardware)? Can we get away with a single list for all versions of the adsp?
In msm-4.4 the max rate for these where changed to 384000, see:
9c46f74b2724 ("ASoC: msm: add 384KHz playback support")
},
.name = "MM_DL2",
.probe = fe_dai_probe,
.id = MSM_FRONTEND_DAI_MULTIMEDIA2,
- },
+};
Regards, Bjorn
Thanks for the comments.
On 03/01/18 00:03, Bjorn Andersson wrote:
On Thu 14 Dec 09:33 PST 2017, srinivas.kandagatla@linaro.org wrote:
[..]
+enum stream_state {
- IDLE = 0,
- STOPPED,
- RUNNING,
These are too generic.
Yep, I will prefix them with Q6ASM.
+};
+struct q6asm_dai_rtd {
- struct snd_pcm_substream *substream;
- dma_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;
- bool set_channel_map;
- char channel_map[8];
There's a define for this 8
Yes, this is max channels.
+};
+struct q6asm_dai_data {
- u64 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,
If you just put the numbers here, instead of using the PLAYBACK_ defines, it's possible to grok the values of this struct without having to jump to the defines for each one.
This is usually done this way in may other drivers!,
- .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 = {
It is used in q6asm_dai_open().
- .count = ARRAY_SIZE(supported_sample_rates),
- .list = supported_sample_rates,
- .mask = 0,
+};
+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 = dev_get_drvdata(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_dereg_phy_stream(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;
Do you need to roll back the q6asm_map_memory_regions?
yes you are correct, we should roll back the map.
return -ENOMEM;
- }
- prtd->session_id = q6asm_get_session_id(prtd->audio_client);
- ret = q6routing_reg_phy_stream(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, !prtd->set_channel_map,
prtd->channel_map, prtd->bits_per_sample);
set_channel_map and channel_map aren't referenced elsewhere. If this isn't used consider removing it for now.
Will take a closer look before sending next version.
- if (ret < 0)
pr_info("%s: CMD Format block failed\n", __func__);
- prtd->state = RUNNING;
- return 0;
+}
[..]
+static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd) +{
- struct snd_pcm *pcm = rtd->pcm;
- struct snd_pcm_substream *substream;
- struct snd_card *card = rtd->card->snd_card;
- struct device *dev = card->dev;
- struct device_node *node = dev->of_node;
- struct q6asm_dai_data *pdata = dev_get_drvdata(rtd->platform->dev);
- struct of_phandle_args args;
- int size, ret = 0;
- ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args);
- if (ret < 0)
pdata->sid = -1;
- else
pdata->sid = args.args[0];
Is this really how you're supposed to deal with the iommu?
Any suggestions are welcome, I did not find a better way to append sid to iova address from iommu.
Currently downstream abstracts this in ion apis.
- 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;
Just fall through.
yep
- }
- return ret;
+}
[..]
+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 = "MM_DL1",
.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,
I presume the listed frontend DAIs needs to match the firmware of the DSP (and features of hardware)? Can we get away with a single list for all versions of the adsp?
Yes, DSP supports 8 concurrent streams both playback and record streams.
For now I have only added two entires to keep the patch simple but this should be ideally 8 entries.
In msm-4.4 the max rate for these where changed to 384000, see:
9c46f74b2724 ("ASoC: msm: add 384KHz playback support")
sure i will include that in next version.
},
.name = "MM_DL2",
.probe = fe_dai_probe,
.id = MSM_FRONTEND_DAI_MULTIMEDIA2,
- },
+};
Regards, Bjorn
On 12/14/2017 11:03 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. Currently the driver only exposes 2 playback streams for hdmi playback support, it can be easily extended to add all 8 streams.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
sound/soc/qcom/Kconfig | 6 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6asm-dai.c | 534 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 541 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6asm-dai.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index 003ce182691c..ecd1e4ba834d 100644 --- a/sound/soc/qcom/Kconfig
[..]
+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_nolock(prtd->audio_client,
prtd->pcm_count, 0, 0, NO_TIMESTAMP);
break;
- case ASM_CLIENT_EVENT_CMD_EOS_DONE:
prtd->state = STOPPED;
break;
Add support for V2 version of opcode to support latest adsp
- case ASM_CLIENT_EVENT_DATA_WRITE_DONE: {
prtd->pcm_irq_pos += prtd->pcm_count;
snd_pcm_period_elapsed(substream);
if (prtd->state == RUNNING)
q6asm_write_nolock(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 = dev_get_drvdata(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_dereg_phy_stream(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_reg_phy_stream(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, !prtd->set_channel_map,
prtd->channel_map, prtd->bits_per_sample);
- if (ret < 0)
pr_info("%s: CMD Format block failed\n", __func__);
- prtd->state = 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 = 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 q6asm_dai_rtd *prtd;
- struct q6asm_dai_data *pdata;
- struct device *dev = soc_prtd->platform->dev;
- int ret = 0;
- pdata = dev_get_drvdata(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,
(app_cb)event_handler, prtd);
- 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);
- }
- prtd->set_channel_map = false;
- 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_dereg_phy_stream(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 snd_card *card = soc_prtd->card->snd_card;
- return dma_mmap_coherent(card->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 *pcm = rtd->pcm;
- struct snd_pcm_substream *substream;
- struct snd_card *card = rtd->card->snd_card;
- struct device *dev = card->dev;
- struct device_node *node = dev->of_node;
- struct q6asm_dai_data *pdata = dev_get_drvdata(rtd->platform->dev);
- struct of_phandle_args args;
- int size, ret = 0;
- 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;
- }
- 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" },
+};
+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 = "MM_DL1",
.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 = "MM_DL2",
.probe = fe_dai_probe,
.id = MSM_FRONTEND_DAI_MULTIMEDIA2,
- },
+};
+static int q6asm_dai_probe(struct platform_device *pdev) +{
- struct q6asm_dai_data *pdata;
- struct device *dev = &pdev->dev;
- int rc;
- pdata = devm_kzalloc(dev, sizeof(struct q6asm_dai_data), GFP_KERNEL);
- if (!pdata)
return -ENOMEM;
- dev_set_drvdata(dev, pdata);
- rc = snd_soc_register_platform(dev, &q6asm_soc_platform);
- if (rc) {
dev_err(&pdev->dev, "err_dai_platform\n");
return rc;
- }
- rc = snd_soc_register_component(dev, &q6asm_fe_dai_component,
q6asm_fe_dais,
ARRAY_SIZE(q6asm_fe_dais));
- if (rc)
dev_err(dev, "err_dai_component\n");
- return rc;
+}
+static int q6asm_dai_remove(struct platform_device *pdev) +{
- snd_soc_unregister_platform(&pdev->dev);
- return 0;
+}
+static struct platform_driver q6asm_dai_driver = {
- .driver = {
.name = "q6asm_dai",
.owner = THIS_MODULE,
- },
- .probe = q6asm_dai_probe,
- .remove = q6asm_dai_remove,
+};
+module_platform_driver(q6asm_dai_driver);
+MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2");
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 | 22 ++++++++++++++++++++++ 1 file changed, 22 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..27b511dab533 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt @@ -0,0 +1,22 @@ +* 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: +Example: + sound { + compatible = "qcom,snd-apq8096"; + qcom,model = "DB820c"; + };
On Thu, Dec 14, 2017 at 05:34:00PM +0000, srinivas.kandagatla@linaro.org wrote:
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 | 22 ++++++++++++++++++++++ 1 file changed, 22 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..27b511dab533 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt @@ -0,0 +1,22 @@ +* 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:
+Example:
- sound {
compatible = "qcom,snd-apq8096";
qcom,model = "DB820c";
Not documented, but just use "model".
This doesn't look complete. No codec, etc.?
Rob
Thanks for your review comments.
On 16/12/17 17:44, Rob Herring wrote:
On Thu, Dec 14, 2017 at 05:34:00PM +0000, srinivas.kandagatla@linaro.org wrote:
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 | 22 ++++++++++++++++++++++ 1 file changed, 22 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..27b511dab533 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt @@ -0,0 +1,22 @@ +* 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:
+Example:
- sound {
compatible = "qcom,snd-apq8096";
qcom,model = "DB820c";
Not documented, but just use "model".
Yep, I will use that in next version.
This doesn't look complete. No codec, etc.?
All the dai links are done in non-DT way directly in the sound card driver.
Thanks, Srini
Rob
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
+++ b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt
Wouldn't it be possible to describe all(?) qdsp based machines in this one document? I.e. should we name it a little bit more generic?
@@ -0,0 +1,22 @@ +* 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.
Double space before A
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:
+Example:
- sound {
compatible = "qcom,snd-apq8096";
Indentation
qcom,model = "DB820c";
- };
Regards, Bjorn
On 03/01/18 00:28, Bjorn Andersson wrote:
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
+++ b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt
Wouldn't it be possible to describe all(?) qdsp based machines in this one document? I.e. should we name it a little bit more generic?
You mean like downstream ?
I see no harm in trying it out and see how it looks like.
@@ -0,0 +1,22 @@ +* 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.
Double space before A
yep.
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:
+Example:
- sound {
compatible = "qcom,snd-apq8096";
Indentation
yep.
qcom,model = "DB820c";
- };
Regards, Bjorn
On Wed 03 Jan 08:27 PST 2018, Srinivas Kandagatla wrote:
On 03/01/18 00:28, Bjorn Andersson wrote:
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
+++ b/Documentation/devicetree/bindings/sound/qcom,apq8096.txt
Wouldn't it be possible to describe all(?) qdsp based machines in this one document? I.e. should we name it a little bit more generic?
You mean like downstream ?
I mean that I'm guessing that apq8084, msm8994, msm8998 etc would come with the same binding document, but a different compatible.
This is fine for now though, we can always rename the file and reword the description later.
Regards, Bjorn
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
uThis patch adds support to DB820c machine driver.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- sound/soc/qcom/Kconfig | 9 +++- sound/soc/qcom/apq8096.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/apq8096.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index ecd1e4ba834d..748ccc3edefa 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -72,7 +72,6 @@ config SND_SOC_QDSP6_ASM_DAI tristate default n
- config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" select SND_SOC_QDSP6_AFE @@ -87,3 +86,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" + 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..5b2036317f71 --- /dev/null +++ b/sound/soc/qcom/apq8096.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2017 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/dma-mapping.h> +#include <sound/pcm.h> + +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 struct snd_soc_dai_link msm8996_dai_links[] = { + /* FrontEnd DAI Links */ + { + .name = "MultiMedia1 Playback", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MM_DL1", + .platform_name = "q6asm_dai", + .dynamic = 1, + .dpcm_playback = 1, + + .codec_dai_name = "snd-soc-dummy-dai", + .codec_name = "snd-soc-dummy", + }, + /* Backend DAI Links */ + { + .name = "HDMI Playback", + .stream_name = "q6afe_dai", + .cpu_dai_name = "HDMI", + .platform_name = "q6routing", + .no_pcm = 1, + .dpcm_playback = 1, + .be_hw_params_fixup = msm8996_be_hw_params_fixup, + .codec_dai_name = "i2s-hifi", + .codec_name = "hdmi-audio-codec.0.auto", + }, +}; + +static int apq8096_sbc_parse_of(struct snd_soc_card *card) +{ + struct device *dev = card->dev; + int ret; + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) + dev_err(dev, "Error parsing card name: %d\n", ret); + + return ret; +} + +static int msm_snd_apq8096_probe(struct platform_device *pdev) +{ + int ret; + struct snd_soc_card *card; + + card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = &pdev->dev; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + card->dai_link = msm8996_dai_links; + card->num_links = ARRAY_SIZE(msm8996_dai_links); + + ret = apq8096_sbc_parse_of(card); + if (ret) { + dev_err(&pdev->dev, "Error parsing OF data\n"); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "sound card register failed (%d)!\n", ret); + else + dev_err(&pdev->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 platform_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_platform_driver(msm_snd_apq8096_driver); +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("APQ8096 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2");
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
uThis patch adds support to DB820c machine driver.
Drop 'u' and expand the message to claim that this is the machine driver for 8996, used by the db820c.
[..]
+static struct snd_soc_dai_link msm8996_dai_links[] = {
Are there any differences between the DAI links of apq8096 and msm8996?
- /* FrontEnd DAI Links */
- {
.name = "MultiMedia1 Playback",
.stream_name = "MultiMedia1",
.cpu_dai_name = "MM_DL1",
.platform_name = "q6asm_dai",
.dynamic = 1,
.dpcm_playback = 1,
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
- },
- /* Backend DAI Links */
- {
.name = "HDMI Playback",
.stream_name = "q6afe_dai",
.cpu_dai_name = "HDMI",
.platform_name = "q6routing",
.no_pcm = 1,
.dpcm_playback = 1,
.be_hw_params_fixup = msm8996_be_hw_params_fixup,
.codec_dai_name = "i2s-hifi",
.codec_name = "hdmi-audio-codec.0.auto",
- },
+};
+static int apq8096_sbc_parse_of(struct snd_soc_card *card)
msm8996_parse_of()
+{
- struct device *dev = card->dev;
- int ret;
- ret = snd_soc_of_parse_card_name(card, "qcom,model");
- if (ret)
dev_err(dev, "Error parsing card name: %d\n", ret);
- return ret;
+}
+static int msm_snd_apq8096_probe(struct platform_device *pdev)
msm_snd_msm8996_probe()?
+{
- int ret;
- struct snd_soc_card *card;
- card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
- if (!card)
return -ENOMEM;
- card->dev = &pdev->dev;
- ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
- if (ret)
return ret;
- card->dai_link = msm8996_dai_links;
- card->num_links = ARRAY_SIZE(msm8996_dai_links);
- ret = apq8096_sbc_parse_of(card);
- if (ret) {
dev_err(&pdev->dev, "Error parsing OF data\n");
No need to print in both parse_of() and here.
return ret;
- }
- ret = devm_snd_soc_register_card(&pdev->dev, card);
- if (ret)
dev_err(&pdev->dev, "sound card register failed (%d)!\n", ret);
- else
dev_err(&pdev->dev, "sound card register Sucessfull\n");
This isn't an error, skip the print here.
- return ret;
+}
+static const struct of_device_id msm_snd_apq8096_dt_match[] = {
- {.compatible = "qcom,apq8096-sndcard"},
- {}
+};
+static struct platform_driver msm_snd_apq8096_driver = {
- .probe = msm_snd_apq8096_probe,
- .driver = {
.name = "msm-snd-apq8096",
.owner = THIS_MODULE,
Drop the .owner
Regards, Bjorn
Thanks for the review comments.
On 03/01/18 00:16, Bjorn Andersson wrote:
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
uThis patch adds support to DB820c machine driver.
Drop 'u' and expand the message to claim that this is the machine driver for 8996, used by the db820c.
[..]
+static struct snd_soc_dai_link msm8996_dai_links[] = {
Are there any differences between the DAI links of apq8096 and msm8996?
This driver is more of board specific, Am not 100% sure about msm8996, on apq8096 in particularly on db820c we got hdmi and analog audio connected via slimbus and also I2S on lowspeed connector.
- /* FrontEnd DAI Links */
- {
.name = "MultiMedia1 Playback",
.stream_name = "MultiMedia1",
.cpu_dai_name = "MM_DL1",
.platform_name = "q6asm_dai",
.dynamic = 1,
.dpcm_playback = 1,
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
- },
- /* Backend DAI Links */
- {
.name = "HDMI Playback",
.stream_name = "q6afe_dai",
.cpu_dai_name = "HDMI",
.platform_name = "q6routing",
.no_pcm = 1,
.dpcm_playback = 1,
.be_hw_params_fixup = msm8996_be_hw_params_fixup,
.codec_dai_name = "i2s-hifi",
.codec_name = "hdmi-audio-codec.0.auto",
- },
+};
+static int apq8096_sbc_parse_of(struct snd_soc_card *card)
msm8996_parse_of()
sure if it helps.
+{
- struct device *dev = card->dev;
- int ret;
- ret = snd_soc_of_parse_card_name(card, "qcom,model");
- if (ret)
dev_err(dev, "Error parsing card name: %d\n", ret);
- return ret;
+}
+static int msm_snd_apq8096_probe(struct platform_device *pdev)
msm_snd_msm8996_probe()?
sure
+{
- int ret;
- struct snd_soc_card *card;
- card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
- if (!card)
return -ENOMEM;
- card->dev = &pdev->dev;
- ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
- if (ret)
return ret;
- card->dai_link = msm8996_dai_links;
- card->num_links = ARRAY_SIZE(msm8996_dai_links);
- ret = apq8096_sbc_parse_of(card);
- if (ret) {
dev_err(&pdev->dev, "Error parsing OF data\n");
No need to print in both parse_of() and here.
yep.
return ret;
- }
- ret = devm_snd_soc_register_card(&pdev->dev, card);
- if (ret)
dev_err(&pdev->dev, "sound card register failed (%d)!\n", ret);
- else
dev_err(&pdev->dev, "sound card register Sucessfull\n");
This isn't an error, skip the print here.
yep.
- return ret;
+}
+static const struct of_device_id msm_snd_apq8096_dt_match[] = {
- {.compatible = "qcom,apq8096-sndcard"},
- {}
+};
+static struct platform_driver msm_snd_apq8096_driver = {
- .probe = msm_snd_apq8096_probe,
- .driver = {
.name = "msm-snd-apq8096",
.owner = THIS_MODULE,
Drop the .owner
yep
Regards, Bjorn
On Wed 03 Jan 08:27 PST 2018, Srinivas Kandagatla wrote:
Thanks for the review comments.
On 03/01/18 00:16, Bjorn Andersson wrote:
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
uThis patch adds support to DB820c machine driver.
Drop 'u' and expand the message to claim that this is the machine driver for 8996, used by the db820c.
[..]
+static struct snd_soc_dai_link msm8996_dai_links[] = {
Are there any differences between the DAI links of apq8096 and msm8996?
This driver is more of board specific, Am not 100% sure about msm8996, on apq8096 in particularly on db820c we got hdmi and analog audio connected via slimbus and also I2S on lowspeed connector.
Can this be kept platform specific and the board specific routing put in DT?
I don't think we want to have a machine-driver for each board. Right now you have 25 dtbs in mainline, that's going to be a lot of machine drivers...
Regards, Bjorn
On 12/14/2017 09:34 AM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
uThis patch adds support to DB820c machine driver.
Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org
sound/soc/qcom/Kconfig | 9 +++- sound/soc/qcom/apq8096.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/apq8096.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index ecd1e4ba834d..748ccc3edefa 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -72,7 +72,6 @@ config SND_SOC_QDSP6_ASM_DAI tristate default n
config SND_SOC_QDSP6 tristate "SoC ALSA audio driver for QDSP6" select SND_SOC_QDSP6_AFE @@ -87,3 +86,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"
- 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..5b2036317f71 --- /dev/null +++ b/sound/soc/qcom/apq8096.c @@ -0,0 +1,124 @@ +/*
- Copyright (c) 2017 The Linux Foundation. All rights reserved.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License version 2 and
- only version 2 as published by the Free Software Foundation.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/clk.h>
No clk usage though?
+#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <sound/soc.h>
[...]
+static struct snd_soc_dai_link msm8996_dai_links[] = {
- /* FrontEnd DAI Links */
- {
.name = "MultiMedia1 Playback",
.stream_name = "MultiMedia1",
.cpu_dai_name = "MM_DL1",
.platform_name = "q6asm_dai",
.dynamic = 1,
.dpcm_playback = 1,
.codec_dai_name = "snd-soc-dummy-dai",
.codec_name = "snd-soc-dummy",
- },
- /* Backend DAI Links */
- {
.name = "HDMI Playback",
.stream_name = "q6afe_dai",
.cpu_dai_name = "HDMI",
.platform_name = "q6routing",
.no_pcm = 1,
.dpcm_playback = 1,
.be_hw_params_fixup = msm8996_be_hw_params_fixup,
.codec_dai_name = "i2s-hifi",
.codec_name = "hdmi-audio-codec.0.auto",
- },
+};
+static int apq8096_sbc_parse_of(struct snd_soc_card *card) +{
- struct device *dev = card->dev;
- int ret;
- ret = snd_soc_of_parse_card_name(card, "qcom,model");
- if (ret)
dev_err(dev, "Error parsing card name: %d\n", ret);
So this prints an error, and the caller also prints an error when it fails. Double error messages?
- return ret;
+}
+static int msm_snd_apq8096_probe(struct platform_device *pdev) +{
- int ret;
- struct snd_soc_card *card;
- card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
- if (!card)
return -ENOMEM;
- card->dev = &pdev->dev;
- ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
Why do we need to do this? Can you add some sort of comment in the code about why?
Thanks for your review comments,
On 03/01/18 17:20, Stephen Boyd wrote:
On 12/14/2017 09:34 AM, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c new file mode 100644 index 000000000000..5b2036317f71 --- /dev/null +++ b/sound/soc/qcom/apq8096.c @@ -0,0 +1,124 @@
- */
+#include <linux/clk.h>
No clk usage though?
Will remove it in next version.
+#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <sound/soc.h>
[...]
+static int apq8096_sbc_parse_of(struct snd_soc_card *card) +{
- struct device *dev = card->dev;
- int ret;
- ret = snd_soc_of_parse_card_name(card, "qcom,model");
- if (ret)
dev_err(dev, "Error parsing card name: %d\n", ret);
So this prints an error, and the caller also prints an error when it fails. Double error messages?
looks redundant, will remove it.
- return ret;
+}
+static int msm_snd_apq8096_probe(struct platform_device *pdev) +{
- int ret;
- struct snd_soc_card *card;
- card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
- if (!card)
return -ENOMEM;
- card->dev = &pdev->dev;
- ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
Why do we need to do this? Can you add some sort of comment in the code about why?
Even though dsp supports 64 bit addresses, but the sid sits at offset of 32, which brings this restriction of supporting only 32 bit iova.
thanks, srini
On 01/03, Srinivas Kandagatla wrote:
Thanks for your review comments,
On 03/01/18 17:20, Stephen Boyd wrote:
- return ret;
+}
+static int msm_snd_apq8096_probe(struct platform_device *pdev) +{
- int ret;
- struct snd_soc_card *card;
- card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
- if (!card)
return -ENOMEM;
- card->dev = &pdev->dev;
- ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
Why do we need to do this? Can you add some sort of comment in the code about why?
Even though dsp supports 64 bit addresses, but the sid sits at offset of 32, which brings this restriction of supporting only 32 bit iova.
Doesn't the dsp have an iommu in place to make the address translation from 64 to 32 bits transparent? I thought this was what dma-ranges and iommu binding was for, but I'm not well versed on all the details here.
On 03/01/18 19:41, Stephen Boyd wrote:
- ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
Why do we need to do this? Can you add some sort of comment in the code about why?
Even though dsp supports 64 bit addresses, but the sid sits at offset of 32, which brings this restriction of supporting only 32 bit iova.
Doesn't the dsp have an iommu in place to make the address translation from 64 to 32 bits transparent? I thought this was what dma-ranges and iommu binding was for, but I'm not well versed on all the details here.
Thanks for reminding, dma-ranges would work too, I will give that a go in next version.
--srini
On Wed, Jan 03, 2018 at 09:20:45AM -0800, Stephen Boyd wrote:
On 12/14/2017 09:34 AM, srinivas.kandagatla@linaro.org wrote:
uThis patch adds support to DB820c machine driver.
- ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
Why do we need to do this? Can you add some sort of comment in the code about why?
And why are we applying DMA restrictions in a machine driver?
On 04/01/18 12:02, Mark Brown wrote:
On Wed, Jan 03, 2018 at 09:20:45AM -0800, Stephen Boyd wrote:
On 12/14/2017 09:34 AM, srinivas.kandagatla@linaro.org wrote:
uThis patch adds support to DB820c machine driver.
- ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
Why do we need to do this? Can you add some sort of comment in the code about why?
And why are we applying DMA restrictions in a machine driver?
Initially I had this in pcm driver, but looking at example usage of snd_dma_alloc_pages, most of them use card->dev and some of them use pcm device for allocating dma memory.
Also, as I moved most dsp static services and dais out of DT, except codec and sound card, sound card device was the only choice I had for binding with iommu and enforcing iova range restrictions.
This call will be replaced by dma-ranges property in DT either way.
--srini
On Thu, Jan 04, 2018 at 01:44:30PM +0000, Srinivas Kandagatla wrote:
Initially I had this in pcm driver, but looking at example usage of snd_dma_alloc_pages, most of them use card->dev and some of them use pcm device for allocating dma memory.
If they're using card->dev for DMA they're messing up, they need to use the device associated with the DMA controller.
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 | 5 +++++ arch/arm64/boot/dts/qcom/msm8996.dtsi | 33 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi index 9769053957af..b955769b100d 100644 --- a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi +++ b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi @@ -190,6 +190,11 @@ }; };
+ snd { + compatible = "qcom,apq8096-sndcard"; + qcom,model = "DB820c"; + iommus = <&lpass_q6_smmu 1>; + };
gpio_keys { compatible = "gpio-keys"; diff --git a/arch/arm64/boot/dts/qcom/msm8996.dtsi b/arch/arm64/boot/dts/qcom/msm8996.dtsi index a144cec7bb71..25c43fb8ab49 100644 --- a/arch/arm64/boot/dts/qcom/msm8996.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi @@ -1262,6 +1262,7 @@
phys = <&hdmi_phy>; phy-names = "hdmi_phy"; + #sound-dai-cells = <0>;
ports { #address-cells = <1>; @@ -1297,6 +1298,33 @@ "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 { @@ -1325,6 +1353,11 @@ qcom,ipc = <&apcs 16 8>; qcom,smd-edge = <1>; qcom,remote-pid = <2>; + + apr { + compatible = "qcom,apr-msm8996"; + qcom,smd-channels = "apr_audio_svc"; + }; }; };
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
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 | 5 +++++ arch/arm64/boot/dts/qcom/msm8996.dtsi | 33 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi index 9769053957af..b955769b100d 100644 --- a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi +++ b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi @@ -190,6 +190,11 @@ }; };
snd {
compatible = "qcom,apq8096-sndcard";
qcom,model = "DB820c";
iommus = <&lpass_q6_smmu 1>;
};
gpio_keys { compatible = "gpio-keys";
diff --git a/arch/arm64/boot/dts/qcom/msm8996.dtsi b/arch/arm64/boot/dts/qcom/msm8996.dtsi index a144cec7bb71..25c43fb8ab49 100644 --- a/arch/arm64/boot/dts/qcom/msm8996.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi @@ -1262,6 +1262,7 @@
phys = <&hdmi_phy>; phy-names = "hdmi_phy";
#sound-dai-cells = <0>; ports { #address-cells = <1>;
@@ -1297,6 +1298,33 @@ "ref_clk"; }; };
lpass_q6_smmu: arm,smmu-lpass_q6@1600000 {
name this node "iommu"
compatible = "qcom,msm8996-smmu-v2";
reg = <0x1600000 0x20000>;
#iommu-cells = <1>;
power-domains = <&gcc HLOS1_VOTE_LPASS_CORE_GDSC>;
Indentation
#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 {
@@ -1325,6 +1353,11 @@ qcom,ipc = <&apcs 16 8>; qcom,smd-edge = <1>; qcom,remote-pid = <2>;
apr {
"apr-audio-svc", as this is not the only apr channel on this edge.
compatible = "qcom,apr-msm8996";
qcom,smd-channels = "apr_audio_svc";
};};
Regards, Bjorn
Thanks for the comments,
On 03/01/18 00:22, Bjorn Andersson wrote:
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
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 | 5 +++++ arch/arm64/boot/dts/qcom/msm8996.dtsi | 33 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi index 9769053957af..b955769b100d 100644 --- a/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi +++ b/arch/arm64/boot/dts/qcom/apq8096-db820c.dtsi @@ -190,6 +190,11 @@ }; };
snd {
compatible = "qcom,apq8096-sndcard";
qcom,model = "DB820c";
iommus = <&lpass_q6_smmu 1>;
};
gpio_keys { compatible = "gpio-keys";
diff --git a/arch/arm64/boot/dts/qcom/msm8996.dtsi b/arch/arm64/boot/dts/qcom/msm8996.dtsi index a144cec7bb71..25c43fb8ab49 100644 --- a/arch/arm64/boot/dts/qcom/msm8996.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi @@ -1262,6 +1262,7 @@
phys = <&hdmi_phy>; phy-names = "hdmi_phy";
#sound-dai-cells = <0>; ports { #address-cells = <1>;
@@ -1297,6 +1298,33 @@ "ref_clk"; }; };
lpass_q6_smmu: arm,smmu-lpass_q6@1600000 {
name this node "iommu"
will rename it to arm,smmu@1600000
compatible = "qcom,msm8996-smmu-v2";
reg = <0x1600000 0x20000>;
#iommu-cells = <1>;
power-domains = <&gcc HLOS1_VOTE_LPASS_CORE_GDSC>;
Indentation
sure.
#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 {
@@ -1325,6 +1353,11 @@ qcom,ipc = <&apcs 16 8>; qcom,smd-edge = <1>; qcom,remote-pid = <2>;
apr {
"apr-audio-svc", as this is not the only apr channel on this edge.
yep.
compatible = "qcom,apr-msm8996";
qcom,smd-channels = "apr_audio_svc";
};};
Regards, Bjorn
On Wed 03 Jan 08:27 PST 2018, Srinivas Kandagatla wrote:
Thanks for the comments,
On 03/01/18 00:22, Bjorn Andersson wrote:
On Thu 14 Dec 09:34 PST 2017, srinivas.kandagatla@linaro.org wrote:
From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
[..]
lpass_q6_smmu: arm,smmu-lpass_q6@1600000 {
name this node "iommu"
will rename it to arm,smmu@1600000
I don't think you're supposed to have , in the node names, and whenever possible they should be generic names of the function.
So analog to "clock-controller", "dma-controller", "serial", "i2c" etc I believe this should be "iommu".
Regards, Bjorn
participants (7)
-
Bjorn Andersson
-
Mark Brown
-
Rob Herring
-
Rohit Kumar
-
Srinivas Kandagatla
-
srinivas.kandagatla@linaro.org
-
Stephen Boyd