[PATCH 00/19] Add soundwire support for Pink Sardine platform
Pink Sardine(ps) platform is based on ACP6.3 Architecture. ACP6.3 IP has two soundwire controller instance support. This patchset add support for Soundwire controller on Pink Sardine platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Vijendar Mukunda (19): ASoC: amd: ps: create platform devices based on acp config soundwire: amd: Add support for AMD Master driver soundwire: amd: register sdw controller dai ops soundwire: amd: enable build for AMD soundwire master driver soundwire: amd: add soundwire interrupt handling ASoC: amd: ps: add support for soundwire interrupts in acp pci driver ASoC: amd: ps: add soundwire dma driver for pink sardine platform ASoC: amd: ps: add soundwire dma driver dma ops ASoC: amd: ps: add support for Soundwire DMA interrupts ASoC: amd: ps: enable Soundwire DMA driver build ASoC: amd: update comments in Kconfig file ASoC: amd: ps: Add soundwire specific checks in pci driver in pm ops. ASoC: amd: ps: add support for runtime pm ops for soundwire dma driver soundwire: amd: add runtime pm ops for AMD master driver soundwire: amd: add startup and shutdown dai ops soundwire: amd: handle wake enable interrupt soundwire: amd: add pm_prepare callback and pm ops support ASoC: amd: ps: implement system level pm ops for soundwire dma driver ASoC: amd: ps: increase runtime suspend delay
drivers/soundwire/Kconfig | 9 + drivers/soundwire/Makefile | 4 + drivers/soundwire/amd_master.c | 1734 +++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 284 +++++ include/linux/soundwire/sdw_amd.h | 65 ++ sound/soc/amd/Kconfig | 3 +- sound/soc/amd/ps/Makefile | 2 + sound/soc/amd/ps/acp63.h | 98 +- sound/soc/amd/ps/pci-ps.c | 383 ++++++- sound/soc/amd/ps/ps-sdw-dma.c | 728 ++++++++++++ 10 files changed, 3287 insertions(+), 23 deletions(-) create mode 100644 drivers/soundwire/amd_master.c create mode 100644 drivers/soundwire/amd_master.h create mode 100644 include/linux/soundwire/sdw_amd.h create mode 100644 sound/soc/amd/ps/ps-sdw-dma.c
Create platform devices for sdw controllers and PDM controller based on ACP pin config selection and ACPI fw handle for pink sardine platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com --- include/linux/soundwire/sdw_amd.h | 18 +++ sound/soc/amd/ps/acp63.h | 24 ++- sound/soc/amd/ps/pci-ps.c | 248 ++++++++++++++++++++++++++++-- 3 files changed, 277 insertions(+), 13 deletions(-) create mode 100644 include/linux/soundwire/sdw_amd.h
diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h new file mode 100644 index 000000000000..f0123815af46 --- /dev/null +++ b/include/linux/soundwire/sdw_amd.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved. + */ + +#ifndef __SDW_AMD_H +#define __SDW_AMD_H + +#include <linux/soundwire/sdw.h> + +#define AMD_SDW_CLK_STOP_MODE 1 +#define AMD_SDW_POWER_OFF_MODE 2 + +struct acp_sdw_pdata { + u16 instance; + struct mutex *sdw_lock; +}; +#endif diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index b7535c7d093f..ed979e6d0c1d 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -10,7 +10,7 @@ #define ACP_DEVICE_ID 0x15E2 #define ACP63_REG_START 0x1240000 #define ACP63_REG_END 0x1250200 -#define ACP63_DEVS 3 +#define ACP63_DEVS 5
#define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001 #define ACP_PGFSM_CNTL_POWER_ON_MASK 1 @@ -55,8 +55,14 @@
#define ACP63_DMIC_ADDR 2 #define ACP63_PDM_MODE_DEVS 3 -#define ACP63_PDM_DEV_MASK 1 #define ACP_DMIC_DEV 2 +#define ACP63_SDW0_MODE_DEVS 2 +#define ACP63_SDW0_SDW1_MODE_DEVS 3 +#define ACP63_SDW0_PDM_MODE_DEVS 4 +#define ACP63_SDW0_SDW1_PDM_MODE_DEVS 5 +#define ACP63_DMIC_ADDR 2 +#define ACP63_SDW_ADDR 5 +#define AMD_SDW_MAX_CONTROLLERS 2
enum acp_config { ACP_CONFIG_0 = 0, @@ -77,6 +83,12 @@ enum acp_config { ACP_CONFIG_15, };
+enum acp_pdev_mask { + ACP63_PDM_DEV_MASK = 1, + ACP63_SDW_DEV_MASK, + ACP63_SDW_PDM_DEV_MASK, +}; + struct pdm_stream_instance { u16 num_pages; u16 channels; @@ -107,7 +119,15 @@ struct acp63_dev_data { struct resource *res; struct platform_device *pdev[ACP63_DEVS]; struct mutex acp_lock; /* protect shared registers */ + struct fwnode_handle *sdw_fw_node; u16 pdev_mask; u16 pdev_count; u16 pdm_dev_index; + u8 sdw_master_count; + u16 sdw0_dev_index; + u16 sdw1_dev_index; + u16 sdw_dma_dev_index; + bool is_dmic_dev; + bool is_sdw_dev; + bool acp_sdw_power_off; }; diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index e86f23d97584..85154cf0b2a2 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -14,6 +14,7 @@ #include <linux/interrupt.h> #include <sound/pcm_params.h> #include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_amd.h>
#include "acp63.h"
@@ -134,12 +135,68 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) return IRQ_NONE; }
-static void get_acp63_device_config(u32 config, struct pci_dev *pci, - struct acp63_dev_data *acp_data) +static int sdw_amd_scan_controller(struct device *dev) +{ + struct acp63_dev_data *acp_data; + struct fwnode_handle *link; + char name[32]; + u8 count = 0; + u32 acp_sdw_power_mode = 0; + int index; + int ret; + + acp_data = dev_get_drvdata(dev); + acp_data->acp_sdw_power_off = true; + /* Found controller, find links supported */ + ret = fwnode_property_read_u8_array((acp_data->sdw_fw_node), + "mipi-sdw-master-count", &count, 1); + + if (ret) { + dev_err(dev, + "Failed to read mipi-sdw-master-count: %d\n", ret); + return -EINVAL; + } + + /* Check count is within bounds */ + if (count > AMD_SDW_MAX_CONTROLLERS) { + dev_err(dev, "Controller count %d exceeds max %d\n", + count, AMD_SDW_MAX_CONTROLLERS); + return -EINVAL; + } + + if (!count) { + dev_warn(dev, "No SoundWire controllers detected\n"); + return -EINVAL; + } + dev_dbg(dev, "ACPI reports %d Soundwire Controller devices\n", count); + acp_data->sdw_master_count = count; + for (index = 0; index < count; index++) { + snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", index); + link = fwnode_get_named_child_node(acp_data->sdw_fw_node, name); + if (!link) { + dev_err(dev, "Master node %s not found\n", name); + return -EIO; + } + + fwnode_property_read_u32(link, "amd-sdw-power-mode", + &acp_sdw_power_mode); + if (acp_sdw_power_mode != AMD_SDW_POWER_OFF_MODE) + acp_data->acp_sdw_power_off = false; + } + return 0; +} + +static int get_acp63_device_config(u32 config, struct pci_dev *pci, struct acp63_dev_data *acp_data) { struct acpi_device *dmic_dev; + struct acpi_device *sdw_dev; + struct device *dev; const union acpi_object *obj; bool is_dmic_dev = false; + bool is_sdw_dev = false; + int ret; + + dev = &pci->dev;
dmic_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_DMIC_ADDR, 0); if (dmic_dev) { @@ -149,22 +206,84 @@ static void get_acp63_device_config(u32 config, struct pci_dev *pci, is_dmic_dev = true; }
+ sdw_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_SDW_ADDR, 0); + if (sdw_dev) { + is_sdw_dev = true; + acp_data->sdw_fw_node = acpi_fwnode_handle(sdw_dev); + ret = sdw_amd_scan_controller(dev); + if (ret) + return ret; + } + + dev_dbg(&pci->dev, "Audio Mode %d\n", config); switch (config) { - case ACP_CONFIG_0: - case ACP_CONFIG_1: + case ACP_CONFIG_4: + case ACP_CONFIG_5: + case ACP_CONFIG_10: + case ACP_CONFIG_11: + if (is_dmic_dev) { + acp_data->pdev_mask = ACP63_PDM_DEV_MASK; + acp_data->pdev_count = ACP63_PDM_MODE_DEVS; + } + break; case ACP_CONFIG_2: case ACP_CONFIG_3: - case ACP_CONFIG_9: - case ACP_CONFIG_15: - dev_dbg(&pci->dev, "Audio Mode %d\n", config); + if (is_sdw_dev) { + switch (acp_data->sdw_master_count) { + case 1: + acp_data->pdev_mask = ACP63_SDW_DEV_MASK; + acp_data->pdev_count = ACP63_SDW0_MODE_DEVS; + break; + case 2: + acp_data->pdev_mask = ACP63_SDW_DEV_MASK; + acp_data->pdev_count = ACP63_SDW0_SDW1_MODE_DEVS; + break; + default: + return -EINVAL; + } + } break; - default: - if (is_dmic_dev) { + case ACP_CONFIG_6: + case ACP_CONFIG_7: + case ACP_CONFIG_12: + case ACP_CONFIG_8: + case ACP_CONFIG_13: + case ACP_CONFIG_14: + if (is_dmic_dev && is_sdw_dev) { + switch (acp_data->sdw_master_count) { + case 1: + acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK; + acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS; + break; + case 2: + acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK; + acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS; + break; + default: + return -EINVAL; + } + } else if (is_dmic_dev) { acp_data->pdev_mask = ACP63_PDM_DEV_MASK; acp_data->pdev_count = ACP63_PDM_MODE_DEVS; + } else if (is_sdw_dev) { + switch (acp_data->sdw_master_count) { + case 1: + acp_data->pdev_mask = ACP63_SDW_DEV_MASK; + acp_data->pdev_count = ACP63_SDW0_MODE_DEVS; + break; + case 2: + acp_data->pdev_mask = ACP63_SDW_DEV_MASK; + acp_data->pdev_count = ACP63_SDW0_SDW1_MODE_DEVS; + break; + default: + return -EINVAL; + } } break; + default: + break; } + return 0; }
static void acp63_fill_platform_dev_info(struct platform_device_info *pdevinfo, @@ -188,6 +307,7 @@ static void acp63_fill_platform_dev_info(struct platform_device_info *pdevinfo,
static int create_acp63_platform_devs(struct pci_dev *pci, struct acp63_dev_data *adata, u32 addr) { + struct acp_sdw_pdata *sdw_pdata; struct platform_device_info pdevinfo[ACP63_DEVS]; struct device *parent; int index; @@ -220,8 +340,110 @@ static int create_acp63_platform_devs(struct pci_dev *pci, struct acp63_dev_data acp63_fill_platform_dev_info(&pdevinfo[2], parent, NULL, "acp_ps_mach", 0, NULL, 0, NULL, 0); break; + case ACP63_SDW_DEV_MASK: + if (adata->pdev_count == ACP63_SDW0_MODE_DEVS) { + sdw_pdata = devm_kzalloc(&pci->dev, sizeof(struct acp_sdw_pdata), + GFP_KERNEL); + if (!sdw_pdata) { + ret = -ENOMEM; + goto de_init; + } + + sdw_pdata->instance = 0; + sdw_pdata->sdw_lock = &adata->acp_lock; + adata->sdw0_dev_index = 0; + adata->sdw_dma_dev_index = 1; + acp63_fill_platform_dev_info(&pdevinfo[0], parent, adata->sdw_fw_node, + "amd_sdw_controller", 0, adata->res, 1, + sdw_pdata, sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[1], parent, NULL, "amd_ps_sdw_dma", + 0, adata->res, 1, &adata->acp_lock, + sizeof(adata->acp_lock)); + } else if (adata->pdev_count == ACP63_SDW0_SDW1_MODE_DEVS) { + sdw_pdata = devm_kzalloc(&pci->dev, sizeof(struct acp_sdw_pdata) * 2, + GFP_KERNEL); + if (!sdw_pdata) { + ret = -ENOMEM; + goto de_init; + } + + sdw_pdata[0].instance = 0; + sdw_pdata[1].instance = 1; + sdw_pdata[0].sdw_lock = &adata->acp_lock; + sdw_pdata[1].sdw_lock = &adata->acp_lock; + sdw_pdata->sdw_lock = &adata->acp_lock; + adata->sdw0_dev_index = 0; + adata->sdw1_dev_index = 1; + adata->sdw_dma_dev_index = 2; + acp63_fill_platform_dev_info(&pdevinfo[0], parent, adata->sdw_fw_node, + "amd_sdw_controller", 0, adata->res, 1, + &sdw_pdata[0], sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[1], parent, adata->sdw_fw_node, + "amd_sdw_controller", 1, adata->res, 1, + &sdw_pdata[1], sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[2], parent, NULL, "amd_ps_sdw_dma", + 0, adata->res, 1, &adata->acp_lock, + sizeof(adata->acp_lock)); + } + break; + case ACP63_SDW_PDM_DEV_MASK: + if (adata->pdev_count == ACP63_SDW0_PDM_MODE_DEVS) { + sdw_pdata = devm_kzalloc(&pci->dev, sizeof(struct acp_sdw_pdata), + GFP_KERNEL); + if (!sdw_pdata) { + ret = -ENOMEM; + goto de_init; + } + + sdw_pdata->instance = 0; + sdw_pdata->sdw_lock = &adata->acp_lock; + adata->pdm_dev_index = 0; + adata->sdw0_dev_index = 1; + adata->sdw_dma_dev_index = 2; + acp63_fill_platform_dev_info(&pdevinfo[0], parent, NULL, "acp_ps_pdm_dma", + 0, adata->res, 1, &adata->acp_lock, + sizeof(adata->acp_lock)); + acp63_fill_platform_dev_info(&pdevinfo[1], parent, adata->sdw_fw_node, + "amd_sdw_controller", 0, adata->res, 1, + sdw_pdata, sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[2], parent, NULL, "amd_ps_sdw_dma", + 0, adata->res, 1, &adata->acp_lock, + sizeof(adata->acp_lock)); + acp63_fill_platform_dev_info(&pdevinfo[3], parent, NULL, "dmic-codec", + 0, NULL, 0, NULL, 0); + } else if (adata->pdev_count == ACP63_SDW0_SDW1_PDM_MODE_DEVS) { + sdw_pdata = devm_kzalloc(&pci->dev, sizeof(struct acp_sdw_pdata) * 2, + GFP_KERNEL); + if (!sdw_pdata) { + ret = -ENOMEM; + goto de_init; + } + sdw_pdata[0].instance = 0; + sdw_pdata[1].instance = 1; + sdw_pdata[0].sdw_lock = &adata->acp_lock; + sdw_pdata[1].sdw_lock = &adata->acp_lock; + adata->pdm_dev_index = 0; + adata->sdw0_dev_index = 1; + adata->sdw1_dev_index = 2; + adata->sdw_dma_dev_index = 3; + acp63_fill_platform_dev_info(&pdevinfo[0], parent, NULL, "acp_ps_pdm_dma", + 0, adata->res, 1, &adata->acp_lock, + sizeof(adata->acp_lock)); + acp63_fill_platform_dev_info(&pdevinfo[1], parent, adata->sdw_fw_node, + "amd_sdw_controller", 0, adata->res, 1, + &sdw_pdata[0], sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[2], parent, adata->sdw_fw_node, + "amd_sdw_controller", 1, adata->res, 1, + &sdw_pdata[1], sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[3], parent, NULL, "amd_ps_sdw_dma", + 0, adata->res, 1, &adata->acp_lock, + sizeof(adata->acp_lock)); + acp63_fill_platform_dev_info(&pdevinfo[4], parent, NULL, "dmic-codec", + 0, NULL, 0, NULL, 0); + } + break; default: - dev_dbg(&pci->dev, "No PDM devices found\n"); + dev_dbg(&pci->dev, "No PDM or Soundwire controller devices found\n"); return 0; }
@@ -299,7 +521,11 @@ static int snd_acp63_probe(struct pci_dev *pci, goto de_init; } val = acp63_readl(adata->acp63_base + ACP_PIN_CONFIG); - get_acp63_device_config(val, pci, adata); + ret = get_acp63_device_config(val, pci, adata); + if (ret) { + dev_err(&pci->dev, "get acp device config failed:%d\n", ret); + goto de_init; + } ret = create_acp63_platform_devs(pci, adata, addr); if (ret < 0) { dev_err(&pci->dev, "ACP platform devices creation failed\n");
On 1/11/2023 10:02 AM, Vijendar Mukunda wrote:
Create platform devices for sdw controllers and PDM controller based on ACP pin config selection and ACPI fw handle for pink sardine platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com
include/linux/soundwire/sdw_amd.h | 18 +++ sound/soc/amd/ps/acp63.h | 24 ++- sound/soc/amd/ps/pci-ps.c | 248 ++++++++++++++++++++++++++++-- 3 files changed, 277 insertions(+), 13 deletions(-) create mode 100644 include/linux/soundwire/sdw_amd.h
...
diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index e86f23d97584..85154cf0b2a2 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -14,6 +14,7 @@ #include <linux/interrupt.h> #include <sound/pcm_params.h> #include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_amd.h>
#include "acp63.h"
@@ -134,12 +135,68 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) return IRQ_NONE; }
-static void get_acp63_device_config(u32 config, struct pci_dev *pci,
struct acp63_dev_data *acp_data)
+static int sdw_amd_scan_controller(struct device *dev) +{
- struct acp63_dev_data *acp_data;
- struct fwnode_handle *link;
- char name[32];
- u8 count = 0;
- u32 acp_sdw_power_mode = 0;
- int index;
- int ret;
- acp_data = dev_get_drvdata(dev);
- acp_data->acp_sdw_power_off = true;
- /* Found controller, find links supported */
- ret = fwnode_property_read_u8_array((acp_data->sdw_fw_node),
"mipi-sdw-master-count", &count, 1);
- if (ret) {
dev_err(dev,
"Failed to read mipi-sdw-master-count: %d\n", ret);
return -EINVAL;
- }
- /* Check count is within bounds */
- if (count > AMD_SDW_MAX_CONTROLLERS) {
dev_err(dev, "Controller count %d exceeds max %d\n",
count, AMD_SDW_MAX_CONTROLLERS);
return -EINVAL;
- }
- if (!count) {
dev_warn(dev, "No SoundWire controllers detected\n");
return -EINVAL;
- }
- dev_dbg(dev, "ACPI reports %d Soundwire Controller devices\n", count);
- acp_data->sdw_master_count = count;
Double space before '='.
- for (index = 0; index < count; index++) {
snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", index);
link = fwnode_get_named_child_node(acp_data->sdw_fw_node, name);
if (!link) {
dev_err(dev, "Master node %s not found\n", name);
return -EIO;
}
fwnode_property_read_u32(link, "amd-sdw-power-mode",
&acp_sdw_power_mode);
if (acp_sdw_power_mode != AMD_SDW_POWER_OFF_MODE)
acp_data->acp_sdw_power_off = false;
- }
- return 0;
+}
+static int get_acp63_device_config(u32 config, struct pci_dev *pci, struct acp63_dev_data *acp_data) { struct acpi_device *dmic_dev;
struct acpi_device *sdw_dev;
struct device *dev; const union acpi_object *obj; bool is_dmic_dev = false;
bool is_sdw_dev = false;
int ret;
dev = &pci->dev;
dmic_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_DMIC_ADDR, 0);
If you set dev above, you might as well use it throughout the function context? Like above in ACPI_COMPANION?
if (dmic_dev) { @@ -149,22 +206,84 @@ static void get_acp63_device_config(u32 config, struct pci_dev *pci, is_dmic_dev = true; }
- sdw_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_SDW_ADDR, 0);
- if (sdw_dev) {
is_sdw_dev = true;
acp_data->sdw_fw_node = acpi_fwnode_handle(sdw_dev);
ret = sdw_amd_scan_controller(dev);
Or just use &pci->dev here, so there is no need for separate variable?
On 11/01/23 18:57, Amadeusz Sławiński wrote:
On 1/11/2023 10:02 AM, Vijendar Mukunda wrote:
Create platform devices for sdw controllers and PDM controller based on ACP pin config selection and ACPI fw handle for pink sardine platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com
include/linux/soundwire/sdw_amd.h | 18 +++ sound/soc/amd/ps/acp63.h | 24 ++- sound/soc/amd/ps/pci-ps.c | 248 ++++++++++++++++++++++++++++-- 3 files changed, 277 insertions(+), 13 deletions(-) create mode 100644 include/linux/soundwire/sdw_amd.h
...
diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index e86f23d97584..85154cf0b2a2 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -14,6 +14,7 @@ #include <linux/interrupt.h> #include <sound/pcm_params.h> #include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_amd.h> #include "acp63.h" @@ -134,12 +135,68 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) return IRQ_NONE; } -static void get_acp63_device_config(u32 config, struct pci_dev *pci, - struct acp63_dev_data *acp_data) +static int sdw_amd_scan_controller(struct device *dev) +{ + struct acp63_dev_data *acp_data; + struct fwnode_handle *link; + char name[32]; + u8 count = 0; + u32 acp_sdw_power_mode = 0; + int index; + int ret;
+ acp_data = dev_get_drvdata(dev); + acp_data->acp_sdw_power_off = true; + /* Found controller, find links supported */ + ret = fwnode_property_read_u8_array((acp_data->sdw_fw_node), + "mipi-sdw-master-count", &count, 1);
+ if (ret) { + dev_err(dev, + "Failed to read mipi-sdw-master-count: %d\n", ret); + return -EINVAL; + }
+ /* Check count is within bounds */ + if (count > AMD_SDW_MAX_CONTROLLERS) { + dev_err(dev, "Controller count %d exceeds max %d\n", + count, AMD_SDW_MAX_CONTROLLERS); + return -EINVAL; + }
+ if (!count) { + dev_warn(dev, "No SoundWire controllers detected\n"); + return -EINVAL; + } + dev_dbg(dev, "ACPI reports %d Soundwire Controller devices\n", count); + acp_data->sdw_master_count = count;
Double space before '='. will fix it.
+ for (index = 0; index < count; index++) { + snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", index); + link = fwnode_get_named_child_node(acp_data->sdw_fw_node, name); + if (!link) { + dev_err(dev, "Master node %s not found\n", name); + return -EIO; + }
+ fwnode_property_read_u32(link, "amd-sdw-power-mode", + &acp_sdw_power_mode); + if (acp_sdw_power_mode != AMD_SDW_POWER_OFF_MODE) + acp_data->acp_sdw_power_off = false; + } + return 0; +}
+static int get_acp63_device_config(u32 config, struct pci_dev *pci, struct acp63_dev_data *acp_data) { struct acpi_device *dmic_dev; + struct acpi_device *sdw_dev; + struct device *dev; const union acpi_object *obj; bool is_dmic_dev = false; + bool is_sdw_dev = false; + int ret;
+ dev = &pci->dev; dmic_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_DMIC_ADDR, 0);
If you set dev above, you might as well use it throughout the function context? Like above in ACPI_COMPANION?
will use pci->dev throughtout the function context.
if (dmic_dev) { @@ -149,22 +206,84 @@ static void get_acp63_device_config(u32 config, struct pci_dev *pci, is_dmic_dev = true; } + sdw_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_SDW_ADDR, 0); + if (sdw_dev) { + is_sdw_dev = true; + acp_data->sdw_fw_node = acpi_fwnode_handle(sdw_dev); + ret = sdw_amd_scan_controller(dev);
Or just use &pci->dev here, so there is no need for separate variable?
will remove the "dev" local variable.
+#define AMD_SDW_CLK_STOP_MODE 1
there are multiple modes for clock stop in SoundWire, and multiple ways for the link manager to deal with clock stop, you want a comment to describe what this define refers to.
+#define AMD_SDW_POWER_OFF_MODE 2
+struct acp_sdw_pdata {
- u16 instance;
- struct mutex *sdw_lock;
need a comment on what this lock protects.
+}; +#endif diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index b7535c7d093f..ed979e6d0c1d 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -10,7 +10,7 @@ #define ACP_DEVICE_ID 0x15E2 #define ACP63_REG_START 0x1240000 #define ACP63_REG_END 0x1250200 -#define ACP63_DEVS 3 +#define ACP63_DEVS 5
#define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001 #define ACP_PGFSM_CNTL_POWER_ON_MASK 1 @@ -55,8 +55,14 @@
#define ACP63_DMIC_ADDR 2 #define ACP63_PDM_MODE_DEVS 3 -#define ACP63_PDM_DEV_MASK 1 #define ACP_DMIC_DEV 2 +#define ACP63_SDW0_MODE_DEVS 2 +#define ACP63_SDW0_SDW1_MODE_DEVS 3 +#define ACP63_SDW0_PDM_MODE_DEVS 4 +#define ACP63_SDW0_SDW1_PDM_MODE_DEVS 5 +#define ACP63_DMIC_ADDR 2 +#define ACP63_SDW_ADDR 5 +#define AMD_SDW_MAX_CONTROLLERS 2
enum acp_config { ACP_CONFIG_0 = 0, @@ -77,6 +83,12 @@ enum acp_config { ACP_CONFIG_15, };
+enum acp_pdev_mask {
- ACP63_PDM_DEV_MASK = 1,
- ACP63_SDW_DEV_MASK,
- ACP63_SDW_PDM_DEV_MASK,
+};
struct pdm_stream_instance { u16 num_pages; u16 channels; @@ -107,7 +119,15 @@ struct acp63_dev_data { struct resource *res; struct platform_device *pdev[ACP63_DEVS]; struct mutex acp_lock; /* protect shared registers */
- struct fwnode_handle *sdw_fw_node; u16 pdev_mask; u16 pdev_count; u16 pdm_dev_index;
- u8 sdw_master_count;
for new contributions, it's recommended to use manager and peripheral.
- u16 sdw0_dev_index;
- u16 sdw1_dev_index;
probably need a comment on what the 0 and 1 refer to, it's not clear if there's any sort of dependency/link with the 'sdw_master_count' above.
If this is related to the two controllers mentioned in the cover letter, then an explanation of the sdw_master_count would be needed as well (single variable for two controllers?)
- u16 sdw_dma_dev_index;
- bool is_dmic_dev;
- bool is_sdw_dev;
- bool acp_sdw_power_off;
}; diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index e86f23d97584..85154cf0b2a2 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -14,6 +14,7 @@ #include <linux/interrupt.h> #include <sound/pcm_params.h> #include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_amd.h>
#include "acp63.h"
@@ -134,12 +135,68 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) return IRQ_NONE; }
-static void get_acp63_device_config(u32 config, struct pci_dev *pci,
struct acp63_dev_data *acp_data)
+static int sdw_amd_scan_controller(struct device *dev) +{
- struct acp63_dev_data *acp_data;
- struct fwnode_handle *link;
- char name[32];
- u8 count = 0;
- u32 acp_sdw_power_mode = 0;
- int index;
- int ret;
- acp_data = dev_get_drvdata(dev);
- acp_data->acp_sdw_power_off = true;
- /* Found controller, find links supported */
- ret = fwnode_property_read_u8_array((acp_data->sdw_fw_node),
"mipi-sdw-master-count", &count, 1);
- if (ret) {
dev_err(dev,
"Failed to read mipi-sdw-master-count: %d\n", ret);
one line?
return -EINVAL;
- }
- /* Check count is within bounds */
- if (count > AMD_SDW_MAX_CONTROLLERS) {
dev_err(dev, "Controller count %d exceeds max %d\n",
count, AMD_SDW_MAX_CONTROLLERS);
No. controllers and masters are different concepts, see the DisCo specification for SoundWire. A Controller can have multiple Masters.
return -EINVAL;
- }
- if (!count) {
dev_warn(dev, "No SoundWire controllers detected\n");
return -EINVAL;
- }
is this really a warning, looks like a dev_dbg or info to me.
- dev_dbg(dev, "ACPI reports %d Soundwire Controller devices\n", count);
the term device is incorrect here, the DisCo spec does not expose ACPI devices for each master.
"ACPI reports %d Managers"
- acp_data->sdw_master_count = count;
- for (index = 0; index < count; index++) {
snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", index);
link = fwnode_get_named_child_node(acp_data->sdw_fw_node, name);
if (!link) {
dev_err(dev, "Master node %s not found\n", name);
return -EIO;
}
fwnode_property_read_u32(link, "amd-sdw-power-mode",
&acp_sdw_power_mode);
if (acp_sdw_power_mode != AMD_SDW_POWER_OFF_MODE)
acp_data->acp_sdw_power_off = false;
does power-off mean 'clock-stop'?
- }
- return 0;
+}
if (is_dmic_dev && is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS;
break;
so the cover letter is indeed wrong and confuses two controllers for two managers.
default:
return -EINVAL;
}
} else if (is_dmic_dev) { acp_data->pdev_mask = ACP63_PDM_DEV_MASK; acp_data->pdev_count = ACP63_PDM_MODE_DEVS;
} else if (is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_MODE_DEVS;
break;
default:
return -EINVAL;
} break;}
- default:
}break;
- return 0;
}
On 11/01/23 19:02, Pierre-Louis Bossart wrote:
+#define AMD_SDW_CLK_STOP_MODE 1
there are multiple modes for clock stop in SoundWire, and multiple ways for the link manager to deal with clock stop, you want a comment to describe what this define refers to.
will add comments about flags explanation.
+#define AMD_SDW_POWER_OFF_MODE 2
+struct acp_sdw_pdata {
- u16 instance;
- struct mutex *sdw_lock;
need a comment on what this lock protects.
+}; +#endif diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index b7535c7d093f..ed979e6d0c1d 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -10,7 +10,7 @@ #define ACP_DEVICE_ID 0x15E2 #define ACP63_REG_START 0x1240000 #define ACP63_REG_END 0x1250200 -#define ACP63_DEVS 3 +#define ACP63_DEVS 5
#define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001 #define ACP_PGFSM_CNTL_POWER_ON_MASK 1 @@ -55,8 +55,14 @@
#define ACP63_DMIC_ADDR 2 #define ACP63_PDM_MODE_DEVS 3 -#define ACP63_PDM_DEV_MASK 1 #define ACP_DMIC_DEV 2 +#define ACP63_SDW0_MODE_DEVS 2 +#define ACP63_SDW0_SDW1_MODE_DEVS 3 +#define ACP63_SDW0_PDM_MODE_DEVS 4 +#define ACP63_SDW0_SDW1_PDM_MODE_DEVS 5 +#define ACP63_DMIC_ADDR 2 +#define ACP63_SDW_ADDR 5 +#define AMD_SDW_MAX_CONTROLLERS 2
enum acp_config { ACP_CONFIG_0 = 0, @@ -77,6 +83,12 @@ enum acp_config { ACP_CONFIG_15, };
+enum acp_pdev_mask {
- ACP63_PDM_DEV_MASK = 1,
- ACP63_SDW_DEV_MASK,
- ACP63_SDW_PDM_DEV_MASK,
+};
struct pdm_stream_instance { u16 num_pages; u16 channels; @@ -107,7 +119,15 @@ struct acp63_dev_data { struct resource *res; struct platform_device *pdev[ACP63_DEVS]; struct mutex acp_lock; /* protect shared registers */
- struct fwnode_handle *sdw_fw_node; u16 pdev_mask; u16 pdev_count; u16 pdm_dev_index;
- u8 sdw_master_count;
for new contributions, it's recommended to use manager and peripheral.
will use manager and peripheral terminology.
- u16 sdw0_dev_index;
- u16 sdw1_dev_index;
probably need a comment on what the 0 and 1 refer to, it's not clear if there's any sort of dependency/link with the 'sdw_master_count' above.
If this is related to the two controllers mentioned in the cover letter, then an explanation of the sdw_master_count would be needed as well (single variable for two controllers?)
will add comments for dev_index variables.
- u16 sdw_dma_dev_index;
- bool is_dmic_dev;
- bool is_sdw_dev;
- bool acp_sdw_power_off;
}; diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index e86f23d97584..85154cf0b2a2 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -14,6 +14,7 @@ #include <linux/interrupt.h> #include <sound/pcm_params.h> #include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_amd.h>
#include "acp63.h"
@@ -134,12 +135,68 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) return IRQ_NONE; }
-static void get_acp63_device_config(u32 config, struct pci_dev *pci,
struct acp63_dev_data *acp_data)
+static int sdw_amd_scan_controller(struct device *dev) +{
- struct acp63_dev_data *acp_data;
- struct fwnode_handle *link;
- char name[32];
- u8 count = 0;
- u32 acp_sdw_power_mode = 0;
- int index;
- int ret;
- acp_data = dev_get_drvdata(dev);
- acp_data->acp_sdw_power_off = true;
- /* Found controller, find links supported */
- ret = fwnode_property_read_u8_array((acp_data->sdw_fw_node),
"mipi-sdw-master-count", &count, 1);
- if (ret) {
dev_err(dev,
"Failed to read mipi-sdw-master-count: %d\n", ret);
one line?
will fix it.
return -EINVAL;
- }
- /* Check count is within bounds */
- if (count > AMD_SDW_MAX_CONTROLLERS) {
dev_err(dev, "Controller count %d exceeds max %d\n",
count, AMD_SDW_MAX_CONTROLLERS);
No. controllers and masters are different concepts, see the DisCo specification for SoundWire. A Controller can have multiple Masters.
Will correct it.
return -EINVAL;
- }
- if (!count) {
dev_warn(dev, "No SoundWire controllers detected\n");
return -EINVAL;
- }
is this really a warning, looks like a dev_dbg or info to me.
- dev_dbg(dev, "ACPI reports %d Soundwire Controller devices\n", count);
the term device is incorrect here, the DisCo spec does not expose ACPI devices for each master.
"ACPI reports %d Managers"
will correct it.
- acp_data->sdw_master_count = count;
- for (index = 0; index < count; index++) {
snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", index);
link = fwnode_get_named_child_node(acp_data->sdw_fw_node, name);
if (!link) {
dev_err(dev, "Master node %s not found\n", name);
return -EIO;
}
fwnode_property_read_u32(link, "amd-sdw-power-mode",
&acp_sdw_power_mode);
if (acp_sdw_power_mode != AMD_SDW_POWER_OFF_MODE)
acp_data->acp_sdw_power_off = false;
does power-off mean 'clock-stop'? No. We will add comment for acp_sdw_power_off flag.
- }
- return 0;
+}
if (is_dmic_dev && is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS;
break;
so the cover letter is indeed wrong and confuses two controllers for two managers.
ACP IP has two independent manager instances driven by separate controller each which are connected in different power domains.
we should create two separate ACPI companion devices for separate manager instance. Currently we have limitations with BIOS. we are going with single ACPI companion device. We will update the changes later.
default:
return -EINVAL;
}
} else if (is_dmic_dev) { acp_data->pdev_mask = ACP63_PDM_DEV_MASK; acp_data->pdev_count = ACP63_PDM_MODE_DEVS;
} else if (is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_MODE_DEVS;
break;
default:
return -EINVAL;
} break;}
- default:
}break;
- return 0;
}
if (is_dmic_dev && is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS;
break;
so the cover letter is indeed wrong and confuses two controllers for two managers.
ACP IP has two independent manager instances driven by separate controller each which are connected in different power domains.
we should create two separate ACPI companion devices for separate manager instance. Currently we have limitations with BIOS. we are going with single ACPI companion device. We will update the changes later.
Humm, this is tricky. The BIOS interface isn't something that can be changed at will on the kernel side, you'd have to maintain two solutions with a means to detect which one to use.
Or is this is a temporary issue on development devices, then that part should probably not be upstreamed.
On 13/01/23 22:41, Pierre-Louis Bossart wrote:
if (is_dmic_dev && is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS;
break;
so the cover letter is indeed wrong and confuses two controllers for two managers.
ACP IP has two independent manager instances driven by separate controller each which are connected in different power domains.
we should create two separate ACPI companion devices for separate manager instance. Currently we have limitations with BIOS. we are going with single ACPI companion device. We will update the changes later.
Humm, this is tricky. The BIOS interface isn't something that can be changed at will on the kernel side, you'd have to maintain two solutions with a means to detect which one to use.
Or is this is a temporary issue on development devices, then that part should probably not be upstreamed.
It's a temporary issue on development devices. We had discussion with Windows dev team and BIOS team. They have agreed to modify ACPI companion device logic. We will update the two companion devices logic for two manager instances in V2 version.
On 16/01/23 13:32, Mukunda,Vijendar wrote:
On 13/01/23 22:41, Pierre-Louis Bossart wrote:
if (is_dmic_dev && is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS;
break;
so the cover letter is indeed wrong and confuses two controllers for two managers.
ACP IP has two independent manager instances driven by separate controller each which are connected in different power domains.
we should create two separate ACPI companion devices for separate manager instance. Currently we have limitations with BIOS. we are going with single ACPI companion device. We will update the changes later.
Humm, this is tricky. The BIOS interface isn't something that can be changed at will on the kernel side, you'd have to maintain two solutions with a means to detect which one to use.
Or is this is a temporary issue on development devices, then that part should probably not be upstreamed.
It's a temporary issue on development devices. We had discussion with Windows dev team and BIOS team. They have agreed to modify ACPI companion device logic. We will update the two companion devices logic for two manager instances in V2 version.
After experimenting, two ACPI companion devices approach, we got an update from Windows team, there is a limitation on windows stack. For current platform, we can't proceed with two ACPI companion devices.
Even on Linux side, if we create two ACPI companion devices followed by creating a single soundwire manager instance per Soundwire controller, we have observed an issue in a scenario, where similar codec parts(UID are also same) are connected on both soundwire manager instances.
As per MIPI Disco spec, for single link controllers Link ID should be set to zero. If we use Link ID as zero, for the soundwire manager which is on the second soundwire controller ACPI device scope, then soundwire framework is not allowing to create peripheral device node as its duplicate one.
If we want to support two ACPI companion device approach on our future platforms, how to proceed?
On 1/31/23 07:09, Mukunda,Vijendar wrote:
On 16/01/23 13:32, Mukunda,Vijendar wrote:
On 13/01/23 22:41, Pierre-Louis Bossart wrote:
if (is_dmic_dev && is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS;
break;
so the cover letter is indeed wrong and confuses two controllers for two managers.
ACP IP has two independent manager instances driven by separate controller each which are connected in different power domains.
we should create two separate ACPI companion devices for separate manager instance. Currently we have limitations with BIOS. we are going with single ACPI companion device. We will update the changes later.
Humm, this is tricky. The BIOS interface isn't something that can be changed at will on the kernel side, you'd have to maintain two solutions with a means to detect which one to use.
Or is this is a temporary issue on development devices, then that part should probably not be upstreamed.
It's a temporary issue on development devices. We had discussion with Windows dev team and BIOS team. They have agreed to modify ACPI companion device logic. We will update the two companion devices logic for two manager instances in V2 version.
After experimenting, two ACPI companion devices approach, we got an update from Windows team, there is a limitation on windows stack. For current platform, we can't proceed with two ACPI companion devices.
Even on Linux side, if we create two ACPI companion devices followed by creating a single soundwire manager instance per Soundwire controller, we have observed an issue in a scenario, where similar codec parts(UID are also same) are connected on both soundwire manager instances.
If I'm not mistaken, the specific failure in the Linux stack is because of the duplicated sysfs files since the same UID is used on both manager instances, right?
At least with how the kernel handles it today I don't see how this should be handled. You can't disambiguate between the two different ACPI devices when they would be identical unless you had another property.
As per MIPI Disco spec, for single link controllers Link ID should be set to zero. If we use Link ID as zero, for the soundwire manager which is on the second soundwire controller ACPI device scope, then soundwire framework is not allowing to create peripheral device node as its duplicate one.
If we want to support two ACPI companion device approach on our future platforms, how to proceed?
From my understanding I would think this should be an exception for ps platform, but this should be discussed for a future spec revision.
Maybe in a future spec revision another property can be utilized in conjunction with ACPI device to disambiguate this case.
On 1/31/23 07:09, Mukunda,Vijendar wrote:
On 16/01/23 13:32, Mukunda,Vijendar wrote:
On 13/01/23 22:41, Pierre-Louis Bossart wrote:
if (is_dmic_dev && is_sdw_dev) {
switch (acp_data->sdw_master_count) {
case 1:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS;
break;
case 2:
acp_data->pdev_mask = ACP63_SDW_PDM_DEV_MASK;
acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS;
break;
so the cover letter is indeed wrong and confuses two controllers for two managers.
ACP IP has two independent manager instances driven by separate controller each which are connected in different power domains.
we should create two separate ACPI companion devices for separate manager instance. Currently we have limitations with BIOS. we are going with single ACPI companion device. We will update the changes later.
Humm, this is tricky. The BIOS interface isn't something that can be changed at will on the kernel side, you'd have to maintain two solutions with a means to detect which one to use.
Or is this is a temporary issue on development devices, then that part should probably not be upstreamed.
It's a temporary issue on development devices. We had discussion with Windows dev team and BIOS team. They have agreed to modify ACPI companion device logic. We will update the two companion devices logic for two manager instances in V2 version.
After experimenting, two ACPI companion devices approach, we got an update from Windows team, there is a limitation on windows stack. For current platform, we can't proceed with two ACPI companion devices.
so how would the two controllers be declared then in the DSDT used by Windows? There's a contradiction between having a single companion device and the ability to set the 'manager-number' to one.
You probably want to give an example of what you have, otherwise we probably will talk past each other.
Even on Linux side, if we create two ACPI companion devices followed by creating a single soundwire manager instance per Soundwire controller, we have observed an issue in a scenario, where similar codec parts(UID are also same) are connected on both soundwire manager instances.
We've been handling this case of two identical amplifiers on two different links for the last 3 years. I don't see how this could be a problem, the codecs are declared in the scope of the companion device and the _ADR defines in bits [51..48] which link the codec is connected to.
see example below from a TigerLake device with two identical amsp on link 1 and 2.
Scope (_SB.PC00.HDAS.SNDW) { Device (SWD1) { Name (_ADR, 0x000131025D131601) // _ADR: Address
Device (SWD2) { Name (_ADR, 0x000230025D131601) // _ADR: Address
As per MIPI Disco spec, for single link controllers Link ID should be set to zero. If we use Link ID as zero, for the soundwire manager which is on the second soundwire controller ACPI device scope, then soundwire framework is not allowing to create peripheral device node as its duplicate one.
I still don't see how it's possible. There is an IDA used in the bus allocation
static int sdw_get_id(struct sdw_bus *bus) { int rc = ida_alloc(&sdw_bus_ida, GFP_KERNEL);
if (rc < 0) return rc;
bus->id = rc; return 0; }
and that's used for debugfs
/* create the debugfs master-N */ snprintf(name, sizeof(name), "master-%d-%d", bus->id, bus->link_id);
as well as in sdw_master_device_add(): dev_set_name(&md->dev, "sdw-master-%d", bus->id);
can you clarify what part of the 'SoundWire framework' is problematic? I guess the problem is that you have identical devices with the same _ADR under the same manager, which is problematic indeed, but that's not a SoundWire framework issue, just not a supported configuration.
If we want to support two ACPI companion device approach on our future platforms, how to proceed?
Well how about dealing with a single companion device first, cause that's what you have now and that's already problematic.
[Public]
-----Original Message----- From: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Sent: Tuesday, January 31, 2023 10:01 To: Mukunda, Vijendar Vijendar.Mukunda@amd.com; broonie@kernel.org; vkoul@kernel.org; alsa-devel@alsa-project.org Cc: Katragadda, Mastan Mastan.Katragadda@amd.com; Dommati, Sunil- kumar Sunil-kumar.Dommati@amd.com; open list <linux- kernel@vger.kernel.org>; Hiregoudar, Basavaraj Basavaraj.Hiregoudar@amd.com; Takashi Iwai tiwai@suse.com; Liam Girdwood lgirdwood@gmail.com; Nathan Chancellor nathan@kernel.org; Limonciello, Mario Mario.Limonciello@amd.com; kondaveeti, Arungopal Arungopal.kondaveeti@amd.com; Sanyog Kale sanyog.r.kale@intel.com; Bard Liao yung-chuan.liao@linux.intel.com; Saba Kareem, Syed Syed.SabaKareem@amd.com Subject: Re: [PATCH 01/19] ASoC: amd: ps: create platform devices based on acp config
On 1/31/23 07:09, Mukunda,Vijendar wrote:
On 16/01/23 13:32, Mukunda,Vijendar wrote:
On 13/01/23 22:41, Pierre-Louis Bossart wrote:
> + if (is_dmic_dev && is_sdw_dev) { > + switch (acp_data->sdw_master_count) { > + case 1: > + acp_data->pdev_mask =
ACP63_SDW_PDM_DEV_MASK;
> + acp_data->pdev_count =
ACP63_SDW0_PDM_MODE_DEVS;
> + break; > + case 2: > + acp_data->pdev_mask =
ACP63_SDW_PDM_DEV_MASK;
> + acp_data->pdev_count =
ACP63_SDW0_SDW1_PDM_MODE_DEVS;
> + break; so the cover letter is indeed wrong and confuses two controllers for
two
managers.
ACP IP has two independent manager instances driven by separate
controller
each which are connected in different power domains.
we should create two separate ACPI companion devices for separate manager instance. Currently we have limitations with BIOS. we are going with single ACPI companion device. We will update the changes later.
Humm, this is tricky. The BIOS interface isn't something that can be changed at will on the kernel side, you'd have to maintain two solutions with a means to detect which one to use.
Or is this is a temporary issue on development devices, then that part should probably not be upstreamed.
It's a temporary issue on development devices. We had discussion with Windows dev team and BIOS team. They have agreed to modify ACPI companion device logic. We will update the two companion devices logic for two manager instances in V2 version.
After experimenting, two ACPI companion devices approach, we got an update from Windows team, there is a limitation on windows stack. For current platform, we can't proceed with two ACPI companion devices.
so how would the two controllers be declared then in the DSDT used by Windows? There's a contradiction between having a single companion device and the ability to set the 'manager-number' to one.
You probably want to give an example of what you have, otherwise we probably will talk past each other.
Even on Linux side, if we create two ACPI companion devices followed by creating a single soundwire manager instance per Soundwire controller, we have observed an issue in a scenario, where similar codec parts(UID are also same) are connected on both soundwire manager instances.
We've been handling this case of two identical amplifiers on two different links for the last 3 years. I don't see how this could be a problem, the codecs are declared in the scope of the companion device and the _ADR defines in bits [51..48] which link the codec is connected to.
The problem is that there are two managers in the specified AMD design, and the codecs are both on "Link 0" for each manager.
So the _ADR really is identical for both.
see example below from a TigerLake device with two identical amsp on link 1 and 2.
Scope (_SB.PC00.HDAS.SNDW) { Device (SWD1) { Name (_ADR, 0x000131025D131601) // _ADR: Address
Device (SWD2) { Name (_ADR, 0x000230025D131601) // _ADR: Address
As per MIPI Disco spec, for single link controllers Link ID should be set to zero. If we use Link ID as zero, for the soundwire manager which is on the second soundwire controller ACPI device scope, then soundwire framework is not allowing to create peripheral device node as its duplicate one.
I still don't see how it's possible. There is an IDA used in the bus allocation
static int sdw_get_id(struct sdw_bus *bus) { int rc = ida_alloc(&sdw_bus_ida, GFP_KERNEL);
if (rc < 0) return rc;
bus->id = rc; return 0; }
and that's used for debugfs
/* create the debugfs master-N */ snprintf(name, sizeof(name), "master-%d-%d", bus->id, bus-
link_id);
as well as in sdw_master_device_add(): dev_set_name(&md->dev, "sdw-master-%d", bus->id);
can you clarify what part of the 'SoundWire framework' is problematic? I guess the problem is that you have identical devices with the same _ADR under the same manager, which is problematic indeed, but that's not a SoundWire framework issue, just not a supported configuration.
If we want to support two ACPI companion device approach on our future platforms, how to proceed?
Well how about dealing with a single companion device first, cause that's what you have now and that's already problematic.
we should create two separate ACPI companion devices for separate manager instance. Currently we have limitations with BIOS. we are going with single ACPI companion device. We will update the changes later.
Humm, this is tricky. The BIOS interface isn't something that can be changed at will on the kernel side, you'd have to maintain two solutions with a means to detect which one to use.
Or is this is a temporary issue on development devices, then that part should probably not be upstreamed.
It's a temporary issue on development devices. We had discussion with Windows dev team and BIOS team. They have agreed to modify ACPI companion device logic. We will update the two companion devices logic for two manager instances in V2 version.
After experimenting, two ACPI companion devices approach, we got an update from Windows team, there is a limitation on windows stack. For current platform, we can't proceed with two ACPI companion devices.
so how would the two controllers be declared then in the DSDT used by Windows? There's a contradiction between having a single companion device and the ability to set the 'manager-number' to one.
You probably want to give an example of what you have, otherwise we probably will talk past each other.
Even on Linux side, if we create two ACPI companion devices followed by creating a single soundwire manager instance per Soundwire controller, we have observed an issue in a scenario, where similar codec parts(UID are also same) are connected on both soundwire manager instances.
We've been handling this case of two identical amplifiers on two different links for the last 3 years. I don't see how this could be a problem, the codecs are declared in the scope of the companion device and the _ADR defines in bits [51..48] which link the codec is connected to.
The problem is that there are two managers in the specified AMD design, and the codecs are both on "Link 0" for each manager.
You're confusing Controller and Manager.
A Manager is the same as a 'Link', the two terms are interchangeable. It makes no sense to refer to a link number for a manager because there is no such concept.
Only a Controller can have multiple links or managers. And each Controller needs to be declared as an ACPI device if you want to use the DisCo properties.
The Managers/Links are not described as ACPI devices, that's a regrettable design decision made in MIPI circles many moons ago, that's why in the Intel code we have to manually create auxiliary devices based on the 'mipi-sdw-master-count' property.
So the _ADR really is identical for both.
That cannot possible work, even for Windows. You need to have a controller scope, and the _ADR can then be identical for different peripherals as long as this ADR is local to a controller scope.
On 01/02/23 06:21, Pierre-Louis Bossart wrote:
> we should create two separate ACPI companion devices for separate > manager instance. Currently we have limitations with BIOS. > we are going with single ACPI companion device. > We will update the changes later. Humm, this is tricky. The BIOS interface isn't something that can be changed at will on the kernel side, you'd have to maintain two solutions with a means to detect which one to use.
Or is this is a temporary issue on development devices, then that part should probably not be upstreamed.
It's a temporary issue on development devices. We had discussion with Windows dev team and BIOS team. They have agreed to modify ACPI companion device logic. We will update the two companion devices logic for two manager instances in V2 version.
After experimenting, two ACPI companion devices approach, we got an update from Windows team, there is a limitation on windows stack. For current platform, we can't proceed with two ACPI companion devices.
so how would the two controllers be declared then in the DSDT used by Windows? There's a contradiction between having a single companion device and the ability to set the 'manager-number' to one.
You probably want to give an example of what you have, otherwise we probably will talk past each other.
Even on Linux side, if we create two ACPI companion devices followed by creating a single soundwire manager instance per Soundwire controller, we have observed an issue in a scenario, where similar codec parts(UID are also same) are connected on both soundwire manager instances.
We've been handling this case of two identical amplifiers on two different links for the last 3 years. I don't see how this could be a problem, the codecs are declared in the scope of the companion device and the _ADR defines in bits [51..48] which link the codec is connected to.
The problem is that there are two managers in the specified AMD design, and the codecs are both on "Link 0" for each manager.
You're confusing Controller and Manager.
A Manager is the same as a 'Link', the two terms are interchangeable. It makes no sense to refer to a link number for a manager because there is no such concept.
Only a Controller can have multiple links or managers. And each Controller needs to be declared as an ACPI device if you want to use the DisCo properties.
The Managers/Links are not described as ACPI devices, that's a regrettable design decision made in MIPI circles many moons ago, that's why in the Intel code we have to manually create auxiliary devices based on the 'mipi-sdw-master-count' property.
So the _ADR really is identical for both.
Yes Controller has ACPI scope. Under controller based on "mipi-sdw-manager-list" property manager instances will be created. Manager and Link terms are interchangeable.
Below is the sample DSDT file if we go with two ACPI companion devices.
Scope (_SB.ACP) {
Device (SWC0) { Name (_ADR, 0x05) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 1}, // v 1.0 }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), // Hierarchical Extension Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
} // END SWC0
Device (SWC1) { Name (_ADR, 0x09) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 1}, }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
} // END SWC1 } }
In above case, two manager instances will be created. When manager under SWC1 scope tries to add peripheral device, In sdw_slave_add() API its failing because peripheral device descriptor uses link id followed by 48bit encoded address. In above scenarios, both the manager's link id is zero only.
That cannot possible work, even for Windows. You need to have a controller scope, and the _ADR can then be identical for different peripherals as long as this ADR is local to a controller scope.
Yes Controller has ACPI scope. Under controller based on "mipi-sdw-manager-list" property manager instances will be created. Manager and Link terms are interchangeable.
Below is the sample DSDT file if we go with two ACPI companion devices.
Scope (_SB.ACP) {
Device (SWC0) { Name (_ADR, 0x05) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 1}, // v 1.0 }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), // Hierarchical Extension Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
} // END SWC0
Device (SWC1) { Name (_ADR, 0x09) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 1}, }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
} // END SWC1 } }
that looks good to me.
In above case, two manager instances will be created. When manager under SWC1 scope tries to add peripheral device, In sdw_slave_add() API its failing because peripheral device descriptor uses link id followed by 48bit encoded address. In above scenarios, both the manager's link id is zero only.
what fails exactly? The device_register() ?
If yes, what the issue. the device name?
I wonder if we need to use something like
"name shall be sdw:bus_id:link:mfg:part:class"
so as to uniquify the device name, if that was the problem.
On 01/02/23 07:33, Pierre-Louis Bossart wrote:
Yes Controller has ACPI scope. Under controller based on "mipi-sdw-manager-list" property manager instances will be created. Manager and Link terms are interchangeable.
Below is the sample DSDT file if we go with two ACPI companion devices.
Scope (_SB.ACP) {
Device (SWC0) { Name (_ADR, 0x05) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 1}, // v 1.0 }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), // Hierarchical Extension Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
} // END SWC0
Device (SWC1) { Name (_ADR, 0x09) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 1}, }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
} // END SWC1 } }
that looks good to me.
In above case, two manager instances will be created. When manager under SWC1 scope tries to add peripheral device, In sdw_slave_add() API its failing because peripheral device descriptor uses link id followed by 48bit encoded address. In above scenarios, both the manager's link id is zero only.
what fails exactly? The device_register() ?
If yes, what the issue. the device name?
device_register() is failing because of duplication of device name.
I wonder if we need to use something like
"name shall be sdw:bus_id:link:mfg:part:class"
so as to uniquify the device name, if that was the problem.
Yes correct.
In above case, two manager instances will be created. When manager under SWC1 scope tries to add peripheral device, In sdw_slave_add() API its failing because peripheral device descriptor uses link id followed by 48bit encoded address. In above scenarios, both the manager's link id is zero only.
what fails exactly? The device_register() ?
If yes, what the issue. the device name?
device_register() is failing because of duplication of device name.
I wonder if we need to use something like
"name shall be sdw:bus_id:link:mfg:part:class"
so as to uniquify the device name, if that was the problem.
Yes correct.
can you check https://github.com/thesofproject/linux/pull/4165 and see if this works for you? I tested it on Intel platforms.
On 01/02/23 09:22, Pierre-Louis Bossart wrote:
In above case, two manager instances will be created. When manager under SWC1 scope tries to add peripheral device, In sdw_slave_add() API its failing because peripheral device descriptor uses link id followed by 48bit encoded address. In above scenarios, both the manager's link id is zero only.
what fails exactly? The device_register() ?
If yes, what the issue. the device name?
device_register() is failing because of duplication of device name.
I wonder if we need to use something like
"name shall be sdw:bus_id:link:mfg:part:class"
so as to uniquify the device name, if that was the problem.
Yes correct.
can you check https://github.com/thesofproject/linux/pull/4165 and see if this works for you? I tested it on Intel platforms.
It's working fine on our platform. As mentioned earlier in this thread, we can't go with two ACPI companion device approach due to limitations on windows stack for current platform.
On 2/1/23 00:01, Mukunda,Vijendar wrote:
On 01/02/23 09:22, Pierre-Louis Bossart wrote:
In above case, two manager instances will be created. When manager under SWC1 scope tries to add peripheral device, In sdw_slave_add() API its failing because peripheral device descriptor uses link id followed by 48bit encoded address. In above scenarios, both the manager's link id is zero only.
what fails exactly? The device_register() ?
If yes, what the issue. the device name?
device_register() is failing because of duplication of device name.
I wonder if we need to use something like
"name shall be sdw:bus_id:link:mfg:part:class"
so as to uniquify the device name, if that was the problem.
Yes correct.
can you check https://github.com/thesofproject/linux/pull/4165 and see if this works for you? I tested it on Intel platforms.
It's working fine on our platform. As mentioned earlier in this thread, we can't go with two ACPI companion device approach due to limitations on windows stack for current platform.
Thanks for testing.
So if you can't go with 2 ACPI companion devices, what does the 'Windows' DSDT look like and how would you identify that there are two controllers on the platform?
On 02/02/23 04:38, Pierre-Louis Bossart wrote:
On 2/1/23 00:01, Mukunda,Vijendar wrote:
On 01/02/23 09:22, Pierre-Louis Bossart wrote:
In above case, two manager instances will be created. When manager under SWC1 scope tries to add peripheral device, In sdw_slave_add() API its failing because peripheral device descriptor uses link id followed by 48bit encoded address. In above scenarios, both the manager's link id is zero only.
what fails exactly? The device_register() ?
If yes, what the issue. the device name?
device_register() is failing because of duplication of device name.
I wonder if we need to use something like
"name shall be sdw:bus_id:link:mfg:part:class"
so as to uniquify the device name, if that was the problem.
Yes correct.
can you check https://github.com/thesofproject/linux/pull/4165 and see if this works for you? I tested it on Intel platforms.
It's working fine on our platform. As mentioned earlier in this thread, we can't go with two ACPI companion device approach due to limitations on windows stack for current platform.
Thanks for testing.
So if you can't go with 2 ACPI companion devices, what does the 'Windows' DSDT look like and how would you identify that there are two controllers on the platform?
We are not populating two controller devices. Instead of it, we are populating single controller device with two independent manager instances under the same ACPI device scope. We have configuration register to identify sound wire manager instances on the platform. Below is the sample DSDT for Windows & Linux.
Scope (_SB.ACP) { Device (SDWC) { Name (_ADR, 0x05) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 2}, }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, Package (2) {"mipi-sdw-link-1-subproperties", "SWM1"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM Name(SWM1,Package(){ ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM1 additional properties } }) // End SWM1.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
Device (SLV1) { // SoundWire Slave 1 Name(_ADR, 0x000130025D131601) } // END SLV1
} // END SDWC }
> In above case, two manager instances will be created. > When manager under SWC1 scope tries to add peripheral > device, In sdw_slave_add() API its failing because peripheral > device descriptor uses link id followed by 48bit encoded address. > In above scenarios, both the manager's link id is zero only.
So here you're reporting that the issue is that all devices use link0 ...
what fails exactly? The device_register() ?
If yes, what the issue. the device name?
device_register() is failing because of duplication of device name.
I wonder if we need to use something like
"name shall be sdw:bus_id:link:mfg:part:class"
so as to uniquify the device name, if that was the problem.
Yes correct.
can you check https://github.com/thesofproject/linux/pull/4165 and see if this works for you? I tested it on Intel platforms.
It's working fine on our platform. As mentioned earlier in this thread, we can't go with two ACPI companion device approach due to limitations on windows stack for current platform.
Thanks for testing.
So if you can't go with 2 ACPI companion devices, what does the 'Windows' DSDT look like and how would you identify that there are two controllers on the platform?
We are not populating two controller devices. Instead of it, we are populating single controller device with two independent manager instances under the same ACPI device scope. We have configuration register to identify sound wire manager instances on the platform. Below is the sample DSDT for Windows & Linux.
Scope (_SB.ACP) { Device (SDWC) { Name (_ADR, 0x05) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 2}, }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, Package (2) {"mipi-sdw-link-1-subproperties", "SWM1"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM Name(SWM1,Package(){ ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM1 additional properties } }) // End SWM1.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
Device (SLV1) { // SoundWire Slave 1 Name(_ADR, 0x000130025D131601) } // END SLV1
... but here you have two different link numbers.
I interpret this as SLV0 on link0 and SLV1 on link1.
So what's the issue?
On 06/02/23 20:20, Pierre-Louis Bossart wrote:
>> In above case, two manager instances will be created. >> When manager under SWC1 scope tries to add peripheral >> device, In sdw_slave_add() API its failing because peripheral >> device descriptor uses link id followed by 48bit encoded address. >> In above scenarios, both the manager's link id is zero only.
So here you're reporting that the issue is that all devices use link0 ...
> what fails exactly? The device_register() ? > > If yes, what the issue. the device name? device_register() is failing because of duplication of device name. > I wonder if we need to use something like > > "name shall be sdw:bus_id:link:mfg:part:class" > > so as to uniquify the device name, if that was the problem. Yes correct.
can you check https://github.com/thesofproject/linux/pull/4165 and see if this works for you? I tested it on Intel platforms.
It's working fine on our platform. As mentioned earlier in this thread, we can't go with two ACPI companion device approach due to limitations on windows stack for current platform.
Thanks for testing.
So if you can't go with 2 ACPI companion devices, what does the 'Windows' DSDT look like and how would you identify that there are two controllers on the platform?
We are not populating two controller devices. Instead of it, we are populating single controller device with two independent manager instances under the same ACPI device scope. We have configuration register to identify sound wire manager instances on the platform. Below is the sample DSDT for Windows & Linux.
Scope (_SB.ACP) { Device (SDWC) { Name (_ADR, 0x05) // _ADR: Address Name(_DSD, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, Package (2) {"mipi-sdw-manager-list", 2}, }, ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), Package () { Package (2) {"mipi-sdw-link-0-subproperties", "SWM0"}, Package (2) {"mipi-sdw-link-1-subproperties", "SWM1"}, } }) // End _DSD Name(SWM0, Package() { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM0 additional properties } }) // End SWM0.SWM Name(SWM1,Package(){ ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package (2) {"mipi-sdw-sw-interface-revision", 0x00010000}, // ... place holder for SWM1 additional properties } }) // End SWM1.SWM
Device (SLV0) { // SoundWire Slave 0 Name(_ADR, 0x000032025D131601) } // END SLV0
Device (SLV1) { // SoundWire Slave 1 Name(_ADR, 0x000130025D131601) } // END SLV1
... but here you have two different link numbers.
I interpret this as SLV0 on link0 and SLV1 on link1.
So what's the issue?
This solution works fine for us. We have shared sample DSDT for reference. By reading the ACP configuration register, controller count information is retrieved. Each Controller device scope has a single manager instance. To support this design, we need to create two ACPI companion devices. Under each controller device, one manager instance device is scoped. In this case, we will read "mipi-sdw-manager-list" as 1 for each controller. As per your review comment, we can't go with two ACPI companion devices approach due to Windows stack limitation. Windows DSDT implementation will refer to single ACPI companion device with two manager instances as mentioned in earlier reply. We are going to use the same for Linux.
AMD ACP IP block has two soundwire controller devices. Add support for - Master driver probe & remove sequence - Helper functions to enable/disable interrupts, Initialize sdw controller, enable sdw pads - Master driver sdw_master_ops & port_ops callbacks
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- drivers/soundwire/amd_master.c | 1075 +++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 279 ++++++++ include/linux/soundwire/sdw_amd.h | 21 + 3 files changed, 1375 insertions(+) create mode 100644 drivers/soundwire/amd_master.c create mode 100644 drivers/soundwire/amd_master.h
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c new file mode 100644 index 000000000000..7e1f618254ac --- /dev/null +++ b/drivers/soundwire/amd_master.c @@ -0,0 +1,1075 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SoundWire AMD Master driver + * + * Copyright 2023 Advanced Micro Devices, Inc. + */ + +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw_amd.h> +#include <linux/wait.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bus.h" +#include "amd_master.h" + +#define DRV_NAME "amd_sdw_controller" + +#define to_amd_sdw(b) container_of(b, struct amd_sdwc_ctrl, bus) + +static int amd_enable_sdw_pads(struct amd_sdwc_ctrl *ctrl) +{ + u32 sw_pad_enable_mask; + u32 sw_pad_pulldown_mask; + u32 sw_pad_pulldown_val; + u32 val = 0; + + switch (ctrl->instance) { + case ACP_SDW0: + sw_pad_enable_mask = AMD_SDW0_PAD_KEEPER_EN_MASK; + sw_pad_pulldown_mask = AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK; + break; + case ACP_SDW1: + sw_pad_enable_mask = AMD_SDW1_PAD_KEEPER_EN_MASK; + sw_pad_pulldown_mask = AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK; + break; + default: + return -EINVAL; + } + + mutex_lock(ctrl->sdw_lock); + val = acp_reg_readl(ctrl->mmio + ACP_SW_PAD_KEEPER_EN); + val |= sw_pad_enable_mask; + acp_reg_writel(val, ctrl->mmio + ACP_SW_PAD_KEEPER_EN); + mutex_unlock(ctrl->sdw_lock); + usleep_range(1000, 1500); + + mutex_lock(ctrl->sdw_lock); + sw_pad_pulldown_val = acp_reg_readl(ctrl->mmio + ACP_PAD_PULLDOWN_CTRL); + sw_pad_pulldown_val &= sw_pad_pulldown_mask; + acp_reg_writel(sw_pad_pulldown_val, ctrl->mmio + ACP_PAD_PULLDOWN_CTRL); + mutex_unlock(ctrl->sdw_lock); + return 0; +} + +static int amd_init_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{ + u32 acp_sw_en_reg, acp_sw_en_stat_reg, sw_bus_reset_reg; + u32 val = 0; + u32 timeout = 0; + u32 retry_count = 0; + + switch (ctrl->instance) { + case ACP_SDW0: + acp_sw_en_reg = ACP_SW_EN; + acp_sw_en_stat_reg = ACP_SW_EN_STATUS; + sw_bus_reset_reg = ACP_SW_BUS_RESET_CTRL; + break; + case ACP_SDW1: + acp_sw_en_reg = ACP_P1_SW_EN; + acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS; + sw_bus_reset_reg = ACP_P1_SW_BUS_RESET_CTRL; + break; + default: + return -EINVAL; + } + + acp_reg_writel(AMD_SDW_ENABLE, ctrl->mmio + acp_sw_en_reg); + do { + val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg); + if (val) + break; + usleep_range(10, 50); + } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT); + + if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT) + return -ETIMEDOUT; + + /* Sdw Controller reset */ + acp_reg_writel(AMD_SDW_BUS_RESET_REQ, ctrl->mmio + sw_bus_reset_reg); + val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg); + while (!(val & AMD_SDW_BUS_RESET_DONE)) { + val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg); + if (timeout > AMD_DELAY_LOOP_ITERATION) + break; + usleep_range(1, 5); + timeout++; + } + timeout = 0; + acp_reg_writel(AMD_SDW_BUS_RESET_CLEAR_REQ, ctrl->mmio + sw_bus_reset_reg); + val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg); + while (val) { + val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg); + if (timeout > AMD_DELAY_LOOP_ITERATION) + break; + usleep_range(1, 5); + timeout++; + } + if (timeout == AMD_DELAY_LOOP_ITERATION) { + dev_err(ctrl->dev, "Failed to reset SW%x Soundwire Controller\n", ctrl->instance); + return -ETIMEDOUT; + } + retry_count = 0; + acp_reg_writel(AMD_SDW_DISABLE, ctrl->mmio + acp_sw_en_reg); + do { + val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg); + if (!val) + break; + usleep_range(10, 50); + } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT); + + if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT) + return -ETIMEDOUT; + return 0; +} + +static int amd_enable_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{ + u32 acp_sw_en_reg; + u32 acp_sw_en_stat_reg; + u32 val = 0; + u32 retry_count = 0; + + switch (ctrl->instance) { + case ACP_SDW0: + acp_sw_en_reg = ACP_SW_EN; + acp_sw_en_stat_reg = ACP_SW_EN_STATUS; + break; + case ACP_SDW1: + acp_sw_en_reg = ACP_P1_SW_EN; + acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS; + break; + default: + return -EINVAL; + } + acp_reg_writel(AMD_SDW_ENABLE, ctrl->mmio + acp_sw_en_reg); + + do { + val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg); + if (val) + break; + usleep_range(10, 50); + } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT); + + if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT) + return -ETIMEDOUT; + return 0; +} + +static int amd_disable_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{ + u32 clk_resume_ctrl_reg; + u32 acp_sw_en_reg; + u32 acp_sw_en_stat_reg; + u32 val = 0; + u32 retry_count = 0; + + switch (ctrl->instance) { + case ACP_SDW0: + acp_sw_en_reg = ACP_SW_EN; + acp_sw_en_stat_reg = ACP_SW_EN_STATUS; + clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL; + break; + case ACP_SDW1: + acp_sw_en_reg = ACP_P1_SW_EN; + acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS; + clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL; + break; + default: + return -EINVAL; + } + acp_reg_writel(AMD_SDW_DISABLE, ctrl->mmio + acp_sw_en_reg); + + /* + * After invoking controller disable sequence, check whether + * controller has executed clock stop sequence. In this case, + * controller should ignore checking enable status register. + */ + val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg); + if (val) + return 0; + + do { + val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg); + if (!val) + break; + usleep_range(10, 50); + } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT); + + if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT) + return -ETIMEDOUT; + return 0; +} + +static int amd_enable_sdw_interrupts(struct amd_sdwc_ctrl *ctrl) +{ + u32 val; + u32 acp_ext_intr_stat, acp_ext_intr_ctrl, acp_sdw_intr_mask; + u32 sw_stat_mask_0to7, sw_stat_mask_8to11, sw_err_intr_mask; + + switch (ctrl->instance) { + case ACP_SDW0: + acp_ext_intr_ctrl = ACP_EXTERNAL_INTR_CNTL; + acp_sdw_intr_mask = AMD_SDW0_EXT_INTR_MASK; + acp_ext_intr_stat = ACP_EXTERNAL_INTR_STAT; + sw_stat_mask_0to7 = SW_STATE_CHANGE_STATUS_MASK_0TO7; + sw_stat_mask_8to11 = SW_STATE_CHANGE_STATUS_MASK_8TO11; + sw_err_intr_mask = SW_ERROR_INTR_MASK; + break; + case ACP_SDW1: + acp_ext_intr_ctrl = ACP_EXTERNAL_INTR_CNTL1; + acp_sdw_intr_mask = AMD_SDW1_EXT_INTR_MASK; + acp_ext_intr_stat = ACP_EXTERNAL_INTR_STAT1; + sw_stat_mask_0to7 = P1_SW_STATE_CHANGE_STATUS_MASK_0TO7; + sw_stat_mask_8to11 = P1_SW_STATE_CHANGE_STATUS_MASK_8TO11; + sw_err_intr_mask = P1_SW_ERROR_INTR_MASK; + break; + default: + return -EINVAL; + } + mutex_lock(ctrl->sdw_lock); + val = acp_reg_readl(ctrl->mmio + acp_ext_intr_ctrl); + val |= acp_sdw_intr_mask; + acp_reg_writel(val, ctrl->mmio + acp_ext_intr_ctrl); + val = acp_reg_readl(ctrl->mmio + acp_ext_intr_ctrl); + mutex_unlock(ctrl->sdw_lock); + dev_dbg(ctrl->dev, "%s: acp_ext_intr_ctrl[0x%x]:0x%x\n", __func__, acp_ext_intr_ctrl, val); + val = acp_reg_readl(ctrl->mmio + acp_ext_intr_stat); + if (val) + acp_reg_writel(val, ctrl->mmio + acp_ext_intr_stat); + acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, ctrl->mmio + sw_stat_mask_0to7); + acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, ctrl->mmio + sw_stat_mask_8to11); + acp_reg_writel(AMD_SDW_IRQ_ERROR_MASK, ctrl->mmio + sw_err_intr_mask); + return 0; +} + +static int amd_disable_sdw_interrupts(struct amd_sdwc_ctrl *ctrl) +{ + u32 val; + u32 acp_ext_intr_cntl; + u32 acp_sdw_intr_mask; + u32 sw_stat_mask_0to7; + u32 sw_stat_mask_8to11; + u32 sw_err_intr_mask; + + switch (ctrl->instance) { + case ACP_SDW0: + acp_ext_intr_cntl = ACP_EXTERNAL_INTR_CNTL; + acp_sdw_intr_mask = AMD_SDW0_EXT_INTR_MASK; + sw_stat_mask_0to7 = SW_STATE_CHANGE_STATUS_MASK_0TO7; + sw_stat_mask_8to11 = SW_STATE_CHANGE_STATUS_MASK_8TO11; + sw_err_intr_mask = SW_ERROR_INTR_MASK; + break; + case ACP_SDW1: + acp_ext_intr_cntl = ACP_EXTERNAL_INTR_CNTL1; + acp_sdw_intr_mask = AMD_SDW1_EXT_INTR_MASK; + sw_stat_mask_0to7 = P1_SW_STATE_CHANGE_STATUS_MASK_0TO7; + sw_stat_mask_8to11 = P1_SW_STATE_CHANGE_STATUS_MASK_8TO11; + sw_err_intr_mask = P1_SW_ERROR_INTR_MASK; + break; + default: + return -EINVAL; + } + mutex_lock(ctrl->sdw_lock); + val = acp_reg_readl(ctrl->mmio + acp_ext_intr_cntl); + val &= ~acp_sdw_intr_mask; + acp_reg_writel(val, ctrl->mmio + acp_ext_intr_cntl); + mutex_unlock(ctrl->sdw_lock); + + acp_reg_writel(0x00, ctrl->mmio + sw_stat_mask_0to7); + acp_reg_writel(0x00, ctrl->mmio + sw_stat_mask_8to11); + acp_reg_writel(0x00, ctrl->mmio + sw_err_intr_mask); + return 0; +} + +static int amd_sdwc_set_frameshape(struct amd_sdwc_ctrl *ctrl, u32 rows, u32 cols) +{ + u32 sdw_rows, sdw_cols, frame_size; + u32 acp_sw_frame_reg; + + switch (ctrl->instance) { + case ACP_SDW0: + acp_sw_frame_reg = ACP_SW_FRAMESIZE; + break; + case ACP_SDW1: + acp_sw_frame_reg = ACP_P1_SW_FRAMESIZE; + break; + default: + return -EINVAL; + } + sdw_cols = sdw_find_col_index(cols); + sdw_rows = sdw_find_row_index(rows); + frame_size = (sdw_rows << 3) | sdw_cols; + acp_reg_writel(frame_size, ctrl->mmio + acp_sw_frame_reg); + return 0; +} + +static void amd_sdwc_ctl_word_prep(u32 *low_word, u32 *high_word, u32 cmd_type, + struct sdw_msg *msg, int cmd_offset) +{ + u32 low_data = 0, high_data = 0; + u16 addr; + u8 addr_high, addr_low; + u8 data = 0; + + addr = msg->addr + cmd_offset; + addr_high = (addr & 0xFF00) >> 8; + addr_low = addr & 0xFF; + + if (cmd_type == AMD_SDW_CMD_WRITE) + data = msg->buf[cmd_offset]; + + high_data = FIELD_PREP(AMD_SDW_MCP_CMD_DEV_ADDR, msg->dev_num); + high_data |= FIELD_PREP(AMD_SDW_MCP_CMD_COMMAND, cmd_type); + high_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_HIGH, addr_high); + low_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_LOW, addr_low); + low_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_DATA, data); + + *high_word = high_data; + *low_word = low_data; +} + +static u64 amd_sdwc_send_cmd_get_resp(struct amd_sdwc_ctrl *ctrl, u32 lword, u32 uword) +{ + u64 resp = 0; + u32 imm_cmd_stat_reg, imm_cmd_uword_reg, imm_cmd_lword_reg; + u32 imm_resp_uword_reg, imm_resp_lword_reg; + u32 resp_lower, resp_high; + u32 sts = 0; + u32 timeout = 0; + + switch (ctrl->instance) { + case ACP_SDW0: + imm_cmd_stat_reg = SW_IMM_CMD_STS; + imm_cmd_uword_reg = SW_IMM_CMD_UPPER_WORD; + imm_cmd_lword_reg = SW_IMM_CMD_LOWER_QWORD; + imm_resp_uword_reg = SW_IMM_RESP_UPPER_WORD; + imm_resp_lword_reg = SW_IMM_RESP_LOWER_QWORD; + break; + case ACP_SDW1: + imm_cmd_stat_reg = P1_SW_IMM_CMD_STS; + imm_cmd_uword_reg = P1_SW_IMM_CMD_UPPER_WORD; + imm_cmd_lword_reg = P1_SW_IMM_CMD_LOWER_QWORD; + imm_resp_uword_reg = P1_SW_IMM_RESP_UPPER_WORD; + imm_resp_lword_reg = P1_SW_IMM_RESP_LOWER_QWORD; + break; + default: + return -EINVAL; + } + sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg); + while (sts & AMD_SDW_IMM_CMD_BUSY) { + sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg); + if (timeout > AMD_SDW_RETRY_COUNT) { + dev_err(ctrl->dev, "SDW%x previous cmd status clear failed\n", + ctrl->instance); + return -ETIMEDOUT; + } + timeout++; + } + + timeout = 0; + if (sts & AMD_SDW_IMM_RES_VALID) { + dev_err(ctrl->dev, "SDW%x controller is in bad state\n", ctrl->instance); + acp_reg_writel(0x00, ctrl->mmio + imm_cmd_stat_reg); + } + acp_reg_writel(uword, ctrl->mmio + imm_cmd_uword_reg); + acp_reg_writel(lword, ctrl->mmio + imm_cmd_lword_reg); + + sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg); + while (!(sts & AMD_SDW_IMM_RES_VALID)) { + sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg); + if (timeout > AMD_SDW_RETRY_COUNT) { + dev_err(ctrl->dev, "SDW%x cmd response timeout occurred\n", ctrl->instance); + return -ETIMEDOUT; + } + timeout++; + } + resp_high = acp_reg_readl(ctrl->mmio + imm_resp_uword_reg); + resp_lower = acp_reg_readl(ctrl->mmio + imm_resp_lword_reg); + timeout = 0; + acp_reg_writel(AMD_SDW_IMM_RES_VALID, ctrl->mmio + imm_cmd_stat_reg); + while ((sts & AMD_SDW_IMM_RES_VALID)) { + sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg); + if (timeout > AMD_SDW_RETRY_COUNT) { + dev_err(ctrl->dev, "SDW%x cmd status retry failed\n", ctrl->instance); + return -ETIMEDOUT; + } + timeout++; + } + resp = resp_high; + resp = (resp << 32) | resp_lower; + return resp; +} + +static enum sdw_command_response +amd_program_scp_addr(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg) +{ + struct sdw_msg scp_msg = {0}; + u64 response_buf[2] = {0}; + u32 uword = 0, lword = 0; + int nack = 0, no_ack = 0; + int index, timeout = 0; + + scp_msg.dev_num = msg->dev_num; + scp_msg.addr = SDW_SCP_ADDRPAGE1; + scp_msg.buf = &msg->addr_page1; + amd_sdwc_ctl_word_prep(&lword, &uword, AMD_SDW_CMD_WRITE, &scp_msg, 0); + response_buf[0] = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword); + scp_msg.addr = SDW_SCP_ADDRPAGE2; + scp_msg.buf = &msg->addr_page2; + amd_sdwc_ctl_word_prep(&lword, &uword, AMD_SDW_CMD_WRITE, &scp_msg, 0); + response_buf[1] = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword); + + /* check response the writes */ + for (index = 0; index < 2; index++) { + if (response_buf[index] == -ETIMEDOUT) { + dev_err(ctrl->dev, "Program SCP cmd timeout\n"); + timeout = 1; + } else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) { + no_ack = 1; + if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) { + nack = 1; + dev_err(ctrl->dev, "Program SCP NACK received\n"); + } + } + } + + if (timeout) { + dev_err_ratelimited(ctrl->dev, + "SCP_addrpage command timeout for Slave %d\n", msg->dev_num); + return SDW_CMD_TIMEOUT; + } + + if (nack) { + dev_err_ratelimited(ctrl->dev, + "SCP_addrpage NACKed for Slave %d\n", msg->dev_num); + return SDW_CMD_FAIL; + } + + if (no_ack) { + dev_dbg_ratelimited(ctrl->dev, + "SCP_addrpage ignored for Slave %d\n", msg->dev_num); + return SDW_CMD_IGNORED; + } + return SDW_CMD_OK; +} + +static int amd_prep_msg(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg, int *cmd) +{ + int ret; + + if (msg->page) { + ret = amd_program_scp_addr(ctrl, msg); + if (ret) { + msg->len = 0; + return ret; + } + } + switch (msg->flags) { + case SDW_MSG_FLAG_READ: + *cmd = AMD_SDW_CMD_READ; + break; + case SDW_MSG_FLAG_WRITE: + *cmd = AMD_SDW_CMD_WRITE; + break; + default: + dev_err(ctrl->dev, "Invalid msg cmd: %d\n", msg->flags); + return -EINVAL; + } + return 0; +} + +static unsigned int _amd_sdwc_xfer_msg(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg, + int cmd, int cmd_offset) +{ + u64 response = 0; + u32 uword = 0, lword = 0; + int nack = 0, no_ack = 0; + int timeout = 0; + + amd_sdwc_ctl_word_prep(&lword, &uword, cmd, msg, cmd_offset); + response = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword); + + if (response & AMD_SDW_MCP_RESP_ACK) { + if (cmd == AMD_SDW_CMD_READ) + msg->buf[cmd_offset] = FIELD_GET(AMD_SDW_MCP_RESP_RDATA, response); + } else { + no_ack = 1; + if (response == -ETIMEDOUT) { + timeout = 1; + } else if (response & AMD_SDW_MCP_RESP_NACK) { + nack = 1; + dev_err(ctrl->dev, "Program SCP NACK received\n"); + } + } + + if (timeout) { + dev_err_ratelimited(ctrl->dev, "command timeout for Slave %d\n", msg->dev_num); + return SDW_CMD_TIMEOUT; + } + if (nack) { + dev_err_ratelimited(ctrl->dev, + "command response NACK received for Slave %d\n", msg->dev_num); + return SDW_CMD_FAIL; + } + + if (no_ack) { + dev_err_ratelimited(ctrl->dev, "command is ignored for Slave %d\n", msg->dev_num); + return SDW_CMD_IGNORED; + } + return SDW_CMD_OK; +} + +static enum sdw_command_response amd_sdwc_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) +{ + struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); + int ret, i; + int cmd = 0; + + ret = amd_prep_msg(ctrl, msg, &cmd); + if (ret) + return SDW_CMD_FAIL_OTHER; + for (i = 0; i < msg->len; i++) { + ret = _amd_sdwc_xfer_msg(ctrl, msg, cmd, i); + if (ret) + return ret; + } + return SDW_CMD_OK; +} + +static enum sdw_command_response +amd_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num) +{ + struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); + struct sdw_msg msg; + + /* Create dummy message with valid device number */ + memset(&msg, 0, sizeof(msg)); + msg.dev_num = dev_num; + return amd_program_scp_addr(ctrl, &msg); +} + +static u32 amd_sdwc_read_ping_status(struct sdw_bus *bus) +{ + struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); + u64 response; + u32 slave_stat = 0; + + response = amd_sdwc_send_cmd_get_resp(ctrl, 0, 0); + /* slave status from ping response*/ + slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response); + slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; + dev_dbg(ctrl->dev, "%s: slave_stat:0x%x\n", __func__, slave_stat); + return slave_stat; +} + +static void amd_sdwc_compute_slave_ports(struct sdw_master_runtime *m_rt, + struct sdw_transport_data *t_data) +{ + struct sdw_slave_runtime *s_rt = NULL; + struct sdw_port_runtime *p_rt; + int port_bo, sample_int; + unsigned int rate, bps, ch = 0; + unsigned int slave_total_ch; + struct sdw_bus_params *b_params = &m_rt->bus->params; + + port_bo = t_data->block_offset; + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + sample_int = (m_rt->bus->params.curr_dr_freq / rate); + slave_total_ch = 0; + + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + ch = sdw_ch_mask_to_ch(p_rt->ch_mask); + + sdw_fill_xport_params(&p_rt->transport_params, + p_rt->num, false, + SDW_BLK_GRP_CNT_1, + sample_int, port_bo, port_bo >> 8, + t_data->hstart, + t_data->hstop, + SDW_BLK_PKG_PER_PORT, 0x0); + + sdw_fill_port_params(&p_rt->port_params, + p_rt->num, bps, + SDW_PORT_FLOW_MODE_ISOCH, + b_params->s_data_mode); + + port_bo += bps * ch; + slave_total_ch += ch; + } + + if (m_rt->direction == SDW_DATA_DIR_TX && + m_rt->ch_count == slave_total_ch) { + port_bo = t_data->block_offset; + } + } +} + +static int amd_sdwc_compute_params(struct sdw_bus *bus) +{ + struct sdw_transport_data t_data = {0}; + struct sdw_master_runtime *m_rt; + struct sdw_port_runtime *p_rt; + struct sdw_bus_params *b_params = &bus->params; + int port_bo, hstart, hstop, sample_int; + unsigned int rate, bps; + + port_bo = 0; + hstart = 1; + hstop = bus->params.col - 1; + t_data.hstop = hstop; + t_data.hstart = hstart; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + sample_int = (bus->params.curr_dr_freq / rate); + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + port_bo = (p_rt->num * 64) + 1; + dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n", + p_rt->num, hstart, hstop, port_bo); + sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, + false, SDW_BLK_GRP_CNT_1, sample_int, + port_bo, port_bo >> 8, hstart, hstop, + SDW_BLK_PKG_PER_PORT, 0x0); + + sdw_fill_port_params(&p_rt->port_params, + p_rt->num, bps, + SDW_PORT_FLOW_MODE_ISOCH, + b_params->m_data_mode); + t_data.hstart = hstart; + t_data.hstop = hstop; + t_data.block_offset = port_bo; + t_data.sub_block_offset = 0; + } + amd_sdwc_compute_slave_ports(m_rt, &t_data); + } + return 0; +} + +static int amd_sdwc_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params, + unsigned int bank) +{ + struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); + u32 channel_type, frame_fmt_reg, dpn_frame_fmt; + + dev_dbg(ctrl->dev, "%s: p_params->num:0x%x\n", __func__, p_params->num); + switch (ctrl->instance) { + case ACP_SDW0: + channel_type = p_params->num; + break; + case ACP_SDW1: + channel_type = p_params->num + ACP_SDW0_MAX_DAI; + break; + default: + return -EINVAL; + } + + switch (channel_type) { + case ACP_SDW0_AUDIO_TX: + frame_fmt_reg = ACP_SW_AUDIO_TX_FRAME_FORMAT; + break; + case ACP_SDW0_HS_TX: + frame_fmt_reg = ACP_SW_HEADSET_TX_FRAME_FORMAT; + break; + case ACP_SDW0_BT_TX: + frame_fmt_reg = ACP_SW_BT_TX_FRAME_FORMAT; + break; + case ACP_SDW1_BT_TX: + frame_fmt_reg = ACP_P1_SW_BT_TX_FRAME_FORMAT; + break; + case ACP_SDW0_AUDIO_RX: + frame_fmt_reg = ACP_SW_AUDIO_RX_FRAME_FORMAT; + break; + case ACP_SDW0_HS_RX: + frame_fmt_reg = ACP_SW_HEADSET_RX_FRAME_FORMAT; + break; + case ACP_SDW0_BT_RX: + frame_fmt_reg = ACP_SW_BT_RX_FRAME_FORMAT; + break; + case ACP_SDW1_BT_RX: + frame_fmt_reg = ACP_P1_SW_BT_RX_FRAME_FORMAT; + break; + default: + dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type); + return -EINVAL; + } + dpn_frame_fmt = acp_reg_readl(ctrl->mmio + frame_fmt_reg); + u32p_replace_bits(&dpn_frame_fmt, p_params->flow_mode, AMD_DPN_FRAME_FMT_PFM); + u32p_replace_bits(&dpn_frame_fmt, p_params->data_mode, AMD_DPN_FRAME_FMT_PDM); + u32p_replace_bits(&dpn_frame_fmt, p_params->bps - 1, AMD_DPN_FRAME_FMT_WORD_LEN); + acp_reg_writel(dpn_frame_fmt, ctrl->mmio + frame_fmt_reg); + return 0; +} + +static int amd_sdwc_transport_params(struct sdw_bus *bus, + struct sdw_transport_params *params, + enum sdw_reg_bank bank) +{ + struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); + u32 ssp_counter_reg; + u32 dpn_frame_fmt; + u32 dpn_sampleinterval; + u32 dpn_hctrl; + u32 dpn_offsetctrl; + u32 dpn_lanectrl; + u32 channel_type; + u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg; + u32 offset_reg, lane_ctrl_reg; + + switch (ctrl->instance) { + case ACP_SDW0: + ssp_counter_reg = ACP_SW_SSP_COUNTER; + channel_type = params->port_num; + break; + case ACP_SDW1: + ssp_counter_reg = ACP_P1_SW_SSP_COUNTER; + channel_type = params->port_num + ACP_SDW0_MAX_DAI; + break; + default: + return -EINVAL; + } + acp_reg_writel(AMD_SDW_SSP_COUNTER_VAL, ctrl->mmio + ssp_counter_reg); + dev_dbg(bus->dev, "%s: p_params->num:0x%x entry channel_type:0x%x\n", + __func__, params->port_num, channel_type); + + switch (channel_type) { + case ACP_SDW0_AUDIO_TX: + { + frame_fmt_reg = ACP_SW_AUDIO_TX_FRAME_FORMAT; + sample_int_reg = ACP_SW_AUDIO_TX_SAMPLEINTERVAL; + hctrl_dp0_reg = ACP_SW_AUDIO_TX_HCTRL_DP0; + offset_reg = ACP_SW_AUDIO_TX_OFFSET_DP0; + lane_ctrl_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0; + break; + } + case ACP_SDW0_HS_TX: + { + frame_fmt_reg = ACP_SW_HEADSET_TX_FRAME_FORMAT; + sample_int_reg = ACP_SW_HEADSET_TX_SAMPLEINTERVAL; + hctrl_dp0_reg = ACP_SW_HEADSET_TX_HCTRL; + offset_reg = ACP_SW_HEADSET_TX_OFFSET; + lane_ctrl_reg = ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0; + break; + } + case ACP_SDW0_BT_TX: + { + frame_fmt_reg = ACP_SW_BT_TX_FRAME_FORMAT; + sample_int_reg = ACP_SW_BT_TX_SAMPLEINTERVAL; + hctrl_dp0_reg = ACP_SW_BT_TX_HCTRL; + offset_reg = ACP_SW_BT_TX_OFFSET; + lane_ctrl_reg = ACP_SW_BT_TX_CHANNEL_ENABLE_DP0; + break; + } + case ACP_SDW1_BT_TX: + { + frame_fmt_reg = ACP_P1_SW_BT_TX_FRAME_FORMAT; + sample_int_reg = ACP_P1_SW_BT_TX_SAMPLEINTERVAL; + hctrl_dp0_reg = ACP_P1_SW_BT_TX_HCTRL; + offset_reg = ACP_P1_SW_BT_TX_OFFSET; + lane_ctrl_reg = ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0; + break; + } + case ACP_SDW0_AUDIO_RX: + { + frame_fmt_reg = ACP_SW_AUDIO_RX_FRAME_FORMAT; + sample_int_reg = ACP_SW_AUDIO_RX_SAMPLEINTERVAL; + hctrl_dp0_reg = ACP_SW_AUDIO_RX_HCTRL_DP0; + offset_reg = ACP_SW_AUDIO_RX_OFFSET_DP0; + lane_ctrl_reg = ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0; + break; + } + case ACP_SDW0_HS_RX: + { + frame_fmt_reg = ACP_SW_HEADSET_RX_FRAME_FORMAT; + sample_int_reg = ACP_SW_HEADSET_RX_SAMPLEINTERVAL; + hctrl_dp0_reg = ACP_SW_HEADSET_RX_HCTRL; + offset_reg = ACP_SW_HEADSET_RX_OFFSET; + lane_ctrl_reg = ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0; + break; + } + case ACP_SDW0_BT_RX: + { + frame_fmt_reg = ACP_SW_BT_RX_FRAME_FORMAT; + sample_int_reg = ACP_SW_BT_RX_SAMPLEINTERVAL; + hctrl_dp0_reg = ACP_SW_BT_RX_HCTRL; + offset_reg = ACP_SW_BT_RX_OFFSET; + lane_ctrl_reg = ACP_SW_BT_RX_CHANNEL_ENABLE_DP0; + break; + } + case ACP_SDW1_BT_RX: + { + frame_fmt_reg = ACP_P1_SW_BT_RX_FRAME_FORMAT; + sample_int_reg = ACP_P1_SW_BT_RX_SAMPLEINTERVAL; + hctrl_dp0_reg = ACP_P1_SW_BT_RX_HCTRL; + offset_reg = ACP_P1_SW_BT_RX_OFFSET; + lane_ctrl_reg = ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0; + break; + } + default: + dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type); + return -EINVAL; + } + dpn_frame_fmt = acp_reg_readl(ctrl->mmio + frame_fmt_reg); + u32p_replace_bits(&dpn_frame_fmt, params->blk_pkg_mode, AMD_DPN_FRAME_FMT_BLK_PKG_MODE); + u32p_replace_bits(&dpn_frame_fmt, params->blk_grp_ctrl, AMD_DPN_FRAME_FMT_BLK_GRP_CTRL); + u32p_replace_bits(&dpn_frame_fmt, SDW_STREAM_PCM, AMD_DPN_FRAME_FMT_PCM_OR_PDM); + acp_reg_writel(dpn_frame_fmt, ctrl->mmio + frame_fmt_reg); + + dpn_sampleinterval = params->sample_interval - 1; + acp_reg_writel(dpn_sampleinterval, ctrl->mmio + sample_int_reg); + + dpn_hctrl = FIELD_PREP(AMD_DPN_HCTRL_HSTOP, params->hstop); + dpn_hctrl |= FIELD_PREP(AMD_DPN_HCTRL_HSTART, params->hstart); + acp_reg_writel(dpn_hctrl, ctrl->mmio + hctrl_dp0_reg); + + dpn_offsetctrl = FIELD_PREP(AMD_DPN_OFFSET_CTRL_1, params->offset1); + dpn_offsetctrl |= FIELD_PREP(AMD_DPN_OFFSET_CTRL_2, params->offset2); + acp_reg_writel(dpn_offsetctrl, ctrl->mmio + offset_reg); + + dpn_lanectrl = acp_reg_readl(ctrl->mmio + lane_ctrl_reg); + u32p_replace_bits(&dpn_lanectrl, params->lane_ctrl, AMD_DPN_CH_EN_LCTRL); + acp_reg_writel(dpn_lanectrl, ctrl->mmio + lane_ctrl_reg); + return 0; +} + +static int amd_sdwc_port_enable(struct sdw_bus *bus, + struct sdw_enable_ch *enable_ch, + unsigned int bank) +{ + struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); + u32 dpn_ch_enable; + u32 ch_enable_reg, channel_type; + + switch (ctrl->instance) { + case ACP_SDW0: + channel_type = enable_ch->port_num; + break; + case ACP_SDW1: + channel_type = enable_ch->port_num + ACP_SDW0_MAX_DAI; + break; + default: + return -EINVAL; + } + + switch (channel_type) { + case ACP_SDW0_AUDIO_TX: + ch_enable_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_HS_TX: + ch_enable_reg = ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_BT_TX: + ch_enable_reg = ACP_SW_BT_TX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW1_BT_TX: + ch_enable_reg = ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_AUDIO_RX: + ch_enable_reg = ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_HS_RX: + ch_enable_reg = ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_BT_RX: + ch_enable_reg = ACP_SW_BT_RX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW1_BT_RX: + ch_enable_reg = ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0; + break; + default: + dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type); + return -EINVAL; + } + + dpn_ch_enable = acp_reg_readl(ctrl->mmio + ch_enable_reg); + u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK); + if (enable_ch->enable) + acp_reg_writel(dpn_ch_enable, ctrl->mmio + ch_enable_reg); + else + acp_reg_writel(0, ctrl->mmio + ch_enable_reg); + return 0; +} + +static int sdw_master_read_amd_prop(struct sdw_bus *bus) +{ + struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); + struct fwnode_handle *link; + struct sdw_master_prop *prop; + u32 quirk_mask = 0; + u32 wake_en_mask = 0; + u32 power_mode_mask = 0; + char name[32]; + + prop = &bus->prop; + /* Find master handle */ + snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", bus->link_id); + link = device_get_named_child_node(bus->dev, name); + if (!link) { + dev_err(bus->dev, "Master node %s not found\n", name); + return -EIO; + } + fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask); + if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE)) + prop->hw_disabled = true; + prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH | + SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY; + + fwnode_property_read_u32(link, "amd-sdw-wake-enable", &wake_en_mask); + ctrl->wake_en_mask = wake_en_mask; + fwnode_property_read_u32(link, "amd-sdw-power-mode", &power_mode_mask); + ctrl->power_mode_mask = power_mode_mask; + return 0; +} + +static int amd_prop_read(struct sdw_bus *bus) +{ + sdw_master_read_prop(bus); + sdw_master_read_amd_prop(bus); + return 0; +} + +static const struct sdw_master_port_ops amd_sdwc_port_ops = { + .dpn_set_port_params = amd_sdwc_port_params, + .dpn_set_port_transport_params = amd_sdwc_transport_params, + .dpn_port_enable_ch = amd_sdwc_port_enable, +}; + +static const struct sdw_master_ops amd_sdwc_ops = { + .read_prop = amd_prop_read, + .xfer_msg = amd_sdwc_xfer_msg, + .reset_page_addr = amd_reset_page_addr, + .read_ping_status = amd_sdwc_read_ping_status, +}; + +static void amd_sdwc_probe_work(struct work_struct *work) +{ + struct amd_sdwc_ctrl *ctrl = container_of(work, struct amd_sdwc_ctrl, probe_work); + struct sdw_master_prop *prop; + int ret; + + prop = &ctrl->bus.prop; + if (!prop->hw_disabled) { + ret = amd_enable_sdw_pads(ctrl); + if (ret) + return; + ret = amd_init_sdw_controller(ctrl); + if (ret) + return; + amd_enable_sdw_interrupts(ctrl); + ret = amd_enable_sdw_controller(ctrl); + if (ret) + return; + ret = amd_sdwc_set_frameshape(ctrl, 50, 10); + if (!ret) + ctrl->startup_done = true; + } +} + +static int amd_sdwc_probe(struct platform_device *pdev) +{ + const struct acp_sdw_pdata *pdata = pdev->dev.platform_data; + struct resource *res; + struct device *dev = &pdev->dev; + struct sdw_master_prop *prop; + struct sdw_bus_params *params; + struct amd_sdwc_ctrl *ctrl; + int ret; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "platform_data not retrieved\n"); + return -ENODEV; + } + ctrl = devm_kzalloc(&pdev->dev, sizeof(struct amd_sdwc_ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENOMEM; + } + ctrl->mmio = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR(ctrl->mmio)) { + dev_err(&pdev->dev, "mmio not found\n"); + return PTR_ERR(ctrl->mmio); + } + ctrl->instance = pdata->instance; + ctrl->sdw_lock = pdata->sdw_lock; + ctrl->rows_index = sdw_find_row_index(50); + ctrl->cols_index = sdw_find_col_index(10); + + ctrl->dev = dev; + dev_set_drvdata(&pdev->dev, ctrl); + + ctrl->bus.ops = &amd_sdwc_ops; + ctrl->bus.port_ops = &amd_sdwc_port_ops; + ctrl->bus.compute_params = &amd_sdwc_compute_params; + ctrl->bus.clk_stop_timeout = 1; + switch (ctrl->instance) { + case ACP_SDW0: + ctrl->num_dout_ports = AMD_SDW0_MAX_TX_PORTS; + ctrl->num_din_ports = AMD_SDW0_MAX_RX_PORTS; + break; + case ACP_SDW1: + ctrl->num_dout_ports = AMD_SDW1_MAX_TX_PORTS; + ctrl->num_din_ports = AMD_SDW1_MAX_RX_PORTS; + break; + default: + return -EINVAL; + } + params = &ctrl->bus.params; + params->max_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2; + params->curr_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2; + params->col = 10; + params->row = 50; + + prop = &ctrl->bus.prop; + prop->clk_freq = &amd_sdwc_freq_tbl[0]; + prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ; + ctrl->bus.link_id = ctrl->instance; + ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode); + if (ret) { + dev_err(dev, "Failed to register Soundwire controller (%d)\n", + ret); + return ret; + } + INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); + schedule_work(&ctrl->probe_work); + return 0; +} + +static int amd_sdwc_remove(struct platform_device *pdev) +{ + struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(&pdev->dev); + int ret; + + amd_disable_sdw_interrupts(ctrl); + sdw_bus_master_delete(&ctrl->bus); + ret = amd_disable_sdw_controller(ctrl); + return ret; +} + +static struct platform_driver amd_sdwc_driver = { + .probe = &amd_sdwc_probe, + .remove = &amd_sdwc_remove, + .driver = { + .name = "amd_sdw_controller", + } +}; +module_platform_driver(amd_sdwc_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD soundwire driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); + diff --git a/drivers/soundwire/amd_master.h b/drivers/soundwire/amd_master.h new file mode 100644 index 000000000000..42f32ca0c7a8 --- /dev/null +++ b/drivers/soundwire/amd_master.h @@ -0,0 +1,279 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved. + */ + +#ifndef __AMD_MASTER_H +#define __AMD_MASTER_H + +#define ACP_PAD_PULLDOWN_CTRL 0x0001448 +#define ACP_SW_PAD_KEEPER_EN 0x0001454 +#define ACP_SW_WAKE_EN 0x0001458 +#define ACP_SW1_WAKE_EN 0x0001460 +#define ACP_SW_I2S_ERROR_REASON 0x00018B4 + +#define ACP_EXTERNAL_INTR_ENB 0x0001A00 +#define ACP_EXTERNAL_INTR_CNTL 0x0001A04 +#define ACP_EXTERNAL_INTR_CNTL1 0x0001A08 +#define ACP_EXTERNAL_INTR_STAT 0x0001A0C +#define ACP_EXTERNAL_INTR_STAT1 0x0001A10 +#define ACP_ERROR_STATUS 0x0001A4C +#define ACP_P1_SW_I2S_ERROR_REASON 0x0001A50 + +#define ACP_SW_EN 0x0003000 +#define ACP_SW_EN_STATUS 0x0003004 +#define ACP_SW_FRAMESIZE 0x0003008 +#define ACP_SW_SSP_COUNTER 0x000300C +#define ACP_SW_AUDIO_TX_EN 0x0003010 +#define ACP_SW_AUDIO_TX_EN_STATUS 0x0003014 +#define ACP_SW_AUDIO_TX_FRAME_FORMAT 0x0003018 +#define ACP_SW_AUDIO_TX_SAMPLEINTERVAL 0x000301C +#define ACP_SW_AUDIO_TX_HCTRL_DP0 0x0003020 +#define ACP_SW_AUDIO_TX_HCTRL_DP1 0x0003024 +#define ACP_SW_AUDIO_TX_HCTRL_DP2 0x0003028 +#define ACP_SW_AUDIO_TX_HCTRL_DP3 0x000302C +#define ACP_SW_AUDIO_TX_OFFSET_DP0 0x0003030 +#define ACP_SW_AUDIO_TX_OFFSET_DP1 0x0003034 +#define ACP_SW_AUDIO_TX_OFFSET_DP2 0x0003038 +#define ACP_SW_AUDIO_TX_OFFSET_DP3 0x000303C +#define ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0 0x0003040 +#define ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP1 0x0003044 +#define ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP2 0x0003048 +#define ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP3 0x000304C +#define ACP_SW_BT_TX_EN 0x0003050 +#define ACP_SW_BT_TX_EN_STATUS 0x0003054 +#define ACP_SW_BT_TX_FRAME_FORMAT 0x0003058 +#define ACP_SW_BT_TX_SAMPLEINTERVAL 0x000305C +#define ACP_SW_BT_TX_HCTRL 0x0003060 +#define ACP_SW_BT_TX_OFFSET 0x0003064 +#define ACP_SW_BT_TX_CHANNEL_ENABLE_DP0 0x0003068 +#define ACP_SW_HEADSET_TX_EN 0x000306C +#define ACP_SW_HEADSET_TX_EN_STATUS 0x0003070 +#define ACP_SW_HEADSET_TX_FRAME_FORMAT 0x0003074 +#define ACP_SW_HEADSET_TX_SAMPLEINTERVAL 0x0003078 +#define ACP_SW_HEADSET_TX_HCTRL 0x000307C +#define ACP_SW_HEADSET_TX_OFFSET 0x0003080 +#define ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0 0x0003084 +#define ACP_SW_AUDIO_RX_EN 0x0003088 +#define ACP_SW_AUDIO_RX_EN_STATUS 0x000308C +#define ACP_SW_AUDIO_RX_FRAME_FORMAT 0x0003090 +#define ACP_SW_AUDIO_RX_SAMPLEINTERVAL 0x0003094 +#define ACP_SW_AUDIO_RX_HCTRL_DP0 0x0003098 +#define ACP_SW_AUDIO_RX_HCTRL_DP1 0x000309C +#define ACP_SW_AUDIO_RX_HCTRL_DP2 0x0003100 +#define ACP_SW_AUDIO_RX_HCTRL_DP3 0x0003104 +#define ACP_SW_AUDIO_RX_OFFSET_DP0 0x0003108 +#define ACP_SW_AUDIO_RX_OFFSET_DP1 0x000310C +#define ACP_SW_AUDIO_RX_OFFSET_DP2 0x0003110 +#define ACP_SW_AUDIO_RX_OFFSET_DP3 0x0003114 +#define ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0 0x0003118 +#define ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP1 0x000311C +#define ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP2 0x0003120 +#define ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP3 0x0003124 +#define ACP_SW_BT_RX_EN 0x0003128 +#define ACP_SW_BT_RX_EN_STATUS 0x000312C +#define ACP_SW_BT_RX_FRAME_FORMAT 0x0003130 +#define ACP_SW_BT_RX_SAMPLEINTERVAL 0x0003134 +#define ACP_SW_BT_RX_HCTRL 0x0003138 +#define ACP_SW_BT_RX_OFFSET 0x000313C +#define ACP_SW_BT_RX_CHANNEL_ENABLE_DP0 0x0003140 +#define ACP_SW_HEADSET_RX_EN 0x0003144 +#define ACP_SW_HEADSET_RX_EN_STATUS 0x0003148 +#define ACP_SW_HEADSET_RX_FRAME_FORMAT 0x000314C +#define ACP_SW_HEADSET_RX_SAMPLEINTERVAL 0x0003150 +#define ACP_SW_HEADSET_RX_HCTRL 0x0003154 +#define ACP_SW_HEADSET_RX_OFFSET 0x0003158 +#define ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0 0x000315C +#define ACP_SW_BPT_PORT_EN 0x0003160 +#define ACP_SW_BPT_PORT_EN_STATUS 0x0003164 +#define ACP_SW_BPT_PORT_FRAME_FORMAT 0x0003168 +#define ACP_SW_BPT_PORT_SAMPLEINTERVAL 0x000316C +#define ACP_SW_BPT_PORT_HCTRL 0x0003170 +#define ACP_SW_BPT_PORT_OFFSET 0x0003174 +#define ACP_SW_BPT_PORT_CHANNEL_ENABLE 0x0003178 +#define ACP_SW_BPT_PORT_FIRST_BYTE_ADDR 0x000317C +#define ACP_SW_CLK_RESUME_CTRL 0x0003180 +#define ACP_SW_CLK_RESUME_DELAY_CNTR 0x0003184 +#define ACP_SW_BUS_RESET_CTRL 0x0003188 +#define ACP_SW_PRBS_ERR_STATUS 0x000318C +#define SW_IMM_CMD_UPPER_WORD 0x0003230 +#define SW_IMM_CMD_LOWER_QWORD 0x0003234 +#define SW_IMM_RESP_UPPER_WORD 0x0003238 +#define SW_IMM_RESP_LOWER_QWORD 0x000323C +#define SW_IMM_CMD_STS 0x0003240 +#define SW_BRA_BASE_ADDRESS 0x0003244 +#define SW_BRA_TRANSFER_SIZE 0x0003248 +#define SW_BRA_DMA_BUSY 0x000324C +#define SW_BRA_RESP 0x0003250 +#define SW_BRA_RESP_FRAME_ADDR 0x0003254 +#define SW_BRA_CURRENT_TRANSFER_SIZE 0x0003258 +#define SW_STATE_CHANGE_STATUS_0TO7 0x000325C +#define SW_STATE_CHANGE_STATUS_8TO11 0x0003260 +#define SW_STATE_CHANGE_STATUS_MASK_0TO7 0x0003264 +#define SW_STATE_CHANGE_STATUS_MASK_8TO11 0x0003268 +#define SW_CLK_FREQUENCY_CTRL 0x000326C +#define SW_ERROR_INTR_MASK 0x0003270 +#define SW_PHY_TEST_MODE_DATA_OFF 0x0003274 + +#define ACP_P1_SW_EN 0x0003C00 +#define ACP_P1_SW_EN_STATUS 0x0003C04 +#define ACP_P1_SW_FRAMESIZE 0x0003C08 +#define ACP_P1_SW_SSP_COUNTER 0x0003C0C +#define ACP_P1_SW_BT_TX_EN 0x0003C50 +#define ACP_P1_SW_BT_TX_EN_STATUS 0x0003C54 +#define ACP_P1_SW_BT_TX_FRAME_FORMAT 0x0003C58 +#define ACP_P1_SW_BT_TX_SAMPLEINTERVAL 0x0003C5C +#define ACP_P1_SW_BT_TX_HCTRL 0x0003C60 +#define ACP_P1_SW_BT_TX_OFFSET 0x0003C64 +#define ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0 0x0003C68 +#define ACP_P1_SW_BT_RX_EN 0x0003D28 +#define ACP_P1_SW_BT_RX_EN_STATUS 0x0003D2C +#define ACP_P1_SW_BT_RX_FRAME_FORMAT 0x0003D30 +#define ACP_P1_SW_BT_RX_SAMPLEINTERVAL 0x0003D34 +#define ACP_P1_SW_BT_RX_HCTRL 0x0003D38 +#define ACP_P1_SW_BT_RX_OFFSET 0x0003D3C +#define ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0 0x0003D40 +#define ACP_P1_SW_BPT_PORT_EN 0x0003D60 +#define ACP_P1_SW_BPT_PORT_EN_STATUS 0x0003D64 +#define ACP_P1_SW_BPT_PORT_FRAME_FORMAT 0x0003D68 +#define ACP_P1_SW_BPT_PORT_SAMPLEINTERVAL 0x0003D6C +#define ACP_P1_SW_BPT_PORT_HCTRL 0x0003D70 +#define ACP_P1_SW_BPT_PORT_OFFSET 0x0003D74 +#define ACP_P1_SW_BPT_PORT_CHANNEL_ENABLE 0x0003D78 +#define ACP_P1_SW_BPT_PORT_FIRST_BYTE_ADDR 0x0003D7C +#define ACP_P1_SW_CLK_RESUME_CTRL 0x0003D80 +#define ACP_P1_SW_CLK_RESUME_DELAY_CNTR 0x0003D84 +#define ACP_P1_SW_BUS_RESET_CTRL 0x0003D88 +#define ACP_P1_SW_PRBS_ERR_STATUS 0x0003D8C + +#define P1_SW_IMM_CMD_UPPER_WORD 0x0003E30 +#define P1_SW_IMM_CMD_LOWER_QWORD 0x0003E34 +#define P1_SW_IMM_RESP_UPPER_WORD 0x0003E38 +#define P1_SW_IMM_RESP_LOWER_QWORD 0x0003E3C +#define P1_SW_IMM_CMD_STS 0x0003E40 +#define P1_SW_BRA_BASE_ADDRESS 0x0003E44 +#define P1_SW_BRA_TRANSFER_SIZE 0x0003E48 +#define P1_SW_BRA_DMA_BUSY 0x0003E4C +#define P1_SW_BRA_RESP 0x0003E50 +#define P1_SW_BRA_RESP_FRAME_ADDR 0x0003E54 +#define P1_SW_BRA_CURRENT_TRANSFER_SIZE 0x0003E58 +#define P1_SW_STATE_CHANGE_STATUS_0TO7 0x0003E5C +#define P1_SW_STATE_CHANGE_STATUS_8TO11 0x0003E60 +#define P1_SW_STATE_CHANGE_STATUS_MASK_0TO7 0x0003E64 +#define P1_SW_STATE_CHANGE_STATUS_MASK_8TO11 0x0003E68 +#define P1_SW_CLK_FREQUENCY_CTRL 0x0003E6C +#define P1_SW_ERROR_INTR_MASK 0x0003E70 +#define P1_SW_PHY_TEST_MODE_DATA_OFF 0x0003E74 + +#define ACP_PHY_BASE_ADDRESS 0x1240000 +#define AMD_DELAY_LOOP_ITERATION 1000 +#define AMD_SDW_DEFAULT_CLK_FREQ 12000000 +#define AMD_SDW_RETRY_COUNT 1000 + +#define AMD_SDW_MCP_RESP_ACK BIT(0) +#define AMD_SDW_MCP_RESP_NACK BIT(1) +#define AMD_SDW_MCP_RESP_RDATA GENMASK(14, 7) + +#define AMD_SDW_MCP_CMD_SSP_TAG BIT(31) +#define AMD_SDW_MCP_CMD_COMMAND GENMASK(14, 12) +#define AMD_SDW_MCP_CMD_DEV_ADDR GENMASK(11, 8) +#define AMD_SDW_MCP_CMD_REG_ADDR_HIGH GENMASK(7, 0) +#define AMD_SDW_MCP_CMD_REG_ADDR_LOW GENMASK(31, 24) +#define AMD_SDW_MCP_CMD_REG_DATA GENMASK(14, 7) +#define AMD_SDW_MCP_SLAVE_STAT_0_3 GENMASK(14, 7) +#define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) +#define AMD_SDW_MCP_SLAVE_STATUS_MASK GENMASK(1, 0) +#define AMD_SDW_MCP_SLAVE_STATUS_BITS GENMASK(3, 2) +#define AMD_SDW_MCP_SLAVE_STATUS_8TO_11 GENMASK(15, 0) +#define AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(x) BIT(((x) * 4)) +#define AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(x) (((x) * 4) + 1) + +#define AMD_SDW_MASTER_SUSPEND_DELAY_MS 3000 +#define AMD_SDW_CLK_STOP_MAX_RETRY_COUNT 100 +#define AMD_SDW_QUIRK_MASK_BUS_ENABLE BIT(0) + +#define AMD_SDW_IMM_RES_VALID 1 +#define AMD_SDW_IMM_CMD_BUSY 2 +#define AMD_SDW_ENABLE 1 +#define AMD_SDW_DISABLE 0 +#define AMD_SDW_BUS_RESET_CLEAR_REQ 0 +#define AMD_SDW_BUS_RESET_REQ 1 +#define AMD_SDW_BUS_RESET_DONE 2 +#define AMD_SDW_BUS_BASE_FREQ 24000000 + +#define AMD_SDW0_EXT_INTR_MASK 0x200000 +#define AMD_SDW1_EXT_INTR_MASK 4 +#define AMD_SDW_IRQ_MASK_0TO7 0x77777777 +#define AMD_SDW_IRQ_MASK_8TO11 0x000D7777 +#define AMD_SDW_IRQ_ERROR_MASK 0xFF +#define AMD_SDW_MAX_FREQ_NUM 1 +#define AMD_SDW0_MAX_TX_PORTS 3 +#define AMD_SDW0_MAX_RX_PORTS 3 +#define AMD_SDW1_MAX_TX_PORTS 1 +#define AMD_SDW1_MAX_RX_PORTS 1 + +#define AMD_SDW_SLAVE_0_ATTACHED 5 +#define AMD_SDW_SSP_COUNTER_VAL 3 + +#define AMD_DPN_FRAME_FMT_PFM GENMASK(1, 0) +#define AMD_DPN_FRAME_FMT_PDM GENMASK(3, 2) +#define AMD_DPN_FRAME_FMT_BLK_PKG_MODE BIT(4) +#define AMD_DPN_FRAME_FMT_BLK_GRP_CTRL GENMASK(6, 5) +#define AMD_DPN_FRAME_FMT_WORD_LEN GENMASK(12, 7) +#define AMD_DPN_FRAME_FMT_PCM_OR_PDM BIT(13) +#define AMD_DPN_HCTRL_HSTOP GENMASK(3, 0) +#define AMD_DPN_HCTRL_HSTART GENMASK(7, 4) +#define AMD_DPN_OFFSET_CTRL_1 GENMASK(7, 0) +#define AMD_DPN_OFFSET_CTRL_2 GENMASK(15, 8) +#define AMD_DPN_CH_EN_LCTRL GENMASK(2, 0) +#define AMD_DPN_CH_EN_CHMASK GENMASK(10, 3) +#define AMD_SDW_STAT_MAX_RETRY_COUNT 100 +#define AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7F9F +#define AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7FFA +#define AMD_SDW0_PAD_PULLDOWN_CTRL_DISABLE_MASK 0x60 +#define AMD_SDW1_PAD_PULLDOWN_CTRL_DISABLE_MASK 5 +#define AMD_SDW0_PAD_KEEPER_EN_MASK 1 +#define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10 +#define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E +#define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF + +enum amd_sdw_channel { + /* SDW0 */ + ACP_SDW0_AUDIO_TX = 0, + ACP_SDW0_BT_TX, + ACP_SDW0_HS_TX, + ACP_SDW0_AUDIO_RX, + ACP_SDW0_BT_RX, + ACP_SDW0_HS_RX, + /* SDW1 */ + ACP_SDW1_BT_TX, + ACP_SDW1_BT_RX, +}; + +enum amd_sdw_cmd_type { + AMD_SDW_CMD_PING = 0, + AMD_SDW_CMD_READ = 2, + AMD_SDW_CMD_WRITE = 3, +}; + +static u32 amd_sdwc_freq_tbl[AMD_SDW_MAX_FREQ_NUM] = { + AMD_SDW_DEFAULT_CLK_FREQ, +}; + +struct sdw_transport_data { + int hstart; + int hstop; + int block_offset; + int sub_block_offset; +}; + +static inline u32 acp_reg_readl(void __iomem *base_addr) +{ + return readl(base_addr); +} + +static inline void acp_reg_writel(u32 val, void __iomem *base_addr) +{ + writel(val, base_addr); +} +#endif diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index f0123815af46..5ec39f8c2f2e 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -10,9 +10,30 @@
#define AMD_SDW_CLK_STOP_MODE 1 #define AMD_SDW_POWER_OFF_MODE 2 +#define ACP_SDW0 0 +#define ACP_SDW1 1 +#define ACP_SDW0_MAX_DAI 6
struct acp_sdw_pdata { u16 instance; struct mutex *sdw_lock; }; + +struct amd_sdwc_ctrl { + struct sdw_bus bus; + struct device *dev; + void __iomem *mmio; + struct work_struct probe_work; + struct mutex *sdw_lock; + int num_din_ports; + int num_dout_ports; + int cols_index; + int rows_index; + u32 instance; + u32 quirks; + u32 wake_en_mask; + int num_ports; + bool startup_done; + u32 power_mode_mask; +}; #endif
On 1/11/2023 10:02 AM, Vijendar Mukunda wrote:
AMD ACP IP block has two soundwire controller devices. Add support for
- Master driver probe & remove sequence
- Helper functions to enable/disable interrupts, Initialize sdw controller, enable sdw pads
- Master driver sdw_master_ops & port_ops callbacks
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
...
+static int amd_sdwc_compute_params(struct sdw_bus *bus) +{
- struct sdw_transport_data t_data = {0};
- struct sdw_master_runtime *m_rt;
- struct sdw_port_runtime *p_rt;
- struct sdw_bus_params *b_params = &bus->params;
- int port_bo, hstart, hstop, sample_int;
- unsigned int rate, bps;
- port_bo = 0;
Double space before '='.
- hstart = 1;
- hstop = bus->params.col - 1;
- t_data.hstop = hstop;
- t_data.hstart = hstart;
- list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (bus->params.curr_dr_freq / rate);
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
port_bo = (p_rt->num * 64) + 1;
dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n",
p_rt->num, hstart, hstop, port_bo);
sdw_fill_xport_params(&p_rt->transport_params, p_rt->num,
false, SDW_BLK_GRP_CNT_1, sample_int,
port_bo, port_bo >> 8, hstart, hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->m_data_mode);
t_data.hstart = hstart;
t_data.hstop = hstop;
t_data.block_offset = port_bo;
t_data.sub_block_offset = 0;
}
amd_sdwc_compute_slave_ports(m_rt, &t_data);
- }
- return 0;
+}
...
+static int amd_sdwc_port_enable(struct sdw_bus *bus,
struct sdw_enable_ch *enable_ch,
unsigned int bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 dpn_ch_enable;
- u32 ch_enable_reg, channel_type;
- switch (ctrl->instance) {
- case ACP_SDW0:
channel_type = enable_ch->port_num;
break;
- case ACP_SDW1:
channel_type = enable_ch->port_num + ACP_SDW0_MAX_DAI;
break;
- default:
return -EINVAL;
- }
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
ch_enable_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_HS_TX:
ch_enable_reg = ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_BT_TX:
ch_enable_reg = ACP_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW1_BT_TX:
ch_enable_reg = ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_AUDIO_RX:
ch_enable_reg = ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_HS_RX:
ch_enable_reg = ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_BT_RX:
ch_enable_reg = ACP_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW1_BT_RX:
ch_enable_reg = ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- default:
dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type);
return -EINVAL;
- }
- dpn_ch_enable = acp_reg_readl(ctrl->mmio + ch_enable_reg);
Double space after '='.
- u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK);
- if (enable_ch->enable)
acp_reg_writel(dpn_ch_enable, ctrl->mmio + ch_enable_reg);
- else
acp_reg_writel(0, ctrl->mmio + ch_enable_reg);
- return 0;
+}
...
+static void amd_sdwc_probe_work(struct work_struct *work) +{
- struct amd_sdwc_ctrl *ctrl = container_of(work, struct amd_sdwc_ctrl, probe_work);
Double space before '='.
- struct sdw_master_prop *prop;
- int ret;
- prop = &ctrl->bus.prop;
- if (!prop->hw_disabled) {
ret = amd_enable_sdw_pads(ctrl);
if (ret)
return;
ret = amd_init_sdw_controller(ctrl);
if (ret)
return;
amd_enable_sdw_interrupts(ctrl);
ret = amd_enable_sdw_controller(ctrl);
if (ret)
return;
ret = amd_sdwc_set_frameshape(ctrl, 50, 10);
if (!ret)
ctrl->startup_done = true;
- }
+}
+static int amd_sdwc_probe(struct platform_device *pdev) +{
- const struct acp_sdw_pdata *pdata = pdev->dev.platform_data;
- struct resource *res;
- struct device *dev = &pdev->dev;
Same as in previous patch, you assign dev here, but keep using &pdev->dev below?
- struct sdw_master_prop *prop;
- struct sdw_bus_params *params;
- struct amd_sdwc_ctrl *ctrl;
- int ret;
- if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "platform_data not retrieved\n");
return -ENODEV;
- }
- ctrl = devm_kzalloc(&pdev->dev, sizeof(struct amd_sdwc_ctrl), GFP_KERNEL);
- if (!ctrl)
return -ENOMEM;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENOMEM;
- }
- ctrl->mmio = devm_ioremap(&pdev->dev, res->start, resource_size(res));
- if (IS_ERR(ctrl->mmio)) {
dev_err(&pdev->dev, "mmio not found\n");
return PTR_ERR(ctrl->mmio);
- }
- ctrl->instance = pdata->instance;
- ctrl->sdw_lock = pdata->sdw_lock;
Double space before '='.
- ctrl->rows_index = sdw_find_row_index(50);
- ctrl->cols_index = sdw_find_col_index(10);
- ctrl->dev = dev;
- dev_set_drvdata(&pdev->dev, ctrl);
- ctrl->bus.ops = &amd_sdwc_ops;
- ctrl->bus.port_ops = &amd_sdwc_port_ops;
- ctrl->bus.compute_params = &amd_sdwc_compute_params;
- ctrl->bus.clk_stop_timeout = 1;
- switch (ctrl->instance) {
- case ACP_SDW0:
ctrl->num_dout_ports = AMD_SDW0_MAX_TX_PORTS;
Double space after '='.
ctrl->num_din_ports = AMD_SDW0_MAX_RX_PORTS;
break;
- case ACP_SDW1:
ctrl->num_dout_ports = AMD_SDW1_MAX_TX_PORTS;
ctrl->num_din_ports = AMD_SDW1_MAX_RX_PORTS;
break;
- default:
return -EINVAL;
- }
- params = &ctrl->bus.params;
- params->max_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->curr_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->col = 10;
- params->row = 50;
- prop = &ctrl->bus.prop;
- prop->clk_freq = &amd_sdwc_freq_tbl[0];
- prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ;
- ctrl->bus.link_id = ctrl->instance;
- ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode);
- if (ret) {
dev_err(dev, "Failed to register Soundwire controller (%d)\n",
ret);
return ret;
- }
- INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work);
- schedule_work(&ctrl->probe_work);
- return 0;
+}
+static int amd_sdwc_remove(struct platform_device *pdev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(&pdev->dev);
- int ret;
You may need to cancel work if someone tries to unload driver before probe work completes. Something like
cancel_work_sync(&ctrl->probe_work);
should probably work here.
- amd_disable_sdw_interrupts(ctrl);
- sdw_bus_master_delete(&ctrl->bus);
- ret = amd_disable_sdw_controller(ctrl);
- return ret;
+}
On 11/01/23 19:29, Amadeusz Sławiński wrote:
On 1/11/2023 10:02 AM, Vijendar Mukunda wrote:
AMD ACP IP block has two soundwire controller devices. Add support for
- Master driver probe & remove sequence
- Helper functions to enable/disable interrupts, Initialize sdw controller,
enable sdw pads
- Master driver sdw_master_ops & port_ops callbacks
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
...
+static int amd_sdwc_compute_params(struct sdw_bus *bus) +{ + struct sdw_transport_data t_data = {0}; + struct sdw_master_runtime *m_rt; + struct sdw_port_runtime *p_rt; + struct sdw_bus_params *b_params = &bus->params; + int port_bo, hstart, hstop, sample_int; + unsigned int rate, bps;
+ port_bo = 0;
Double space before '='.
will fix it.
+ hstart = 1; + hstop = bus->params.col - 1; + t_data.hstop = hstop; + t_data.hstart = hstart;
+ list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + sample_int = (bus->params.curr_dr_freq / rate); + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + port_bo = (p_rt->num * 64) + 1; + dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n", + p_rt->num, hstart, hstop, port_bo); + sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, + false, SDW_BLK_GRP_CNT_1, sample_int, + port_bo, port_bo >> 8, hstart, hstop, + SDW_BLK_PKG_PER_PORT, 0x0);
+ sdw_fill_port_params(&p_rt->port_params, + p_rt->num, bps, + SDW_PORT_FLOW_MODE_ISOCH, + b_params->m_data_mode); + t_data.hstart = hstart; + t_data.hstop = hstop; + t_data.block_offset = port_bo; + t_data.sub_block_offset = 0; + } + amd_sdwc_compute_slave_ports(m_rt, &t_data); + } + return 0; +}
...
+static int amd_sdwc_port_enable(struct sdw_bus *bus, + struct sdw_enable_ch *enable_ch, + unsigned int bank) +{ + struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); + u32 dpn_ch_enable; + u32 ch_enable_reg, channel_type;
+ switch (ctrl->instance) { + case ACP_SDW0: + channel_type = enable_ch->port_num; + break; + case ACP_SDW1: + channel_type = enable_ch->port_num + ACP_SDW0_MAX_DAI; + break; + default: + return -EINVAL; + }
+ switch (channel_type) { + case ACP_SDW0_AUDIO_TX: + ch_enable_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_HS_TX: + ch_enable_reg = ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_BT_TX: + ch_enable_reg = ACP_SW_BT_TX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW1_BT_TX: + ch_enable_reg = ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_AUDIO_RX: + ch_enable_reg = ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_HS_RX: + ch_enable_reg = ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW0_BT_RX: + ch_enable_reg = ACP_SW_BT_RX_CHANNEL_ENABLE_DP0; + break; + case ACP_SDW1_BT_RX: + ch_enable_reg = ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0; + break; + default: + dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type); + return -EINVAL; + }
+ dpn_ch_enable = acp_reg_readl(ctrl->mmio + ch_enable_reg);
Double space after '='.
will fix it.
+ u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK); + if (enable_ch->enable) + acp_reg_writel(dpn_ch_enable, ctrl->mmio + ch_enable_reg); + else + acp_reg_writel(0, ctrl->mmio + ch_enable_reg); + return 0; +}
...
+static void amd_sdwc_probe_work(struct work_struct *work) +{ + struct amd_sdwc_ctrl *ctrl = container_of(work, struct amd_sdwc_ctrl, probe_work);
Double space before '='.
Will fix it.
+ struct sdw_master_prop *prop; + int ret;
+ prop = &ctrl->bus.prop; + if (!prop->hw_disabled) { + ret = amd_enable_sdw_pads(ctrl); + if (ret) + return; + ret = amd_init_sdw_controller(ctrl); + if (ret) + return; + amd_enable_sdw_interrupts(ctrl); + ret = amd_enable_sdw_controller(ctrl); + if (ret) + return; + ret = amd_sdwc_set_frameshape(ctrl, 50, 10); + if (!ret) + ctrl->startup_done = true; + } +}
+static int amd_sdwc_probe(struct platform_device *pdev) +{ + const struct acp_sdw_pdata *pdata = pdev->dev.platform_data; + struct resource *res; + struct device *dev = &pdev->dev;
Same as in previous patch, you assign dev here, but keep using &pdev->dev below? will remove dev.
+ struct sdw_master_prop *prop; + struct sdw_bus_params *params; + struct amd_sdwc_ctrl *ctrl; + int ret;
+ if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "platform_data not retrieved\n"); + return -ENODEV; + } + ctrl = devm_kzalloc(&pdev->dev, sizeof(struct amd_sdwc_ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENOMEM; + } + ctrl->mmio = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (IS_ERR(ctrl->mmio)) { + dev_err(&pdev->dev, "mmio not found\n"); + return PTR_ERR(ctrl->mmio); + } + ctrl->instance = pdata->instance; + ctrl->sdw_lock = pdata->sdw_lock;
Double space before '='. will fix it.
+ ctrl->rows_index = sdw_find_row_index(50); + ctrl->cols_index = sdw_find_col_index(10);
+ ctrl->dev = dev; + dev_set_drvdata(&pdev->dev, ctrl);
+ ctrl->bus.ops = &amd_sdwc_ops; + ctrl->bus.port_ops = &amd_sdwc_port_ops; + ctrl->bus.compute_params = &amd_sdwc_compute_params; + ctrl->bus.clk_stop_timeout = 1; + switch (ctrl->instance) { + case ACP_SDW0: + ctrl->num_dout_ports = AMD_SDW0_MAX_TX_PORTS;
Double space after '='.
will fix it.
+ ctrl->num_din_ports = AMD_SDW0_MAX_RX_PORTS; + break; + case ACP_SDW1: + ctrl->num_dout_ports = AMD_SDW1_MAX_TX_PORTS; + ctrl->num_din_ports = AMD_SDW1_MAX_RX_PORTS; + break; + default: + return -EINVAL; + } + params = &ctrl->bus.params; + params->max_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2; + params->curr_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2; + params->col = 10; + params->row = 50;
+ prop = &ctrl->bus.prop; + prop->clk_freq = &amd_sdwc_freq_tbl[0]; + prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ; + ctrl->bus.link_id = ctrl->instance; + ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode); + if (ret) { + dev_err(dev, "Failed to register Soundwire controller (%d)\n", + ret); + return ret; + } + INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); + schedule_work(&ctrl->probe_work); + return 0; +}
+static int amd_sdwc_remove(struct platform_device *pdev) +{ + struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(&pdev->dev); + int ret;
You may need to cancel work if someone tries to unload driver before probe work completes. Something like
cancel_work_sync(&ctrl->probe_work);
should probably work here.
will fix it and post the v2 patch.
+ amd_disable_sdw_interrupts(ctrl); + sdw_bus_master_delete(&ctrl->bus); + ret = amd_disable_sdw_controller(ctrl); + return ret; +}
On 1/11/23 03:02, Vijendar Mukunda wrote:
AMD ACP IP block has two soundwire controller devices.
s/controller/manager?
Add support for
- Master driver probe & remove sequence
- Helper functions to enable/disable interrupts, Initialize sdw controller, enable sdw pads
- Master driver sdw_master_ops & port_ops callbacks
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
drivers/soundwire/amd_master.c | 1075 +++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 279 ++++++++ include/linux/soundwire/sdw_amd.h | 21 + 3 files changed, 1375 insertions(+) create mode 100644 drivers/soundwire/amd_master.c create mode 100644 drivers/soundwire/amd_master.h
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c new file mode 100644 index 000000000000..7e1f618254ac --- /dev/null +++ b/drivers/soundwire/amd_master.c @@ -0,0 +1,1075 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- SoundWire AMD Master driver
- Copyright 2023 Advanced Micro Devices, Inc.
- */
+#include <linux/completion.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw_amd.h> +#include <linux/wait.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bus.h" +#include "amd_master.h"
+#define DRV_NAME "amd_sdw_controller"
+#define to_amd_sdw(b) container_of(b, struct amd_sdwc_ctrl, bus)
+static int amd_enable_sdw_pads(struct amd_sdwc_ctrl *ctrl) +{
- u32 sw_pad_enable_mask;
- u32 sw_pad_pulldown_mask;
- u32 sw_pad_pulldown_val;
- u32 val = 0;
- switch (ctrl->instance) {
Goodness no. A controller has one or more masters. It cannot have pins as described in the SoundWire master specification.
- case ACP_SDW0:
sw_pad_enable_mask = AMD_SDW0_PAD_KEEPER_EN_MASK;
sw_pad_pulldown_mask = AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK;
break;
- case ACP_SDW1:
sw_pad_enable_mask = AMD_SDW1_PAD_KEEPER_EN_MASK;
sw_pad_pulldown_mask = AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK;
break;
- default:
return -EINVAL;
- }
- mutex_lock(ctrl->sdw_lock);
- val = acp_reg_readl(ctrl->mmio + ACP_SW_PAD_KEEPER_EN);
- val |= sw_pad_enable_mask;
- acp_reg_writel(val, ctrl->mmio + ACP_SW_PAD_KEEPER_EN);
- mutex_unlock(ctrl->sdw_lock);
- usleep_range(1000, 1500);
- mutex_lock(ctrl->sdw_lock);
- sw_pad_pulldown_val = acp_reg_readl(ctrl->mmio + ACP_PAD_PULLDOWN_CTRL);
- sw_pad_pulldown_val &= sw_pad_pulldown_mask;
- acp_reg_writel(sw_pad_pulldown_val, ctrl->mmio + ACP_PAD_PULLDOWN_CTRL);
- mutex_unlock(ctrl->sdw_lock);
- return 0;
+}
+static int amd_init_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{
- u32 acp_sw_en_reg, acp_sw_en_stat_reg, sw_bus_reset_reg;
- u32 val = 0;
- u32 timeout = 0;
- u32 retry_count = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
acp_sw_en_reg = ACP_SW_EN;
acp_sw_en_stat_reg = ACP_SW_EN_STATUS;
sw_bus_reset_reg = ACP_SW_BUS_RESET_CTRL;
break;
- case ACP_SDW1:
acp_sw_en_reg = ACP_P1_SW_EN;
acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS;
sw_bus_reset_reg = ACP_P1_SW_BUS_RESET_CTRL;
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_ENABLE, ctrl->mmio + acp_sw_en_reg);
- do {
val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg);
if (val)
break;
usleep_range(10, 50);
- } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT);
- if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT)
return -ETIMEDOUT;
- /* Sdw Controller reset */
- acp_reg_writel(AMD_SDW_BUS_RESET_REQ, ctrl->mmio + sw_bus_reset_reg);
- val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg);
- while (!(val & AMD_SDW_BUS_RESET_DONE)) {
val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg);
if (timeout > AMD_DELAY_LOOP_ITERATION)
break;
usleep_range(1, 5);
timeout++;
- }
no test on timeout here to check if the bus was indeed reset?
If you are talking about bus_reset you are referring to a master btw. The terms bus/master/link are interchangeable. A controller is not defined in the SoundWire specification, this is part of the DisCo spec to deal with enumeration when multiple bus/master/link are supported in the platform.
- timeout = 0;
- acp_reg_writel(AMD_SDW_BUS_RESET_CLEAR_REQ, ctrl->mmio + sw_bus_reset_reg);
- val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg);
- while (val) {
val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg);
if (timeout > AMD_DELAY_LOOP_ITERATION)
break;
usleep_range(1, 5);
timeout++;
- }
- if (timeout == AMD_DELAY_LOOP_ITERATION) {
dev_err(ctrl->dev, "Failed to reset SW%x Soundwire Controller\n", ctrl->instance);
return -ETIMEDOUT;
- }
- retry_count = 0;
- acp_reg_writel(AMD_SDW_DISABLE, ctrl->mmio + acp_sw_en_reg);
- do {
val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg);
if (!val)
break;
usleep_range(10, 50);
- } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT);
- if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT)
return -ETIMEDOUT;
- return 0;
+}
+static int amd_enable_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{
- u32 acp_sw_en_reg;
- u32 acp_sw_en_stat_reg;
- u32 val = 0;
- u32 retry_count = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
acp_sw_en_reg = ACP_SW_EN;
acp_sw_en_stat_reg = ACP_SW_EN_STATUS;
break;
- case ACP_SDW1:
acp_sw_en_reg = ACP_P1_SW_EN;
acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS;
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_ENABLE, ctrl->mmio + acp_sw_en_reg);
- do {
val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg);
if (val)
break;
usleep_range(10, 50);
- } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT);
- if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT)
return -ETIMEDOUT;
- return 0;
+}
+static int amd_disable_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{
- u32 clk_resume_ctrl_reg;
- u32 acp_sw_en_reg;
- u32 acp_sw_en_stat_reg;
- u32 val = 0;
- u32 retry_count = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
acp_sw_en_reg = ACP_SW_EN;
acp_sw_en_stat_reg = ACP_SW_EN_STATUS;
clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL;
break;
- case ACP_SDW1:
acp_sw_en_reg = ACP_P1_SW_EN;
acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS;
clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL;
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_DISABLE, ctrl->mmio + acp_sw_en_reg);
- /*
* After invoking controller disable sequence, check whether
* controller has executed clock stop sequence. In this case,
* controller should ignore checking enable status register.
again clock stop is a sequence at the master/link/bus level, not the controller.
*/
- val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
- if (val)
return 0;
- do {
val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg);
if (!val)
break;
usleep_range(10, 50);
- } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT);
- if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT)
return -ETIMEDOUT;
- return 0;
+}
+static int amd_enable_sdw_interrupts(struct amd_sdwc_ctrl *ctrl) +{
- u32 val;
- u32 acp_ext_intr_stat, acp_ext_intr_ctrl, acp_sdw_intr_mask;
- u32 sw_stat_mask_0to7, sw_stat_mask_8to11, sw_err_intr_mask;
- switch (ctrl->instance) {
- case ACP_SDW0:
acp_ext_intr_ctrl = ACP_EXTERNAL_INTR_CNTL;
should be renamed and end in CNTL0 if the other is CNTL1
And it's manager anyways, not controller.
acp_sdw_intr_mask = AMD_SDW0_EXT_INTR_MASK;
acp_ext_intr_stat = ACP_EXTERNAL_INTR_STAT;
sw_stat_mask_0to7 = SW_STATE_CHANGE_STATUS_MASK_0TO7;
sw_stat_mask_8to11 = SW_STATE_CHANGE_STATUS_MASK_8TO11;
sw_err_intr_mask = SW_ERROR_INTR_MASK;
break;
- case ACP_SDW1:
acp_ext_intr_ctrl = ACP_EXTERNAL_INTR_CNTL1;
acp_sdw_intr_mask = AMD_SDW1_EXT_INTR_MASK;
acp_ext_intr_stat = ACP_EXTERNAL_INTR_STAT1;
sw_stat_mask_0to7 = P1_SW_STATE_CHANGE_STATUS_MASK_0TO7;
sw_stat_mask_8to11 = P1_SW_STATE_CHANGE_STATUS_MASK_8TO11;
sw_err_intr_mask = P1_SW_ERROR_INTR_MASK;
break;
- default:
return -EINVAL;
- }
- mutex_lock(ctrl->sdw_lock);
- val = acp_reg_readl(ctrl->mmio + acp_ext_intr_ctrl);
- val |= acp_sdw_intr_mask;
- acp_reg_writel(val, ctrl->mmio + acp_ext_intr_ctrl);
- val = acp_reg_readl(ctrl->mmio + acp_ext_intr_ctrl);
- mutex_unlock(ctrl->sdw_lock);
- dev_dbg(ctrl->dev, "%s: acp_ext_intr_ctrl[0x%x]:0x%x\n", __func__, acp_ext_intr_ctrl, val);
- val = acp_reg_readl(ctrl->mmio + acp_ext_intr_stat);
- if (val)
acp_reg_writel(val, ctrl->mmio + acp_ext_intr_stat);
- acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, ctrl->mmio + sw_stat_mask_0to7);
- acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, ctrl->mmio + sw_stat_mask_8to11);
- acp_reg_writel(AMD_SDW_IRQ_ERROR_MASK, ctrl->mmio + sw_err_intr_mask);
- return 0;
+}
+static u64 amd_sdwc_send_cmd_get_resp(struct amd_sdwc_ctrl *ctrl, u32 lword, u32 uword) +{
- u64 resp = 0;
- u32 imm_cmd_stat_reg, imm_cmd_uword_reg, imm_cmd_lword_reg;
- u32 imm_resp_uword_reg, imm_resp_lword_reg;
- u32 resp_lower, resp_high;
- u32 sts = 0;
- u32 timeout = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
imm_cmd_stat_reg = SW_IMM_CMD_STS;
imm_cmd_uword_reg = SW_IMM_CMD_UPPER_WORD;
imm_cmd_lword_reg = SW_IMM_CMD_LOWER_QWORD;
imm_resp_uword_reg = SW_IMM_RESP_UPPER_WORD;
imm_resp_lword_reg = SW_IMM_RESP_LOWER_QWORD;
break;
- case ACP_SDW1:
imm_cmd_stat_reg = P1_SW_IMM_CMD_STS;
imm_cmd_uword_reg = P1_SW_IMM_CMD_UPPER_WORD;
imm_cmd_lword_reg = P1_SW_IMM_CMD_LOWER_QWORD;
imm_resp_uword_reg = P1_SW_IMM_RESP_UPPER_WORD;
imm_resp_lword_reg = P1_SW_IMM_RESP_LOWER_QWORD;
naming consistency would be good, the P1 is sometimes a prefix, sometimes a suffix, sometimes in the middle. Pick one.
break;
- default:
return -EINVAL;
- }
- sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
- while (sts & AMD_SDW_IMM_CMD_BUSY) {
sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
if (timeout > AMD_SDW_RETRY_COUNT) {
dev_err(ctrl->dev, "SDW%x previous cmd status clear failed\n",
ctrl->instance);
return -ETIMEDOUT;
}
timeout++;
- }
- timeout = 0;
- if (sts & AMD_SDW_IMM_RES_VALID) {
dev_err(ctrl->dev, "SDW%x controller is in bad state\n", ctrl->instance);
acp_reg_writel(0x00, ctrl->mmio + imm_cmd_stat_reg);
- }
- acp_reg_writel(uword, ctrl->mmio + imm_cmd_uword_reg);
- acp_reg_writel(lword, ctrl->mmio + imm_cmd_lword_reg);
- sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
- while (!(sts & AMD_SDW_IMM_RES_VALID)) {
sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
if (timeout > AMD_SDW_RETRY_COUNT) {
dev_err(ctrl->dev, "SDW%x cmd response timeout occurred\n", ctrl->instance);
return -ETIMEDOUT;
}
timeout++;
- }
- resp_high = acp_reg_readl(ctrl->mmio + imm_resp_uword_reg);
- resp_lower = acp_reg_readl(ctrl->mmio + imm_resp_lword_reg);
- timeout = 0;
- acp_reg_writel(AMD_SDW_IMM_RES_VALID, ctrl->mmio + imm_cmd_stat_reg);
- while ((sts & AMD_SDW_IMM_RES_VALID)) {
sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
if (timeout > AMD_SDW_RETRY_COUNT) {
dev_err(ctrl->dev, "SDW%x cmd status retry failed\n", ctrl->instance);
return -ETIMEDOUT;
}
timeout++;
- }
- resp = resp_high;
- resp = (resp << 32) | resp_lower;
- return resp;
+}
+static enum sdw_command_response +amd_program_scp_addr(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg) +{
- struct sdw_msg scp_msg = {0};
- u64 response_buf[2] = {0};
- u32 uword = 0, lword = 0;
- int nack = 0, no_ack = 0;
- int index, timeout = 0;
- scp_msg.dev_num = msg->dev_num;
- scp_msg.addr = SDW_SCP_ADDRPAGE1;
- scp_msg.buf = &msg->addr_page1;
- amd_sdwc_ctl_word_prep(&lword, &uword, AMD_SDW_CMD_WRITE, &scp_msg, 0);
- response_buf[0] = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword);
- scp_msg.addr = SDW_SCP_ADDRPAGE2;
- scp_msg.buf = &msg->addr_page2;
- amd_sdwc_ctl_word_prep(&lword, &uword, AMD_SDW_CMD_WRITE, &scp_msg, 0);
- response_buf[1] = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword);
- /* check response the writes */
response to the writes? after the writes?
- for (index = 0; index < 2; index++) {
if (response_buf[index] == -ETIMEDOUT) {
dev_err(ctrl->dev, "Program SCP cmd timeout\n");
timeout = 1;
} else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
no_ack = 1;
if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(ctrl->dev, "Program SCP NACK received\n");
}
this is a copy of the cadence_master.c code... With the error added that this is not for a controller but for a master...
}
- }
- if (timeout) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_dbg_ratelimited(ctrl->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
this should probably become a helper since the response is really the same as in cadence_master.c
There's really room for optimization and reuse here.
+}
+static int amd_prep_msg(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg, int *cmd) +{
- int ret;
- if (msg->page) {
ret = amd_program_scp_addr(ctrl, msg);
if (ret) {
msg->len = 0;
return ret;
}
- }
- switch (msg->flags) {
- case SDW_MSG_FLAG_READ:
*cmd = AMD_SDW_CMD_READ;
break;
- case SDW_MSG_FLAG_WRITE:
*cmd = AMD_SDW_CMD_WRITE;
break;
- default:
dev_err(ctrl->dev, "Invalid msg cmd: %d\n", msg->flags);
return -EINVAL;
- }
- return 0;
+}
this is the same code as in cadence_master.c
you just replaced sdw_cnds by amd_sdw_ctrl (which is a mistake) and cdns->dev by ctrl->dev.
+static unsigned int _amd_sdwc_xfer_msg(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg,
int cmd, int cmd_offset)
+{
- u64 response = 0;
- u32 uword = 0, lword = 0;
- int nack = 0, no_ack = 0;
- int timeout = 0;
- amd_sdwc_ctl_word_prep(&lword, &uword, cmd, msg, cmd_offset);
- response = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword);
- if (response & AMD_SDW_MCP_RESP_ACK) {
if (cmd == AMD_SDW_CMD_READ)
msg->buf[cmd_offset] = FIELD_GET(AMD_SDW_MCP_RESP_RDATA, response);
- } else {
no_ack = 1;
if (response == -ETIMEDOUT) {
timeout = 1;
} else if (response & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(ctrl->dev, "Program SCP NACK received\n");
}
- }
- if (timeout) {
dev_err_ratelimited(ctrl->dev, "command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(ctrl->dev,
"command response NACK received for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_err_ratelimited(ctrl->dev, "command is ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
+}
+static enum sdw_command_response amd_sdwc_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- int ret, i;
- int cmd = 0;
- ret = amd_prep_msg(ctrl, msg, &cmd);
- if (ret)
return SDW_CMD_FAIL_OTHER;
- for (i = 0; i < msg->len; i++) {
ret = _amd_sdwc_xfer_msg(ctrl, msg, cmd, i);
if (ret)
return ret;
- }
- return SDW_CMD_OK;
+}
+static enum sdw_command_response +amd_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- struct sdw_msg msg;
- /* Create dummy message with valid device number */
- memset(&msg, 0, sizeof(msg));
- msg.dev_num = dev_num;
- return amd_program_scp_addr(ctrl, &msg);
+}
+static u32 amd_sdwc_read_ping_status(struct sdw_bus *bus) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u64 response;
- u32 slave_stat = 0;
- response = amd_sdwc_send_cmd_get_resp(ctrl, 0, 0);
- /* slave status from ping response*/
- slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
- dev_dbg(ctrl->dev, "%s: slave_stat:0x%x\n", __func__, slave_stat);
- return slave_stat;
+}
+static void amd_sdwc_compute_slave_ports(struct sdw_master_runtime *m_rt,
struct sdw_transport_data *t_data)
+{
- struct sdw_slave_runtime *s_rt = NULL;
- struct sdw_port_runtime *p_rt;
- int port_bo, sample_int;
- unsigned int rate, bps, ch = 0;
- unsigned int slave_total_ch;
- struct sdw_bus_params *b_params = &m_rt->bus->params;
- port_bo = t_data->block_offset;
- list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (m_rt->bus->params.curr_dr_freq / rate);
slave_total_ch = 0;
list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
ch = sdw_ch_mask_to_ch(p_rt->ch_mask);
sdw_fill_xport_params(&p_rt->transport_params,
p_rt->num, false,
SDW_BLK_GRP_CNT_1,
sample_int, port_bo, port_bo >> 8,
t_data->hstart,
t_data->hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->s_data_mode);
port_bo += bps * ch;
slave_total_ch += ch;
}
if (m_rt->direction == SDW_DATA_DIR_TX &&
m_rt->ch_count == slave_total_ch) {
port_bo = t_data->block_offset;
}
- }
+}
ok, this is really bad.
This is a verbatim copy of the same function in generic_bandwidth_allocation.c
see https://elixir.bootlin.com/linux/latest/source/drivers/soundwire/generic_ban...
You only removed the comments and renamed the function.
Seriously? Why would you do that?
And in addition, this has *NOTHING* to do with the master support.
Programming the ports on peripheral side is something that happens at the stream level.
I am afraid it's a double NAK, or rather NAK^2 from me here.
+static int amd_sdwc_compute_params(struct sdw_bus *bus) +{
- struct sdw_transport_data t_data = {0};
- struct sdw_master_runtime *m_rt;
- struct sdw_port_runtime *p_rt;
- struct sdw_bus_params *b_params = &bus->params;
- int port_bo, hstart, hstop, sample_int;
- unsigned int rate, bps;
- port_bo = 0;
- hstart = 1;
- hstop = bus->params.col - 1;
- t_data.hstop = hstop;
- t_data.hstart = hstart;
- list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (bus->params.curr_dr_freq / rate);
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
port_bo = (p_rt->num * 64) + 1;
dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n",
p_rt->num, hstart, hstop, port_bo);
sdw_fill_xport_params(&p_rt->transport_params, p_rt->num,
false, SDW_BLK_GRP_CNT_1, sample_int,
port_bo, port_bo >> 8, hstart, hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->m_data_mode);
t_data.hstart = hstart;
t_data.hstop = hstop;
t_data.block_offset = port_bo;
t_data.sub_block_offset = 0;
}
amd_sdwc_compute_slave_ports(m_rt, &t_data);
- }
- return 0;
+}
this is a variation on sdw_compute_master_ports() in generic_allocation.c
You would need a lot more comments to convince me that this is intentional and needed.
+static int amd_sdwc_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params,
unsigned int bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 channel_type, frame_fmt_reg, dpn_frame_fmt;
- dev_dbg(ctrl->dev, "%s: p_params->num:0x%x\n", __func__, p_params->num);
- switch (ctrl->instance) {
- case ACP_SDW0:
channel_type = p_params->num;
break;
- case ACP_SDW1:
channel_type = p_params->num + ACP_SDW0_MAX_DAI;
break;
- default:
return -EINVAL;
- }
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
you'll have to explain what you mean by 'channel_type'
This looks like the streams that can be supported by this master implementation, with dailinks for each.
frame_fmt_reg = ACP_SW_AUDIO_TX_FRAME_FORMAT;
break;
- case ACP_SDW0_HS_TX:
frame_fmt_reg = ACP_SW_HEADSET_TX_FRAME_FORMAT;
break;
- case ACP_SDW0_BT_TX:
frame_fmt_reg = ACP_SW_BT_TX_FRAME_FORMAT;
break;
- case ACP_SDW1_BT_TX:
frame_fmt_reg = ACP_P1_SW_BT_TX_FRAME_FORMAT;
break;
- case ACP_SDW0_AUDIO_RX:
frame_fmt_reg = ACP_SW_AUDIO_RX_FRAME_FORMAT;
break;
- case ACP_SDW0_HS_RX:
frame_fmt_reg = ACP_SW_HEADSET_RX_FRAME_FORMAT;
break;
- case ACP_SDW0_BT_RX:
frame_fmt_reg = ACP_SW_BT_RX_FRAME_FORMAT;
break;
- case ACP_SDW1_BT_RX:
frame_fmt_reg = ACP_P1_SW_BT_RX_FRAME_FORMAT;
break;
- default:
dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type);
return -EINVAL;
- }
- dpn_frame_fmt = acp_reg_readl(ctrl->mmio + frame_fmt_reg);
- u32p_replace_bits(&dpn_frame_fmt, p_params->flow_mode, AMD_DPN_FRAME_FMT_PFM);
- u32p_replace_bits(&dpn_frame_fmt, p_params->data_mode, AMD_DPN_FRAME_FMT_PDM);
- u32p_replace_bits(&dpn_frame_fmt, p_params->bps - 1, AMD_DPN_FRAME_FMT_WORD_LEN);
- acp_reg_writel(dpn_frame_fmt, ctrl->mmio + frame_fmt_reg);
- return 0;
+}
+static int amd_sdwc_transport_params(struct sdw_bus *bus,
struct sdw_transport_params *params,
enum sdw_reg_bank bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 ssp_counter_reg;
- u32 dpn_frame_fmt;
- u32 dpn_sampleinterval;
- u32 dpn_hctrl;
- u32 dpn_offsetctrl;
- u32 dpn_lanectrl;
- u32 channel_type;
- u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg;
- u32 offset_reg, lane_ctrl_reg;
- switch (ctrl->instance) {
- case ACP_SDW0:
ssp_counter_reg = ACP_SW_SSP_COUNTER;
channel_type = params->port_num;
break;
- case ACP_SDW1:
ssp_counter_reg = ACP_P1_SW_SSP_COUNTER;
channel_type = params->port_num + ACP_SDW0_MAX_DAI;
There's obviously a dependency between SDW0 and SDW1 managers that you haven't described?
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_SSP_COUNTER_VAL, ctrl->mmio + ssp_counter_reg);
- dev_dbg(bus->dev, "%s: p_params->num:0x%x entry channel_type:0x%x\n",
__func__, params->port_num, channel_type);
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
- {
frame_fmt_reg = ACP_SW_AUDIO_TX_FRAME_FORMAT;
sample_int_reg = ACP_SW_AUDIO_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_AUDIO_TX_HCTRL_DP0;
offset_reg = ACP_SW_AUDIO_TX_OFFSET_DP0;
lane_ctrl_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
This is confusing. Is this about enabling a stream or selecting the lane for this port? Same for all cases.
is this saying that the two cases are handled by the same register - unlike what is normative for the peripherals where the two concepts are handeld in DPN_ChannelEn and DPN_LaneCtrl registers?
break;
- }
- case ACP_SDW0_HS_TX:
- {
frame_fmt_reg = ACP_SW_HEADSET_TX_FRAME_FORMAT;
sample_int_reg = ACP_SW_HEADSET_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_HEADSET_TX_HCTRL;
offset_reg = ACP_SW_HEADSET_TX_OFFSET;
lane_ctrl_reg = ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW0_BT_TX:
- {
frame_fmt_reg = ACP_SW_BT_TX_FRAME_FORMAT;
sample_int_reg = ACP_SW_BT_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_BT_TX_HCTRL;
offset_reg = ACP_SW_BT_TX_OFFSET;
lane_ctrl_reg = ACP_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW1_BT_TX:
- {
frame_fmt_reg = ACP_P1_SW_BT_TX_FRAME_FORMAT;
sample_int_reg = ACP_P1_SW_BT_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_P1_SW_BT_TX_HCTRL;
offset_reg = ACP_P1_SW_BT_TX_OFFSET;
lane_ctrl_reg = ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW0_AUDIO_RX:
- {
frame_fmt_reg = ACP_SW_AUDIO_RX_FRAME_FORMAT;
sample_int_reg = ACP_SW_AUDIO_RX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_AUDIO_RX_HCTRL_DP0;
offset_reg = ACP_SW_AUDIO_RX_OFFSET_DP0;
lane_ctrl_reg = ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW0_HS_RX:
- {
frame_fmt_reg = ACP_SW_HEADSET_RX_FRAME_FORMAT;
sample_int_reg = ACP_SW_HEADSET_RX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_HEADSET_RX_HCTRL;
offset_reg = ACP_SW_HEADSET_RX_OFFSET;
lane_ctrl_reg = ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW0_BT_RX:
- {
frame_fmt_reg = ACP_SW_BT_RX_FRAME_FORMAT;
sample_int_reg = ACP_SW_BT_RX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_BT_RX_HCTRL;
offset_reg = ACP_SW_BT_RX_OFFSET;
lane_ctrl_reg = ACP_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW1_BT_RX:
- {
frame_fmt_reg = ACP_P1_SW_BT_RX_FRAME_FORMAT;
sample_int_reg = ACP_P1_SW_BT_RX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_P1_SW_BT_RX_HCTRL;
offset_reg = ACP_P1_SW_BT_RX_OFFSET;
lane_ctrl_reg = ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- }
- default:
dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type);
return -EINVAL;
- }
- dpn_frame_fmt = acp_reg_readl(ctrl->mmio + frame_fmt_reg);
- u32p_replace_bits(&dpn_frame_fmt, params->blk_pkg_mode, AMD_DPN_FRAME_FMT_BLK_PKG_MODE);
- u32p_replace_bits(&dpn_frame_fmt, params->blk_grp_ctrl, AMD_DPN_FRAME_FMT_BLK_GRP_CTRL);
- u32p_replace_bits(&dpn_frame_fmt, SDW_STREAM_PCM, AMD_DPN_FRAME_FMT_PCM_OR_PDM);
- acp_reg_writel(dpn_frame_fmt, ctrl->mmio + frame_fmt_reg);
- dpn_sampleinterval = params->sample_interval - 1;
- acp_reg_writel(dpn_sampleinterval, ctrl->mmio + sample_int_reg);
- dpn_hctrl = FIELD_PREP(AMD_DPN_HCTRL_HSTOP, params->hstop);
- dpn_hctrl |= FIELD_PREP(AMD_DPN_HCTRL_HSTART, params->hstart);
- acp_reg_writel(dpn_hctrl, ctrl->mmio + hctrl_dp0_reg);
- dpn_offsetctrl = FIELD_PREP(AMD_DPN_OFFSET_CTRL_1, params->offset1);
- dpn_offsetctrl |= FIELD_PREP(AMD_DPN_OFFSET_CTRL_2, params->offset2);
- acp_reg_writel(dpn_offsetctrl, ctrl->mmio + offset_reg);
- dpn_lanectrl = acp_reg_readl(ctrl->mmio + lane_ctrl_reg);
- u32p_replace_bits(&dpn_lanectrl, params->lane_ctrl, AMD_DPN_CH_EN_LCTRL);
- acp_reg_writel(dpn_lanectrl, ctrl->mmio + lane_ctrl_reg);
- return 0;
+}
+static int amd_sdwc_port_enable(struct sdw_bus *bus,
struct sdw_enable_ch *enable_ch,
unsigned int bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 dpn_ch_enable;
- u32 ch_enable_reg, channel_type;
- switch (ctrl->instance) {
- case ACP_SDW0:
channel_type = enable_ch->port_num;
break;
- case ACP_SDW1:
channel_type = enable_ch->port_num + ACP_SDW0_MAX_DAI;
break;
- default:
return -EINVAL;
- }
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
ch_enable_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
in the function above, I commented on
lane_ctrl_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
This looks really weird. You need to add comments is this is really intentional.
break;
- case ACP_SDW0_HS_TX:
ch_enable_reg = ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_BT_TX:
ch_enable_reg = ACP_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW1_BT_TX:
ch_enable_reg = ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_AUDIO_RX:
ch_enable_reg = ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_HS_RX:
ch_enable_reg = ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_BT_RX:
ch_enable_reg = ACP_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW1_BT_RX:
ch_enable_reg = ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- default:
dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type);
return -EINVAL;
- }
- dpn_ch_enable = acp_reg_readl(ctrl->mmio + ch_enable_reg);
- u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK);
- if (enable_ch->enable)
acp_reg_writel(dpn_ch_enable, ctrl->mmio + ch_enable_reg);
- else
acp_reg_writel(0, ctrl->mmio + ch_enable_reg);
- return 0;
+}
+static int sdw_master_read_amd_prop(struct sdw_bus *bus) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- struct fwnode_handle *link;
- struct sdw_master_prop *prop;
- u32 quirk_mask = 0;
- u32 wake_en_mask = 0;
- u32 power_mode_mask = 0;
- char name[32];
- prop = &bus->prop;
- /* Find master handle */
- snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", bus->link_id);
- link = device_get_named_child_node(bus->dev, name);
- if (!link) {
dev_err(bus->dev, "Master node %s not found\n", name);
return -EIO;
- }
- fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask);
- if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE))
prop->hw_disabled = true;
same quirk as Intel, nice :-)
- prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
And here too. Is this really needed or just-copy-pasted?
- fwnode_property_read_u32(link, "amd-sdw-wake-enable", &wake_en_mask);
- ctrl->wake_en_mask = wake_en_mask;
- fwnode_property_read_u32(link, "amd-sdw-power-mode", &power_mode_mask);
- ctrl->power_mode_mask = power_mode_mask;
- return 0;
+}
+static int amd_sdwc_probe(struct platform_device *pdev)
why not use an auxiliary device? we've moved away from platform devices maybe two years ago.
+{
- const struct acp_sdw_pdata *pdata = pdev->dev.platform_data;
- struct resource *res;
- struct device *dev = &pdev->dev;
- struct sdw_master_prop *prop;
- struct sdw_bus_params *params;
- struct amd_sdwc_ctrl *ctrl;
- int ret;
- if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "platform_data not retrieved\n");
return -ENODEV;
- }
- ctrl = devm_kzalloc(&pdev->dev, sizeof(struct amd_sdwc_ctrl), GFP_KERNEL);
- if (!ctrl)
return -ENOMEM;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENOMEM;
- }
- ctrl->mmio = devm_ioremap(&pdev->dev, res->start, resource_size(res));
- if (IS_ERR(ctrl->mmio)) {
dev_err(&pdev->dev, "mmio not found\n");
return PTR_ERR(ctrl->mmio);
- }
- ctrl->instance = pdata->instance;
- ctrl->sdw_lock = pdata->sdw_lock;
- ctrl->rows_index = sdw_find_row_index(50);
- ctrl->cols_index = sdw_find_col_index(10);
- ctrl->dev = dev;
- dev_set_drvdata(&pdev->dev, ctrl);
- ctrl->bus.ops = &amd_sdwc_ops;
- ctrl->bus.port_ops = &amd_sdwc_port_ops;
- ctrl->bus.compute_params = &amd_sdwc_compute_params;
- ctrl->bus.clk_stop_timeout = 1;
- switch (ctrl->instance) {
- case ACP_SDW0:
ctrl->num_dout_ports = AMD_SDW0_MAX_TX_PORTS;
ctrl->num_din_ports = AMD_SDW0_MAX_RX_PORTS;
break;
- case ACP_SDW1:
ctrl->num_dout_ports = AMD_SDW1_MAX_TX_PORTS;
ctrl->num_din_ports = AMD_SDW1_MAX_RX_PORTS;
break;
- default:
return -EINVAL;
- }
- params = &ctrl->bus.params;
- params->max_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->curr_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->col = 10;
- params->row = 50;
- prop = &ctrl->bus.prop;
- prop->clk_freq = &amd_sdwc_freq_tbl[0];
- prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ;
- ctrl->bus.link_id = ctrl->instance;
- ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode);
- if (ret) {
dev_err(dev, "Failed to register Soundwire controller (%d)\n",
master. the confusion continues.
ret);
return ret;
- }
- INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work);
- schedule_work(&ctrl->probe_work);
- return 0;
+}
+static int amd_sdwc_remove(struct platform_device *pdev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(&pdev->dev);
- int ret;
- amd_disable_sdw_interrupts(ctrl);
- sdw_bus_master_delete(&ctrl->bus);
- ret = amd_disable_sdw_controller(ctrl);
- return ret;
+}
+static struct platform_driver amd_sdwc_driver = {
- .probe = &amd_sdwc_probe,
- .remove = &amd_sdwc_remove,
- .driver = {
.name = "amd_sdw_controller",
- }
+}; +module_platform_driver(amd_sdwc_driver);
+MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD soundwire driver"); +MODULE_LICENSE("GPL v2");
"GPL" is enough
+enum amd_sdw_channel {
- /* SDW0 */
- ACP_SDW0_AUDIO_TX = 0,
- ACP_SDW0_BT_TX,
- ACP_SDW0_HS_TX,
- ACP_SDW0_AUDIO_RX,
- ACP_SDW0_BT_RX,
- ACP_SDW0_HS_RX,
- /* SDW1 */
- ACP_SDW1_BT_TX,
- ACP_SDW1_BT_RX,
+};
you really need to comment on this. It looks like you've special-cased manager ports for specific usages? This is perfectly fine in closed applications, but it's not clear how it might work with headset, amplifier and mic codec devices.
diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index f0123815af46..5ec39f8c2f2e 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -10,9 +10,30 @@
#define AMD_SDW_CLK_STOP_MODE 1 #define AMD_SDW_POWER_OFF_MODE 2 +#define ACP_SDW0 0 +#define ACP_SDW1 1 +#define ACP_SDW0_MAX_DAI 6
is this related to the definition of amd_sdw_channel or the number of ports available?
struct acp_sdw_pdata { u16 instance; struct mutex *sdw_lock; };
+struct amd_sdwc_ctrl {
- struct sdw_bus bus;
- struct device *dev;
- void __iomem *mmio;
- struct work_struct probe_work;
- struct mutex *sdw_lock;
comment please.
- int num_din_ports;
- int num_dout_ports;
- int cols_index;
- int rows_index;
- u32 instance;
- u32 quirks;
- u32 wake_en_mask;
- int num_ports;
- bool startup_done;
ah this was an Intel definition. Due to power dependencies we had to split the probe and startup step. Does AMD have a need for this? Is the SoundWire master IP dependent on DSP boot or something?
- u32 power_mode_mask;
+}; #endif
On 11/01/23 20:07, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
AMD ACP IP block has two soundwire controller devices.
s/controller/manager?
will rephrase the commit message.
Add support for
- Master driver probe & remove sequence
- Helper functions to enable/disable interrupts, Initialize sdw controller, enable sdw pads
- Master driver sdw_master_ops & port_ops callbacks
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
drivers/soundwire/amd_master.c | 1075 +++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 279 ++++++++ include/linux/soundwire/sdw_amd.h | 21 + 3 files changed, 1375 insertions(+) create mode 100644 drivers/soundwire/amd_master.c create mode 100644 drivers/soundwire/amd_master.h
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c new file mode 100644 index 000000000000..7e1f618254ac --- /dev/null +++ b/drivers/soundwire/amd_master.c @@ -0,0 +1,1075 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- SoundWire AMD Master driver
- Copyright 2023 Advanced Micro Devices, Inc.
- */
+#include <linux/completion.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw_amd.h> +#include <linux/wait.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bus.h" +#include "amd_master.h"
+#define DRV_NAME "amd_sdw_controller"
+#define to_amd_sdw(b) container_of(b, struct amd_sdwc_ctrl, bus)
+static int amd_enable_sdw_pads(struct amd_sdwc_ctrl *ctrl) +{
- u32 sw_pad_enable_mask;
- u32 sw_pad_pulldown_mask;
- u32 sw_pad_pulldown_val;
- u32 val = 0;
- switch (ctrl->instance) {
Goodness no. A controller has one or more masters. It cannot have pins as described in the SoundWire master specification.
we are referring to manager instance only. will modify the private data structure name.
- case ACP_SDW0:
sw_pad_enable_mask = AMD_SDW0_PAD_KEEPER_EN_MASK;
sw_pad_pulldown_mask = AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK;
break;
- case ACP_SDW1:
sw_pad_enable_mask = AMD_SDW1_PAD_KEEPER_EN_MASK;
sw_pad_pulldown_mask = AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK;
break;
- default:
return -EINVAL;
- }
- mutex_lock(ctrl->sdw_lock);
- val = acp_reg_readl(ctrl->mmio + ACP_SW_PAD_KEEPER_EN);
- val |= sw_pad_enable_mask;
- acp_reg_writel(val, ctrl->mmio + ACP_SW_PAD_KEEPER_EN);
- mutex_unlock(ctrl->sdw_lock);
- usleep_range(1000, 1500);
- mutex_lock(ctrl->sdw_lock);
- sw_pad_pulldown_val = acp_reg_readl(ctrl->mmio + ACP_PAD_PULLDOWN_CTRL);
- sw_pad_pulldown_val &= sw_pad_pulldown_mask;
- acp_reg_writel(sw_pad_pulldown_val, ctrl->mmio + ACP_PAD_PULLDOWN_CTRL);
- mutex_unlock(ctrl->sdw_lock);
- return 0;
+}
+static int amd_init_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{
- u32 acp_sw_en_reg, acp_sw_en_stat_reg, sw_bus_reset_reg;
- u32 val = 0;
- u32 timeout = 0;
- u32 retry_count = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
acp_sw_en_reg = ACP_SW_EN;
acp_sw_en_stat_reg = ACP_SW_EN_STATUS;
sw_bus_reset_reg = ACP_SW_BUS_RESET_CTRL;
break;
- case ACP_SDW1:
acp_sw_en_reg = ACP_P1_SW_EN;
acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS;
sw_bus_reset_reg = ACP_P1_SW_BUS_RESET_CTRL;
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_ENABLE, ctrl->mmio + acp_sw_en_reg);
- do {
val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg);
if (val)
break;
usleep_range(10, 50);
- } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT);
- if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT)
return -ETIMEDOUT;
- /* Sdw Controller reset */
- acp_reg_writel(AMD_SDW_BUS_RESET_REQ, ctrl->mmio + sw_bus_reset_reg);
- val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg);
- while (!(val & AMD_SDW_BUS_RESET_DONE)) {
val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg);
if (timeout > AMD_DELAY_LOOP_ITERATION)
break;
usleep_range(1, 5);
timeout++;
- }
no test on timeout here to check if the bus was indeed reset?
It's a miss. we will add the code.
If you are talking about bus_reset you are referring to a master btw. The terms bus/master/link are interchangeable. A controller is not defined in the SoundWire specification, this is part of the DisCo spec to deal with enumeration when multiple bus/master/link are supported in the platform.
- timeout = 0;
- acp_reg_writel(AMD_SDW_BUS_RESET_CLEAR_REQ, ctrl->mmio + sw_bus_reset_reg);
- val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg);
- while (val) {
val = acp_reg_readl(ctrl->mmio + sw_bus_reset_reg);
if (timeout > AMD_DELAY_LOOP_ITERATION)
break;
usleep_range(1, 5);
timeout++;
- }
- if (timeout == AMD_DELAY_LOOP_ITERATION) {
dev_err(ctrl->dev, "Failed to reset SW%x Soundwire Controller\n", ctrl->instance);
return -ETIMEDOUT;
- }
- retry_count = 0;
- acp_reg_writel(AMD_SDW_DISABLE, ctrl->mmio + acp_sw_en_reg);
- do {
val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg);
if (!val)
break;
usleep_range(10, 50);
- } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT);
- if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT)
return -ETIMEDOUT;
- return 0;
+}
+static int amd_enable_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{
- u32 acp_sw_en_reg;
- u32 acp_sw_en_stat_reg;
- u32 val = 0;
- u32 retry_count = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
acp_sw_en_reg = ACP_SW_EN;
acp_sw_en_stat_reg = ACP_SW_EN_STATUS;
break;
- case ACP_SDW1:
acp_sw_en_reg = ACP_P1_SW_EN;
acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS;
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_ENABLE, ctrl->mmio + acp_sw_en_reg);
- do {
val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg);
if (val)
break;
usleep_range(10, 50);
- } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT);
- if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT)
return -ETIMEDOUT;
- return 0;
+}
+static int amd_disable_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{
- u32 clk_resume_ctrl_reg;
- u32 acp_sw_en_reg;
- u32 acp_sw_en_stat_reg;
- u32 val = 0;
- u32 retry_count = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
acp_sw_en_reg = ACP_SW_EN;
acp_sw_en_stat_reg = ACP_SW_EN_STATUS;
clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL;
break;
- case ACP_SDW1:
acp_sw_en_reg = ACP_P1_SW_EN;
acp_sw_en_stat_reg = ACP_P1_SW_EN_STATUS;
clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL;
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_DISABLE, ctrl->mmio + acp_sw_en_reg);
- /*
* After invoking controller disable sequence, check whether
* controller has executed clock stop sequence. In this case,
* controller should ignore checking enable status register.
again clock stop is a sequence at the master/link/bus level, not the controller.
Throughout the code we are referring manager instance only.
*/
- val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
- if (val)
return 0;
- do {
val = acp_reg_readl(ctrl->mmio + acp_sw_en_stat_reg);
if (!val)
break;
usleep_range(10, 50);
- } while (retry_count++ < AMD_SDW_STAT_MAX_RETRY_COUNT);
- if (retry_count > AMD_SDW_STAT_MAX_RETRY_COUNT)
return -ETIMEDOUT;
- return 0;
+}
+static int amd_enable_sdw_interrupts(struct amd_sdwc_ctrl *ctrl) +{
- u32 val;
- u32 acp_ext_intr_stat, acp_ext_intr_ctrl, acp_sdw_intr_mask;
- u32 sw_stat_mask_0to7, sw_stat_mask_8to11, sw_err_intr_mask;
- switch (ctrl->instance) {
- case ACP_SDW0:
acp_ext_intr_ctrl = ACP_EXTERNAL_INTR_CNTL;
should be renamed and end in CNTL0 if the other is CNTL1
Its register header file macro. It's still good to go.
And it's manager anyways, not controller.
It's manager only.
acp_sdw_intr_mask = AMD_SDW0_EXT_INTR_MASK;
acp_ext_intr_stat = ACP_EXTERNAL_INTR_STAT;
sw_stat_mask_0to7 = SW_STATE_CHANGE_STATUS_MASK_0TO7;
sw_stat_mask_8to11 = SW_STATE_CHANGE_STATUS_MASK_8TO11;
sw_err_intr_mask = SW_ERROR_INTR_MASK;
break;
- case ACP_SDW1:
acp_ext_intr_ctrl = ACP_EXTERNAL_INTR_CNTL1;
acp_sdw_intr_mask = AMD_SDW1_EXT_INTR_MASK;
acp_ext_intr_stat = ACP_EXTERNAL_INTR_STAT1;
sw_stat_mask_0to7 = P1_SW_STATE_CHANGE_STATUS_MASK_0TO7;
sw_stat_mask_8to11 = P1_SW_STATE_CHANGE_STATUS_MASK_8TO11;
sw_err_intr_mask = P1_SW_ERROR_INTR_MASK;
break;
- default:
return -EINVAL;
- }
- mutex_lock(ctrl->sdw_lock);
- val = acp_reg_readl(ctrl->mmio + acp_ext_intr_ctrl);
- val |= acp_sdw_intr_mask;
- acp_reg_writel(val, ctrl->mmio + acp_ext_intr_ctrl);
- val = acp_reg_readl(ctrl->mmio + acp_ext_intr_ctrl);
- mutex_unlock(ctrl->sdw_lock);
- dev_dbg(ctrl->dev, "%s: acp_ext_intr_ctrl[0x%x]:0x%x\n", __func__, acp_ext_intr_ctrl, val);
- val = acp_reg_readl(ctrl->mmio + acp_ext_intr_stat);
- if (val)
acp_reg_writel(val, ctrl->mmio + acp_ext_intr_stat);
- acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, ctrl->mmio + sw_stat_mask_0to7);
- acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, ctrl->mmio + sw_stat_mask_8to11);
- acp_reg_writel(AMD_SDW_IRQ_ERROR_MASK, ctrl->mmio + sw_err_intr_mask);
- return 0;
+}
+static u64 amd_sdwc_send_cmd_get_resp(struct amd_sdwc_ctrl *ctrl, u32 lword, u32 uword) +{
- u64 resp = 0;
- u32 imm_cmd_stat_reg, imm_cmd_uword_reg, imm_cmd_lword_reg;
- u32 imm_resp_uword_reg, imm_resp_lword_reg;
- u32 resp_lower, resp_high;
- u32 sts = 0;
- u32 timeout = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
imm_cmd_stat_reg = SW_IMM_CMD_STS;
imm_cmd_uword_reg = SW_IMM_CMD_UPPER_WORD;
imm_cmd_lword_reg = SW_IMM_CMD_LOWER_QWORD;
imm_resp_uword_reg = SW_IMM_RESP_UPPER_WORD;
imm_resp_lword_reg = SW_IMM_RESP_LOWER_QWORD;
break;
- case ACP_SDW1:
imm_cmd_stat_reg = P1_SW_IMM_CMD_STS;
imm_cmd_uword_reg = P1_SW_IMM_CMD_UPPER_WORD;
imm_cmd_lword_reg = P1_SW_IMM_CMD_LOWER_QWORD;
imm_resp_uword_reg = P1_SW_IMM_RESP_UPPER_WORD;
imm_resp_lword_reg = P1_SW_IMM_RESP_LOWER_QWORD;
naming consistency would be good, the P1 is sometimes a prefix, sometimes a suffix, sometimes in the middle. Pick one.
These are taken from our AMD register header file. It's still good to go.
break;
- default:
return -EINVAL;
- }
- sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
- while (sts & AMD_SDW_IMM_CMD_BUSY) {
sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
if (timeout > AMD_SDW_RETRY_COUNT) {
dev_err(ctrl->dev, "SDW%x previous cmd status clear failed\n",
ctrl->instance);
return -ETIMEDOUT;
}
timeout++;
- }
- timeout = 0;
- if (sts & AMD_SDW_IMM_RES_VALID) {
dev_err(ctrl->dev, "SDW%x controller is in bad state\n", ctrl->instance);
acp_reg_writel(0x00, ctrl->mmio + imm_cmd_stat_reg);
- }
- acp_reg_writel(uword, ctrl->mmio + imm_cmd_uword_reg);
- acp_reg_writel(lword, ctrl->mmio + imm_cmd_lword_reg);
- sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
- while (!(sts & AMD_SDW_IMM_RES_VALID)) {
sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
if (timeout > AMD_SDW_RETRY_COUNT) {
dev_err(ctrl->dev, "SDW%x cmd response timeout occurred\n", ctrl->instance);
return -ETIMEDOUT;
}
timeout++;
- }
- resp_high = acp_reg_readl(ctrl->mmio + imm_resp_uword_reg);
- resp_lower = acp_reg_readl(ctrl->mmio + imm_resp_lword_reg);
- timeout = 0;
- acp_reg_writel(AMD_SDW_IMM_RES_VALID, ctrl->mmio + imm_cmd_stat_reg);
- while ((sts & AMD_SDW_IMM_RES_VALID)) {
sts = acp_reg_readl(ctrl->mmio + imm_cmd_stat_reg);
if (timeout > AMD_SDW_RETRY_COUNT) {
dev_err(ctrl->dev, "SDW%x cmd status retry failed\n", ctrl->instance);
return -ETIMEDOUT;
}
timeout++;
- }
- resp = resp_high;
- resp = (resp << 32) | resp_lower;
- return resp;
+}
+static enum sdw_command_response +amd_program_scp_addr(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg) +{
- struct sdw_msg scp_msg = {0};
- u64 response_buf[2] = {0};
- u32 uword = 0, lword = 0;
- int nack = 0, no_ack = 0;
- int index, timeout = 0;
- scp_msg.dev_num = msg->dev_num;
- scp_msg.addr = SDW_SCP_ADDRPAGE1;
- scp_msg.buf = &msg->addr_page1;
- amd_sdwc_ctl_word_prep(&lword, &uword, AMD_SDW_CMD_WRITE, &scp_msg, 0);
- response_buf[0] = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword);
- scp_msg.addr = SDW_SCP_ADDRPAGE2;
- scp_msg.buf = &msg->addr_page2;
- amd_sdwc_ctl_word_prep(&lword, &uword, AMD_SDW_CMD_WRITE, &scp_msg, 0);
- response_buf[1] = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword);
- /* check response the writes */
response to the writes? after the writes?
will correct the comment.
- for (index = 0; index < 2; index++) {
if (response_buf[index] == -ETIMEDOUT) {
dev_err(ctrl->dev, "Program SCP cmd timeout\n");
timeout = 1;
} else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
no_ack = 1;
if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(ctrl->dev, "Program SCP NACK received\n");
}
this is a copy of the cadence_master.c code... With the error added that this is not for a controller but for a master...
Its manager instance only. Our immediate command and response mechanism allows sending commands over the link and get the response for every command immediately, unlike as mentioned in candence_master.c.
}
- }
- if (timeout) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_dbg_ratelimited(ctrl->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
this should probably become a helper since the response is really the same as in cadence_master.c
There's really room for optimization and reuse here.
not really needed. Please refer above comment as command/response mechanism differs from Intel's implementation.
+}
+static int amd_prep_msg(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg, int *cmd) +{
- int ret;
- if (msg->page) {
ret = amd_program_scp_addr(ctrl, msg);
if (ret) {
msg->len = 0;
return ret;
}
- }
- switch (msg->flags) {
- case SDW_MSG_FLAG_READ:
*cmd = AMD_SDW_CMD_READ;
break;
- case SDW_MSG_FLAG_WRITE:
*cmd = AMD_SDW_CMD_WRITE;
break;
- default:
dev_err(ctrl->dev, "Invalid msg cmd: %d\n", msg->flags);
return -EINVAL;
- }
- return 0;
+}
this is the same code as in cadence_master.c
you just replaced sdw_cnds by amd_sdw_ctrl (which is a mistake) and cdns->dev by ctrl->dev.
+static unsigned int _amd_sdwc_xfer_msg(struct amd_sdwc_ctrl *ctrl, struct sdw_msg *msg,
int cmd, int cmd_offset)
+{
- u64 response = 0;
- u32 uword = 0, lword = 0;
- int nack = 0, no_ack = 0;
- int timeout = 0;
- amd_sdwc_ctl_word_prep(&lword, &uword, cmd, msg, cmd_offset);
- response = amd_sdwc_send_cmd_get_resp(ctrl, lword, uword);
- if (response & AMD_SDW_MCP_RESP_ACK) {
if (cmd == AMD_SDW_CMD_READ)
msg->buf[cmd_offset] = FIELD_GET(AMD_SDW_MCP_RESP_RDATA, response);
- } else {
no_ack = 1;
if (response == -ETIMEDOUT) {
timeout = 1;
} else if (response & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(ctrl->dev, "Program SCP NACK received\n");
}
- }
- if (timeout) {
dev_err_ratelimited(ctrl->dev, "command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(ctrl->dev,
"command response NACK received for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_err_ratelimited(ctrl->dev, "command is ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
+}
+static enum sdw_command_response amd_sdwc_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- int ret, i;
- int cmd = 0;
- ret = amd_prep_msg(ctrl, msg, &cmd);
- if (ret)
return SDW_CMD_FAIL_OTHER;
- for (i = 0; i < msg->len; i++) {
ret = _amd_sdwc_xfer_msg(ctrl, msg, cmd, i);
if (ret)
return ret;
- }
- return SDW_CMD_OK;
+}
+static enum sdw_command_response +amd_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- struct sdw_msg msg;
- /* Create dummy message with valid device number */
- memset(&msg, 0, sizeof(msg));
- msg.dev_num = dev_num;
- return amd_program_scp_addr(ctrl, &msg);
+}
+static u32 amd_sdwc_read_ping_status(struct sdw_bus *bus) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u64 response;
- u32 slave_stat = 0;
- response = amd_sdwc_send_cmd_get_resp(ctrl, 0, 0);
- /* slave status from ping response*/
- slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
- dev_dbg(ctrl->dev, "%s: slave_stat:0x%x\n", __func__, slave_stat);
- return slave_stat;
+}
+static void amd_sdwc_compute_slave_ports(struct sdw_master_runtime *m_rt,
struct sdw_transport_data *t_data)
+{
- struct sdw_slave_runtime *s_rt = NULL;
- struct sdw_port_runtime *p_rt;
- int port_bo, sample_int;
- unsigned int rate, bps, ch = 0;
- unsigned int slave_total_ch;
- struct sdw_bus_params *b_params = &m_rt->bus->params;
- port_bo = t_data->block_offset;
- list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (m_rt->bus->params.curr_dr_freq / rate);
slave_total_ch = 0;
list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
ch = sdw_ch_mask_to_ch(p_rt->ch_mask);
sdw_fill_xport_params(&p_rt->transport_params,
p_rt->num, false,
SDW_BLK_GRP_CNT_1,
sample_int, port_bo, port_bo >> 8,
t_data->hstart,
t_data->hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->s_data_mode);
port_bo += bps * ch;
slave_total_ch += ch;
}
if (m_rt->direction == SDW_DATA_DIR_TX &&
m_rt->ch_count == slave_total_ch) {
port_bo = t_data->block_offset;
}
- }
+}
ok, this is really bad.
This is a verbatim copy of the same function in generic_bandwidth_allocation.c
see https://nam11.safelinks.protection.outlook.com/?url=https%3A%2F%2Felixir.boo...
You only removed the comments and renamed the function.
Seriously? Why would you do that?
And in addition, this has *NOTHING* to do with the master support.
Programming the ports on peripheral side is something that happens at the stream level.
I am afraid it's a double NAK, or rather NAK^2 from me here.
Our intention is to implement our own compute params callback. Sorry, instead of making a copied one , we could have exported this API.
+static int amd_sdwc_compute_params(struct sdw_bus *bus) +{
- struct sdw_transport_data t_data = {0};
- struct sdw_master_runtime *m_rt;
- struct sdw_port_runtime *p_rt;
- struct sdw_bus_params *b_params = &bus->params;
- int port_bo, hstart, hstop, sample_int;
- unsigned int rate, bps;
- port_bo = 0;
- hstart = 1;
- hstop = bus->params.col - 1;
- t_data.hstop = hstop;
- t_data.hstart = hstart;
- list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (bus->params.curr_dr_freq / rate);
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
port_bo = (p_rt->num * 64) + 1;
dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n",
p_rt->num, hstart, hstop, port_bo);
sdw_fill_xport_params(&p_rt->transport_params, p_rt->num,
false, SDW_BLK_GRP_CNT_1, sample_int,
port_bo, port_bo >> 8, hstart, hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->m_data_mode);
t_data.hstart = hstart;
t_data.hstop = hstop;
t_data.block_offset = port_bo;
t_data.sub_block_offset = 0;
}
amd_sdwc_compute_slave_ports(m_rt, &t_data);
- }
- return 0;
+}
this is a variation on sdw_compute_master_ports() in generic_allocation.c
You would need a lot more comments to convince me that this is intentional and needed.
This is intentional. We have a HW bug, if we go it generic bdw allocation API, when we launch multiple streams, we are observing noise for shorter duration for active stream. To avoid that, we have slightly modified the sdw_compute_master_ports() API. As of now we are enabling solution for 48khz, 2Ch, 16bit. We will expand the coverage in the future.
+static int amd_sdwc_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params,
unsigned int bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 channel_type, frame_fmt_reg, dpn_frame_fmt;
- dev_dbg(ctrl->dev, "%s: p_params->num:0x%x\n", __func__, p_params->num);
- switch (ctrl->instance) {
- case ACP_SDW0:
channel_type = p_params->num;
break;
- case ACP_SDW1:
channel_type = p_params->num + ACP_SDW0_MAX_DAI;
break;
- default:
return -EINVAL;
- }
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
you'll have to explain what you mean by 'channel_type'
This looks like the streams that can be supported by this master implementation, with dailinks for each.
SDW0 Manager instance registers 6 CPU DAI (3 TX & 3 RX Ports) whereas SDW1 Manager Instance registers 2 CPU DAI (one TX & one RX port)
Each port number on Manager side is mapped to a channel number. i.e SDW0 Pin0 -> port number 0 -> Audio TX SDW0 Pin1 -> Port number 1 -> Headset TX SDW0 Pin2 -> Port number 2 -> BT TX SDW0 Pin3 -> port number 3 -> Audio RX SDW0 Pin4 -> Port number 4 -> Headset RX SDW0 Pin5 -> Port number 5 -> BT RX
Whereas for SDW1 instance
SDW1 Pin0 -> port number 0 -> P1 BT TX SDW1 Pin1 -> Port number 1 -> P1 BT RX We use this channel value to program register set for transport params, port params and Channel enable for each manager instance. We need to use same channel mapping for programming DMA controller registers in Soundwire DMA driver. i.e if AUDIO TX channel is selected then we need to use Audio TX registers for DMA programming in Soundwire DMA driver.
frame_fmt_reg = ACP_SW_AUDIO_TX_FRAME_FORMAT;
break;
- case ACP_SDW0_HS_TX:
frame_fmt_reg = ACP_SW_HEADSET_TX_FRAME_FORMAT;
break;
- case ACP_SDW0_BT_TX:
frame_fmt_reg = ACP_SW_BT_TX_FRAME_FORMAT;
break;
- case ACP_SDW1_BT_TX:
frame_fmt_reg = ACP_P1_SW_BT_TX_FRAME_FORMAT;
break;
- case ACP_SDW0_AUDIO_RX:
frame_fmt_reg = ACP_SW_AUDIO_RX_FRAME_FORMAT;
break;
- case ACP_SDW0_HS_RX:
frame_fmt_reg = ACP_SW_HEADSET_RX_FRAME_FORMAT;
break;
- case ACP_SDW0_BT_RX:
frame_fmt_reg = ACP_SW_BT_RX_FRAME_FORMAT;
break;
- case ACP_SDW1_BT_RX:
frame_fmt_reg = ACP_P1_SW_BT_RX_FRAME_FORMAT;
break;
- default:
dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type);
return -EINVAL;
- }
- dpn_frame_fmt = acp_reg_readl(ctrl->mmio + frame_fmt_reg);
- u32p_replace_bits(&dpn_frame_fmt, p_params->flow_mode, AMD_DPN_FRAME_FMT_PFM);
- u32p_replace_bits(&dpn_frame_fmt, p_params->data_mode, AMD_DPN_FRAME_FMT_PDM);
- u32p_replace_bits(&dpn_frame_fmt, p_params->bps - 1, AMD_DPN_FRAME_FMT_WORD_LEN);
- acp_reg_writel(dpn_frame_fmt, ctrl->mmio + frame_fmt_reg);
- return 0;
+}
+static int amd_sdwc_transport_params(struct sdw_bus *bus,
struct sdw_transport_params *params,
enum sdw_reg_bank bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 ssp_counter_reg;
- u32 dpn_frame_fmt;
- u32 dpn_sampleinterval;
- u32 dpn_hctrl;
- u32 dpn_offsetctrl;
- u32 dpn_lanectrl;
- u32 channel_type;
- u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg;
- u32 offset_reg, lane_ctrl_reg;
- switch (ctrl->instance) {
- case ACP_SDW0:
ssp_counter_reg = ACP_SW_SSP_COUNTER;
channel_type = params->port_num;
break;
- case ACP_SDW1:
ssp_counter_reg = ACP_P1_SW_SSP_COUNTER;
channel_type = params->port_num + ACP_SDW0_MAX_DAI;
There's obviously a dependency between SDW0 and SDW1 managers that you haven't described?
No, both are independent manager instances which are connected in different power domains.
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_SSP_COUNTER_VAL, ctrl->mmio + ssp_counter_reg);
- dev_dbg(bus->dev, "%s: p_params->num:0x%x entry channel_type:0x%x\n",
__func__, params->port_num, channel_type);
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
- {
frame_fmt_reg = ACP_SW_AUDIO_TX_FRAME_FORMAT;
sample_int_reg = ACP_SW_AUDIO_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_AUDIO_TX_HCTRL_DP0;
offset_reg = ACP_SW_AUDIO_TX_OFFSET_DP0;
lane_ctrl_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
This is confusing. Is this about enabling a stream or selecting the lane for this port? Same for all cases.
is this saying that the two cases are handled by the same register - unlike what is normative for the peripherals where the two concepts are handeld in DPN_ChannelEn and DPN_LaneCtrl registers?
we have to refer the same register to program channel enable and lane ctrl as per our soundwire register definition.
break;
- }
- case ACP_SDW0_HS_TX:
- {
frame_fmt_reg = ACP_SW_HEADSET_TX_FRAME_FORMAT;
sample_int_reg = ACP_SW_HEADSET_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_HEADSET_TX_HCTRL;
offset_reg = ACP_SW_HEADSET_TX_OFFSET;
lane_ctrl_reg = ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW0_BT_TX:
- {
frame_fmt_reg = ACP_SW_BT_TX_FRAME_FORMAT;
sample_int_reg = ACP_SW_BT_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_BT_TX_HCTRL;
offset_reg = ACP_SW_BT_TX_OFFSET;
lane_ctrl_reg = ACP_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW1_BT_TX:
- {
frame_fmt_reg = ACP_P1_SW_BT_TX_FRAME_FORMAT;
sample_int_reg = ACP_P1_SW_BT_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_P1_SW_BT_TX_HCTRL;
offset_reg = ACP_P1_SW_BT_TX_OFFSET;
lane_ctrl_reg = ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW0_AUDIO_RX:
- {
frame_fmt_reg = ACP_SW_AUDIO_RX_FRAME_FORMAT;
sample_int_reg = ACP_SW_AUDIO_RX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_AUDIO_RX_HCTRL_DP0;
offset_reg = ACP_SW_AUDIO_RX_OFFSET_DP0;
lane_ctrl_reg = ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW0_HS_RX:
- {
frame_fmt_reg = ACP_SW_HEADSET_RX_FRAME_FORMAT;
sample_int_reg = ACP_SW_HEADSET_RX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_HEADSET_RX_HCTRL;
offset_reg = ACP_SW_HEADSET_RX_OFFSET;
lane_ctrl_reg = ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW0_BT_RX:
- {
frame_fmt_reg = ACP_SW_BT_RX_FRAME_FORMAT;
sample_int_reg = ACP_SW_BT_RX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_BT_RX_HCTRL;
offset_reg = ACP_SW_BT_RX_OFFSET;
lane_ctrl_reg = ACP_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- }
- case ACP_SDW1_BT_RX:
- {
frame_fmt_reg = ACP_P1_SW_BT_RX_FRAME_FORMAT;
sample_int_reg = ACP_P1_SW_BT_RX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_P1_SW_BT_RX_HCTRL;
offset_reg = ACP_P1_SW_BT_RX_OFFSET;
lane_ctrl_reg = ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- }
- default:
dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type);
return -EINVAL;
- }
- dpn_frame_fmt = acp_reg_readl(ctrl->mmio + frame_fmt_reg);
- u32p_replace_bits(&dpn_frame_fmt, params->blk_pkg_mode, AMD_DPN_FRAME_FMT_BLK_PKG_MODE);
- u32p_replace_bits(&dpn_frame_fmt, params->blk_grp_ctrl, AMD_DPN_FRAME_FMT_BLK_GRP_CTRL);
- u32p_replace_bits(&dpn_frame_fmt, SDW_STREAM_PCM, AMD_DPN_FRAME_FMT_PCM_OR_PDM);
- acp_reg_writel(dpn_frame_fmt, ctrl->mmio + frame_fmt_reg);
- dpn_sampleinterval = params->sample_interval - 1;
- acp_reg_writel(dpn_sampleinterval, ctrl->mmio + sample_int_reg);
- dpn_hctrl = FIELD_PREP(AMD_DPN_HCTRL_HSTOP, params->hstop);
- dpn_hctrl |= FIELD_PREP(AMD_DPN_HCTRL_HSTART, params->hstart);
- acp_reg_writel(dpn_hctrl, ctrl->mmio + hctrl_dp0_reg);
- dpn_offsetctrl = FIELD_PREP(AMD_DPN_OFFSET_CTRL_1, params->offset1);
- dpn_offsetctrl |= FIELD_PREP(AMD_DPN_OFFSET_CTRL_2, params->offset2);
- acp_reg_writel(dpn_offsetctrl, ctrl->mmio + offset_reg);
- dpn_lanectrl = acp_reg_readl(ctrl->mmio + lane_ctrl_reg);
- u32p_replace_bits(&dpn_lanectrl, params->lane_ctrl, AMD_DPN_CH_EN_LCTRL);
- acp_reg_writel(dpn_lanectrl, ctrl->mmio + lane_ctrl_reg);
- return 0;
+}
+static int amd_sdwc_port_enable(struct sdw_bus *bus,
struct sdw_enable_ch *enable_ch,
unsigned int bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 dpn_ch_enable;
- u32 ch_enable_reg, channel_type;
- switch (ctrl->instance) {
- case ACP_SDW0:
channel_type = enable_ch->port_num;
break;
- case ACP_SDW1:
channel_type = enable_ch->port_num + ACP_SDW0_MAX_DAI;
break;
- default:
return -EINVAL;
- }
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
ch_enable_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
in the function above, I commented on
lane_ctrl_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
This looks really weird. You need to add comments is this is really intentional.
Please refer above comment reply. We will add comments.
break;
- case ACP_SDW0_HS_TX:
ch_enable_reg = ACP_SW_HEADSET_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_BT_TX:
ch_enable_reg = ACP_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW1_BT_TX:
ch_enable_reg = ACP_P1_SW_BT_TX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_AUDIO_RX:
ch_enable_reg = ACP_SW_AUDIO_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_HS_RX:
ch_enable_reg = ACP_SW_HEADSET_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW0_BT_RX:
ch_enable_reg = ACP_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- case ACP_SDW1_BT_RX:
ch_enable_reg = ACP_P1_SW_BT_RX_CHANNEL_ENABLE_DP0;
break;
- default:
dev_err(bus->dev, "%s:Invalid channel:%d\n", __func__, channel_type);
return -EINVAL;
- }
- dpn_ch_enable = acp_reg_readl(ctrl->mmio + ch_enable_reg);
- u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK);
- if (enable_ch->enable)
acp_reg_writel(dpn_ch_enable, ctrl->mmio + ch_enable_reg);
- else
acp_reg_writel(0, ctrl->mmio + ch_enable_reg);
- return 0;
+}
+static int sdw_master_read_amd_prop(struct sdw_bus *bus) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- struct fwnode_handle *link;
- struct sdw_master_prop *prop;
- u32 quirk_mask = 0;
- u32 wake_en_mask = 0;
- u32 power_mode_mask = 0;
- char name[32];
- prop = &bus->prop;
- /* Find master handle */
- snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", bus->link_id);
- link = device_get_named_child_node(bus->dev, name);
- if (!link) {
dev_err(bus->dev, "Master node %s not found\n", name);
return -EIO;
- }
- fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask);
- if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE))
prop->hw_disabled = true;
same quirk as Intel, nice :-)
- prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
And here too. Is this really needed or just-copy-pasted?
No, It's not a copy and paste. We have seen issues bus clash/parity errors during peripheral enumeration/initialization across the multiple links without this quirk. We have also seen device alerts missed during peripheral initialization sequence.
- fwnode_property_read_u32(link, "amd-sdw-wake-enable", &wake_en_mask);
- ctrl->wake_en_mask = wake_en_mask;
- fwnode_property_read_u32(link, "amd-sdw-power-mode", &power_mode_mask);
- ctrl->power_mode_mask = power_mode_mask;
- return 0;
+}
+static int amd_sdwc_probe(struct platform_device *pdev)
why not use an auxiliary device? we've moved away from platform devices maybe two years ago.
+{
- const struct acp_sdw_pdata *pdata = pdev->dev.platform_data;
- struct resource *res;
- struct device *dev = &pdev->dev;
- struct sdw_master_prop *prop;
- struct sdw_bus_params *params;
- struct amd_sdwc_ctrl *ctrl;
- int ret;
- if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "platform_data not retrieved\n");
return -ENODEV;
- }
- ctrl = devm_kzalloc(&pdev->dev, sizeof(struct amd_sdwc_ctrl), GFP_KERNEL);
- if (!ctrl)
return -ENOMEM;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENOMEM;
- }
- ctrl->mmio = devm_ioremap(&pdev->dev, res->start, resource_size(res));
- if (IS_ERR(ctrl->mmio)) {
dev_err(&pdev->dev, "mmio not found\n");
return PTR_ERR(ctrl->mmio);
- }
- ctrl->instance = pdata->instance;
- ctrl->sdw_lock = pdata->sdw_lock;
- ctrl->rows_index = sdw_find_row_index(50);
- ctrl->cols_index = sdw_find_col_index(10);
- ctrl->dev = dev;
- dev_set_drvdata(&pdev->dev, ctrl);
- ctrl->bus.ops = &amd_sdwc_ops;
- ctrl->bus.port_ops = &amd_sdwc_port_ops;
- ctrl->bus.compute_params = &amd_sdwc_compute_params;
- ctrl->bus.clk_stop_timeout = 1;
- switch (ctrl->instance) {
- case ACP_SDW0:
ctrl->num_dout_ports = AMD_SDW0_MAX_TX_PORTS;
ctrl->num_din_ports = AMD_SDW0_MAX_RX_PORTS;
break;
- case ACP_SDW1:
ctrl->num_dout_ports = AMD_SDW1_MAX_TX_PORTS;
ctrl->num_din_ports = AMD_SDW1_MAX_RX_PORTS;
break;
- default:
return -EINVAL;
- }
- params = &ctrl->bus.params;
- params->max_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->curr_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->col = 10;
- params->row = 50;
- prop = &ctrl->bus.prop;
- prop->clk_freq = &amd_sdwc_freq_tbl[0];
- prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ;
- ctrl->bus.link_id = ctrl->instance;
- ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode);
- if (ret) {
dev_err(dev, "Failed to register Soundwire controller (%d)\n",
master. the confusion continues.
It's manager instance.
ret);
return ret;
- }
- INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work);
- schedule_work(&ctrl->probe_work);
- return 0;
+}
+static int amd_sdwc_remove(struct platform_device *pdev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(&pdev->dev);
- int ret;
- amd_disable_sdw_interrupts(ctrl);
- sdw_bus_master_delete(&ctrl->bus);
- ret = amd_disable_sdw_controller(ctrl);
- return ret;
+}
+static struct platform_driver amd_sdwc_driver = {
- .probe = &amd_sdwc_probe,
- .remove = &amd_sdwc_remove,
- .driver = {
.name = "amd_sdw_controller",
- }
+}; +module_platform_driver(amd_sdwc_driver);
+MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD soundwire driver"); +MODULE_LICENSE("GPL v2");
"GPL" is enough
will modify the license.
+enum amd_sdw_channel {
- /* SDW0 */
- ACP_SDW0_AUDIO_TX = 0,
- ACP_SDW0_BT_TX,
- ACP_SDW0_HS_TX,
- ACP_SDW0_AUDIO_RX,
- ACP_SDW0_BT_RX,
- ACP_SDW0_HS_RX,
- /* SDW1 */
- ACP_SDW1_BT_TX,
- ACP_SDW1_BT_RX,
+};
you really need to comment on this. It looks like you've special-cased manager ports for specific usages? This is perfectly fine in closed applications, but it's not clear how it might work with headset, amplifier and mic codec devices.
will add comments.
diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index f0123815af46..5ec39f8c2f2e 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -10,9 +10,30 @@
#define AMD_SDW_CLK_STOP_MODE 1 #define AMD_SDW_POWER_OFF_MODE 2 +#define ACP_SDW0 0 +#define ACP_SDW1 1 +#define ACP_SDW0_MAX_DAI 6
is this related to the definition of amd_sdw_channel or the number of ports available?
port number and channel count is same for SDW0 instance. Please go through channel mapping explanation mentioned in one of the above content.
struct acp_sdw_pdata { u16 instance; struct mutex *sdw_lock; };
+struct amd_sdwc_ctrl {
- struct sdw_bus bus;
- struct device *dev;
- void __iomem *mmio;
- struct work_struct probe_work;
- struct mutex *sdw_lock;
comment please.
will add comment.
- int num_din_ports;
- int num_dout_ports;
- int cols_index;
- int rows_index;
- u32 instance;
- u32 quirks;
- u32 wake_en_mask;
- int num_ports;
- bool startup_done;
ah this was an Intel definition. Due to power dependencies we had to split the probe and startup step. Does AMD have a need for this? Is the SoundWire master IP dependent on DSP boot or something?
if you are referring to startup_done flag, we have already explained in another patch reply.
- u32 power_mode_mask;
+}; #endif
- for (index = 0; index < 2; index++) {
if (response_buf[index] == -ETIMEDOUT) {
dev_err(ctrl->dev, "Program SCP cmd timeout\n");
timeout = 1;
} else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
no_ack = 1;
if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(ctrl->dev, "Program SCP NACK received\n");
}
this is a copy of the cadence_master.c code... With the error added that this is not for a controller but for a master...
Its manager instance only. Our immediate command and response mechanism allows sending commands over the link and get the response for every command immediately, unlike as mentioned in candence_master.c.
I don't get the reply. The Cadence IP also has the ability to get the response immediately. There's limited scope for creativity, the commands are defined in the spec and the responses as well.
}
- }
- if (timeout) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_dbg_ratelimited(ctrl->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
this should probably become a helper since the response is really the same as in cadence_master.c
There's really room for optimization and reuse here.
not really needed. Please refer above comment as command/response mechanism differs from Intel's implementation.
how? there's a buffer of responses in both cases. please clarify.
+static void amd_sdwc_compute_slave_ports(struct sdw_master_runtime *m_rt,
struct sdw_transport_data *t_data)
+{
- struct sdw_slave_runtime *s_rt = NULL;
- struct sdw_port_runtime *p_rt;
- int port_bo, sample_int;
- unsigned int rate, bps, ch = 0;
- unsigned int slave_total_ch;
- struct sdw_bus_params *b_params = &m_rt->bus->params;
- port_bo = t_data->block_offset;
- list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (m_rt->bus->params.curr_dr_freq / rate);
slave_total_ch = 0;
list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
ch = sdw_ch_mask_to_ch(p_rt->ch_mask);
sdw_fill_xport_params(&p_rt->transport_params,
p_rt->num, false,
SDW_BLK_GRP_CNT_1,
sample_int, port_bo, port_bo >> 8,
t_data->hstart,
t_data->hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->s_data_mode);
port_bo += bps * ch;
slave_total_ch += ch;
}
if (m_rt->direction == SDW_DATA_DIR_TX &&
m_rt->ch_count == slave_total_ch) {
port_bo = t_data->block_offset;
}
- }
+}
ok, this is really bad.
This is a verbatim copy of the same function in generic_bandwidth_allocation.c
see https://nam11.safelinks.protection.outlook.com/?url=https%3A%2F%2Felixir.boo...
You only removed the comments and renamed the function.
Seriously? Why would you do that?
And in addition, this has *NOTHING* to do with the master support.
Programming the ports on peripheral side is something that happens at the stream level.
I am afraid it's a double NAK, or rather NAK^2 from me here.
Our intention is to implement our own compute params callback. Sorry, instead of making a copied one , we could have exported this API.
ok.
+static int amd_sdwc_compute_params(struct sdw_bus *bus) +{
- struct sdw_transport_data t_data = {0};
- struct sdw_master_runtime *m_rt;
- struct sdw_port_runtime *p_rt;
- struct sdw_bus_params *b_params = &bus->params;
- int port_bo, hstart, hstop, sample_int;
- unsigned int rate, bps;
- port_bo = 0;
- hstart = 1;
- hstop = bus->params.col - 1;
- t_data.hstop = hstop;
- t_data.hstart = hstart;
- list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (bus->params.curr_dr_freq / rate);
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
port_bo = (p_rt->num * 64) + 1;
dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n",
p_rt->num, hstart, hstop, port_bo);
sdw_fill_xport_params(&p_rt->transport_params, p_rt->num,
false, SDW_BLK_GRP_CNT_1, sample_int,
port_bo, port_bo >> 8, hstart, hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->m_data_mode);
t_data.hstart = hstart;
t_data.hstop = hstop;
t_data.block_offset = port_bo;
t_data.sub_block_offset = 0;
}
amd_sdwc_compute_slave_ports(m_rt, &t_data);
- }
- return 0;
+}
this is a variation on sdw_compute_master_ports() in generic_allocation.c
You would need a lot more comments to convince me that this is intentional and needed.
This is intentional. We have a HW bug, if we go it generic bdw allocation API, when we launch multiple streams, we are observing noise for shorter duration for active stream. To avoid that, we have slightly modified the sdw_compute_master_ports() API. As of now we are enabling solution for 48khz, 2Ch, 16bit. We will expand the coverage in the future.
That's fine, it's perfectly ok to have different strategies on the host side. Exporting additional functions from generic_bandwidth_allocation.c would help, you can pick what you need.
+static int amd_sdwc_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params,
unsigned int bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 channel_type, frame_fmt_reg, dpn_frame_fmt;
- dev_dbg(ctrl->dev, "%s: p_params->num:0x%x\n", __func__, p_params->num);
- switch (ctrl->instance) {
- case ACP_SDW0:
channel_type = p_params->num;
break;
- case ACP_SDW1:
channel_type = p_params->num + ACP_SDW0_MAX_DAI;
break;
- default:
return -EINVAL;
- }
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
you'll have to explain what you mean by 'channel_type'
This looks like the streams that can be supported by this master implementation, with dailinks for each.
SDW0 Manager instance registers 6 CPU DAI (3 TX & 3 RX Ports) whereas SDW1 Manager Instance registers 2 CPU DAI (one TX & one RX port)
Each port number on Manager side is mapped to a channel number. i.e SDW0 Pin0 -> port number 0 -> Audio TX SDW0 Pin1 -> Port number 1 -> Headset TX SDW0 Pin2 -> Port number 2 -> BT TX SDW0 Pin3 -> port number 3 -> Audio RX SDW0 Pin4 -> Port number 4 -> Headset RX SDW0 Pin5 -> Port number 5 -> BT RX
Whereas for SDW1 instance
SDW1 Pin0 -> port number 0 -> P1 BT TX SDW1 Pin1 -> Port number 1 -> P1 BT RX We use this channel value to program register set for transport params, port params and Channel enable for each manager instance. We need to use same channel mapping for programming DMA controller registers in Soundwire DMA driver. i.e if AUDIO TX channel is selected then we need to use Audio TX registers for DMA programming in Soundwire DMA driver.
Ah, that's an important piece of information that should probably be captured to help reviewers. On the Intel side the assignment from stream types to ports is handled at the machine driver + topology level.
+static int amd_sdwc_transport_params(struct sdw_bus *bus,
struct sdw_transport_params *params,
enum sdw_reg_bank bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 ssp_counter_reg;
- u32 dpn_frame_fmt;
- u32 dpn_sampleinterval;
- u32 dpn_hctrl;
- u32 dpn_offsetctrl;
- u32 dpn_lanectrl;
- u32 channel_type;
- u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg;
- u32 offset_reg, lane_ctrl_reg;
- switch (ctrl->instance) {
- case ACP_SDW0:
ssp_counter_reg = ACP_SW_SSP_COUNTER;
channel_type = params->port_num;
break;
- case ACP_SDW1:
ssp_counter_reg = ACP_P1_SW_SSP_COUNTER;
channel_type = params->port_num + ACP_SDW0_MAX_DAI;
There's obviously a dependency between SDW0 and SDW1 managers that you haven't described?
No, both are independent manager instances which are connected in different power domains.
if they are independent, then why does the channel type for SDW1 depends on SDW0_MAX_DAI?
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_SSP_COUNTER_VAL, ctrl->mmio + ssp_counter_reg);
- dev_dbg(bus->dev, "%s: p_params->num:0x%x entry channel_type:0x%x\n",
__func__, params->port_num, channel_type);
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
- {
frame_fmt_reg = ACP_SW_AUDIO_TX_FRAME_FORMAT;
sample_int_reg = ACP_SW_AUDIO_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_AUDIO_TX_HCTRL_DP0;
offset_reg = ACP_SW_AUDIO_TX_OFFSET_DP0;
lane_ctrl_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
This is confusing. Is this about enabling a stream or selecting the lane for this port? Same for all cases.
is this saying that the two cases are handled by the same register - unlike what is normative for the peripherals where the two concepts are handeld in DPN_ChannelEn and DPN_LaneCtrl registers?
we have to refer the same register to program channel enable and lane ctrl as per our soundwire register definition.
ok, please clarify with a comment. It's fine but different from other implementations on device and host sides.
+static int sdw_master_read_amd_prop(struct sdw_bus *bus) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- struct fwnode_handle *link;
- struct sdw_master_prop *prop;
- u32 quirk_mask = 0;
- u32 wake_en_mask = 0;
- u32 power_mode_mask = 0;
- char name[32];
- prop = &bus->prop;
- /* Find master handle */
- snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", bus->link_id);
- link = device_get_named_child_node(bus->dev, name);
- if (!link) {
dev_err(bus->dev, "Master node %s not found\n", name);
return -EIO;
- }
- fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask);
- if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE))
prop->hw_disabled = true;
same quirk as Intel, nice :-)
- prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
And here too. Is this really needed or just-copy-pasted?
No, It's not a copy and paste. We have seen issues bus clash/parity errors during peripheral enumeration/initialization across the multiple links without this quirk. We have also seen device alerts missed during peripheral initialization sequence.
ah, that's good to some extent that it wasn't the Intel IP behaving :-)
On 14/01/23 00:11, Pierre-Louis Bossart wrote:
- for (index = 0; index < 2; index++) {
if (response_buf[index] == -ETIMEDOUT) {
dev_err(ctrl->dev, "Program SCP cmd timeout\n");
timeout = 1;
} else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
no_ack = 1;
if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(ctrl->dev, "Program SCP NACK received\n");
}
this is a copy of the cadence_master.c code... With the error added that this is not for a controller but for a master...
Its manager instance only. Our immediate command and response mechanism allows sending commands over the link and get the response for every command immediately, unlike as mentioned in candence_master.c.
I don't get the reply. The Cadence IP also has the ability to get the response immediately. There's limited scope for creativity, the commands are defined in the spec and the responses as well.
As per our understanding in Intel code, responses are processed after sending all commands. In our case, we send the command and process the response immediately before invoking the next command.
}
- }
- if (timeout) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_dbg_ratelimited(ctrl->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
this should probably become a helper since the response is really the same as in cadence_master.c
There's really room for optimization and reuse here.
not really needed. Please refer above comment as command/response mechanism differs from Intel's implementation.
how? there's a buffer of responses in both cases. please clarify.
Ours implementation is not interrupt driven like Intel. When we send command over the link, we will wait for command's response in polling method and process the response immediately before issuing the new command.
+static void amd_sdwc_compute_slave_ports(struct sdw_master_runtime *m_rt,
struct sdw_transport_data *t_data)
+{
- struct sdw_slave_runtime *s_rt = NULL;
- struct sdw_port_runtime *p_rt;
- int port_bo, sample_int;
- unsigned int rate, bps, ch = 0;
- unsigned int slave_total_ch;
- struct sdw_bus_params *b_params = &m_rt->bus->params;
- port_bo = t_data->block_offset;
- list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (m_rt->bus->params.curr_dr_freq / rate);
slave_total_ch = 0;
list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
ch = sdw_ch_mask_to_ch(p_rt->ch_mask);
sdw_fill_xport_params(&p_rt->transport_params,
p_rt->num, false,
SDW_BLK_GRP_CNT_1,
sample_int, port_bo, port_bo >> 8,
t_data->hstart,
t_data->hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->s_data_mode);
port_bo += bps * ch;
slave_total_ch += ch;
}
if (m_rt->direction == SDW_DATA_DIR_TX &&
m_rt->ch_count == slave_total_ch) {
port_bo = t_data->block_offset;
}
- }
+}
ok, this is really bad.
This is a verbatim copy of the same function in generic_bandwidth_allocation.c
see https://nam11.safelinks.protection.outlook.com/?url=https%3A%2F%2Felixir.boo...
You only removed the comments and renamed the function.
Seriously? Why would you do that?
And in addition, this has *NOTHING* to do with the master support.
Programming the ports on peripheral side is something that happens at the stream level.
I am afraid it's a double NAK, or rather NAK^2 from me here.
Our intention is to implement our own compute params callback. Sorry, instead of making a copied one , we could have exported this API.
ok.
+static int amd_sdwc_compute_params(struct sdw_bus *bus) +{
- struct sdw_transport_data t_data = {0};
- struct sdw_master_runtime *m_rt;
- struct sdw_port_runtime *p_rt;
- struct sdw_bus_params *b_params = &bus->params;
- int port_bo, hstart, hstop, sample_int;
- unsigned int rate, bps;
- port_bo = 0;
- hstart = 1;
- hstop = bus->params.col - 1;
- t_data.hstop = hstop;
- t_data.hstart = hstart;
- list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (bus->params.curr_dr_freq / rate);
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
port_bo = (p_rt->num * 64) + 1;
dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n",
p_rt->num, hstart, hstop, port_bo);
sdw_fill_xport_params(&p_rt->transport_params, p_rt->num,
false, SDW_BLK_GRP_CNT_1, sample_int,
port_bo, port_bo >> 8, hstart, hstop,
SDW_BLK_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->m_data_mode);
t_data.hstart = hstart;
t_data.hstop = hstop;
t_data.block_offset = port_bo;
t_data.sub_block_offset = 0;
}
amd_sdwc_compute_slave_ports(m_rt, &t_data);
- }
- return 0;
+}
this is a variation on sdw_compute_master_ports() in generic_allocation.c
You would need a lot more comments to convince me that this is intentional and needed.
This is intentional. We have a HW bug, if we go it generic bdw allocation API, when we launch multiple streams, we are observing noise for shorter duration for active stream. To avoid that, we have slightly modified the sdw_compute_master_ports() API. As of now we are enabling solution for 48khz, 2Ch, 16bit. We will expand the coverage in the future.
That's fine, it's perfectly ok to have different strategies on the host side. Exporting additional functions from generic_bandwidth_allocation.c would help, you can pick what you need.
+static int amd_sdwc_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params,
unsigned int bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 channel_type, frame_fmt_reg, dpn_frame_fmt;
- dev_dbg(ctrl->dev, "%s: p_params->num:0x%x\n", __func__, p_params->num);
- switch (ctrl->instance) {
- case ACP_SDW0:
channel_type = p_params->num;
break;
- case ACP_SDW1:
channel_type = p_params->num + ACP_SDW0_MAX_DAI;
break;
- default:
return -EINVAL;
- }
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
you'll have to explain what you mean by 'channel_type'
This looks like the streams that can be supported by this master implementation, with dailinks for each.
SDW0 Manager instance registers 6 CPU DAI (3 TX & 3 RX Ports) whereas SDW1 Manager Instance registers 2 CPU DAI (one TX & one RX port)
Each port number on Manager side is mapped to a channel number. i.e SDW0 Pin0 -> port number 0 -> Audio TX SDW0 Pin1 -> Port number 1 -> Headset TX SDW0 Pin2 -> Port number 2 -> BT TX SDW0 Pin3 -> port number 3 -> Audio RX SDW0 Pin4 -> Port number 4 -> Headset RX SDW0 Pin5 -> Port number 5 -> BT RX
Whereas for SDW1 instance
SDW1 Pin0 -> port number 0 -> P1 BT TX SDW1 Pin1 -> Port number 1 -> P1 BT RX We use this channel value to program register set for transport params, port params and Channel enable for each manager instance. We need to use same channel mapping for programming DMA controller registers in Soundwire DMA driver. i.e if AUDIO TX channel is selected then we need to use Audio TX registers for DMA programming in Soundwire DMA driver.
Ah, that's an important piece of information that should probably be captured to help reviewers. On the Intel side the assignment from stream types to ports is handled at the machine driver + topology level.
We will add comments in the code.
+static int amd_sdwc_transport_params(struct sdw_bus *bus,
struct sdw_transport_params *params,
enum sdw_reg_bank bank)
+{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- u32 ssp_counter_reg;
- u32 dpn_frame_fmt;
- u32 dpn_sampleinterval;
- u32 dpn_hctrl;
- u32 dpn_offsetctrl;
- u32 dpn_lanectrl;
- u32 channel_type;
- u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg;
- u32 offset_reg, lane_ctrl_reg;
- switch (ctrl->instance) {
- case ACP_SDW0:
ssp_counter_reg = ACP_SW_SSP_COUNTER;
channel_type = params->port_num;
break;
- case ACP_SDW1:
ssp_counter_reg = ACP_P1_SW_SSP_COUNTER;
channel_type = params->port_num + ACP_SDW0_MAX_DAI;
There's obviously a dependency between SDW0 and SDW1 managers that you haven't described?
No, both are independent manager instances which are connected in different power domains.
if they are independent, then why does the channel type for SDW1 depends on SDW0_MAX_DAI?
There is no hard dependency for SDW1 on SDW0_MAX_DAI. We will modify the code.
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_SSP_COUNTER_VAL, ctrl->mmio + ssp_counter_reg);
- dev_dbg(bus->dev, "%s: p_params->num:0x%x entry channel_type:0x%x\n",
__func__, params->port_num, channel_type);
- switch (channel_type) {
- case ACP_SDW0_AUDIO_TX:
- {
frame_fmt_reg = ACP_SW_AUDIO_TX_FRAME_FORMAT;
sample_int_reg = ACP_SW_AUDIO_TX_SAMPLEINTERVAL;
hctrl_dp0_reg = ACP_SW_AUDIO_TX_HCTRL_DP0;
offset_reg = ACP_SW_AUDIO_TX_OFFSET_DP0;
lane_ctrl_reg = ACP_SW_AUDIO_TX_CHANNEL_ENABLE_DP0;
This is confusing. Is this about enabling a stream or selecting the lane for this port? Same for all cases.
is this saying that the two cases are handled by the same register - unlike what is normative for the peripherals where the two concepts are handeld in DPN_ChannelEn and DPN_LaneCtrl registers?
we have to refer the same register to program channel enable and lane ctrl as per our soundwire register definition.
ok, please clarify with a comment. It's fine but different from other implementations on device and host sides.
Will add comment.
+static int sdw_master_read_amd_prop(struct sdw_bus *bus) +{
- struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus);
- struct fwnode_handle *link;
- struct sdw_master_prop *prop;
- u32 quirk_mask = 0;
- u32 wake_en_mask = 0;
- u32 power_mode_mask = 0;
- char name[32];
- prop = &bus->prop;
- /* Find master handle */
- snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", bus->link_id);
- link = device_get_named_child_node(bus->dev, name);
- if (!link) {
dev_err(bus->dev, "Master node %s not found\n", name);
return -EIO;
- }
- fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask);
- if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE))
prop->hw_disabled = true;
same quirk as Intel, nice :-)
- prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
And here too. Is this really needed or just-copy-pasted?
No, It's not a copy and paste. We have seen issues bus clash/parity errors during peripheral enumeration/initialization across the multiple links without this quirk. We have also seen device alerts missed during peripheral initialization sequence.
ah, that's good to some extent that it wasn't the Intel IP behaving :-)
On 1/16/23 01:53, Mukunda,Vijendar wrote:
On 14/01/23 00:11, Pierre-Louis Bossart wrote:
- for (index = 0; index < 2; index++) {
if (response_buf[index] == -ETIMEDOUT) {
dev_err(ctrl->dev, "Program SCP cmd timeout\n");
timeout = 1;
} else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
no_ack = 1;
if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(ctrl->dev, "Program SCP NACK received\n");
}
this is a copy of the cadence_master.c code... With the error added that this is not for a controller but for a master...
Its manager instance only. Our immediate command and response mechanism allows sending commands over the link and get the response for every command immediately, unlike as mentioned in candence_master.c.
I don't get the reply. The Cadence IP also has the ability to get the response immediately. There's limited scope for creativity, the commands are defined in the spec and the responses as well.
As per our understanding in Intel code, responses are processed after sending all commands. In our case, we send the command and process the response immediately before invoking the next command.
The Cadence IP can queue a number of commands, I think 8 off the top of my head. But the response is provided immediately after each command.
Maybe the disconnect is that there's an ability to define a watermark on the response buffer, so that the software can decide to process the command responses in one shot.
}
- }
- if (timeout) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_dbg_ratelimited(ctrl->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
this should probably become a helper since the response is really the same as in cadence_master.c
There's really room for optimization and reuse here.
not really needed. Please refer above comment as command/response mechanism differs from Intel's implementation.
how? there's a buffer of responses in both cases. please clarify.
Ours implementation is not interrupt driven like Intel. When we send command over the link, we will wait for command's response in polling method and process the response immediately before issuing the new command.
On the Intel side we use an interrupt to avoid polling, and in case of N commands the watermark will be set to N to reduce the overhead. That said, most users only use 1 command at a time, it's only recently that Cirrus Logic experimented with multiple commands to speed-up transfers.
Even if there are differences in the way the responses are processed, whether one-at-a-time or in a batch, the point remains that each command response can be individually analyzed and that could be using a helper - moving code from cadence_master.c into the bus layer.
On 16/01/23 20:27, Pierre-Louis Bossart wrote:
On 1/16/23 01:53, Mukunda,Vijendar wrote:
On 14/01/23 00:11, Pierre-Louis Bossart wrote:
- for (index = 0; index < 2; index++) {
if (response_buf[index] == -ETIMEDOUT) {
dev_err(ctrl->dev, "Program SCP cmd timeout\n");
timeout = 1;
} else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
no_ack = 1;
if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(ctrl->dev, "Program SCP NACK received\n");
}
this is a copy of the cadence_master.c code... With the error added that this is not for a controller but for a master...
Its manager instance only. Our immediate command and response mechanism allows sending commands over the link and get the response for every command immediately, unlike as mentioned in candence_master.c.
I don't get the reply. The Cadence IP also has the ability to get the response immediately. There's limited scope for creativity, the commands are defined in the spec and the responses as well.
As per our understanding in Intel code, responses are processed after sending all commands. In our case, we send the command and process the response immediately before invoking the next command.
The Cadence IP can queue a number of commands, I think 8 off the top of my head. But the response is provided immediately after each command.
Maybe the disconnect is that there's an ability to define a watermark on the response buffer, so that the software can decide to process the command responses in one shot.
}
- }
- if (timeout) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(ctrl->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_dbg_ratelimited(ctrl->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
this should probably become a helper since the response is really the same as in cadence_master.c
There's really room for optimization and reuse here.
not really needed. Please refer above comment as command/response mechanism differs from Intel's implementation.
how? there's a buffer of responses in both cases. please clarify.
Ours implementation is not interrupt driven like Intel. When we send command over the link, we will wait for command's response in polling method and process the response immediately before issuing the new command.
On the Intel side we use an interrupt to avoid polling, and in case of N commands the watermark will be set to N to reduce the overhead. That said, most users only use 1 command at a time, it's only recently that Cirrus Logic experimented with multiple commands to speed-up transfers.
Even if there are differences in the way the responses are processed, whether one-at-a-time or in a batch, the point remains that each command response can be individually analyzed and that could be using a helper - moving code from cadence_master.c into the bus layer.
will implement a helper function to analyze the response.
Register dai ops for two controller instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- drivers/soundwire/amd_master.c | 186 ++++++++++++++++++++++++++++++ include/linux/soundwire/sdw_amd.h | 21 ++++ 2 files changed, 207 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 7e1f618254ac..93bffe6ff9e2 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -952,6 +952,186 @@ static const struct sdw_master_ops amd_sdwc_ops = { .read_ping_status = amd_sdwc_read_ping_status, };
+static int amd_sdwc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai); + struct sdw_amd_dma_data *dma; + struct sdw_stream_config sconfig; + struct sdw_port_config *pconfig; + int ch, dir; + int ret; + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) + return -EIO; + + ch = params_channels(params); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = SDW_DATA_DIR_RX; + else + dir = SDW_DATA_DIR_TX; + dev_dbg(ctrl->dev, "%s: dir:%d dai->id:0x%x\n", __func__, dir, dai->id); + dma->hw_params = params; + + sconfig.direction = dir; + sconfig.ch_count = ch; + sconfig.frame_rate = params_rate(params); + sconfig.type = dma->stream_type; + + sconfig.bps = snd_pcm_format_width(params_format(params)); + + /* Port configuration */ + pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL); + if (!pconfig) { + ret = -ENOMEM; + goto error; + } + + pconfig->num = dai->id; + pconfig->ch_mask = (1 << ch) - 1; + ret = sdw_stream_add_master(&ctrl->bus, &sconfig, + pconfig, 1, dma->stream); + if (ret) + dev_err(ctrl->dev, "add master to stream failed:%d\n", ret); + + kfree(pconfig); +error: + return ret; +} + +static int amd_sdwc_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai); + struct sdw_amd_dma_data *dma; + int ret; + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) + return -EIO; + + ret = sdw_stream_remove_master(&ctrl->bus, dma->stream); + if (ret < 0) { + dev_err(dai->dev, "remove master from stream %s failed: %d\n", + dma->stream->name, ret); + return ret; + } + dma->hw_params = NULL; + return 0; +} + +static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{ + struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai); + struct sdw_amd_dma_data *dma; + + if (stream) { + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dma = dai->playback_dma_data; + else + dma = dai->capture_dma_data; + + if (dma) { + dev_err(dai->dev, + "dma_data already allocated for dai %s\n", + dai->name); + return -EINVAL; + } + + /* allocate and set dma info */ + dma = kzalloc(sizeof(*dma), GFP_KERNEL); + if (!dma) + return -ENOMEM; + dma->stream_type = SDW_STREAM_PCM; + dma->bus = &ctrl->bus; + dma->link_id = ctrl->instance; + dma->stream = stream; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = dma; + else + dai->capture_dma_data = dma; + } else { + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + kfree(dai->playback_dma_data); + dai->playback_dma_data = NULL; + } else { + kfree(dai->capture_dma_data); + dai->capture_dma_data = NULL; + } + } + return 0; +} + +static int amd_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{ + return amd_set_sdw_stream(dai, stream, direction); +} + +static void *amd_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{ + struct sdw_amd_dma_data *dma; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dma = dai->playback_dma_data; + else + dma = dai->capture_dma_data; + + if (!dma) + return ERR_PTR(-EINVAL); + + return dma->stream; +} + +static const struct snd_soc_dai_ops amd_sdwc_dai_ops = { + .hw_params = amd_sdwc_hw_params, + .hw_free = amd_sdwc_hw_free, + .set_stream = amd_pcm_set_sdw_stream, + .get_stream = amd_get_sdw_stream, +}; + +static const struct snd_soc_component_driver amd_sdwc_dai_component = { + .name = "soundwire", +}; + +static int amd_sdwc_register_dais(struct amd_sdwc_ctrl *ctrl) +{ + struct snd_soc_dai_driver *dais; + struct snd_soc_pcm_stream *stream; + struct device *dev; + int i, num_dais; + + dev = ctrl->dev; + num_dais = ctrl->num_dout_ports + ctrl->num_din_ports; + dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL); + if (!dais) + return -ENOMEM; + for (i = 0; i < num_dais; i++) { + dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", ctrl->instance, i); + if (!dais[i].name) { + dev_err(ctrl->dev, "-ENOMEM dai name allocation failed\n"); + return -ENOMEM; + } + + if (i < ctrl->num_dout_ports) + stream = &dais[i].playback; + else + stream = &dais[i].capture; + + stream->channels_min = 2; + stream->channels_max = 2; + stream->rates = SNDRV_PCM_RATE_48000; + stream->formats = SNDRV_PCM_FMTBIT_S16_LE; + + dais[i].ops = &amd_sdwc_dai_ops; + dais[i].id = i; + } + + return devm_snd_soc_register_component(ctrl->dev, &amd_sdwc_dai_component, + dais, num_dais); +} + static void amd_sdwc_probe_work(struct work_struct *work) { struct amd_sdwc_ctrl *ctrl = container_of(work, struct amd_sdwc_ctrl, probe_work); @@ -1043,6 +1223,12 @@ static int amd_sdwc_probe(struct platform_device *pdev) ret); return ret; } + ret = amd_sdwc_register_dais(ctrl); + if (ret) { + dev_err(dev, "CPU DAI registration failed\n"); + sdw_bus_master_delete(&ctrl->bus); + return ret; + } INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); schedule_work(&ctrl->probe_work); return 0; diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index 5ec39f8c2f2e..7a99d782969f 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -13,6 +13,7 @@ #define ACP_SDW0 0 #define ACP_SDW1 1 #define ACP_SDW0_MAX_DAI 6 +#define AMD_SDW_MAX_DAIS 8
struct acp_sdw_pdata { u16 instance; @@ -25,6 +26,7 @@ struct amd_sdwc_ctrl { void __iomem *mmio; struct work_struct probe_work; struct mutex *sdw_lock; + struct sdw_stream_runtime *sruntime[AMD_SDW_MAX_DAIS]; int num_din_ports; int num_dout_ports; int cols_index; @@ -36,4 +38,23 @@ struct amd_sdwc_ctrl { bool startup_done; u32 power_mode_mask; }; + +/** + * struct sdw_amd_dma_data: AMD DMA data + * + * @name: SoundWire stream name + * @stream: stream runtime + * @bus: Bus handle + * @stream_type: Stream type + * @link_id: Master link id + * @hw_params: hw_params to be applied in .prepare step + */ +struct sdw_amd_dma_data { + char *name; + struct sdw_stream_runtime *stream; + struct sdw_bus *bus; + enum sdw_stream_type stream_type; + int link_id; + struct snd_pcm_hw_params *hw_params; +}; #endif
On 1/11/23 03:02, Vijendar Mukunda wrote:
Register dai ops for two controller instances.
manager instances
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 7e1f618254ac..93bffe6ff9e2 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -952,6 +952,186 @@ static const struct sdw_master_ops amd_sdwc_ops = { .read_ping_status = amd_sdwc_read_ping_status, };
+static int amd_sdwc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
- struct sdw_stream_config sconfig;
- struct sdw_port_config *pconfig;
- int ch, dir;
- int ret;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
return -EIO;
- ch = params_channels(params);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
dir = SDW_DATA_DIR_RX;
- else
dir = SDW_DATA_DIR_TX;
- dev_dbg(ctrl->dev, "%s: dir:%d dai->id:0x%x\n", __func__, dir, dai->id);
- dma->hw_params = params;
- sconfig.direction = dir;
- sconfig.ch_count = ch;
- sconfig.frame_rate = params_rate(params);
- sconfig.type = dma->stream_type;
- sconfig.bps = snd_pcm_format_width(params_format(params));
- /* Port configuration */
- pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL);
- if (!pconfig) {
ret = -ENOMEM;
goto error;
- }
- pconfig->num = dai->id;
- pconfig->ch_mask = (1 << ch) - 1;
- ret = sdw_stream_add_master(&ctrl->bus, &sconfig,
pconfig, 1, dma->stream);
- if (ret)
dev_err(ctrl->dev, "add master to stream failed:%d\n", ret);
- kfree(pconfig);
+error:
- return ret;
+}
This looks inspired from intel.c, but you are not programming ANY registers here. is this intentional?
+static int amd_sdwc_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
- int ret;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
return -EIO;
- ret = sdw_stream_remove_master(&ctrl->bus, dma->stream);
- if (ret < 0) {
dev_err(dai->dev, "remove master from stream %s failed: %d\n",
dma->stream->name, ret);
return ret;
- }
- dma->hw_params = NULL;
- return 0;
+}
+static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
you want to avoid using dma_data and use your own runtime. We made that change recently for cadence_runtime.c
- if (stream) {
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
dma = dai->playback_dma_data;
else
dma = dai->capture_dma_data;
if (dma) {
dev_err(dai->dev,
"dma_data already allocated for dai %s\n",
dai->name);
return -EINVAL;
}
/* allocate and set dma info */
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
if (!dma)
return -ENOMEM;
dma->stream_type = SDW_STREAM_PCM;
dma->bus = &ctrl->bus;
dma->link_id = ctrl->instance;
dma->stream = stream;
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
dai->playback_dma_data = dma;
else
dai->capture_dma_data = dma;
- } else {
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
kfree(dai->playback_dma_data);
dai->playback_dma_data = NULL;
} else {
kfree(dai->capture_dma_data);
dai->capture_dma_data = NULL;
}
- }
- return 0;
+}
+static int amd_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- return amd_set_sdw_stream(dai, stream, direction);
+}
+static void *amd_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{
- struct sdw_amd_dma_data *dma;
- if (direction == SNDRV_PCM_STREAM_PLAYBACK)
dma = dai->playback_dma_data;
- else
dma = dai->capture_dma_data;
- if (!dma)
return ERR_PTR(-EINVAL);
- return dma->stream;
+}
+static const struct snd_soc_dai_ops amd_sdwc_dai_ops = {
- .hw_params = amd_sdwc_hw_params,
- .hw_free = amd_sdwc_hw_free,
- .set_stream = amd_pcm_set_sdw_stream,
In the first patch there was support for PDM exposed, but here it's PDM only?
- .get_stream = amd_get_sdw_stream,
+};
+static const struct snd_soc_component_driver amd_sdwc_dai_component = {
- .name = "soundwire",
+};
+static int amd_sdwc_register_dais(struct amd_sdwc_ctrl *ctrl) +{
- struct snd_soc_dai_driver *dais;
- struct snd_soc_pcm_stream *stream;
- struct device *dev;
- int i, num_dais;
- dev = ctrl->dev;
- num_dais = ctrl->num_dout_ports + ctrl->num_din_ports;
- dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL);
- if (!dais)
return -ENOMEM;
- for (i = 0; i < num_dais; i++) {
dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", ctrl->instance, i);
if (!dais[i].name) {
dev_err(ctrl->dev, "-ENOMEM dai name allocation failed\n");
remove, we don't add error logs on memory allocation issues.
return -ENOMEM;
}
if (i < ctrl->num_dout_ports)
stream = &dais[i].playback;
else
stream = &dais[i].capture;
stream->channels_min = 2;
stream->channels_max = 2;
Is this a port limitation or just a software definition?
stream->rates = SNDRV_PCM_RATE_48000;
stream->formats = SNDRV_PCM_FMTBIT_S16_LE;
Wondering if this is needed. I don't even recall why it's in the Intel code, we tested with 32 bit data and 192kHz, that looks unnecessary to me unless the hardware is really limited to those values.
dais[i].ops = &amd_sdwc_dai_ops;
dais[i].id = i;
- }
- return devm_snd_soc_register_component(ctrl->dev, &amd_sdwc_dai_component,
dais, num_dais);
+}
static void amd_sdwc_probe_work(struct work_struct *work) { struct amd_sdwc_ctrl *ctrl = container_of(work, struct amd_sdwc_ctrl, probe_work); @@ -1043,6 +1223,12 @@ static int amd_sdwc_probe(struct platform_device *pdev) ret); return ret; }
- ret = amd_sdwc_register_dais(ctrl);
- if (ret) {
dev_err(dev, "CPU DAI registration failed\n");
sdw_bus_master_delete(&ctrl->bus);
return ret;
- } INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); schedule_work(&ctrl->probe_work); return 0;
diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index 5ec39f8c2f2e..7a99d782969f 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -13,6 +13,7 @@ #define ACP_SDW0 0 #define ACP_SDW1 1 #define ACP_SDW0_MAX_DAI 6 +#define AMD_SDW_MAX_DAIS 8
How does this work? 6 dais for the first master and 2 for the second?
struct acp_sdw_pdata { u16 instance; @@ -25,6 +26,7 @@ struct amd_sdwc_ctrl { void __iomem *mmio; struct work_struct probe_work; struct mutex *sdw_lock;
- struct sdw_stream_runtime *sruntime[AMD_SDW_MAX_DAIS];
well no, a stream runtime needs to be allocated per stream and usually there's a 1:1 mapping between dailink and stream. A stream may use multiple DAIs, possibly on different masters - just like a dailink can rely on multiple cpu- and codec-dais.
You are conflating/confusing concepts I am afraid here.
int num_din_ports; int num_dout_ports; int cols_index; @@ -36,4 +38,23 @@ struct amd_sdwc_ctrl { bool startup_done; u32 power_mode_mask; };
+/**
- struct sdw_amd_dma_data: AMD DMA data
- @name: SoundWire stream name
- @stream: stream runtime
- @bus: Bus handle
- @stream_type: Stream type
- @link_id: Master link id
- @hw_params: hw_params to be applied in .prepare step
- */
+struct sdw_amd_dma_data {
- char *name;
- struct sdw_stream_runtime *stream;
- struct sdw_bus *bus;
- enum sdw_stream_type stream_type;
- int link_id;
- struct snd_pcm_hw_params *hw_params;
+}; #endif
On 11/01/23 20:28, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
Register dai ops for two controller instances.
manager instances
will change it.
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 7e1f618254ac..93bffe6ff9e2 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -952,6 +952,186 @@ static const struct sdw_master_ops amd_sdwc_ops = { .read_ping_status = amd_sdwc_read_ping_status, };
+static int amd_sdwc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
- struct sdw_stream_config sconfig;
- struct sdw_port_config *pconfig;
- int ch, dir;
- int ret;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
return -EIO;
- ch = params_channels(params);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
dir = SDW_DATA_DIR_RX;
- else
dir = SDW_DATA_DIR_TX;
- dev_dbg(ctrl->dev, "%s: dir:%d dai->id:0x%x\n", __func__, dir, dai->id);
- dma->hw_params = params;
- sconfig.direction = dir;
- sconfig.ch_count = ch;
- sconfig.frame_rate = params_rate(params);
- sconfig.type = dma->stream_type;
- sconfig.bps = snd_pcm_format_width(params_format(params));
- /* Port configuration */
- pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL);
- if (!pconfig) {
ret = -ENOMEM;
goto error;
- }
- pconfig->num = dai->id;
- pconfig->ch_mask = (1 << ch) - 1;
- ret = sdw_stream_add_master(&ctrl->bus, &sconfig,
pconfig, 1, dma->stream);
- if (ret)
dev_err(ctrl->dev, "add master to stream failed:%d\n", ret);
- kfree(pconfig);
+error:
- return ret;
+}
This looks inspired from intel.c, but you are not programming ANY registers here. is this intentional?
We don't have any additional registers to be programmed like intel.
+static int amd_sdwc_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
- int ret;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
return -EIO;
- ret = sdw_stream_remove_master(&ctrl->bus, dma->stream);
- if (ret < 0) {
dev_err(dai->dev, "remove master from stream %s failed: %d\n",
dma->stream->name, ret);
return ret;
- }
- dma->hw_params = NULL;
- return 0;
+}
+static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
you want to avoid using dma_data and use your own runtime. We made that change recently for cadence_runtime.c
will check the implementation.
- if (stream) {
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
dma = dai->playback_dma_data;
else
dma = dai->capture_dma_data;
if (dma) {
dev_err(dai->dev,
"dma_data already allocated for dai %s\n",
dai->name);
return -EINVAL;
}
/* allocate and set dma info */
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
if (!dma)
return -ENOMEM;
dma->stream_type = SDW_STREAM_PCM;
dma->bus = &ctrl->bus;
dma->link_id = ctrl->instance;
dma->stream = stream;
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
dai->playback_dma_data = dma;
else
dai->capture_dma_data = dma;
- } else {
if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
kfree(dai->playback_dma_data);
dai->playback_dma_data = NULL;
} else {
kfree(dai->capture_dma_data);
dai->capture_dma_data = NULL;
}
- }
- return 0;
+}
+static int amd_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- return amd_set_sdw_stream(dai, stream, direction);
+}
+static void *amd_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{
- struct sdw_amd_dma_data *dma;
- if (direction == SNDRV_PCM_STREAM_PLAYBACK)
dma = dai->playback_dma_data;
- else
dma = dai->capture_dma_data;
- if (!dma)
return ERR_PTR(-EINVAL);
- return dma->stream;
+}
+static const struct snd_soc_dai_ops amd_sdwc_dai_ops = {
- .hw_params = amd_sdwc_hw_params,
- .hw_free = amd_sdwc_hw_free,
- .set_stream = amd_pcm_set_sdw_stream,
In the first patch there was support for PDM exposed, but here it's PDM only?
Didn't get your question. First patch talks about creating dev nodes for Soundwire managers and ACP PDM controller based on ACP pin config.
Let us know if we are missing anything?
- .get_stream = amd_get_sdw_stream,
+};
+static const struct snd_soc_component_driver amd_sdwc_dai_component = {
- .name = "soundwire",
+};
+static int amd_sdwc_register_dais(struct amd_sdwc_ctrl *ctrl) +{
- struct snd_soc_dai_driver *dais;
- struct snd_soc_pcm_stream *stream;
- struct device *dev;
- int i, num_dais;
- dev = ctrl->dev;
- num_dais = ctrl->num_dout_ports + ctrl->num_din_ports;
- dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL);
- if (!dais)
return -ENOMEM;
- for (i = 0; i < num_dais; i++) {
dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", ctrl->instance, i);
if (!dais[i].name) {
dev_err(ctrl->dev, "-ENOMEM dai name allocation failed\n");
remove, we don't add error logs on memory allocation issues.
return -ENOMEM;
}
if (i < ctrl->num_dout_ports)
stream = &dais[i].playback;
else
stream = &dais[i].capture;
stream->channels_min = 2;
stream->channels_max = 2;
Is this a port limitation or just a software definition?
stream->rates = SNDRV_PCM_RATE_48000;
stream->formats = SNDRV_PCM_FMTBIT_S16_LE;
Wondering if this is needed. I don't even recall why it's in the Intel code, we tested with 32 bit data and 192kHz, that looks unnecessary to me unless the hardware is really limited to those values.
dais[i].ops = &amd_sdwc_dai_ops;
dais[i].id = i;
- }
- return devm_snd_soc_register_component(ctrl->dev, &amd_sdwc_dai_component,
dais, num_dais);
+}
static void amd_sdwc_probe_work(struct work_struct *work) { struct amd_sdwc_ctrl *ctrl = container_of(work, struct amd_sdwc_ctrl, probe_work); @@ -1043,6 +1223,12 @@ static int amd_sdwc_probe(struct platform_device *pdev) ret); return ret; }
- ret = amd_sdwc_register_dais(ctrl);
- if (ret) {
dev_err(dev, "CPU DAI registration failed\n");
sdw_bus_master_delete(&ctrl->bus);
return ret;
- } INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); schedule_work(&ctrl->probe_work); return 0;
diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index 5ec39f8c2f2e..7a99d782969f 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -13,6 +13,7 @@ #define ACP_SDW0 0 #define ACP_SDW1 1 #define ACP_SDW0_MAX_DAI 6 +#define AMD_SDW_MAX_DAIS 8
How does this work? 6 dais for the first master and 2 for the second?
struct acp_sdw_pdata { u16 instance; @@ -25,6 +26,7 @@ struct amd_sdwc_ctrl { void __iomem *mmio; struct work_struct probe_work; struct mutex *sdw_lock;
- struct sdw_stream_runtime *sruntime[AMD_SDW_MAX_DAIS];
well no, a stream runtime needs to be allocated per stream and usually there's a 1:1 mapping between dailink and stream. A stream may use multiple DAIs, possibly on different masters - just like a dailink can rely on multiple cpu- and codec-dais.
You are conflating/confusing concepts I am afraid here.
int num_din_ports; int num_dout_ports; int cols_index; @@ -36,4 +38,23 @@ struct amd_sdwc_ctrl { bool startup_done; u32 power_mode_mask; };
+/**
- struct sdw_amd_dma_data: AMD DMA data
- @name: SoundWire stream name
- @stream: stream runtime
- @bus: Bus handle
- @stream_type: Stream type
- @link_id: Master link id
- @hw_params: hw_params to be applied in .prepare step
- */
+struct sdw_amd_dma_data {
- char *name;
- struct sdw_stream_runtime *stream;
- struct sdw_bus *bus;
- enum sdw_stream_type stream_type;
- int link_id;
- struct snd_pcm_hw_params *hw_params;
+}; #endif
+static int amd_sdwc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
- struct sdw_stream_config sconfig;
- struct sdw_port_config *pconfig;
- int ch, dir;
- int ret;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
return -EIO;
- ch = params_channels(params);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
dir = SDW_DATA_DIR_RX;
- else
dir = SDW_DATA_DIR_TX;
- dev_dbg(ctrl->dev, "%s: dir:%d dai->id:0x%x\n", __func__, dir, dai->id);
- dma->hw_params = params;
- sconfig.direction = dir;
- sconfig.ch_count = ch;
- sconfig.frame_rate = params_rate(params);
- sconfig.type = dma->stream_type;
- sconfig.bps = snd_pcm_format_width(params_format(params));
- /* Port configuration */
- pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL);
- if (!pconfig) {
ret = -ENOMEM;
goto error;
- }
- pconfig->num = dai->id;
- pconfig->ch_mask = (1 << ch) - 1;
- ret = sdw_stream_add_master(&ctrl->bus, &sconfig,
pconfig, 1, dma->stream);
- if (ret)
dev_err(ctrl->dev, "add master to stream failed:%d\n", ret);
- kfree(pconfig);
+error:
- return ret;
+}
This looks inspired from intel.c, but you are not programming ANY registers here. is this intentional?
We don't have any additional registers to be programmed like intel.
ok, this is worthy of a comment.
+static const struct snd_soc_dai_ops amd_sdwc_dai_ops = {
- .hw_params = amd_sdwc_hw_params,
- .hw_free = amd_sdwc_hw_free,
- .set_stream = amd_pcm_set_sdw_stream,
In the first patch there was support for PDM exposed, but here it's PDM only?
Didn't get your question. First patch talks about creating dev nodes for Soundwire managers and ACP PDM controller based on ACP pin config.
Sorry, my comment has a typo.
I meant that the first patch exposed PDM support but here you only have PCM?
On 1/11/2023 10:02 AM, Vijendar Mukunda wrote:
Register dai ops for two controller instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
+static int amd_sdwc_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
- int ret;
- dma = snd_soc_dai_get_dma_data(dai, substream);
- if (!dma)
return -EIO;
- ret = sdw_stream_remove_master(&ctrl->bus, dma->stream);
- if (ret < 0) {
dev_err(dai->dev, "remove master from stream %s failed: %d\n",
dma->stream->name, ret);
return ret;
- }
- dma->hw_params = NULL;
- return 0;
+}
+static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dma_data *dma;
- if (stream) {
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
dma = dai->playback_dma_data;
else
dma = dai->capture_dma_data;
The patch itself looks ok, but I have generic ASoC API question. Could we perhaps change snd_soc_dai_get_dma_data() definition, so that instead of it being: static inline void *snd_soc_dai_get_dma_data(const struct snd_soc_dai *dai, const struct snd_pcm_substream *ss) it would be something like: static inline void *snd_soc_dai_get_dma_data(const struct snd_soc_dai *dai, int direction)
it would require converting current calls from something like dma = snd_soc_dai_get_dma_data(dai, substream); to dma = snd_soc_dai_get_dma_data(dai, substream->stream); but would also allow for use in code like above? It would become just: dma = snd_soc_dai_get_dma_data(dai, direction);
The more I'm looking at the soc-dai.h header the more I like this idea, as other functions in the area seem to pass stream/direction explicitly instead of substream.
Mark, what do you think?
Enable build for AMD soundwire master driver for AMD platforms.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- drivers/soundwire/Kconfig | 9 +++++++++ drivers/soundwire/Makefile | 4 ++++ 2 files changed, 13 insertions(+)
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index 2b7795233282..a597bb6c42fd 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -46,4 +46,13 @@ config SOUNDWIRE_QCOM config SOUNDWIRE_GENERIC_ALLOCATION tristate
+config SOUNDWIRE_AMD + tristate "AMD Soundwire Master driver" + depends on ACPI && SND_SOC + help + Soundwire AMD Master driver. + If you have an AMD platform which has a Soundwire Master then + enable this config option to get the Soundwire support for that + device. + endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index ca97414ada70..86ecae19eacd 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -26,3 +26,7 @@ obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o #Qualcomm driver soundwire-qcom-y := qcom.o obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o + +#AMD driver +soundwire-amd-y := amd_master.o +obj-$(CONFIG_SOUNDWIRE_AMD) += soundwire-amd.o
Hi Vijendar,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on broonie-sound/for-next] [also build test WARNING on next-20230119] [cannot apply to vkoul-dmaengine/next linus/master v6.2-rc4] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Vijendar-Mukunda/ASoC-amd-ps-... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next patch link: https://lore.kernel.org/r/20230111090222.2016499-5-Vijendar.Mukunda%40amd.co... patch subject: [PATCH 04/19] soundwire: amd: enable build for AMD soundwire master driver config: i386-allyesconfig (https://download.01.org/0day-ci/archive/20230120/202301200258.mTFnFc1h-lkp@i...) compiler: clang version 14.0.6 (https://github.com/llvm/llvm-project f28c006a5895fc0e329fe15fead81e37457cb1d1) reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/intel-lab-lkp/linux/commit/52f32d2187adf9e16e4d4a3108e8ca... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Vijendar-Mukunda/ASoC-amd-ps-create-platform-devices-based-on-acp-config/20230111-170749 git checkout 52f32d2187adf9e16e4d4a3108e8ca47efa26aa2 # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=i386 olddefconfig COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=i386 SHELL=/bin/bash drivers/soundwire/
If you fix the issue, kindly add following tag where applicable | Reported-by: kernel test robot lkp@intel.com
All warnings (new ones prefixed by >>):
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 3 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 3 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 3 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 4 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 4 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 5 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 6 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 5 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 6 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ include/linux/bitfield.h:129:30: note: expanded from macro 'FIELD_GET' (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \ ^~~~~
drivers/soundwire/amd_master.c:569:26: warning: shift count is negative [-Wshift-count-negative]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ include/linux/bitfield.h:129:50: note: expanded from macro 'FIELD_GET' (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \ ~~~~~~~~~^~~~~~ include/linux/bitfield.h:45:38: note: expanded from macro '__bf_shf' #define __bf_shf(x) (__builtin_ffsll(x) - 1) ^ 11 warnings generated.
vim +569 drivers/soundwire/amd_master.c
1e349784c25702 Vijendar Mukunda 2023-01-11 559 1e349784c25702 Vijendar Mukunda 2023-01-11 560 static u32 amd_sdwc_read_ping_status(struct sdw_bus *bus) 1e349784c25702 Vijendar Mukunda 2023-01-11 561 { 1e349784c25702 Vijendar Mukunda 2023-01-11 562 struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); 1e349784c25702 Vijendar Mukunda 2023-01-11 563 u64 response; 1e349784c25702 Vijendar Mukunda 2023-01-11 564 u32 slave_stat = 0; 1e349784c25702 Vijendar Mukunda 2023-01-11 565 1e349784c25702 Vijendar Mukunda 2023-01-11 566 response = amd_sdwc_send_cmd_get_resp(ctrl, 0, 0); 1e349784c25702 Vijendar Mukunda 2023-01-11 567 /* slave status from ping response*/ 1e349784c25702 Vijendar Mukunda 2023-01-11 568 slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response); 1e349784c25702 Vijendar Mukunda 2023-01-11 @569 slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; 1e349784c25702 Vijendar Mukunda 2023-01-11 570 dev_dbg(ctrl->dev, "%s: slave_stat:0x%x\n", __func__, slave_stat); 1e349784c25702 Vijendar Mukunda 2023-01-11 571 return slave_stat; 1e349784c25702 Vijendar Mukunda 2023-01-11 572 } 1e349784c25702 Vijendar Mukunda 2023-01-11 573
Add support for handling soundwire controller interrupts.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com --- drivers/soundwire/amd_master.c | 156 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 1 + include/linux/soundwire/sdw_amd.h | 3 + 3 files changed, 160 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 93bffe6ff9e2..c7063b8bdd7b 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -557,6 +557,47 @@ amd_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num) return amd_program_scp_addr(ctrl, &msg); }
+static void amd_sdwc_process_ping_status(u64 response, struct amd_sdwc_ctrl *ctrl) +{ + u64 slave_stat = 0; + u32 val = 0; + u16 dev_index; + + /* slave status from ping response*/ + slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response); + slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; + + dev_dbg(ctrl->dev, "%s: slave_stat:0x%llx\n", __func__, slave_stat); + for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) { + val = (slave_stat >> (dev_index * 2)) & AMD_SDW_MCP_SLAVE_STATUS_MASK; + dev_dbg(ctrl->dev, "%s val:0x%x\n", __func__, val); + switch (val) { + case SDW_SLAVE_ATTACHED: + ctrl->status[dev_index] = SDW_SLAVE_ATTACHED; + break; + case SDW_SLAVE_UNATTACHED: + ctrl->status[dev_index] = SDW_SLAVE_UNATTACHED; + break; + case SDW_SLAVE_ALERT: + ctrl->status[dev_index] = SDW_SLAVE_ALERT; + break; + default: + ctrl->status[dev_index] = SDW_SLAVE_RESERVED; + break; + } + } +} + +static void amd_sdwc_read_and_process_ping_status(struct amd_sdwc_ctrl *ctrl) +{ + u64 response = 0; + + mutex_lock(&ctrl->bus.msg_lock); + response = amd_sdwc_send_cmd_get_resp(ctrl, 0, 0); + mutex_unlock(&ctrl->bus.msg_lock); + amd_sdwc_process_ping_status(response, ctrl); +} + static u32 amd_sdwc_read_ping_status(struct sdw_bus *bus) { struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); @@ -1132,6 +1173,119 @@ static int amd_sdwc_register_dais(struct amd_sdwc_ctrl *ctrl) dais, num_dais); }
+static void amd_sdwc_update_slave_status_work(struct work_struct *work) +{ + struct amd_sdwc_ctrl *ctrl = + container_of(work, struct amd_sdwc_ctrl, amd_sdw_work); + u32 sw_status_change_mask_0to7_reg; + u32 sw_status_change_mask_8to11_reg; + + switch (ctrl->instance) { + case ACP_SDW0: + sw_status_change_mask_0to7_reg = SW_STATE_CHANGE_STATUS_MASK_0TO7; + sw_status_change_mask_8to11_reg = SW_STATE_CHANGE_STATUS_MASK_8TO11; + break; + case ACP_SDW1: + sw_status_change_mask_0to7_reg = P1_SW_STATE_CHANGE_STATUS_MASK_0TO7; + sw_status_change_mask_8to11_reg = P1_SW_STATE_CHANGE_STATUS_MASK_8TO11; + break; + default: + dev_err(ctrl->dev, "Invalid Soundwire controller instance\n"); + return; + } + + if (ctrl->status[0] == SDW_SLAVE_ATTACHED) { + acp_reg_writel(0, ctrl->mmio + sw_status_change_mask_0to7_reg); + acp_reg_writel(0, ctrl->mmio + sw_status_change_mask_8to11_reg); + } + +update_status: + sdw_handle_slave_status(&ctrl->bus, ctrl->status); + if (ctrl->status[0] == SDW_SLAVE_ATTACHED) { + acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, ctrl->mmio + sw_status_change_mask_0to7_reg); + acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, + ctrl->mmio + sw_status_change_mask_8to11_reg); + amd_sdwc_read_and_process_ping_status(ctrl); + goto update_status; + } +} + +static void amd_sdwc_update_slave_status(u32 status_change_0to7, u32 status_change_8to11, + struct amd_sdwc_ctrl *ctrl) +{ + u64 slave_stat = 0; + u32 val = 0; + int dev_index; + + if (status_change_0to7 == AMD_SDW_SLAVE_0_ATTACHED) + memset(ctrl->status, 0, sizeof(ctrl->status)); + slave_stat = status_change_0to7; + slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STATUS_8TO_11, status_change_8to11) << 32; + dev_dbg(ctrl->dev, "%s: status_change_0to7:0x%x status_change_8to11:0x%x\n", + __func__, status_change_0to7, status_change_8to11); + if (slave_stat) { + for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) { + if (slave_stat & AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(dev_index)) { + val = (slave_stat >> AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(dev_index)) & + AMD_SDW_MCP_SLAVE_STATUS_MASK; + switch (val) { + case SDW_SLAVE_ATTACHED: + ctrl->status[dev_index] = SDW_SLAVE_ATTACHED; + break; + case SDW_SLAVE_UNATTACHED: + ctrl->status[dev_index] = SDW_SLAVE_UNATTACHED; + break; + case SDW_SLAVE_ALERT: + ctrl->status[dev_index] = SDW_SLAVE_ALERT; + break; + default: + ctrl->status[dev_index] = SDW_SLAVE_RESERVED; + break; + } + } + } + } +} + +static void amd_sdwc_irq_thread(struct work_struct *work) +{ + struct amd_sdwc_ctrl *ctrl = + container_of(work, struct amd_sdwc_ctrl, amd_sdw_irq_thread); + u32 sw_status_change_0to7_reg; + u32 sw_status_change_8to11_reg; + u32 status_change_8to11; + u32 status_change_0to7; + + switch (ctrl->instance) { + case ACP_SDW0: + sw_status_change_0to7_reg = SW_STATE_CHANGE_STATUS_0TO7; + sw_status_change_8to11_reg = SW_STATE_CHANGE_STATUS_8TO11; + break; + case ACP_SDW1: + sw_status_change_0to7_reg = P1_SW_STATE_CHANGE_STATUS_0TO7; + sw_status_change_8to11_reg = P1_SW_STATE_CHANGE_STATUS_8TO11; + break; + default: + dev_err(ctrl->dev, "Invalid Soundwire controller instance\n"); + return; + } + + status_change_8to11 = acp_reg_readl(ctrl->mmio + sw_status_change_8to11_reg); + status_change_0to7 = acp_reg_readl(ctrl->mmio + sw_status_change_0to7_reg); + dev_dbg(ctrl->dev, "%s [SDW%d] SDW INT: 0to7=0x%x, 8to11=0x%x\n", + __func__, ctrl->instance, status_change_0to7, status_change_8to11); + if (status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) { + amd_sdwc_read_and_process_ping_status(ctrl); + } else { + /* Check for the updated status on Slave device */ + amd_sdwc_update_slave_status(status_change_0to7, status_change_8to11, ctrl); + } + if (status_change_8to11 || status_change_0to7) + schedule_work(&ctrl->amd_sdw_work); + acp_reg_writel(0x00, ctrl->mmio + sw_status_change_8to11_reg); + acp_reg_writel(0x00, ctrl->mmio + sw_status_change_0to7_reg); +} + static void amd_sdwc_probe_work(struct work_struct *work) { struct amd_sdwc_ctrl *ctrl = container_of(work, struct amd_sdwc_ctrl, probe_work); @@ -1229,6 +1383,8 @@ static int amd_sdwc_probe(struct platform_device *pdev) sdw_bus_master_delete(&ctrl->bus); return ret; } + INIT_WORK(&ctrl->amd_sdw_irq_thread, amd_sdwc_irq_thread); + INIT_WORK(&ctrl->amd_sdw_work, amd_sdwc_update_slave_status_work); INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); schedule_work(&ctrl->probe_work); return 0; diff --git a/drivers/soundwire/amd_master.h b/drivers/soundwire/amd_master.h index 42f32ca0c7a8..b43a5d6496cb 100644 --- a/drivers/soundwire/amd_master.h +++ b/drivers/soundwire/amd_master.h @@ -236,6 +236,7 @@ #define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10 #define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E #define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF +#define AMD_SDW_PREQ_INTR_STAT BIT(19)
enum amd_sdw_channel { /* SDW0 */ diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index 7a99d782969f..2db03b2f0c3b 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -24,9 +24,12 @@ struct amd_sdwc_ctrl { struct sdw_bus bus; struct device *dev; void __iomem *mmio; + struct work_struct amd_sdw_irq_thread; + struct work_struct amd_sdw_work; struct work_struct probe_work; struct mutex *sdw_lock; struct sdw_stream_runtime *sruntime[AMD_SDW_MAX_DAIS]; + enum sdw_slave_status status[SDW_MAX_DEVICES + 1]; int num_din_ports; int num_dout_ports; int cols_index;
+static void amd_sdwc_process_ping_status(u64 response, struct amd_sdwc_ctrl *ctrl) +{
- u64 slave_stat = 0;
- u32 val = 0;
- u16 dev_index;
- /* slave status from ping response*/
response */
- slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
- dev_dbg(ctrl->dev, "%s: slave_stat:0x%llx\n", __func__, slave_stat);
- for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) {
val = (slave_stat >> (dev_index * 2)) & AMD_SDW_MCP_SLAVE_STATUS_MASK;
dev_dbg(ctrl->dev, "%s val:0x%x\n", __func__, val);
switch (val) {
case SDW_SLAVE_ATTACHED:
ctrl->status[dev_index] = SDW_SLAVE_ATTACHED;
break;
case SDW_SLAVE_UNATTACHED:
ctrl->status[dev_index] = SDW_SLAVE_UNATTACHED;
break;
case SDW_SLAVE_ALERT:
ctrl->status[dev_index] = SDW_SLAVE_ALERT;
break;
default:
ctrl->status[dev_index] = SDW_SLAVE_RESERVED;
break;
}
- }
+}
+static void amd_sdwc_read_and_process_ping_status(struct amd_sdwc_ctrl *ctrl) +{
- u64 response = 0;
- mutex_lock(&ctrl->bus.msg_lock);
- response = amd_sdwc_send_cmd_get_resp(ctrl, 0, 0);
- mutex_unlock(&ctrl->bus.msg_lock);
- amd_sdwc_process_ping_status(response, ctrl);
Is this saying that you actually need to send a PING frame manually every time the manager needs to check the device status, including interrupts?
+}
static u32 amd_sdwc_read_ping_status(struct sdw_bus *bus) { struct amd_sdwc_ctrl *ctrl = to_amd_sdw(bus); @@ -1132,6 +1173,119 @@ static int amd_sdwc_register_dais(struct amd_sdwc_ctrl *ctrl) dais, num_dais); }
+static void amd_sdwc_update_slave_status_work(struct work_struct *work) +{
- struct amd_sdwc_ctrl *ctrl =
container_of(work, struct amd_sdwc_ctrl, amd_sdw_work);
- u32 sw_status_change_mask_0to7_reg;
- u32 sw_status_change_mask_8to11_reg;
- switch (ctrl->instance) {
- case ACP_SDW0:
sw_status_change_mask_0to7_reg = SW_STATE_CHANGE_STATUS_MASK_0TO7;
sw_status_change_mask_8to11_reg = SW_STATE_CHANGE_STATUS_MASK_8TO11;
break;
- case ACP_SDW1:
sw_status_change_mask_0to7_reg = P1_SW_STATE_CHANGE_STATUS_MASK_0TO7;
sw_status_change_mask_8to11_reg = P1_SW_STATE_CHANGE_STATUS_MASK_8TO11;
break;
- default:
dev_err(ctrl->dev, "Invalid Soundwire controller instance\n");
return;
- }
- if (ctrl->status[0] == SDW_SLAVE_ATTACHED) {
acp_reg_writel(0, ctrl->mmio + sw_status_change_mask_0to7_reg);
acp_reg_writel(0, ctrl->mmio + sw_status_change_mask_8to11_reg);
- }
+update_status:
- sdw_handle_slave_status(&ctrl->bus, ctrl->status);
- if (ctrl->status[0] == SDW_SLAVE_ATTACHED) {
acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, ctrl->mmio + sw_status_change_mask_0to7_reg);
acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11,
ctrl->mmio + sw_status_change_mask_8to11_reg);
amd_sdwc_read_and_process_ping_status(ctrl);
goto update_status;
- }
well no, you have to use some sort of retry count. You cannot handle interrupts in a loop like this, a faulty or chatty device would keep signaling an issue and you would be stuck here for a while.
In addition, it's not clear if this is really needed. We added this loop in cadence_master.c because of issues with multiple devices becoming attached at the same time and how the hardware works. As it turns out, this update_status loop seems to be a paranoid case, the actually cause for devices de-attaching was found by Cirrus Logic and fixed in "soundwire: cadence: fix updating slave status when a bus has multiple peripherals"
You would need to explain how the status is detected and if any race conditions can occur.
Hi Vijendar,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on broonie-sound/for-next] [also build test WARNING on next-20230119] [cannot apply to vkoul-dmaengine/next linus/master v6.2-rc4] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Vijendar-Mukunda/ASoC-amd-ps-... base: https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next patch link: https://lore.kernel.org/r/20230111090222.2016499-6-Vijendar.Mukunda%40amd.co... patch subject: [PATCH 05/19] soundwire: amd: add soundwire interrupt handling config: i386-allyesconfig (https://download.01.org/0day-ci/archive/20230120/202301200537.eS27M0By-lkp@i...) compiler: clang version 14.0.6 (https://github.com/llvm/llvm-project f28c006a5895fc0e329fe15fead81e37457cb1d1) reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/intel-lab-lkp/linux/commit/ee04e2b3a1ee45081b430ae161c53a... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Vijendar-Mukunda/ASoC-amd-ps-create-platform-devices-based-on-acp-config/20230111-170749 git checkout ee04e2b3a1ee45081b430ae161c53aa8964d5c36 # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=i386 olddefconfig COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=i386 SHELL=/bin/bash drivers/soundwire/
If you fix the issue, kindly add following tag where applicable | Reported-by: kernel test robot lkp@intel.com
All warnings (new ones prefixed by >>):
^~~~~~~~~ drivers/soundwire/amd_master.c:610:26: warning: shift count is negative [-Wshift-count-negative] slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 6 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~ drivers/soundwire/amd_master.c:610:26: warning: shift count is negative [-Wshift-count-negative] slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 5 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~ drivers/soundwire/amd_master.c:610:26: warning: shift count is negative [-Wshift-count-negative] slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ note: (skipping 6 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all) include/linux/compiler_types.h:358:22: note: expanded from macro 'compiletime_assert' _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__) ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:346:23: note: expanded from macro '_compiletime_assert' __compiletime_assert(condition, msg, prefix, suffix) ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ include/linux/compiler_types.h:338:9: note: expanded from macro '__compiletime_assert' if (!(condition)) \ ^~~~~~~~~ drivers/soundwire/amd_master.c:610:26: warning: shift count is negative [-Wshift-count-negative] slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ include/linux/bitfield.h:129:30: note: expanded from macro 'FIELD_GET' (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \ ^~~~~ drivers/soundwire/amd_master.c:610:26: warning: shift count is negative [-Wshift-count-negative] slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ drivers/soundwire/amd_master.h:184:38: note: expanded from macro 'AMD_SDW_MCP_SLAVE_STAT_4_11' #define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK(39, 24) ^ include/linux/bits.h:38:31: note: expanded from macro 'GENMASK' (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) ^ include/linux/bits.h:36:11: note: expanded from macro '__GENMASK' (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) ^ include/linux/bitfield.h:129:50: note: expanded from macro 'FIELD_GET' (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \ ~~~~~~~~~^~~~~~ include/linux/bitfield.h:45:38: note: expanded from macro '__bf_shf' #define __bf_shf(x) (__builtin_ffsll(x) - 1) ^
drivers/soundwire/amd_master.c:1223:80: warning: shift count >= width of type [-Wshift-count-overflow]
slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STATUS_8TO_11, status_change_8to11) << 32; ^ ~~ 23 warnings generated.
vim +1223 drivers/soundwire/amd_master.c
1212 1213 static void amd_sdwc_update_slave_status(u32 status_change_0to7, u32 status_change_8to11, 1214 struct amd_sdwc_ctrl *ctrl) 1215 { 1216 u64 slave_stat = 0; 1217 u32 val = 0; 1218 int dev_index; 1219 1220 if (status_change_0to7 == AMD_SDW_SLAVE_0_ATTACHED) 1221 memset(ctrl->status, 0, sizeof(ctrl->status)); 1222 slave_stat = status_change_0to7;
1223 slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STATUS_8TO_11, status_change_8to11) << 32;
1224 dev_dbg(ctrl->dev, "%s: status_change_0to7:0x%x status_change_8to11:0x%x\n", 1225 __func__, status_change_0to7, status_change_8to11); 1226 if (slave_stat) { 1227 for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) { 1228 if (slave_stat & AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(dev_index)) { 1229 val = (slave_stat >> AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(dev_index)) & 1230 AMD_SDW_MCP_SLAVE_STATUS_MASK; 1231 switch (val) { 1232 case SDW_SLAVE_ATTACHED: 1233 ctrl->status[dev_index] = SDW_SLAVE_ATTACHED; 1234 break; 1235 case SDW_SLAVE_UNATTACHED: 1236 ctrl->status[dev_index] = SDW_SLAVE_UNATTACHED; 1237 break; 1238 case SDW_SLAVE_ALERT: 1239 ctrl->status[dev_index] = SDW_SLAVE_ALERT; 1240 break; 1241 default: 1242 ctrl->status[dev_index] = SDW_SLAVE_RESERVED; 1243 break; 1244 } 1245 } 1246 } 1247 } 1248 } 1249
Handle soundwire controller related interrupts in ACP PCI driver irq handler and schedule controller work queue for further processing.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/acp63.h | 4 ++++ sound/soc/amd/ps/pci-ps.c | 40 ++++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index ed979e6d0c1d..0bd9dc363461 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -64,6 +64,10 @@ #define ACP63_SDW_ADDR 5 #define AMD_SDW_MAX_CONTROLLERS 2
+#define ACP_SDW0_IRQ_MASK 21 +#define ACP_SDW1_IRQ_MASK 2 +#define ACP_ERROR_IRQ_MASK 29 + enum acp_config { ACP_CONFIG_0 = 0, ACP_CONFIG_1, diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 85154cf0b2a2..0fbe5e27f3fb 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -67,6 +67,7 @@ static int acp63_reset(void __iomem *acp_base) static void acp63_enable_interrupts(void __iomem *acp_base) { acp63_writel(1, acp_base + ACP_EXTERNAL_INTR_ENB); + acp63_writel(BIT(ACP_ERROR_IRQ_MASK), acp_base + ACP_EXTERNAL_INTR_CNTL); }
static void acp63_disable_interrupts(void __iomem *acp_base) @@ -116,23 +117,52 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) { struct acp63_dev_data *adata; struct pdm_dev_data *ps_pdm_data; - u32 val; + struct amd_sdwc_ctrl *ctrl; + u32 ext_intr_stat, ext_intr_stat1; + u16 irq_flag = 0; u16 pdev_index;
adata = dev_id; if (!adata) return IRQ_NONE; + ext_intr_stat = acp63_readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + if (ext_intr_stat & BIT(ACP_SDW0_IRQ_MASK)) { + pdev_index = adata->sdw0_dev_index; + ctrl = dev_get_drvdata(&adata->pdev[pdev_index]->dev); + acp63_writel(BIT(ACP_SDW0_IRQ_MASK), adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + schedule_work(&ctrl->amd_sdw_irq_thread); + irq_flag = 1; + }
- val = acp63_readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT); - if (val & BIT(PDM_DMA_STAT)) { + ext_intr_stat1 = acp63_readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT1); + if (ext_intr_stat1 & BIT(ACP_SDW1_IRQ_MASK)) { + pdev_index = adata->sdw1_dev_index; + ctrl = dev_get_drvdata(&adata->pdev[pdev_index]->dev); + acp63_writel(BIT(ACP_SDW1_IRQ_MASK), adata->acp63_base + ACP_EXTERNAL_INTR_STAT1); + schedule_work(&ctrl->amd_sdw_irq_thread); + irq_flag = 1; + } + + if (ext_intr_stat & BIT(ACP_ERROR_IRQ_MASK)) { + acp63_writel(BIT(ACP_ERROR_IRQ_MASK), adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + acp63_writel(0, adata->acp63_base + ACP_SW_I2S_ERROR_REASON); + acp63_writel(0, adata->acp63_base + ACP_P1_SW_I2S_ERROR_REASON); + acp63_writel(0, adata->acp63_base + ACP_ERROR_STATUS); + irq_flag = 1; + } + + if (ext_intr_stat & BIT(PDM_DMA_STAT)) { pdev_index = adata->pdm_dev_index; ps_pdm_data = dev_get_drvdata(&adata->pdev[pdev_index]->dev); acp63_writel(BIT(PDM_DMA_STAT), adata->acp63_base + ACP_EXTERNAL_INTR_STAT); if (ps_pdm_data->capture_stream) snd_pcm_period_elapsed(ps_pdm_data->capture_stream); - return IRQ_HANDLED; + irq_flag = 1; } - return IRQ_NONE; + if (irq_flag) + return IRQ_HANDLED; + else + return IRQ_NONE; }
static int sdw_amd_scan_controller(struct device *dev)
Soundwire DMA platform driver binds to the platform device created by ACP PCI device. Soundwire DMA driver registers ALSA DMA component with ASoC framework.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/acp63.h | 5 +++ sound/soc/amd/ps/ps-sdw-dma.c | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 sound/soc/amd/ps/ps-sdw-dma.c
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 0bd9dc363461..b462320fdf2a 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -135,3 +135,8 @@ struct acp63_dev_data { bool is_sdw_dev; bool acp_sdw_power_off; }; + +struct sdw_dma_dev_data { + void __iomem *acp_base; + struct mutex *acp_lock; +}; diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c new file mode 100644 index 000000000000..388a4b7df715 --- /dev/null +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AMD ALSA SoC Pink Sardine Soundwire DMA Driver + * + * Copyright 2023 Advanced Micro Devices, Inc. + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include "acp63.h" + +#define DRV_NAME "amd_ps_sdw_dma" + +static const struct snd_soc_component_driver acp63_sdw_component = { + .name = DRV_NAME, +}; + +static int acp63_sdw_platform_probe(struct platform_device *pdev) +{ + struct resource *res; + struct sdw_dma_dev_data *sdw_data; + int status; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "platform_data not retrieved\n"); + return -ENODEV; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENODEV; + } + + sdw_data = devm_kzalloc(&pdev->dev, sizeof(*sdw_data), GFP_KERNEL); + if (!sdw_data) + return -ENOMEM; + + sdw_data->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!sdw_data->acp_base) + return -ENOMEM; + + sdw_data->acp_lock = pdev->dev.platform_data; + dev_set_drvdata(&pdev->dev, sdw_data); + status = devm_snd_soc_register_component(&pdev->dev, + &acp63_sdw_component, + NULL, 0); + if (status) { + dev_err(&pdev->dev, "Fail to register acp pdm dai\n"); + + return -ENODEV; + } + return 0; +} + +static struct platform_driver acp63_sdw_dma_driver = { + .probe = acp63_sdw_platform_probe, + .driver = { + .name = "amd_ps_sdw_dma", + }, +}; + +module_platform_driver(acp63_sdw_dma_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD ACP6.3 PS SDW DMA Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME);
On 1/11/23 03:02, Vijendar Mukunda wrote:
Soundwire DMA platform driver binds to the platform device created by ACP PCI device. Soundwire DMA driver registers ALSA DMA component with ASoC framework.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
sound/soc/amd/ps/acp63.h | 5 +++ sound/soc/amd/ps/ps-sdw-dma.c | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 sound/soc/amd/ps/ps-sdw-dma.c
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 0bd9dc363461..b462320fdf2a 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -135,3 +135,8 @@ struct acp63_dev_data { bool is_sdw_dev; bool acp_sdw_power_off; };
+struct sdw_dma_dev_data {
- void __iomem *acp_base;
- struct mutex *acp_lock;
+}; diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c new file mode 100644 index 000000000000..388a4b7df715 --- /dev/null +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- AMD ALSA SoC Pink Sardine Soundwire DMA Driver
- Copyright 2023 Advanced Micro Devices, Inc.
- */
+#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include "acp63.h"
+#define DRV_NAME "amd_ps_sdw_dma"
+static const struct snd_soc_component_driver acp63_sdw_component = {
- .name = DRV_NAME,
+};
+static int acp63_sdw_platform_probe(struct platform_device *pdev) +{
- struct resource *res;
- struct sdw_dma_dev_data *sdw_data;
- int status;
- if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "platform_data not retrieved\n");
return -ENODEV;
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
- }
- sdw_data = devm_kzalloc(&pdev->dev, sizeof(*sdw_data), GFP_KERNEL);
- if (!sdw_data)
return -ENOMEM;
- sdw_data->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
- if (!sdw_data->acp_base)
return -ENOMEM;
- sdw_data->acp_lock = pdev->dev.platform_data;
- dev_set_drvdata(&pdev->dev, sdw_data);
- status = devm_snd_soc_register_component(&pdev->dev,
&acp63_sdw_component,
NULL, 0);
- if (status) {
dev_err(&pdev->dev, "Fail to register acp pdm dai\n");
not sure what this means? Are you registering a PDM component or a DMA one?
return -ENODEV;
- }
- return 0;
+}
+static struct platform_driver acp63_sdw_dma_driver = {
- .probe = acp63_sdw_platform_probe,
- .driver = {
.name = "amd_ps_sdw_dma",
- },
+};
+module_platform_driver(acp63_sdw_dma_driver);
+MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD ACP6.3 PS SDW DMA Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME);
On 11/01/23 20:52, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
Soundwire DMA platform driver binds to the platform device created by ACP PCI device. Soundwire DMA driver registers ALSA DMA component with ASoC framework.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
sound/soc/amd/ps/acp63.h | 5 +++ sound/soc/amd/ps/ps-sdw-dma.c | 72 +++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 sound/soc/amd/ps/ps-sdw-dma.c
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 0bd9dc363461..b462320fdf2a 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -135,3 +135,8 @@ struct acp63_dev_data { bool is_sdw_dev; bool acp_sdw_power_off; };
+struct sdw_dma_dev_data {
- void __iomem *acp_base;
- struct mutex *acp_lock;
+}; diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c new file mode 100644 index 000000000000..388a4b7df715 --- /dev/null +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- AMD ALSA SoC Pink Sardine Soundwire DMA Driver
- Copyright 2023 Advanced Micro Devices, Inc.
- */
+#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include "acp63.h"
+#define DRV_NAME "amd_ps_sdw_dma"
+static const struct snd_soc_component_driver acp63_sdw_component = {
- .name = DRV_NAME,
+};
+static int acp63_sdw_platform_probe(struct platform_device *pdev) +{
- struct resource *res;
- struct sdw_dma_dev_data *sdw_data;
- int status;
- if (!pdev->dev.platform_data) {
dev_err(&pdev->dev, "platform_data not retrieved\n");
return -ENODEV;
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
- }
- sdw_data = devm_kzalloc(&pdev->dev, sizeof(*sdw_data), GFP_KERNEL);
- if (!sdw_data)
return -ENOMEM;
- sdw_data->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
- if (!sdw_data->acp_base)
return -ENOMEM;
- sdw_data->acp_lock = pdev->dev.platform_data;
- dev_set_drvdata(&pdev->dev, sdw_data);
- status = devm_snd_soc_register_component(&pdev->dev,
&acp63_sdw_component,
NULL, 0);
- if (status) {
dev_err(&pdev->dev, "Fail to register acp pdm dai\n");
not sure what this means? Are you registering a PDM component or a DMA one?
It's my bad. We will correct the log statement.
return -ENODEV;
- }
- return 0;
+}
+static struct platform_driver acp63_sdw_dma_driver = {
- .probe = acp63_sdw_platform_probe,
- .driver = {
.name = "amd_ps_sdw_dma",
- },
+};
+module_platform_driver(acp63_sdw_dma_driver);
+MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD ACP6.3 PS SDW DMA Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME);
Add Soundwire DMA driver dma ops for Pink Sardine platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/acp63.h | 61 ++++ sound/soc/amd/ps/ps-sdw-dma.c | 531 ++++++++++++++++++++++++++++++++++ 2 files changed, 592 insertions(+)
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index b462320fdf2a..8963cfb6120d 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -67,6 +67,38 @@ #define ACP_SDW0_IRQ_MASK 21 #define ACP_SDW1_IRQ_MASK 2 #define ACP_ERROR_IRQ_MASK 29 +#define ACP_AUDIO_TX_THRESHOLD 28 +#define ACP_BT_TX_THRESHOLD 26 +#define ACP_HS_TX_THRESHOLD 24 +#define ACP_AUDIO_RX_THRESHOLD 27 +#define ACP_BT_RX_THRESHOLD 25 +#define ACP_HS_RX_THRESHOLD 23 +#define ACP_P1_BT_TX_THRESHOLD 6 +#define ACP_P1_BT_RX_THRESHOLD 5 +#define ACP_SDW_DMA_IRQ_MASK 0x1F800000 +#define ACP_P1_SDW_DMA_IRQ_MASK 0x60 +#define ACP63_SDW_MAX_STREAMS 8 + +#define ACP_DELAY_US 5 +#define SDW_MEM_WINDOW_START 0x4800000 +#define ACP_SDW_SRAM_PTE_OFFSET 0x03800400 +#define SDW_PTE_OFFSET 0x400 +#define SDW_FIFO_SIZE 0x100 +#define SDW_DMA_SIZE 0x40 +#define ACP_SDW_FIFO_OFFSET 0x100 +#define ACP_SDW_RING_BUFF_ADDR_OFFSET (128 * 1024) + +#define SDW_PLAYBACK_MIN_NUM_PERIODS 2 +#define SDW_PLAYBACK_MAX_NUM_PERIODS 8 +#define SDW_PLAYBACK_MAX_PERIOD_SIZE 8192 +#define SDW_PLAYBACK_MIN_PERIOD_SIZE 1024 +#define SDW_CAPTURE_MIN_NUM_PERIODS 2 +#define SDW_CAPTURE_MAX_NUM_PERIODS 8 +#define SDW_CAPTURE_MAX_PERIOD_SIZE 8192 +#define SDW_CAPTURE_MIN_PERIOD_SIZE 1024 + +#define SDW_MAX_BUFFER (SDW_PLAYBACK_MAX_PERIOD_SIZE * SDW_PLAYBACK_MAX_NUM_PERIODS) +#define SDW_MIN_BUFFER SDW_MAX_BUFFER
enum acp_config { ACP_CONFIG_0 = 0, @@ -93,6 +125,17 @@ enum acp_pdev_mask { ACP63_SDW_PDM_DEV_MASK, };
+enum channel_type { + ACP_SDW_AUDIO_TX = 0, + ACP_SDW_BT_TX, + ACP_SDW_HS_TX, + ACP_SDW_AUDIO_RX, + ACP_SDW_BT_RX, + ACP_SDW_HS_RX, + ACP_SDW1_BT_TX, + ACP_SDW1_BT_RX, +}; + struct pdm_stream_instance { u16 num_pages; u16 channels; @@ -139,4 +182,22 @@ struct acp63_dev_data { struct sdw_dma_dev_data { void __iomem *acp_base; struct mutex *acp_lock; + struct snd_pcm_substream *sdw_stream[ACP63_SDW_MAX_STREAMS]; +}; + +struct sdw_stream_instance { + u16 num_pages; + u16 channels; + u32 stream_id; + dma_addr_t dma_addr; + u64 bytescount; + void __iomem *acp_base; +}; + +union acp_sdw_dma_count { + struct { + u32 low; + u32 high; + } bcount; + u64 bytescount; }; diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index 388a4b7df715..e94f76053c66 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -12,12 +12,543 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dai.h> +#include <linux/soundwire/sdw_amd.h> #include "acp63.h"
#define DRV_NAME "amd_ps_sdw_dma"
+static const struct snd_pcm_hardware acp63_sdw_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .buffer_bytes_max = SDW_PLAYBACK_MAX_NUM_PERIODS * SDW_PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = SDW_PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = SDW_PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = SDW_PLAYBACK_MIN_NUM_PERIODS, + .periods_max = SDW_PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp63_sdw_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .buffer_bytes_max = SDW_CAPTURE_MAX_NUM_PERIODS * SDW_CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = SDW_CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = SDW_CAPTURE_MAX_PERIOD_SIZE, + .periods_min = SDW_CAPTURE_MIN_NUM_PERIODS, + .periods_max = SDW_CAPTURE_MAX_NUM_PERIODS, +}; + +static void acp63_config_dma(struct sdw_stream_instance *sdw_ins, u32 stream_id) +{ + u16 page_idx; + u32 low, high, val; + dma_addr_t addr; + + addr = sdw_ins->dma_addr; + val = SDW_PTE_OFFSET + (stream_id * 256); + + /* Group Enable */ + acp63_writel(ACP_SDW_SRAM_PTE_OFFSET | BIT(31), sdw_ins->acp_base + + ACPAXI2AXI_ATU_BASE_ADDR_GRP_2); + acp63_writel(PAGE_SIZE_4K_ENABLE, sdw_ins->acp_base + + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2); + for (page_idx = 0; page_idx < sdw_ins->num_pages; page_idx++) { + /* Load the low address of page int ACP SRAM through SRBM */ + low = lower_32_bits(addr); + high = upper_32_bits(addr); + + acp63_writel(low, sdw_ins->acp_base + ACP_SCRATCH_REG_0 + val); + high |= BIT(31); + acp63_writel(high, sdw_ins->acp_base + ACP_SCRATCH_REG_0 + val + 4); + val += 8; + addr += PAGE_SIZE; + } + + /*cache Invalidation added for Testing */ + acp63_writel(0x1, sdw_ins->acp_base + ACPAXI2AXI_ATU_CTRL); +} + +static int acp63_configure_sdw_ringbuffer(void __iomem *acp_base, u32 stream_id, u32 size) +{ + u32 reg_dma_size; + u32 reg_fifo_addr; + u32 reg_fifo_size; + u32 reg_ring_buf_size; + u32 reg_ring_buf_addr; + u32 sdw_fifo_addr; + u32 sdw_ring_buf_addr; + u32 sdw_ring_buf_size; + + switch (stream_id) { + case ACP_SDW_AUDIO_TX: + reg_dma_size = ACP_AUDIO_TX_DMA_SIZE; + reg_fifo_addr = ACP_AUDIO_TX_FIFOADDR; + reg_fifo_size = ACP_AUDIO_TX_FIFOSIZE; + reg_ring_buf_size = ACP_AUDIO_TX_RINGBUFSIZE; + reg_ring_buf_addr = ACP_AUDIO_TX_RINGBUFADDR; + break; + case ACP_SDW_BT_TX: + reg_dma_size = ACP_BT_TX_DMA_SIZE; + reg_fifo_addr = ACP_BT_TX_FIFOADDR; + reg_fifo_size = ACP_BT_TX_FIFOSIZE; + reg_ring_buf_size = ACP_BT_TX_RINGBUFSIZE; + reg_ring_buf_addr = ACP_BT_TX_RINGBUFADDR; + break; + case ACP_SDW_HS_TX: + reg_dma_size = ACP_HS_TX_DMA_SIZE; + reg_fifo_addr = ACP_HS_TX_FIFOADDR; + reg_fifo_size = ACP_HS_TX_FIFOSIZE; + reg_ring_buf_size = ACP_HS_TX_RINGBUFSIZE; + reg_ring_buf_addr = ACP_HS_TX_RINGBUFADDR; + break; + case ACP_SDW1_BT_TX: + reg_dma_size = ACP_P1_BT_TX_DMA_SIZE; + reg_fifo_addr = ACP_P1_BT_TX_FIFOADDR; + reg_fifo_size = ACP_P1_BT_TX_FIFOSIZE; + reg_ring_buf_size = ACP_P1_BT_TX_RINGBUFSIZE; + reg_ring_buf_addr = ACP_P1_BT_TX_RINGBUFADDR; + break; + case ACP_SDW_AUDIO_RX: + reg_dma_size = ACP_AUDIO_RX_DMA_SIZE; + reg_fifo_addr = ACP_AUDIO_RX_FIFOADDR; + reg_fifo_size = ACP_AUDIO_RX_FIFOSIZE; + reg_ring_buf_size = ACP_AUDIO_RX_RINGBUFSIZE; + reg_ring_buf_addr = ACP_AUDIO_RX_RINGBUFADDR; + break; + case ACP_SDW_BT_RX: + reg_dma_size = ACP_BT_RX_DMA_SIZE; + reg_fifo_addr = ACP_BT_RX_FIFOADDR; + reg_fifo_size = ACP_BT_RX_FIFOSIZE; + reg_ring_buf_size = ACP_BT_RX_RINGBUFSIZE; + reg_ring_buf_addr = ACP_BT_RX_RINGBUFADDR; + break; + case ACP_SDW_HS_RX: + reg_dma_size = ACP_HS_RX_DMA_SIZE; + reg_fifo_addr = ACP_HS_RX_FIFOADDR; + reg_fifo_size = ACP_HS_RX_FIFOSIZE; + reg_ring_buf_size = ACP_HS_RX_RINGBUFSIZE; + reg_ring_buf_addr = ACP_HS_RX_RINGBUFADDR; + break; + case ACP_SDW1_BT_RX: + reg_dma_size = ACP_P1_BT_RX_DMA_SIZE; + reg_fifo_addr = ACP_P1_BT_RX_FIFOADDR; + reg_fifo_size = ACP_P1_BT_RX_FIFOSIZE; + reg_ring_buf_size = ACP_P1_BT_RX_RINGBUFSIZE; + reg_ring_buf_addr = ACP_P1_BT_RX_RINGBUFADDR; + break; + default: + return -EINVAL; + } + sdw_fifo_addr = ACP_SDW_FIFO_OFFSET * stream_id; + sdw_ring_buf_addr = SDW_MEM_WINDOW_START + (stream_id * ACP_SDW_RING_BUFF_ADDR_OFFSET); + sdw_ring_buf_size = size; + acp63_writel(sdw_ring_buf_size, acp_base + reg_ring_buf_size); + acp63_writel(sdw_ring_buf_addr, acp_base + reg_ring_buf_addr); + acp63_writel(sdw_fifo_addr, acp_base + reg_fifo_addr); + acp63_writel(SDW_DMA_SIZE, acp_base + reg_dma_size); + acp63_writel(SDW_FIFO_SIZE, acp_base + reg_fifo_size); + return 0; +} + +static int acp63_sdw_dma_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + struct sdw_dma_dev_data *sdw_dev_data; + struct sdw_stream_instance *sdw_stream_data; + struct snd_soc_dai *cpu_dai; + struct amd_sdwc_ctrl *ctrl; + struct snd_soc_pcm_runtime *prtd = substream->private_data; + int ret; + + runtime = substream->runtime; + sdw_dev_data = dev_get_drvdata(component->dev); + cpu_dai = asoc_rtd_to_cpu(prtd, 0); + ctrl = snd_soc_dai_get_drvdata(cpu_dai); + sdw_stream_data = kzalloc(sizeof(*sdw_stream_data), GFP_KERNEL); + if (!sdw_stream_data) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = acp63_sdw_hardware_playback; + else + runtime->hw = acp63_sdw_hardware_capture; + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(component->dev, "set integer constraint failed\n"); + kfree(sdw_stream_data); + return ret; + } + + if (ctrl->instance == ACP_SDW1) + sdw_stream_data->stream_id = cpu_dai->id + ACP_SDW0_MAX_DAI; + else + sdw_stream_data->stream_id = cpu_dai->id; + sdw_stream_data->acp_base = sdw_dev_data->acp_base; + runtime->private_data = sdw_stream_data; + return ret; +} + +static int acp63_sdw_dma_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct sdw_stream_instance *sdw_stream_data; + struct sdw_dma_dev_data *sdw_data; + u32 period_bytes; + u32 water_mark_size_reg; + u32 irq_mask, ext_intr_ctrl; + u64 size; + u32 stream_id; + u32 acp_ext_intr_cntl_reg; + int ret; + + stream_id = 0; + sdw_data = dev_get_drvdata(component->dev); + sdw_stream_data = substream->runtime->private_data; + if (!sdw_stream_data) + return -EINVAL; + stream_id = sdw_stream_data->stream_id; + sdw_data->sdw_stream[stream_id] = substream; + size = params_buffer_bytes(params); + period_bytes = params_period_bytes(params); + sdw_stream_data->dma_addr = substream->runtime->dma_addr; + sdw_stream_data->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); + acp63_config_dma(sdw_stream_data, stream_id); + ret = acp63_configure_sdw_ringbuffer(sdw_stream_data->acp_base, stream_id, size); + if (ret) { + dev_err(component->dev, "Invalid channel type\n"); + return -EINVAL; + } + switch (stream_id) { + case ACP_SDW_AUDIO_TX: + water_mark_size_reg = ACP_AUDIO_TX_INTR_WATERMARK_SIZE; + irq_mask = BIT(ACP_AUDIO_TX_THRESHOLD); + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL; + break; + case ACP_SDW_BT_TX: + water_mark_size_reg = ACP_BT_TX_INTR_WATERMARK_SIZE; + irq_mask = BIT(ACP_BT_TX_THRESHOLD); + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL; + break; + case ACP_SDW_HS_TX: + water_mark_size_reg = ACP_HS_TX_INTR_WATERMARK_SIZE; + irq_mask = BIT(ACP_HS_TX_THRESHOLD); + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL; + break; + case ACP_SDW1_BT_TX: + water_mark_size_reg = ACP_P1_BT_TX_INTR_WATERMARK_SIZE; + irq_mask = BIT(ACP_P1_BT_TX_THRESHOLD); + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1; + break; + case ACP_SDW_AUDIO_RX: + water_mark_size_reg = ACP_AUDIO_RX_INTR_WATERMARK_SIZE; + irq_mask = BIT(ACP_AUDIO_RX_THRESHOLD); + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL; + break; + case ACP_SDW_BT_RX: + water_mark_size_reg = ACP_BT_RX_INTR_WATERMARK_SIZE; + irq_mask = BIT(ACP_BT_RX_THRESHOLD); + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL; + break; + case ACP_SDW_HS_RX: + water_mark_size_reg = ACP_HS_RX_INTR_WATERMARK_SIZE; + irq_mask = BIT(ACP_HS_RX_THRESHOLD); + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL; + break; + case ACP_SDW1_BT_RX: + water_mark_size_reg = ACP_P1_BT_RX_INTR_WATERMARK_SIZE; + irq_mask = BIT(ACP_P1_BT_RX_THRESHOLD); + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1; + break; + default: + dev_err(component->dev, "%s: Invalid channel type\n", __func__); + return -EINVAL; + } + + ext_intr_ctrl = acp63_readl(sdw_stream_data->acp_base + acp_ext_intr_cntl_reg); + ext_intr_ctrl |= irq_mask; + acp63_writel(ext_intr_ctrl, sdw_stream_data->acp_base + acp_ext_intr_cntl_reg); + acp63_writel(period_bytes, sdw_stream_data->acp_base + water_mark_size_reg); + return 0; +} + +static u64 acp63_sdw_get_byte_count(struct sdw_stream_instance *rtd) +{ + union acp_sdw_dma_count byte_count; + u32 pos_low_reg, pos_high_reg; + + byte_count.bytescount = 0; + switch (rtd->stream_id) { + case ACP_SDW_AUDIO_TX: + pos_low_reg = ACP_AUDIO_TX_LINEARPOSITIONCNTR_LOW; + pos_high_reg = ACP_AUDIO_TX_LINEARPOSITIONCNTR_HIGH; + break; + case ACP_SDW_BT_TX: + pos_low_reg = ACP_BT_TX_LINEARPOSITIONCNTR_LOW; + pos_high_reg = ACP_BT_TX_LINEARPOSITIONCNTR_HIGH; + break; + case ACP_SDW_HS_TX: + pos_low_reg = ACP_HS_TX_LINEARPOSITIONCNTR_LOW; + pos_high_reg = ACP_HS_TX_LINEARPOSITIONCNTR_HIGH; + break; + case ACP_SDW1_BT_TX: + pos_low_reg = ACP_P1_BT_TX_LINEARPOSITIONCNTR_LOW; + pos_high_reg = ACP_P1_BT_TX_LINEARPOSITIONCNTR_HIGH; + break; + case ACP_SDW_AUDIO_RX: + pos_low_reg = ACP_AUDIO_RX_LINEARPOSITIONCNTR_LOW; + pos_high_reg = ACP_AUDIO_RX_LINEARPOSITIONCNTR_HIGH; + break; + case ACP_SDW_BT_RX: + pos_low_reg = ACP_BT_RX_LINEARPOSITIONCNTR_LOW; + pos_high_reg = ACP_BT_RX_LINEARPOSITIONCNTR_HIGH; + break; + case ACP_SDW_HS_RX: + pos_low_reg = ACP_HS_RX_LINEARPOSITIONCNTR_LOW; + pos_high_reg = ACP_HS_RX_LINEARPOSITIONCNTR_HIGH; + break; + case ACP_SDW1_BT_RX: + pos_low_reg = ACP_P1_BT_RX_LINEARPOSITIONCNTR_LOW; + pos_high_reg = ACP_P1_BT_RX_LINEARPOSITIONCNTR_HIGH; + break; + default: + pr_err("%s Invalid stream id:%d\n", __func__, rtd->stream_id); + return -EINVAL; + } + + if (pos_low_reg) { + byte_count.bcount.high = acp63_readl(rtd->acp_base + pos_high_reg); + byte_count.bcount.low = acp63_readl(rtd->acp_base + pos_low_reg); + } + return byte_count.bytescount; +} + +static snd_pcm_uframes_t acp63_sdw_dma_pointer(struct snd_soc_component *comp, + struct snd_pcm_substream *stream) +{ + struct sdw_stream_instance *sdw_ins; + u32 pos, buffersize; + u64 bytescount; + + sdw_ins = stream->runtime->private_data; + buffersize = frames_to_bytes(stream->runtime, + stream->runtime->buffer_size); + bytescount = acp63_sdw_get_byte_count(sdw_ins); + if (bytescount > sdw_ins->bytescount) + bytescount -= sdw_ins->bytescount; + pos = do_div(bytescount, buffersize); + return bytes_to_frames(stream->runtime, pos); +} + +static int acp63_sdw_dma_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct device *parent = component->dev->parent; + + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, + parent, SDW_MIN_BUFFER, SDW_MAX_BUFFER); + return 0; +} + +static int acp63_sdw_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct sdw_dma_dev_data *sdw_dma_data; + struct snd_soc_pcm_runtime *prtd; + struct sdw_stream_instance *sdw_ins; + + prtd = asoc_substream_to_rtd(substream); + component = snd_soc_rtdcom_lookup(prtd, DRV_NAME); + sdw_dma_data = dev_get_drvdata(component->dev); + sdw_ins = substream->runtime->private_data; + if (!sdw_ins) + return -EINVAL; + sdw_dma_data->sdw_stream[sdw_ins->stream_id] = NULL; + kfree(sdw_ins); + return 0; +} + +static int acp63_sdw_dma_start(struct snd_pcm_substream *stream) +{ + struct sdw_stream_instance *sdw_ins; + struct snd_soc_pcm_runtime *prtd; + u32 stream_id; + u32 sdw_dma_reg; + u32 sdw_dma_en_stat_reg; + u32 sdw_dma_stat; + u32 val; + int timeout = 0; + + sdw_ins = stream->runtime->private_data; + prtd = stream->private_data; + stream_id = sdw_ins->stream_id; + switch (stream_id) { + case ACP_SDW_AUDIO_TX: + sdw_dma_reg = ACP_SW_AUDIO_TX_EN; + sdw_dma_en_stat_reg = ACP_SW_AUDIO_TX_EN_STATUS; + break; + case ACP_SDW_BT_TX: + sdw_dma_reg = ACP_SW_BT_TX_EN; + sdw_dma_en_stat_reg = ACP_SW_BT_TX_EN_STATUS; + break; + case ACP_SDW_HS_TX: + sdw_dma_reg = ACP_SW_HEADSET_TX_EN; + sdw_dma_en_stat_reg = ACP_SW_HEADSET_TX_EN_STATUS; + break; + case ACP_SDW1_BT_TX: + sdw_dma_reg = ACP_P1_SW_BT_TX_EN; + sdw_dma_en_stat_reg = ACP_P1_SW_BT_TX_EN_STATUS; + break; + case ACP_SDW_AUDIO_RX: + sdw_dma_reg = ACP_SW_AUDIO_RX_EN; + sdw_dma_en_stat_reg = ACP_SW_AUDIO_RX_EN_STATUS; + break; + case ACP_SDW_BT_RX: + sdw_dma_reg = ACP_SW_BT_RX_EN; + sdw_dma_en_stat_reg = ACP_SW_BT_RX_EN_STATUS; + break; + case ACP_SDW_HS_RX: + sdw_dma_reg = ACP_SW_HEADSET_RX_EN; + sdw_dma_en_stat_reg = ACP_SW_HEADSET_RX_EN_STATUS; + break; + case ACP_SDW1_BT_RX: + sdw_dma_reg = ACP_P1_SW_BT_RX_EN; + sdw_dma_en_stat_reg = ACP_P1_SW_BT_RX_EN_STATUS; + break; + default: + return -EINVAL; + } + acp63_writel(0x01, sdw_ins->acp_base + sdw_dma_reg); + while (++timeout < ACP_COUNTER) { + sdw_dma_stat = acp63_readl(sdw_ins->acp_base + sdw_dma_en_stat_reg); + if (sdw_dma_stat & BIT(0)) { + val = acp63_readl(sdw_ins->acp_base + sdw_dma_reg); + dev_dbg(prtd->dev, "%s stream_id:0x%x dma_reg[0x%x]:0x%x\n", + __func__, stream_id, sdw_dma_reg, val); + return 0; + } + udelay(ACP_DELAY_US); + } + return -ETIMEDOUT; +} + +static int acp63_sdw_dma_stop(struct snd_pcm_substream *stream) +{ + struct sdw_stream_instance *sdw_ins; + struct snd_soc_pcm_runtime *prtd; + u32 stream_id; + u32 sdw_dma_reg; + u32 sdw_dma_en_stat_reg; + u32 sdw_dma_stat; + u32 val; + int timeout = 0; + + prtd = stream->private_data; + sdw_ins = stream->runtime->private_data; + stream_id = sdw_ins->stream_id; + switch (stream_id) { + case ACP_SDW_AUDIO_TX: + sdw_dma_reg = ACP_SW_AUDIO_TX_EN; + sdw_dma_en_stat_reg = ACP_SW_AUDIO_TX_EN_STATUS; + break; + case ACP_SDW_BT_TX: + sdw_dma_reg = ACP_SW_BT_TX_EN; + sdw_dma_en_stat_reg = ACP_SW_BT_TX_EN_STATUS; + break; + case ACP_SDW_HS_TX: + sdw_dma_reg = ACP_SW_HEADSET_TX_EN; + sdw_dma_en_stat_reg = ACP_SW_HEADSET_TX_EN_STATUS; + break; + case ACP_SDW1_BT_TX: + sdw_dma_reg = ACP_P1_SW_BT_TX_EN; + sdw_dma_en_stat_reg = ACP_P1_SW_BT_TX_EN_STATUS; + break; + case ACP_SDW_AUDIO_RX: + sdw_dma_reg = ACP_SW_AUDIO_RX_EN; + sdw_dma_en_stat_reg = ACP_SW_AUDIO_RX_EN_STATUS; + break; + case ACP_SDW_BT_RX: + sdw_dma_reg = ACP_SW_BT_RX_EN; + sdw_dma_en_stat_reg = ACP_SW_BT_RX_EN_STATUS; + break; + case ACP_SDW_HS_RX: + sdw_dma_reg = ACP_SW_HEADSET_RX_EN; + sdw_dma_en_stat_reg = ACP_SW_HEADSET_RX_EN_STATUS; + break; + case ACP_SDW1_BT_RX: + sdw_dma_reg = ACP_P1_SW_BT_RX_EN; + sdw_dma_en_stat_reg = ACP_P1_SW_BT_RX_EN_STATUS; + break; + default: + return -EINVAL; + } + acp63_writel(0, sdw_ins->acp_base + sdw_dma_reg); + while (++timeout < ACP_COUNTER) { + sdw_dma_stat = acp63_readl(sdw_ins->acp_base + sdw_dma_en_stat_reg); + if (sdw_dma_stat == 0) { + val = acp63_readl(sdw_ins->acp_base + sdw_dma_reg); + dev_dbg(prtd->dev, "%s stream_id:0x%x dma_reg[0x%x]:0x%x\n", + __func__, stream_id, sdw_dma_reg, val); + return 0; + } + udelay(ACP_DELAY_US); + } + return -ETIMEDOUT; +} + +static int acp63_sdw_dma_trigger(struct snd_soc_component *comp, + struct snd_pcm_substream *substream, + int cmd) +{ + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + ret = acp63_sdw_dma_start(substream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + ret = acp63_sdw_dma_stop(substream); + break; + default: + ret = -EINVAL; + } + if (ret) + dev_err(comp->dev, "trigger %d failed: %d", cmd, ret); + return ret; +} + static const struct snd_soc_component_driver acp63_sdw_component = { .name = DRV_NAME, + .open = acp63_sdw_dma_open, + .close = acp63_sdw_dma_close, + .hw_params = acp63_sdw_dma_hw_params, + .trigger = acp63_sdw_dma_trigger, + .pointer = acp63_sdw_dma_pointer, + .pcm_construct = acp63_sdw_dma_new, };
static int acp63_sdw_platform_probe(struct platform_device *pdev)
On Wed, Jan 11, 2023 at 02:32:11PM +0530, Vijendar Mukunda wrote:
+static int acp63_sdw_dma_start(struct snd_pcm_substream *stream) +{
- struct sdw_stream_instance *sdw_ins;
- struct snd_soc_pcm_runtime *prtd;
- u32 stream_id;
- u32 sdw_dma_reg;
- u32 sdw_dma_en_stat_reg;
- u32 sdw_dma_stat;
- u32 val;
- int timeout = 0;
- sdw_ins = stream->runtime->private_data;
- prtd = stream->private_data;
- stream_id = sdw_ins->stream_id;
- switch (stream_id) {
- case ACP_SDW_AUDIO_TX:
sdw_dma_reg = ACP_SW_AUDIO_TX_EN;
sdw_dma_en_stat_reg = ACP_SW_AUDIO_TX_EN_STATUS;
break;
Not super urgent but if you're respinning then it looks like the register selection here is the same in _dma_stop() so they could be shared. Indeed it generally feels like it might be nicer to have a table of structs listing the registers needed per stream so all these switch statements can be more like
sdw_dma_reg = stream_registers[sdw_ins->stream_id];
That'd make each function smaller and I'd expect it'd be a bit easier to add new streams for new hardware that way.
On 11/01/23 18:34, Mark Brown wrote:
On Wed, Jan 11, 2023 at 02:32:11PM +0530, Vijendar Mukunda wrote:
+static int acp63_sdw_dma_start(struct snd_pcm_substream *stream) +{
- struct sdw_stream_instance *sdw_ins;
- struct snd_soc_pcm_runtime *prtd;
- u32 stream_id;
- u32 sdw_dma_reg;
- u32 sdw_dma_en_stat_reg;
- u32 sdw_dma_stat;
- u32 val;
- int timeout = 0;
- sdw_ins = stream->runtime->private_data;
- prtd = stream->private_data;
- stream_id = sdw_ins->stream_id;
- switch (stream_id) {
- case ACP_SDW_AUDIO_TX:
sdw_dma_reg = ACP_SW_AUDIO_TX_EN;
sdw_dma_en_stat_reg = ACP_SW_AUDIO_TX_EN_STATUS;
break;
Not super urgent but if you're respinning then it looks like the register selection here is the same in _dma_stop() so they could be shared. Indeed it generally feels like it might be nicer to have a table of structs listing the registers needed per stream so all these switch statements can be more like
sdw_dma_reg = stream_registers[sdw_ins->stream_id];
That'd make each function smaller and I'd expect it'd be a bit easier to add new streams for new hardware that way.
will fix it in next version.
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add Soundwire DMA driver dma ops for Pink Sardine platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
sound/soc/amd/ps/acp63.h | 61 ++++ sound/soc/amd/ps/ps-sdw-dma.c | 531 ++++++++++++++++++++++++++++++++++ 2 files changed, 592 insertions(+)
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index b462320fdf2a..8963cfb6120d 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -67,6 +67,38 @@ #define ACP_SDW0_IRQ_MASK 21 #define ACP_SDW1_IRQ_MASK 2 #define ACP_ERROR_IRQ_MASK 29 +#define ACP_AUDIO_TX_THRESHOLD 28 +#define ACP_BT_TX_THRESHOLD 26 +#define ACP_HS_TX_THRESHOLD 24 +#define ACP_AUDIO_RX_THRESHOLD 27 +#define ACP_BT_RX_THRESHOLD 25 +#define ACP_HS_RX_THRESHOLD 23 +#define ACP_P1_BT_TX_THRESHOLD 6 +#define ACP_P1_BT_RX_THRESHOLD 5 +#define ACP_SDW_DMA_IRQ_MASK 0x1F800000 +#define ACP_P1_SDW_DMA_IRQ_MASK 0x60 +#define ACP63_SDW_MAX_STREAMS 8
+#define ACP_DELAY_US 5 +#define SDW_MEM_WINDOW_START 0x4800000 +#define ACP_SDW_SRAM_PTE_OFFSET 0x03800400 +#define SDW_PTE_OFFSET 0x400 +#define SDW_FIFO_SIZE 0x100 +#define SDW_DMA_SIZE 0x40 +#define ACP_SDW_FIFO_OFFSET 0x100 +#define ACP_SDW_RING_BUFF_ADDR_OFFSET (128 * 1024)
+#define SDW_PLAYBACK_MIN_NUM_PERIODS 2 +#define SDW_PLAYBACK_MAX_NUM_PERIODS 8 +#define SDW_PLAYBACK_MAX_PERIOD_SIZE 8192 +#define SDW_PLAYBACK_MIN_PERIOD_SIZE 1024 +#define SDW_CAPTURE_MIN_NUM_PERIODS 2 +#define SDW_CAPTURE_MAX_NUM_PERIODS 8 +#define SDW_CAPTURE_MAX_PERIOD_SIZE 8192 +#define SDW_CAPTURE_MIN_PERIOD_SIZE 1024
+#define SDW_MAX_BUFFER (SDW_PLAYBACK_MAX_PERIOD_SIZE * SDW_PLAYBACK_MAX_NUM_PERIODS) +#define SDW_MIN_BUFFER SDW_MAX_BUFFER
enum acp_config { ACP_CONFIG_0 = 0, @@ -93,6 +125,17 @@ enum acp_pdev_mask { ACP63_SDW_PDM_DEV_MASK, };
+enum channel_type {
- ACP_SDW_AUDIO_TX = 0,
- ACP_SDW_BT_TX,
- ACP_SDW_HS_TX,
- ACP_SDW_AUDIO_RX,
- ACP_SDW_BT_RX,
- ACP_SDW_HS_RX,
- ACP_SDW1_BT_TX,
- ACP_SDW1_BT_RX,
+};
this was defined in another patch already?
struct pdm_stream_instance { u16 num_pages; u16 channels; @@ -139,4 +182,22 @@ struct acp63_dev_data { struct sdw_dma_dev_data { void __iomem *acp_base; struct mutex *acp_lock;
- struct snd_pcm_substream *sdw_stream[ACP63_SDW_MAX_STREAMS];
+};
+struct sdw_stream_instance {
sdw_stream is already a well-defined concept. Please use sdw_dma_stream or something less confusing naming-wise.
- u16 num_pages;
- u16 channels;
- u32 stream_id;
- dma_addr_t dma_addr;
- u64 bytescount;
- void __iomem *acp_base;
+};
+union acp_sdw_dma_count {
- struct {
- u32 low;
- u32 high;
- } bcount;
- u64 bytescount;
}; diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index 388a4b7df715..e94f76053c66 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -12,12 +12,543 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dai.h> +#include <linux/soundwire/sdw_amd.h> #include "acp63.h"
#define DRV_NAME "amd_ps_sdw_dma"
+static const struct snd_pcm_hardware acp63_sdw_hardware_playback = {
- .info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
- .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000,
- .rate_min = 48000,
- .rate_max = 48000,
is this really limited to 2ch 48kHz? This doesn't align with the references to Bluetooth above?
- .buffer_bytes_max = SDW_PLAYBACK_MAX_NUM_PERIODS * SDW_PLAYBACK_MAX_PERIOD_SIZE,
- .period_bytes_min = SDW_PLAYBACK_MIN_PERIOD_SIZE,
- .period_bytes_max = SDW_PLAYBACK_MAX_PERIOD_SIZE,
- .periods_min = SDW_PLAYBACK_MIN_NUM_PERIODS,
- .periods_max = SDW_PLAYBACK_MAX_NUM_PERIODS,
+};
+static const struct snd_pcm_hardware acp63_sdw_hardware_capture = {
- .info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
- .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000,
- .rate_min = 48000,
- .rate_max = 48000,
same here?
- .buffer_bytes_max = SDW_CAPTURE_MAX_NUM_PERIODS * SDW_CAPTURE_MAX_PERIOD_SIZE,
- .period_bytes_min = SDW_CAPTURE_MIN_PERIOD_SIZE,
- .period_bytes_max = SDW_CAPTURE_MAX_PERIOD_SIZE,
- .periods_min = SDW_CAPTURE_MIN_NUM_PERIODS,
- .periods_max = SDW_CAPTURE_MAX_NUM_PERIODS,
+};
+static void acp63_config_dma(struct sdw_stream_instance *sdw_ins, u32 stream_id) +{
- u16 page_idx;
- u32 low, high, val;
- dma_addr_t addr;
- addr = sdw_ins->dma_addr;
- val = SDW_PTE_OFFSET + (stream_id * 256);
- /* Group Enable */
- acp63_writel(ACP_SDW_SRAM_PTE_OFFSET | BIT(31), sdw_ins->acp_base +
ACPAXI2AXI_ATU_BASE_ADDR_GRP_2);
- acp63_writel(PAGE_SIZE_4K_ENABLE, sdw_ins->acp_base +
ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2);
- for (page_idx = 0; page_idx < sdw_ins->num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
acp63_writel(low, sdw_ins->acp_base + ACP_SCRATCH_REG_0 + val);
high |= BIT(31);
acp63_writel(high, sdw_ins->acp_base + ACP_SCRATCH_REG_0 + val + 4);
val += 8;
addr += PAGE_SIZE;
- }
- /*cache Invalidation added for Testing */
/* cache
- acp63_writel(0x1, sdw_ins->acp_base + ACPAXI2AXI_ATU_CTRL);
+}
+static int acp63_sdw_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
- struct sdw_stream_instance *sdw_stream_data;
- struct sdw_dma_dev_data *sdw_data;
- u32 period_bytes;
- u32 water_mark_size_reg;
- u32 irq_mask, ext_intr_ctrl;
- u64 size;
- u32 stream_id;
- u32 acp_ext_intr_cntl_reg;
- int ret;
- stream_id = 0;
useless initialization...
- sdw_data = dev_get_drvdata(component->dev);
- sdw_stream_data = substream->runtime->private_data;
- if (!sdw_stream_data)
return -EINVAL;
- stream_id = sdw_stream_data->stream_id;
... overriden here
- sdw_data->sdw_stream[stream_id] = substream;
- size = params_buffer_bytes(params);
- period_bytes = params_period_bytes(params);
- sdw_stream_data->dma_addr = substream->runtime->dma_addr;
- sdw_stream_data->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
- acp63_config_dma(sdw_stream_data, stream_id);
- ret = acp63_configure_sdw_ringbuffer(sdw_stream_data->acp_base, stream_id, size);
- if (ret) {
dev_err(component->dev, "Invalid channel type\n");
return -EINVAL;
- }
- switch (stream_id) {
- case ACP_SDW_AUDIO_TX:
water_mark_size_reg = ACP_AUDIO_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_AUDIO_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
so there's ONE resource to deal with external codecs? How does this work if you have a headset codec and an amplifier?
- case ACP_SDW_BT_TX:
water_mark_size_reg = ACP_BT_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_BT_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW_HS_TX:
water_mark_size_reg = ACP_HS_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_HS_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW1_BT_TX:
water_mark_size_reg = ACP_P1_BT_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_P1_BT_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1;
break;
- case ACP_SDW_AUDIO_RX:
water_mark_size_reg = ACP_AUDIO_RX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_AUDIO_RX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW_BT_RX:
water_mark_size_reg = ACP_BT_RX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_BT_RX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW_HS_RX:
water_mark_size_reg = ACP_HS_RX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_HS_RX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW1_BT_RX:
water_mark_size_reg = ACP_P1_BT_RX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_P1_BT_RX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1;
break;
- default:
dev_err(component->dev, "%s: Invalid channel type\n", __func__);
return -EINVAL;
- }
- ext_intr_ctrl = acp63_readl(sdw_stream_data->acp_base + acp_ext_intr_cntl_reg);
- ext_intr_ctrl |= irq_mask;
- acp63_writel(ext_intr_ctrl, sdw_stream_data->acp_base + acp_ext_intr_cntl_reg);
- acp63_writel(period_bytes, sdw_stream_data->acp_base + water_mark_size_reg);
- return 0;
+}
On 11/01/23 21:04, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add Soundwire DMA driver dma ops for Pink Sardine platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
sound/soc/amd/ps/acp63.h | 61 ++++ sound/soc/amd/ps/ps-sdw-dma.c | 531 ++++++++++++++++++++++++++++++++++ 2 files changed, 592 insertions(+)
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index b462320fdf2a..8963cfb6120d 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -67,6 +67,38 @@ #define ACP_SDW0_IRQ_MASK 21 #define ACP_SDW1_IRQ_MASK 2 #define ACP_ERROR_IRQ_MASK 29 +#define ACP_AUDIO_TX_THRESHOLD 28 +#define ACP_BT_TX_THRESHOLD 26 +#define ACP_HS_TX_THRESHOLD 24 +#define ACP_AUDIO_RX_THRESHOLD 27 +#define ACP_BT_RX_THRESHOLD 25 +#define ACP_HS_RX_THRESHOLD 23 +#define ACP_P1_BT_TX_THRESHOLD 6 +#define ACP_P1_BT_RX_THRESHOLD 5 +#define ACP_SDW_DMA_IRQ_MASK 0x1F800000 +#define ACP_P1_SDW_DMA_IRQ_MASK 0x60 +#define ACP63_SDW_MAX_STREAMS 8
+#define ACP_DELAY_US 5 +#define SDW_MEM_WINDOW_START 0x4800000 +#define ACP_SDW_SRAM_PTE_OFFSET 0x03800400 +#define SDW_PTE_OFFSET 0x400 +#define SDW_FIFO_SIZE 0x100 +#define SDW_DMA_SIZE 0x40 +#define ACP_SDW_FIFO_OFFSET 0x100 +#define ACP_SDW_RING_BUFF_ADDR_OFFSET (128 * 1024)
+#define SDW_PLAYBACK_MIN_NUM_PERIODS 2 +#define SDW_PLAYBACK_MAX_NUM_PERIODS 8 +#define SDW_PLAYBACK_MAX_PERIOD_SIZE 8192 +#define SDW_PLAYBACK_MIN_PERIOD_SIZE 1024 +#define SDW_CAPTURE_MIN_NUM_PERIODS 2 +#define SDW_CAPTURE_MAX_NUM_PERIODS 8 +#define SDW_CAPTURE_MAX_PERIOD_SIZE 8192 +#define SDW_CAPTURE_MIN_PERIOD_SIZE 1024
+#define SDW_MAX_BUFFER (SDW_PLAYBACK_MAX_PERIOD_SIZE * SDW_PLAYBACK_MAX_NUM_PERIODS) +#define SDW_MIN_BUFFER SDW_MAX_BUFFER
enum acp_config { ACP_CONFIG_0 = 0, @@ -93,6 +125,17 @@ enum acp_pdev_mask { ACP63_SDW_PDM_DEV_MASK, };
+enum channel_type {
- ACP_SDW_AUDIO_TX = 0,
- ACP_SDW_BT_TX,
- ACP_SDW_HS_TX,
- ACP_SDW_AUDIO_RX,
- ACP_SDW_BT_RX,
- ACP_SDW_HS_RX,
- ACP_SDW1_BT_TX,
- ACP_SDW1_BT_RX,
+};
this was defined in another patch already?
will drop this change.
struct pdm_stream_instance { u16 num_pages; u16 channels; @@ -139,4 +182,22 @@ struct acp63_dev_data { struct sdw_dma_dev_data { void __iomem *acp_base; struct mutex *acp_lock;
- struct snd_pcm_substream *sdw_stream[ACP63_SDW_MAX_STREAMS];
+};
+struct sdw_stream_instance {
sdw_stream is already a well-defined concept. Please use sdw_dma_stream or something less confusing naming-wise.
will rename it.
- u16 num_pages;
- u16 channels;
- u32 stream_id;
- dma_addr_t dma_addr;
- u64 bytescount;
- void __iomem *acp_base;
+};
+union acp_sdw_dma_count {
- struct {
- u32 low;
- u32 high;
- } bcount;
- u64 bytescount;
}; diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index 388a4b7df715..e94f76053c66 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -12,12 +12,543 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dai.h> +#include <linux/soundwire/sdw_amd.h> #include "acp63.h"
#define DRV_NAME "amd_ps_sdw_dma"
+static const struct snd_pcm_hardware acp63_sdw_hardware_playback = {
- .info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
- .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000,
- .rate_min = 48000,
- .rate_max = 48000,
is this really limited to 2ch 48kHz? This doesn't align with the references to Bluetooth above?
As of now we are enabling solution for 48khz, 2Ch, 16bit. We will expand the coverage in the future.
- .buffer_bytes_max = SDW_PLAYBACK_MAX_NUM_PERIODS * SDW_PLAYBACK_MAX_PERIOD_SIZE,
- .period_bytes_min = SDW_PLAYBACK_MIN_PERIOD_SIZE,
- .period_bytes_max = SDW_PLAYBACK_MAX_PERIOD_SIZE,
- .periods_min = SDW_PLAYBACK_MIN_NUM_PERIODS,
- .periods_max = SDW_PLAYBACK_MAX_NUM_PERIODS,
+};
+static const struct snd_pcm_hardware acp63_sdw_hardware_capture = {
- .info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
- .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000,
- .rate_min = 48000,
- .rate_max = 48000,
same here?
- .buffer_bytes_max = SDW_CAPTURE_MAX_NUM_PERIODS * SDW_CAPTURE_MAX_PERIOD_SIZE,
- .period_bytes_min = SDW_CAPTURE_MIN_PERIOD_SIZE,
- .period_bytes_max = SDW_CAPTURE_MAX_PERIOD_SIZE,
- .periods_min = SDW_CAPTURE_MIN_NUM_PERIODS,
- .periods_max = SDW_CAPTURE_MAX_NUM_PERIODS,
+};
+static void acp63_config_dma(struct sdw_stream_instance *sdw_ins, u32 stream_id) +{
- u16 page_idx;
- u32 low, high, val;
- dma_addr_t addr;
- addr = sdw_ins->dma_addr;
- val = SDW_PTE_OFFSET + (stream_id * 256);
- /* Group Enable */
- acp63_writel(ACP_SDW_SRAM_PTE_OFFSET | BIT(31), sdw_ins->acp_base +
ACPAXI2AXI_ATU_BASE_ADDR_GRP_2);
- acp63_writel(PAGE_SIZE_4K_ENABLE, sdw_ins->acp_base +
ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2);
- for (page_idx = 0; page_idx < sdw_ins->num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
acp63_writel(low, sdw_ins->acp_base + ACP_SCRATCH_REG_0 + val);
high |= BIT(31);
acp63_writel(high, sdw_ins->acp_base + ACP_SCRATCH_REG_0 + val + 4);
val += 8;
addr += PAGE_SIZE;
- }
- /*cache Invalidation added for Testing */
Below register programming is required. We will remove the comment.
/* cache
- acp63_writel(0x1, sdw_ins->acp_base + ACPAXI2AXI_ATU_CTRL);
+} +static int acp63_sdw_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
+{
- struct sdw_stream_instance *sdw_stream_data;
- struct sdw_dma_dev_data *sdw_data;
- u32 period_bytes;
- u32 water_mark_size_reg;
- u32 irq_mask, ext_intr_ctrl;
- u64 size;
- u32 stream_id;
- u32 acp_ext_intr_cntl_reg;
- int ret;
- stream_id = 0;
useless initialization...
Will remove it.
- sdw_data = dev_get_drvdata(component->dev);
- sdw_stream_data = substream->runtime->private_data;
- if (!sdw_stream_data)
return -EINVAL;
- stream_id = sdw_stream_data->stream_id;
... overriden here
- sdw_data->sdw_stream[stream_id] = substream;
- size = params_buffer_bytes(params);
- period_bytes = params_period_bytes(params);
- sdw_stream_data->dma_addr = substream->runtime->dma_addr;
- sdw_stream_data->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
- acp63_config_dma(sdw_stream_data, stream_id);
- ret = acp63_configure_sdw_ringbuffer(sdw_stream_data->acp_base, stream_id, size);
- if (ret) {
dev_err(component->dev, "Invalid channel type\n");
return -EINVAL;
- }
- switch (stream_id) {
- case ACP_SDW_AUDIO_TX:
water_mark_size_reg = ACP_AUDIO_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_AUDIO_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
so there's ONE resource to deal with external codecs? How does this work if you have a headset codec and an amplifier?
Are you referring to playing a same stream over headset codec and amplifier? It's all about channel selection from DMA perspective. We have tested speaker aggregation and headset playback use cases.
- case ACP_SDW_BT_TX:
water_mark_size_reg = ACP_BT_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_BT_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW_HS_TX:
water_mark_size_reg = ACP_HS_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_HS_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW1_BT_TX:
water_mark_size_reg = ACP_P1_BT_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_P1_BT_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1;
break;
- case ACP_SDW_AUDIO_RX:
water_mark_size_reg = ACP_AUDIO_RX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_AUDIO_RX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW_BT_RX:
water_mark_size_reg = ACP_BT_RX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_BT_RX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW_HS_RX:
water_mark_size_reg = ACP_HS_RX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_HS_RX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
- case ACP_SDW1_BT_RX:
water_mark_size_reg = ACP_P1_BT_RX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_P1_BT_RX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1;
break;
- default:
dev_err(component->dev, "%s: Invalid channel type\n", __func__);
return -EINVAL;
- }
- ext_intr_ctrl = acp63_readl(sdw_stream_data->acp_base + acp_ext_intr_cntl_reg);
- ext_intr_ctrl |= irq_mask;
- acp63_writel(ext_intr_ctrl, sdw_stream_data->acp_base + acp_ext_intr_cntl_reg);
- acp63_writel(period_bytes, sdw_stream_data->acp_base + water_mark_size_reg);
- return 0;
+}
- sdw_data->sdw_stream[stream_id] = substream;
- size = params_buffer_bytes(params);
- period_bytes = params_period_bytes(params);
- sdw_stream_data->dma_addr = substream->runtime->dma_addr;
- sdw_stream_data->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
- acp63_config_dma(sdw_stream_data, stream_id);
- ret = acp63_configure_sdw_ringbuffer(sdw_stream_data->acp_base, stream_id, size);
- if (ret) {
dev_err(component->dev, "Invalid channel type\n");
return -EINVAL;
- }
- switch (stream_id) {
- case ACP_SDW_AUDIO_TX:
water_mark_size_reg = ACP_AUDIO_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_AUDIO_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
so there's ONE resource to deal with external codecs? How does this work if you have a headset codec and an amplifier?
Are you referring to playing a same stream over headset codec and amplifier? It's all about channel selection from DMA perspective. We have tested speaker aggregation and headset playback use cases.
No, I wasn't asking about playing the same content to different sinks.
I was referring to playing/recording different content to/from different devices.
Even when interfacing with a single device, there are interesting topologies in the SDCA spec that would require multiple DMA transfers conveying unrelated content (or processed content from the same source).
On 13/01/23 22:35, Pierre-Louis Bossart wrote:
- sdw_data->sdw_stream[stream_id] = substream;
- size = params_buffer_bytes(params);
- period_bytes = params_period_bytes(params);
- sdw_stream_data->dma_addr = substream->runtime->dma_addr;
- sdw_stream_data->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
- acp63_config_dma(sdw_stream_data, stream_id);
- ret = acp63_configure_sdw_ringbuffer(sdw_stream_data->acp_base, stream_id, size);
- if (ret) {
dev_err(component->dev, "Invalid channel type\n");
return -EINVAL;
- }
- switch (stream_id) {
- case ACP_SDW_AUDIO_TX:
water_mark_size_reg = ACP_AUDIO_TX_INTR_WATERMARK_SIZE;
irq_mask = BIT(ACP_AUDIO_TX_THRESHOLD);
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
break;
so there's ONE resource to deal with external codecs? How does this work if you have a headset codec and an amplifier?
Are you referring to playing a same stream over headset codec and amplifier? It's all about channel selection from DMA perspective. We have tested speaker aggregation and headset playback use cases.
No, I wasn't asking about playing the same content to different sinks.
I was referring to playing/recording different content to/from different devices.
Yes , it's possible. We have tested parallel RX and TX streams. Please refer patch 2 review comments reply.
For ex, speaker playback we are using Audio_TX Channel whereas for Headset playback we are using Headset_Tx channel.
Even when interfacing with a single device, there are interesting topologies in the SDCA spec that would require multiple DMA transfers conveying unrelated content (or processed content from the same source).
Initialize workqueue for DMA interrupts handling. Whenever audio data equal to the Soundwire FIFO watermark level are produced/consumed, interrupt is generated. Acknowledge the interrupt and schedule the workqueue.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/acp63.h | 2 + sound/soc/amd/ps/pci-ps.c | 81 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 8963cfb6120d..833d0b5aa73d 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -167,9 +167,11 @@ struct acp63_dev_data { struct platform_device *pdev[ACP63_DEVS]; struct mutex acp_lock; /* protect shared registers */ struct fwnode_handle *sdw_fw_node; + struct work_struct acp_sdw_dma_work; u16 pdev_mask; u16 pdev_count; u16 pdm_dev_index; + u16 dma_intr_stat[ACP63_SDW_MAX_STREAMS]; u8 sdw_master_count; u16 sdw0_dev_index; u16 sdw1_dev_index; diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 0fbe5e27f3fb..5b82ee8e3ad8 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -113,14 +113,37 @@ static int acp63_deinit(void __iomem *acp_base, struct device *dev) return 0; }
+static void acp63_sdw_dma_workthread(struct work_struct *work) +{ + struct acp63_dev_data *adata = container_of(work, struct acp63_dev_data, + acp_sdw_dma_work); + struct sdw_dma_dev_data *sdw_dma_data; + u32 stream_index; + u16 pdev_index; + + pdev_index = adata->sdw_dma_dev_index; + sdw_dma_data = dev_get_drvdata(&adata->pdev[pdev_index]->dev); + + for (stream_index = 0; stream_index < ACP63_SDW_MAX_STREAMS; stream_index++) { + if (adata->dma_intr_stat[stream_index]) { + if (sdw_dma_data->sdw_stream[stream_index]) + snd_pcm_period_elapsed(sdw_dma_data->sdw_stream[stream_index]); + adata->dma_intr_stat[stream_index] = 0; + } + } +} + static irqreturn_t acp63_irq_handler(int irq, void *dev_id) { struct acp63_dev_data *adata; struct pdm_dev_data *ps_pdm_data; struct amd_sdwc_ctrl *ctrl; u32 ext_intr_stat, ext_intr_stat1; + u32 stream_id = 0; u16 irq_flag = 0; + u16 sdw_dma_irq_flag = 0; u16 pdev_index; + u16 index;
adata = dev_id; if (!adata) @@ -159,7 +182,58 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) snd_pcm_period_elapsed(ps_pdm_data->capture_stream); irq_flag = 1; } - if (irq_flag) + if (ext_intr_stat & ACP_SDW_DMA_IRQ_MASK) { + for (index = ACP_HS_RX_THRESHOLD; index <= ACP_AUDIO_TX_THRESHOLD; index++) { + if (ext_intr_stat & BIT(index)) { + acp63_writel(BIT(index), + adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + switch (index) { + case ACP_AUDIO_TX_THRESHOLD: + stream_id = ACP_SDW_AUDIO_TX; + break; + case ACP_BT_TX_THRESHOLD: + stream_id = ACP_SDW_BT_TX; + break; + case ACP_HS_TX_THRESHOLD: + stream_id = ACP_SDW_HS_TX; + break; + case ACP_AUDIO_RX_THRESHOLD: + stream_id = ACP_SDW_AUDIO_RX; + break; + case ACP_BT_RX_THRESHOLD: + stream_id = ACP_SDW_BT_RX; + break; + case ACP_HS_RX_THRESHOLD: + stream_id = ACP_SDW_HS_RX; + break; + } + + adata->dma_intr_stat[stream_id] = 1; + sdw_dma_irq_flag = 1; + } + } + } + + /* SDW1 BT RX */ + if (ext_intr_stat1 & BIT(ACP_P1_BT_RX_THRESHOLD)) { + acp63_writel(BIT(ACP_P1_BT_RX_THRESHOLD), + adata->acp63_base + ACP_EXTERNAL_INTR_STAT1); + adata->dma_intr_stat[ACP_SDW1_BT_RX] = 1; + sdw_dma_irq_flag = 1; + } + + /* SDW1 BT TX*/ + if (ext_intr_stat1 & BIT(ACP_P1_BT_TX_THRESHOLD)) { + acp63_writel(BIT(ACP_P1_BT_TX_THRESHOLD), + adata->acp63_base + ACP_EXTERNAL_INTR_STAT1); + adata->dma_intr_stat[ACP_SDW1_BT_TX] = 1; + sdw_dma_irq_flag = 1; + } + + if (sdw_dma_irq_flag) + schedule_work(&adata->acp_sdw_dma_work); + + if (irq_flag || sdw_dma_irq_flag) return IRQ_HANDLED; else return IRQ_NONE; @@ -240,6 +314,7 @@ static int get_acp63_device_config(u32 config, struct pci_dev *pci, struct acp63 if (sdw_dev) { is_sdw_dev = true; acp_data->sdw_fw_node = acpi_fwnode_handle(sdw_dev); + INIT_WORK(&acp_data->acp_sdw_dma_work, acp63_sdw_dma_workthread); ret = sdw_amd_scan_controller(dev); if (ret) return ret; @@ -612,6 +687,10 @@ static void snd_acp63_remove(struct pci_dev *pci) int ret, index;
adata = pci_get_drvdata(pci); + + if (adata->pdev_mask & ACP63_SDW_DEV_MASK) + cancel_work_sync(&adata->acp_sdw_dma_work); + for (index = 0; index < adata->pdev_count; index++) platform_device_unregister(adata->pdev[index]); ret = acp63_deinit(adata->acp63_base, &pci->dev);
@@ -167,9 +167,11 @@ struct acp63_dev_data { struct platform_device *pdev[ACP63_DEVS]; struct mutex acp_lock; /* protect shared registers */ struct fwnode_handle *sdw_fw_node;
- struct work_struct acp_sdw_dma_work; u16 pdev_mask; u16 pdev_count; u16 pdm_dev_index;
- u16 dma_intr_stat[ACP63_SDW_MAX_STREAMS];
streams and DMAs are different things in SoundWire. You can have a 1:N mapping.
u8 sdw_master_count; u16 sdw0_dev_index; u16 sdw1_dev_index; diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 0fbe5e27f3fb..5b82ee8e3ad8 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -113,14 +113,37 @@ static int acp63_deinit(void __iomem *acp_base, struct device *dev) return 0; }
+static void acp63_sdw_dma_workthread(struct work_struct *work) +{
- struct acp63_dev_data *adata = container_of(work, struct acp63_dev_data,
acp_sdw_dma_work);
- struct sdw_dma_dev_data *sdw_dma_data;
- u32 stream_index;
- u16 pdev_index;
- pdev_index = adata->sdw_dma_dev_index;
- sdw_dma_data = dev_get_drvdata(&adata->pdev[pdev_index]->dev);
- for (stream_index = 0; stream_index < ACP63_SDW_MAX_STREAMS; stream_index++) {
if (adata->dma_intr_stat[stream_index]) {
if (sdw_dma_data->sdw_stream[stream_index])
snd_pcm_period_elapsed(sdw_dma_data->sdw_stream[stream_index]);
is there a reason why you do this in a work thread?
IIRC we did this in SOF because of an issue where during an xrun a stop IPC would be sent while we were dealing with an IPC.
I don't quite see why it's needed for a DMA?
What am I missing?
adata->dma_intr_stat[stream_index] = 0;
}
- }
+}
On 11/01/23 21:08, Pierre-Louis Bossart wrote:
@@ -167,9 +167,11 @@ struct acp63_dev_data { struct platform_device *pdev[ACP63_DEVS]; struct mutex acp_lock; /* protect shared registers */ struct fwnode_handle *sdw_fw_node;
- struct work_struct acp_sdw_dma_work; u16 pdev_mask; u16 pdev_count; u16 pdm_dev_index;
- u16 dma_intr_stat[ACP63_SDW_MAX_STREAMS];
streams and DMAs are different things in SoundWire. You can have a 1:N mapping.
u8 sdw_master_count; u16 sdw0_dev_index; u16 sdw1_dev_index; diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 0fbe5e27f3fb..5b82ee8e3ad8 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -113,14 +113,37 @@ static int acp63_deinit(void __iomem *acp_base, struct device *dev) return 0; }
+static void acp63_sdw_dma_workthread(struct work_struct *work) +{
- struct acp63_dev_data *adata = container_of(work, struct acp63_dev_data,
acp_sdw_dma_work);
- struct sdw_dma_dev_data *sdw_dma_data;
- u32 stream_index;
- u16 pdev_index;
- pdev_index = adata->sdw_dma_dev_index;
- sdw_dma_data = dev_get_drvdata(&adata->pdev[pdev_index]->dev);
- for (stream_index = 0; stream_index < ACP63_SDW_MAX_STREAMS; stream_index++) {
if (adata->dma_intr_stat[stream_index]) {
if (sdw_dma_data->sdw_stream[stream_index])
snd_pcm_period_elapsed(sdw_dma_data->sdw_stream[stream_index]);
is there a reason why you do this in a work thread?
IIRC we did this in SOF because of an issue where during an xrun a stop IPC would be sent while we were dealing with an IPC.
I don't quite see why it's needed for a DMA?
What am I missing?
Initially, we have used in atomic context. We have seen issues during stream closure, in interrupt context , handling period_elapsed causing sleep in atomic context. To avoid that , we have declared dai_link as non-atomic and moved period_elapsed code to work queue.
adata->dma_intr_stat[stream_index] = 0;
}
- }
+}
Enable Soundwire DMA driver build for PS platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/Makefile | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/sound/soc/amd/ps/Makefile b/sound/soc/amd/ps/Makefile index 383973a12f6a..a3da8670e3e2 100644 --- a/sound/soc/amd/ps/Makefile +++ b/sound/soc/amd/ps/Makefile @@ -3,7 +3,9 @@ snd-pci-ps-objs := pci-ps.o snd-ps-pdm-dma-objs := ps-pdm-dma.o snd-soc-ps-mach-objs := ps-mach.o +snd-ps-sdw-dma-objs := ps-sdw-dma.o
obj-$(CONFIG_SND_SOC_AMD_PS) += snd-pci-ps.o obj-$(CONFIG_SND_SOC_AMD_PS) += snd-ps-pdm-dma.o +obj-$(CONFIG_SND_SOC_AMD_PS) += snd-ps-sdw-dma.o obj-$(CONFIG_SND_SOC_AMD_PS_MACH) += snd-soc-ps-mach.o
Update comments in Kconfig file for PS platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig index c88ebd84bdd5..50bcec55a275 100644 --- a/sound/soc/amd/Kconfig +++ b/sound/soc/amd/Kconfig @@ -134,7 +134,8 @@ config SND_SOC_AMD_PS help This option enables Audio Coprocessor i.e ACP v6.3 support on AMD Pink sardine platform. By enabling this flag build will be - triggered for ACP PCI driver, ACP PDM DMA driver. + triggered for ACP PCI driver, ACP PDM DMA driver, ACP SDW DMA + driver. Say m if you have such a device. If unsure select "N".
AMD Soundwire controller supports different power modes. In case of Soundwire Power off Mode, ACP pci parent driver should invoke acp de-init and init sequence during suspend/resume callbacks.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/pci-ps.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 5b82ee8e3ad8..7e152241c155 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -655,10 +655,15 @@ static int snd_acp63_probe(struct pci_dev *pci, static int __maybe_unused snd_acp63_suspend(struct device *dev) { struct acp63_dev_data *adata; - int ret; + int ret = 0;
adata = dev_get_drvdata(dev); - ret = acp63_deinit(adata->acp63_base, dev); + if (adata->pdev_mask & ACP63_SDW_DEV_MASK) { + if (adata->acp_sdw_power_off) + ret = acp63_deinit(adata->acp63_base, dev); + } else { + ret = acp63_deinit(adata->acp63_base, dev); + } if (ret) dev_err(dev, "ACP de-init failed\n"); return ret; @@ -667,10 +672,15 @@ static int __maybe_unused snd_acp63_suspend(struct device *dev) static int __maybe_unused snd_acp63_resume(struct device *dev) { struct acp63_dev_data *adata; - int ret; + int ret = 0;
adata = dev_get_drvdata(dev); - ret = acp63_init(adata->acp63_base, dev); + if (adata->pdev_mask & ACP63_SDW_DEV_MASK) { + if (adata->acp_sdw_power_off) + ret = acp63_init(adata->acp63_base, dev); + } else { + ret = acp63_init(adata->acp63_base, dev); + } if (ret) dev_err(dev, "ACP init failed\n"); return ret;
Add support for runtime pm ops for soundwire dma driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/ps-sdw-dma.c | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+)
diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index e94f76053c66..960c0bc5e848 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -12,6 +12,7 @@ #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dai.h> +#include <linux/pm_runtime.h> #include <linux/soundwire/sdw_amd.h> #include "acp63.h"
@@ -56,6 +57,30 @@ static const struct snd_pcm_hardware acp63_sdw_hardware_capture = { .periods_max = SDW_CAPTURE_MAX_NUM_PERIODS, };
+static void acp63_enable_disable_sdw_dma_interrupts(void __iomem *acp_base, bool enable) +{ + u32 ext_intr_cntl, ext_intr_cntl1, irq_mask, irq_mask1; + + irq_mask = ACP_SDW_DMA_IRQ_MASK; + irq_mask1 = ACP_P1_SDW_DMA_IRQ_MASK; + + if (enable) { + ext_intr_cntl = acp63_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_intr_cntl |= irq_mask; + acp63_writel(ext_intr_cntl, acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_intr_cntl1 = acp63_readl(acp_base + ACP_EXTERNAL_INTR_CNTL1); + ext_intr_cntl1 |= irq_mask1; + acp63_writel(ext_intr_cntl1, acp_base + ACP_EXTERNAL_INTR_CNTL1); + } else { + ext_intr_cntl = acp63_readl(acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_intr_cntl &= ~irq_mask; + acp63_writel(ext_intr_cntl, acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_intr_cntl1 = acp63_readl(acp_base + ACP_EXTERNAL_INTR_CNTL1); + ext_intr_cntl1 &= ~irq_mask1; + acp63_writel(ext_intr_cntl1, acp_base + ACP_EXTERNAL_INTR_CNTL1); + } +} + static void acp63_config_dma(struct sdw_stream_instance *sdw_ins, u32 stream_id) { u16 page_idx; @@ -585,13 +610,54 @@ static int acp63_sdw_platform_probe(struct platform_device *pdev)
return -ENODEV; } + pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_allow(&pdev->dev); + return 0; +} + +static int acp63_sdw_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int __maybe_unused acp63_sdw_pcm_runtime_suspend(struct device *dev) +{ + struct sdw_dma_dev_data *sdw_dma_data; + + sdw_dma_data = dev_get_drvdata(dev); + mutex_lock(sdw_dma_data->acp_lock); + acp63_enable_disable_sdw_dma_interrupts(sdw_dma_data->acp_base, false); + mutex_unlock(sdw_dma_data->acp_lock); + + return 0; +} + +static int __maybe_unused acp63_sdw_pcm_runtime_resume(struct device *dev) +{ + struct sdw_dma_dev_data *sdw_dma_data; + + sdw_dma_data = dev_get_drvdata(dev); + mutex_lock(sdw_dma_data->acp_lock); + acp63_enable_disable_sdw_dma_interrupts(sdw_dma_data->acp_base, true); + mutex_unlock(sdw_dma_data->acp_lock); + return 0; }
+static const struct dev_pm_ops acp63_pm_ops = { + SET_RUNTIME_PM_OPS(acp63_sdw_pcm_runtime_suspend, + acp63_sdw_pcm_runtime_resume, NULL) +}; + static struct platform_driver acp63_sdw_dma_driver = { .probe = acp63_sdw_platform_probe, + .remove = acp63_sdw_platform_remove, .driver = { .name = "amd_ps_sdw_dma", + .pm = &acp63_pm_ops, }, };
Add support for runtime pm ops for AMD master driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com --- drivers/soundwire/amd_master.c | 205 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 3 + include/linux/soundwire/sdw_amd.h | 1 + 3 files changed, 209 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index c7063b8bdd7b..d2d7f07de202 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -15,6 +15,7 @@ #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw_amd.h> +#include <linux/pm_runtime.h> #include <linux/wait.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -290,6 +291,17 @@ static int amd_disable_sdw_interrupts(struct amd_sdwc_ctrl *ctrl) return 0; }
+static int amd_deinit_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{ + int ret; + + ret = amd_disable_sdw_interrupts(ctrl); + if (ret) + return ret; + ret = amd_disable_sdw_controller(ctrl); + return ret; +} + static int amd_sdwc_set_frameshape(struct amd_sdwc_ctrl *ctrl, u32 rows, u32 cols) { u32 sdw_rows, sdw_cols, frame_size; @@ -1387,6 +1399,12 @@ static int amd_sdwc_probe(struct platform_device *pdev) INIT_WORK(&ctrl->amd_sdw_work, amd_sdwc_update_slave_status_work); INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); schedule_work(&ctrl->probe_work); + /* Enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, AMD_SDW_MASTER_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); return 0; }
@@ -1398,14 +1416,201 @@ static int amd_sdwc_remove(struct platform_device *pdev) amd_disable_sdw_interrupts(ctrl); sdw_bus_master_delete(&ctrl->bus); ret = amd_disable_sdw_controller(ctrl); + pm_runtime_disable(&pdev->dev); return ret; }
+static int amd_sdwc_clock_stop(struct amd_sdwc_ctrl *ctrl) +{ + u32 clk_resume_ctrl_reg; + u32 wake_en_reg; + u32 val; + u32 retry_count = 0; + int ret; + + ret = sdw_bus_prep_clk_stop(&ctrl->bus); + if (ret < 0 && ret != -ENODATA) { + dev_err(ctrl->dev, "prepare clock stop failed %d", ret); + return ret; + } + ret = sdw_bus_clk_stop(&ctrl->bus); + if (ret < 0 && ret != -ENODATA) { + dev_err(ctrl->dev, "bus clock stop failed %d", ret); + return ret; + } + switch (ctrl->instance) { + case ACP_SDW0: + clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL; + wake_en_reg = ACP_SW_WAKE_EN; + break; + case ACP_SDW1: + clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL; + wake_en_reg = ACP_SW1_WAKE_EN; + break; + default: + return -EINVAL; + } + + do { + val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg); + if (val & AMD_SDW_CLK_STOP_DONE) { + ctrl->clk_stopped = true; + break; + } + } while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT); + + if (!ctrl->clk_stopped) { + dev_err(ctrl->dev, "SDW%x clock stop failed\n", ctrl->instance); + return -ETIMEDOUT; + } + + if (ctrl->wake_en_mask) + acp_reg_writel(0x01, ctrl->mmio + wake_en_reg); + + dev_dbg(ctrl->dev, "SDW%x clock stop successful\n", ctrl->instance); + return 0; +} + +static int amd_sdwc_clock_stop_exit(struct amd_sdwc_ctrl *ctrl) +{ + int ret; + u32 clk_resume_ctrl_reg; + u32 val = 0; + u32 retry_count = 0; + + switch (ctrl->instance) { + case ACP_SDW0: + clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL; + break; + case ACP_SDW1: + clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL; + break; + default: + return -EINVAL; + } + if (ctrl->clk_stopped) { + val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg); + val |= AMD_SDW_CLK_RESUME_REQ; + acp_reg_writel(val, ctrl->mmio + clk_resume_ctrl_reg); + do { + val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg); + if (val & AMD_SDW_CLK_RESUME_DONE) + break; + usleep_range(10, 100); + } while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT); + if (val & AMD_SDW_CLK_RESUME_DONE) { + acp_reg_writel(0, ctrl->mmio + clk_resume_ctrl_reg); + ret = sdw_bus_exit_clk_stop(&ctrl->bus); + if (ret < 0) + dev_err(ctrl->dev, "bus failed to exit clock stop %d\n", ret); + ctrl->clk_stopped = false; + } + } + if (ctrl->clk_stopped) { + dev_err(ctrl->dev, "SDW%x clock stop exit failed\n", ctrl->instance); + return -ETIMEDOUT; + } + + dev_dbg(ctrl->dev, "SDW%x clock stop exit successful\n", ctrl->instance); + + return 0; +} + +static int __maybe_unused amd_suspend_runtime(struct device *dev) +{ + struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev); + struct sdw_bus *bus = &ctrl->bus; + int ret; + + if (bus->prop.hw_disabled || !ctrl->startup_done) { + dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n", + bus->link_id); + return 0; + } + if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { + ret = amd_sdwc_clock_stop(ctrl); + if (ret) + return ret; + } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + ret = amd_sdwc_clock_stop(ctrl); + if (ret) + return ret; + ret = amd_deinit_sdw_controller(ctrl); + if (ret) + return ret; + } + return 0; +} + +static int __maybe_unused amd_resume_runtime(struct device *dev) +{ + struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev); + struct sdw_bus *bus = &ctrl->bus; + int ret; + u32 clk_resume_ctrl_reg; + u32 val = 0; + u32 retry_count = 0; + + if (bus->prop.hw_disabled || !ctrl->startup_done) { + dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n", + bus->link_id); + return 0; + } + + switch (ctrl->instance) { + case ACP_SDW0: + clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL; + break; + case ACP_SDW1: + clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL; + break; + default: + return -EINVAL; + } + + if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { + ret = amd_sdwc_clock_stop_exit(ctrl); + if (ret) + return ret; + } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg); + if (val) { + val |= AMD_SDW_CLK_RESUME_REQ; + acp_reg_writel(val, ctrl->mmio + clk_resume_ctrl_reg); + do { + val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg); + if (val & AMD_SDW_CLK_RESUME_DONE) + break; + usleep_range(10, 100); + } while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT); + if (val & AMD_SDW_CLK_RESUME_DONE) { + acp_reg_writel(0, ctrl->mmio + clk_resume_ctrl_reg); + ctrl->clk_stopped = false; + } + } + sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET); + amd_init_sdw_controller(ctrl); + amd_enable_sdw_interrupts(ctrl); + ret = amd_enable_sdw_controller(ctrl); + if (ret) + return ret; + ret = amd_sdwc_set_frameshape(ctrl, 50, 10); + if (ret) + return ret; + } + return 0; +} + +static const struct dev_pm_ops amd_pm = { + SET_RUNTIME_PM_OPS(amd_suspend_runtime, amd_resume_runtime, NULL) +}; + static struct platform_driver amd_sdwc_driver = { .probe = &amd_sdwc_probe, .remove = &amd_sdwc_remove, .driver = { .name = "amd_sdw_controller", + .pm = &amd_pm, } }; module_platform_driver(amd_sdwc_driver); diff --git a/drivers/soundwire/amd_master.h b/drivers/soundwire/amd_master.h index b43a5d6496cb..cc254255512b 100644 --- a/drivers/soundwire/amd_master.h +++ b/drivers/soundwire/amd_master.h @@ -237,6 +237,9 @@ #define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E #define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF #define AMD_SDW_PREQ_INTR_STAT BIT(19) +#define AMD_SDW_CLK_STOP_DONE 1 +#define AMD_SDW_CLK_RESUME_REQ 2 +#define AMD_SDW_CLK_RESUME_DONE 3
enum amd_sdw_channel { /* SDW0 */ diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index 2db03b2f0c3b..f362f195b887 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -38,6 +38,7 @@ struct amd_sdwc_ctrl { u32 quirks; u32 wake_en_mask; int num_ports; + bool clk_stopped; bool startup_done; u32 power_mode_mask; };
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add support for runtime pm ops for AMD master driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com
drivers/soundwire/amd_master.c | 205 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 3 + include/linux/soundwire/sdw_amd.h | 1 + 3 files changed, 209 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index c7063b8bdd7b..d2d7f07de202 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -15,6 +15,7 @@ #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw_amd.h> +#include <linux/pm_runtime.h> #include <linux/wait.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -290,6 +291,17 @@ static int amd_disable_sdw_interrupts(struct amd_sdwc_ctrl *ctrl) return 0; }
+static int amd_deinit_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{
- int ret;
- ret = amd_disable_sdw_interrupts(ctrl);
- if (ret)
return ret;
- ret = amd_disable_sdw_controller(ctrl);
- return ret;
+}
static int amd_sdwc_set_frameshape(struct amd_sdwc_ctrl *ctrl, u32 rows, u32 cols) { u32 sdw_rows, sdw_cols, frame_size; @@ -1387,6 +1399,12 @@ static int amd_sdwc_probe(struct platform_device *pdev) INIT_WORK(&ctrl->amd_sdw_work, amd_sdwc_update_slave_status_work); INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); schedule_work(&ctrl->probe_work);
- /* Enable runtime PM */
- pm_runtime_set_autosuspend_delay(dev, AMD_SDW_MASTER_SUSPEND_DELAY_MS);
- pm_runtime_use_autosuspend(dev);
- pm_runtime_mark_last_busy(dev);
- pm_runtime_set_active(dev);
- pm_runtime_enable(dev); return 0;
}
@@ -1398,14 +1416,201 @@ static int amd_sdwc_remove(struct platform_device *pdev) amd_disable_sdw_interrupts(ctrl); sdw_bus_master_delete(&ctrl->bus); ret = amd_disable_sdw_controller(ctrl);
- pm_runtime_disable(&pdev->dev); return ret;
}
+static int amd_sdwc_clock_stop(struct amd_sdwc_ctrl *ctrl) +{
- u32 clk_resume_ctrl_reg;
- u32 wake_en_reg;
- u32 val;
- u32 retry_count = 0;
- int ret;
- ret = sdw_bus_prep_clk_stop(&ctrl->bus);
- if (ret < 0 && ret != -ENODATA) {
dev_err(ctrl->dev, "prepare clock stop failed %d", ret);
return ret;
- }
- ret = sdw_bus_clk_stop(&ctrl->bus);
- if (ret < 0 && ret != -ENODATA) {
dev_err(ctrl->dev, "bus clock stop failed %d", ret);
return ret;
You need to be very careful here, because returning an error may prevent the device from suspending.
If it's safe and possible to recover during the resume step, you probably want to log the error but let the suspend continue.
- }
- switch (ctrl->instance) {
- case ACP_SDW0:
clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL;
wake_en_reg = ACP_SW_WAKE_EN;
break;
- case ACP_SDW1:
clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL;
wake_en_reg = ACP_SW1_WAKE_EN;
break;
- default:
return -EINVAL;
- }
why not store these offsets during the probe and use them directly here? You know at probe time which master you're using.
- do {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
if (val & AMD_SDW_CLK_STOP_DONE) {
ctrl->clk_stopped = true;
break;
}
- } while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT);
- if (!ctrl->clk_stopped) {
dev_err(ctrl->dev, "SDW%x clock stop failed\n", ctrl->instance);
return -ETIMEDOUT;
- }
- if (ctrl->wake_en_mask)
acp_reg_writel(0x01, ctrl->mmio + wake_en_reg);
- dev_dbg(ctrl->dev, "SDW%x clock stop successful\n", ctrl->instance);
- return 0;
+}
+static int amd_sdwc_clock_stop_exit(struct amd_sdwc_ctrl *ctrl) +{
- int ret;
- u32 clk_resume_ctrl_reg;
- u32 val = 0;
- u32 retry_count = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL;
break;
- case ACP_SDW1:
clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL;
break;
- default:
return -EINVAL;
- }
- if (ctrl->clk_stopped) {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
val |= AMD_SDW_CLK_RESUME_REQ;
acp_reg_writel(val, ctrl->mmio + clk_resume_ctrl_reg);
do {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
if (val & AMD_SDW_CLK_RESUME_DONE)
break;
usleep_range(10, 100);
} while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT);
if (val & AMD_SDW_CLK_RESUME_DONE) {
acp_reg_writel(0, ctrl->mmio + clk_resume_ctrl_reg);
ret = sdw_bus_exit_clk_stop(&ctrl->bus);
if (ret < 0)
dev_err(ctrl->dev, "bus failed to exit clock stop %d\n", ret);
ctrl->clk_stopped = false;
}
- }
- if (ctrl->clk_stopped) {
dev_err(ctrl->dev, "SDW%x clock stop exit failed\n", ctrl->instance);
return -ETIMEDOUT;
- }
- dev_dbg(ctrl->dev, "SDW%x clock stop exit successful\n", ctrl->instance);
- return 0;
+}
+static int __maybe_unused amd_suspend_runtime(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
do you have a case where the startup is not done? This was an Intel-specific thing.
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = amd_sdwc_clock_stop(ctrl);
if (ret)
return ret;
- } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
ret = amd_sdwc_clock_stop(ctrl);
if (ret)
return ret;
ret = amd_deinit_sdw_controller(ctrl);
if (ret)
return ret;
- }
- return 0;
+}
+static int __maybe_unused amd_resume_runtime(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- u32 clk_resume_ctrl_reg;
- u32 val = 0;
- u32 retry_count = 0;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
same here
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- switch (ctrl->instance) {
- case ACP_SDW0:
clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL;
break;
- case ACP_SDW1:
clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL;
break;
- default:
return -EINVAL;
- }
select registers in the probe.
- if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = amd_sdwc_clock_stop_exit(ctrl);
if (ret)
return ret;
- } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
if (val) {
val |= AMD_SDW_CLK_RESUME_REQ;
acp_reg_writel(val, ctrl->mmio + clk_resume_ctrl_reg);
do {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
if (val & AMD_SDW_CLK_RESUME_DONE)
break;
usleep_range(10, 100);
} while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT);
if (val & AMD_SDW_CLK_RESUME_DONE) {
acp_reg_writel(0, ctrl->mmio + clk_resume_ctrl_reg);
ctrl->clk_stopped = false;
}
}
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
amd_init_sdw_controller(ctrl);
amd_enable_sdw_interrupts(ctrl);
ret = amd_enable_sdw_controller(ctrl);
if (ret)
return ret;
ret = amd_sdwc_set_frameshape(ctrl, 50, 10);
this should be defined at probe time, using magic numbers like this will not work in all cases and totally depends on the frame rate and bandwidth needs.
if (ret)
return ret;
- }
- return 0;
+}
On 11/01/23 21:17, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add support for runtime pm ops for AMD master driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com
drivers/soundwire/amd_master.c | 205 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 3 + include/linux/soundwire/sdw_amd.h | 1 + 3 files changed, 209 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index c7063b8bdd7b..d2d7f07de202 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -15,6 +15,7 @@ #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw_amd.h> +#include <linux/pm_runtime.h> #include <linux/wait.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -290,6 +291,17 @@ static int amd_disable_sdw_interrupts(struct amd_sdwc_ctrl *ctrl) return 0; }
+static int amd_deinit_sdw_controller(struct amd_sdwc_ctrl *ctrl) +{
- int ret;
- ret = amd_disable_sdw_interrupts(ctrl);
- if (ret)
return ret;
- ret = amd_disable_sdw_controller(ctrl);
- return ret;
+}
static int amd_sdwc_set_frameshape(struct amd_sdwc_ctrl *ctrl, u32 rows, u32 cols) { u32 sdw_rows, sdw_cols, frame_size; @@ -1387,6 +1399,12 @@ static int amd_sdwc_probe(struct platform_device *pdev) INIT_WORK(&ctrl->amd_sdw_work, amd_sdwc_update_slave_status_work); INIT_WORK(&ctrl->probe_work, amd_sdwc_probe_work); schedule_work(&ctrl->probe_work);
- /* Enable runtime PM */
- pm_runtime_set_autosuspend_delay(dev, AMD_SDW_MASTER_SUSPEND_DELAY_MS);
- pm_runtime_use_autosuspend(dev);
- pm_runtime_mark_last_busy(dev);
- pm_runtime_set_active(dev);
- pm_runtime_enable(dev); return 0;
}
@@ -1398,14 +1416,201 @@ static int amd_sdwc_remove(struct platform_device *pdev) amd_disable_sdw_interrupts(ctrl); sdw_bus_master_delete(&ctrl->bus); ret = amd_disable_sdw_controller(ctrl);
- pm_runtime_disable(&pdev->dev); return ret;
}
+static int amd_sdwc_clock_stop(struct amd_sdwc_ctrl *ctrl) +{
- u32 clk_resume_ctrl_reg;
- u32 wake_en_reg;
- u32 val;
- u32 retry_count = 0;
- int ret;
- ret = sdw_bus_prep_clk_stop(&ctrl->bus);
- if (ret < 0 && ret != -ENODATA) {
dev_err(ctrl->dev, "prepare clock stop failed %d", ret);
return ret;
- }
- ret = sdw_bus_clk_stop(&ctrl->bus);
- if (ret < 0 && ret != -ENODATA) {
dev_err(ctrl->dev, "bus clock stop failed %d", ret);
return ret;
You need to be very careful here, because returning an error may prevent the device from suspending.
If it's safe and possible to recover during the resume step, you probably want to log the error but let the suspend continue.
will fix it.
- }
- switch (ctrl->instance) {
- case ACP_SDW0:
clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL;
wake_en_reg = ACP_SW_WAKE_EN;
break;
- case ACP_SDW1:
clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL;
wake_en_reg = ACP_SW1_WAKE_EN;
break;
- default:
return -EINVAL;
- }
why not store these offsets during the probe and use them directly here? You know at probe time which master you're using.
will fix it.
- do {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
if (val & AMD_SDW_CLK_STOP_DONE) {
ctrl->clk_stopped = true;
break;
}
- } while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT);
- if (!ctrl->clk_stopped) {
dev_err(ctrl->dev, "SDW%x clock stop failed\n", ctrl->instance);
return -ETIMEDOUT;
- }
- if (ctrl->wake_en_mask)
acp_reg_writel(0x01, ctrl->mmio + wake_en_reg);
- dev_dbg(ctrl->dev, "SDW%x clock stop successful\n", ctrl->instance);
- return 0;
+}
+static int amd_sdwc_clock_stop_exit(struct amd_sdwc_ctrl *ctrl) +{
- int ret;
- u32 clk_resume_ctrl_reg;
- u32 val = 0;
- u32 retry_count = 0;
- switch (ctrl->instance) {
- case ACP_SDW0:
clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL;
break;
- case ACP_SDW1:
clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL;
break;
- default:
return -EINVAL;
- }
- if (ctrl->clk_stopped) {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
val |= AMD_SDW_CLK_RESUME_REQ;
acp_reg_writel(val, ctrl->mmio + clk_resume_ctrl_reg);
do {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
if (val & AMD_SDW_CLK_RESUME_DONE)
break;
usleep_range(10, 100);
} while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT);
if (val & AMD_SDW_CLK_RESUME_DONE) {
acp_reg_writel(0, ctrl->mmio + clk_resume_ctrl_reg);
ret = sdw_bus_exit_clk_stop(&ctrl->bus);
if (ret < 0)
dev_err(ctrl->dev, "bus failed to exit clock stop %d\n", ret);
ctrl->clk_stopped = false;
}
- }
- if (ctrl->clk_stopped) {
dev_err(ctrl->dev, "SDW%x clock stop exit failed\n", ctrl->instance);
return -ETIMEDOUT;
- }
- dev_dbg(ctrl->dev, "SDW%x clock stop exit successful\n", ctrl->instance);
- return 0;
+}
+static int __maybe_unused amd_suspend_runtime(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
do you have a case where the startup is not done? This was an Intel-specific thing.
We have included startup_done flag in probe_work to check whether Manager has started. In case if manager init sequence fails, then there is no need to apply any PM ops.
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = amd_sdwc_clock_stop(ctrl);
if (ret)
return ret;
- } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
ret = amd_sdwc_clock_stop(ctrl);
if (ret)
return ret;
ret = amd_deinit_sdw_controller(ctrl);
if (ret)
return ret;
- }
- return 0;
+}
+static int __maybe_unused amd_resume_runtime(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- u32 clk_resume_ctrl_reg;
- u32 val = 0;
- u32 retry_count = 0;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
same here
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- switch (ctrl->instance) {
- case ACP_SDW0:
clk_resume_ctrl_reg = ACP_SW_CLK_RESUME_CTRL;
break;
- case ACP_SDW1:
clk_resume_ctrl_reg = ACP_P1_SW_CLK_RESUME_CTRL;
break;
- default:
return -EINVAL;
- }
select registers in the probe.
will fix it.
- if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = amd_sdwc_clock_stop_exit(ctrl);
if (ret)
return ret;
- } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
if (val) {
val |= AMD_SDW_CLK_RESUME_REQ;
acp_reg_writel(val, ctrl->mmio + clk_resume_ctrl_reg);
do {
val = acp_reg_readl(ctrl->mmio + clk_resume_ctrl_reg);
if (val & AMD_SDW_CLK_RESUME_DONE)
break;
usleep_range(10, 100);
} while (retry_count++ < AMD_SDW_CLK_STOP_MAX_RETRY_COUNT);
if (val & AMD_SDW_CLK_RESUME_DONE) {
acp_reg_writel(0, ctrl->mmio + clk_resume_ctrl_reg);
ctrl->clk_stopped = false;
}
}
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
amd_init_sdw_controller(ctrl);
amd_enable_sdw_interrupts(ctrl);
ret = amd_enable_sdw_controller(ctrl);
if (ret)
return ret;
ret = amd_sdwc_set_frameshape(ctrl, 50, 10);
this should be defined at probe time, using magic numbers like this will not work in all cases and totally depends on the frame rate and bandwidth needs.
Will fix it.
if (ret)
return ret;
- }
- return 0;
+}
+static int __maybe_unused amd_suspend_runtime(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
do you have a case where the startup is not done? This was an Intel-specific thing.
We have included startup_done flag in probe_work to check whether Manager has started. In case if manager init sequence fails, then there is no need to apply any PM ops.
Not following, sorry.
We introduced the .startup callback for intel because of a power dependency where we could not access and initialize the registers at the .probe time for the master driver.
Do you have a similar dependency, and if not why not remove this flag?
Add start up and shutdown dai ops for AMD Master driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- drivers/soundwire/amd_master.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index d2d7f07de202..290c59ab7760 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -1117,6 +1117,15 @@ static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int directi return 0; }
+static void amd_sdwc_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai); + + pm_runtime_mark_last_busy(ctrl->dev); + pm_runtime_put_autosuspend(ctrl->dev); +} + static int amd_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) { return amd_set_sdw_stream(dai, stream, direction); @@ -1137,9 +1146,27 @@ static void *amd_get_sdw_stream(struct snd_soc_dai *dai, int direction) return dma->stream; }
+static int amd_sdwc_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct amd_sdwc_ctrl *ctrl = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = pm_runtime_get_sync(ctrl->dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(ctrl->dev, + "pm_runtime_get_sync failed in %s, ret %d\n", + __func__, ret); + pm_runtime_put_noidle(ctrl->dev); + return ret; + } + return 0; +} + static const struct snd_soc_dai_ops amd_sdwc_dai_ops = { .hw_params = amd_sdwc_hw_params, .hw_free = amd_sdwc_hw_free, + .startup = amd_sdwc_startup, + .shutdown = amd_sdwc_shutdown, .set_stream = amd_pcm_set_sdw_stream, .get_stream = amd_get_sdw_stream, };
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add start up and shutdown dai ops for AMD Master driver.
I don't think those startup and shutdown ops are necessary, we removed them for Intel, see "soundwire: intel: remove DAI startup/shutdown"
On 11/01/23 21:19, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add start up and shutdown dai ops for AMD Master driver.
I don't think those startup and shutdown ops are necessary, we removed them for Intel, see "soundwire: intel: remove DAI startup/shutdown"
will drop this patch.
Add wake enable interrupt support for both the soundwire controller instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com --- drivers/soundwire/amd_master.c | 9 +++++++++ drivers/soundwire/amd_master.h | 1 + include/linux/soundwire/sdw_amd.h | 1 + 3 files changed, 11 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 290c59ab7760..2fd77a673c22 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -1219,6 +1219,13 @@ static void amd_sdwc_update_slave_status_work(struct work_struct *work) u32 sw_status_change_mask_0to7_reg; u32 sw_status_change_mask_8to11_reg;
+ if (ctrl->wake_event) { + pm_runtime_resume(ctrl->dev); + acp_reg_writel(0x00, ctrl->mmio + ACP_SW_WAKE_EN); + ctrl->wake_event = false; + return; + } + switch (ctrl->instance) { case ACP_SDW0: sw_status_change_mask_0to7_reg = SW_STATE_CHANGE_STATUS_MASK_0TO7; @@ -1258,6 +1265,8 @@ static void amd_sdwc_update_slave_status(u32 status_change_0to7, u32 status_chan
if (status_change_0to7 == AMD_SDW_SLAVE_0_ATTACHED) memset(ctrl->status, 0, sizeof(ctrl->status)); + if (status_change_8to11 & AMD_SDW_WAKE_STAT_MASK) + ctrl->wake_event = true; slave_stat = status_change_0to7; slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STATUS_8TO_11, status_change_8to11) << 32; dev_dbg(ctrl->dev, "%s: status_change_0to7:0x%x status_change_8to11:0x%x\n", diff --git a/drivers/soundwire/amd_master.h b/drivers/soundwire/amd_master.h index cc254255512b..e32884e3a45b 100644 --- a/drivers/soundwire/amd_master.h +++ b/drivers/soundwire/amd_master.h @@ -240,6 +240,7 @@ #define AMD_SDW_CLK_STOP_DONE 1 #define AMD_SDW_CLK_RESUME_REQ 2 #define AMD_SDW_CLK_RESUME_DONE 3 +#define AMD_SDW_WAKE_STAT_MASK BIT(16)
enum amd_sdw_channel { /* SDW0 */ diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index f362f195b887..e3e539b868be 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -40,6 +40,7 @@ struct amd_sdwc_ctrl { int num_ports; bool clk_stopped; bool startup_done; + bool wake_event; u32 power_mode_mask; };
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add wake enable interrupt support for both the soundwire controller
SoundWire.
instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com
drivers/soundwire/amd_master.c | 9 +++++++++ drivers/soundwire/amd_master.h | 1 + include/linux/soundwire/sdw_amd.h | 1 + 3 files changed, 11 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 290c59ab7760..2fd77a673c22 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -1219,6 +1219,13 @@ static void amd_sdwc_update_slave_status_work(struct work_struct *work) u32 sw_status_change_mask_0to7_reg; u32 sw_status_change_mask_8to11_reg;
- if (ctrl->wake_event) {
pm_runtime_resume(ctrl->dev);
acp_reg_writel(0x00, ctrl->mmio + ACP_SW_WAKE_EN);
ctrl->wake_event = false;
return;
- }
this is surprising.
A wake event typically happens when the bus is in clock-stop mode. You cannot deal with wake events while dealing with the peripheral status update, because you can only get that status when the manager is up-and-running. There's a conceptual miss here, no?
If the wake comes from the PCI side, then it's the same comment: why would the wake be handled while updating the peripheral status.
What am I missing?
On 11/01/23 21:24, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add wake enable interrupt support for both the soundwire controller
SoundWire.
instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com
drivers/soundwire/amd_master.c | 9 +++++++++ drivers/soundwire/amd_master.h | 1 + include/linux/soundwire/sdw_amd.h | 1 + 3 files changed, 11 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 290c59ab7760..2fd77a673c22 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -1219,6 +1219,13 @@ static void amd_sdwc_update_slave_status_work(struct work_struct *work) u32 sw_status_change_mask_0to7_reg; u32 sw_status_change_mask_8to11_reg;
- if (ctrl->wake_event) {
pm_runtime_resume(ctrl->dev);
acp_reg_writel(0x00, ctrl->mmio + ACP_SW_WAKE_EN);
ctrl->wake_event = false;
return;
- }
this is surprising.
A wake event typically happens when the bus is in clock-stop mode. You cannot deal with wake events while dealing with the peripheral status update, because you can only get that status when the manager is up-and-running. There's a conceptual miss here, no?
If the wake comes from the PCI side, then it's the same comment: why would the wake be handled while updating the peripheral status.
What am I missing?
It's a miss. This should be moved out of slave_status_work() even though when wake enable irq is received we are just returning from API. will move wake interrupt handling in to a separate helper function.
Add pm_prepare callback and System level pm ops support for AMD master driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com --- drivers/soundwire/amd_master.c | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 2fd77a673c22..f4478cc17aac 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -1552,6 +1552,80 @@ static int amd_sdwc_clock_stop_exit(struct amd_sdwc_ctrl *ctrl) return 0; }
+static int amd_resume_child_device(struct device *dev, void *data) +{ + int ret; + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + if (!slave->probed) { + dev_dbg(dev, "skipping device, no probed driver\n"); + return 0; + } + if (!slave->dev_num_sticky) { + dev_dbg(dev, "skipping device, never detected on bus\n"); + return 0; + } + + if (!pm_runtime_suspended(dev)) + return 0; + ret = pm_request_resume(dev); + if (ret < 0) + dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret); + + return ret; +} + +static int __maybe_unused amd_pm_prepare(struct device *dev) +{ + struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev); + struct sdw_bus *bus = &ctrl->bus; + int ret; + + if (bus->prop.hw_disabled || !ctrl->startup_done) { + dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n", + bus->link_id); + return 0; + } + ret = device_for_each_child(bus->dev, NULL, amd_resume_child_device); + if (ret < 0) + dev_err(dev, "%s: amd_resume_child_device failed: %d\n", __func__, ret); + if (pm_runtime_suspended(dev) && ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { + ret = pm_request_resume(dev); + if (ret < 0) { + dev_err(bus->dev, "pm_request_resume failed: %d\n", ret); + return 0; + } + } + return 0; +} + +static int __maybe_unused amd_suspend(struct device *dev) +{ + struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev); + struct sdw_bus *bus = &ctrl->bus; + int ret; + + if (bus->prop.hw_disabled || !ctrl->startup_done) { + dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n", + bus->link_id); + return 0; + } + + if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { + ret = amd_sdwc_clock_stop(ctrl); + if (ret) + return ret; + } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + ret = amd_sdwc_clock_stop(ctrl); + if (ret) + return ret; + ret = amd_deinit_sdw_controller(ctrl); + if (ret) + return ret; + } + return 0; +} + static int __maybe_unused amd_suspend_runtime(struct device *dev) { struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev); @@ -1638,6 +1712,8 @@ static int __maybe_unused amd_resume_runtime(struct device *dev) }
static const struct dev_pm_ops amd_pm = { + .prepare = amd_pm_prepare, + SET_SYSTEM_SLEEP_PM_OPS(amd_suspend, amd_resume_runtime) SET_RUNTIME_PM_OPS(amd_suspend_runtime, amd_resume_runtime, NULL) };
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add pm_prepare callback and System level pm ops support for AMD master driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com
drivers/soundwire/amd_master.c | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 2fd77a673c22..f4478cc17aac 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -1552,6 +1552,80 @@ static int amd_sdwc_clock_stop_exit(struct amd_sdwc_ctrl *ctrl) return 0; }
+static int amd_resume_child_device(struct device *dev, void *data) +{
- int ret;
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
- }
- if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
- }
- if (!pm_runtime_suspended(dev))
return 0;
- ret = pm_request_resume(dev);
- if (ret < 0)
dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
- return ret;
+}
+static int __maybe_unused amd_pm_prepare(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- ret = device_for_each_child(bus->dev, NULL, amd_resume_child_device);
- if (ret < 0)
dev_err(dev, "%s: amd_resume_child_device failed: %d\n", __func__, ret);
- if (pm_runtime_suspended(dev) && ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = pm_request_resume(dev);
if (ret < 0) {
dev_err(bus->dev, "pm_request_resume failed: %d\n", ret);
return 0;
}
- }
- return 0;
+}
This seems to be inspired by the Intel code, but is this necessary here?
For Intel, we saw cases where we had to pm_resume before doing a system suspend, otherwise the hardware was in a bad state.
Do you actually need to do so, or is is possible to do a system suspend when the clock is stopped.
And in the case where the bus is in 'power-off' mode, do you actually need to resume at all?
+static int __maybe_unused amd_suspend(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = amd_sdwc_clock_stop(ctrl);
if (ret)
return ret;
- } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
ret = amd_sdwc_clock_stop(ctrl);
do you actually need to stop the clock before powering-off? This seems counter intuitive and not so useful?
if (ret)
return ret;
ret = amd_deinit_sdw_controller(ctrl);
if (ret)
return ret;
- }
- return 0;
+}
static int __maybe_unused amd_suspend_runtime(struct device *dev) { struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev); @@ -1638,6 +1712,8 @@ static int __maybe_unused amd_resume_runtime(struct device *dev) }
static const struct dev_pm_ops amd_pm = {
- .prepare = amd_pm_prepare,
- SET_SYSTEM_SLEEP_PM_OPS(amd_suspend, amd_resume_runtime) SET_RUNTIME_PM_OPS(amd_suspend_runtime, amd_resume_runtime, NULL)
};
On 11/01/23 21:28, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
Add pm_prepare callback and System level pm ops support for AMD master driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com
drivers/soundwire/amd_master.c | 76 ++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/drivers/soundwire/amd_master.c b/drivers/soundwire/amd_master.c index 2fd77a673c22..f4478cc17aac 100644 --- a/drivers/soundwire/amd_master.c +++ b/drivers/soundwire/amd_master.c @@ -1552,6 +1552,80 @@ static int amd_sdwc_clock_stop_exit(struct amd_sdwc_ctrl *ctrl) return 0; }
+static int amd_resume_child_device(struct device *dev, void *data) +{
- int ret;
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
- }
- if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
- }
- if (!pm_runtime_suspended(dev))
return 0;
- ret = pm_request_resume(dev);
- if (ret < 0)
dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret);
- return ret;
+}
+static int __maybe_unused amd_pm_prepare(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- ret = device_for_each_child(bus->dev, NULL, amd_resume_child_device);
- if (ret < 0)
dev_err(dev, "%s: amd_resume_child_device failed: %d\n", __func__, ret);
- if (pm_runtime_suspended(dev) && ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = pm_request_resume(dev);
if (ret < 0) {
dev_err(bus->dev, "pm_request_resume failed: %d\n", ret);
return 0;
}
- }
- return 0;
+}
This seems to be inspired by the Intel code, but is this necessary here?
No It's not inspired by intel code. Initially, we haven't included pm_prepare callback. We have observed issues without pm_prepare callback.
For Intel, we saw cases where we had to pm_resume before doing a system suspend, otherwise the hardware was in a bad state.
Do you actually need to do so, or is is possible to do a system suspend when the clock is stopped.
And in the case where the bus is in 'power-off' mode, do you actually need to resume at all?
Our platform supports different power modes. To support all combinations, we have included pm_prepare callback.
+static int __maybe_unused amd_suspend(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- if (ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = amd_sdwc_clock_stop(ctrl);
if (ret)
return ret;
- } else if (ctrl->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
ret = amd_sdwc_clock_stop(ctrl);
do you actually need to stop the clock before powering-off? This seems counter intuitive and not so useful?
Yes, as per our design, we need to stop the clock before powering off.
if (ret)
return ret;
ret = amd_deinit_sdw_controller(ctrl);
if (ret)
return ret;
- }
- return 0;
+}
static int __maybe_unused amd_suspend_runtime(struct device *dev) { struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev); @@ -1638,6 +1712,8 @@ static int __maybe_unused amd_resume_runtime(struct device *dev) }
static const struct dev_pm_ops amd_pm = {
- .prepare = amd_pm_prepare,
- SET_SYSTEM_SLEEP_PM_OPS(amd_suspend, amd_resume_runtime) SET_RUNTIME_PM_OPS(amd_suspend_runtime, amd_resume_runtime, NULL)
};
+static int __maybe_unused amd_pm_prepare(struct device *dev) +{
- struct amd_sdwc_ctrl *ctrl = dev_get_drvdata(dev);
- struct sdw_bus *bus = &ctrl->bus;
- int ret;
- if (bus->prop.hw_disabled || !ctrl->startup_done) {
dev_dbg(bus->dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
- }
- ret = device_for_each_child(bus->dev, NULL, amd_resume_child_device);
- if (ret < 0)
dev_err(dev, "%s: amd_resume_child_device failed: %d\n", __func__, ret);
- if (pm_runtime_suspended(dev) && ctrl->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
ret = pm_request_resume(dev);
if (ret < 0) {
dev_err(bus->dev, "pm_request_resume failed: %d\n", ret);
return 0;
}
- }
- return 0;
+}
This seems to be inspired by the Intel code, but is this necessary here?
No It's not inspired by intel code. Initially, we haven't included pm_prepare callback. We have observed issues without pm_prepare callback.
For Intel, we saw cases where we had to pm_resume before doing a system suspend, otherwise the hardware was in a bad state.
Do you actually need to do so, or is is possible to do a system suspend when the clock is stopped.
And in the case where the bus is in 'power-off' mode, do you actually need to resume at all?
Our platform supports different power modes. To support all combinations, we have included pm_prepare callback.
do you actually need to stop the clock before powering-off? This seems counter intuitive and not so useful?
Yes, as per our design, we need to stop the clock before powering off.
It'd be good to add comments capturing these points, that would be useful for new contributors and reviewers to know this is intentional and required by the hardware programming sequences.
Add support for system level pm ops for soundwire dma driver for pink sardine platform.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/ps-sdw-dma.c | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+)
diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index 960c0bc5e848..ba2eea23d41e 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -623,6 +623,64 @@ static int acp63_sdw_platform_remove(struct platform_device *pdev) return 0; }
+static int __maybe_unused acp63_sdw_pcm_resume(struct device *dev) +{ + struct sdw_dma_dev_data *sdw_dma_data; + struct sdw_stream_instance *sdw_ins; + struct snd_pcm_runtime *runtime; + u32 period_bytes, buf_size, water_mark_size_reg; + int ret; + int index; + + sdw_dma_data = dev_get_drvdata(dev); + for (index = 0; index < ACP63_SDW_MAX_STREAMS; index++) { + if (sdw_dma_data->sdw_stream[index] && + sdw_dma_data->sdw_stream[index]->runtime) { + switch (index) { + case ACP_SDW_AUDIO_TX: + water_mark_size_reg = ACP_AUDIO_TX_INTR_WATERMARK_SIZE; + break; + case ACP_SDW_BT_TX: + water_mark_size_reg = ACP_BT_TX_INTR_WATERMARK_SIZE; + break; + case ACP_SDW_HS_TX: + water_mark_size_reg = ACP_HS_TX_INTR_WATERMARK_SIZE; + break; + case ACP_SDW1_BT_TX: + water_mark_size_reg = ACP_P1_BT_TX_INTR_WATERMARK_SIZE; + break; + case ACP_SDW_AUDIO_RX: + water_mark_size_reg = ACP_AUDIO_RX_INTR_WATERMARK_SIZE; + break; + case ACP_SDW_BT_RX: + water_mark_size_reg = ACP_BT_RX_INTR_WATERMARK_SIZE; + break; + case ACP_SDW_HS_RX: + water_mark_size_reg = ACP_HS_RX_INTR_WATERMARK_SIZE; + break; + case ACP_SDW1_BT_RX: + water_mark_size_reg = ACP_P1_BT_RX_INTR_WATERMARK_SIZE; + break; + default: + dev_err(dev, "%s: Invalid channel type\n", __func__); + return -EINVAL; + } + runtime = sdw_dma_data->sdw_stream[index]->runtime; + sdw_ins = runtime->private_data; + period_bytes = frames_to_bytes(runtime, runtime->period_size); + buf_size = frames_to_bytes(runtime, runtime->buffer_size); + acp63_config_dma(sdw_ins, index); + ret = acp63_configure_sdw_ringbuffer(sdw_dma_data->acp_base, index, + buf_size); + if (ret) + return ret; + acp63_writel(period_bytes, sdw_dma_data->acp_base + water_mark_size_reg); + } + } + acp63_enable_disable_sdw_dma_interrupts(sdw_dma_data->acp_base, true); + return 0; +} + static int __maybe_unused acp63_sdw_pcm_runtime_suspend(struct device *dev) { struct sdw_dma_dev_data *sdw_dma_data; @@ -650,6 +708,7 @@ static int __maybe_unused acp63_sdw_pcm_runtime_resume(struct device *dev) static const struct dev_pm_ops acp63_pm_ops = { SET_RUNTIME_PM_OPS(acp63_sdw_pcm_runtime_suspend, acp63_sdw_pcm_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(acp63_sdw_pcm_runtime_suspend, acp63_sdw_pcm_resume) };
static struct platform_driver acp63_sdw_dma_driver = {
To avoid ACP entering into D3 state during slave enumeration and initialization on two soundwire controller instances for multiple codecs, increase the runtime suspend delay to 3 seconds.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- sound/soc/amd/ps/acp63.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 833d0b5aa73d..6c8849f2bcec 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -51,7 +51,7 @@ #define MIN_BUFFER MAX_BUFFER
/* time in ms for runtime suspend delay */ -#define ACP_SUSPEND_DELAY_MS 2000 +#define ACP_SUSPEND_DELAY_MS 3000
#define ACP63_DMIC_ADDR 2 #define ACP63_PDM_MODE_DEVS 3
On 1/11/23 03:02, Vijendar Mukunda wrote:
To avoid ACP entering into D3 state during slave enumeration and initialization on two soundwire controller instances for multiple codecs, increase the runtime suspend delay to 3 seconds.
You have a parent PCI device and a set of child devices for each manager. The parent PCI device cannot suspend before all its children are also suspended, so shouldn't the delay be modified at the manager level?
Not getting what this delay is and how this would deal with a lengthy enumeration/initialization process.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
sound/soc/amd/ps/acp63.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 833d0b5aa73d..6c8849f2bcec 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -51,7 +51,7 @@ #define MIN_BUFFER MAX_BUFFER
/* time in ms for runtime suspend delay */ -#define ACP_SUSPEND_DELAY_MS 2000 +#define ACP_SUSPEND_DELAY_MS 3000
#define ACP63_DMIC_ADDR 2 #define ACP63_PDM_MODE_DEVS 3
On 11/01/23 21:32, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
To avoid ACP entering into D3 state during slave enumeration and initialization on two soundwire controller instances for multiple codecs, increase the runtime suspend delay to 3 seconds.
You have a parent PCI device and a set of child devices for each manager. The parent PCI device cannot suspend before all its children are also suspended, so shouldn't the delay be modified at the manager level?
Not getting what this delay is and how this would deal with a lengthy enumeration/initialization process.
Yes agreed. Until Child devices are suspended, parent device will be in D0 state. We will rephrase the commit message.
Machine driver node will be created by ACP PCI driver. We have added delay in machine driver to make sure two manager instances completes codec enumeration and peripheral initialization before registering the sound card. Without adding delay in machine driver will result early card registration before codec initialization is completed. Manager will enter in to bad state due to codec read/write failures. We are intended to keep the ACP in D0 state, till sound card is created and jack controls are initialized. To handle, at manager level increased runtime suspend delay.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
sound/soc/amd/ps/acp63.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 833d0b5aa73d..6c8849f2bcec 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -51,7 +51,7 @@ #define MIN_BUFFER MAX_BUFFER
/* time in ms for runtime suspend delay */ -#define ACP_SUSPEND_DELAY_MS 2000 +#define ACP_SUSPEND_DELAY_MS 3000
#define ACP63_DMIC_ADDR 2 #define ACP63_PDM_MODE_DEVS 3
On 1/12/23 05:02, Mukunda,Vijendar wrote:
On 11/01/23 21:32, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
To avoid ACP entering into D3 state during slave enumeration and initialization on two soundwire controller instances for multiple codecs, increase the runtime suspend delay to 3 seconds.
You have a parent PCI device and a set of child devices for each manager. The parent PCI device cannot suspend before all its children are also suspended, so shouldn't the delay be modified at the manager level?
Not getting what this delay is and how this would deal with a lengthy enumeration/initialization process.
Yes agreed. Until Child devices are suspended, parent device will be in D0 state. We will rephrase the commit message.
Machine driver node will be created by ACP PCI driver. We have added delay in machine driver to make sure two manager instances completes codec enumeration and peripheral initialization before registering the sound card. Without adding delay in machine driver will result early card registration before codec initialization is completed. Manager will enter in to bad state due to codec read/write failures. We are intended to keep the ACP in D0 state, till sound card is created and jack controls are initialized. To handle, at manager level increased runtime suspend delay.
This doesn't look too good. You should not assume any timing dependencies in the machine driver probe. I made that mistake in earlier versions and we had to revisit all this to make sure drivers could be bound/unbound at any time.
On 1/12/2023 08:54, Pierre-Louis Bossart wrote:
On 1/12/23 05:02, Mukunda,Vijendar wrote:
On 11/01/23 21:32, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
To avoid ACP entering into D3 state during slave enumeration and initialization on two soundwire controller instances for multiple codecs, increase the runtime suspend delay to 3 seconds.
You have a parent PCI device and a set of child devices for each manager. The parent PCI device cannot suspend before all its children are also suspended, so shouldn't the delay be modified at the manager level?
Not getting what this delay is and how this would deal with a lengthy enumeration/initialization process.
Yes agreed. Until Child devices are suspended, parent device will be in D0 state. We will rephrase the commit message.
Machine driver node will be created by ACP PCI driver. We have added delay in machine driver to make sure two manager instances completes codec enumeration and peripheral initialization before registering the sound card. Without adding delay in machine driver will result early card registration before codec initialization is completed. Manager will enter in to bad state due to codec read/write failures. We are intended to keep the ACP in D0 state, till sound card is created and jack controls are initialized. To handle, at manager level increased runtime suspend delay.
This doesn't look too good. You should not assume any timing dependencies in the machine driver probe. I made that mistake in earlier versions and we had to revisit all this to make sure drivers could be bound/unbound at any time.
Rather than a timing dependency, could you perhaps prohibit runtime PM and have a codec make a callback to indicate it's fully initialized and then allow runtime PM again?
On 1/12/23 09:29, Limonciello, Mario wrote:
On 1/12/2023 08:54, Pierre-Louis Bossart wrote:
On 1/12/23 05:02, Mukunda,Vijendar wrote:
On 11/01/23 21:32, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
To avoid ACP entering into D3 state during slave enumeration and initialization on two soundwire controller instances for multiple codecs, increase the runtime suspend delay to 3 seconds.
You have a parent PCI device and a set of child devices for each manager. The parent PCI device cannot suspend before all its children are also suspended, so shouldn't the delay be modified at the manager level?
Not getting what this delay is and how this would deal with a lengthy enumeration/initialization process.
Yes agreed. Until Child devices are suspended, parent device will be in D0 state. We will rephrase the commit message.
Machine driver node will be created by ACP PCI driver. We have added delay in machine driver to make sure two manager instances completes codec enumeration and peripheral initialization before registering the sound card. Without adding delay in machine driver will result early card registration before codec initialization is completed. Manager will enter in to bad state due to codec read/write failures. We are intended to keep the ACP in D0 state, till sound card is created and jack controls are initialized. To handle, at manager level increased runtime suspend delay.
This doesn't look too good. You should not assume any timing dependencies in the machine driver probe. I made that mistake in earlier versions and we had to revisit all this to make sure drivers could be bound/unbound at any time.
Rather than a timing dependency, could you perhaps prohibit runtime PM and have a codec make a callback to indicate it's fully initialized and then allow runtime PM again?
We already have enumeration and initialization 'struct completion' that are used by codec drivers to know if the hardware is usable. We also have pm_runtime_get_sync() is the bus layer to make sure the codec is resumed before being accessed.
The explanations above confuse card registration and manager probe/initialization. These are two different things. Maybe there's indeed a missing part in the SoundWire PM assumptions, but I am not getting what the issue is.
On 12/01/23 21:35, Pierre-Louis Bossart wrote:
On 1/12/23 09:29, Limonciello, Mario wrote:
On 1/12/2023 08:54, Pierre-Louis Bossart wrote:
On 1/12/23 05:02, Mukunda,Vijendar wrote:
On 11/01/23 21:32, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
To avoid ACP entering into D3 state during slave enumeration and initialization on two soundwire controller instances for multiple codecs, increase the runtime suspend delay to 3 seconds.
You have a parent PCI device and a set of child devices for each manager. The parent PCI device cannot suspend before all its children are also suspended, so shouldn't the delay be modified at the manager level?
Not getting what this delay is and how this would deal with a lengthy enumeration/initialization process.
Yes agreed. Until Child devices are suspended, parent device will be in D0 state. We will rephrase the commit message.
Machine driver node will be created by ACP PCI driver. We have added delay in machine driver to make sure two manager instances completes codec enumeration and peripheral initialization before registering the sound card. Without adding delay in machine driver will result early card registration before codec initialization is completed. Manager will enter in to bad state due to codec read/write failures. We are intended to keep the ACP in D0 state, till sound card is created and jack controls are initialized. To handle, at manager level increased runtime suspend delay.
This doesn't look too good. You should not assume any timing dependencies in the machine driver probe. I made that mistake in earlier versions and we had to revisit all this to make sure drivers could be bound/unbound at any time.
Rather than a timing dependency, could you perhaps prohibit runtime PM and have a codec make a callback to indicate it's fully initialized and then allow runtime PM again?
We already have enumeration and initialization 'struct completion' that are used by codec drivers to know if the hardware is usable. We also have pm_runtime_get_sync() is the bus layer to make sure the codec is resumed before being accessed.
Instead of walking through codec list and checking completion status for every codec over the link, can we have some solution where once all codecs gets enumerated and initialized, a variable in bus instance will be updated to know all peripherals initialized. So that we can check this variable in machine driver.
The explanations above confuse card registration and manager probe/initialization. These are two different things. Maybe there's indeed a missing part in the SoundWire PM assumptions, but I am not getting what the issue is.
We will rephrase the commit message. At manager level we want to increase the delay to 3s.
> increase the runtime suspend delay to 3 seconds. You have a parent PCI device and a set of child devices for each manager. The parent PCI device cannot suspend before all its children are also suspended, so shouldn't the delay be modified at the manager level?
Not getting what this delay is and how this would deal with a lengthy enumeration/initialization process.
Yes agreed. Until Child devices are suspended, parent device will be in D0 state. We will rephrase the commit message.
Machine driver node will be created by ACP PCI driver. We have added delay in machine driver to make sure two manager instances completes codec enumeration and peripheral initialization before registering the sound card. Without adding delay in machine driver will result early card registration before codec initialization is completed. Manager will enter in to bad state due to codec read/write failures. We are intended to keep the ACP in D0 state, till sound card is created and jack controls are initialized. To handle, at manager level increased runtime suspend delay.
This doesn't look too good. You should not assume any timing dependencies in the machine driver probe. I made that mistake in earlier versions and we had to revisit all this to make sure drivers could be bound/unbound at any time.
Rather than a timing dependency, could you perhaps prohibit runtime PM and have a codec make a callback to indicate it's fully initialized and then allow runtime PM again?
We already have enumeration and initialization 'struct completion' that are used by codec drivers to know if the hardware is usable. We also have pm_runtime_get_sync() is the bus layer to make sure the codec is resumed before being accessed.
Instead of walking through codec list and checking completion status for every codec over the link, can we have some solution where once all codecs gets enumerated and initialized, a variable in bus instance will be updated to know all peripherals initialized. So that we can check this variable in machine driver.
No, because the bus cannot know for sure what codecs to expect on the platform.
This comes from the design, we first create a bunch of devices based on ACPI information, which causes the drivers to probe. Then when the bus starts, codecs that are physically present on the bus will attach and be initialized in the update_status callback.
It's perfectly acceptable for devices to be exposed in ACPI and not be present on a board. The bus wouldn't know what is needed.
I am still not clear on what the "early card registration" issue might be.
Can you clarify which codec registers are accessed in that case, are those supposed to be managed with regmap? one possibility is that we need to make sure the codec drivers are in regmap cache_only probe at the probe time, that may prevent this sort of uncontrolled register access. I had a PR on this that I haven't touched in a while, see [1]
I do recall some issues with the codec jacks, where if the card registration happens too late the codec might have suspended. But we added pm_runtime_resume_and_get in the set_jack_detect callbacks, so that was solved.
On Fri, Jan 13, 2023 at 11:33:09AM -0600, Pierre-Louis Bossart wrote:
I do recall some issues with the codec jacks, where if the card registration happens too late the codec might have suspended. But we added pm_runtime_resume_and_get in the set_jack_detect callbacks, so that was solved.
Right, I would expect that whatever needs the device to be powered on would be explicitly ensuring that this is done rather than tweaking timeouts - the timeouts should be more of a performance thing to avoid bouncing power too much, not a correctness thing.
On 14/01/23 01:27, Mark Brown wrote:
On Fri, Jan 13, 2023 at 11:33:09AM -0600, Pierre-Louis Bossart wrote:
I do recall some issues with the codec jacks, where if the card registration happens too late the codec might have suspended. But we added pm_runtime_resume_and_get in the set_jack_detect callbacks, so that was solved.
Right, I would expect that whatever needs the device to be powered on would be explicitly ensuring that this is done rather than tweaking timeouts - the timeouts should be more of a performance thing to avoid bouncing power too much, not a correctness thing.
Machine driver probe is executed in parallel with Manager driver probe sequence. Because of it, before completion of all peripherals enumeration across the multiple links, if card registration is completed, codec register writes will fail as Codec device numbers are not assigned.
If we understood correctly, as per your suggestion, We shouldn't use any time bounds in machine driver probe sequence and before registering the sound card, need to traverses through all peripheral initialization completion status for all the managers.
On 1/16/23 02:35, Mukunda,Vijendar wrote:
On 14/01/23 01:27, Mark Brown wrote:
On Fri, Jan 13, 2023 at 11:33:09AM -0600, Pierre-Louis Bossart wrote:
I do recall some issues with the codec jacks, where if the card registration happens too late the codec might have suspended. But we added pm_runtime_resume_and_get in the set_jack_detect callbacks, so that was solved.
Right, I would expect that whatever needs the device to be powered on would be explicitly ensuring that this is done rather than tweaking timeouts - the timeouts should be more of a performance thing to avoid bouncing power too much, not a correctness thing.
Machine driver probe is executed in parallel with Manager driver probe sequence. Because of it, before completion of all peripherals enumeration across the multiple links, if card registration is completed, codec register writes will fail as Codec device numbers are not assigned.
If we understood correctly, as per your suggestion, We shouldn't use any time bounds in machine driver probe sequence and before registering the sound card, need to traverses through all peripheral initialization completion status for all the managers.
What's not clear in your reply is this:
What codec registers are accessed as a result of the machine driver probe and card registration, and in what part of the card registration?
Are we talking about SoundWire 'standard' registers for device/port management, about vendor specific ones that are exposed to userspace, or vendor-specific ones entirely configured by the driver/regmap.
You've got to give us more data or understanding of the sequence to help. Saying there's a race condition doesn't really help if there's nothing that explains what codec registers are accessed and when.
On 16/01/23 20:32, Pierre-Louis Bossart wrote:
On 1/16/23 02:35, Mukunda,Vijendar wrote:
On 14/01/23 01:27, Mark Brown wrote:
On Fri, Jan 13, 2023 at 11:33:09AM -0600, Pierre-Louis Bossart wrote:
I do recall some issues with the codec jacks, where if the card registration happens too late the codec might have suspended. But we added pm_runtime_resume_and_get in the set_jack_detect callbacks, so that was solved.
Right, I would expect that whatever needs the device to be powered on would be explicitly ensuring that this is done rather than tweaking timeouts - the timeouts should be more of a performance thing to avoid bouncing power too much, not a correctness thing.
Machine driver probe is executed in parallel with Manager driver probe sequence. Because of it, before completion of all peripherals enumeration across the multiple links, if card registration is completed, codec register writes will fail as Codec device numbers are not assigned.
If we understood correctly, as per your suggestion, We shouldn't use any time bounds in machine driver probe sequence and before registering the sound card, need to traverses through all peripheral initialization completion status for all the managers.
What's not clear in your reply is this:
What codec registers are accessed as a result of the machine driver probe and card registration, and in what part of the card registration?
Are we talking about SoundWire 'standard' registers for device/port management, about vendor specific ones that are exposed to userspace, or vendor-specific ones entirely configured by the driver/regmap.
You've got to give us more data or understanding of the sequence to help. Saying there's a race condition doesn't really help if there's nothing that explains what codec registers are accessed and when.
We have come across a race condition, where sound card registration is successful before codec enumerations across all the links gets completed and our manager instance going into bad state.
Please refer below link for error logs. https://pastebin.com/ZYEN928S
On 1/17/23 05:33, Mukunda,Vijendar wrote:
On 16/01/23 20:32, Pierre-Louis Bossart wrote:
On 1/16/23 02:35, Mukunda,Vijendar wrote:
On 14/01/23 01:27, Mark Brown wrote:
On Fri, Jan 13, 2023 at 11:33:09AM -0600, Pierre-Louis Bossart wrote:
I do recall some issues with the codec jacks, where if the card registration happens too late the codec might have suspended. But we added pm_runtime_resume_and_get in the set_jack_detect callbacks, so that was solved.
Right, I would expect that whatever needs the device to be powered on would be explicitly ensuring that this is done rather than tweaking timeouts - the timeouts should be more of a performance thing to avoid bouncing power too much, not a correctness thing.
Machine driver probe is executed in parallel with Manager driver probe sequence. Because of it, before completion of all peripherals enumeration across the multiple links, if card registration is completed, codec register writes will fail as Codec device numbers are not assigned.
If we understood correctly, as per your suggestion, We shouldn't use any time bounds in machine driver probe sequence and before registering the sound card, need to traverses through all peripheral initialization completion status for all the managers.
What's not clear in your reply is this:
What codec registers are accessed as a result of the machine driver probe and card registration, and in what part of the card registration?
Are we talking about SoundWire 'standard' registers for device/port management, about vendor specific ones that are exposed to userspace, or vendor-specific ones entirely configured by the driver/regmap.
You've got to give us more data or understanding of the sequence to help. Saying there's a race condition doesn't really help if there's nothing that explains what codec registers are accessed and when.
We have come across a race condition, where sound card registration is successful before codec enumerations across all the links gets completed and our manager instance going into bad state.
Please refer below link for error logs. https://pastebin.com/ZYEN928S
You have two RT1316 register areas that are accessed while the codec is not even enumerated:
[ 2.755828] rt1316-sdca sdw:0:025d:1316:01:0: ASoC: error at snd_soc_component_update_bits on sdw:0:025d:1316:01:0 for register: [0x41080100] -22
[ 2.758904] rt1316-sdca sdw:0:025d:1316:01:0: ASoC: error at snd_soc_component_update_bits on sdw:0:025d:1316:01:0 for register: [0x00003004] -110
The last one is clearly listed in the regmap list.
You probably want to reverse-engineer what causes these accesses. I see this suspicious kcontrol definition that might be related:
SOC_SINGLE("Left I Tag Select", 0x3004, 4, 7, 0),
On Tue, Jan 17, 2023 at 05:51:03AM -0600, Pierre-Louis Bossart wrote:
On 1/17/23 05:33, Mukunda,Vijendar wrote:
[ 2.758904] rt1316-sdca sdw:0:025d:1316:01:0: ASoC: error at snd_soc_component_update_bits on sdw:0:025d:1316:01:0 for register: [0x00003004] -110
The last one is clearly listed in the regmap list.
You probably want to reverse-engineer what causes these accesses. I see this suspicious kcontrol definition that might be related:
SOC_SINGLE("Left I Tag Select", 0x3004, 4, 7, 0),
Looks like a case for putting the CODEC in cache only mode...
On 1/17/23 06:16, Mark Brown wrote:
On Tue, Jan 17, 2023 at 05:51:03AM -0600, Pierre-Louis Bossart wrote:
On 1/17/23 05:33, Mukunda,Vijendar wrote:
[ 2.758904] rt1316-sdca sdw:0:025d:1316:01:0: ASoC: error at snd_soc_component_update_bits on sdw:0:025d:1316:01:0 for register: [0x00003004] -110
The last one is clearly listed in the regmap list.
You probably want to reverse-engineer what causes these accesses. I see this suspicious kcontrol definition that might be related:
SOC_SINGLE("Left I Tag Select", 0x3004, 4, 7, 0),
Looks like a case for putting the CODEC in cache only mode...
Right, and I think we'd need to do this during the probe instead of the hardware initialization (which could happen at a later time).
I started a PR to try and improve regmap handling, see https://github.com/thesofproject/linux/pull/3941
I was trying to solve the case where codecs become unattached, but apparently the problem is hardware-related. One of the suggested improvements was to move the cache_only part earlier to prevent such accesses. Unfortunately the work isn't complete so that PR is just a draft at the moment.
On 1/11/23 03:02, Vijendar Mukunda wrote:
Pink Sardine(ps) platform is based on ACP6.3 Architecture. ACP6.3 IP has two soundwire controller instance support.
After reviewing patch1, it looks like there's a confusion between controller and manager? the use of the 'sdw-master-count' property is not for controller count, it's defined within the scope of an ACPI controller device (usually a companion device to the PCI device).
This patchset add support for Soundwire controller on Pink Sardine platform.
That's great, thanks for sharing.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Vijendar Mukunda (19): ASoC: amd: ps: create platform devices based on acp config soundwire: amd: Add support for AMD Master driver soundwire: amd: register sdw controller dai ops soundwire: amd: enable build for AMD soundwire master driver soundwire: amd: add soundwire interrupt handling ASoC: amd: ps: add support for soundwire interrupts in acp pci driver ASoC: amd: ps: add soundwire dma driver for pink sardine platform ASoC: amd: ps: add soundwire dma driver dma ops ASoC: amd: ps: add support for Soundwire DMA interrupts ASoC: amd: ps: enable Soundwire DMA driver build ASoC: amd: update comments in Kconfig file ASoC: amd: ps: Add soundwire specific checks in pci driver in pm ops. ASoC: amd: ps: add support for runtime pm ops for soundwire dma driver soundwire: amd: add runtime pm ops for AMD master driver soundwire: amd: add startup and shutdown dai ops soundwire: amd: handle wake enable interrupt soundwire: amd: add pm_prepare callback and pm ops support ASoC: amd: ps: implement system level pm ops for soundwire dma driver ASoC: amd: ps: increase runtime suspend delay
drivers/soundwire/Kconfig | 9 + drivers/soundwire/Makefile | 4 + drivers/soundwire/amd_master.c | 1734 +++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 284 +++++ include/linux/soundwire/sdw_amd.h | 65 ++ sound/soc/amd/Kconfig | 3 +- sound/soc/amd/ps/Makefile | 2 + sound/soc/amd/ps/acp63.h | 98 +- sound/soc/amd/ps/pci-ps.c | 383 ++++++- sound/soc/amd/ps/ps-sdw-dma.c | 728 ++++++++++++ 10 files changed, 3287 insertions(+), 23 deletions(-) create mode 100644 drivers/soundwire/amd_master.c create mode 100644 drivers/soundwire/amd_master.h create mode 100644 include/linux/soundwire/sdw_amd.h create mode 100644 sound/soc/amd/ps/ps-sdw-dma.c
On 11/01/23 19:06, Pierre-Louis Bossart wrote:
On 1/11/23 03:02, Vijendar Mukunda wrote:
Pink Sardine(ps) platform is based on ACP6.3 Architecture. ACP6.3 IP has two soundwire controller instance support.
After reviewing patch1, it looks like there's a confusion between controller and manager? the use of the 'sdw-master-count' property is not for controller count, it's defined within the scope of an ACPI controller device (usually a companion device to the PCI device).
Sorry for confusion. We are meant to refer Master/Manager instance in throughout the code. we will replace the "Controller" reference with "Manager". We will correct the cover letter.
This patchset add support for Soundwire controller on Pink Sardine platform.
That's great, thanks for sharing.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Vijendar Mukunda (19): ASoC: amd: ps: create platform devices based on acp config soundwire: amd: Add support for AMD Master driver soundwire: amd: register sdw controller dai ops soundwire: amd: enable build for AMD soundwire master driver soundwire: amd: add soundwire interrupt handling ASoC: amd: ps: add support for soundwire interrupts in acp pci driver ASoC: amd: ps: add soundwire dma driver for pink sardine platform ASoC: amd: ps: add soundwire dma driver dma ops ASoC: amd: ps: add support for Soundwire DMA interrupts ASoC: amd: ps: enable Soundwire DMA driver build ASoC: amd: update comments in Kconfig file ASoC: amd: ps: Add soundwire specific checks in pci driver in pm ops. ASoC: amd: ps: add support for runtime pm ops for soundwire dma driver soundwire: amd: add runtime pm ops for AMD master driver soundwire: amd: add startup and shutdown dai ops soundwire: amd: handle wake enable interrupt soundwire: amd: add pm_prepare callback and pm ops support ASoC: amd: ps: implement system level pm ops for soundwire dma driver ASoC: amd: ps: increase runtime suspend delay
drivers/soundwire/Kconfig | 9 + drivers/soundwire/Makefile | 4 + drivers/soundwire/amd_master.c | 1734 +++++++++++++++++++++++++++++ drivers/soundwire/amd_master.h | 284 +++++ include/linux/soundwire/sdw_amd.h | 65 ++ sound/soc/amd/Kconfig | 3 +- sound/soc/amd/ps/Makefile | 2 + sound/soc/amd/ps/acp63.h | 98 +- sound/soc/amd/ps/pci-ps.c | 383 ++++++- sound/soc/amd/ps/ps-sdw-dma.c | 728 ++++++++++++ 10 files changed, 3287 insertions(+), 23 deletions(-) create mode 100644 drivers/soundwire/amd_master.c create mode 100644 drivers/soundwire/amd_master.h create mode 100644 include/linux/soundwire/sdw_amd.h create mode 100644 sound/soc/amd/ps/ps-sdw-dma.c
participants (8)
-
Amadeusz Sławiński
-
kernel test robot
-
Limonciello, Mario
-
Mario Limonciello
-
Mark Brown
-
Mukunda,Vijendar
-
Pierre-Louis Bossart
-
Vijendar Mukunda