[alsa-devel] [PATCH 00/12] Add ASoC support for AMD APUs
Hi,
This patch set implements support for i2s audio and new AMD GPUs. The i2s codec is fed by a DMA engine on the GPU. To handle this we create an amd gnb (graphics northbridge) bus which we can hang the i2s codec on. Because of this, this patch set covers two subsystems: drm and alsa. The drm patches add support for the gnb bus and the ACP hw block which provides the DMA engine for the i2s codec. The alsa patches add the ASoC driver for the i2s codec. Since the alsa changes depend on the drm changes in this patch set as well as some other drm changes queued for 4.3, I'd like to take the alsa patches in via the drm tree.
Patch 3 adds the register headers for the ACP block which is a pretty big patch so I've excluded it from email. The entire patch set can be viewed here: http://cgit.freedesktop.org/~agd5f/linux/log/?h=acp-upstream
Thanks,
Alex
Chunming Zhou (2): drm/amdgpu: add amd_gnb_bus support drm/amd: add ACP 2.x register headers
Maruthi Bayyavarapu (1): drm/amd: add ACP driver support (v4)
Maruthi Srinivas Bayyavarapu (9): drm/amd: remove amd gnb bus default runtime pm ops drm/amd: modify ACP DMA buffer position update logic (v2) drm/amd: add ACP suspend/resume functionality drm/amd: change ACP SRAM banks used for audio drm/amd: shutdown unused ACP SRAM memory banks drm/amd: remove bitfield usage in ACP IP module ASoC: AMD: add AMD ASoC ACP-I2S driver (v2) ASoC: AMD: add suspend/resume for ACP PCM driver ASoC: AMD: add ACP PCM driver runtime PM
drivers/gpu/drm/Kconfig | 5 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/amd/acp/Kconfig | 9 + drivers/gpu/drm/amd/acp/Makefile | 9 + drivers/gpu/drm/amd/acp/acp_hw.c | 1134 ++++++++++ drivers/gpu/drm/amd/acp/acp_hw.h | 91 + drivers/gpu/drm/amd/acp/include/acp_gfx_if.h | 49 + drivers/gpu/drm/amd/acp/include/amd_acp.h | 214 ++ drivers/gpu/drm/amd/amdgpu/Makefile | 14 +- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 11 + drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 201 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h | 40 + drivers/gpu/drm/amd/amdgpu/vi.c | 12 + drivers/gpu/drm/amd/bus/Kconfig | 7 + drivers/gpu/drm/amd/bus/Makefile | 4 + drivers/gpu/drm/amd/bus/amd_gnb_bus.c | 261 +++ drivers/gpu/drm/amd/include/amd_shared.h | 1 + .../gpu/drm/amd/include/asic_reg/acp/acp_2_1_d.h | 437 ++++ .../drm/amd/include/asic_reg/acp/acp_2_1_enum.h | 1198 ++++++++++ .../drm/amd/include/asic_reg/acp/acp_2_1_sh_mask.h | 1568 +++++++++++++ .../gpu/drm/amd/include/asic_reg/acp/acp_2_2_d.h | 609 ++++++ .../drm/amd/include/asic_reg/acp/acp_2_2_enum.h | 1068 +++++++++ .../drm/amd/include/asic_reg/acp/acp_2_2_sh_mask.h | 2292 ++++++++++++++++++++ .../gpu/drm/amd/include/asic_reg/acp/acp_2_3_d.h | 582 +++++ .../drm/amd/include/asic_reg/acp/acp_2_3_enum.h | 1079 +++++++++ .../drm/amd/include/asic_reg/acp/acp_2_3_sh_mask.h | 2192 +++++++++++++++++++ drivers/gpu/drm/amd/include/bus/amd_gnb_bus.h | 78 + sound/soc/Kconfig | 2 +- sound/soc/Makefile | 1 + sound/soc/amd/Kconfig | 13 + sound/soc/amd/Makefile | 11 + sound/soc/amd/acp-pcm-dma.c | 759 +++++++ sound/soc/amd/acp-rt286.c | 264 +++ 33 files changed, 14214 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/drm/amd/acp/Kconfig create mode 100644 drivers/gpu/drm/amd/acp/Makefile create mode 100644 drivers/gpu/drm/amd/acp/acp_hw.c create mode 100644 drivers/gpu/drm/amd/acp/acp_hw.h create mode 100644 drivers/gpu/drm/amd/acp/include/acp_gfx_if.h create mode 100644 drivers/gpu/drm/amd/acp/include/amd_acp.h create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h create mode 100644 drivers/gpu/drm/amd/bus/Kconfig create mode 100644 drivers/gpu/drm/amd/bus/Makefile create mode 100644 drivers/gpu/drm/amd/bus/amd_gnb_bus.c create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_1_d.h create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_1_enum.h create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_1_sh_mask.h create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_2_d.h create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_2_enum.h create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_2_sh_mask.h create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_3_d.h create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_3_enum.h create mode 100644 drivers/gpu/drm/amd/include/asic_reg/acp/acp_2_3_sh_mask.h create mode 100644 drivers/gpu/drm/amd/include/bus/amd_gnb_bus.h create mode 100644 sound/soc/amd/Kconfig create mode 100644 sound/soc/amd/Makefile create mode 100644 sound/soc/amd/acp-pcm-dma.c create mode 100644 sound/soc/amd/acp-rt286.c
From: Chunming Zhou david1.zhou@amd.com
This is used by the incoming ACP driver. The DMA engine for the i2s audio codec is part of the GPU.
This exposes an amd gnb bus for the i2s codec to hang off of.
Reviewed-by: Jammy Zhou Jammy.Zhou@amd.com Signed-off-by: Chunming Zhou david1.zhou@amd.com Signed-off-by: Alex Deucher alexander.deucher@amd.com --- drivers/gpu/drm/Kconfig | 3 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/amd/bus/Kconfig | 7 + drivers/gpu/drm/amd/bus/Makefile | 4 + drivers/gpu/drm/amd/bus/amd_gnb_bus.c | 266 ++++++++++++++++++++++++++ drivers/gpu/drm/amd/include/bus/amd_gnb_bus.h | 78 ++++++++ 6 files changed, 359 insertions(+) create mode 100644 drivers/gpu/drm/amd/bus/Kconfig create mode 100644 drivers/gpu/drm/amd/bus/Makefile create mode 100644 drivers/gpu/drm/amd/bus/amd_gnb_bus.c create mode 100644 drivers/gpu/drm/amd/include/bus/amd_gnb_bus.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index c46ca31..8c3cc9e 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -120,6 +120,8 @@ config DRM_RADEON
source "drivers/gpu/drm/radeon/Kconfig"
+source "drivers/gpu/drm/amd/bus/Kconfig" + config DRM_AMDGPU tristate "AMD GPU" depends on DRM && PCI @@ -134,6 +136,7 @@ config DRM_AMDGPU select HWMON select BACKLIGHT_CLASS_DEVICE select INTERVAL_TREE + select DRM_AMD_GNB_BUS help Choose this option if you have a recent AMD Radeon graphics card.
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 5713d05..5380477 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_DRM_TDFX) += tdfx/ obj-$(CONFIG_DRM_R128) += r128/ obj-$(CONFIG_HSA_AMD) += amd/amdkfd/ obj-$(CONFIG_DRM_RADEON)+= radeon/ +obj-$(CONFIG_DRM_AMD_GNB_BUS) += amd/bus/ obj-$(CONFIG_DRM_AMDGPU)+= amd/amdgpu/ obj-$(CONFIG_DRM_MGA) += mga/ obj-$(CONFIG_DRM_I810) += i810/ diff --git a/drivers/gpu/drm/amd/bus/Kconfig b/drivers/gpu/drm/amd/bus/Kconfig new file mode 100644 index 0000000..f101dd8 --- /dev/null +++ b/drivers/gpu/drm/amd/bus/Kconfig @@ -0,0 +1,7 @@ +menu "AMD GNB BUS" + visible if 0 + +config DRM_AMD_GNB_BUS + tristate "AMD GNB bus - used for GNB IPs such as ACP and ISP" + +endmenu diff --git a/drivers/gpu/drm/amd/bus/Makefile b/drivers/gpu/drm/amd/bus/Makefile new file mode 100644 index 0000000..c41ffc9 --- /dev/null +++ b/drivers/gpu/drm/amd/bus/Makefile @@ -0,0 +1,4 @@ +# +ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/amd/include/bus/ + +obj-$(CONFIG_DRM_AMD_GNB_BUS) := amd_gnb_bus.o diff --git a/drivers/gpu/drm/amd/bus/amd_gnb_bus.c b/drivers/gpu/drm/amd/bus/amd_gnb_bus.c new file mode 100644 index 0000000..071b16c --- /dev/null +++ b/drivers/gpu/drm/amd/bus/amd_gnb_bus.c @@ -0,0 +1,266 @@ +/* + * Copyright 2014 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + */ +#include <linux/module.h> +#include <linux/device.h> +#include <linux/pm_runtime.h> +#include "amd_gnb_bus.h" + +#define to_amd_gnb_bus_device(x) container_of((x), struct amd_gnb_bus_dev, dev) +#define to_amd_gnb_bus_driver(drv) (container_of((drv), \ + struct amd_gnb_bus_driver, \ + driver)) + +static int amd_gnb_bus_match(struct device *dev, struct device_driver *drv) +{ + struct amd_gnb_bus_dev *amd_gnb_bus_dev = to_amd_gnb_bus_device(dev); + struct amd_gnb_bus_driver *amd_gnb_bus_driver = + to_amd_gnb_bus_driver(drv); + + return amd_gnb_bus_dev->ip == amd_gnb_bus_driver->ip ? 1 : 0; +} + +#ifdef CONFIG_PM_SLEEP +static int amd_gnb_bus_legacy_suspend(struct device *dev, pm_message_t mesg) +{ + struct amd_gnb_bus_dev *amd_gnb_bus_dev = to_amd_gnb_bus_device(dev); + struct amd_gnb_bus_driver *driver; + + if (!amd_gnb_bus_dev || !dev->driver) + return 0; + driver = to_amd_gnb_bus_driver(dev->driver); + if (!driver->suspend) + return 0; + return driver->suspend(amd_gnb_bus_dev, mesg); +} + +static int amd_gnb_bus_legacy_resume(struct device *dev) +{ + struct amd_gnb_bus_dev *amd_gnb_bus_dev = to_amd_gnb_bus_device(dev); + struct amd_gnb_bus_driver *driver; + + if (!amd_gnb_bus_dev || !dev->driver) + return 0; + driver = to_amd_gnb_bus_driver(dev->driver); + if (!driver->resume) + return 0; + return driver->resume(amd_gnb_bus_dev); +} + +static int amd_gnb_bus_device_pm_suspend(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_suspend(dev); + else + return amd_gnb_bus_legacy_suspend(dev, PMSG_SUSPEND); +} + +static int amd_gnb_bus_device_pm_resume(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_resume(dev); + else + return amd_gnb_bus_legacy_resume(dev); +} + +static int amd_gnb_bus_device_pm_freeze(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_freeze(dev); + else + return amd_gnb_bus_legacy_suspend(dev, PMSG_FREEZE); +} + +static int amd_gnb_bus_device_pm_thaw(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_thaw(dev); + else + return amd_gnb_bus_legacy_resume(dev); +} + +static int amd_gnb_bus_device_pm_poweroff(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_poweroff(dev); + else + return amd_gnb_bus_legacy_suspend(dev, PMSG_HIBERNATE); +} + +static int amd_gnb_bus_device_pm_restore(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_restore(dev); + else + return amd_gnb_bus_legacy_resume(dev); +} +#else /* !CONFIG_PM_SLEEP */ +#define amd_gnb_bus_device_pm_suspend NULL +#define amd_gnb_bus_device_pm_resume NULL +#define amd_gnb_bus_device_pm_freeze NULL +#define amd_gnb_bus_device_pm_thaw NULL +#define amd_gnb_bus_device_pm_poweroff NULL +#define amd_gnb_bus_device_pm_restore NULL +#endif /* !CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops amd_gnb_bus_device_pm_ops = { + .suspend = amd_gnb_bus_device_pm_suspend, + .resume = amd_gnb_bus_device_pm_resume, + .freeze = amd_gnb_bus_device_pm_freeze, + .thaw = amd_gnb_bus_device_pm_thaw, + .poweroff = amd_gnb_bus_device_pm_poweroff, + .restore = amd_gnb_bus_device_pm_restore, + SET_RUNTIME_PM_OPS( + pm_generic_runtime_suspend, + pm_generic_runtime_resume, + pm_runtime_idle + ) +}; + +/* The bus should only be registered by the first amd_gnb, but further + * socs can add devices to the bus. */ +struct bus_type amd_gnb_bus_type = { + .name = "amd_gnb", + .match = amd_gnb_bus_match, + .pm = &amd_gnb_bus_device_pm_ops, +}; +EXPORT_SYMBOL(amd_gnb_bus_type); + +static int amd_gnb_bus_drv_probe(struct device *_dev) +{ + struct amd_gnb_bus_driver *drv = to_amd_gnb_bus_driver(_dev->driver); + struct amd_gnb_bus_dev *dev = to_amd_gnb_bus_device(_dev); + + return drv->probe(dev); +} + +static int amd_gnb_bus_drv_remove(struct device *_dev) +{ + struct amd_gnb_bus_driver *drv = to_amd_gnb_bus_driver(_dev->driver); + struct amd_gnb_bus_dev *dev = to_amd_gnb_bus_device(_dev); + + return drv->remove(dev); +} + +static void amd_gnb_bus_drv_shutdown(struct device *_dev) +{ + struct amd_gnb_bus_driver *drv = to_amd_gnb_bus_driver(_dev->driver); + struct amd_gnb_bus_dev *dev = to_amd_gnb_bus_device(_dev); + + drv->shutdown(dev); +} + +int amd_gnb_bus_register_driver(struct amd_gnb_bus_driver *drv, + struct module *owner, + const char *mod_name) +{ + /* initialize common driver fields */ + drv->driver.name = drv->name; + drv->driver.bus = &amd_gnb_bus_type; + drv->driver.owner = owner; + drv->driver.mod_name = mod_name; + + if (drv->probe) + drv->driver.probe = amd_gnb_bus_drv_probe; + if (drv->remove) + drv->driver.remove = amd_gnb_bus_drv_remove; + if (drv->shutdown) + drv->driver.shutdown = amd_gnb_bus_drv_shutdown; + + /* register with core */ + return driver_register(&drv->driver); +} +EXPORT_SYMBOL(amd_gnb_bus_register_driver); + +void amd_gnb_bus_unregister_driver(struct amd_gnb_bus_driver *drv) +{ + /* register with core */ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL(amd_gnb_bus_unregister_driver); + +int amd_gnb_bus_register_device(struct amd_gnb_bus_dev *dev) +{ + dev->dev.bus = &amd_gnb_bus_type; + return device_add(&dev->dev); +} +EXPORT_SYMBOL(amd_gnb_bus_register_device); + +void amd_gnb_bus_unregister_device(struct amd_gnb_bus_dev *dev) +{ + if (dev) + device_del(&dev->dev); +} +EXPORT_SYMBOL(amd_gnb_bus_unregister_device); + +int amd_gnb_bus_device_init(struct amd_gnb_bus_dev *bus_dev, + enum amd_gnb_bus_ip ip, + char *dev_name, + void *handle, + struct device *parent) +{ + device_initialize(&bus_dev->dev); + bus_dev->dev.init_name = dev_name; + bus_dev->ip = ip; + bus_dev->private_data = handle; + bus_dev->dev.parent = parent; + return amd_gnb_bus_register_device(bus_dev); +} +EXPORT_SYMBOL(amd_gnb_bus_device_init); + +static int __init amd_gnb_bus_init(void) +{ + int ret = 0; + /* does this need to be thread safe? */ + ret = bus_register(&amd_gnb_bus_type); + if (ret) + pr_err("%s: bus register failed\n", __func__); + else + pr_info("%s: initialization is successful\n", __func__); + + return ret; +} + +static void __exit amd_gnb_bus_exit(void) +{ + bus_unregister(&amd_gnb_bus_type); +} + +module_init(amd_gnb_bus_init); +module_exit(amd_gnb_bus_exit); + +MODULE_AUTHOR("AMD"); +MODULE_DESCRIPTION("AMD GPU bus"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/gpu/drm/amd/include/bus/amd_gnb_bus.h b/drivers/gpu/drm/amd/include/bus/amd_gnb_bus.h new file mode 100644 index 0000000..3e90077 --- /dev/null +++ b/drivers/gpu/drm/amd/include/bus/amd_gnb_bus.h @@ -0,0 +1,78 @@ +/* + * Copyright 2014 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + */ + +#ifndef __AMD_GNB_BUS_H__ +#define __AMD_GNB_BUS_H__ + +#include <linux/types.h> + +enum amd_gnb_bus_ip { + AMD_GNB_IP_ACP_DMA, + AMD_GNB_IP_ACP_I2S, + AMD_GNB_IP_ACP_PCM, + AMD_GNB_IP_ISP, + AMD_GNB_IP_NUM +}; + +struct amd_gnb_bus_dev { + struct device dev; /* generic device interface */ + enum amd_gnb_bus_ip ip; + /* private data can be acp_handle/isp_handle etc.*/ + void *private_data; +}; + +struct amd_gnb_bus_driver { + const char *name; + enum amd_gnb_bus_ip ip; + int (*probe)(struct amd_gnb_bus_dev *dev); /* New device inserted */ + int (*remove)(struct amd_gnb_bus_dev *dev); /* Device removed */ + int (*suspend)(struct amd_gnb_bus_dev *dev, pm_message_t state); + int (*resume)(struct amd_gnb_bus_dev *dev); + void (*shutdown)(struct amd_gnb_bus_dev *dev); + struct device_driver driver; /* generic device driver interface */ +}; + +#define amd_gnb_to_acp_device(x) container_of((x), \ + struct amd_gnb_bus_dev_acp, \ + base) +#define amd_gnb_to_isp_device(x) container_of((x), \ + struct amd_gnb_bus_dev_isp, \ + base) +#define amd_gnb_parent_to_pci_device(x) container_of((x)->dev.parent, \ + struct pci_dev, \ + dev) + +int amd_gnb_bus_register_device(struct amd_gnb_bus_dev *dev); +void amd_gnb_bus_unregister_device(struct amd_gnb_bus_dev *dev); +int amd_gnb_bus_device_init(struct amd_gnb_bus_dev *bus_dev, + enum amd_gnb_bus_ip ip, + char *dev_name, + void *handle, + struct device *parent); +int amd_gnb_bus_register_driver(struct amd_gnb_bus_driver *drv, + struct module *owner, + const char *mod_name); +void amd_gnb_bus_unregister_driver(struct amd_gnb_bus_driver *drv); + +#endif
@@ -134,6 +136,7 @@ config DRM_AMDGPU select HWMON select BACKLIGHT_CLASS_DEVICE select INTERVAL_TREE
- select DRM_AMD_GNB_BUS
Here you select the symbol.
[...]
+config DRM_AMD_GNB_BUS
- tristate "AMD GNB bus - used for GNB IPs such as ACP and ISP"
Here you make it user selectable. Use either or having both doesn't work too well.
+endmenu diff --git a/drivers/gpu/drm/amd/bus/Makefile b/drivers/gpu/drm/amd/bus/Makefile new file mode 100644 index 0000000..c41ffc9 --- /dev/null +++ b/drivers/gpu/drm/amd/bus/Makefile @@ -0,0 +1,4 @@ +# +ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/amd/include/bus/
+obj-$(CONFIG_DRM_AMD_GNB_BUS) := amd_gnb_bus.o diff --git a/drivers/gpu/drm/amd/bus/amd_gnb_bus.c b/drivers/gpu/drm/amd/bus/amd_gnb_bus.c new file mode 100644 index 0000000..071b16c --- /dev/null +++ b/drivers/gpu/drm/amd/bus/amd_gnb_bus.c @@ -0,0 +1,266 @@
[..]
+#ifdef CONFIG_PM_SLEEP +static int amd_gnb_bus_legacy_suspend(struct device *dev, pm_message_t mesg) +{
- struct amd_gnb_bus_dev *amd_gnb_bus_dev = to_amd_gnb_bus_device(dev);
- struct amd_gnb_bus_driver *driver;
- if (!amd_gnb_bus_dev || !dev->driver)
return 0;
- driver = to_amd_gnb_bus_driver(dev->driver);
- if (!driver->suspend)
return 0;
- return driver->suspend(amd_gnb_bus_dev, mesg);
+}
+static int amd_gnb_bus_legacy_resume(struct device *dev) +{
- struct amd_gnb_bus_dev *amd_gnb_bus_dev = to_amd_gnb_bus_device(dev);
- struct amd_gnb_bus_driver *driver;
- if (!amd_gnb_bus_dev || !dev->driver)
return 0;
- driver = to_amd_gnb_bus_driver(dev->driver);
- if (!driver->resume)
return 0;
- return driver->resume(amd_gnb_bus_dev);
+}
[...]
Preferably don't add support for legacy suspend/resume to new subsystems. Support for this is supposed to be removed from the kernel. Just use dev_pm_ops and then you can drop all of the above since the PM core does the right thing on its own for dev_pm_ops. No need to have support at the bus level if there is nothing special to do at the bus level.
[...]
On Thu, Aug 06, 2015 at 10:25:02AM -0400, Alex Deucher wrote:
From: Chunming Zhou david1.zhou@amd.com
This is used by the incoming ACP driver. The DMA engine for the i2s audio codec is part of the GPU.
This exposes an amd gnb bus for the i2s codec to hang off of.
Could you be more specific about what an "amd gnd bus" is please?
+enum amd_gnb_bus_ip {
- AMD_GNB_IP_ACP_DMA,
- AMD_GNB_IP_ACP_I2S,
- AMD_GNB_IP_ACP_PCM,
- AMD_GNB_IP_ISP,
- AMD_GNB_IP_NUM
+};
+struct amd_gnb_bus_dev {
- struct device dev; /* generic device interface */
- enum amd_gnb_bus_ip ip;
- /* private data can be acp_handle/isp_handle etc.*/
- void *private_data;
+};
Looking at the code I'm not seeing too much bus specific except for the above which looks like the sort of device we usually represent as a MFD (with the MFD providing resource distribution and arbitration between various component devices which fit into the subsystem). Why code a new bus for this device?
On Fri, Aug 7, 2015 at 6:25 AM, Mark Brown broonie@kernel.org wrote:
On Thu, Aug 06, 2015 at 10:25:02AM -0400, Alex Deucher wrote:
From: Chunming Zhou david1.zhou@amd.com
This is used by the incoming ACP driver. The DMA engine for the i2s audio codec is part of the GPU.
This exposes an amd gnb bus for the i2s codec to hang off of.
Could you be more specific about what an "amd gnd bus" is please?
It's bus to hang hw blocks of the GPU on that are controlled by other subsystems.
+enum amd_gnb_bus_ip {
AMD_GNB_IP_ACP_DMA,
AMD_GNB_IP_ACP_I2S,
AMD_GNB_IP_ACP_PCM,
AMD_GNB_IP_ISP,
AMD_GNB_IP_NUM
+};
+struct amd_gnb_bus_dev {
struct device dev; /* generic device interface */
enum amd_gnb_bus_ip ip;
/* private data can be acp_handle/isp_handle etc.*/
void *private_data;
+};
Looking at the code I'm not seeing too much bus specific except for the above which looks like the sort of device we usually represent as a MFD (with the MFD providing resource distribution and arbitration between various component devices which fit into the subsystem). Why code a new bus for this device?
Adding Felix who did worked on the design for this. The idea is that there are hw blocks on the GPU that are controlled by drivers that are part of other subsystems. Those drivers need access to resources (e.g., the MMIO aperture) controlled by the GPU driver. I guess this is a MFD of sorts. If this is not the preferred way to handle this type of device, what is? Can you point me to another driver that handles this differently?
Alex
Hi,
To elaborate more on Alex's explanation ...
AMD SOCs have audio (and in the future potentially also camera image signal processors) IPs built into the GNB (graphics north bridge). These IPs are programmed through MMIO registers in the graphics MMIO aperture. They send events to the host through the graphics IRQ. And they use memory that is accessed through the graphics memory controller. Therefore the GPU driver must be involved in programming these IPs.
However, these functions are not exposed to user mode by the graphics driver subsystem in Linux. Audio is handled by ALSA and camera ISPs are handled by V4L2. We want to represent these IPs as separate devices in the device hierarchy, so that ALSA and V4L2 drivers can discover and bind to them using the standard mechanisms.
Therefore we created this "virtual" GNB bus that allows us to create devices in a sensible place in the device hierarchy, as child devices of the GNB. The enum amd_gnb_bus_ip serves as device ID on this bus. The struct amd_gnb_bus_dev represents the device. Private_data is specific to the type of device. It contains high-level interfaces for ALSA drivers to talk to the audio IP and V4L2 driver to talk to the ISP IP. Any direct HW access, memory management and IRQ handling is done inside the GPU driver.
Regards, Felix
On 15-08-07 10:17 AM, Alex Deucher wrote:
On Fri, Aug 7, 2015 at 6:25 AM, Mark Brown broonie@kernel.org wrote:
On Thu, Aug 06, 2015 at 10:25:02AM -0400, Alex Deucher wrote:
From: Chunming Zhou david1.zhou@amd.com
This is used by the incoming ACP driver. The DMA engine for the i2s audio codec is part of the GPU.
This exposes an amd gnb bus for the i2s codec to hang off of.
Could you be more specific about what an "amd gnd bus" is please?
It's bus to hang hw blocks of the GPU on that are controlled by other subsystems.
+enum amd_gnb_bus_ip {
AMD_GNB_IP_ACP_DMA,
AMD_GNB_IP_ACP_I2S,
AMD_GNB_IP_ACP_PCM,
AMD_GNB_IP_ISP,
AMD_GNB_IP_NUM
+};
+struct amd_gnb_bus_dev {
struct device dev; /* generic device interface */
enum amd_gnb_bus_ip ip;
/* private data can be acp_handle/isp_handle etc.*/
void *private_data;
+};
Looking at the code I'm not seeing too much bus specific except for the above which looks like the sort of device we usually represent as a MFD (with the MFD providing resource distribution and arbitration between various component devices which fit into the subsystem). Why code a new bus for this device?
Adding Felix who did worked on the design for this. The idea is that there are hw blocks on the GPU that are controlled by drivers that are part of other subsystems. Those drivers need access to resources (e.g., the MMIO aperture) controlled by the GPU driver. I guess this is a MFD of sorts. If this is not the preferred way to handle this type of device, what is? Can you point me to another driver that handles this differently?
Alex
On Fri, Aug 07, 2015 at 12:16:03PM -0400, Felix Kuehling wrote:
Hi,
To elaborate more on Alex's explanation ...
Please don't top post, reply in line deleting any unneeded context so people have context for what's being discussed.
Therefore we created this "virtual" GNB bus that allows us to create devices in a sensible place in the device hierarchy, as child devices of the GNB. The enum amd_gnb_bus_ip serves as device ID on this bus. The
Like I say this just sounds like exactly the sort of thing we handle with an MFD, it's a very common pattern.
On 15-08-07 02:24 PM, Mark Brown wrote:
Like I say this just sounds like exactly the sort of thing we handle with an MFD, it's a very common pattern.
OK, the MFD documentation in Documentation/devicetree/bindings/mfd/ seemed to imply a dependency on a devicetree. It took me a moment to realize that's just the most common scenario.
In our case we don't have a devicetree, we're talking about a PC platform. The GPU (GNB) is a PCIe device. Does the following sound like proper use of the MFD framework for our case?
1. GPU driver gets initialized, detects a GPU with audio co-processor (ACP) 2. GPU driver registers mfd_cell for the ACP device using mfd_add_hotplug_devices * It's not really hot-plug, but the mem_base, irq_base, irq_domain parameters don't make sense for us 3. Platform_data in the MFD cell contains audio driver-specific data, function pointers, etc. for the audio driver to use 4. Audio driver binds to platform device created by mfd_add_hotplug_devices based on driver name
Or do we have to convert our GPU device to be an MFD cell itself, a peer of the ACP cell?
Thanks, Felix
On Fri, Aug 07, 2015 at 04:03:08PM -0400, Felix Kuehling wrote:
- GPU driver gets initialized, detects a GPU with audio co-processor (ACP)
- GPU driver registers mfd_cell for the ACP device using mfd_add_hotplug_devices
- It's not really hot-plug, but the mem_base, irq_base, irq_domain parameters don't make sense for us
All those should be optional...
- Platform_data in the MFD cell contains audio driver-specific data, function pointers, etc. for the audio driver to use
Note that a MFD knows that its parent is the core device so it can just look at the driver data of the parent unless things vary per child.
- Audio driver binds to platform device created by mfd_add_hotplug_devices based on driver name
Or do we have to convert our GPU device to be an MFD cell itself, a peer of the ACP cell?
If they're all part of the same block of hardware that'd be more normal, but it all depends on what the code looks like and what the relevant maintainers think.
On Fri, Aug 07, 2015 at 10:17:36AM -0400, Alex Deucher wrote:
On Fri, Aug 7, 2015 at 6:25 AM, Mark Brown broonie@kernel.org wrote:
Looking at the code I'm not seeing too much bus specific except for the above which looks like the sort of device we usually represent as a MFD (with the MFD providing resource distribution and arbitration between various component devices which fit into the subsystem). Why code a new bus for this device?
Adding Felix who did worked on the design for this. The idea is that there are hw blocks on the GPU that are controlled by drivers that are part of other subsystems. Those drivers need access to resources (e.g., the MMIO aperture) controlled by the GPU driver. I guess this is a MFD of sorts. If this is not the preferred way to handle this type of device, what is? Can you point me to another driver that handles this differently?
Yeah, this sounds like a MFD - see drivers/mfd and take a look at how the drivers there deal with this.
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
with the default gnb bus runtime pm ops, alsa pcm device attached to it is unable to get runtime suspended/resumed.
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed-by: Alex Deucher alexander.deucher@amd.com --- drivers/gpu/drm/amd/bus/amd_gnb_bus.c | 5 ----- 1 file changed, 5 deletions(-)
diff --git a/drivers/gpu/drm/amd/bus/amd_gnb_bus.c b/drivers/gpu/drm/amd/bus/amd_gnb_bus.c index 071b16c..13d5506 100644 --- a/drivers/gpu/drm/amd/bus/amd_gnb_bus.c +++ b/drivers/gpu/drm/amd/bus/amd_gnb_bus.c @@ -142,11 +142,6 @@ static const struct dev_pm_ops amd_gnb_bus_device_pm_ops = { .thaw = amd_gnb_bus_device_pm_thaw, .poweroff = amd_gnb_bus_device_pm_poweroff, .restore = amd_gnb_bus_device_pm_restore, - SET_RUNTIME_PM_OPS( - pm_generic_runtime_suspend, - pm_generic_runtime_resume, - pm_runtime_idle - ) };
/* The bus should only be registered by the first amd_gnb, but further
From: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com
This adds the ACP (Audio CoProcessor) IP driver and wires it up to the amdgpu driver. The ACP block provides the DMA engine and bus for the i2s codec which is supported by an alsa driver. This is required for audio on APUs that utilize an i2s codec.
v2: integrate i2s/az check patch v3: s/amd_acp/amdgpu_acp/ v4: update copyright notice
Reviewed-by: Jammy Zhou Jammy.Zhou@amd.com Reviewed-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Signed-off-by: Chunming Zhou david1.zhou@amd.com Signed-off-by: Alex Deucher alexander.deucher@amd.com --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/amd/acp/Kconfig | 9 + drivers/gpu/drm/amd/acp/Makefile | 9 + drivers/gpu/drm/amd/acp/acp_hw.c | 964 +++++++++++++++++++++++++++ drivers/gpu/drm/amd/acp/acp_hw.h | 99 +++ drivers/gpu/drm/amd/acp/include/acp_gfx_if.h | 49 ++ drivers/gpu/drm/amd/acp/include/amd_acp.h | 196 ++++++ drivers/gpu/drm/amd/amdgpu/Makefile | 14 +- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 11 + drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 201 ++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h | 40 ++ drivers/gpu/drm/amd/amdgpu/vi.c | 12 + drivers/gpu/drm/amd/include/amd_shared.h | 1 + 13 files changed, 1606 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/amd/acp/Kconfig create mode 100644 drivers/gpu/drm/amd/acp/Makefile create mode 100644 drivers/gpu/drm/amd/acp/acp_hw.c create mode 100644 drivers/gpu/drm/amd/acp/acp_hw.h create mode 100644 drivers/gpu/drm/amd/acp/include/acp_gfx_if.h create mode 100644 drivers/gpu/drm/amd/acp/include/amd_acp.h create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 8c3cc9e..88daeee 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -144,6 +144,8 @@ config DRM_AMDGPU
source "drivers/gpu/drm/amd/amdgpu/Kconfig"
+source "drivers/gpu/drm/amd/acp/Kconfig" + source "drivers/gpu/drm/nouveau/Kconfig"
config DRM_I810 diff --git a/drivers/gpu/drm/amd/acp/Kconfig b/drivers/gpu/drm/amd/acp/Kconfig new file mode 100644 index 0000000..11c5faf --- /dev/null +++ b/drivers/gpu/drm/amd/acp/Kconfig @@ -0,0 +1,9 @@ +menu "ACP Configuration" + +config DRM_AMD_ACP + bool "Enable ACP IP support" + default y + help + Choose this option to enable ACP IP support for AMD SOCs. + +endmenu diff --git a/drivers/gpu/drm/amd/acp/Makefile b/drivers/gpu/drm/amd/acp/Makefile new file mode 100644 index 0000000..c8c3303 --- /dev/null +++ b/drivers/gpu/drm/amd/acp/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the ACP, which is a sub-component +# of AMDSOC/AMDGPU drm driver. +# It provides the HW control for ACP related functionalities. + +ccflags-y += -Idrivers/gpu/drm/amd/include/asic_reg/acp +subdir-ccflags-y += -I$(AMDACPPATH)/ -I$(AMDACPPATH)/include + +AMD_ACP_FILES := $(AMDACPPATH)/acp_hw.o diff --git a/drivers/gpu/drm/amd/acp/acp_hw.c b/drivers/gpu/drm/amd/acp/acp_hw.c new file mode 100644 index 0000000..069fea7 --- /dev/null +++ b/drivers/gpu/drm/amd/acp/acp_hw.c @@ -0,0 +1,964 @@ +/* + * Copyright 2015 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * NOTE: + * Certain pieces were reused from Synopsis I2S IP related code, + * which otherwise can also be found at: + * sound/soc/dwc/designware_i2s.c + * + * Copyright notice as appears in the above file: + * + * Copyright (C) 2010 ST Microelectronics + * Rajeev Kumar rajeevkumar.linux@gmail.com + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/errno.h> + +#define VISLANDS30_IV_SRCID_ACP 0x000000a2 // 162 + +#include "acp_gfx_if.h" +#include "acp_hw.h" + +#include "acp_2_2_d.h" +#include "acp_2_2_sh_mask.h" + +/* Configure a given dma channel parameters - enable/disble, + * number of descriptors, priority */ + +static void config_acp_dma_channel(struct amd_acp_device *acp_dev, u8 ch_num, + u16 dscr_strt_idx, u16 num_dscrs, + enum acp_dma_priority_level priority_level) +{ + u32 dma_ctrl; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + /* read the dma control register and disable the channel run field */ + dma_ctrl = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CNTL_0 + ch_num); + /* clear the dma channel control bits */ + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK; + + cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_CNTL_0 + ch_num), + dma_ctrl); + + /* there is no transfer happening on this channel so + * program DMAChDscrStrIdx to the index number of the first descriptor + * to be processed. + */ + cgs_write_register(acp_prv->cgs_device, + (mmACP_DMA_DSCR_STRT_IDX_0 + ch_num), + (ACP_DMA_DSCR_STRT_IDX_0__DMAChDscrStrtIdx_MASK & + dscr_strt_idx)); + + /* program DMAChDscrDscrCnt to the number of descriptors to be + * processed in the transfer + */ + cgs_write_register(acp_prv->cgs_device, + (mmACP_DMA_DSCR_CNT_0 + ch_num), + (ACP_DMA_DSCR_CNT_0__DMAChDscrCnt_MASK & num_dscrs)); + + /* set DMAChPrioLvl according to the priority */ + cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_PRIO_0 + ch_num), + priority_level); +} + + + +/* Initialize the dma descriptors location in SRAM and page size */ +static void acp_dma_descr_init(struct amd_acp_device *acp_dev) +{ + u32 sram_pte_offset = 0; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + /* SRAM starts at 0x04000000. From that offset one page (4KB) left for + * filling DMA descriptors.sram_pte_offset = 0x04001000 , used for + * filling system RAM's physical pages. + * This becomes the ALSA's Ring buffer start address + */ + sram_pte_offset = ACP_DAGB_GRP_SRAM_BASE_ADDRESS; + + /* snoopable */ + sram_pte_offset |= ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBSnoopSel_MASK; + /* Memmory is system mmemory */ + sram_pte_offset |= ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBTargetMemSel_MASK; + /* Page Enabled */ + sram_pte_offset |= ACP_DAGB_BASE_ADDR_GRP_1__AXI2DAGBGrpEnable_MASK; + + cgs_write_register(acp_prv->cgs_device, mmACP_DAGB_BASE_ADDR_GRP_1, + sram_pte_offset); + cgs_write_register(acp_prv->cgs_device, mmACP_DAGB_PAGE_SIZE_GRP_1, + PAGE_SIZE_4K_ENABLE); +} + +/* Initialize a dma descriptor in SRAM based on descritor information passed */ +static void config_dma_descriptor_in_sram(struct amd_acp_device *acp_dev, + u16 descr_idx, + acp_dma_dscr_transfer_t *descr_info) +{ + u32 sram_offset; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + sram_offset = (descr_idx * sizeof(acp_dma_dscr_transfer_t)); + + /* program the source base address. */ + cgs_write_register(acp_prv->cgs_device, mmACP_SRBM_Targ_Idx_Addr, + sram_offset); + cgs_write_register(acp_prv->cgs_device, mmACP_SRBM_Targ_Idx_Data, + descr_info->src); + /* program the destination base address. */ + cgs_write_register(acp_prv->cgs_device, mmACP_SRBM_Targ_Idx_Addr, + (sram_offset + 4)); + cgs_write_register(acp_prv->cgs_device, mmACP_SRBM_Targ_Idx_Data, + descr_info->dest); + + /* program the number of bytes to be transferred for this descriptor. */ + cgs_write_register(acp_prv->cgs_device, mmACP_SRBM_Targ_Idx_Addr, + (sram_offset + 8)); + cgs_write_register(acp_prv->cgs_device, mmACP_SRBM_Targ_Idx_Data, + descr_info->size_xfer_dir.val); +} + +/* Initialize the DMA descriptor information */ +static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, + u32 size, int direction, u32 pte_offset) +{ + u16 dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH12; + u16 num_descr; + acp_dma_dscr_transfer_t dmadscr[2]; + + num_descr = 2; + + dmadscr[0].size_xfer_dir.val = (u32) 0x0; + if (direction == STREAM_PLAYBACK) { + dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH12; + dmadscr[0].dest = ACP_SHARED_RAM_BANK_38_ADDRESS; + dmadscr[0].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + + (pte_offset * PAGE_SIZE_4K); + dmadscr[0].size_xfer_dir.s.trans_direction = + ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM; + } else if (direction == STREAM_CAPTURE) { + dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH14; + dmadscr[0].src = ACP_SHARED_RAM_BANK_47_ADDRESS; + dmadscr[0].dest = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + + (pte_offset * PAGE_SIZE_4K); + dmadscr[0].size_xfer_dir.s.trans_direction = + ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION; + } + + /* allot 1 period size per descriptor = total size (size) /2 + * => params_buffer_bytes(params)/params_periods(params); + */ + dmadscr[0].size_xfer_dir.s.size = size / 2; + + dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x0; + + config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[0]); + + dmadscr[1].size_xfer_dir.val = (u32) 0x0; + dmadscr[1].dest = dmadscr[0].dest + dmadscr[0].size_xfer_dir.s.size; + dmadscr[1].src = dmadscr[0].src + dmadscr[0].size_xfer_dir.s.size; + dmadscr[1].size_xfer_dir.s.size = dmadscr[0].size_xfer_dir.s.size; + dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x0; + + if (direction == STREAM_PLAYBACK) { + dma_dscr_idx = PLAYBACK_END_DMA_DESCR_CH12; + dmadscr[1].size_xfer_dir.s.trans_direction = + ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM; + } else if (direction == STREAM_CAPTURE) { + dma_dscr_idx = CAPTURE_END_DMA_DESCR_CH14; + dmadscr[1].size_xfer_dir.s.trans_direction = + ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION; + } + + config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[1]); + + if (direction == STREAM_PLAYBACK) { + /* starting descriptor for this channel */ + dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH12; + config_acp_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM, + dma_dscr_idx, num_descr, + ACP_DMA_PRIORITY_LEVEL_NORMAL); + } else if (direction == STREAM_CAPTURE) { + /* starting descriptor for this channel */ + dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH14; + config_acp_dma_channel(acp_dev, ACP_TO_SYSRAM_CH_NUM, + dma_dscr_idx, num_descr, + ACP_DMA_PRIORITY_LEVEL_NORMAL); + } +} + +/* Initialize the i2s dma descriptors in SRAM */ +static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev, + u32 size, int direction) +{ + + u16 num_descr; + u16 dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH13; + acp_dma_dscr_transfer_t dmadscr[2]; + + num_descr = 2; + + /* Let I2s Know the direction of transfer and source/destination + * of data + */ + dmadscr[0].size_xfer_dir.val = (u32) 0x0; + dmadscr[0].size_xfer_dir.s.size = (size / 2); + dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; + if (direction == STREAM_PLAYBACK) { + dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH13; + dmadscr[0].src = ACP_SHARED_RAM_BANK_38_ADDRESS; + dmadscr[0].size_xfer_dir.s.trans_direction = TO_ACP_I2S_1; + } else if (direction == STREAM_CAPTURE) { + dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH15; + dmadscr[0].dest = ACP_SHARED_RAM_BANK_47_ADDRESS; + dmadscr[0].size_xfer_dir.s.trans_direction = 0xa; + } + config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[0]); + + dmadscr[1].size_xfer_dir.val = (u32) 0x0; + dmadscr[1].size_xfer_dir.s.size = (size / 2); + dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x1; + if (direction == STREAM_PLAYBACK) { + dma_dscr_idx = PLAYBACK_END_DMA_DESCR_CH13; + dmadscr[1].src = dmadscr[0].src + + dmadscr[0].size_xfer_dir.s.size; + dmadscr[1].size_xfer_dir.s.trans_direction = TO_ACP_I2S_1; + + } else if (direction == STREAM_CAPTURE) { + dma_dscr_idx = CAPTURE_END_DMA_DESCR_CH15; + dmadscr[1].dest = dmadscr[0].dest + + dmadscr[0].size_xfer_dir.s.size; + dmadscr[1].size_xfer_dir.s.trans_direction = 0xa; + } + config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[1]); + + /* Configure the DMA channel with the above descriptore */ + if (direction == STREAM_PLAYBACK) { + /* starting descriptor for this channel */ + dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH13; + config_acp_dma_channel(acp_dev, ACP_TO_I2S_DMA_CH_NUM, + dma_dscr_idx, num_descr, + ACP_DMA_PRIORITY_LEVEL_NORMAL); + } else if (direction == STREAM_CAPTURE) { + /* starting descriptor for this channel */ + dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH15; + config_acp_dma_channel(acp_dev, I2S_TO_ACP_DMA_CH_NUM, + dma_dscr_idx, num_descr, + ACP_DMA_PRIORITY_LEVEL_NORMAL); + } + +} + +static u16 get_dscr_idx(struct amd_acp_device *acp_dev, int direction) +{ + u16 dscr_idx; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + if (direction == STREAM_PLAYBACK) { + dscr_idx = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CUR_DSCR_13); + dscr_idx = (dscr_idx == PLAYBACK_START_DMA_DESCR_CH13) ? + PLAYBACK_END_DMA_DESCR_CH12 : + PLAYBACK_START_DMA_DESCR_CH12; + } else { + dscr_idx = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CUR_DSCR_15); + dscr_idx = (dscr_idx == CAPTURE_START_DMA_DESCR_CH15) ? + CAPTURE_END_DMA_DESCR_CH14 : + CAPTURE_START_DMA_DESCR_CH14; + } + + return dscr_idx; + +} + +/* Create page table entries in ACP SRAM for the allocated memory */ +static void acp_pte_config(struct amd_acp_device *acp_dev, struct page *pg, + u16 num_of_pages, u32 pte_offset) +{ + u16 page_idx; + u64 addr; + u32 low; + u32 high; + u32 offset; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + offset = ACP_DAGB_GRP_SRBM_SRAM_BASE_OFFSET + (pte_offset * 8); + for (page_idx = 0; page_idx < (num_of_pages); page_idx++) { + /* Load the low address of page int ACP SRAM through SRBM */ + cgs_write_register(acp_prv->cgs_device, + mmACP_SRBM_Targ_Idx_Addr, + (offset + (page_idx * 8))); + addr = page_to_phys(pg); + + low = lower_32_bits(addr); + high = upper_32_bits(addr); + + cgs_write_register(acp_prv->cgs_device, + mmACP_SRBM_Targ_Idx_Data, low); + + /* Load the High address of page int ACP SRAM through SRBM */ + cgs_write_register(acp_prv->cgs_device, + mmACP_SRBM_Targ_Idx_Addr, + (offset + (page_idx * 8) + 4)); + + /* page enable in ACP */ + high |= BIT(31); + cgs_write_register(acp_prv->cgs_device, + mmACP_SRBM_Targ_Idx_Data, high); + + /* Move to next physically contiguos page */ + pg++; + } +} + + +/* enables/disables ACP's external interrupt */ +static void acp_enable_external_interrupts(struct amd_acp_device *acp_dev, + int enable) +{ + u32 acp_ext_intr_enb; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + acp_ext_intr_enb = enable ? + ACP_EXTERNAL_INTR_ENB__ACPExtIntrEnb_MASK : + 0; + + /* Write the Software External Interrupt Enable register */ + cgs_write_register(acp_prv->cgs_device, + mmACP_EXTERNAL_INTR_ENB, acp_ext_intr_enb); +} + +/* Clear (acknowledge) DMA 'Interrupt on Complete' (IOC) in ACP + * external interrupt status register + */ +static void acp_ext_stat_clear_dmaioc(struct amd_acp_device *acp_dev, u8 ch_num) +{ + u32 ext_intr_stat; + u32 chmask = BIT(ch_num); + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + ext_intr_stat = cgs_read_register(acp_prv->cgs_device, + mmACP_EXTERNAL_INTR_STAT); + if (ext_intr_stat & (chmask << + ACP_EXTERNAL_INTR_STAT__DMAIOCStat__SHIFT)) { + + ext_intr_stat &= (chmask << + ACP_EXTERNAL_INTR_STAT__DMAIOCAck__SHIFT); + cgs_write_register(acp_prv->cgs_device, + mmACP_EXTERNAL_INTR_STAT, ext_intr_stat); + } +} + +/* Check whether interrupt (IOC) is generated or not */ +static u16 acp_get_intr_flag(struct amd_acp_device *acp_dev) +{ + u32 ext_intr_status; + u32 intr_gen; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + ext_intr_status = cgs_read_register(acp_prv->cgs_device, + mmACP_EXTERNAL_INTR_STAT); + intr_gen = (((ext_intr_status & + ACP_EXTERNAL_INTR_STAT__DMAIOCStat_MASK) >> + ACP_EXTERNAL_INTR_STAT__DMAIOCStat__SHIFT)); + + return intr_gen; +} + +static int irq_set_source(void *private_data, unsigned src_id, unsigned type, + int enabled) +{ + struct amd_acp_device *acp_dev = + ((struct acp_irq_prv *)private_data)->acp_dev; + + if (src_id == VISLANDS30_IV_SRCID_ACP) { + acp_enable_external_interrupts(acp_dev, enabled); + return 0; + } else { + return -1; + } +} + + +static inline void i2s_clear_irqs(struct amd_acp_device *acp_dev, + int direction) +{ + u32 i = 0; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + if (direction == STREAM_PLAYBACK) { + for (i = 0; i < 4; i++) + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SSP_TOR0 + (0x10 * i)), 0); + } else { + for (i = 0; i < 4; i++) + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SMICSP_ROR0 +(0x10 * i)), 0); + } +} + +static void i2s_disable_channels(struct amd_acp_device *acp_dev, + u32 stream) +{ + u32 i = 0; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + if (stream == STREAM_PLAYBACK) { + for (i = 0; i < 4; i++) + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SSP_TER0 + (0x10 * i)), 0); + } else { + for (i = 0; i < 4; i++) + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SMICSP_RER0 + (0x10 * i)), 0); + } +} + +static void configure_i2s_stream(struct amd_acp_device *acp_dev, + struct acp_i2s_config *i2s_config) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + if (i2s_config->direction == STREAM_PLAYBACK) { + /* Transmit configuration register for data width */ + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SSP_TCR0 + (0x10 * + i2s_config->ch_reg)), + i2s_config->xfer_resolution); + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SSP_TFCR0 + (0x10 * + i2s_config->ch_reg)), + 0x02); + + /* Read interrupt mask register */ + i2s_config->irq = + cgs_read_register(acp_prv->cgs_device, + (mmACP_I2SSP_IMR0 + + (0x10 * i2s_config->ch_reg))); + /* TX FIFO Overrun,Empty interrupts */ + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SSP_IMR0 + (0x10 * + i2s_config->ch_reg)), + (i2s_config->irq & ~0x30)); + /*Enable Transmit */ + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SSP_TER0 + (0x10 * + i2s_config->ch_reg)), 1); + } else { + /* Receive configuration register for data width */ + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SMICSP_RCR0 + (0x10 * + i2s_config->ch_reg)), + i2s_config->xfer_resolution); + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SMICSP_RFCR0 + (0x10 * + i2s_config->ch_reg)), 0x07); + /*Read interrupt mask register */ + i2s_config->irq = cgs_read_register(acp_prv->cgs_device, + (mmACP_I2SMICSP_IMR0 + + (0x10 * i2s_config->ch_reg))); + + /* TX FIFO Overrun,Empty interrupts */ + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SMICSP_IMR0 + (0x10 * + i2s_config->ch_reg)), + i2s_config->irq & ~0x03); + /*Enable Receive */ + cgs_write_register(acp_prv->cgs_device, + (mmACP_I2SMICSP_RER0 + (0x10 * + i2s_config->ch_reg)), 1); + + } +} + +static void config_acp_dma(struct amd_acp_device *acp_dev, + struct acp_dma_config *dma_config) +{ + u32 pte_offset; + + if (dma_config->direction == STREAM_PLAYBACK) + pte_offset = PLAYBACK_PTE_OFFSET; + else + pte_offset = CAPTURE_PTE_OFFSET; + + acp_pte_config(acp_dev, dma_config->pg, dma_config->num_of_pages, + pte_offset); + + /* Configure System memory to acp dma descriptors */ + set_acp_sysmem_dma_descriptors(acp_dev, dma_config->size, + dma_config->direction, + pte_offset); + + /* Configure acp to i2s dma descriptors */ + set_acp_to_i2s_dma_descriptors(acp_dev, dma_config->size, + dma_config->direction); +} + +/* Start a given dma channel */ +static int acp_dma_start(struct amd_acp_device *acp_dev, + u16 ch_num, bool is_circular) +{ + int status; + u32 dma_ctrl; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + status = STATUS_UNSUCCESSFUL; + + /* read the dma control register and disable the channel run field */ + dma_ctrl = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CNTL_0 + ch_num); + + /*Invalidating the DAGB cache */ + cgs_write_register(acp_prv->cgs_device, mmACP_DAGB_ATU_CTRL, ENABLE); + + /* configure the DMA channel and start the DMA transfer + * set dmachrun bit to start the transfer and enable the + * interrupt on completion of the dma transfer + */ + dma_ctrl |= ACP_DMA_CNTL_0__DMAChRun_MASK; + + if ((ch_num == ACP_TO_I2S_DMA_CH_NUM) || + (ch_num == I2S_TO_ACP_DMA_CH_NUM)) { + dma_ctrl |= ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + cgs_irq_get(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 0); + } else { + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + } + + /* enable for ACP SRAM to/from I2S DMA channel */ + if (is_circular == true) + dma_ctrl |= ACP_DMA_CNTL_0__Circular_DMA_En_MASK; + else + dma_ctrl &= ~ACP_DMA_CNTL_0__Circular_DMA_En_MASK; + + cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_CNTL_0 + ch_num), + dma_ctrl); + + status = STATUS_SUCCESS; + + return status; +} + +/* Stop a given dma channel number*/ +static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num) +{ + int status = STATUS_UNSUCCESSFUL; + u32 dma_ctrl; + u32 dma_ch_sts; + u32 delay_time = ACP_DMA_RESET_TIME; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + if (acp_dev == NULL) + return status; + + /* register mask value to check the channel status bits */ + dma_ctrl = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CNTL_0 + ch_num); + + /* clear the dma control register fields before writing zero + * in reset bit + */ + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK; + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK; + + cgs_write_register(acp_prv->cgs_device, + (mmACP_DMA_CNTL_0 + ch_num), dma_ctrl); + dma_ch_sts = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CH_STS); + + if (dma_ch_sts & BIT(ch_num)) { + /* set the reset bit for this channel + * to stop the dma transfer */ + dma_ctrl |= ACP_DMA_CNTL_0__DMAChRst_MASK; + cgs_write_register(acp_prv->cgs_device, + (mmACP_DMA_CNTL_0 + ch_num), dma_ctrl); + } + + /* if channel transfer is not stopped with in time delay + * return this status */ + status = -EBUSY; + + /* check the channel status bit for some time and return the status */ + while (0 < delay_time) { + dma_ch_sts = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CH_STS); + if (!(dma_ch_sts & BIT(ch_num))) { + /* clear the reset flag after successfully stopping + the dma transfer and break from the loop */ + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK; + + cgs_write_register(acp_prv->cgs_device, + (mmACP_DMA_CNTL_0 + ch_num), dma_ctrl); + status = STATUS_SUCCESS; + break; + } + delay_time--; + } + + if ((ch_num == ACP_TO_I2S_DMA_CH_NUM) || + (ch_num == I2S_TO_ACP_DMA_CH_NUM)) { + cgs_irq_put(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 0); + } + + return status; +} + +static int dma_irq_handler(void *prv_data) +{ + u16 play_intr, capture_intr; + u16 dscr_idx, intr_flag; + int priority_level = 0x0; + int dma_transfer_status = STATUS_UNSUCCESSFUL; + struct acp_irq_prv *idata = prv_data; + struct amd_acp_device *acp_dev = idata->acp_dev; + + intr_flag = acp_get_intr_flag(acp_dev); + play_intr = (intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)); + capture_intr = (intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM)); + + if (!play_intr && !capture_intr) { + /* We registered for DMA Interrupt-On-Complete interrupts only. + * If we hit here, just return. */ + pr_info("ACP:irq_handler: play_intr && capture_intr = false\n"); + return 0; + } + + if (play_intr) { + dscr_idx = get_dscr_idx(acp_dev, STREAM_PLAYBACK); + config_acp_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM, dscr_idx, + 1, priority_level); + dma_transfer_status = acp_dma_start(acp_dev, + SYSRAM_TO_ACP_CH_NUM, + false); + idata->set_elapsed(idata->dev, play_intr, capture_intr); + + acp_ext_stat_clear_dmaioc(acp_dev, ACP_TO_I2S_DMA_CH_NUM); + } + + if (capture_intr) { + dscr_idx = get_dscr_idx(acp_dev, STREAM_CAPTURE); + config_acp_dma_channel(acp_dev, ACP_TO_SYSRAM_CH_NUM, dscr_idx, + 1, priority_level); + dma_transfer_status = acp_dma_start(acp_dev, + ACP_TO_SYSRAM_CH_NUM, + false); + idata->set_elapsed(idata->dev, play_intr, capture_intr); + + acp_ext_stat_clear_dmaioc(acp_dev, I2S_TO_ACP_DMA_CH_NUM); + } + return 0; +} + +static int irq_handler(void *private_data, unsigned src_id, + const uint32_t *iv_entry) +{ + if (src_id == VISLANDS30_IV_SRCID_ACP) + return dma_irq_handler(private_data); + else + return -1; +} + +/* Initialize and bring ACP hardware to default state. */ +static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) +{ + u32 val; + u32 timeout_value; + int acp_hw_init_status = STATUS_UNSUCCESSFUL; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + if (acp_dev == NULL) + return acp_hw_init_status; + + /* Assert Soft reset of ACP */ + val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET); + + val |= ACP_SOFT_RESET__SoftResetAud_MASK; + cgs_write_register(acp_prv->cgs_device, + mmACP_SOFT_RESET, val); + + timeout_value = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (timeout_value--) { + val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET); + if (ACP_SOFT_RESET__SoftResetAudDone_MASK == + (val & ACP_SOFT_RESET__SoftResetAudDone_MASK)) + break; + } + + /* Enabling clock to ACP and waits until the clock is enabled */ + val = cgs_read_register(acp_prv->cgs_device, mmACP_CONTROL); + val = val | ACP_CONTROL__ClkEn_MASK; + cgs_write_register(acp_prv->cgs_device, mmACP_CONTROL, val); + + timeout_value = ACP_CLOCK_EN_TIME_OUT_VALUE; + + while (timeout_value--) { + val = cgs_read_register(acp_prv->cgs_device, mmACP_STATUS); + if (val & (u32) 0x1) + break; + udelay(100); + } + + /* Deassert the SOFT RESET flags */ + val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET); + val &= ~ACP_SOFT_RESET__SoftResetAud_MASK; + cgs_write_register(acp_prv->cgs_device, mmACP_SOFT_RESET, val); + + /* initiailizing Garlic Control DAGB register */ + cgs_write_register(acp_prv->cgs_device, mmACP_AXI2DAGB_ONION_CNTL, + ONION_CNTL_DEFAULT); + + /* initiailizing Onion Control DAGB registers */ + cgs_write_register(acp_prv->cgs_device, mmACP_AXI2DAGB_GARLIC_CNTL, + GARLIC_CNTL_DEFAULT); + + acp_dma_descr_init(acp_dev); + + /* DMA DSCR BASE ADDRESS IN SRAM */ + cgs_write_register(acp_prv->cgs_device, mmACP_DMA_DESC_BASE_ADDR, + ACP_SRAM_BASE_ADDRESS); + + /* Num of descriptiors in SRAM 0x4, means 256 descriptors;(64 * 4) */ + cgs_write_register(acp_prv->cgs_device, mmACP_DMA_DESC_MAX_NUM_DSCR, + 0x4); + + cgs_write_register(acp_prv->cgs_device, mmACP_EXTERNAL_INTR_CNTL, + ACP_EXTERNAL_INTR_CNTL__DMAIOCMask_MASK); + + cgs_add_irq_source(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 1, + irq_set_source, irq_handler, iprv); + + pr_info("ACP: Initialized.\n"); + return STATUS_SUCCESS; +} + +static void acp_hw_deinit(struct amd_acp_device *acp_dev) +{ + u32 val; + u32 timeout_value; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + /* Assert Soft reset of ACP */ + val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET); + + val |= ACP_SOFT_RESET__SoftResetAud_MASK; + cgs_write_register(acp_prv->cgs_device, mmACP_SOFT_RESET, val); + + timeout_value = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (timeout_value--) { + val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET); + if (ACP_SOFT_RESET__SoftResetAudDone_MASK == + (val & ACP_SOFT_RESET__SoftResetAudDone_MASK)) { + break; + } + } + /** Disable ACP clock */ + val = cgs_read_register(acp_prv->cgs_device, mmACP_CONTROL); + val &= ~ACP_CONTROL__ClkEn_MASK; + cgs_write_register(acp_prv->cgs_device, mmACP_CONTROL, val); + + timeout_value = ACP_CLOCK_EN_TIME_OUT_VALUE; + + while (timeout_value--) { + val = cgs_read_register(acp_prv->cgs_device, mmACP_STATUS); + if (!(val & (u32) 0x1)) + break; + udelay(100); + } + + pr_info("ACP: De-Initialized.\n"); +} + + +/* Get the number of bytes consumed for SRAM_TO_I2S DMA + * channel during rendering + */ +static u32 acp_update_dma_pointer(struct amd_acp_device *acp_dev, int direction, + u32 period_size) +{ + u32 pos; + u16 dscr; + u32 mul; + u32 dma_config; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + pos = 0; + + if (direction == STREAM_PLAYBACK) { + dscr = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CUR_DSCR_13); + pos = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CUR_TRANS_CNT_13); + /* dscr = either 2 or 3 only */ + mul = (dscr == PLAYBACK_START_DMA_DESCR_CH13) ? 1 : 0; + pos = (mul * period_size) + pos; + } else if (direction == STREAM_CAPTURE) { + dscr = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CUR_DSCR_15); + pos = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CUR_TRANS_CNT_15); + dma_config = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CNTL_14); + if (dma_config != 0) { + mul = (dscr == CAPTURE_START_DMA_DESCR_CH15) ? 1 : 0; + pos = (mul * period_size) + pos; + } + } + + return pos; +} + +/* Wait for complete buffering to complete in HOST + * to SRAM DMA channel + */ +static void wait_for_prebuffer_finish(struct amd_acp_device *acp_dev) +{ + u32 dma_ch_sts; + u32 channel_mask = BIT(SYSRAM_TO_ACP_CH_NUM); + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + do { + /* Read the channel status to poll dma transfer completion + * (System RAM to SRAM) + * In this case, it will be runtime->start_threshold + * (2 ALSA periods) of transfer. Rendering starts after this + * threshold is met. + */ + dma_ch_sts = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CH_STS); + udelay(20); + } while (dma_ch_sts & channel_mask); +} + +static void i2s_reset(struct amd_acp_device *acp_dev, int direction) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + if (direction == STREAM_PLAYBACK) + cgs_write_register(acp_prv->cgs_device, mmACP_I2SSP_TXFFR, 1); + else + cgs_write_register(acp_prv->cgs_device, + mmACP_I2SMICSP_RXFFR, 1); + +} + +static void i2s_start(struct amd_acp_device *acp_dev, int direction) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + if (direction == STREAM_PLAYBACK) { + cgs_write_register(acp_prv->cgs_device, mmACP_I2SSP_IER, 1); + cgs_write_register(acp_prv->cgs_device, mmACP_I2SSP_ITER, 1); + + } else { + cgs_write_register(acp_prv->cgs_device, mmACP_I2SMICSP_IER, 1); + cgs_write_register(acp_prv->cgs_device, mmACP_I2SMICSP_IRER, 1); + } + + cgs_write_register(acp_prv->cgs_device, mmACP_I2SSP_CER, 1); +} + +static void i2s_stop(struct amd_acp_device *acp_dev, int direction) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + i2s_clear_irqs(acp_dev, direction); + + if (direction == STREAM_PLAYBACK) + cgs_write_register(acp_prv->cgs_device, mmACP_I2SSP_ITER, 0); + else + cgs_write_register(acp_prv->cgs_device, mmACP_I2SMICSP_IRER, 0); + + if (direction == STREAM_PLAYBACK) { + cgs_write_register(acp_prv->cgs_device, mmACP_I2SSP_CER, 0); + cgs_write_register(acp_prv->cgs_device, mmACP_I2SSP_IER, 0); + } else { + cgs_write_register(acp_prv->cgs_device, mmACP_I2SMICSP_CER, 0); + cgs_write_register(acp_prv->cgs_device, mmACP_I2SMICSP_IER, 0); + } +} + +static void configure_i2s(struct amd_acp_device *acp_dev, + struct acp_i2s_config *i2s_config) +{ + i2s_disable_channels(acp_dev, i2s_config->direction); + configure_i2s_stream(acp_dev, i2s_config); +} + +int amd_acp_hw_init(void *cgs_device, + unsigned acp_version_major, unsigned acp_version_minor, + struct amd_acp_private **acp_private) +{ + unsigned int acp_mode = ACP_MODE_I2S; + + if ((acp_version_major == 2) && (acp_version_minor == 2)) + acp_mode = cgs_read_register(cgs_device, + mmACP_AZALIA_I2S_SELECT); + + if (acp_mode != ACP_MODE_I2S) + return -ENODEV; + + *acp_private = kzalloc(sizeof(struct amd_acp_private), GFP_KERNEL); + if (*acp_private == NULL) + return -ENOMEM; + + (*acp_private)->cgs_device = cgs_device; + (*acp_private)->acp_version_major = acp_version_major; + (*acp_private)->acp_version_minor = acp_version_minor; + + (*acp_private)->public.init = acp_hw_init; + (*acp_private)->public.fini = acp_hw_deinit; + (*acp_private)->public.config_dma = config_acp_dma; + (*acp_private)->public.config_dma_channel = config_acp_dma_channel; + (*acp_private)->public.dma_start = acp_dma_start; + (*acp_private)->public.dma_stop = acp_dma_stop; + (*acp_private)->public.update_dma_pointer = acp_update_dma_pointer; + (*acp_private)->public.prebuffer_audio = wait_for_prebuffer_finish; + + (*acp_private)->public.i2s_reset = i2s_reset; + (*acp_private)->public.config_i2s = configure_i2s; + (*acp_private)->public.i2s_start = i2s_start; + (*acp_private)->public.i2s_stop = i2s_stop; + + return 0; +} + +int amd_acp_hw_fini(struct amd_acp_private *acp_private) +{ + kfree(acp_private); + return 0; +} + +void amd_acp_suspend(struct amd_acp_private *acp_private) +{ + /* TODO */ +} + +void amd_acp_resume(struct amd_acp_private *acp_private) +{ + /* TODO */ +} diff --git a/drivers/gpu/drm/amd/acp/acp_hw.h b/drivers/gpu/drm/amd/acp/acp_hw.h new file mode 100644 index 0000000..384d97d --- /dev/null +++ b/drivers/gpu/drm/amd/acp/acp_hw.h @@ -0,0 +1,99 @@ +#ifndef __ACP_HW_H +#define __ACP_HW_H + +#define ACP_MODE_I2S 0 +#define ACP_MODE_AZ 1 + +#define DISABLE 0 +#define ENABLE 1 + +#define PAGE_SIZE_4K 4096 +#define PAGE_SIZE_4K_ENABLE 0x02 + +#define PLAYBACK_PTE_OFFSET 10 +#define CAPTURE_PTE_OFFSET 0 + +#define GARLIC_CNTL_DEFAULT 0x00000FB4 +#define ONION_CNTL_DEFAULT 0x00000FB4 + +#define ACP_PHYSICAL_BASE 0x14000 + +/* Playback SRAM address (as a destination in dma descriptor) */ +#define ACP_SHARED_RAM_BANK_38_ADDRESS 0x404A000 + +/* Capture SRAM address (as a source in dma descriptor) */ +#define ACP_SHARED_RAM_BANK_47_ADDRESS 0x405C000 + +#define ACP_DMA_RESET_TIME 10000 +#define ACP_CLOCK_EN_TIME_OUT_VALUE 0x000000FF +#define ACP_SOFT_RESET_DONE_TIME_OUT_VALUE 0x000000FF +#define ACP_DMA_COMPLETE_TIME_OUT_VALUE 0x000000FF + +#define ACP_SRAM_BASE_ADDRESS 0x4000000 +#define ACP_DAGB_GRP_SRAM_BASE_ADDRESS 0x4001000 +#define ACP_DAGB_GRP_SRBM_SRAM_BASE_OFFSET 0x1000 +#define ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS 0x00000000 +#define ACP_INTERNAL_APERTURE_WINDOW_4_ADDRESS 0x01800000 + +enum { + STREAM_PLAYBACK = 0, + STREAM_CAPTURE, + STREAM_LAST = STREAM_CAPTURE, +}; + +enum { + To_DAGB_O = 0x0, + To_DAGB_G, + TO_ACP_I2S_1, + TO_BLUETOOTH, + TO_ACP_I2S_2, + FROM_DAGB_O, + FROM_DAGB_G, + FROM_ACP_I2S_1, + FROM_ACP_I2S_2 +}; + +enum { + ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION = 0x0, + ACP_DMA_ATTRIBUTES_SHARED_MEM_TO_DAGB_GARLIC = 0x1, + ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM = 0x8, + ACP_DMA_ATTRIBUTES_DAGB_GARLIC_TO_SHAREDMEM = 0x9, + ACP_DMA_ATTRIBUTES_FORCE_SIZE = 0xF +}; + +typedef struct acp_dma_dscr_size_transfer_direction { + /* Specifies the number of bytes need to be transferred + * from source to destination memory. */ + u32 size:16; + /* Specifies transfer direction. */ + u32 trans_direction:4; + /* reserved. */ + u32 reserved1:2; + /* Specifies the IOC enable or not for descriptor. */ + /* Defaultly this will be 0, for the last descriptor, make it Enable */ + u32 ioc:1; + /* reserved. */ + u32 reserved2:9; +} acp_dma_dscr_size_transfer_direction_t; + +typedef union { + u32 val:32; + acp_dma_dscr_size_transfer_direction_t s; +} acp_dma_dscr_size_transfer_direction_u; + +typedef struct acp_dma_dscr_transfer { + /* Specifies the source memory location for the DMA data transfer. */ + u32 src; + /* Specifies the destination memory location to where the data will + be transferred. + */ + u32 dest; + /* Specifies the number of bytes need to be transferred + * from source to destination memory.Transfer direction & IOC enable + */ + acp_dma_dscr_size_transfer_direction_u size_xfer_dir; + /** Reserved for future use */ + u32 reserved; +} acp_dma_dscr_transfer_t; + +#endif /*__ACP_HW_H */ diff --git a/drivers/gpu/drm/amd/acp/include/acp_gfx_if.h b/drivers/gpu/drm/amd/acp/include/acp_gfx_if.h new file mode 100644 index 0000000..83662d6 --- /dev/null +++ b/drivers/gpu/drm/amd/acp/include/acp_gfx_if.h @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * +*/ + +#ifndef _ACP_GFX_IF_H +#define _ACP_GFX_IF_H + +#include <linux/types.h> +#include "cgs_linux.h" +#include "cgs_common.h" +#include "amd_acp.h" + +struct amd_acp_private { + /* The public struture is first, so that pointers can be cast + * between the public and private structure */ + struct amd_acp_device public; + + /* private elements not expose through the bus interface */ + void *cgs_device; + unsigned acp_version_major, acp_version_minor; +}; + +int amd_acp_hw_init(void *cgs_device, + unsigned acp_version_major, unsigned acp_version_minor, + struct amd_acp_private **apriv); +int amd_acp_hw_fini(struct amd_acp_private *apriv); +void amd_acp_suspend(struct amd_acp_private *acp_private); +void amd_acp_resume(struct amd_acp_private *acp_private); + +#endif /* _ACP_GFX_IF_H */ diff --git a/drivers/gpu/drm/amd/acp/include/amd_acp.h b/drivers/gpu/drm/amd/acp/include/amd_acp.h new file mode 100644 index 0000000..988d72a --- /dev/null +++ b/drivers/gpu/drm/amd/acp/include/amd_acp.h @@ -0,0 +1,196 @@ +/* + * Copyright 2015 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * +*/ + +#ifndef _AMD_ACP_H +#define _AMD_ACP_H + +#include <linux/types.h> + +/* Playback DMA channels */ +#define SYSRAM_TO_ACP_CH_NUM 12 +#define ACP_TO_I2S_DMA_CH_NUM 13 + +/* Capture DMA channels */ +#define ACP_TO_SYSRAM_CH_NUM 14 +#define I2S_TO_ACP_DMA_CH_NUM 15 + +#define PLAYBACK_START_DMA_DESCR_CH12 0 +#define PLAYBACK_END_DMA_DESCR_CH12 1 + +#define PLAYBACK_START_DMA_DESCR_CH13 2 +#define PLAYBACK_END_DMA_DESCR_CH13 3 + + +#define CAPTURE_START_DMA_DESCR_CH14 4 +#define CAPTURE_END_DMA_DESCR_CH14 5 + +#define CAPTURE_START_DMA_DESCR_CH15 6 +#define CAPTURE_END_DMA_DESCR_CH15 7 + +#define STATUS_SUCCESS 0 +#define STATUS_UNSUCCESSFUL -1 + +enum acp_dma_priority_level { + /* 0x0 Specifies the DMA channel is given normal priority */ + ACP_DMA_PRIORITY_LEVEL_NORMAL = 0x0, + /* 0x1 Specifies the DMA channel is given high priority */ + ACP_DMA_PRIORITY_LEVEL_HIGH = 0x1, + ACP_DMA_PRIORITY_LEVEL_FORCESIZE = 0xFF +}; + +struct acp_dma_config { + struct page *pg; + u16 num_of_pages; + u16 direction; + uint64_t size; +}; + +struct acp_i2s_config { + u16 direction; + u32 xfer_resolution; + u32 irq; + u32 ch_reg; +}; + +struct acp_irq_prv { + struct device *dev; + struct amd_acp_device *acp_dev; + void (*set_elapsed)(struct device *pdev, u16 play_intr, + u16 capture_intr); +}; + +/* Public interface of ACP device exposed on AMD GNB bus */ +struct amd_acp_device { + /* Handshake when ALSA driver connects, disconnects + * TBD: is this really needed? */ + int (*init)(struct amd_acp_device *acp_dev, void *iprv); + void (*fini)(struct amd_acp_device *acp_dev); + + /** + * config_dma() - Configure ACP internal DMA controller + * @acp_dev: acp device + * @acp_dma_config: DMA configuration parameters + * + * This will configure the DMA controller with the given + * configuration parameters. + */ + void (*config_dma)(struct amd_acp_device *acp_dev, + struct acp_dma_config *dma_config); + + /** + * config_dma_channel() - Configure ACP DMA channel + * @acp_dev: acp device + * @ch_num: channel number to be configured + * @dscr_strt_idx: DMA descriptor starting index + * @priority_level: priority level of channel + * + * This will configure the DMA channel with the given + * configuration parameters. + */ + void (*config_dma_channel)(struct amd_acp_device *acp_dev, + u8 ch_num, u16 dscr_strt_idx, u16 num_dscrs, + enum acp_dma_priority_level priority_level); + + /** + * dma_start() - Start ACP DMA engine + * @acp_dev: acp device + * @ch_num: DMA channel number + * @is_circular: configure circular DMA + * + * Start DMA channel as configured. + */ + int (*dma_start)(struct amd_acp_device *acp_dev, u16 ch_num, + bool is_circular); + + /** + * dma_stop() - Stop ACP DMA engine + * @acp_dev: acp device + * @ch_num: DMA channel number + * + * Stop DMA channel as configured. + */ + int (*dma_stop)(struct amd_acp_device *acp_dev, u8 ch_num); + + /** + * update_dma_pointer() - Query the buffer postion + * @acp_dev: acp device + * @direction: Dma transfer direction + * @period_size: size of buffer in-terms of ALSA terminology + * + * This will query the buffer position from ACP IP, based on data + * produced/consumed + */ + u32 (*update_dma_pointer)(struct amd_acp_device *acp_dev, + int direction, u32 period_size); + + /** + * prebuffer_audio() - Wait for buffering to complete + * @acp_dev: acp device + * + * Wait for buffering to complete in HOST to SRAM DMA channel. + */ + void (*prebuffer_audio)(struct amd_acp_device *acp_dev); + + /** + * i2s_reset() - Reset i2s FIFOs + * @acp_dev: acp device + * @direction: direction of stream – playback/record + * + * Resets I2S FIFOs + */ + void (*i2s_reset)(struct amd_acp_device *acp_dev, int direction); + + /** + * config_i2s() - Configure the i2s controller + * @acp_dev: acp device + * @i2s_config: configuration of i2s controller + * + * This will configure the i2s controller instance used on the + * board, with the given configuration parameters. + */ + void (*config_i2s)(struct amd_acp_device *acp_dev, + struct acp_i2s_config *i2s_config); + + /** + * i2s_start() - Start i2s controller + * @acp_dev: acp device + * @direction: direction of stream – playback/record + * + * Starts I2S data transmission + */ + void (*i2s_start)(struct amd_acp_device *acp_dev, int direction); + + /** + * i2s_stop() - Stop i2s controller + * @acp_dev: acp device + * @stream: Type of stream – playback/record + * + * Stops I2S data transmission + */ + void (*i2s_stop)(struct amd_acp_device *acp_dev, int direction); + + /* TODO: Need callback registration interface for asynchronous + * notifications */ +}; + +#endif /* _AMD_ACP_H */ diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile index ccdbb35..07bfe2a 100644 --- a/drivers/gpu/drm/amd/amdgpu/Makefile +++ b/drivers/gpu/drm/amd/amdgpu/Makefile @@ -3,7 +3,9 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/amd/include/asic_reg \ - -Idrivers/gpu/drm/amd/include + -Idrivers/gpu/drm/amd/include \ + -Idrivers/gpu/drm/amd/include/bus \ + -Idrivers/gpu/drm/amd/acp/include
amdgpu-y := amdgpu_drv.o
@@ -80,6 +82,16 @@ amdgpu-y += \ # add cgs amdgpu-y += amdgpu_cgs.o
+# ACP componet +ifneq ($(CONFIG_DRM_AMD_ACP),) +amdgpu-y += amdgpu_acp.o + +AMDACPPATH := ../acp +include drivers/gpu/drm/amd/acp/Makefile + +amdgpu-y += $(AMD_ACP_FILES) +endif + amdgpu-$(CONFIG_COMPAT) += amdgpu_ioc32.o amdgpu-$(CONFIG_VGA_SWITCHEROO) += amdgpu_atpx_handler.o amdgpu-$(CONFIG_ACPI) += amdgpu_acpi.o diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 66ffdc1..08d241d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -53,6 +53,7 @@ #include "amdgpu_irq.h" #include "amdgpu_ucode.h" #include "amdgpu_gds.h" +#include "amdgpu_acp.h"
/* * Modules parameters. @@ -1853,6 +1854,12 @@ int amdgpu_ctx_put(struct amdgpu_ctx *ctx);
extern int amdgpu_ctx_ioctl(struct drm_device *dev, void *data, struct drm_file *filp); +/* + * CGS + */ +void *amdgpu_cgs_create_device(struct amdgpu_device *adev); +void amdgpu_cgs_destroy_device(void *cgs_device); +
/* * Core structure, functions and helpers. @@ -1869,6 +1876,10 @@ struct amdgpu_device { struct pci_dev *pdev; struct rw_semaphore exclusive_lock;
+#ifdef CONFIG_DRM_AMD_ACP + struct amdgpu_acp acp; +#endif + /* ASIC */ enum amdgpu_asic_type asic_type; uint32_t family; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c new file mode 100644 index 0000000..e4112ea --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c @@ -0,0 +1,201 @@ +/* + * Copyright 2015 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#include "amdgpu.h" +#include "atom.h" +#include "amdgpu_acp.h" +#include "amd_gnb_bus.h" +#include "acp_gfx_if.h" + +static int acp_early_init(void *handle) +{ + return 0; +} + +static int acp_sw_init(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + adev->acp.parent = adev->dev; + + adev->acp.cgs_device = + amdgpu_cgs_create_device(adev); + if (!adev->acp.cgs_device) + return -EINVAL; + + return 0; +} + +static int acp_sw_fini(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + if (adev->acp.cgs_device) + amdgpu_cgs_destroy_device(adev->acp.cgs_device); + + return 0; +} + +/** + * acp_hw_init - start and test UVD block + * + * @adev: amdgpu_device pointer + * + */ +static int acp_hw_init(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + int r; + const struct amdgpu_ip_block_version *ip_version = + amdgpu_get_ip_block(adev, AMD_IP_BLOCK_TYPE_ACP); + + if (!ip_version) + return -EINVAL; + + r = amd_acp_hw_init(adev->acp.cgs_device, + ip_version->major, ip_version->minor, + &adev->acp.private); + /* -ENODEV means board uses AZ rather than ACP */ + if (r == -ENODEV) + return 0; + else if (r) + return r; + r = amd_gnb_bus_device_init(&adev->acp.acp_pcm_dev, + AMD_GNB_IP_ACP_PCM, + "acp_pcm_dev", + adev->acp.private, + adev->acp.parent); + if (r) { + amd_acp_hw_fini(adev->acp.private); + return r; + } + + return 0; +} + +/** + * acp_hw_fini - stop the hardware block + * + * @adev: amdgpu_device pointer + * + */ +static int acp_hw_fini(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + if (adev->acp.private) { + amd_acp_hw_fini(adev->acp.private); + amd_gnb_bus_unregister_device(&adev->acp.acp_pcm_dev); + } + + return 0; +} + +static int acp_suspend(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + if (adev->acp.private) + amd_acp_suspend(adev->acp.private); + + return 0; +} + +static int acp_resume(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + if (adev->acp.private) + amd_acp_resume(adev->acp.private); + + return 0; +} + +static bool acp_is_idle(void *handle) +{ + return true; +} + +static int acp_wait_for_idle(void *handle) +{ + return 0; +} + +static int acp_soft_reset(void *handle) +{ + return 0; +} + +static void acp_print_status(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + dev_info(adev->dev, "ACP STATUS\n"); +} + +static int acp_set_clockgating_state(void *handle, + enum amd_clockgating_state state) +{ + return 0; +} + +static int acp_set_powergating_state(void *handle, + enum amd_powergating_state state) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + /* This doesn't actually powergate the ACP block. + * That's done in the dpm code via the SMC. This + * just re-inits the block as necessary. The actual + * gating still happens in the dpm code. We should + * revisit this when there is a cleaner line between + * the smc and the hw blocks + */ + if (state == AMD_PG_STATE_GATE) { + if (adev->acp.private) + amd_acp_suspend(adev->acp.private); + } else { + if (adev->acp.private) + amd_acp_resume(adev->acp.private); + } + return 0; +} + +const struct amd_ip_funcs acp_ip_funcs = { + .early_init = acp_early_init, + .late_init = NULL, + .sw_init = acp_sw_init, + .sw_fini = acp_sw_fini, + .hw_init = acp_hw_init, + .hw_fini = acp_hw_fini, + .suspend = acp_suspend, + .resume = acp_resume, + .is_idle = acp_is_idle, + .wait_for_idle = acp_wait_for_idle, + .soft_reset = acp_soft_reset, + .print_status = acp_print_status, + .set_clockgating_state = acp_set_clockgating_state, + .set_powergating_state = acp_set_powergating_state, +}; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h new file mode 100644 index 0000000..424cd52f --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h @@ -0,0 +1,40 @@ +/* + * Copyright 2015 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: AMD + * + */ + +#ifndef __AMDGPU_ACP_H__ +#define __AMDGPU_ACP_H__ + +#include "amd_gnb_bus.h" + +struct amdgpu_acp { + struct device *parent; + struct amd_gnb_bus_dev acp_pcm_dev; + void *cgs_device; + struct amd_acp_private *private; +}; + +extern const struct amd_ip_funcs acp_ip_funcs; + +#endif /* __AMDGPU_ACP_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index 68552da..be99670 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -71,6 +71,9 @@ #include "uvd_v5_0.h" #include "uvd_v6_0.h" #include "vce_v3_0.h" +#if defined(CONFIG_DRM_AMD_ACP) +#include "amdgpu_acp.h" +#endif
/* * Indirect registers accessor @@ -1193,6 +1196,15 @@ static const struct amdgpu_ip_block_version cz_ip_blocks[] = .rev = 0, .funcs = &vce_v3_0_ip_funcs, }, +#if defined(CONFIG_DRM_AMD_ACP) + { + .type = AMD_IP_BLOCK_TYPE_ACP, + .major = 2, + .minor = 2, + .rev = 0, + .funcs = &acp_ip_funcs, + }, +#endif };
int vi_set_ip_blocks(struct amdgpu_device *adev) diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h index 5bdf1b4..832602e 100644 --- a/drivers/gpu/drm/amd/include/amd_shared.h +++ b/drivers/gpu/drm/amd/include/amd_shared.h @@ -33,6 +33,7 @@ enum amd_ip_block_type { AMD_IP_BLOCK_TYPE_SDMA, AMD_IP_BLOCK_TYPE_UVD, AMD_IP_BLOCK_TYPE_VCE, + AMD_IP_BLOCK_TYPE_ACP, };
enum amd_clockgating_state {
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
The following are design changes for ACP DMA : 1. For capture usecase, DMA Interrupt on Complete is added for ACP_TO_SYSRAM_CH_NUM DMA channel 2. For playback usecase, the destination for DMA descriptors is reversed. 3. Finally, modified DMA buffer position update logic as per above changes.
v2: minor code cleanups
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed-by: Alex Deucher alexander.deucher@amd.com Reviewed-by: Murali Krishna Vemuri murali-krishna.vemuri@amd.com Signed-off-by: Alex Deucher alexander.deucher@amd.com --- drivers/gpu/drm/amd/acp/acp_hw.c | 156 ++++++++++++++++++++++----------------- drivers/gpu/drm/amd/acp/acp_hw.h | 19 ++--- 2 files changed, 93 insertions(+), 82 deletions(-)
diff --git a/drivers/gpu/drm/amd/acp/acp_hw.c b/drivers/gpu/drm/amd/acp/acp_hw.c index 069fea7..34bb0fe 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.c +++ b/drivers/gpu/drm/amd/acp/acp_hw.c @@ -40,7 +40,7 @@ #include <linux/delay.h> #include <linux/errno.h>
-#define VISLANDS30_IV_SRCID_ACP 0x000000a2 // 162 +#define VISLANDS30_IV_SRCID_ACP 0x000000a2
#include "acp_gfx_if.h" #include "acp_hw.h" @@ -91,10 +91,9 @@ static void config_acp_dma_channel(struct amd_acp_device *acp_dev, u8 ch_num,
/* Initialize the dma descriptors location in SRAM and page size */ -static void acp_dma_descr_init(struct amd_acp_device *acp_dev) +static void acp_dma_descr_init(struct amd_acp_private *acp_prv) { u32 sram_pte_offset = 0; - struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev;
/* SRAM starts at 0x04000000. From that offset one page (4KB) left for * filling DMA descriptors.sram_pte_offset = 0x04001000 , used for @@ -146,10 +145,11 @@ static void config_dma_descriptor_in_sram(struct amd_acp_device *acp_dev,
/* Initialize the DMA descriptor information */ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, - u32 size, int direction, u32 pte_offset) + u32 size, int direction, + u32 pte_offset) { - u16 dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH12; u16 num_descr; + u16 dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH12; acp_dma_dscr_transfer_t dmadscr[2];
num_descr = 2; @@ -157,43 +157,45 @@ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, dmadscr[0].size_xfer_dir.val = (u32) 0x0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH12; - dmadscr[0].dest = ACP_SHARED_RAM_BANK_38_ADDRESS; + dmadscr[0].dest = ACP_SHARED_RAM_BANK_38_ADDRESS + (size / 2); dmadscr[0].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + (pte_offset * PAGE_SIZE_4K); dmadscr[0].size_xfer_dir.s.trans_direction = ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM; - } else if (direction == STREAM_CAPTURE) { + dmadscr[0].size_xfer_dir.s.size = (size / 2); + dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x0; + } else { dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH14; dmadscr[0].src = ACP_SHARED_RAM_BANK_47_ADDRESS; dmadscr[0].dest = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + (pte_offset * PAGE_SIZE_4K); dmadscr[0].size_xfer_dir.s.trans_direction = ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION; + dmadscr[0].size_xfer_dir.s.size = size / 2; + dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; }
- /* allot 1 period size per descriptor = total size (size) /2 - * => params_buffer_bytes(params)/params_periods(params); - */ - dmadscr[0].size_xfer_dir.s.size = size / 2; - - dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x0; - config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[0]);
dmadscr[1].size_xfer_dir.val = (u32) 0x0; - dmadscr[1].dest = dmadscr[0].dest + dmadscr[0].size_xfer_dir.s.size; - dmadscr[1].src = dmadscr[0].src + dmadscr[0].size_xfer_dir.s.size; - dmadscr[1].size_xfer_dir.s.size = dmadscr[0].size_xfer_dir.s.size; - dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x0; - if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_END_DMA_DESCR_CH12; + dmadscr[1].dest = ACP_SHARED_RAM_BANK_38_ADDRESS; + dmadscr[1].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + + (pte_offset * PAGE_SIZE_4K) + (size / 2); dmadscr[1].size_xfer_dir.s.trans_direction = ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM; - } else if (direction == STREAM_CAPTURE) { + dmadscr[1].size_xfer_dir.s.size = (size / 2); + dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x0; + } else { dma_dscr_idx = CAPTURE_END_DMA_DESCR_CH14; dmadscr[1].size_xfer_dir.s.trans_direction = ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION; + dmadscr[1].size_xfer_dir.val = (u32) 0x0; + dmadscr[1].dest = dmadscr[0].dest + (size / 2); + dmadscr[1].src = dmadscr[0].src + (size / 2); + dmadscr[1].size_xfer_dir.s.size = (size / 2); + dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x1; }
config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[1]); @@ -204,7 +206,7 @@ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, config_acp_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM, dma_dscr_idx, num_descr, ACP_DMA_PRIORITY_LEVEL_NORMAL); - } else if (direction == STREAM_CAPTURE) { + } else { /* starting descriptor for this channel */ dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH14; config_acp_dma_channel(acp_dev, ACP_TO_SYSRAM_CH_NUM, @@ -228,34 +230,38 @@ static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev, * of data */ dmadscr[0].size_xfer_dir.val = (u32) 0x0; - dmadscr[0].size_xfer_dir.s.size = (size / 2); - dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH13; dmadscr[0].src = ACP_SHARED_RAM_BANK_38_ADDRESS; dmadscr[0].size_xfer_dir.s.trans_direction = TO_ACP_I2S_1; - } else if (direction == STREAM_CAPTURE) { + dmadscr[0].size_xfer_dir.s.size = (size / 2); + dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; + } else { dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH15; dmadscr[0].dest = ACP_SHARED_RAM_BANK_47_ADDRESS; - dmadscr[0].size_xfer_dir.s.trans_direction = 0xa; + dmadscr[0].size_xfer_dir.s.trans_direction = FROM_ACP_I2S_1; + dmadscr[0].size_xfer_dir.s.size = (size / 2); + dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; } + config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[0]);
dmadscr[1].size_xfer_dir.val = (u32) 0x0; - dmadscr[1].size_xfer_dir.s.size = (size / 2); - dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x1; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_END_DMA_DESCR_CH13; - dmadscr[1].src = dmadscr[0].src + - dmadscr[0].size_xfer_dir.s.size; + dmadscr[1].src = dmadscr[0].src + (size / 2); dmadscr[1].size_xfer_dir.s.trans_direction = TO_ACP_I2S_1; + dmadscr[1].size_xfer_dir.s.size = (size / 2); + dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x1;
- } else if (direction == STREAM_CAPTURE) { + } else { dma_dscr_idx = CAPTURE_END_DMA_DESCR_CH15; - dmadscr[1].dest = dmadscr[0].dest + - dmadscr[0].size_xfer_dir.s.size; - dmadscr[1].size_xfer_dir.s.trans_direction = 0xa; + dmadscr[1].dest = dmadscr[0].dest + (size / 2); + dmadscr[1].size_xfer_dir.s.trans_direction = FROM_ACP_I2S_1; + dmadscr[1].size_xfer_dir.s.size = (size / 2); + dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x1; } + config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[1]);
/* Configure the DMA channel with the above descriptore */ @@ -265,7 +271,7 @@ static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev, config_acp_dma_channel(acp_dev, ACP_TO_I2S_DMA_CH_NUM, dma_dscr_idx, num_descr, ACP_DMA_PRIORITY_LEVEL_NORMAL); - } else if (direction == STREAM_CAPTURE) { + } else { /* starting descriptor for this channel */ dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH15; config_acp_dma_channel(acp_dev, I2S_TO_ACP_DMA_CH_NUM, @@ -284,8 +290,8 @@ static u16 get_dscr_idx(struct amd_acp_device *acp_dev, int direction) dscr_idx = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CUR_DSCR_13); dscr_idx = (dscr_idx == PLAYBACK_START_DMA_DESCR_CH13) ? - PLAYBACK_END_DMA_DESCR_CH12 : - PLAYBACK_START_DMA_DESCR_CH12; + PLAYBACK_START_DMA_DESCR_CH12 : + PLAYBACK_END_DMA_DESCR_CH12; } else { dscr_idx = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CUR_DSCR_15); @@ -470,7 +476,7 @@ static void configure_i2s_stream(struct amd_acp_device *acp_dev, /*Enable Transmit */ cgs_write_register(acp_prv->cgs_device, (mmACP_I2SSP_TER0 + (0x10 * - i2s_config->ch_reg)), 1); + i2s_config->ch_reg)), 1); } else { /* Receive configuration register for data width */ cgs_write_register(acp_prv->cgs_device, @@ -479,11 +485,11 @@ static void configure_i2s_stream(struct amd_acp_device *acp_dev, i2s_config->xfer_resolution); cgs_write_register(acp_prv->cgs_device, (mmACP_I2SMICSP_RFCR0 + (0x10 * - i2s_config->ch_reg)), 0x07); + i2s_config->ch_reg)), 0x07); /*Read interrupt mask register */ i2s_config->irq = cgs_read_register(acp_prv->cgs_device, (mmACP_I2SMICSP_IMR0 + - (0x10 * i2s_config->ch_reg))); + (0x10 * i2s_config->ch_reg)));
/* TX FIFO Overrun,Empty interrupts */ cgs_write_register(acp_prv->cgs_device, @@ -493,8 +499,7 @@ static void configure_i2s_stream(struct amd_acp_device *acp_dev, /*Enable Receive */ cgs_write_register(acp_prv->cgs_device, (mmACP_I2SMICSP_RER0 + (0x10 * - i2s_config->ch_reg)), 1); - + i2s_config->ch_reg)), 1); } }
@@ -545,16 +550,18 @@ static int acp_dma_start(struct amd_acp_device *acp_dev, dma_ctrl |= ACP_DMA_CNTL_0__DMAChRun_MASK;
if ((ch_num == ACP_TO_I2S_DMA_CH_NUM) || + (ch_num == ACP_TO_SYSRAM_CH_NUM) || (ch_num == I2S_TO_ACP_DMA_CH_NUM)) { dma_ctrl |= ACP_DMA_CNTL_0__DMAChIOCEn_MASK; - cgs_irq_get(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 0); } else { dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK; }
/* enable for ACP SRAM to/from I2S DMA channel */ - if (is_circular == true) + if (is_circular == true) { dma_ctrl |= ACP_DMA_CNTL_0__Circular_DMA_En_MASK; + cgs_irq_get(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 0); + } else dma_ctrl &= ~ACP_DMA_CNTL_0__Circular_DMA_En_MASK;
@@ -562,7 +569,6 @@ static int acp_dma_start(struct amd_acp_device *acp_dev, dma_ctrl);
status = STATUS_SUCCESS; - return status; }
@@ -614,7 +620,8 @@ static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num) dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK;
cgs_write_register(acp_prv->cgs_device, - (mmACP_DMA_CNTL_0 + ch_num), dma_ctrl); + (mmACP_DMA_CNTL_0 + ch_num), + dma_ctrl); status = STATUS_SUCCESS; break; } @@ -631,47 +638,58 @@ static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num)
static int dma_irq_handler(void *prv_data) { - u16 play_intr, capture_intr; + u16 play_acp_i2s_intr, cap_i2s_acp_intr, cap_acp_sysram_intr; u16 dscr_idx, intr_flag; + u32 ext_intr_status; int priority_level = 0x0; int dma_transfer_status = STATUS_UNSUCCESSFUL; struct acp_irq_prv *idata = prv_data; struct amd_acp_device *acp_dev = idata->acp_dev; + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev;
intr_flag = acp_get_intr_flag(acp_dev); - play_intr = (intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)); - capture_intr = (intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM));
- if (!play_intr && !capture_intr) { + play_acp_i2s_intr = (intr_flag & BIT(ACP_TO_I2S_DMA_CH_NUM)); + cap_i2s_acp_intr = (intr_flag & BIT(I2S_TO_ACP_DMA_CH_NUM)); + cap_acp_sysram_intr = (intr_flag & BIT(ACP_TO_SYSRAM_CH_NUM)); + + if (!play_acp_i2s_intr && !cap_i2s_acp_intr && !cap_acp_sysram_intr) { /* We registered for DMA Interrupt-On-Complete interrupts only. - * If we hit here, just return. */ - pr_info("ACP:irq_handler: play_intr && capture_intr = false\n"); + * If we hit here, log, acknowledge it and return. */ + ext_intr_status = cgs_read_register(acp_prv->cgs_device, + mmACP_EXTERNAL_INTR_STAT); + pr_info("ACP: Not a DMA IOC irq: %x\n", ext_intr_status); return 0; }
- if (play_intr) { + if (play_acp_i2s_intr) { dscr_idx = get_dscr_idx(acp_dev, STREAM_PLAYBACK); config_acp_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM, dscr_idx, 1, priority_level); dma_transfer_status = acp_dma_start(acp_dev, SYSRAM_TO_ACP_CH_NUM, false); - idata->set_elapsed(idata->dev, play_intr, capture_intr); + idata->set_elapsed(idata->dev, 1, 0);
acp_ext_stat_clear_dmaioc(acp_dev, ACP_TO_I2S_DMA_CH_NUM); }
- if (capture_intr) { + if (cap_i2s_acp_intr) { dscr_idx = get_dscr_idx(acp_dev, STREAM_CAPTURE); config_acp_dma_channel(acp_dev, ACP_TO_SYSRAM_CH_NUM, dscr_idx, 1, priority_level); dma_transfer_status = acp_dma_start(acp_dev, ACP_TO_SYSRAM_CH_NUM, false); - idata->set_elapsed(idata->dev, play_intr, capture_intr);
acp_ext_stat_clear_dmaioc(acp_dev, I2S_TO_ACP_DMA_CH_NUM); } + + if (cap_acp_sysram_intr) { + idata->set_elapsed(idata->dev, 0, 1); + acp_ext_stat_clear_dmaioc(acp_dev, ACP_TO_SYSRAM_CH_NUM); + } + return 0; }
@@ -737,7 +755,7 @@ static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) cgs_write_register(acp_prv->cgs_device, mmACP_AXI2DAGB_GARLIC_CNTL, GARLIC_CNTL_DEFAULT);
- acp_dma_descr_init(acp_dev); + acp_dma_descr_init(acp_prv);
/* DMA DSCR BASE ADDRESS IN SRAM */ cgs_write_register(acp_prv->cgs_device, mmACP_DMA_DESC_BASE_ADDR, @@ -811,24 +829,24 @@ static u32 acp_update_dma_pointer(struct amd_acp_device *acp_dev, int direction, if (direction == STREAM_PLAYBACK) { dscr = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CUR_DSCR_13); - pos = cgs_read_register(acp_prv->cgs_device, - mmACP_DMA_CUR_TRANS_CNT_13); - /* dscr = either 2 or 3 only */ - mul = (dscr == PLAYBACK_START_DMA_DESCR_CH13) ? 1 : 0; - pos = (mul * period_size) + pos; - } else if (direction == STREAM_CAPTURE) { - dscr = cgs_read_register(acp_prv->cgs_device, - mmACP_DMA_CUR_DSCR_15); - pos = cgs_read_register(acp_prv->cgs_device, - mmACP_DMA_CUR_TRANS_CNT_15); + + mul = (dscr == PLAYBACK_START_DMA_DESCR_CH13) ? 0 : 1; + pos = (mul * period_size); + + } else { dma_config = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CNTL_14); if (dma_config != 0) { - mul = (dscr == CAPTURE_START_DMA_DESCR_CH15) ? 1 : 0; - pos = (mul * period_size) + pos; + dscr = cgs_read_register(acp_prv->cgs_device, + mmACP_DMA_CUR_DSCR_14); + mul = (dscr == CAPTURE_START_DMA_DESCR_CH14) ? 1 : 2; + pos = (mul * period_size); } - }
+ if (pos >= (2 * period_size)) + pos = 0; + + } return pos; }
diff --git a/drivers/gpu/drm/amd/acp/acp_hw.h b/drivers/gpu/drm/amd/acp/acp_hw.h index 384d97d..5e7e225 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.h +++ b/drivers/gpu/drm/amd/acp/acp_hw.h @@ -22,7 +22,7 @@ #define ACP_SHARED_RAM_BANK_38_ADDRESS 0x404A000
/* Capture SRAM address (as a source in dma descriptor) */ -#define ACP_SHARED_RAM_BANK_47_ADDRESS 0x405C000 +#define ACP_SHARED_RAM_BANK_47_ADDRESS 0x4054000
#define ACP_DMA_RESET_TIME 10000 #define ACP_CLOCK_EN_TIME_OUT_VALUE 0x000000FF @@ -35,6 +35,11 @@ #define ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS 0x00000000 #define ACP_INTERNAL_APERTURE_WINDOW_4_ADDRESS 0x01800000
+#define TO_ACP_I2S_1 0x2 +#define TO_ACP_I2S_2 0x4 +#define FROM_ACP_I2S_1 0xa +#define FROM_ACP_I2S_2 0xb + enum { STREAM_PLAYBACK = 0, STREAM_CAPTURE, @@ -42,18 +47,6 @@ enum { };
enum { - To_DAGB_O = 0x0, - To_DAGB_G, - TO_ACP_I2S_1, - TO_BLUETOOTH, - TO_ACP_I2S_2, - FROM_DAGB_O, - FROM_DAGB_G, - FROM_ACP_I2S_1, - FROM_ACP_I2S_2 -}; - -enum { ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION = 0x0, ACP_DMA_ATTRIBUTES_SHARED_MEM_TO_DAGB_GARLIC = 0x1, ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM = 0x8,
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
ACP IP can be powered on and off during system wide suspend/resume transition. AMD ASoC PCM device will use this module during system suspend/resume and PCM device's runtime pm.
Also, updated code comments.
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed-by: Alex Deucher alexander.deucher@amd.com Reviewed-by: Murali Krishna Vemuri murali-krishna.vemuri@amd.com Signed-off-by: Alex Deucher alexander.deucher@amd.com --- drivers/gpu/drm/amd/acp/acp_hw.c | 220 +++++++++++++++++++++++------- drivers/gpu/drm/amd/acp/acp_hw.h | 19 +++ drivers/gpu/drm/amd/acp/include/amd_acp.h | 18 +++ 3 files changed, 204 insertions(+), 53 deletions(-)
diff --git a/drivers/gpu/drm/amd/acp/acp_hw.c b/drivers/gpu/drm/amd/acp/acp_hw.c index 34bb0fe..830a7da 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.c +++ b/drivers/gpu/drm/amd/acp/acp_hw.c @@ -40,17 +40,16 @@ #include <linux/delay.h> #include <linux/errno.h>
-#define VISLANDS30_IV_SRCID_ACP 0x000000a2 - #include "acp_gfx_if.h" #include "acp_hw.h"
#include "acp_2_2_d.h" #include "acp_2_2_sh_mask.h"
+#define VISLANDS30_IV_SRCID_ACP 0x000000a2 + /* Configure a given dma channel parameters - enable/disble, * number of descriptors, priority */ - static void config_acp_dma_channel(struct amd_acp_device *acp_dev, u8 ch_num, u16 dscr_strt_idx, u16 num_dscrs, enum acp_dma_priority_level priority_level) @@ -58,38 +57,30 @@ static void config_acp_dma_channel(struct amd_acp_device *acp_dev, u8 ch_num, u32 dma_ctrl; struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev;
- /* read the dma control register and disable the channel run field */ + /* disable the channel run field */ dma_ctrl = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CNTL_0 + ch_num); - /* clear the dma channel control bits */ dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK; - cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_CNTL_0 + ch_num), dma_ctrl);
- /* there is no transfer happening on this channel so - * program DMAChDscrStrIdx to the index number of the first descriptor - * to be processed. - */ + /* program a DMA channel with first descriptor to be processed. */ cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_DSCR_STRT_IDX_0 + ch_num), (ACP_DMA_DSCR_STRT_IDX_0__DMAChDscrStrtIdx_MASK & dscr_strt_idx));
- /* program DMAChDscrDscrCnt to the number of descriptors to be - * processed in the transfer - */ + /* program a DMA channel with the number of descriptors to be + * processed in the transfer */ cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_DSCR_CNT_0 + ch_num), (ACP_DMA_DSCR_CNT_0__DMAChDscrCnt_MASK & num_dscrs));
- /* set DMAChPrioLvl according to the priority */ + /* set DMA channel priority */ cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_PRIO_0 + ch_num), priority_level); }
- - /* Initialize the dma descriptors location in SRAM and page size */ static void acp_dma_descr_init(struct amd_acp_private *acp_prv) { @@ -143,7 +134,9 @@ static void config_dma_descriptor_in_sram(struct amd_acp_device *acp_dev, descr_info->size_xfer_dir.val); }
-/* Initialize the DMA descriptor information */ +/* Initialize the DMA descriptor information for transfer between + * system memory <-> ACP SRAM + */ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, u32 size, int direction, u32 pte_offset) @@ -215,7 +208,9 @@ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, } }
-/* Initialize the i2s dma descriptors in SRAM */ +/* Initialize the DMA descriptor information for transfer between + * ACP SRAM <-> I2S + */ static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev, u32 size, int direction) { @@ -226,9 +221,6 @@ static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev,
num_descr = 2;
- /* Let I2s Know the direction of transfer and source/destination - * of data - */ dmadscr[0].size_xfer_dir.val = (u32) 0x0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH13; @@ -304,7 +296,7 @@ static u16 get_dscr_idx(struct amd_acp_device *acp_dev, int direction)
}
-/* Create page table entries in ACP SRAM for the allocated memory */ +/* Create page table entries in ACP SRAM for the allocated memory */ static void acp_pte_config(struct amd_acp_device *acp_dev, struct page *pg, u16 num_of_pages, u32 pte_offset) { @@ -344,7 +336,6 @@ static void acp_pte_config(struct amd_acp_device *acp_dev, struct page *pg, } }
- /* enables/disables ACP's external interrupt */ static void acp_enable_external_interrupts(struct amd_acp_device *acp_dev, int enable) @@ -361,8 +352,8 @@ static void acp_enable_external_interrupts(struct amd_acp_device *acp_dev, mmACP_EXTERNAL_INTR_ENB, acp_ext_intr_enb); }
-/* Clear (acknowledge) DMA 'Interrupt on Complete' (IOC) in ACP - * external interrupt status register +/* Clear (acknowledge) DMA 'Interrupt on Complete' (IOC) in ACP + * external interrupt status register */ static void acp_ext_stat_clear_dmaioc(struct amd_acp_device *acp_dev, u8 ch_num) { @@ -382,7 +373,7 @@ static void acp_ext_stat_clear_dmaioc(struct amd_acp_device *acp_dev, u8 ch_num) } }
-/* Check whether interrupt (IOC) is generated or not */ +/* Check whether ACP DMA interrupt (IOC) is generated or not */ static u16 acp_get_intr_flag(struct amd_acp_device *acp_dev) { u32 ext_intr_status; @@ -412,7 +403,6 @@ static int irq_set_source(void *private_data, unsigned src_id, unsigned type, } }
- static inline void i2s_clear_irqs(struct amd_acp_device *acp_dev, int direction) { @@ -516,17 +506,17 @@ static void config_acp_dma(struct amd_acp_device *acp_dev, acp_pte_config(acp_dev, dma_config->pg, dma_config->num_of_pages, pte_offset);
- /* Configure System memory to acp dma descriptors */ + /* Configure System memory <-> ACP SRAM DMA descriptors */ set_acp_sysmem_dma_descriptors(acp_dev, dma_config->size, dma_config->direction, pte_offset);
- /* Configure acp to i2s dma descriptors */ + /* Configure ACP SRAM <-> I2S DMA descriptors */ set_acp_to_i2s_dma_descriptors(acp_dev, dma_config->size, dma_config->direction); }
-/* Start a given dma channel */ +/* Start a given DMA channel transfer */ static int acp_dma_start(struct amd_acp_device *acp_dev, u16 ch_num, bool is_circular) { @@ -572,7 +562,7 @@ static int acp_dma_start(struct amd_acp_device *acp_dev, return status; }
-/* Stop a given dma channel number*/ +/* Stop a given DMA channel transfer */ static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num) { int status = STATUS_UNSUCCESSFUL; @@ -584,7 +574,6 @@ static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num) if (acp_dev == NULL) return status;
- /* register mask value to check the channel status bits */ dma_ctrl = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CNTL_0 + ch_num);
@@ -636,6 +625,7 @@ static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num) return status; }
+/* ACP DMA irq handler routine for playback, capture usecases */ static int dma_irq_handler(void *prv_data) { u16 play_acp_i2s_intr, cap_i2s_acp_intr, cap_acp_sysram_intr; @@ -655,7 +645,7 @@ static int dma_irq_handler(void *prv_data)
if (!play_acp_i2s_intr && !cap_i2s_acp_intr && !cap_acp_sysram_intr) { /* We registered for DMA Interrupt-On-Complete interrupts only. - * If we hit here, log, acknowledge it and return. */ + * If we hit here, log it and return. */ ext_intr_status = cgs_read_register(acp_prv->cgs_device, mmACP_EXTERNAL_INTR_STAT); pr_info("ACP: Not a DMA IOC irq: %x\n", ext_intr_status); @@ -702,16 +692,92 @@ static int irq_handler(void *private_data, unsigned src_id, return -1; }
+/* power off a tile/block within ACP */ +static void acp_suspend_tile(struct amd_acp_private *acp_prv, int tile) +{ + u32 val = 0; + u32 timeout = 0; + + if ((tile < ACP_TILE_P1) || (tile > ACP_TILE_DSP2)) { + pr_err(" %s : Invalid ACP power tile index\n", __func__); + return; + } + + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_READ_REG_0 + tile); + val &= ACP_TILE_ON_MASK; + + if (val == 0x0) { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_RETAIN_REG); + val = val | (1 << tile); + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_RETAIN_REG, + val); + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_CONFIG_REG, + 0x500 + tile); + + timeout = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (timeout--) { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_READ_REG_0 + tile); + val = val & ACP_TILE_ON_MASK; + if (val == ACP_TILE_OFF_MASK) + break; + } + + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_RETAIN_REG); + + val |= ACP_TILE_OFF_RETAIN_REG_MASK; + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_RETAIN_REG, + val); + } + +} + +/* power on a tile/block within ACP */ +static void acp_resume_tile(struct amd_acp_private *acp_prv, int tile) +{ + u32 val = 0; + u32 timeout = 0; + + if ((tile < ACP_TILE_P1) || (tile > ACP_TILE_DSP2)) { + pr_err(" %s : Invalid ACP power tile index\n", __func__); + return; + } + + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_READ_REG_0 + tile); + val = val & ACP_TILE_ON_MASK; + + if (val != 0x0) { + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_CONFIG_REG, + 0x600 + tile); + timeout = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (timeout--) { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_READ_REG_0 + tile); + val = val & ACP_TILE_ON_MASK; + if (val == 0x0) + break; + } + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_RETAIN_REG); + if (tile == ACP_TILE_P1) + val = val & (ACP_TILE_P1_MASK); + else if (tile == ACP_TILE_P2) + val = val & (ACP_TILE_P2_MASK); + + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_RETAIN_REG, + val); + } +} + /* Initialize and bring ACP hardware to default state. */ -static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) +static void acp_init(struct amd_acp_private *acp_prv) { u32 val; u32 timeout_value; - int acp_hw_init_status = STATUS_UNSUCCESSFUL; - struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; - - if (acp_dev == NULL) - return acp_hw_init_status;
/* Assert Soft reset of ACP */ val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET); @@ -728,7 +794,7 @@ static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) break; }
- /* Enabling clock to ACP and waits until the clock is enabled */ + /* Enable clock to ACP and wait until the clock is enabled */ val = cgs_read_register(acp_prv->cgs_device, mmACP_CONTROL); val = val | ACP_CONTROL__ClkEn_MASK; cgs_write_register(acp_prv->cgs_device, mmACP_CONTROL, val); @@ -757,7 +823,6 @@ static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv)
acp_dma_descr_init(acp_prv);
- /* DMA DSCR BASE ADDRESS IN SRAM */ cgs_write_register(acp_prv->cgs_device, mmACP_DMA_DESC_BASE_ADDR, ACP_SRAM_BASE_ADDRESS);
@@ -768,20 +833,34 @@ static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) cgs_write_register(acp_prv->cgs_device, mmACP_EXTERNAL_INTR_CNTL, ACP_EXTERNAL_INTR_CNTL__DMAIOCMask_MASK);
+ pr_info("ACP: Initialized.\n"); + +} + +static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + acp_init(acp_prv); + cgs_add_irq_source(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 1, - irq_set_source, irq_handler, iprv); + irq_set_source, irq_handler, iprv); + + /* Disable DSPs which are not used */ + acp_suspend_tile(acp_prv, ACP_TILE_DSP0); + acp_suspend_tile(acp_prv, ACP_TILE_DSP1); + acp_suspend_tile(acp_prv, ACP_TILE_DSP2);
- pr_info("ACP: Initialized.\n"); return STATUS_SUCCESS; }
-static void acp_hw_deinit(struct amd_acp_device *acp_dev) +/* Deintialize ACP */ +static void acp_deinit(struct amd_acp_private *acp_prv) { u32 val; u32 timeout_value; - struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev;
- /* Assert Soft reset of ACP */ + /* Assert Soft reset of ACP */ val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET);
val |= ACP_SOFT_RESET__SoftResetAud_MASK; @@ -795,7 +874,7 @@ static void acp_hw_deinit(struct amd_acp_device *acp_dev) break; } } - /** Disable ACP clock */ + /** Disable ACP clock */ val = cgs_read_register(acp_prv->cgs_device, mmACP_CONTROL); val &= ~ACP_CONTROL__ClkEn_MASK; cgs_write_register(acp_prv->cgs_device, mmACP_CONTROL, val); @@ -812,9 +891,15 @@ static void acp_hw_deinit(struct amd_acp_device *acp_dev) pr_info("ACP: De-Initialized.\n"); }
+static void acp_hw_deinit(struct amd_acp_device *acp_dev) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + acp_deinit(acp_prv); +}
-/* Get the number of bytes consumed for SRAM_TO_I2S DMA - * channel during rendering +/* Update DMA postion in audio ring buffer at period level granularity. + * This will be used by ALSA PCM driver */ static u32 acp_update_dma_pointer(struct amd_acp_device *acp_dev, int direction, u32 period_size) @@ -824,6 +909,7 @@ static u32 acp_update_dma_pointer(struct amd_acp_device *acp_dev, int direction, u32 mul; u32 dma_config; struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + pos = 0;
if (direction == STREAM_PLAYBACK) { @@ -850,8 +936,8 @@ static u32 acp_update_dma_pointer(struct amd_acp_device *acp_dev, int direction, return pos; }
-/* Wait for complete buffering to complete in HOST - * to SRAM DMA channel +/* Wait for initial buffering to complete in HOST to SRAM DMA channel + * for plaback usecase */ static void wait_for_prebuffer_finish(struct amd_acp_device *acp_dev) { @@ -927,6 +1013,22 @@ static void configure_i2s(struct amd_acp_device *acp_dev, configure_i2s_stream(acp_dev, i2s_config); }
+void amd_acp_pcm_suspend(struct amd_acp_device *acp_dev) +{ + struct amd_acp_private *acp_prv; + + acp_prv = (struct amd_acp_private *)acp_dev; + amd_acp_suspend(acp_prv); +} + +void amd_acp_pcm_resume(struct amd_acp_device *acp_dev) +{ + struct amd_acp_private *acp_prv; + + acp_prv = (struct amd_acp_private *)acp_dev; + amd_acp_resume(acp_prv); +} + int amd_acp_hw_init(void *cgs_device, unsigned acp_version_major, unsigned acp_version_minor, struct amd_acp_private **acp_private) @@ -962,6 +1064,9 @@ int amd_acp_hw_init(void *cgs_device, (*acp_private)->public.i2s_start = i2s_start; (*acp_private)->public.i2s_stop = i2s_stop;
+ (*acp_private)->public.acp_suspend = amd_acp_pcm_suspend; + (*acp_private)->public.acp_resume = amd_acp_pcm_resume; + return 0; }
@@ -973,10 +1078,19 @@ int amd_acp_hw_fini(struct amd_acp_private *acp_private)
void amd_acp_suspend(struct amd_acp_private *acp_private) { - /* TODO */ + acp_suspend_tile(acp_private, ACP_TILE_P2); + acp_suspend_tile(acp_private, ACP_TILE_P1); }
void amd_acp_resume(struct amd_acp_private *acp_private) { - /* TODO */ + acp_resume_tile(acp_private, ACP_TILE_P1); + acp_resume_tile(acp_private, ACP_TILE_P2); + + acp_init(acp_private); + + /* Disable DSPs which are not going to be used */ + acp_suspend_tile(acp_private, ACP_TILE_DSP0); + acp_suspend_tile(acp_private, ACP_TILE_DSP1); + acp_suspend_tile(acp_private, ACP_TILE_DSP2); } diff --git a/drivers/gpu/drm/amd/acp/acp_hw.h b/drivers/gpu/drm/amd/acp/acp_hw.h index 5e7e225..4aa6b1c 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.h +++ b/drivers/gpu/drm/amd/acp/acp_hw.h @@ -40,6 +40,25 @@ #define FROM_ACP_I2S_1 0xa #define FROM_ACP_I2S_2 0xb
+#define ACP_TILE_ON_MASK 0x03 +#define ACP_TILE_OFF_MASK 0x02 +#define ACP_TILE_ON_RETAIN_REG_MASK 0x1f +#define ACP_TILE_OFF_RETAIN_REG_MASK 0x20 + +#define ACP_TILE_P1_MASK 0x3e +#define ACP_TILE_P2_MASK 0x3d +#define ACP_TILE_DSP0_MASK 0x3b +#define ACP_TILE_DSP1_MASK 0x37 +#define ACP_TILE_DSP2_MASK 0x2f + +enum { + ACP_TILE_P1 = 0, + ACP_TILE_P2, + ACP_TILE_DSP0, + ACP_TILE_DSP1, + ACP_TILE_DSP2, +}; + enum { STREAM_PLAYBACK = 0, STREAM_CAPTURE, diff --git a/drivers/gpu/drm/amd/acp/include/amd_acp.h b/drivers/gpu/drm/amd/acp/include/amd_acp.h index 988d72a..8c1d31a 100644 --- a/drivers/gpu/drm/amd/acp/include/amd_acp.h +++ b/drivers/gpu/drm/amd/acp/include/amd_acp.h @@ -189,8 +189,26 @@ struct amd_acp_device { */ void (*i2s_stop)(struct amd_acp_device *acp_dev, int direction);
+ /** + * acp_suspend() - Power off ACP + * @acp_dev: acp device + * + * Switch off power tiles of ACP + */ + void (*acp_suspend)(struct amd_acp_device *acp_dev); + + /** + * acp_resume() - Power on ACP + * @acp_dev: acp device + * + * Switch on power tiles of ACP + */ + + void (*acp_resume)(struct amd_acp_device *acp_dev); + /* TODO: Need callback registration interface for asynchronous * notifications */ + };
#endif /* _AMD_ACP_H */
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
There are 48 SRAM memory banks in ACP 2.1. Use bank index - 0 for DMA descriptors 1 to 4 for playback, 5 to 8 for capture.
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed-by: Murali Krishna Vemuri murali-krishna.vemuri@amd.com Acked-by: Alex Deucher alexander.deucher@amd.com --- drivers/gpu/drm/amd/acp/acp_hw.c | 10 +++++----- drivers/gpu/drm/amd/acp/acp_hw.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/amd/acp/acp_hw.c b/drivers/gpu/drm/amd/acp/acp_hw.c index 830a7da..be364ab 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.c +++ b/drivers/gpu/drm/amd/acp/acp_hw.c @@ -150,7 +150,7 @@ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, dmadscr[0].size_xfer_dir.val = (u32) 0x0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH12; - dmadscr[0].dest = ACP_SHARED_RAM_BANK_38_ADDRESS + (size / 2); + dmadscr[0].dest = ACP_SHARED_RAM_BANK_1_ADDRESS + (size / 2); dmadscr[0].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + (pte_offset * PAGE_SIZE_4K); dmadscr[0].size_xfer_dir.s.trans_direction = @@ -159,7 +159,7 @@ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x0; } else { dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH14; - dmadscr[0].src = ACP_SHARED_RAM_BANK_47_ADDRESS; + dmadscr[0].src = ACP_SHARED_RAM_BANK_5_ADDRESS; dmadscr[0].dest = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + (pte_offset * PAGE_SIZE_4K); dmadscr[0].size_xfer_dir.s.trans_direction = @@ -173,7 +173,7 @@ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, dmadscr[1].size_xfer_dir.val = (u32) 0x0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_END_DMA_DESCR_CH12; - dmadscr[1].dest = ACP_SHARED_RAM_BANK_38_ADDRESS; + dmadscr[1].dest = ACP_SHARED_RAM_BANK_1_ADDRESS; dmadscr[1].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + (pte_offset * PAGE_SIZE_4K) + (size / 2); dmadscr[1].size_xfer_dir.s.trans_direction = @@ -224,13 +224,13 @@ static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev, dmadscr[0].size_xfer_dir.val = (u32) 0x0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH13; - dmadscr[0].src = ACP_SHARED_RAM_BANK_38_ADDRESS; + dmadscr[0].src = ACP_SHARED_RAM_BANK_1_ADDRESS; dmadscr[0].size_xfer_dir.s.trans_direction = TO_ACP_I2S_1; dmadscr[0].size_xfer_dir.s.size = (size / 2); dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; } else { dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH15; - dmadscr[0].dest = ACP_SHARED_RAM_BANK_47_ADDRESS; + dmadscr[0].dest = ACP_SHARED_RAM_BANK_5_ADDRESS; dmadscr[0].size_xfer_dir.s.trans_direction = FROM_ACP_I2S_1; dmadscr[0].size_xfer_dir.s.size = (size / 2); dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; diff --git a/drivers/gpu/drm/amd/acp/acp_hw.h b/drivers/gpu/drm/amd/acp/acp_hw.h index 4aa6b1c..b58349c 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.h +++ b/drivers/gpu/drm/amd/acp/acp_hw.h @@ -19,10 +19,10 @@ #define ACP_PHYSICAL_BASE 0x14000
/* Playback SRAM address (as a destination in dma descriptor) */ -#define ACP_SHARED_RAM_BANK_38_ADDRESS 0x404A000 +#define ACP_SHARED_RAM_BANK_1_ADDRESS 0x4002000
/* Capture SRAM address (as a source in dma descriptor) */ -#define ACP_SHARED_RAM_BANK_47_ADDRESS 0x4054000 +#define ACP_SHARED_RAM_BANK_5_ADDRESS 0x400A000
#define ACP_DMA_RESET_TIME 10000 #define ACP_CLOCK_EN_TIME_OUT_VALUE 0x000000FF
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
ACP internal memory banks 0 to 9 are used for audio usecases (playback/capture). Remaining banks 10 to 47 are shutdown to save power.
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed-by: Murali Krishna Vemuri murali-krishna.vemuri@amd.com Acked-by: Alex Deucher alexander.deucher@amd.com --- drivers/gpu/drm/amd/acp/acp_hw.c | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+)
diff --git a/drivers/gpu/drm/amd/acp/acp_hw.c b/drivers/gpu/drm/amd/acp/acp_hw.c index be364ab..7ee2aa6 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.c +++ b/drivers/gpu/drm/amd/acp/acp_hw.c @@ -773,6 +773,52 @@ static void acp_resume_tile(struct amd_acp_private *acp_prv, int tile) } }
+/* Shutdown unused SRAM memory banks in ACP IP */ +static void acp_turnoff_sram_banks(struct amd_acp_private *acp_prv) +{ + /* Bank 0 : used for DMA descriptors + * Bank 1 to 4 : used for playback + * Bank 5 to 8 : used for capture + * Each bank is 8kB and max size allocated for playback/ capture is + * 16kB(max period size) * 2(max periods) reserved for playback/capture + * in ALSA driver + * Turn off all SRAM banks except above banks during playback/capture + */ + u32 val, bank; + + for (bank = 9; bank < 32; bank++) { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_MEM_SHUT_DOWN_REQ_LO); + if (!(val & (1 << bank))) { + val |= 1 << bank; + cgs_write_register(acp_prv->cgs_device, + mmACP_MEM_SHUT_DOWN_REQ_LO, val); + /* If ACP_MEM_SHUT_DOWN_STS_LO is 0xFFFFFFFF, then + * shutdown sequence is complete. */ + do { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_MEM_SHUT_DOWN_STS_LO); + } while (val != 0xFFFFFFFF); + } + } + + for (bank = 32; bank < 48; bank++) { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_MEM_SHUT_DOWN_REQ_HI); + if (!(val & (1 << (bank - 32)))) { + val |= 1 << (bank - 32); + cgs_write_register(acp_prv->cgs_device, + mmACP_MEM_SHUT_DOWN_REQ_HI, val); + /* If ACP_MEM_SHUT_DOWN_STS_HI is 0x0000FFFF, then + * shutdown sequence is complete. */ + do { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_MEM_SHUT_DOWN_STS_HI); + } while (val != 0x0000FFFF); + } + } +} + /* Initialize and bring ACP hardware to default state. */ static void acp_init(struct amd_acp_private *acp_prv) { @@ -833,6 +879,7 @@ static void acp_init(struct amd_acp_private *acp_prv) cgs_write_register(acp_prv->cgs_device, mmACP_EXTERNAL_INTR_CNTL, ACP_EXTERNAL_INTR_CNTL__DMAIOCMask_MASK);
+ acp_turnoff_sram_banks(acp_prv); pr_info("ACP: Initialized.\n");
}
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
Replaced usage of bitfield logic with alternative
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed- by: Alex Deucher alexander.deucher@amd.com --- drivers/gpu/drm/amd/acp/acp_hw.c | 61 +++++++++++++++++----------------------- drivers/gpu/drm/amd/acp/acp_hw.h | 22 +-------------- 2 files changed, 27 insertions(+), 56 deletions(-)
diff --git a/drivers/gpu/drm/amd/acp/acp_hw.c b/drivers/gpu/drm/amd/acp/acp_hw.c index 7ee2aa6..744d908 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.c +++ b/drivers/gpu/drm/amd/acp/acp_hw.c @@ -131,7 +131,7 @@ static void config_dma_descriptor_in_sram(struct amd_acp_device *acp_dev, cgs_write_register(acp_prv->cgs_device, mmACP_SRBM_Targ_Idx_Addr, (sram_offset + 8)); cgs_write_register(acp_prv->cgs_device, mmACP_SRBM_Targ_Idx_Data, - descr_info->size_xfer_dir.val); + descr_info->xfer_val); }
/* Initialize the DMA descriptor information for transfer between @@ -147,48 +147,44 @@ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev,
num_descr = 2;
- dmadscr[0].size_xfer_dir.val = (u32) 0x0; + dmadscr[0].xfer_val = 0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH12; dmadscr[0].dest = ACP_SHARED_RAM_BANK_1_ADDRESS + (size / 2); dmadscr[0].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + (pte_offset * PAGE_SIZE_4K); - dmadscr[0].size_xfer_dir.s.trans_direction = - ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM; - dmadscr[0].size_xfer_dir.s.size = (size / 2); - dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x0; + dmadscr[0].xfer_val |= (DISABLE << 22) | + (ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM << 16) | + (size / 2); } else { dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH14; dmadscr[0].src = ACP_SHARED_RAM_BANK_5_ADDRESS; dmadscr[0].dest = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + (pte_offset * PAGE_SIZE_4K); - dmadscr[0].size_xfer_dir.s.trans_direction = - ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION; - dmadscr[0].size_xfer_dir.s.size = size / 2; - dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; + dmadscr[0].xfer_val |= + (ENABLE << 22) | + (ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION << 16) | + (size / 2); }
config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[0]);
- dmadscr[1].size_xfer_dir.val = (u32) 0x0; + dmadscr[1].xfer_val = 0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_END_DMA_DESCR_CH12; dmadscr[1].dest = ACP_SHARED_RAM_BANK_1_ADDRESS; dmadscr[1].src = ACP_INTERNAL_APERTURE_WINDOW_0_ADDRESS + (pte_offset * PAGE_SIZE_4K) + (size / 2); - dmadscr[1].size_xfer_dir.s.trans_direction = - ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM; - dmadscr[1].size_xfer_dir.s.size = (size / 2); - dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x0; + dmadscr[1].xfer_val |= (DISABLE << 22) | + (ACP_DMA_ATTRIBUTES_DAGB_ONION_TO_SHAREDMEM << 16) | + (size / 2); } else { dma_dscr_idx = CAPTURE_END_DMA_DESCR_CH14; - dmadscr[1].size_xfer_dir.s.trans_direction = - ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION; - dmadscr[1].size_xfer_dir.val = (u32) 0x0; dmadscr[1].dest = dmadscr[0].dest + (size / 2); dmadscr[1].src = dmadscr[0].src + (size / 2); - dmadscr[1].size_xfer_dir.s.size = (size / 2); - dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x1; + dmadscr[1].xfer_val |= (ENABLE << 22) | + (ACP_DMA_ATTRIBUTES_SHAREDMEM_TO_DAGB_ONION << 16) | + (size / 2); }
config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[1]); @@ -221,37 +217,32 @@ static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev,
num_descr = 2;
- dmadscr[0].size_xfer_dir.val = (u32) 0x0; + dmadscr[0].xfer_val = 0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH13; dmadscr[0].src = ACP_SHARED_RAM_BANK_1_ADDRESS; - dmadscr[0].size_xfer_dir.s.trans_direction = TO_ACP_I2S_1; - dmadscr[0].size_xfer_dir.s.size = (size / 2); - dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; + dmadscr[0].xfer_val |= (ENABLE << 22) | (TO_ACP_I2S_1 << 16) | + (size / 2); } else { dma_dscr_idx = CAPTURE_START_DMA_DESCR_CH15; dmadscr[0].dest = ACP_SHARED_RAM_BANK_5_ADDRESS; - dmadscr[0].size_xfer_dir.s.trans_direction = FROM_ACP_I2S_1; - dmadscr[0].size_xfer_dir.s.size = (size / 2); - dmadscr[0].size_xfer_dir.s.ioc = (u32) 0x1; + dmadscr[0].xfer_val |= (ENABLE << 22) | + (FROM_ACP_I2S_1 << 16) | (size / 2); }
config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[0]);
- dmadscr[1].size_xfer_dir.val = (u32) 0x0; + dmadscr[1].xfer_val = 0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_END_DMA_DESCR_CH13; dmadscr[1].src = dmadscr[0].src + (size / 2); - dmadscr[1].size_xfer_dir.s.trans_direction = TO_ACP_I2S_1; - dmadscr[1].size_xfer_dir.s.size = (size / 2); - dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x1; - + dmadscr[1].xfer_val |= (ENABLE << 22) | (TO_ACP_I2S_1 << 16) | + (size / 2); } else { dma_dscr_idx = CAPTURE_END_DMA_DESCR_CH15; dmadscr[1].dest = dmadscr[0].dest + (size / 2); - dmadscr[1].size_xfer_dir.s.trans_direction = FROM_ACP_I2S_1; - dmadscr[1].size_xfer_dir.s.size = (size / 2); - dmadscr[1].size_xfer_dir.s.ioc = (u32) 0x1; + dmadscr[1].xfer_val |= (ENABLE << 22) | + (FROM_ACP_I2S_1 << 16) | (size / 2); }
config_dma_descriptor_in_sram(acp_dev, dma_dscr_idx, &dmadscr[1]); diff --git a/drivers/gpu/drm/amd/acp/acp_hw.h b/drivers/gpu/drm/amd/acp/acp_hw.h index b58349c..e3a102c 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.h +++ b/drivers/gpu/drm/amd/acp/acp_hw.h @@ -73,26 +73,6 @@ enum { ACP_DMA_ATTRIBUTES_FORCE_SIZE = 0xF };
-typedef struct acp_dma_dscr_size_transfer_direction { - /* Specifies the number of bytes need to be transferred - * from source to destination memory. */ - u32 size:16; - /* Specifies transfer direction. */ - u32 trans_direction:4; - /* reserved. */ - u32 reserved1:2; - /* Specifies the IOC enable or not for descriptor. */ - /* Defaultly this will be 0, for the last descriptor, make it Enable */ - u32 ioc:1; - /* reserved. */ - u32 reserved2:9; -} acp_dma_dscr_size_transfer_direction_t; - -typedef union { - u32 val:32; - acp_dma_dscr_size_transfer_direction_t s; -} acp_dma_dscr_size_transfer_direction_u; - typedef struct acp_dma_dscr_transfer { /* Specifies the source memory location for the DMA data transfer. */ u32 src; @@ -103,7 +83,7 @@ typedef struct acp_dma_dscr_transfer { /* Specifies the number of bytes need to be transferred * from source to destination memory.Transfer direction & IOC enable */ - acp_dma_dscr_size_transfer_direction_u size_xfer_dir; + u32 xfer_val; /** Reserved for future use */ u32 reserved; } acp_dma_dscr_transfer_t;
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
ACP IP block consists of dedicated DMA and I2S blocks. The PCM driver provides the DMA and CPU DAI components to ALSA core. Machine driver provides the audio functionality together with the PCM driver and rt286 codec driver.
v2: squash in Kconfig fix
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed-by: Alex Deucher alexander.deucher@amd.com Reviewed-by: Murali Krishna Vemuri murali-krishna.vemuri@amd.com --- sound/soc/Kconfig | 2 +- sound/soc/Makefile | 1 + sound/soc/amd/Kconfig | 13 + sound/soc/amd/Makefile | 11 + sound/soc/amd/acp-pcm-dma.c | 661 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/amd/acp-rt286.c | 265 ++++++++++++++++++ 6 files changed, 952 insertions(+), 1 deletion(-) create mode 100644 sound/soc/amd/Kconfig create mode 100644 sound/soc/amd/Makefile create mode 100644 sound/soc/amd/acp-pcm-dma.c create mode 100644 sound/soc/amd/acp-rt286.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 2ae9619..5965657 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -32,6 +32,7 @@ config SND_SOC_GENERIC_DMAENGINE_PCM
# All the supported SoCs source "sound/soc/adi/Kconfig" +source "sound/soc/amd/Kconfig" source "sound/soc/atmel/Kconfig" source "sound/soc/au1x/Kconfig" source "sound/soc/bcm/Kconfig" @@ -67,4 +68,3 @@ source "sound/soc/codecs/Kconfig" source "sound/soc/generic/Kconfig"
endif # SND_SOC - diff --git a/sound/soc/Makefile b/sound/soc/Makefile index e189903..e2b017a 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -41,3 +41,4 @@ obj-$(CONFIG_SND_SOC) += txx9/ obj-$(CONFIG_SND_SOC) += ux500/ obj-$(CONFIG_SND_SOC) += xtensa/ obj-$(CONFIG_SND_SOC) += zte/ +obj-$(CONFIG_SND_SOC) += amd/ diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig new file mode 100644 index 0000000..07677de --- /dev/null +++ b/sound/soc/amd/Kconfig @@ -0,0 +1,13 @@ + config SND_SOC_AMD_CZ_RT286_MACH + tristate "AMD ASoC Audio driver for Carrizo with rt286 codec" + select SND_SOC_RT286 + select SND_SOC_AMD_ACP + depends on I2C_DESIGNWARE_PLATFORM + help + This option enables AMD I2S Audio support on Carrizo + with ALC288 codec. + config SND_SOC_AMD_ACP + tristate "AMD Audio Coprocessor support" + depends on DRM_AMD_GNB_BUS + help + This option enables ACP support (DMA,I2S) on AMD platforms. diff --git a/sound/soc/amd/Makefile b/sound/soc/amd/Makefile new file mode 100644 index 0000000..63b6f83 --- /dev/null +++ b/sound/soc/amd/Makefile @@ -0,0 +1,11 @@ +ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/amdsoc/ +ccflags-y += -Idrivers/gpu/drm/amdsoc/include/ +ccflags-y += -Idrivers/gpu/drm/amd/include/bus/ +ccflags-y += -Idrivers/gpu/drm/amd/acp/include +ccflags-y += -Idrivers/gpu/drm/amd/include/ +ccflags-y += -Idrivers/gpu/drm/amd/include/asic_reg/acp +snd-soc-acp-pcm-objs := acp-pcm-dma.o +snd-soc-acp-rt286-mach-objs := acp-rt286.o + +obj-$(CONFIG_SND_SOC_AMD_ACP) += snd-soc-acp-pcm.o +obj-$(CONFIG_SND_SOC_AMD_CZ_RT286_MACH) += snd-soc-acp-rt286-mach.o diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c new file mode 100644 index 0000000..0f58957 --- /dev/null +++ b/sound/soc/amd/acp-pcm-dma.c @@ -0,0 +1,661 @@ +/* + * AMD ALSA SoC PCM Driver + * + * Copyright 2014-2015 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + */ + +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/pci.h> + +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "amd_acp.h" +#include "amd_gnb_bus.h" + +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 2 +#define PLAYBACK_MAX_PERIOD_SIZE 16384 +#define PLAYBACK_MIN_PERIOD_SIZE 1024 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 2 +#define CAPTURE_MAX_PERIOD_SIZE 16384 +#define CAPTURE_MIN_PERIOD_SIZE 1024 + +#define NUM_DSCRS_PER_CHANNEL 2 + +#define MAX_BUFFER (PLAYBACK_MAX_PERIOD_SIZE * PLAYBACK_MAX_NUM_PERIODS) +#define MIN_BUFFER MAX_BUFFER + +#define TWO_CHANNEL_SUPPORT 2 /* up to 2.0 */ +#define FOUR_CHANNEL_SUPPORT 4 /* up to 3.1 */ +#define SIX_CHANNEL_SUPPORT 6 /* up to 5.1 */ +#define EIGHT_CHANNEL_SUPPORT 8 /* up to 7.1 */ + + +static const struct snd_pcm_hardware acp_pcm_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + /* formats,rates,channels based on i2s doc. */ + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_96000, + .rate_min = 8000, + .rate_max = 96000, + .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware acp_pcm_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + /* formats,rates,channels based on i2s doc. */ + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +struct audio_drv_data { + struct snd_pcm_substream *play_stream; + struct snd_pcm_substream *capture_stream; + struct amd_acp_device *acp_dev; + struct acp_irq_prv *iprv; +}; + +struct audio_substream_data { + struct amd_acp_device *acp_dev; + struct page *pg; + struct acp_dma_config *dma_config; + struct acp_i2s_config *i2s_config; + unsigned int order; +}; + +static const struct snd_soc_component_driver dw_i2s_component = { + .name = "dw-i2s.0", +}; + +static void acp_pcm_period_elapsed(struct device *dev, u16 play_intr, + u16 capture_intr) +{ + struct snd_pcm_substream *substream; + struct audio_drv_data *irq_data = + (struct audio_drv_data *)dev_get_drvdata(dev); + + /* Inform ALSA about the period elapsed (one out of two periods) */ + if (play_intr) + substream = irq_data->play_stream; + else if (capture_intr) + substream = irq_data->capture_stream; + + if (substream->runtime && snd_pcm_running(substream)) + snd_pcm_period_elapsed(substream); +} + +static int acp_dma_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *prtd = substream->private_data; + struct audio_drv_data *intr_data = + (struct audio_drv_data *)dev_get_drvdata(prtd->platform->dev); + + struct audio_substream_data *adata = + kzalloc(sizeof(struct audio_substream_data), GFP_KERNEL); + if (adata == NULL) + return -ENOMEM; + + adata->dma_config = + kzalloc(sizeof(struct acp_dma_config), GFP_KERNEL); + if (adata->dma_config == NULL) { + kfree(adata); + return -ENOMEM; + } + + adata->i2s_config = + kzalloc(sizeof(struct acp_i2s_config), GFP_KERNEL); + if (adata->i2s_config == NULL) { + kfree(adata->dma_config); + kfree(adata); + return -ENOMEM; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = acp_pcm_hardware_playback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + runtime->hw = acp_pcm_hardware_capture; + else { + pr_err("Error in stream type\n"); + return -EINVAL; + } + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_err("snd_pcm_hw_constraint_integer failed\n"); + return ret; + } + + adata->acp_dev = intr_data->acp_dev; + runtime->private_data = adata; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + intr_data->play_stream = substream; + else + intr_data->capture_stream = substream; + + return 0; +} + +static int acp_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int status; + uint64_t size; + struct snd_dma_buffer *dma_buffer; + struct page *pg; + u16 num_of_pages; + + struct snd_pcm_runtime *runtime; + struct audio_substream_data *rtd; + struct amd_acp_device *acp_dev; + + if (WARN_ON(!substream)) + return -EINVAL; + + dma_buffer = &substream->dma_buffer; + + runtime = substream->runtime; + rtd = runtime->private_data; + + if (WARN_ON(!rtd)) + return -EINVAL; + acp_dev = rtd->acp_dev; + + size = params_buffer_bytes(params); + status = snd_pcm_lib_malloc_pages(substream, size); + if (status < 0) + return status; + + memset(substream->runtime->dma_area, 0, params_buffer_bytes(params)); + pg = virt_to_page(substream->dma_buffer.area); + + if (NULL != pg) { + /* Save for runtime private data */ + rtd->pg = pg; + rtd->order = get_order(size); + + /*Let ACP know the Allocated memory */ + num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT; + + /* Fill the page table entries in ACP SRAM */ + + rtd->dma_config->pg = pg; + rtd->dma_config->size = size; + rtd->dma_config->num_of_pages = num_of_pages; + rtd->dma_config->direction = substream->stream; + + acp_dev->config_dma(acp_dev, rtd->dma_config); + + status = 0; + } else { + status = -ENOMEM; + } + return status; +} + +static int acp_dma_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static snd_pcm_uframes_t acp_dma_pointer(struct snd_pcm_substream *substream) +{ + u32 pos = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + struct amd_acp_device *acp_dev = rtd->acp_dev; + + pos = acp_dev->update_dma_pointer(acp_dev, substream->stream, + frames_to_bytes(runtime, runtime->period_size)); + return bytes_to_frames(runtime, pos); + +} + +static int acp_dma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return snd_pcm_lib_default_mmap(substream, vma); +} + +static int acp_dma_prepare(struct snd_pcm_substream *substream) +{ + int ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + struct amd_acp_device *acp_dev = rtd->acp_dev; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + + acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM, + PLAYBACK_START_DMA_DESCR_CH12, + NUM_DSCRS_PER_CHANNEL, 0); + acp_dev->config_dma_channel(acp_dev, ACP_TO_I2S_DMA_CH_NUM, + PLAYBACK_START_DMA_DESCR_CH13, + NUM_DSCRS_PER_CHANNEL, 0); + /* Fill ACP SRAM with zeros from System RAM which is zero-ed + * in hw_params */ + ret = acp_dev->dma_start(rtd->acp_dev, + SYSRAM_TO_ACP_CH_NUM, false); + if (ret < 0) + ret = -EFAULT; + + /* Now configure DMA to transfer only first half of System RAM + * buffer before playback is triggered. This will overwrite + * zero-ed second half of SRAM buffer */ + acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM, + PLAYBACK_START_DMA_DESCR_CH12, + 1, 0); + } else { + acp_dev->config_dma_channel(acp_dev, ACP_TO_SYSRAM_CH_NUM, + CAPTURE_START_DMA_DESCR_CH14, + NUM_DSCRS_PER_CHANNEL, 0); + acp_dev->config_dma_channel(acp_dev, I2S_TO_ACP_DMA_CH_NUM, + CAPTURE_START_DMA_DESCR_CH15, + NUM_DSCRS_PER_CHANNEL, 0); + } + return 0; +} + +static int acp_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + struct amd_acp_device *acp_dev = rtd->acp_dev; + + int ret = -EIO; + + if (!rtd) + return -EINVAL; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = acp_dev->dma_start(rtd->acp_dev, + SYSRAM_TO_ACP_CH_NUM, false); + if (ret < 0) + ret = -EFAULT; + else + acp_dev->prebuffer_audio(rtd->acp_dev); + + ret = acp_dev->dma_start(acp_dev, + ACP_TO_I2S_DMA_CH_NUM, true); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = acp_dev->dma_start(acp_dev, + I2S_TO_ACP_DMA_CH_NUM, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = acp_dev->dma_stop(acp_dev, SYSRAM_TO_ACP_CH_NUM); + if (0 == ret) + ret = acp_dev->dma_stop(acp_dev, + ACP_TO_I2S_DMA_CH_NUM); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = acp_dev->dma_stop(acp_dev, I2S_TO_ACP_DMA_CH_NUM); + if (0 == ret) + ret = acp_dev->dma_stop(acp_dev, + ACP_TO_SYSRAM_CH_NUM); + } + break; + default: + ret = -EINVAL; + + } + return ret; +} + +static int acp_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_pcm *pcm; + + pcm = rtd->pcm; + ret = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + NULL, MIN_BUFFER, MAX_BUFFER); + return ret; +} + +static int acp_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + + kfree(rtd->dma_config); + kfree(rtd->i2s_config); + kfree(rtd); + + return 0; +} + +static int acp_dai_i2s_hwparams(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *prtd = substream->private_data; + + struct audio_substream_data *rtd = runtime->private_data; + struct amd_acp_device *acp_dev = rtd->acp_dev; + struct device *dev = prtd->platform->dev; + + u32 chan_nr; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + rtd->i2s_config->xfer_resolution = 0x02; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + rtd->i2s_config->xfer_resolution = 0x04; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + rtd->i2s_config->xfer_resolution = 0x05; + break; + + default: + dev_err(dev, "designware-i2s: unsuppted PCM fmt"); + return -EINVAL; + } + + chan_nr = params_channels(params); + + switch (chan_nr) { + case EIGHT_CHANNEL_SUPPORT: + rtd->i2s_config->ch_reg = 3; + break; + case SIX_CHANNEL_SUPPORT: + rtd->i2s_config->ch_reg = 2; + break; + case FOUR_CHANNEL_SUPPORT: + rtd->i2s_config->ch_reg = 1; + break; + case TWO_CHANNEL_SUPPORT: + rtd->i2s_config->ch_reg = 0; + break; + default: + dev_err(dev, "channel not supported\n"); + return -EINVAL; + } + + rtd->i2s_config->direction = substream->stream; + + acp_dev->config_i2s(acp_dev, rtd->i2s_config); + + return 0; +} + +static int acp_dai_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + struct amd_acp_device *acp_dev = rtd->acp_dev; + + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + acp_dev->i2s_start(acp_dev, substream->stream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + acp_dev->i2s_stop(acp_dev, substream->stream); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int acp_dai_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_substream_data *rtd = runtime->private_data; + struct amd_acp_device *acp_dev = rtd->acp_dev; + + acp_dev->i2s_reset(acp_dev, substream->stream); + return 0; +} + +static struct snd_pcm_ops acp_dma_ops = { + .open = acp_dma_open, + .close = acp_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = acp_dma_hw_params, + .hw_free = acp_dma_hw_free, + .trigger = acp_dma_trigger, + .pointer = acp_dma_pointer, + .mmap = acp_dma_mmap, + .prepare = acp_dma_prepare, +}; + +struct snd_soc_dai_ops acp_dai_i2s_ops = { + .prepare = acp_dai_i2s_prepare, + .hw_params = acp_dai_i2s_hwparams, + .trigger = acp_dai_i2s_trigger, +}; + +/* CZ i2s configuration */ +static struct snd_soc_dai_driver i2s_dai_driver_cz = { + .playback = { + .stream_name = "I2S Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .stream_name = "I2S Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &acp_dai_i2s_ops, +}; + +static struct snd_soc_platform_driver acp_asoc_platform = { + .ops = &acp_dma_ops, + .pcm_new = acp_dma_new, +}; + +static int acp_alsa_register(struct device *dev, struct amd_acp_device *acp_dev, + struct amd_gnb_bus_dev *adev) +{ + int status; + + status = snd_soc_register_platform(dev, &acp_asoc_platform); + if (STATUS_SUCCESS != status) { + dev_err(dev, "Unable to register ALSA platform device\n"); + goto exit_platform; + } else { + /* SNDRV_PCM_FMTBIT_S16_LE is not supported in CZ */ + status = snd_soc_register_component(dev, + &dw_i2s_component, + &i2s_dai_driver_cz, 1); + + if (STATUS_SUCCESS != status) { + dev_err(dev, "Unable to register i2s dai\n"); + goto exit_dai; + } else { + dev_info(dev, "ACP device registered with ALSA\n"); + return status; + } + } + +exit_dai: + snd_soc_unregister_platform(dev); +exit_platform: + acp_dev->fini(acp_dev); + return status; +} + +static int acp_amdsoc_probe(struct amd_gnb_bus_dev *adev) +{ + int status; + struct audio_drv_data *audio_drv_data; + struct amd_acp_device *acp_dev = adev->private_data; + + if (AMD_GNB_IP_ACP_PCM != adev->ip) { + dev_err(&adev->dev, "Not an ACP Device on AMD GNB bus\n"); + return -ENODEV; + } + + audio_drv_data = devm_kzalloc(&adev->dev, + sizeof(struct audio_drv_data), + GFP_KERNEL); + if (audio_drv_data == NULL) + return -ENOMEM; + + audio_drv_data->iprv = devm_kzalloc(&adev->dev, + sizeof(struct acp_irq_prv), + GFP_KERNEL); + if (audio_drv_data->iprv == NULL) + return -ENOMEM; + + /* The following members gets populated in device 'open' + * function. Till then interrupts are disabled in 'acp_hw_init' + * and device doesn't generate any interrupts. + */ + + audio_drv_data->play_stream = NULL; + audio_drv_data->capture_stream = NULL; + audio_drv_data->acp_dev = acp_dev; + + audio_drv_data->iprv->dev = &adev->dev; + audio_drv_data->iprv->acp_dev = acp_dev; + audio_drv_data->iprv->set_elapsed = acp_pcm_period_elapsed; + + dev_set_drvdata(&adev->dev, audio_drv_data); + + /* Initialize the ACP */ + status = acp_dev->init(acp_dev, audio_drv_data->iprv); + + if (STATUS_SUCCESS == status) + status = acp_alsa_register(&adev->dev, acp_dev, adev); + else + pr_err("ACP initialization Failed\n"); + + return status; +} + +static int acp_amdsoc_remove(struct amd_gnb_bus_dev *adev) +{ + struct amd_acp_device *acp_dev = adev->private_data; + + snd_soc_unregister_component(&adev->dev); + snd_soc_unregister_platform(&adev->dev); + + acp_dev->fini(acp_dev); + + return 0; +} + +static struct amd_gnb_bus_driver acp_dma_driver = { + .name = "acp-pcm-driver", + .ip = AMD_GNB_IP_ACP_PCM, + .probe = acp_amdsoc_probe, + .remove = acp_amdsoc_remove, +}; + +static int __init amdsoc_bus_acp_dma_driver_init(void) +{ + int ret = 0; + + ret = amd_gnb_bus_register_driver(&acp_dma_driver, + THIS_MODULE, "acp-pcm-driver"); + if (ret) { + pr_err("ACP: Unable to register with AMD GNB BUS!\n"); + return ret; + } + + return 0; + +} + +static void __exit amdsoc_bus_acp_dma_driver_exit(void) +{ + pr_info("ACP: PCM driver exit\n"); + amd_gnb_bus_unregister_driver(&acp_dma_driver); +} + +module_init(amdsoc_bus_acp_dma_driver_init); +module_exit(amdsoc_bus_acp_dma_driver_exit); + +MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com"); +MODULE_DESCRIPTION("AMD ACP PCM Driver"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/sound/soc/amd/acp-rt286.c b/sound/soc/amd/acp-rt286.c new file mode 100644 index 0000000..7d4bdf9 --- /dev/null +++ b/sound/soc/amd/acp-rt286.c @@ -0,0 +1,265 @@ +/* + * Machine driver for AMD ACP Audio engine using Realtek RT286 codec + * + * Copyright 2014-2015 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + */ + +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/jack.h> +#include <linux/gpio.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/acpi.h> + +#include "../codecs/rt286.h" + +#ifdef CONFIG_PINCTRL_AMD + +#define CZ_HPJACK_GPIO 7 +#define CZ_HPJACK_DEBOUNCE 150 + +#endif + +#define CZ_CODEC_I2C_ADDR 0x1c +#define CZ_CODEC_I2C_ADAPTER_ID 3 + +struct i2c_client *i2c_client; + +struct acp_rt286 { + int gpio_hp_det; +}; + +static struct snd_soc_jack cz_jack; +static struct snd_soc_jack_pin cz_pins[] = { + { + .pin = "Analog Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headphones", + .mask = SND_JACK_HEADPHONE, + }, +}; + +static int carrizo_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_card *card = rtd->card; + int sample_rate; + int err; + + err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + dev_err(card->dev, "unable to set codec dai format\n"); + return err; + } + + sample_rate = params_rate(params); + + err = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, + SND_SOC_CLOCK_OUT); + if (err < 0) { + dev_err(card->dev, "unable to set codec dai clock\n"); + return err; + } + + return 0; + +} + +static struct snd_soc_ops carrizo_rt286_ops = { + .hw_params = carrizo_hw_params, +}; + +static int carrizo_init(struct snd_soc_pcm_runtime *rtd) +{ + /* TODO: check whether dapm widgets needs to be + * dsiconnected initially. */ + int ret; + struct snd_soc_card *card; + struct snd_soc_codec *codec; + + codec = rtd->codec; + card = rtd->card; + ret = snd_soc_card_jack_new(card, "Headset", + SND_JACK_HEADSET, &cz_jack, cz_pins, ARRAY_SIZE(cz_pins)); + + if (ret) + return ret; + + rt286_mic_detect(codec, &cz_jack); + return 0; +} + + +static struct snd_soc_dai_link carrizo_dai_rt286 = { + .name = "amd-rt286", + .stream_name = "RT286_AIF1", + .platform_name = "acp_pcm_dev", + .cpu_dai_name = "acp_pcm_dev", + .codec_dai_name = "rt286-aif1", + .codec_name = "rt286.3-001c", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM, + .ignore_suspend = 1, + .ops = &carrizo_rt286_ops, + .init = carrizo_init, +}; + +static const struct snd_soc_dapm_widget cz_widgets[] = { + SND_SOC_DAPM_HP("Headphones", NULL), + SND_SOC_DAPM_MIC("Analog Mic", NULL), +}; + +static const struct snd_soc_dapm_route cz_audio_route[] = { + {"Headphones", NULL, "HPO L"}, + {"Headphones", NULL, "HPO R"}, + {"MIC1", NULL, "Analog Mic"}, +}; + +static struct snd_soc_card carrizo_card = { + .name = "acp-rt286", + .owner = THIS_MODULE, + .dai_link = &carrizo_dai_rt286, + .num_links = 1, + + .dapm_widgets = cz_widgets, + .num_dapm_widgets = ARRAY_SIZE(cz_widgets), + .dapm_routes = cz_audio_route, + .num_dapm_routes = ARRAY_SIZE(cz_audio_route), +}; + +static int carrizo_probe(struct platform_device *pdev) +{ + int ret; + struct acp_rt286 *machine; + struct snd_soc_card *card; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct acp_rt286), + GFP_KERNEL); + if (!machine) + return -ENOMEM; + + card = &carrizo_card; + carrizo_card.dev = &pdev->dev; + + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, + "snd_soc_register_card(%s) failed: %d\n", + carrizo_card.name, ret); + return ret; + } + return 0; +} + +static int carrizo_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card; + + card = platform_get_drvdata(pdev); + snd_soc_unregister_card(card); + + return 0; +} + +static const struct acpi_device_id cz_audio_acpi_match[] = { + { "I2SC1002", 0 }, + {}, +}; + +static struct platform_driver carrizo_pcm_driver = { + .driver = { + .name = "carrizo_i2s_audio", + .acpi_match_table = ACPI_PTR(cz_audio_acpi_match), + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, + .probe = carrizo_probe, + .remove = carrizo_remove, +}; + +static int __init cz_audio_init(void) +{ + int ret; + struct i2c_adapter *adapter; + struct i2c_board_info cz_board_info; + const char *codec_acpi_name = "rt288"; + + adapter = i2c_get_adapter(CZ_CODEC_I2C_ADAPTER_ID); + if (!adapter) + return -ENODEV; + + memset(&cz_board_info, 0, sizeof(struct i2c_board_info)); + cz_board_info.addr = CZ_CODEC_I2C_ADDR; + strlcpy(cz_board_info.type, codec_acpi_name, I2C_NAME_SIZE); + +#ifdef CONFIG_PINCTRL_AMD + if (gpio_is_valid(CZ_HPJACK_GPIO)) { + ret = gpio_request_one(CZ_HPJACK_GPIO, GPIOF_DIR_IN | + GPIOF_EXPORT, "hp-gpio"); + if (ret != 0) + pr_err("gpio_request_one failed : err %d\n", ret); + + cz_board_info.irq = gpio_to_irq(CZ_HPJACK_GPIO); + + gpio_set_debounce(CZ_HPJACK_GPIO, CZ_HPJACK_DEBOUNCE); + } +#endif + i2c_client = i2c_new_device(adapter, &cz_board_info); + i2c_put_adapter(adapter); + if (!i2c_client) + return -ENODEV; + + platform_driver_register(&carrizo_pcm_driver); + return 0; +} + +static void __exit cz_audio_exit(void) +{ +#ifdef CONFIG_PINCTRL_AMD + if (gpio_is_valid(CZ_HPJACK_GPIO)) + gpio_free(CZ_HPJACK_GPIO); +#endif + i2c_unregister_device(i2c_client); + + platform_driver_unregister(&carrizo_pcm_driver); +} + +module_init(cz_audio_init); +module_exit(cz_audio_exit); + +MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com"); +MODULE_DESCRIPTION("CZ-rt288 Audio Support"); +MODULE_LICENSE("GPL and additional rights");
On Thu, Aug 06, 2015 at 10:25:10AM -0400, Alex Deucher wrote:
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
ACP IP block consists of dedicated DMA and I2S blocks. The PCM driver provides the DMA and CPU DAI components to ALSA core. Machine driver provides the audio functionality together with the PCM driver and rt286 codec driver.
Quite a few comments below, a lot of them fairly simple stylistic and process ones but there seem to be some really big issues in the way the machine driver is implemented and in particular with things being manually registered rather than instantiated with ACPI. I'm also worried about the unusual licensing.
Please also coordinate with Intel on ACPI bindings for ASoC style audio subsystems, I've CCed in Vinod who's been working on it (and Liam is also at Intel).
v2: squash in Kconfig fix
Please follow the patch submission process in SubmittingPatches: put any versioning in the subject line inside the [] and put noise like inter version changelogs after the ---.
--- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -41,3 +41,4 @@ obj-$(CONFIG_SND_SOC) += txx9/ obj-$(CONFIG_SND_SOC) += ux500/ obj-$(CONFIG_SND_SOC) += xtensa/ obj-$(CONFIG_SND_SOC) += zte/ +obj-$(CONFIG_SND_SOC) += amd/
Please keep the Makefile sorted as well as the Kconfig.
diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig new file mode 100644 index 0000000..07677de --- /dev/null +++ b/sound/soc/amd/Kconfig @@ -0,0 +1,13 @@
- config SND_SOC_AMD_CZ_RT286_MACH
tristate "AMD ASoC Audio driver for Carrizo with rt286 codec"
- select SND_SOC_RT286
- select SND_SOC_AMD_ACP
depends on I2C_DESIGNWARE_PLATFORM
help
This option enables AMD I2S Audio support on Carrizo
with ALC288 codec.
It looks like you've got tab/space here. You're also adding a machine driver in the same patch as the base driver support, please don't do that - send one patch per driver.
new file mode 100644 index 0000000..63b6f83 --- /dev/null +++ b/sound/soc/amd/Makefile @@ -0,0 +1,11 @@ +ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/amdsoc/ +ccflags-y += -Idrivers/gpu/drm/amdsoc/include/ +ccflags-y += -Idrivers/gpu/drm/amd/include/bus/ +ccflags-y += -Idrivers/gpu/drm/amd/acp/include +ccflags-y += -Idrivers/gpu/drm/amd/include/ +ccflags-y += -Idrivers/gpu/drm/amd/include/asic_reg/acp
Eew, no - please put these headers in include.
- AMD ALSA SoC PCM Driver
- Copyright 2014-2015 Advanced Micro Devices, Inc.
- Permission is hereby granted, free of charge, to any person obtaining a
- copy of this software and associated documentation files (the "Software"),
- to deal in the Software without restriction, including without limitation
- the rights to use, copy, modify, merge, publish, distribute, sublicense,
- and/or sell copies of the Software, and to permit persons to whom the
- Software is furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- OTHER DEALINGS IN THE SOFTWARE.
All the ASoC APIs are EXPORT_SYMBOL_GPL() and this doesn't explicitly grant GPL rights. Your MODULE_LICENSE() says
+MODULE_LICENSE("GPL and additional rights");
but I'm not 100% sure that's the case - it looks like a separate MIT/BSD license rather than a GPL grant. This doesn't make me happy about the licensing status. The clearest thing would be either to just license under the GPL, or to dual license.
- .fifo_size = 0,
No need to set static structures to 0.
+static const struct snd_soc_component_driver dw_i2s_component = {
- .name = "dw-i2s.0",
+};
.0? What's going on here...
+static void acp_pcm_period_elapsed(struct device *dev, u16 play_intr,
u16 capture_intr)
+{
- struct snd_pcm_substream *substream;
- struct audio_drv_data *irq_data =
(struct audio_drv_data *)dev_get_drvdata(dev);
No need to cast away from void.
- /* Inform ALSA about the period elapsed (one out of two periods) */
- if (play_intr)
substream = irq_data->play_stream;
- else if (capture_intr)
substream = irq_data->capture_stream;
- if (substream->runtime && snd_pcm_running(substream))
What if both play_intr and capture_intr or set, or if neither of them is set?
- adata->dma_config =
kzalloc(sizeof(struct acp_dma_config), GFP_KERNEL);
- if (adata->dma_config == NULL) {
kfree(adata);
return -ENOMEM;
- }
Why are all these structs allocated separately and not just embedded into adata?
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = acp_pcm_hardware_playback;
- else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
runtime->hw = acp_pcm_hardware_capture;
- else {
pr_err("Error in stream type\n");
return -EINVAL;
- }
You should have { } on all branches of an if statement if you need it on one. The usual idiom for this check is just to do "if (playback)" and not have the third error case.
- ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
- if (ret < 0) {
pr_err("snd_pcm_hw_constraint_integer failed\n");
Please use dev_ prints so people can tell more easily what the source of the message is.
- if (WARN_ON(!substream))
return -EINVAL;
The subsystem will check this for you.
- memset(substream->runtime->dma_area, 0, params_buffer_bytes(params));
- pg = virt_to_page(substream->dma_buffer.area);
- if (NULL != pg) {
/* Save for runtime private data */
rtd->pg = pg;
rtd->order = get_order(size);
/*Let ACP know the Allocated memory */
num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
Spaces in the comments.
/* Fill ACP SRAM with zeros from System RAM which is zero-ed
* in hw_params */
ret = acp_dev->dma_start(rtd->acp_dev,
SYSRAM_TO_ACP_CH_NUM, false);
if (ret < 0)
ret = -EFAULT;
/* Now configure DMA to transfer only first half of System RAM
* buffer before playback is triggered. This will overwrite
* zero-ed second half of SRAM buffer */
acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
PLAYBACK_START_DMA_DESCR_CH12,
1, 0);
Why? The comments describe what's happening but it's not clear why it's happening.
+static int acp_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct audio_substream_data *rtd = runtime->private_data;
- struct amd_acp_device *acp_dev = rtd->acp_dev;
- int ret = -EIO;
This means the compiler won't be able to spot missing initialisation problems - do we need to do it? Also I notice you've got a *lot* of blocks of variable declarations separated by spaces which is really unusual for the kernel.
- if (!rtd)
return -EINVAL;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = acp_dev->dma_start(rtd->acp_dev,
SYSRAM_TO_ACP_CH_NUM, false);
if (ret < 0)
ret = -EFAULT;
Don't ignore the error code you got, pass it back.
- default:
dev_err(dev, "designware-i2s: unsuppted PCM fmt");
return -EINVAL;
This doesn't appear to be a designware-i2s driver, we already have one of those? It's also better to print out the value that we're erroring on, it can help people figure out problems.
+static int acp_alsa_register(struct device *dev, struct amd_acp_device *acp_dev,
struct amd_gnb_bus_dev *adev)
+{
- int status;
- status = snd_soc_register_platform(dev, &acp_asoc_platform);
- if (STATUS_SUCCESS != status) {
STATUS_SUCCESS!? It's also very unusual to have the variable and the constant this way round in the kernel - I am noticing a lot of style issues in here.
+static void __exit amdsoc_bus_acp_dma_driver_exit(void) +{
- pr_info("ACP: PCM driver exit\n");
Don't include noise like this in the kernel logs, it's not adding anything.
- amd_gnb_bus_unregister_driver(&acp_dma_driver);
+}
+module_init(amdsoc_bus_acp_dma_driver_init); +module_exit(amdsoc_bus_acp_dma_driver_exit);
+MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com"); +MODULE_DESCRIPTION("AMD ACP PCM Driver"); +MODULE_LICENSE("GPL and additional rights");
How will this module get autoloaded?
+#include "../codecs/rt286.h"
+#ifdef CONFIG_PINCTRL_AMD
+#define CZ_HPJACK_GPIO 7
Hard coded system wide magic numbers? Please don't do this.
- err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
- if (err < 0) {
dev_err(card->dev, "unable to set codec dai format\n");
return err;
- }
Set this in the dai_link.
- err = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000,
SND_SOC_CLOCK_OUT);
- if (err < 0) {
dev_err(card->dev, "unable to set codec dai clock\n");
return err;
- }
Set this in the link init function, no need to reset it each time we configure since it never changes.
+static int carrizo_init(struct snd_soc_pcm_runtime *rtd) +{
- /* TODO: check whether dapm widgets needs to be
* dsiconnected initially. */
They don't.
+static struct snd_soc_dai_link carrizo_dai_rt286 = {
- .name = "amd-rt286",
- .stream_name = "RT286_AIF1",
- .platform_name = "acp_pcm_dev",
- .cpu_dai_name = "acp_pcm_dev",
- .codec_dai_name = "rt286-aif1",
- .codec_name = "rt286.3-001c",
- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
Oh, you did initialise this in the dai_link :(
- .ignore_suspend = 1,
Why is this being set - this doesn't look like it's a CODEC<->CODEC link?
- ret = snd_soc_register_card(card);
- if (ret) {
devm_snd_soc_register_card()
+static const struct acpi_device_id cz_audio_acpi_match[] = {
- { "I2SC1002", 0 },
- {},
+};
ACPI has bindings for GPIOs, you should be able to use those to discover the detection GPIO.
+static int __init cz_audio_init(void) +{
- int ret;
- struct i2c_adapter *adapter;
- struct i2c_board_info cz_board_info;
- const char *codec_acpi_name = "rt288";
- adapter = i2c_get_adapter(CZ_CODEC_I2C_ADAPTER_ID);
- if (!adapter)
return -ENODEV;
- memset(&cz_board_info, 0, sizeof(struct i2c_board_info));
- cz_board_info.addr = CZ_CODEC_I2C_ADDR;
- strlcpy(cz_board_info.type, codec_acpi_name, I2C_NAME_SIZE);
+#ifdef CONFIG_PINCTRL_AMD
- if (gpio_is_valid(CZ_HPJACK_GPIO)) {
ret = gpio_request_one(CZ_HPJACK_GPIO, GPIOF_DIR_IN |
GPIOF_EXPORT, "hp-gpio");
if (ret != 0)
pr_err("gpio_request_one failed : err %d\n", ret);
As well as the whole thing with getting this from ACPI rather than defining magic numbers this should be done in the card init not in the module init (like other card drivers do). This then means you don't need any global variables.
+#endif
- i2c_client = i2c_new_device(adapter, &cz_board_info);
- i2c_put_adapter(adapter);
- if (!i2c_client)
return -ENODEV;
No, definitely not - ACPI has perfectly good bindings for instantiating I2C devices (indeed the laptop I'm typing this on actually uses them to instantiate exactly the same RT286 audio CODEC you're using here), please use them.
Hi Mark,
Thanks, I will work on the review feedback.
Regards, Maruthi
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org] Sent: 07 August 2015 00:52 To: Alex Deucher Cc: airlied@gmail.com; dri-devel@lists.freedesktop.org; alsa-devel@alsa-project.org; tiwai@suse.de; perex@perex.cz; lgirdwood@gmail.com; Bayyavarapu, Maruthi; Vinod Koul Subject: Re: [PATCH 10/12] ASoC: AMD: add AMD ASoC ACP-I2S driver (v2)
On Thu, Aug 06, 2015 at 10:25:10AM -0400, Alex Deucher wrote:
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
ACP IP block consists of dedicated DMA and I2S blocks. The PCM driver provides the DMA and CPU DAI components to ALSA core. Machine driver provides the audio functionality together with the PCM driver and rt286 codec driver.
Quite a few comments below, a lot of them fairly simple stylistic and process ones but there seem to be some really big issues in the way the machine driver is implemented and in particular with things being manually registered rather than instantiated with ACPI. I'm also worried about the unusual licensing.
Please also coordinate with Intel on ACPI bindings for ASoC style audio subsystems, I've CCed in Vinod who's been working on it (and Liam is also at Intel).
v2: squash in Kconfig fix
Please follow the patch submission process in SubmittingPatches: put any versioning in the subject line inside the [] and put noise like inter version changelogs after the ---.
--- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -41,3 +41,4 @@ obj-$(CONFIG_SND_SOC) += txx9/ obj-$(CONFIG_SND_SOC) += ux500/ obj-$(CONFIG_SND_SOC) += xtensa/ obj-$(CONFIG_SND_SOC) += zte/ +obj-$(CONFIG_SND_SOC) += amd/
Please keep the Makefile sorted as well as the Kconfig.
diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig new file mode 100644 index 0000000..07677de --- /dev/null +++ b/sound/soc/amd/Kconfig @@ -0,0 +1,13 @@
- config SND_SOC_AMD_CZ_RT286_MACH
tristate "AMD ASoC Audio driver for Carrizo with rt286 codec"
- select SND_SOC_RT286
- select SND_SOC_AMD_ACP
depends on I2C_DESIGNWARE_PLATFORM
help
This option enables AMD I2S Audio support on Carrizo
with ALC288 codec.
It looks like you've got tab/space here. You're also adding a machine driver in the same patch as the base driver support, please don't do that - send one patch per driver.
new file mode 100644 index 0000000..63b6f83 --- /dev/null +++ b/sound/soc/amd/Makefile @@ -0,0 +1,11 @@ +ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/amdsoc/ ccflags-y += +-Idrivers/gpu/drm/amdsoc/include/ +ccflags-y += -Idrivers/gpu/drm/amd/include/bus/ +ccflags-y += -Idrivers/gpu/drm/amd/acp/include ccflags-y += +-Idrivers/gpu/drm/amd/include/ ccflags-y += +-Idrivers/gpu/drm/amd/include/asic_reg/acp
Eew, no - please put these headers in include.
- AMD ALSA SoC PCM Driver
- Copyright 2014-2015 Advanced Micro Devices, Inc.
- Permission is hereby granted, free of charge, to any person
- obtaining a
- copy of this software and associated documentation files (the
- "Software"),
- to deal in the Software without restriction, including without
- limitation
- the rights to use, copy, modify, merge, publish, distribute,
- sublicense,
- and/or sell copies of the Software, and to permit persons to whom
- the
- Software is furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be
- included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
- SHALL
- THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM,
- DAMAGES OR
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- OTHERWISE,
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
- OR
- OTHER DEALINGS IN THE SOFTWARE.
All the ASoC APIs are EXPORT_SYMBOL_GPL() and this doesn't explicitly grant GPL rights. Your MODULE_LICENSE() says
+MODULE_LICENSE("GPL and additional rights");
but I'm not 100% sure that's the case - it looks like a separate MIT/BSD license rather than a GPL grant. This doesn't make me happy about the licensing status. The clearest thing would be either to just license under the GPL, or to dual license.
- .fifo_size = 0,
No need to set static structures to 0.
+static const struct snd_soc_component_driver dw_i2s_component = {
- .name = "dw-i2s.0",
+};
.0? What's going on here...
+static void acp_pcm_period_elapsed(struct device *dev, u16 play_intr,
u16 capture_intr)
+{
- struct snd_pcm_substream *substream;
- struct audio_drv_data *irq_data =
(struct audio_drv_data *)dev_get_drvdata(dev);
No need to cast away from void.
- /* Inform ALSA about the period elapsed (one out of two periods) */
- if (play_intr)
substream = irq_data->play_stream;
- else if (capture_intr)
substream = irq_data->capture_stream;
- if (substream->runtime && snd_pcm_running(substream))
What if both play_intr and capture_intr or set, or if neither of them is set?
- adata->dma_config =
kzalloc(sizeof(struct acp_dma_config), GFP_KERNEL);
- if (adata->dma_config == NULL) {
kfree(adata);
return -ENOMEM;
- }
Why are all these structs allocated separately and not just embedded into adata?
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = acp_pcm_hardware_playback;
- else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
runtime->hw = acp_pcm_hardware_capture;
- else {
pr_err("Error in stream type\n");
return -EINVAL;
- }
You should have { } on all branches of an if statement if you need it on one. The usual idiom for this check is just to do "if (playback)" and not have the third error case.
- ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
- if (ret < 0) {
pr_err("snd_pcm_hw_constraint_integer failed\n");
Please use dev_ prints so people can tell more easily what the source of the message is.
- if (WARN_ON(!substream))
return -EINVAL;
The subsystem will check this for you.
- memset(substream->runtime->dma_area, 0, params_buffer_bytes(params));
- pg = virt_to_page(substream->dma_buffer.area);
- if (NULL != pg) {
/* Save for runtime private data */
rtd->pg = pg;
rtd->order = get_order(size);
/*Let ACP know the Allocated memory */
num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
Spaces in the comments.
/* Fill ACP SRAM with zeros from System RAM which is zero-ed
* in hw_params */
ret = acp_dev->dma_start(rtd->acp_dev,
SYSRAM_TO_ACP_CH_NUM, false);
if (ret < 0)
ret = -EFAULT;
/* Now configure DMA to transfer only first half of System RAM
* buffer before playback is triggered. This will overwrite
* zero-ed second half of SRAM buffer */
acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
PLAYBACK_START_DMA_DESCR_CH12,
1, 0);
Why? The comments describe what's happening but it's not clear why it's happening.
+static int acp_dma_trigger(struct snd_pcm_substream *substream, int +cmd) {
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct audio_substream_data *rtd = runtime->private_data;
- struct amd_acp_device *acp_dev = rtd->acp_dev;
- int ret = -EIO;
This means the compiler won't be able to spot missing initialisation problems - do we need to do it? Also I notice you've got a *lot* of blocks of variable declarations separated by spaces which is really unusual for the kernel.
- if (!rtd)
return -EINVAL;
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_RESUME:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = acp_dev->dma_start(rtd->acp_dev,
SYSRAM_TO_ACP_CH_NUM, false);
if (ret < 0)
ret = -EFAULT;
Don't ignore the error code you got, pass it back.
- default:
dev_err(dev, "designware-i2s: unsuppted PCM fmt");
return -EINVAL;
This doesn't appear to be a designware-i2s driver, we already have one of those? It's also better to print out the value that we're erroring on, it can help people figure out problems.
+static int acp_alsa_register(struct device *dev, struct amd_acp_device *acp_dev,
struct amd_gnb_bus_dev *adev)
+{
- int status;
- status = snd_soc_register_platform(dev, &acp_asoc_platform);
- if (STATUS_SUCCESS != status) {
STATUS_SUCCESS!? It's also very unusual to have the variable and the constant this way round in the kernel - I am noticing a lot of style issues in here.
+static void __exit amdsoc_bus_acp_dma_driver_exit(void) +{
- pr_info("ACP: PCM driver exit\n");
Don't include noise like this in the kernel logs, it's not adding anything.
- amd_gnb_bus_unregister_driver(&acp_dma_driver);
+}
+module_init(amdsoc_bus_acp_dma_driver_init); +module_exit(amdsoc_bus_acp_dma_driver_exit);
+MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com"); +MODULE_DESCRIPTION("AMD ACP PCM Driver"); MODULE_LICENSE("GPL and +additional rights");
How will this module get autoloaded?
+#include "../codecs/rt286.h"
+#ifdef CONFIG_PINCTRL_AMD
+#define CZ_HPJACK_GPIO 7
Hard coded system wide magic numbers? Please don't do this.
- err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM);
- if (err < 0) {
dev_err(card->dev, "unable to set codec dai format\n");
return err;
- }
Set this in the dai_link.
- err = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000,
SND_SOC_CLOCK_OUT);
- if (err < 0) {
dev_err(card->dev, "unable to set codec dai clock\n");
return err;
- }
Set this in the link init function, no need to reset it each time we configure since it never changes.
+static int carrizo_init(struct snd_soc_pcm_runtime *rtd) {
- /* TODO: check whether dapm widgets needs to be
* dsiconnected initially. */
They don't.
+static struct snd_soc_dai_link carrizo_dai_rt286 = {
- .name = "amd-rt286",
- .stream_name = "RT286_AIF1",
- .platform_name = "acp_pcm_dev",
- .cpu_dai_name = "acp_pcm_dev",
- .codec_dai_name = "rt286-aif1",
- .codec_name = "rt286.3-001c",
- .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM,
Oh, you did initialise this in the dai_link :(
- .ignore_suspend = 1,
Why is this being set - this doesn't look like it's a CODEC<->CODEC link?
- ret = snd_soc_register_card(card);
- if (ret) {
devm_snd_soc_register_card()
+static const struct acpi_device_id cz_audio_acpi_match[] = {
- { "I2SC1002", 0 },
- {},
+};
ACPI has bindings for GPIOs, you should be able to use those to discover the detection GPIO.
+static int __init cz_audio_init(void) {
- int ret;
- struct i2c_adapter *adapter;
- struct i2c_board_info cz_board_info;
- const char *codec_acpi_name = "rt288";
- adapter = i2c_get_adapter(CZ_CODEC_I2C_ADAPTER_ID);
- if (!adapter)
return -ENODEV;
- memset(&cz_board_info, 0, sizeof(struct i2c_board_info));
- cz_board_info.addr = CZ_CODEC_I2C_ADDR;
- strlcpy(cz_board_info.type, codec_acpi_name, I2C_NAME_SIZE);
+#ifdef CONFIG_PINCTRL_AMD
- if (gpio_is_valid(CZ_HPJACK_GPIO)) {
ret = gpio_request_one(CZ_HPJACK_GPIO, GPIOF_DIR_IN |
GPIOF_EXPORT, "hp-gpio");
if (ret != 0)
pr_err("gpio_request_one failed : err %d\n", ret);
As well as the whole thing with getting this from ACPI rather than defining magic numbers this should be done in the card init not in the module init (like other card drivers do). This then means you don't need any global variables.
+#endif
- i2c_client = i2c_new_device(adapter, &cz_board_info);
- i2c_put_adapter(adapter);
- if (!i2c_client)
return -ENODEV;
No, definitely not - ACPI has perfectly good bindings for instantiating I2C devices (indeed the laptop I'm typing this on actually uses them to instantiate exactly the same RT286 audio CODEC you're using here), please use them.
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
Added suspend/resume functionality for AMD ACP ASoC PCM driver. Enabled them in the ASoC machine driver
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed-by: Alex Deucher alexander.deucher@amd.com Reviewed-by: Murali Krishna Vemuri murali-krishna.vemuri@amd.com --- sound/soc/amd/acp-pcm-dma.c | 31 +++++++++++++++++++++++++++++++ sound/soc/amd/acp-rt286.c | 1 - 2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c index 0f58957..db09b77 100644 --- a/sound/soc/amd/acp-pcm-dma.c +++ b/sound/soc/amd/acp-pcm-dma.c @@ -625,11 +625,42 @@ static int acp_amdsoc_remove(struct amd_gnb_bus_dev *adev) return 0; }
+static int acp_pcm_suspend(struct device *dev) +{ + return 0; +} + +static int acp_pcm_resume(struct device *dev) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct audio_substream_data *rtd; + + struct audio_drv_data *irq_data = + (struct audio_drv_data *)dev_get_drvdata(dev); + + substream = irq_data->play_stream; + runtime = substream->runtime; + rtd = runtime->private_data; + + irq_data->acp_dev->config_i2s(irq_data->acp_dev, rtd->i2s_config); + irq_data->acp_dev->config_dma(irq_data->acp_dev, rtd->dma_config); + + return 0; +} + +static const struct dev_pm_ops acp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(acp_pcm_suspend, acp_pcm_resume) +}; + static struct amd_gnb_bus_driver acp_dma_driver = { .name = "acp-pcm-driver", .ip = AMD_GNB_IP_ACP_PCM, .probe = acp_amdsoc_probe, .remove = acp_amdsoc_remove, + .driver = { + .pm = &acp_pm_ops, + }, };
static int __init amdsoc_bus_acp_dma_driver_init(void) diff --git a/sound/soc/amd/acp-rt286.c b/sound/soc/amd/acp-rt286.c index 7d4bdf9..8be9236 100644 --- a/sound/soc/amd/acp-rt286.c +++ b/sound/soc/amd/acp-rt286.c @@ -129,7 +129,6 @@ static struct snd_soc_dai_link carrizo_dai_rt286 = { .codec_name = "rt286.3-001c", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM, - .ignore_suspend = 1, .ops = &carrizo_rt286_ops, .init = carrizo_init, };
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
Added runtime PM functionality to AMD I2S driver. This uses functionality from ACP IP methods.
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Reviewed-by: Alex Deucher alexander.deucher@amd.com Reviewed-by: Murali Krishna Vemuri murali-krishna.vemuri@amd.com --- sound/soc/amd/acp-pcm-dma.c | 87 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 10 deletions(-)
diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c index db09b77..7e0e5cb 100644 --- a/sound/soc/amd/acp-pcm-dma.c +++ b/sound/soc/amd/acp-pcm-dma.c @@ -32,6 +32,7 @@ #include <linux/err.h> #include <linux/io.h> #include <linux/pci.h> +#include <linux/pm_runtime.h>
#include <sound/pcm.h> #include <sound/pcm_params.h> @@ -379,10 +380,12 @@ static int acp_dma_close(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct audio_substream_data *rtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *prtd = substream->private_data; kfree(rtd->dma_config); kfree(rtd->i2s_config); kfree(rtd);
+ pm_runtime_mark_last_busy(prtd->platform->dev); return 0; }
@@ -610,6 +613,10 @@ static int acp_amdsoc_probe(struct amd_gnb_bus_dev *adev) else pr_err("ACP initialization Failed\n");
+ pm_runtime_set_autosuspend_delay(&adev->dev, 10000); + pm_runtime_use_autosuspend(&adev->dev); + pm_runtime_enable(&adev->dev); + return status; }
@@ -621,36 +628,96 @@ static int acp_amdsoc_remove(struct amd_gnb_bus_dev *adev) snd_soc_unregister_platform(&adev->dev);
acp_dev->fini(acp_dev); - + pm_runtime_disable(&adev->dev); return 0; }
static int acp_pcm_suspend(struct device *dev) { + bool pm_rts; + struct audio_drv_data *adata = + (struct audio_drv_data *)dev_get_drvdata(dev); + + pm_rts = pm_runtime_status_suspended(dev); + if (pm_rts == false) + adata->acp_dev->fini(adata->acp_dev); + return 0; }
static int acp_pcm_resume(struct device *dev) { - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; + bool pm_rts; + struct snd_pcm_substream *pstream, *cstream; + struct snd_pcm_runtime *prtd, *crtd; struct audio_substream_data *rtd;
- struct audio_drv_data *irq_data = + struct audio_drv_data *adata = (struct audio_drv_data *)dev_get_drvdata(dev);
- substream = irq_data->play_stream; - runtime = substream->runtime; - rtd = runtime->private_data; + pm_rts = pm_runtime_status_suspended(dev); + if (pm_rts == true) { + /* Resumed from system wide suspend and there is + * no pending audio activity to resume. */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + goto out; + }
- irq_data->acp_dev->config_i2s(irq_data->acp_dev, rtd->i2s_config); - irq_data->acp_dev->config_dma(irq_data->acp_dev, rtd->dma_config); + pstream = adata->play_stream; + prtd = pstream ? pstream->runtime : NULL; + if (prtd != NULL) { + /* Resume playback stream from a suspended state */ + rtd = prtd->private_data;
+ adata->acp_dev->config_dma(adata->acp_dev, rtd->dma_config); + adata->acp_dev->config_i2s(adata->acp_dev, rtd->i2s_config); + } + + cstream = adata->capture_stream; + crtd = cstream ? cstream->runtime : NULL; + if (crtd != NULL) { + /* Resume capture stream from a suspended state */ + rtd = crtd->private_data; + + adata->acp_dev->config_dma(adata->acp_dev, rtd->dma_config); + adata->acp_dev->config_i2s(adata->acp_dev, rtd->i2s_config); + } +out: + return 0; +} + +int acp_pcm_runtime_suspend(struct device *dev) +{ + struct audio_drv_data *adata = + (struct audio_drv_data *)dev_get_drvdata(dev); + + adata->acp_dev->acp_suspend(adata->acp_dev); + return 0; +} + +int acp_pcm_runtime_resume(struct device *dev) +{ + struct audio_drv_data *adata = + (struct audio_drv_data *)dev_get_drvdata(dev); + + adata->acp_dev->acp_resume(adata->acp_dev); + return 0; +} + +int acp_pcm_runtime_idle(struct device *dev) +{ return 0; }
static const struct dev_pm_ops acp_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(acp_pcm_suspend, acp_pcm_resume) + .suspend = acp_pcm_suspend, + .resume = acp_pcm_resume, + .runtime_suspend = acp_pcm_runtime_suspend, + .runtime_resume = acp_pcm_runtime_resume, + .runtime_idle = acp_pcm_runtime_idle };
static struct amd_gnb_bus_driver acp_dma_driver = {
On Thu, Aug 06, 2015 at 10:25:12AM -0400, Alex Deucher wrote:
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
Added runtime PM functionality to AMD I2S driver. This uses functionality from ACP IP methods.
Just add this (and the previous patch) in the driver. The patches are so tiny it's not adding much to split them out like this and it's actually harder to review this one when it's split from the system suspend code.
Hi Mark,
I will send a another patch-set, with patches combined, as suggested.
Regards, Maruthi
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org] Sent: 07 August 2015 00:57 To: Alex Deucher Cc: airlied@gmail.com; dri-devel@lists.freedesktop.org; alsa-devel@alsa-project.org; tiwai@suse.de; perex@perex.cz; lgirdwood@gmail.com; Bayyavarapu, Maruthi Subject: Re: [PATCH 12/12] ASoC: AMD: add ACP PCM driver runtime PM
On Thu, Aug 06, 2015 at 10:25:12AM -0400, Alex Deucher wrote:
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
Added runtime PM functionality to AMD I2S driver. This uses functionality from ACP IP methods.
Just add this (and the previous patch) in the driver. The patches are so tiny it's not adding much to split them out like this and it's actually harder to review this one when it's split from the system suspend code.
participants (5)
-
Alex Deucher
-
Bayyavarapu, Maruthi
-
Felix Kuehling
-
Lars-Peter Clausen
-
Mark Brown