[alsa-devel] [PATCH v3 0/7] ASoC: intel - add skylake PCM driver
This patch series adds the hda codec wrapper first followed by asoc hda controller library, then SKL PCM driver and last decouples the controller for splitting the links
Jeeja KP (7): ASoC: hda - add soc hda codec driver wrapper ALSA: hda - add new HDA registers ASoC: hda - adds SoC controller and stream operations ASoC: intel - add Skylake HDA platform driver ASoC: intel - add Skylake HDA audio driver ASoC: intel - add makefile support for SKL driver ASoC: intel - adds support for decoupled mode in skl driver
include/sound/hda_register.h | 87 +++ include/sound/soc-hdaudio.h | 343 ++++++++++++ sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/hda/Kconfig | 3 + sound/soc/hda/Makefile | 3 + sound/soc/hda/soc-hda-codec.c | 85 +++ sound/soc/hda/soc-hda-codec.h | 44 ++ sound/soc/hda/soc-hdac-controller.c | 289 ++++++++++ sound/soc/hda/soc-hdac-stream.c | 390 ++++++++++++++ sound/soc/intel/Kconfig | 20 + sound/soc/intel/Makefile | 1 + sound/soc/intel/skylake/Makefile | 3 + sound/soc/intel/skylake/hda-skl-pcm.c | 933 +++++++++++++++++++++++++++++++++ sound/soc/intel/skylake/hda-skl.c | 743 ++++++++++++++++++++++++++ sound/soc/intel/skylake/hda-skl.h | 74 +++ 16 files changed, 3020 insertions(+) create mode 100644 include/sound/soc-hdaudio.h create mode 100644 sound/soc/hda/Kconfig create mode 100644 sound/soc/hda/Makefile create mode 100644 sound/soc/hda/soc-hda-codec.c create mode 100644 sound/soc/hda/soc-hda-codec.h create mode 100644 sound/soc/hda/soc-hdac-controller.c create mode 100644 sound/soc/hda/soc-hdac-stream.c create mode 100644 sound/soc/intel/skylake/Makefile create mode 100644 sound/soc/intel/skylake/hda-skl-pcm.c create mode 100644 sound/soc/intel/skylake/hda-skl.c create mode 100644 sound/soc/intel/skylake/hda-skl.h
From: Jeeja KP jeeja.kp@intel.com
For ASoC HDA codecs we need to provide match function based on id_table and driver register/unregister wrapper functions
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/hda/Kconfig | 3 ++ sound/soc/hda/Makefile | 3 ++ sound/soc/hda/soc-hda-codec.c | 85 +++++++++++++++++++++++++++++++++++++++++ sound/soc/hda/soc-hda-codec.h | 44 +++++++++++++++++++++ 6 files changed, 137 insertions(+) create mode 100644 sound/soc/hda/Kconfig create mode 100644 sound/soc/hda/Makefile create mode 100644 sound/soc/hda/soc-hda-codec.c create mode 100644 sound/soc/hda/soc-hda-codec.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 3ba52da18bc6..d3903580cb10 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -40,6 +40,7 @@ source "sound/soc/cirrus/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/dwc/Kconfig" source "sound/soc/fsl/Kconfig" +source "sound/soc/hda/Kconfig" source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 974ba708b482..8741d6a38bf6 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_SND_SOC) += cirrus/ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += dwc/ obj-$(CONFIG_SND_SOC) += fsl/ +obj-$(CONFIG_SND_SOC) += hda/ obj-$(CONFIG_SND_SOC) += jz4740/ obj-$(CONFIG_SND_SOC) += intel/ obj-$(CONFIG_SND_SOC) += mxs/ diff --git a/sound/soc/hda/Kconfig b/sound/soc/hda/Kconfig new file mode 100644 index 000000000000..815943360bc5 --- /dev/null +++ b/sound/soc/hda/Kconfig @@ -0,0 +1,3 @@ +config SND_SOC_HDA_CORE + tristate + select SND_HDA_CORE diff --git a/sound/soc/hda/Makefile b/sound/soc/hda/Makefile new file mode 100644 index 000000000000..9585ab180a55 --- /dev/null +++ b/sound/soc/hda/Makefile @@ -0,0 +1,3 @@ +snd-soc-hda-core-objs := soc-hda-codec.o + +obj-$(CONFIG_SND_SOC_HDA_CORE) += snd-soc-hda-core.o diff --git a/sound/soc/hda/soc-hda-codec.c b/sound/soc/hda/soc-hda-codec.c new file mode 100644 index 000000000000..caceb2136bba --- /dev/null +++ b/sound/soc/hda/soc-hda-codec.c @@ -0,0 +1,85 @@ +/* + * soc-hda-codec.c ASoC High Definition Audio Codec interface Definitions + * + * Copyright (c) 2014-2015 Intel Corporation + * + * Author(s): Jeeja KP jeeja.kp@intel.com + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <sound/hdaudio.h> +#include "soc-hda-codec.h" + +/** + * snd_soc_hda_get_device_id - gets the hdac device id entry + * @hdev: HD-audio core bus + * @drv: HD-audio core stream object to initialize + * + * Compares the hdac device vendor_id and revision_id to the hdac_soc_codec + * driver id_table and returns the matching device id entry. + */ +const struct soc_hda_device_id * +snd_soc_hda_get_device_id( + struct hdac_device *hdev, + struct soc_hda_codec_driver *drv) +{ + if (drv->id_table) { + const struct soc_hda_device_id *id = drv->id_table; + + while (id->name[0]) { + if (hdev->vendor_id == id->id && + (!id->rev_id || id->rev_id == hdev->revision_id)) + return id; + id++; + } + } + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_hda_get_device_id); + +static int hda_codec_match(struct hdac_device *dev, struct hdac_driver *drv) +{ + struct soc_hda_codec_driver *driver = to_soc_hda_codec_driver(drv); + + if (snd_soc_hda_get_device_id(dev, driver)) + return 1; + else + return 0; +} + +/** + * snd_soc_hda_codec_driver_register - register hda codec driver + * @drv: HD Audio soc codec driver + */ +int snd_soc_hda_codec_driver_register(struct soc_hda_codec_driver *drv) +{ + drv->core.driver.bus = &snd_hda_bus_type; + drv->core.type = HDA_DEV_ASOC; + drv->core.match = hda_codec_match; + return driver_register(&drv->core.driver); +} +EXPORT_SYMBOL_GPL(snd_soc_hda_codec_driver_register); + +/** + * snd_soc_hda_codec_driver_unregister - unregister hda codec driver + * @drv: HD Audio soc codec driver + */ +void snd_soc_hda_codec_driver_unregister(struct soc_hda_codec_driver *drv) +{ + driver_unregister(&drv->core.driver); +} +EXPORT_SYMBOL_GPL(snd_soc_hda_codec_driver_unregister); + +MODULE_DESCRIPTION("ASoC HDA codec core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/hda/soc-hda-codec.h b/sound/soc/hda/soc-hda-codec.h new file mode 100644 index 000000000000..f8a199a58926 --- /dev/null +++ b/sound/soc/hda/soc-hda-codec.h @@ -0,0 +1,44 @@ +/* + * ASoC High Definition Audio Codec interface + * + * Copyright (c) 2014-2015 Intel Corporation + * + * Author(s): Jeeja KP jeeja.kp@intel.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include <sound/hdaudio.h> + +/*HDA device table*/ +#define HDA_NAME_SIZE 20 +struct soc_hda_device_id { + __u32 id; + __u32 rev_id; + char name[HDA_NAME_SIZE]; + unsigned long driver_data; +}; + +struct soc_hda_codec_driver { + struct hdac_driver core; + const struct soc_hda_device_id *id_table; +}; + +#define to_hdac_driver(drv) (container_of((drv), \ + struct hdac_driver, driver)) +#define to_soc_hda_codec_driver(hdrv) (container_of((hdrv), \ + struct soc_hda_codec_driver, core)) + +int snd_soc_hda_codec_driver_register(struct soc_hda_codec_driver *drv); +void snd_soc_hda_codec_driver_unregister(struct soc_hda_codec_driver *drv); +const struct soc_hda_device_id *snd_soc_hda_get_device_id( + struct hdac_device *hdev, struct soc_hda_codec_driver *drv);
At Wed, 29 Apr 2015 01:24:24 +0530, Vinod Koul wrote:
From: Jeeja KP jeeja.kp@intel.com
For ASoC HDA codecs we need to provide match function based on id_table and driver register/unregister wrapper functions
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/hda/Kconfig | 3 ++ sound/soc/hda/Makefile | 3 ++ sound/soc/hda/soc-hda-codec.c | 85 +++++++++++++++++++++++++++++++++++++++++ sound/soc/hda/soc-hda-codec.h | 44 +++++++++++++++++++++ 6 files changed, 137 insertions(+) create mode 100644 sound/soc/hda/Kconfig create mode 100644 sound/soc/hda/Makefile create mode 100644 sound/soc/hda/soc-hda-codec.c create mode 100644 sound/soc/hda/soc-hda-codec.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 3ba52da18bc6..d3903580cb10 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -40,6 +40,7 @@ source "sound/soc/cirrus/Kconfig" source "sound/soc/davinci/Kconfig" source "sound/soc/dwc/Kconfig" source "sound/soc/fsl/Kconfig" +source "sound/soc/hda/Kconfig" source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 974ba708b482..8741d6a38bf6 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_SND_SOC) += cirrus/ obj-$(CONFIG_SND_SOC) += davinci/ obj-$(CONFIG_SND_SOC) += dwc/ obj-$(CONFIG_SND_SOC) += fsl/ +obj-$(CONFIG_SND_SOC) += hda/ obj-$(CONFIG_SND_SOC) += jz4740/ obj-$(CONFIG_SND_SOC) += intel/ obj-$(CONFIG_SND_SOC) += mxs/ diff --git a/sound/soc/hda/Kconfig b/sound/soc/hda/Kconfig new file mode 100644 index 000000000000..815943360bc5 --- /dev/null +++ b/sound/soc/hda/Kconfig @@ -0,0 +1,3 @@ +config SND_SOC_HDA_CORE
- tristate
- select SND_HDA_CORE
diff --git a/sound/soc/hda/Makefile b/sound/soc/hda/Makefile new file mode 100644 index 000000000000..9585ab180a55 --- /dev/null +++ b/sound/soc/hda/Makefile @@ -0,0 +1,3 @@ +snd-soc-hda-core-objs := soc-hda-codec.o
+obj-$(CONFIG_SND_SOC_HDA_CORE) += snd-soc-hda-core.o diff --git a/sound/soc/hda/soc-hda-codec.c b/sound/soc/hda/soc-hda-codec.c new file mode 100644 index 000000000000..caceb2136bba --- /dev/null +++ b/sound/soc/hda/soc-hda-codec.c @@ -0,0 +1,85 @@ +/*
- soc-hda-codec.c ASoC High Definition Audio Codec interface Definitions
- Copyright (c) 2014-2015 Intel Corporation
- Author(s): Jeeja KP jeeja.kp@intel.com
- This driver is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This driver is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- */
+#include <linux/module.h> +#include <sound/hdaudio.h> +#include "soc-hda-codec.h"
+/**
- snd_soc_hda_get_device_id - gets the hdac device id entry
- @hdev: HD-audio core bus
Not a bus.
- @drv: HD-audio core stream object to initialize
Not a stream.
- Compares the hdac device vendor_id and revision_id to the hdac_soc_codec
- driver id_table and returns the matching device id entry.
- */
+const struct soc_hda_device_id * +snd_soc_hda_get_device_id(
struct hdac_device *hdev,
struct soc_hda_codec_driver *drv)
The indentation looks a bid odd.
diff --git a/sound/soc/hda/soc-hda-codec.h b/sound/soc/hda/soc-hda-codec.h new file mode 100644 index 000000000000..f8a199a58926 --- /dev/null +++ b/sound/soc/hda/soc-hda-codec.h @@ -0,0 +1,44 @@ +/*
- ASoC High Definition Audio Codec interface
- Copyright (c) 2014-2015 Intel Corporation
- Author(s): Jeeja KP jeeja.kp@intel.com
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the Free
- Software Foundation; either version 2 of the License, or (at your option)
- any later version.
- This program is distributed in the hope that it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- more details.
- */
+#include <sound/hdaudio.h>
+/*HDA device table*/ +#define HDA_NAME_SIZE 20 +struct soc_hda_device_id {
- __u32 id;
- __u32 rev_id;
- char name[HDA_NAME_SIZE];
- unsigned long driver_data;
+};
+struct soc_hda_codec_driver {
- struct hdac_driver core;
- const struct soc_hda_device_id *id_table;
+};
+#define to_hdac_driver(drv) (container_of((drv), \
struct hdac_driver, driver))
+#define to_soc_hda_codec_driver(hdrv) (container_of((hdrv), \
struct soc_hda_codec_driver, core))
+int snd_soc_hda_codec_driver_register(struct soc_hda_codec_driver *drv); +void snd_soc_hda_codec_driver_unregister(struct soc_hda_codec_driver *drv); +const struct soc_hda_device_id *snd_soc_hda_get_device_id(
- struct hdac_device *hdev, struct soc_hda_codec_driver *drv);
The ifdef guard is missing.
Takashi
On Wed, Apr 29, 2015 at 01:24:24AM +0530, Vinod Koul wrote:
For ASoC HDA codecs we need to provide match function based on id_table and driver register/unregister wrapper functions
This changelog doesn't really leave me that much the wiser as to what this is intended to do... what are we matching in what ID table and what are we wrapping?
sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/hda/Kconfig | 3 ++ sound/soc/hda/Makefile | 3 ++ sound/soc/hda/soc-hda-codec.c | 85 +++++++++++++++++++++++++++++++++++++++++ sound/soc/hda/soc-hda-codec.h | 44 +++++++++++++++++++++ 6 files changed, 137 insertions(+)
If this is for CODECs why is it in a new directory?
+const struct soc_hda_device_id * +snd_soc_hda_get_device_id(
struct hdac_device *hdev,
struct soc_hda_codec_driver *drv)
Please can we have more normal indentation - put at least the first argument for the function on the same line as the function name.
On Mon, May 04, 2015 at 02:12:26PM +0100, Mark Brown wrote:
On Wed, Apr 29, 2015 at 01:24:24AM +0530, Vinod Koul wrote:
For ASoC HDA codecs we need to provide match function based on id_table and driver register/unregister wrapper functions
This changelog doesn't really leave me that much the wiser as to what this is intended to do... what are we matching in what ID table and what are we wrapping?
sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/hda/Kconfig | 3 ++ sound/soc/hda/Makefile | 3 ++ sound/soc/hda/soc-hda-codec.c | 85 +++++++++++++++++++++++++++++++++++++++++ sound/soc/hda/soc-hda-codec.h | 44 +++++++++++++++++++++ 6 files changed, 137 insertions(+)
If this is for CODECs why is it in a new directory?
yes it is for HDA codecs which are enumerated over HDA links, but this is the HDA bus code which is ASoC specfic. All generic HDA code which uses the hda lib (new sound/hda) is kept in sound/soc/hda All intel driver updates will go to sound/soc/intel/skylake The codecs drivers will as usual show up in sound/soc/codecs/
+const struct soc_hda_device_id * +snd_soc_hda_get_device_id( + struct hdac_device *hdev, + struct soc_hda_codec_driver *drv)
Please can we have more normal indentation - put at least the first argument for the function on the same line as the function name.
Yes Takashi also pointed out, fixed now. The zeal to keep under 80 chars made it look funny.
Thanks
On Wed, May 06, 2015 at 09:17:50AM +0530, Vinod Koul wrote:
On Mon, May 04, 2015 at 02:12:26PM +0100, Mark Brown wrote:
If this is for CODECs why is it in a new directory?
yes it is for HDA codecs which are enumerated over HDA links, but this is the HDA bus code which is ASoC specfic. All generic HDA code which uses the hda lib (new sound/hda) is kept in sound/soc/hda All intel driver updates will go to sound/soc/intel/skylake The codecs drivers will as usual show up in sound/soc/codecs/
I'm still not 100% clear as to what a "HDA CODEC wrapper" is intended to be. I think this series probably needs an introduction which describes what the overall picture we're aiming for is.
On Wed, May 06, 2015 at 01:51:23PM +0100, Mark Brown wrote:
On Wed, May 06, 2015 at 09:17:50AM +0530, Vinod Koul wrote:
On Mon, May 04, 2015 at 02:12:26PM +0100, Mark Brown wrote:
If this is for CODECs why is it in a new directory?
yes it is for HDA codecs which are enumerated over HDA links, but this is the HDA bus code which is ASoC specfic. All generic HDA code which uses the hda lib (new sound/hda) is kept in sound/soc/hda All intel driver updates will go to sound/soc/intel/skylake The codecs drivers will as usual show up in sound/soc/codecs/
I'm still not 100% clear as to what a "HDA CODEC wrapper" is intended to be. I think this series probably needs an introduction which describes what the overall picture we're aiming for is.
okay let me try that here as well as next version which i will post tomorrow.
So SKL has HDA controller based audio subsystem with DSP and gets better with support for I2S, HDA, PDM links.
So we worked with Takashi on HDA parts and now we have core code of HDA moved into sound/hda which current HDA drivers use and will also be used by ASoC SKL driver.
The SKL platform driver will load and create the soc_hdac_bus which would embed the hdac_bus, same for hdac_device (hda codecs) and hdac_stream (pcms) This is on top of hdac code in Takashi's topic/hda
This patch provides the match function for asoc type hda codecs and lets them get enumerated by hdac. The second patch in this series adds the controller specific soc code. Common parts are in hdac core with changes introduced as part of SKL controller in soc part. Then we add the rest of controller PCM driver code (still HDA) and last patch breaks the HDA streams to host and link which will allow insertion of DSP in between these links.
The subsequent series will add IPC driver for SKL (using common IPC routines), then DSP topology handlers, DSP code with I2S support and then lastly when DFW is accepted then its handlers.
Let me know if you have more questions around this, would be happy to answer them :)
Thanks
From: Jeeja KP jeeja.kp@intel.com
This patch adds new registers as per HD audio Spec like capability registers for processing pipe, software position based FIFO, Multiple Links and Global Time Synchronization.
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/hda_register.h | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+)
diff --git a/include/sound/hda_register.h b/include/sound/hda_register.h index 4f6d3fce6ee6..dced67f5c35f 100644 --- a/include/sound/hda_register.h +++ b/include/sound/hda_register.h @@ -28,6 +28,10 @@ #define AZX_REG_STATESTS 0x0e #define AZX_REG_GSTS 0x10 #define AZX_GSTS_FSTS (1 << 1) /* flush status */ +#define AZX_REG_GCAP2 0x12 +#define AZX_REG_LLCH 0x14 +#define AZX_REG_OUTSTRMPAY 0x18 +#define AZX_REG_INSTRMPAY 0x1A #define AZX_REG_INTCTL 0x20 #define AZX_REG_INTSTS 0x24 #define AZX_REG_WALLCLK 0x30 /* 24Mhz source */ @@ -81,6 +85,7 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_SD_FIFOW 0x0e #define AZX_REG_SD_FIFOSIZE 0x10 #define AZX_REG_SD_FORMAT 0x12 +#define AZX_REG_SD_FIFOL 0x14 #define AZX_REG_SD_BDLPL 0x18 #define AZX_REG_SD_BDLPU 0x1c
@@ -134,6 +139,88 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_MAX_CORB_ENTRIES 256 #define AZX_MAX_RIRB_ENTRIES 256
+#define AZX_REG_CAP_HDR 0x0 +#define CAP_HDR_VER_OFF 28 +#define CAP_HDR_VER_MASK (0xF << CAP_HDR_VER_OFF) +#define CAP_HDR_ID_OFF 16 +#define CAP_HDR_ID_MASK (0xFFF << CAP_HDR_ID_OFF) +#define CAP_HDR_NXT_PTR_MASK 0xFFFF + +/* registers of Software Position Based FIFO Capability Structure */ +#define SPB_CAP_ID 0x4 +#define AZX_REG_SPB_BASE_ADDR 0x700 +#define AZX_REG_SPB_SPBFCH 0x00 +#define AZX_REG_SPB_SPBFCCTL 0x04 +/* Base used to calculate the iterating register offset */ +#define SPB_BASE 0x08 +/* Interval used to calculate the iterating register offset */ +#define SPB_INTERVAL 0x08 + +/* registers of Global Time Synchronization Capability Structure */ +#define GTS_CAP_ID 0x1 +#define AZX_REG_GTS_GTSCH 0x00 +#define AZX_REG_GTS_GTSCD 0x04 +#define AZX_REG_GTS_GTSCTLAC 0x0C +#define GTS_BASE 0x20 +#define GTS_INTERVAL 0x20 + +/* registers for Processing Pipe Capability Structure */ +#define PP_CAP_ID 0x3 +#define AZX_REG_PP_PPCH 0x10 +#define AZX_REG_PP_PPCTL 0x04 +#define PPCTL_PIE (1<<31) +#define PPCTL_GPROCEN (1<<30) +/* _X_ = dma engine # and cannot * exceed 29 (per spec max 30 dma engines) */ +#define PPCTL_PROCEN(_X_) (1<<(_X_)) + +#define AZX_REG_PP_PPSTS 0x08 + +#define PPHC_BASE 0x10 +#define PPHC_INTERVAL 0x10 + +#define AZX_REG_PPHCLLPL 0x0 +#define AZX_REG_PPHCLLPU 0x4 +#define AZX_REG_PPHCLDPL 0x8 +#define AZX_REG_PPHCLDPU 0xC + +#define PPLC_BASE 0x10 +#define PPLC_MULTI 0x10 +#define PPLC_INTERVAL 0x10 + +#define AZX_REG_PPLCCTL 0x0 +#define PPLCCTL_STRM_BITS 4 +#define PPLCCTL_STRM_SHIFT 20 +#define REG_MASK(bit_num, offset) \ + (((1 << (bit_num)) - 1) << (offset)) +#define PPLCCTL_STRM_MASK \ + REG_MASK(PPLCCTL_STRM_BITS, PPLCCTL_STRM_SHIFT) +#define PPLCCTL_RUN (1<<1) +#define PPLCCTL_STRST (1<<0) + +#define AZX_REG_PPLCFMT 0x4 +#define AZX_REG_PPLCLLPL 0x8 +#define AZX_REG_PPLCLLPU 0xC + +/* registers for Multiple Links Capability Structure */ +#define ML_CAP_ID 0x2 +#define AZX_REG_ML_MLCH 0x00 +#define AZX_REG_ML_MLCD 0x04 +#define ML_BASE 0x40 +#define ML_INTERVAL 0x40 + +#define AZX_REG_ML_LCAP 0x00 +#define AZX_REG_ML_LCTL 0x04 +#define AZX_REG_ML_LOSIDV 0x08 +#define AZX_REG_ML_LSDIID 0x0C +#define AZX_REG_ML_LPSOO 0x10 +#define AZX_REG_ML_LPSIO 0x12 +#define AZX_REG_ML_LWALFC 0x18 +#define AZX_REG_ML_LOUTPAY 0x20 +#define AZX_REG_ML_LINPAY 0x30 + +#define MLCTL_SPA (1<<16) +#define MLCTL_CPA 23 + /* * helpers to read the stream position */
At Wed, 29 Apr 2015 01:24:25 +0530, Vinod Koul wrote:
From: Jeeja KP jeeja.kp@intel.com
This patch adds new registers as per HD audio Spec like capability registers for processing pipe, software position based FIFO, Multiple Links and Global Time Synchronization.
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
Should I merge this patch now to topic/hda branch? This looks like the only patch changing the hda core code, the rest are all hda_soc specific.
Or, feel free to take my ack: Acked-by: Takashi Iwai tiwai@suse.de
Takashi
include/sound/hda_register.h | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+)
diff --git a/include/sound/hda_register.h b/include/sound/hda_register.h index 4f6d3fce6ee6..dced67f5c35f 100644 --- a/include/sound/hda_register.h +++ b/include/sound/hda_register.h @@ -28,6 +28,10 @@ #define AZX_REG_STATESTS 0x0e #define AZX_REG_GSTS 0x10 #define AZX_GSTS_FSTS (1 << 1) /* flush status */ +#define AZX_REG_GCAP2 0x12 +#define AZX_REG_LLCH 0x14 +#define AZX_REG_OUTSTRMPAY 0x18 +#define AZX_REG_INSTRMPAY 0x1A #define AZX_REG_INTCTL 0x20 #define AZX_REG_INTSTS 0x24 #define AZX_REG_WALLCLK 0x30 /* 24Mhz source */ @@ -81,6 +85,7 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_SD_FIFOW 0x0e #define AZX_REG_SD_FIFOSIZE 0x10 #define AZX_REG_SD_FORMAT 0x12 +#define AZX_REG_SD_FIFOL 0x14 #define AZX_REG_SD_BDLPL 0x18 #define AZX_REG_SD_BDLPU 0x1c
@@ -134,6 +139,88 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_MAX_CORB_ENTRIES 256 #define AZX_MAX_RIRB_ENTRIES 256
+#define AZX_REG_CAP_HDR 0x0 +#define CAP_HDR_VER_OFF 28 +#define CAP_HDR_VER_MASK (0xF << CAP_HDR_VER_OFF) +#define CAP_HDR_ID_OFF 16 +#define CAP_HDR_ID_MASK (0xFFF << CAP_HDR_ID_OFF) +#define CAP_HDR_NXT_PTR_MASK 0xFFFF
+/* registers of Software Position Based FIFO Capability Structure */ +#define SPB_CAP_ID 0x4 +#define AZX_REG_SPB_BASE_ADDR 0x700 +#define AZX_REG_SPB_SPBFCH 0x00 +#define AZX_REG_SPB_SPBFCCTL 0x04 +/* Base used to calculate the iterating register offset */ +#define SPB_BASE 0x08 +/* Interval used to calculate the iterating register offset */ +#define SPB_INTERVAL 0x08
+/* registers of Global Time Synchronization Capability Structure */ +#define GTS_CAP_ID 0x1 +#define AZX_REG_GTS_GTSCH 0x00 +#define AZX_REG_GTS_GTSCD 0x04 +#define AZX_REG_GTS_GTSCTLAC 0x0C +#define GTS_BASE 0x20 +#define GTS_INTERVAL 0x20
+/* registers for Processing Pipe Capability Structure */ +#define PP_CAP_ID 0x3 +#define AZX_REG_PP_PPCH 0x10 +#define AZX_REG_PP_PPCTL 0x04 +#define PPCTL_PIE (1<<31) +#define PPCTL_GPROCEN (1<<30) +/* _X_ = dma engine # and cannot * exceed 29 (per spec max 30 dma engines) */ +#define PPCTL_PROCEN(_X_) (1<<(_X_))
+#define AZX_REG_PP_PPSTS 0x08
+#define PPHC_BASE 0x10 +#define PPHC_INTERVAL 0x10
+#define AZX_REG_PPHCLLPL 0x0 +#define AZX_REG_PPHCLLPU 0x4 +#define AZX_REG_PPHCLDPL 0x8 +#define AZX_REG_PPHCLDPU 0xC
+#define PPLC_BASE 0x10 +#define PPLC_MULTI 0x10 +#define PPLC_INTERVAL 0x10
+#define AZX_REG_PPLCCTL 0x0 +#define PPLCCTL_STRM_BITS 4 +#define PPLCCTL_STRM_SHIFT 20 +#define REG_MASK(bit_num, offset) \
- (((1 << (bit_num)) - 1) << (offset))
+#define PPLCCTL_STRM_MASK \
- REG_MASK(PPLCCTL_STRM_BITS, PPLCCTL_STRM_SHIFT)
+#define PPLCCTL_RUN (1<<1) +#define PPLCCTL_STRST (1<<0)
+#define AZX_REG_PPLCFMT 0x4 +#define AZX_REG_PPLCLLPL 0x8 +#define AZX_REG_PPLCLLPU 0xC
+/* registers for Multiple Links Capability Structure */ +#define ML_CAP_ID 0x2 +#define AZX_REG_ML_MLCH 0x00 +#define AZX_REG_ML_MLCD 0x04 +#define ML_BASE 0x40 +#define ML_INTERVAL 0x40
+#define AZX_REG_ML_LCAP 0x00 +#define AZX_REG_ML_LCTL 0x04 +#define AZX_REG_ML_LOSIDV 0x08 +#define AZX_REG_ML_LSDIID 0x0C +#define AZX_REG_ML_LPSOO 0x10 +#define AZX_REG_ML_LPSIO 0x12 +#define AZX_REG_ML_LWALFC 0x18 +#define AZX_REG_ML_LOUTPAY 0x20 +#define AZX_REG_ML_LINPAY 0x30
+#define MLCTL_SPA (1<<16) +#define MLCTL_CPA 23
/*
- helpers to read the stream position
*/
1.7.9.5
On Wed, Apr 29, 2015 at 12:41:50PM +0200, Takashi Iwai wrote:
At Wed, 29 Apr 2015 01:24:25 +0530, Vinod Koul wrote:
From: Jeeja KP jeeja.kp@intel.com
This patch adds new registers as per HD audio Spec like capability registers for processing pipe, software position based FIFO, Multiple Links and Global Time Synchronization.
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
Should I merge this patch now to topic/hda branch? This looks like the only patch changing the hda core code, the rest are all hda_soc specific.
yes, I would still request you to go thru the asoc patches :), your comments will help. Specfically am looking at the soc hda layer (1st two asoc patches) and then skl driver controller changes
Or, feel free to take my ack: Acked-by: Takashi Iwai tiwai@suse.de
Thanks
At Wed, 29 Apr 2015 01:24:25 +0530, Vinod Koul wrote:
From: Jeeja KP jeeja.kp@intel.com
This patch adds new registers as per HD audio Spec like capability registers for processing pipe, software position based FIFO, Multiple Links and Global Time Synchronization.
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
include/sound/hda_register.h | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+)
diff --git a/include/sound/hda_register.h b/include/sound/hda_register.h index 4f6d3fce6ee6..dced67f5c35f 100644 --- a/include/sound/hda_register.h +++ b/include/sound/hda_register.h @@ -28,6 +28,10 @@ #define AZX_REG_STATESTS 0x0e #define AZX_REG_GSTS 0x10 #define AZX_GSTS_FSTS (1 << 1) /* flush status */ +#define AZX_REG_GCAP2 0x12 +#define AZX_REG_LLCH 0x14 +#define AZX_REG_OUTSTRMPAY 0x18 +#define AZX_REG_INSTRMPAY 0x1A #define AZX_REG_INTCTL 0x20 #define AZX_REG_INTSTS 0x24 #define AZX_REG_WALLCLK 0x30 /* 24Mhz source */ @@ -81,6 +85,7 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_REG_SD_FIFOW 0x0e #define AZX_REG_SD_FIFOSIZE 0x10 #define AZX_REG_SD_FORMAT 0x12 +#define AZX_REG_SD_FIFOL 0x14 #define AZX_REG_SD_BDLPL 0x18 #define AZX_REG_SD_BDLPU 0x1c
@@ -134,6 +139,88 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; #define AZX_MAX_CORB_ENTRIES 256 #define AZX_MAX_RIRB_ENTRIES 256
+#define AZX_REG_CAP_HDR 0x0 +#define CAP_HDR_VER_OFF 28 +#define CAP_HDR_VER_MASK (0xF << CAP_HDR_VER_OFF) +#define CAP_HDR_ID_OFF 16 +#define CAP_HDR_ID_MASK (0xFFF << CAP_HDR_ID_OFF) +#define CAP_HDR_NXT_PTR_MASK 0xFFFF
In general, it's safer to a unique prefix like AZX_ (although we didn't apply the rule strictly to some defines).
Takashi
From: Jeeja KP jeeja.kp@intel.com
Adds ASoC HDA library for generic HDA SoC controller and stream operations This will be used by SKL HDA driver
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/soc-hdaudio.h | 343 ++++++++++++++++++++++++++++++ sound/soc/hda/Makefile | 2 +- sound/soc/hda/soc-hdac-controller.c | 289 ++++++++++++++++++++++++++ sound/soc/hda/soc-hdac-stream.c | 390 +++++++++++++++++++++++++++++++++++ 4 files changed, 1023 insertions(+), 1 deletion(-) create mode 100644 include/sound/soc-hdaudio.h create mode 100644 sound/soc/hda/soc-hdac-controller.c create mode 100644 sound/soc/hda/soc-hdac-stream.c
diff --git a/include/sound/soc-hdaudio.h b/include/sound/soc-hdaudio.h new file mode 100644 index 000000000000..4e90d25ecf2b --- /dev/null +++ b/include/sound/soc-hdaudio.h @@ -0,0 +1,343 @@ +#ifndef __SOUND_SOC_HDAUDIO_H +#define __SOUND_SOC_HDAUDIO_H + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/hda_register.h> +#include <sound/hdaudio.h> + +struct soc_hdac_bus { + struct hdac_bus bus; + int num_streams; + + bool ppcap; + bool spbcap; + bool mlcap; + bool gtscap; + + void __iomem *ppcap_addr; + void __iomem *spbcap_addr; + void __iomem *mlcap_addr; + void __iomem *gtscap_addr; + + /* linked list of hda links */ + struct list_head hlink_list; +}; + +#define hdac_bus(sbus) (&(sbus)->bus) +#define bus_to_soc_hdac_bus(_bus) \ + container_of(_bus, struct soc_hdac_bus, bus) + +int snd_soc_hdac_bus_parse_capabilities(struct soc_hdac_bus *sbus); +void snd_soc_hdac_bus_ppcap_enable(struct soc_hdac_bus *chip, bool enable); +void snd_soc_hdac_bus_ppcap_int_enable(struct soc_hdac_bus *chip, bool enable); + +/* + * macros for ppcap register read/write + */ +#define _soc_hdac_bus_ppcap_write(type, dev, reg, value) \ + ((dev)->bus.io_ops->reg_write ## type(value, (dev)->ppcap_addr + (reg))) +#define _soc_hdac_bus_ppcap_read(type, dev, reg) \ + ((dev)->bus.io_ops->reg_read ## type((dev)->ppcap_addr + (reg))) + +/* read/write a register, pass without AZX_REG_ prefix */ +#define soc_hdac_bus_ppcap_writel(dev, reg, value) \ + _soc_hdac_bus_ppcap_write(l, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_ppcap_writew(dev, reg, value) \ + _soc_hdac_bus_ppcap_write(w, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_ppcap_writeb(dev, reg, value) \ + _soc_hdac_bus_ppcap_write(b, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_ppcap_readl(dev, reg) \ + _soc_hdac_bus_ppcap_read(l, dev, AZX_REG_ ## reg) +#define soc_hdac_bus_ppcap_readw(dev, reg) \ + _soc_hdac_bus_ppcap_read(w, dev, AZX_REG_ ## reg) +#define soc_hdac_bus_ppcap_readb(dev, reg) \ + _soc_hdac_bus_ppcap_read(b, dev, AZX_REG_ ## reg) + +/* update a register, pass without AZX_REG_ prefix */ +#define soc_hdac_bus_ppcap_updatel(dev, reg, mask, val) \ + soc_hdac_bus_ppcap_writel(dev, reg, \ + (soc_hdac_bus_ppcap_readl(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_bus_ppcap_updatew(dev, reg, mask, val) \ + soc_hdac_bus_ppcap_writew(dev, reg, \ + (soc_hdac_bus_ppcap_readw(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_bus_ppcap_updateb(dev, reg, mask, val) \ + soc_hdac_bus_ppcap_writeb(dev, reg, \ + (soc_hdac_bus_ppcap_readb(dev, reg) & \ + ~(mask)) | (val)) + +void snd_soc_hdac_stream_spbcap_enable(struct soc_hdac_bus *chip, + bool enable, int index); +/* + * macros for spcap register read/write + */ +#define _soc_hdac_bus_spbcap_write(type, dev, reg, value) \ + ((dev)->bus.io_ops->reg_write ## type(value, (dev)->spbcap_addr + (reg))) +#define _soc_hdac_bus_spbcap_read(type, dev, reg) \ + ((dev)->bus.io_ops->reg_read ## type((dev)->spbcap_addr + (reg))) + +/* read/write a register, pass without AZX_REG_ prefix */ +#define soc_hdac_bus_spbcap_writel(dev, reg, value) \ + _soc_hdac_bus_spbcap_write(l, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_spbcap_writew(dev, reg, value) \ + _soc_hdac_bus_spbcap_write(w, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_spbcap_writeb(dev, reg, value) \ + _soc_hdac_bus_spbcap_write(b, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_spbcap_readl(dev, reg) \ + _soc_hdac_bus_spbcap_read(l, dev, AZX_REG_ ## reg) +#define soc_hdac_bus_spbcap_readw(dev, reg) \ + _soc_hdac_bus_spbcap_read(w, dev, AZX_REG_ ## reg) +#define soc_hdac_bus_spbcap_readb(dev, reg) \ + _soc_hdac_bus_spbcap_read(b, dev, AZX_REG_ ## reg) + +/* update a register, pass without AZX_REG_ prefix */ +#define soc_hdac_bus_spbcap_updatel(dev, reg, mask, val) \ + soc_hdac_bus_spbcap_writel(dev, reg, \ + (soc_hdac_bus_spbcap_readl(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_bus_spbcap_updatew(dev, reg, mask, val) \ + soc_hdac_bus_spbcap_writew(dev, reg, \ + (soc_hdac_bus_spbcap_readw(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_bus_spbcap_updateb(dev, reg, mask, val) \ + soc_hdac_bus_spbcap_writeb(dev, reg, \ + (soc_hdac_bus_spbcap_readb(dev, reg) & \ + ~(mask)) | (val)) + +int snd_soc_hdac_bus_get_ml_capablities(struct soc_hdac_bus *bus); +int snd_soc_hdac_bus_map_codec_to_link(struct soc_hdac_bus *bus, int addr); +struct soc_hdac_link *snd_soc_hdac_bus_get_link(struct soc_hdac_bus *bus, + const char *codec_name); + +/* + * macros for mlcap register read/write + */ +#define _soc_hdac_bus_mlcap_write(type, dev, reg, value) \ + ((dev)->bus.io_ops->reg_write ## type(value, (dev)->mlcap_addr + (reg))) +#define _soc_hdac_bus_mlcap_read(type, dev, reg) \ + ((dev)->bus.io_ops->reg_read ## type((dev)->mlcap_addr + (reg))) + +/* read/write a register, pass without AZX_REG_ prefix */ +#define soc_hdac_bus_mlcap_writel(dev, reg, value) \ + _soc_hdac_bus_mlcap_write(l, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_mlcap_writew(dev, reg, value) \ + _soc_hdac_bus_mlcap_write(w, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_mlcap_writeb(dev, reg, value) \ + _soc_hdac_bus_mlcap_write(b, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_mlcap_readl(dev, reg) \ + _soc_hdac_bus_mlcap_read(l, dev, AZX_REG_ ## reg) +#define soc_hdac_bus_mlcap_readw(dev, reg) \ + _soc_hdac_bus_mlcap_read(w, dev, AZX_REG_ ## reg) +#define soc_hdac_bus_mlcap_readb(dev, reg) \ + _soc_hdac_bus_mlcap_read(b, dev, AZX_REG_ ## reg) + +/* update a register, pass without AZX_REG_ prefix */ +#define soc_hdac_bus_mlcap_updatel(dev, reg, mask, val) \ + soc_hdac_bus_mlcap_writel(dev, reg, \ + (soc_hdac_bus_mlcap_readl(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_bus_mlcap_updatew(dev, reg, mask, val) \ + soc_hdac_bus_mlcap_writew(dev, reg, \ + (soc_hdac_bus_mlcap_readw(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_bus_mlcap_updateb(dev, reg, mask, val) \ + soc_hdac_bus_mlcap_writeb(dev, reg, \ + (soc_hdac_bus_mlcap_readb(dev, reg) & \ + ~(mask)) | (val)) + +/* + * macros for gtscap register read/write + */ +#define _soc_hdac_bus_gtscap_write(type, dev, reg, value) \ + ((dev)->bus.io_ops->reg_write ## type(value, (dev)->gtscap_addr + (reg))) +#define _soc_hdac_bus_gtscap_read(type, dev, reg) \ + ((dev)->bus.io_ops->reg_read ## type((dev)->gtscap_addr + (reg))) + +/* read/write a register, pass without AZX_REG_ prefix */ +#define soc_hdac_bus_gtscap_writel(dev, reg, value) \ + _soc_hdac_bus_gtscap_write(l, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_gtscap_writew(dev, reg, value) \ + _soc_hdac_bus_gtscap_write(w, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_gtscap_writeb(dev, reg, value) \ + _soc_hdac_bus_gtscap_write(b, dev, AZX_REG_ ## reg, value) +#define soc_hdac_bus_gtscap_readl(dev, reg) \ + _soc_hdac_bus_gtscap_read(l, dev, AZX_REG_ ## reg) +#define soc_hdac_bus_gtscap_readw(dev, reg) \ + _soc_hdac_bus_gtscap_read(w, dev, AZX_REG_ ## reg) +#define soc_hdac_bus_gtscap_readb(dev, reg) \ + _soc_hdac_bus_gtscap_read(b, dev, AZX_REG_ ## reg) + +/* update a register, pass without AZX_REG_ prefix */ +#define soc_hdac_bus_gtscap_updatel(dev, reg, mask, val) \ + soc_hdac_bus_gtscap_writel(dev, reg, \ + (soc_hdac_bus_gtscap_readl(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_bus_gtscap_updatew(dev, reg, mask, val) \ + soc_hdac_bus_gtscap_writew(dev, reg, \ + (soc_hdac_bus_gtscap_readw(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_bus_gtscap_updateb(dev, reg, mask, val) \ + soc_hdac_bus_gtscap_writeb(dev, reg, \ + (soc_hdac_bus_gtscap_readb(dev, reg) & \ + ~(mask)) | (val)) +enum hdac_stream_type { + HDAC_STREAM_TYPE_COUPLED = 0, + HDAC_STREAM_TYPE_HOST, + HDAC_STREAM_TYPE_LINK +}; + +struct soc_hdac_stream { + struct hdac_stream hstream; + unsigned int decoupled:1; + void __iomem *pphc_addr; /* processing pipe host stream reg pointer */ + void __iomem *pplc_addr; /* processing pipe link stream reg pointer */ + bool link_locked:1; + struct snd_pcm_substream *link_substream; + bool link_prepared; +}; + +#define hdac_stream(s) (&(s)->hstream) +#define stream_to_soc_hdac_stream(s) \ + container_of(s, struct soc_hdac_stream, hstream) + +void snd_soc_hdac_stream_init(struct soc_hdac_bus *bus, struct soc_hdac_stream *azx_dev, + int idx, int direction, int tag); +struct soc_hdac_stream *snd_soc_hdac_stream_assign(struct soc_hdac_bus *bus, + struct snd_pcm_substream *substream, + int type); +void snd_soc_hdac_stream_release(struct soc_hdac_stream *azx_dev, int type); +void snd_soc_hdac_stream_decouple(struct soc_hdac_bus *bus, + struct soc_hdac_stream *azx_dev, bool decouple); +void snd_soc_hdac_stop_streams(struct soc_hdac_bus *sbus); + +/* + * macros for host stream register read/write + */ +#define _soc_hdac_host_stream_write(type, dev, reg, value) \ + ((dev)->hstream.bus->io_ops->reg_write ## type(value, (dev)->pphc_addr + (reg))) +#define _soc_hdac_host_stream_read(type, dev, reg) \ + ((dev)->hstream.bus->io_ops->reg_read ## type((dev)->pphc_addr + (reg))) + +/* read/write a register, pass without AZX_REG_ prefix */ +#define soc_hdac_host_stream_writel(dev, reg, value) \ + _soc_hdac_host_stream_write(l, dev, AZX_REG_ ## reg, value) +#define soc_hdac_host_stream_writew(dev, reg, value) \ + _soc_hdac_host_stream_write(w, dev, AZX_REG_ ## reg, value) +#define soc_hdac_host_stream_writeb(dev, reg, value) \ + _soc_hdac_host_stream_write(b, dev, AZX_REG_ ## reg, value) +#define soc_hdac_host_stream_readl(dev, reg) \ + _soc_hdac_host_stream_read(l, dev, AZX_REG_ ## reg) +#define soc_hdac_host_stream_readw(dev, reg) \ + _soc_hdac_host_stream_read(w, dev, AZX_REG_ ## reg) +#define soc_hdac_host_stream_readb(dev, reg) \ + _soc_hdac_host_stream_read(b, dev, AZX_REG_ ## reg) + +/* update a register, pass without AZX_REG_ prefix */ +#define soc_hdac_host_stream_updatel(dev, reg, mask, val) \ + soc_hdac_host_stream_writel(dev, reg, \ + (soc_hdac_host_stream_readl(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_host_stream_updatew(dev, reg, mask, val) \ + soc_hdac_host_stream_writew(dev, reg, \ + (soc_hdac_host_stream_readw(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_host_stream_updateb(dev, reg, mask, val) \ + soc_hdac_host_stream_writeb(dev, reg, \ + (soc_hdac_host_stream_readb(dev, reg) & \ + ~(mask)) | (val)) + +void snd_soc_hdac_link_stream_start(struct soc_hdac_stream *hstream); +void snd_soc_hdac_link_stream_clear(struct soc_hdac_stream *hstream); +void snd_soc_hdac_link_stream_reset(struct soc_hdac_stream *hstream); +int snd_soc_hdac_link_stream_setup(struct soc_hdac_stream *stream, int fmt); + +/* + * macros for link stream register read/write + */ +#define _soc_hdac_link_stream_write(type, dev, reg, value) \ + ((dev)->hstream.bus->io_ops->reg_write ## type(value, (dev)->pplc_addr + (reg))) +#define _soc_hdac_link_stream_read(type, dev, reg) \ + ((dev)->hstream.bus->io_ops->reg_read ## type((dev)->pplc_addr + (reg))) + +/* read/write a register, pass without AZX_REG_ prefix */ +#define soc_hdac_link_stream_writel(dev, reg, value) \ + _soc_hdac_link_stream_write(l, dev, AZX_REG_ ## reg, value) +#define soc_hdac_link_stream_writew(dev, reg, value) \ + _soc_hdac_link_stream_write(w, dev, AZX_REG_ ## reg, value) +#define soc_hdac_link_stream_writeb(dev, reg, value) \ + _soc_hdac_link_stream_write(b, dev, AZX_REG_ ## reg, value) +#define soc_hdac_link_stream_readl(dev, reg) \ + _soc_hdac_link_stream_read(l, dev, AZX_REG_ ## reg) +#define soc_hdac_link_stream_readw(dev, reg) \ + _soc_hdac_link_stream_read(w, dev, AZX_REG_ ## reg) +#define soc_hdac_link_stream_readb(dev, reg) \ + _soc_hdac_link_stream_read(b, dev, AZX_REG_ ## reg) + +/* update a register, pass without AZX_REG_ prefix */ +#define soc_hdac_link_stream_updatel(dev, reg, mask, val) \ + soc_hdac_link_stream_writel(dev, reg, \ + (soc_hdac_link_stream_readl(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_link_stream_updatew(dev, reg, mask, val) \ + soc_hdac_link_stream_writew(dev, reg, \ + (soc_hdac_link_stream_readw(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_link_stream_updateb(dev, reg, mask, val) \ + soc_hdac_link_stream_writeb(dev, reg, \ + (soc_hdac_link_stream_readb(dev, reg) & \ + ~(mask)) | (val)) +struct soc_hdac_link { + struct hdac_bus *bus; + int index; + void __iomem *ml_addr; /* link output stream reg pointer */ + u32 lcaps; /* link capablities */ + u16 lsdiid; /* link sdi identifier */ + char codec[HDA_MAX_CODECS][32]; /* codecs connectes to the link */ + struct list_head list; +}; + +int snd_soc_hdac_bus_link_power_up(struct soc_hdac_link *link); +int snd_soc_hdac_bus_link_power_down(struct soc_hdac_link *link); +void snd_soc_hdac_link_set_stream_id(struct soc_hdac_link *link, + int stream); +void snd_soc_hdac_link_clear_stream_id(struct soc_hdac_link *link, + int stream); + +/* + * macros for mutilink register read/ write + */ +#define _soc_hdac_link_write(type, dev, reg, value) \ + ((dev)->bus->io_ops->reg_write ## type(value, (dev)->ml_addr + (reg))) +#define _soc_hdac_link_read(type, dev, reg) \ + ((dev)->bus->io_ops->reg_read ## type((dev)->ml_addr + (reg))) + +/* read/write a register, pass without AZX_REG_ prefix */ +#define soc_hdac_link_writel(dev, reg, value) \ + _soc_hdac_link_write(l, dev, AZX_REG_ ## reg, value) +#define soc_hdac_link_writew(dev, reg, value) \ + _soc_hdac_link_write(w, dev, AZX_REG_ ## reg, value) +#define soc_hdac_link_writeb(dev, reg, value) \ + _soc_hdac_link_write(b, dev, AZX_REG_ ## reg, value) +#define soc_hdac_link_readl(dev, reg) \ + _soc_hdac_link_read(l, dev, AZX_REG_ ## reg) +#define soc_hdac_link_readw(dev, reg) \ + _soc_hdac_link_read(w, dev, AZX_REG_ ## reg) +#define soc_hdac_link_readb(dev, reg) \ + _soc_hdac_link_read(b, dev, AZX_REG_ ## reg) + +/* update a register, pass without AZX_REG_ prefix */ +#define soc_hdac_link_updatel(dev, reg, mask, val) \ + soc_hdac_link_writel(dev, reg, \ + (soc_hdac_link_readl(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_link_updatew(dev, reg, mask, val) \ + soc_hdac_link_writew(dev, reg, \ + (soc_hdac_link_readw(dev, reg) & \ + ~(mask)) | (val)) +#define soc_hdac_link_updateb(dev, reg, mask, val) \ + soc_hdac_link_writeb(dev, reg, \ + (soc_hdac_link_readb(dev, reg) & \ + ~(mask)) | (val)) +#endif /* __SOUND_SOC_HDAUDIO_H */ diff --git a/sound/soc/hda/Makefile b/sound/soc/hda/Makefile index 9585ab180a55..ced2c2198dae 100644 --- a/sound/soc/hda/Makefile +++ b/sound/soc/hda/Makefile @@ -1,3 +1,3 @@ -snd-soc-hda-core-objs := soc-hda-codec.o +snd-soc-hda-core-objs := soc-hdac-controller.o soc-hdac-stream.o soc-hda-codec.o
obj-$(CONFIG_SND_SOC_HDA_CORE) += snd-soc-hda-core.o diff --git a/sound/soc/hda/soc-hdac-controller.c b/sound/soc/hda/soc-hdac-controller.c new file mode 100644 index 000000000000..662357cb0c3e --- /dev/null +++ b/sound/soc/hda/soc-hdac-controller.c @@ -0,0 +1,289 @@ +/* + * soc-hdac-controller.c - SoC HD-audio controller functions. + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Jeeja KP jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/hda_register.h> +#include <sound/soc-hdaudio.h> + +/** + * snd_soc_hdac_bus_parse_capabilities - parse capablity structure + * @sbus - HD-audio soc core bus + */ +int snd_soc_hdac_bus_parse_capabilities(struct soc_hdac_bus *sbus) +{ + unsigned int cur_cap; + unsigned int offset; + struct hdac_bus *bus = &sbus->bus; + + offset = snd_hdac_chip_readl(bus, LLCH); + + sbus->ppcap = false; + sbus->mlcap = false; + sbus->spbcap = false; + sbus->gtscap = false; + + /* Lets walk the linked capabilities list */ + do { + cur_cap = _snd_hdac_chip_read(l, bus, offset); + + dev_dbg(bus->dev, + "Capability version: 0x%x", + ((cur_cap & CAP_HDR_VER_MASK) >> CAP_HDR_VER_OFF) + ); + + dev_dbg(bus->dev, + "HDA capability ID: 0x%x", + (cur_cap & CAP_HDR_ID_MASK) >> CAP_HDR_ID_OFF + ); + + switch ((cur_cap & CAP_HDR_ID_MASK) >> CAP_HDR_ID_OFF) { + + case ML_CAP_ID: + dev_dbg(bus->dev, "Found ML capability"); + sbus->mlcap = true; + sbus->mlcap_addr = bus->remap_addr + offset; + break; + + case GTS_CAP_ID: + dev_dbg(bus->dev, "Found GTS capability"); + sbus->gtscap = true; + sbus->gtscap_addr = bus->remap_addr + offset; + break; + + case PP_CAP_ID: + /* PP capability found, + * the Audio DSP is present so let's handle it + */ + dev_dbg(bus->dev, "Found PP capability"); + sbus->ppcap = true; + sbus->mlcap_addr = bus->remap_addr + offset; + /* Register the device in the system */ + break; + + case SPB_CAP_ID: + /* SPIB capability found, handler function */ + dev_dbg(bus->dev, "Found SPB capability"); + /* Setting the offset */ + sbus->spbcap = true; + sbus->spbcap_addr = bus->remap_addr + offset; + break; + + default: + dev_dbg(bus->dev, "Unknown capability\n"); + break; + } + + /* read the offset of next capabiity */ + offset = cur_cap & CAP_HDR_NXT_PTR_MASK; + } while (offset); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_parse_capabilities); + +/* processing pipe helpers */ + +/** + * snd_soc_hdac_bus_ppcap_enable - enable/disable processing pipe capablity + * @sbus - HD-audio soc core bus + * @enable - flag to turn on/off the capablity + */ +void snd_soc_hdac_bus_ppcap_enable(struct soc_hdac_bus *sbus, bool enable) +{ + struct hdac_bus *bus = &sbus->bus; + + if (!sbus->ppcap) { + dev_err(bus->dev, "Address of PP capability is NULL"); + return; + } + + if (enable) + soc_hdac_bus_ppcap_updatew(sbus, PP_PPCTL, 0, PPCTL_GPROCEN); + else + soc_hdac_bus_ppcap_updatew(sbus, PP_PPCTL, PPCTL_GPROCEN, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_ppcap_enable); + +/** + * snd_soc_hdac_bus_ppcap_int_enable - ppcap interrupt enable/disable + * @sbus - HD-audio soc core bus + * @enable - flag to enable/disable interrupt + */ +void snd_soc_hdac_bus_ppcap_int_enable(struct soc_hdac_bus *sbus, bool enable) +{ + struct hdac_bus *bus = &sbus->bus; + + if (!sbus->ppcap) { + dev_err(bus->dev, "Address of PP capability is NULL\n"); + return; + } + + if (enable) + soc_hdac_bus_ppcap_updatew(sbus, PP_PPCTL, 0, PPCTL_PIE); + else + soc_hdac_bus_ppcap_updatew(sbus, PP_PPCTL, PPCTL_PIE, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_ppcap_int_enable); + +/* Multilink helpers */ + +/** + * snd_soc_hdac_bus_get_ml_capablities - get multilink capablity + * @sbus - HD-audio soc core bus + */ +int snd_soc_hdac_bus_get_ml_capablities(struct soc_hdac_bus *sbus) +{ + int idx = 0; + u32 link_count = 0; + struct soc_hdac_link *hlink; + struct hdac_bus *bus = &sbus->bus; + + INIT_LIST_HEAD(&sbus->hlink_list); + + link_count = soc_hdac_bus_mlcap_readb(sbus, ML_MLCD) + 1; + + dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count); + + for (idx = 0; idx < link_count; idx++) { + hlink = devm_kzalloc(bus->dev, sizeof(*hlink), GFP_KERNEL); + if (!hlink) + return -ENOMEM; + hlink->index = idx; + hlink->bus = bus; + hlink->ml_addr = sbus->mlcap_addr + + ML_BASE + + (ML_INTERVAL * + idx); + hlink->lcaps = soc_hdac_link_readw(hlink, ML_LCAP); + hlink->lsdiid = soc_hdac_link_readw(hlink, ML_LSDIID); + + list_add(&hlink->list, &sbus->hlink_list); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_get_ml_capablities); + +/** + * snd_soc_hdac_bus_map_codec_to_link - maps codec to link + * @sbus - HD-audio soc core bus + * @addr - codec address + */ +int snd_soc_hdac_bus_map_codec_to_link(struct soc_hdac_bus *sbus, int addr) +{ + struct soc_hdac_link *hlink; + struct hdac_bus *bus = &sbus->bus; + + list_for_each_entry(hlink, &sbus->hlink_list, list) { + /*check if SDI bit number == Codec address */ + dev_dbg(bus->dev, "lsdid for %d link %x\n", hlink->index, hlink->lsdiid); + if (!(hlink->lsdiid)) + continue; + + if (hlink->lsdiid && (0x1 << addr)) { + snprintf(hlink->codec[addr], + sizeof(hlink->codec[addr]), + "codec#%03x.%d", addr, addr); + break; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_map_codec_to_link); + +/** + * snd_soc_hdac_bus_get_link_index - get link based on codec name + * @sbus - HD-audio soc core bus + * @codec_name - codec name + */ +struct soc_hdac_link *snd_soc_hdac_bus_get_link(struct soc_hdac_bus *sbus, + const char *codec_name) +{ + int i = 0; + struct soc_hdac_link *hlink = NULL; + + list_for_each_entry(hlink, &sbus->hlink_list, list) { + for (i = 0; i < 16 ; i++) { + if (strlen(hlink->codec[i]) == 0) + break; + if (!strncmp(hlink->codec[i], codec_name, + sizeof(codec_name))) + return hlink; + } + } + return hlink; +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_get_link); + +/** + * snd_soc_hdac_bus_link_power_up -power up hda link + * @link - HD-audio soc link + */ +int snd_soc_hdac_bus_link_power_up(struct soc_hdac_link *link) +{ + int timeout; + u32 val; + int mask = (1 << MLCTL_CPA); + + soc_hdac_link_updatel(link, ML_LCTL, 0, MLCTL_SPA); + udelay(3); + timeout = 300; + + do { + val = soc_hdac_link_readl(link, ML_LCTL); + if (((val & mask) >> MLCTL_CPA)) + return 0; + } while (--timeout); + + return -EIO; +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_link_power_up); + +/** + * snd_soc_hdac_bus_link_power_down -power up hda link + * @link - HD-audio soc link + */ +int snd_soc_hdac_bus_link_power_down(struct soc_hdac_link *link) +{ + int timeout; + int mask = (1 << MLCTL_CPA); + u32 val; + + soc_hdac_link_updatel(link, ML_LCTL, MLCTL_SPA, 0); + udelay(3); + timeout = 300; + + do { + val = soc_hdac_link_readl(link, ML_LCTL); + if (!((val & mask) >> MLCTL_CPA)) + return 0; + } while (--timeout); + + return -EIO; +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_link_power_down); + +/* Module information */ +MODULE_AUTHOR("Jeeja KP jeeja.kp@intel.com"); +MODULE_DESCRIPTION("HDA SoC core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/hda/soc-hdac-stream.c b/sound/soc/hda/soc-hdac-stream.c new file mode 100644 index 000000000000..7a2c2242f780 --- /dev/null +++ b/sound/soc/hda/soc-hdac-stream.c @@ -0,0 +1,390 @@ +/* + * soc-hdac-stream.c - SoC HD-audio stream operations. + * + * Copyright (C) 2015 Intel Corp + * Author: Jeeja KP jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/hdaudio.h> +#include <sound/hda_register.h> +#include <sound/soc-hdaudio.h> + +/** + * snd_soc_hdac_stream_init - initialize each stream (aka device) + * @bus: HD-audio soc core bus + * @stream: HD-audio soc core stream object to initialize + * @idx: stream index number + * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE) + * @tag: the tag id to assign + * + * Assign the starting bdl address to each stream (device) and initialize. + */ +void snd_soc_hdac_stream_init(struct soc_hdac_bus *sbus, + struct soc_hdac_stream *stream, + int idx, int direction, int tag) +{ + struct hdac_bus *bus = &sbus->bus; + + if (sbus->ppcap) { + stream->pphc_addr = sbus->ppcap_addr + + PPHC_BASE + + PPHC_INTERVAL * + idx; + + stream->pplc_addr = sbus->ppcap_addr + + PPLC_BASE + + PPLC_MULTI * + sbus->num_streams + + PPLC_INTERVAL * + idx; + } + stream->decoupled = false; + snd_hdac_stream_init(bus, &stream->hstream, idx, direction, tag); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_stream_init); + +/** + * snd_soc_hdac_stream_decouple - decouple the hdac stream + * @bus: HD-audio soc core bus + * @stream: HD-audio soc core stream object to initialize + * @decouple: flag to decouple + */ +void snd_soc_hdac_stream_decouple(struct soc_hdac_bus *sbus, + struct soc_hdac_stream *stream, bool decouple) +{ + struct hdac_stream *hstream = &stream->hstream; + struct hdac_bus *bus = &sbus->bus; + + spin_lock_irq(&bus->reg_lock); + if (decouple) + soc_hdac_bus_ppcap_updatew(sbus, PP_PPCTL, 0, + PPCTL_PROCEN(hstream->index)); + else + soc_hdac_bus_ppcap_updatew(sbus, PP_PPCTL, + PPCTL_PROCEN(hstream->index), 0); + stream->decoupled = decouple; + spin_unlock_irq(&bus->reg_lock); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_stream_decouple); + +/** + * snd_soc_hdac_linkstream_start - start a stream + * @stream: HD-audio soc core stream to start + */ +void snd_soc_hdac_link_stream_start(struct soc_hdac_stream *stream) +{ + soc_hdac_link_stream_updateb(stream, PPLCCTL, + 0, PPLCCTL_RUN); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_link_stream_start); + +/** + * snd_soc_hdac_link_stream_clear - stop a stream DMA + * @stream: HD-audio soc core stream to stop + */ +void snd_soc_hdac_link_stream_clear(struct soc_hdac_stream *stream) +{ + soc_hdac_link_stream_updateb(stream, PPLCCTL, + PPLCCTL_RUN, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_link_stream_clear); + +/** + * snd_soc_hdac_link_stream_reset - reset a stream + * @stream: HD-audio soc core stream to reset + */ +void snd_soc_hdac_link_stream_reset(struct soc_hdac_stream *stream) +{ + unsigned char val; + int timeout; + + snd_soc_hdac_link_stream_clear(stream); + + soc_hdac_link_stream_updateb(stream, PPLCCTL, 0, PPLCCTL_STRST); + udelay(3); + timeout = 300; + do { + val = soc_hdac_link_stream_readb(stream, PPLCCTL) & + PPLCCTL_STRST; + if (val) + break; + } while (--timeout); + val &= ~PPLCCTL_STRST; + soc_hdac_link_stream_writeb(stream, PPLCCTL, val); + udelay(3); + + timeout = 300; + /* waiting for hardware to report that the stream is out of reset */ + do { + val = soc_hdac_link_stream_readb(stream, PPLCCTL) & + PPLCCTL_STRST; + if (!val) + break; + } while (--timeout); + +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_link_stream_reset); + +/** + * snd_soc_hdac_link_stream_setup - set up the SD for streaming + * @stream: HD-audio soc core stream to set up + * @fmt: stream format + */ +int snd_soc_hdac_link_stream_setup(struct soc_hdac_stream *stream, int fmt) +{ + struct hdac_stream *hstream = &stream->hstream; + unsigned int val; + + /* make sure the run bit is zero for SD */ + snd_soc_hdac_link_stream_clear(stream); + /* program the stream_tag */ + val = soc_hdac_link_stream_readl(stream, PPLCCTL); + val = (val & ~PPLCCTL_STRM_MASK) | + (hstream->stream_tag << PPLCCTL_STRM_SHIFT); + soc_hdac_link_stream_writel(stream, PPLCCTL, val); + + /* program the stream format */ + /* this value needs to be the same as the one programmed */ + soc_hdac_link_stream_writew(stream, PPLCFMT, fmt); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_link_stream_setup); + +/** + * snd_soc_hdac_link_set_stream_id - maps stream id to link output + * @link: HD-audio soc link to set up + * @stream: stream id + */ +void snd_soc_hdac_link_set_stream_id(struct soc_hdac_link *link, + int stream) +{ + soc_hdac_link_updatew(link, ML_LOSIDV, (1 << stream), 0); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_link_set_stream_id); + +/** + * snd_soc_hdac_link_clear_stream_id - maps stream id to link output + * @link: HD-audio soc link to set up + * @stream: stream id + */ +void snd_soc_hdac_link_clear_stream_id(struct soc_hdac_link *link, + int stream) +{ + soc_hdac_link_updatew(link, ML_LOSIDV, 0, (1 << stream)); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_link_clear_stream_id); + +static struct soc_hdac_stream * +soc_hdac_link_stream_assign(struct soc_hdac_bus *sbus, + struct snd_pcm_substream *substream) +{ + struct soc_hdac_stream *res = NULL; + struct hdac_stream *stream = NULL; + struct hdac_bus *hbus = &sbus->bus; + + list_for_each_entry(stream, &hbus->stream_list, list) { + struct soc_hdac_stream *hstream = container_of(stream, + struct soc_hdac_stream, + hstream); + if (stream->direction != substream->stream) + continue; + + /* check if decoupled stream and not in use is available */ + if (hstream->decoupled && !hstream->link_locked) { + res = hstream; + break; + } + + if (!hstream->link_locked) { + snd_soc_hdac_stream_decouple(sbus, hstream, true); + res = hstream; + break; + } + } + if (res) { + spin_lock_irq(&hbus->reg_lock); + res->link_locked = 1; + res->link_substream = substream; + spin_unlock_irq(&hbus->reg_lock); + } + return res; +} + +static struct soc_hdac_stream * +soc_hdac_host_stream_assign(struct soc_hdac_bus *sbus, + struct snd_pcm_substream *substream) +{ + struct soc_hdac_stream *res = NULL; + struct hdac_stream *stream = NULL; + struct hdac_bus *hbus = &sbus->bus; + + /* make a non-zero unique key for the substream */ + int key = (substream->pcm->device << 16) | (substream->number << 2) | + (substream->stream + 1); + + list_for_each_entry(stream, &hbus->stream_list, list) { + struct soc_hdac_stream *hstream = container_of(stream, + struct soc_hdac_stream, + hstream); + if (stream->direction != substream->stream) + continue; + + if (stream->assigned_key == key) { + if (!hstream->decoupled) + snd_soc_hdac_stream_decouple(sbus, hstream, true); + res = hstream; + break; + } + } + if (res) { + spin_lock_irq(&hbus->reg_lock); + res->hstream.opened = 1; + res->hstream.running = 0; + res->hstream.assigned_key = key; + res->hstream.substream = substream; + spin_unlock_irq(&hbus->reg_lock); + } + return res; +} + +/** + * snd_soc_hdac_stream_assign - assign a stream for the PCM + * @bus: HD-audio soc core bus + * @substream: PCM substream to assign + * @type: type of stream (coupled, host or link stream) + * + * Based on the type of stream (coupled/host/link) + * if type is coupled , Looks for an unused stream for the given + * PCM substream, assign it and return the stream object. + * if type is host, Looks for an unused decoupled host stream for + * given pcm stream and assign it and return the stream object. + * if type is link, Looks for an unused decoupled link stream for a + * given pcm stream and assing it and return the stream object. + * If no stream is free, returns NULL. The function tries to keep using + * the same stream object when it's used beforehand. when a stream is + * decoupled, it becomes a host stream and link stream. + */ +struct soc_hdac_stream *snd_soc_hdac_stream_assign(struct soc_hdac_bus *sbus, + struct snd_pcm_substream *substream, + int type) +{ + struct soc_hdac_stream *hstream = NULL; + struct hdac_stream *stream = NULL; + struct hdac_bus *hbus = &sbus->bus; + + if (type == HDAC_STREAM_TYPE_COUPLED) { + stream = snd_hdac_stream_assign(hbus, substream); + if (stream) + hstream = container_of(stream, + struct soc_hdac_stream, hstream); + return hstream; + } + + if (!sbus->ppcap) { + dev_err(hbus->dev, "stream type not supported\n"); + return NULL; + } + + if (type == HDAC_STREAM_TYPE_HOST) + return soc_hdac_host_stream_assign(sbus, substream); + else if (type == HDAC_STREAM_TYPE_LINK) + return soc_hdac_link_stream_assign(sbus, substream); + + return NULL; +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_stream_assign); + +/** + * snd_soc_hdac_stream_release - release the assigned stream + * @stream: HD-audio soc core stream to release + * @type: type of stream (coupled, host or link stream) + * + * Release the stream that has been assigned by snd_soc_hdac_stream_assign(). + */ +void snd_soc_hdac_stream_release(struct soc_hdac_stream *stream, int type) +{ + struct hdac_bus *bus = stream->hstream.bus; + struct soc_hdac_bus *sbus = bus_to_soc_hdac_bus(bus); + + if (type == HDAC_STREAM_TYPE_COUPLED) + snd_hdac_stream_release(&stream->hstream); + else if (type == HDAC_STREAM_TYPE_HOST) { + if (stream->decoupled && !stream->link_locked) { + snd_soc_hdac_stream_decouple(sbus, stream, false); + snd_hdac_stream_release(&stream->hstream); + } + } else if (type == HDAC_STREAM_TYPE_LINK) { + if (stream->decoupled) + snd_soc_hdac_stream_decouple(sbus, stream, false); + spin_lock_irq(&bus->reg_lock); + stream->link_locked = 0; + stream->link_substream = NULL; + spin_unlock_irq(&bus->reg_lock); + } +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_stream_release); + +/** + * snd_soc_hdac_stream_spbcap_enable - enable SPIB for a stream + * @sbus - HD-audio soc core bus + * @index: stream index for which SPIB need to be enabled + */ +void snd_soc_hdac_stream_spbcap_enable(struct soc_hdac_bus *sbus, + bool enable, int index) +{ + u32 mask = 0; + u32 register_mask = 0; + struct hdac_bus *bus = &sbus->bus; + + if (!sbus->spbcap) { + dev_err(bus->dev, "Address of SPB capability is NULL"); + return; + } + + mask |= (1 << index); + + register_mask = soc_hdac_bus_spbcap_readl(sbus, SPB_SPBFCCTL); + + mask |= register_mask; + + if (enable) + soc_hdac_bus_spbcap_updatel(sbus, SPB_SPBFCCTL, 0, mask); + else + soc_hdac_bus_spbcap_updatel(sbus, SPB_SPBFCCTL, mask, 0); +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_stream_spbcap_enable); + +/** + * snd_soc_hdac_stop_all_stream - stop all stream if running + * @sbus - HD-audio soc core bus + */ +void snd_soc_hdac_stop_streams(struct soc_hdac_bus *sbus) +{ + struct hdac_bus *bus = hdac_bus(sbus); + struct hdac_stream *stream; + + if (bus->chip_init) { + list_for_each_entry(stream, &bus->stream_list, list) + snd_hdac_stream_stop(stream); + snd_hdac_bus_stop_chip(bus); + } +} +EXPORT_SYMBOL_GPL(snd_soc_hdac_stop_streams);
At Wed, 29 Apr 2015 01:24:26 +0530, Vinod Koul wrote:
+struct soc_hdac_stream {
- struct hdac_stream hstream;
- unsigned int decoupled:1;
- void __iomem *pphc_addr; /* processing pipe host stream reg pointer */
- void __iomem *pplc_addr; /* processing pipe link stream reg pointer */
- bool link_locked:1;
- struct snd_pcm_substream *link_substream;
- bool link_prepared;
The bit fields should be gathered into the same place so that the struct can be packed better. Also, use bool consistently for bit fields, too.
+/**
- snd_soc_hdac_bus_parse_capabilities - parse capablity structure
- @sbus - HD-audio soc core bus
- */
+int snd_soc_hdac_bus_parse_capabilities(struct soc_hdac_bus *sbus) +{
- unsigned int cur_cap;
- unsigned int offset;
- struct hdac_bus *bus = &sbus->bus;
- offset = snd_hdac_chip_readl(bus, LLCH);
- sbus->ppcap = false;
- sbus->mlcap = false;
- sbus->spbcap = false;
- sbus->gtscap = false;
- /* Lets walk the linked capabilities list */
- do {
I'd check the validity of the offset value, at least, to a negative value. When a chip or bus is screwed up, it would return -1.
+/**
- snd_soc_hdac_bus_get_ml_capablities - get multilink capablity
- @sbus - HD-audio soc core bus
- */
+int snd_soc_hdac_bus_get_ml_capablities(struct soc_hdac_bus *sbus) +{
- int idx = 0;
Superfluous initialization.
- u32 link_count = 0;
Ditto.
- struct soc_hdac_link *hlink;
- struct hdac_bus *bus = &sbus->bus;
- INIT_LIST_HEAD(&sbus->hlink_list);
This should be done better in the initializer of soc_hdac_bus object.
- link_count = soc_hdac_bus_mlcap_readb(sbus, ML_MLCD) + 1;
- dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count);
- for (idx = 0; idx < link_count; idx++) {
hlink = devm_kzalloc(bus->dev, sizeof(*hlink), GFP_KERNEL);
if (!hlink)
return -ENOMEM;
hlink->index = idx;
hlink->bus = bus;
hlink->ml_addr = sbus->mlcap_addr +
ML_BASE +
(ML_INTERVAL *
idx);
hlink->lcaps = soc_hdac_link_readw(hlink, ML_LCAP);
hlink->lsdiid = soc_hdac_link_readw(hlink, ML_LSDIID);
list_add(&hlink->list, &sbus->hlink_list);
list_add_tail() is used more often. (Does the order matter?)
+/**
- snd_soc_hdac_bus_map_codec_to_link - maps codec to link
- @sbus - HD-audio soc core bus
- @addr - codec address
- */
+int snd_soc_hdac_bus_map_codec_to_link(struct soc_hdac_bus *sbus, int addr) +{
- struct soc_hdac_link *hlink;
- struct hdac_bus *bus = &sbus->bus;
- list_for_each_entry(hlink, &sbus->hlink_list, list) {
/*check if SDI bit number == Codec address */
dev_dbg(bus->dev, "lsdid for %d link %x\n", hlink->index, hlink->lsdiid);
if (!(hlink->lsdiid))
Superfluous parentheses.
continue;
if (hlink->lsdiid && (0x1 << addr)) {
snprintf(hlink->codec[addr],
sizeof(hlink->codec[addr]),
"codec#%03x.%d", addr, addr);
Does repeating the address twice make sense?
break;
}
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_map_codec_to_link);
+/**
- snd_soc_hdac_bus_get_link_index - get link based on codec name
- @sbus - HD-audio soc core bus
- @codec_name - codec name
- */
+struct soc_hdac_link *snd_soc_hdac_bus_get_link(struct soc_hdac_bus *sbus,
const char *codec_name)
+{
- int i = 0;
- struct soc_hdac_link *hlink = NULL;
- list_for_each_entry(hlink, &sbus->hlink_list, list) {
for (i = 0; i < 16 ; i++) {
Where does 16 comes from? Not HDA_MAX_CODECS?
if (strlen(hlink->codec[i]) == 0)
break;
It can be simplified like if (!hlink->codec[i][0])
if (!strncmp(hlink->codec[i], codec_name,
sizeof(codec_name)))
This looks buggy. sizeof(codec_name) == sizeof(const char *) == 4 or 8.
return hlink;
}
- }
- return hlink;
This also looks buggy. When the loop is out, hlink isn't NULL.
+} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_get_link);
+/**
- snd_soc_hdac_bus_link_power_up -power up hda link
- @link - HD-audio soc link
- */
+int snd_soc_hdac_bus_link_power_up(struct soc_hdac_link *link) +{
- int timeout;
- u32 val;
- int mask = (1 << MLCTL_CPA);
- soc_hdac_link_updatel(link, ML_LCTL, 0, MLCTL_SPA);
- udelay(3);
- timeout = 300;
- do {
val = soc_hdac_link_readl(link, ML_LCTL);
if (((val & mask) >> MLCTL_CPA))
return 0;
- } while (--timeout);
How 300 reads timeout calculated? There is no delay in the loop, so it's quite short.
+/* Module information */ +MODULE_AUTHOR("Jeeja KP jeeja.kp@intel.com"); +MODULE_DESCRIPTION("HDA SoC core"); +MODULE_LICENSE("GPL v2");
There is already module information in soc-hda-codec.c.
Takashi
On Wed, Apr 29, 2015 at 02:26:40PM +0200, Takashi Iwai wrote:
At Wed, 29 Apr 2015 01:24:26 +0530, Vinod Koul wrote:
+struct soc_hdac_stream {
- struct hdac_stream hstream;
- unsigned int decoupled:1;
- void __iomem *pphc_addr; /* processing pipe host stream reg pointer */
- void __iomem *pplc_addr; /* processing pipe link stream reg pointer */
- bool link_locked:1;
- struct snd_pcm_substream *link_substream;
- bool link_prepared;
The bit fields should be gathered into the same place so that the struct can be packed better. Also, use bool consistently for bit fields, too.
Sure, btw should we use bitfields or bool alone. I dont see much reason to use bitfields here?
+/**
- snd_soc_hdac_bus_parse_capabilities - parse capablity structure
- @sbus - HD-audio soc core bus
- */
+int snd_soc_hdac_bus_parse_capabilities(struct soc_hdac_bus *sbus) +{
- unsigned int cur_cap;
- unsigned int offset;
- struct hdac_bus *bus = &sbus->bus;
- offset = snd_hdac_chip_readl(bus, LLCH);
- sbus->ppcap = false;
- sbus->mlcap = false;
- sbus->spbcap = false;
- sbus->gtscap = false;
- /* Lets walk the linked capabilities list */
- do {
I'd check the validity of the offset value, at least, to a negative value. When a chip or bus is screwed up, it would return -1.
Ok
+/**
- snd_soc_hdac_bus_get_ml_capablities - get multilink capablity
- @sbus - HD-audio soc core bus
- */
+int snd_soc_hdac_bus_get_ml_capablities(struct soc_hdac_bus *sbus) +{
- int idx = 0;
Superfluous initialization.
- u32 link_count = 0;
Ditto.
will remove
- struct soc_hdac_link *hlink;
- struct hdac_bus *bus = &sbus->bus;
- INIT_LIST_HEAD(&sbus->hlink_list);
This should be done better in the initializer of soc_hdac_bus object.
Good catch
- link_count = soc_hdac_bus_mlcap_readb(sbus, ML_MLCD) + 1;
- dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count);
- for (idx = 0; idx < link_count; idx++) {
hlink = devm_kzalloc(bus->dev, sizeof(*hlink), GFP_KERNEL);
if (!hlink)
return -ENOMEM;
hlink->index = idx;
hlink->bus = bus;
hlink->ml_addr = sbus->mlcap_addr +
ML_BASE +
(ML_INTERVAL *
idx);
hlink->lcaps = soc_hdac_link_readw(hlink, ML_LCAP);
hlink->lsdiid = soc_hdac_link_readw(hlink, ML_LSDIID);
list_add(&hlink->list, &sbus->hlink_list);
list_add_tail() is used more often. (Does the order matter?)
I dont think so... but i agree would make sense to order it
+/**
- snd_soc_hdac_bus_map_codec_to_link - maps codec to link
- @sbus - HD-audio soc core bus
- @addr - codec address
- */
+int snd_soc_hdac_bus_map_codec_to_link(struct soc_hdac_bus *sbus, int addr) +{
- struct soc_hdac_link *hlink;
- struct hdac_bus *bus = &sbus->bus;
- list_for_each_entry(hlink, &sbus->hlink_list, list) {
/*check if SDI bit number == Codec address */
dev_dbg(bus->dev, "lsdid for %d link %x\n", hlink->index, hlink->lsdiid);
if (!(hlink->lsdiid))
Superfluous parentheses.
will fix
continue;
if (hlink->lsdiid && (0x1 << addr)) {
snprintf(hlink->codec[addr],
sizeof(hlink->codec[addr]),
"codec#%03x.%d", addr, addr);
Does repeating the address twice make sense?
It doesnt :)
break;
}
- }
- return 0;
+} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_map_codec_to_link);
+/**
- snd_soc_hdac_bus_get_link_index - get link based on codec name
- @sbus - HD-audio soc core bus
- @codec_name - codec name
- */
+struct soc_hdac_link *snd_soc_hdac_bus_get_link(struct soc_hdac_bus *sbus,
const char *codec_name)
+{
- int i = 0;
- struct soc_hdac_link *hlink = NULL;
- list_for_each_entry(hlink, &sbus->hlink_list, list) {
for (i = 0; i < 16 ; i++) {
Where does 16 comes from? Not HDA_MAX_CODECS?
HDA Spec :) but i think we should use HDA_MAX_CODECS rather
if (strlen(hlink->codec[i]) == 0)
break;
It can be simplified like if (!hlink->codec[i][0])
yes
if (!strncmp(hlink->codec[i], codec_name,
sizeof(codec_name)))
This looks buggy. sizeof(codec_name) == sizeof(const char *) == 4 or 8.
yes it should be strlen() instead
return hlink;
}
- }
- return hlink;
This also looks buggy. When the loop is out, hlink isn't NULL.
yes we should make it return NULL
+} +EXPORT_SYMBOL_GPL(snd_soc_hdac_bus_get_link);
+/**
- snd_soc_hdac_bus_link_power_up -power up hda link
- @link - HD-audio soc link
- */
+int snd_soc_hdac_bus_link_power_up(struct soc_hdac_link *link) +{
- int timeout;
- u32 val;
- int mask = (1 << MLCTL_CPA);
- soc_hdac_link_updatel(link, ML_LCTL, 0, MLCTL_SPA);
- udelay(3);
- timeout = 300;
- do {
val = soc_hdac_link_readl(link, ML_LCTL);
if (((val & mask) >> MLCTL_CPA))
return 0;
- } while (--timeout);
How 300 reads timeout calculated? There is no delay in the loop, so it's quite short.
I think we should to cpu_relax or add a delay here
+/* Module information */ +MODULE_AUTHOR("Jeeja KP jeeja.kp@intel.com"); +MODULE_DESCRIPTION("HDA SoC core"); +MODULE_LICENSE("GPL v2");
There is already module information in soc-hda-codec.c.
yes will eliminate the duplicate
At Thu, 30 Apr 2015 15:05:04 +0530, Vinod Koul wrote:
On Wed, Apr 29, 2015 at 02:26:40PM +0200, Takashi Iwai wrote:
At Wed, 29 Apr 2015 01:24:26 +0530, Vinod Koul wrote:
+struct soc_hdac_stream {
- struct hdac_stream hstream;
- unsigned int decoupled:1;
- void __iomem *pphc_addr; /* processing pipe host stream reg pointer */
- void __iomem *pplc_addr; /* processing pipe link stream reg pointer */
- bool link_locked:1;
- struct snd_pcm_substream *link_substream;
- bool link_prepared;
The bit fields should be gathered into the same place so that the struct can be packed better. Also, use bool consistently for bit fields, too.
Sure, btw should we use bitfields or bool alone. I dont see much reason to use bitfields here?
You can use a bit field for boolean instead of unsigned int, e.g. bool foo:1;
continue;
if (hlink->lsdiid && (0x1 << addr)) {
snprintf(hlink->codec[addr],
sizeof(hlink->codec[addr]),
"codec#%03x.%d", addr, addr);
Does repeating the address twice make sense?
It doesnt :)
Looking at the codec side, it might be that the ASoC core appends the dot automatically? If so, we may accept it, of course.
Takashi
From: Jeeja KP jeeja.kp@intel.com
This patch starts to add the Skylake HDA platform driver by defining SoC cpu dais, DMA driver ops and implements ALSA operations
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/skylake/hda-skl-pcm.c | 584 +++++++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 sound/soc/intel/skylake/hda-skl-pcm.c
diff --git a/sound/soc/intel/skylake/hda-skl-pcm.c b/sound/soc/intel/skylake/hda-skl-pcm.c new file mode 100644 index 000000000000..e4d1650b49f5 --- /dev/null +++ b/sound/soc/intel/skylake/hda-skl-pcm.c @@ -0,0 +1,584 @@ +/* + * hda-skl-pcm.c -ASoC HDA Platform driver file implementing PCM functionality + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Jeeja KP jeeja.kp@intel.com + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "hda-skl.h" + +#define HDA_MONO 1 +#define HDA_STEREO 2 + +static struct snd_pcm_hardware azx_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + /* No full-resume yet implemented */ + /* SNDRV_PCM_INFO_RESUME |*/ + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_HAS_WALL_CLOCK | /* legacy */ + SNDRV_PCM_INFO_HAS_LINK_ATIME | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = AZX_MAX_BUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = AZX_MAX_BUF_SIZE / 2, + .periods_min = 2, + .periods_max = AZX_MAX_FRAG, + .fifo_size = 0, +}; + +static inline +struct soc_hdac_stream *get_hdac_stream(struct snd_pcm_substream *substream) +{ + return substream->runtime->private_data; +} + +static struct soc_hdac_bus *get_bus_ctx(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct device *dev = rtd->cpu_dai->dev; + struct soc_hdac_bus *sbus = dev_get_drvdata(dev); + + return sbus; +} + +static int substream_alloc_pages(struct soc_hdac_bus *sbus, + struct snd_pcm_substream *substream, + size_t size) +{ + struct soc_hdac_stream *stream = get_hdac_stream(substream); + int ret; + + hdac_stream(stream)->bufsize = 0; + hdac_stream(stream)->period_bytes = 0; + hdac_stream(stream)->format_val = 0; + ret = snd_pcm_lib_malloc_pages(substream, size); + if (ret < 0) + return ret; + return 0; +} + +static int substream_free_pages(struct hdac_bus *bus, + struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static void soc_hda_set_pcm_constrains(struct soc_hdac_bus *sbus, + struct snd_pcm_runtime *runtime) +{ + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + + /* avoid wrap-around with wall-clock */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME, + 20, 178000000); +} + +static int soc_hda_pcm_open(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); + struct soc_hdac_stream *stream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct soc_hda_dma_params *dma_params; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + pm_runtime_get_sync(dai->dev); + + stream = snd_soc_hdac_stream_assign(sbus, substream, + HDAC_STREAM_TYPE_COUPLED); + if (stream == NULL) + return -EBUSY; + + soc_hda_set_pcm_constrains(sbus, runtime); + + /* + * disable WALLCLOCK timestamps for capture streams + * until we figure out how to handle digital inputs + */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_WALL_CLOCK; /* legacy */ + runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_LINK_ATIME; + } + + runtime->private_data = stream; + + dma_params = kzalloc(sizeof(*dma_params), GFP_KERNEL); + if (!dma_params) + return -ENOMEM; + + dma_params->stream_tag = hdac_stream(stream)->stream_tag; + snd_soc_dai_set_dma_data(dai, substream, (void *)dma_params); + + dev_dbg(dai->dev, "stream tag set in dma params=%d\n", + dma_params->stream_tag); + snd_pcm_set_sync(substream); + + return 0; +} + +static int hda_get_format(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct soc_hda_dma_params *dma_params; + int format_val = 0; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + dma_params = (struct soc_hda_dma_params *) + snd_soc_dai_get_dma_data(codec_dai, substream); + + if (!dma_params) + format_val = dma_params->format; + + return format_val; +} + +static int soc_hda_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct soc_hdac_stream *stream = get_hdac_stream(substream); + unsigned int format_val; + int err; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + if (hdac_stream(stream)->prepared) { + dev_dbg(dai->dev, "already stream is prepared - returning\n"); + return 0; + } + + format_val = hda_get_format(substream, dai); + dev_dbg(dai->dev, "stream_tag=%d formatvalue=%d\n", + hdac_stream(stream)->stream_tag, format_val); + snd_hdac_stream_reset(hdac_stream(stream)); + + err = snd_hdac_stream_set_params(hdac_stream(stream), format_val); + + if (err < 0) + return err; + + snd_hdac_stream_setup(hdac_stream(stream)); + hdac_stream(stream)->prepared = 1; + return err; +} + +static int soc_hda_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + ret = substream_alloc_pages(sbus, substream, + params_buffer_bytes(params)); + + if (ret < 0) + return ret; + + if (substream->runtime->dma_area) + memset(substream->runtime->dma_area, 0, + params_buffer_bytes(params)); + + dev_dbg(dai->dev, "format_val, rate=%d, ch=%d, format=%d\n", + runtime->rate, runtime->channels, runtime->format); + + return ret; +} + +static void soc_hda_pcm_close(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct soc_hdac_stream *stream = get_hdac_stream(substream); + struct soc_hda_dma_params *dma_params = NULL; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + snd_soc_hdac_stream_release(stream, HDAC_STREAM_TYPE_COUPLED); + + dma_params = (struct soc_hda_dma_params *) + snd_soc_dai_get_dma_data(dai, substream); + pm_runtime_mark_last_busy(dai->dev); + pm_runtime_put_autosuspend(dai->dev); + kfree(dma_params); +} + +static int soc_hda_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); + struct soc_hdac_stream *stream = get_hdac_stream(substream); + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + snd_hdac_stream_cleanup(hdac_stream(stream)); + hdac_stream(stream)->prepared = 0; + + return substream_free_pages(hdac_bus(sbus), substream); +} + +static struct snd_soc_dai_ops hda_pcm_dai_ops = { + .startup = soc_hda_pcm_open, + .shutdown = soc_hda_pcm_close, + .prepare = soc_hda_pcm_prepare, + .hw_params = soc_hda_pcm_hw_params, + .hw_free = soc_hda_pcm_hw_free, +}; + +static struct snd_soc_dai_driver soc_hda_platform_dai[] = { +{ + .name = "System Pin", + .ops = &hda_pcm_dai_ops, + .playback = { + .stream_name = "System Playback", + .channels_min = HDA_MONO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "System Capture", + .channels_min = HDA_MONO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Deepbuffer Pin", + .ops = &hda_pcm_dai_ops, + .playback = { + .stream_name = "Deepbuffer Playback", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "LowLatency Pin", + .ops = &hda_pcm_dai_ops, + .playback = { + .stream_name = "Low Latency Playback", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static int soc_hda_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *dai_link = rtd->dai_link; + + dev_dbg(rtd->cpu_dai->dev, "In %s:%s\n", __func__, + dai_link->cpu_dai_name); + + runtime = substream->runtime; + snd_soc_set_runtime_hwparams(substream, &azx_pcm_hw); + return 0; +} + +static int soc_hda_platform_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct soc_hdac_bus *sbus = get_bus_ctx(substream); + struct hdac_bus *bus = hdac_bus(sbus); + struct soc_hdac_stream *stream; + struct snd_pcm_substream *s; + bool start; + int sbits = 0; + unsigned long cookie; + struct hdac_stream *hstr; + + stream = get_hdac_stream(substream); + hstr = hdac_stream(stream); + + dev_dbg(bus->dev, "In %s cmd=%d\n", __func__, cmd); + + if (!hstr->prepared) + return -EPIPE; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + start = true; + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + start = false; + break; + + default: + return -EINVAL; + } + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + stream = get_hdac_stream(s); + sbits |= 1 << hdac_stream(stream)->index; + snd_pcm_trigger_done(s, substream); + } + + spin_lock_irqsave(&bus->reg_lock, cookie); + + /* first, set SYNC bits of corresponding streams */ + snd_hdac_stream_sync_trigger(hstr, true, sbits, AZX_REG_SSYNC); + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + stream = get_hdac_stream(s); + if (start) + snd_hdac_stream_start(hdac_stream(stream), true); + else + snd_hdac_stream_stop(hdac_stream(stream)); + } + spin_unlock_irqrestore(&bus->reg_lock, cookie); + + snd_hdac_stream_sync(hstr, start, sbits); + + spin_lock_irqsave(&bus->reg_lock, cookie); + + /* reset SYNC bits */ + snd_hdac_stream_sync_trigger(hstr, false, sbits, AZX_REG_SSYNC); + if (start) + snd_hdac_stream_timecounter_init(hstr, sbits); + spin_unlock_irqrestore(&bus->reg_lock, cookie); + return 0; +} + +/* calculate runtime delay from LPIB */ +static int hda_get_delay_from_lpib(struct soc_hdac_bus *sbus, + struct soc_hdac_stream *sstream, + unsigned int pos) +{ + struct hdac_bus *bus = hdac_bus(sbus); + struct hdac_stream *hstream = hdac_stream(sstream); + struct snd_pcm_substream *substream = hstream->substream; + int stream = substream->stream; + unsigned int lpib_pos = snd_hdac_stream_get_pos_lpib(hstream); + int delay; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = pos - lpib_pos; + else + delay = lpib_pos - pos; + if (delay < 0) { + if (delay >= hstream->delay_negative_threshold) + delay = 0; + else + delay += hstream->bufsize; + } + + if (delay >= hstream->period_bytes) { + dev_info(bus->dev, + "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n", + delay, hstream->period_bytes); + delay = 0; + } + + return bytes_to_frames(substream->runtime, delay); +} + +static unsigned int hda_get_position(struct soc_hdac_stream *hstream, + int codec_delay) +{ + struct hdac_stream *hstr = hdac_stream(hstream); + struct snd_pcm_substream *substream = hstr->substream; + struct soc_hdac_bus *sbus = get_bus_ctx(substream); + unsigned int pos; + int delay = 0; + + /* use the position buffer as default */ + pos = snd_hdac_stream_get_pos_posbuf(hdac_stream(hstream)); + + if (pos >= hdac_stream(hstream)->bufsize) + pos = 0; + + if (substream->runtime) { + delay = hda_get_delay_from_lpib(sbus, hstream, pos) + + codec_delay; + substream->runtime->delay += delay; + } + + return pos; +} + +static snd_pcm_uframes_t soc_hda_platform_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct soc_hdac_stream *hstream = get_hdac_stream(substream); + + return bytes_to_frames(substream->runtime, + hda_get_position(hstream, 0)); +} + +static u64 soc_azx_adjust_codec_delay(struct snd_pcm_substream *substream, + u64 nsec) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_dai *codec_dai = rtd->codec_dai; + u64 codec_frames, codec_nsecs; + + if (!codec_dai->driver->ops->delay) + return nsec; + + codec_frames = codec_dai->driver->ops->delay(substream, codec_dai); + codec_nsecs = div_u64(codec_frames * 1000000000LL, + substream->runtime->rate); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return nsec + codec_nsecs; + + return (nsec > codec_nsecs) ? nsec - codec_nsecs : 0; +} + +static int soc_hda_get_time_info(struct snd_pcm_substream *substream, + struct timespec *system_ts, struct timespec *audio_ts, + struct snd_pcm_audio_tstamp_config *audio_tstamp_config, + struct snd_pcm_audio_tstamp_report *audio_tstamp_report) +{ + struct soc_hdac_stream *sstream = get_hdac_stream(substream); + struct hdac_stream *hstr = hdac_stream(sstream); + u64 nsec; + + if ((substream->runtime->hw.info & SNDRV_PCM_INFO_HAS_LINK_ATIME) && + (audio_tstamp_config->type_requested == SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK)) { + + snd_pcm_gettime(substream->runtime, system_ts); + + nsec = timecounter_read(&hstr->tc); + nsec = div_u64(nsec, 3); /* can be optimized */ + if (audio_tstamp_config->report_delay) + nsec = soc_azx_adjust_codec_delay(substream, nsec); + + *audio_ts = ns_to_timespec(nsec); + + audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK; + audio_tstamp_report->accuracy_report = 1; /* rest of struct is valid */ + audio_tstamp_report->accuracy = 42; /* 24MHzWallClk == 42ns resolution */ + + } else + audio_tstamp_report->actual_type = SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT; + + return 0; +} + +static struct snd_pcm_ops soc_hda_platform_ops = { + .open = soc_hda_platform_open, + .ioctl = snd_pcm_lib_ioctl, + .trigger = soc_hda_platform_pcm_trigger, + .pointer = soc_hda_platform_pcm_pointer, + .get_time_info = soc_hda_get_time_info, + .mmap = snd_pcm_lib_default_mmap, + .page = snd_pcm_sgbuf_ops_page, +}; + +static void soc_hda_pcm_free(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +#define MAX_PREALLOC_SIZE (32 * 1024 * 1024) + +static int soc_hda_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *dai = rtd->cpu_dai; + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); + struct snd_pcm *pcm = rtd->pcm; + unsigned int size; + int retval = 0; + struct hda_skl *hda = to_hda_skl(sbus); + + dev_dbg(dai->dev, "In %s\n", __func__); + if (dai->driver->playback.channels_min || + dai->driver->capture.channels_min) { + /* buffer pre-allocation */ + size = CONFIG_SND_SOC_HDA_PREALLOC_SIZE * 1024; + if (size > MAX_PREALLOC_SIZE) + size = MAX_PREALLOC_SIZE; + retval = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(hda->pci), + size, MAX_PREALLOC_SIZE); + if (retval) { + dev_err(dai->dev, "dma buffer allocationf fail\n"); + return retval; + } + } + return retval; +} + +static struct snd_soc_platform_driver soc_hda_platform_drv = { + .ops = &soc_hda_platform_ops, + .pcm_new = soc_hda_pcm_new, + .pcm_free = soc_hda_pcm_free, +}; + +static const struct snd_soc_component_driver soc_hda_component = { + .name = "pcm", +}; + +int soc_hda_platform_register(struct device *dev) +{ + int ret = 0; + + ret = snd_soc_register_platform(dev, &soc_hda_platform_drv); + if (ret) { + dev_err(dev, "soc platform registration failed %d\n", ret); + return ret; + } + ret = snd_soc_register_component(dev, &soc_hda_component, + soc_hda_platform_dai, + ARRAY_SIZE(soc_hda_platform_dai)); + if (ret) { + dev_err(dev, "soc component registration failed %d\n", ret); + snd_soc_unregister_platform(dev); + } + + return ret; + +} + +int soc_hda_platform_unregister(struct device *dev) +{ + snd_soc_unregister_component(dev); + snd_soc_unregister_platform(dev); + return 0; +}
At Wed, 29 Apr 2015 01:24:27 +0530, Vinod Koul wrote:
+static struct snd_soc_dai_driver soc_hda_platform_dai[] = { +{
- .name = "System Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "System Playback",
.channels_min = HDA_MONO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
.stream_name = "System Capture",
.channels_min = HDA_MONO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
Is it really S24_LE? It's packed in lower 3 bytes of 4 bytes format.
- },
+}, +{
- .name = "Deepbuffer Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "Deepbuffer Playback",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
+}, +{
- .name = "LowLatency Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "Low Latency Playback",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
Are there no S32_LE support at all for all dais?
Takashi
On Wed, Apr 29, 2015 at 02:31:02PM +0200, Takashi Iwai wrote:
At Wed, 29 Apr 2015 01:24:27 +0530, Vinod Koul wrote:
+static struct snd_soc_dai_driver soc_hda_platform_dai[] = { +{
- .name = "System Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "System Playback",
.channels_min = HDA_MONO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
.stream_name = "System Capture",
.channels_min = HDA_MONO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
Is it really S24_LE? It's packed in lower 3 bytes of 4 bytes format.
Yup, we do use S24_LE, thats very common on our DSP based systems
- },
+}, +{
- .name = "Deepbuffer Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "Deepbuffer Playback",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
+}, +{
- .name = "LowLatency Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "Low Latency Playback",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
Are there no S32_LE support at all for all dais?
No, just 16 and 24 bits :)
At Thu, 30 Apr 2015 15:12:21 +0530, Vinod Koul wrote:
On Wed, Apr 29, 2015 at 02:31:02PM +0200, Takashi Iwai wrote:
At Wed, 29 Apr 2015 01:24:27 +0530, Vinod Koul wrote:
+static struct snd_soc_dai_driver soc_hda_platform_dai[] = { +{
- .name = "System Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "System Playback",
.channels_min = HDA_MONO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
.stream_name = "System Capture",
.channels_min = HDA_MONO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
Is it really S24_LE? It's packed in lower 3 bytes of 4 bytes format.
Yup, we do use S24_LE, thats very common on our DSP based systems
OK.
- },
+}, +{
- .name = "Deepbuffer Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "Deepbuffer Playback",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
+}, +{
- .name = "LowLatency Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "Low Latency Playback",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
Are there no S32_LE support at all for all dais?
No, just 16 and 24 bits :)
Hm, so this restriction remains also for decoupled from DSP?
All HDA codecs supports 24bit formats (in S32_LE form), but in your patch, 24bit format is supported only for System Capture.
Takashi
On Thu, Apr 30, 2015 at 11:52:47AM +0200, Takashi Iwai wrote:
- },
+}, +{
- .name = "Deepbuffer Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "Deepbuffer Playback",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
+}, +{
- .name = "LowLatency Pin",
- .ops = &hda_pcm_dai_ops,
- .playback = {
.stream_name = "Low Latency Playback",
.channels_min = HDA_STEREO,
.channels_max = HDA_STEREO,
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
Are there no S32_LE support at all for all dais?
No, just 16 and 24 bits :)
Hm, so this restriction remains also for decoupled from DSP?
Yes it is the DSP which is adding this restriction, we do render at 16 and 24 bits only
All HDA codecs supports 24bit formats (in S32_LE form), but in your patch, 24bit format is supported only for System Capture.
This is platform params, the codec support will be in DAI params for the codec
From: Jeeja KP jeeja.kp@intel.com
This patch follows up by adding the HDA controller operations. This code is mostly derived from Intel HDA PCI driver without legacy bits
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/skylake/hda-skl.c | 721 +++++++++++++++++++++++++++++++++++++ sound/soc/intel/skylake/hda-skl.h | 73 ++++ 2 files changed, 794 insertions(+) create mode 100644 sound/soc/intel/skylake/hda-skl.c create mode 100644 sound/soc/intel/skylake/hda-skl.h
diff --git a/sound/soc/intel/skylake/hda-skl.c b/sound/soc/intel/skylake/hda-skl.c new file mode 100644 index 000000000000..24502f8e796d --- /dev/null +++ b/sound/soc/intel/skylake/hda-skl.c @@ -0,0 +1,721 @@ +/* + * hda-skl.c - Implementation of primary ASoC Intel HD Audio driver + * + * Copyright (C) 2014-2015 Intel Corp + * Author: Jeeja KP jeeja.kp@intel.com + * + * Derived mostly from Intel HDA driver with following copyrights: + * Copyright (c) 2004 Takashi Iwai tiwai@suse.de + * PeiSen Hou pshou@realtek.com.tw + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/mutex.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> + +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/hda_register.h> +#include <sound/hdaudio.h> +#include "hda-skl.h" + +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{Intel, PCH},"); +MODULE_DESCRIPTION("Intel aDSP HDA driver"); + +/* + * initialize the PCI registers + */ +/* update bits in a PCI register byte */ +static void update_pci_byte(struct pci_dev *pci, unsigned int reg, + unsigned char mask, unsigned char val) +{ + unsigned char data; + + pci_read_config_byte(pci, reg, &data); + data &= ~mask; + data |= (val & mask); + pci_write_config_byte(pci, reg, data); +} + +static void azx_init_pci(struct hda_skl *hda) +{ + struct soc_hdac_bus *sbus = &hda->sbus; + + /* + * Clear bits 0-2 of PCI register TCSEL (at offset 0x44) + * TCSEL == Traffic Class Select Register, which sets PCI express QOS + * Ensuring these bits are 0 clears playback static on some HD Audio + * codecs. + * The PCI register TCSEL is defined in the Intel manuals. + */ + dev_dbg(hdac_bus(sbus)->dev, "Clearing TCSEL\n"); + update_pci_byte(hda->pci, AZX_PCIREG_TCSEL, 0x07, 0); +} + +/* + * Check whether the current DMA position is acceptable for updating + * periods. Returns non-zero if it's OK. + */ +static int azx_position_ok(struct hdac_bus *bus, struct hdac_stream *stream) +{ + u32 wallclk; + unsigned int pos = 0; + + wallclk = snd_hdac_chip_readl(bus, WALLCLK) - stream->start_wallclk; + if (wallclk < (stream->period_wallclk * 2) / 3) + return -1; /* bogus (too early) interrupt */ + + if (bus->use_posbuf) { + /* use the position buffer as default */ + pos = snd_hdac_stream_get_pos_posbuf(stream); + if (!pos || pos == (u32)-1) { + dev_info(bus->dev, + "Invalid pos buffer, using LPIB read method instead.\n"); + pos = snd_hdac_stream_get_pos_lpib(stream); + } + } + + if (pos >= stream->bufsize) + pos = 0; + + if (WARN_ONCE(!stream->period_bytes, + "hda-skl: zero stream->period_bytes")) + return -1; /* this shouldn't happen! */ + + if (wallclk < (stream->period_wallclk * 5) / 4 && + pos % stream->period_bytes > stream->period_bytes / 2) + /* NG - it's below the first next period boundary */ + return bus->bdl_pos_adj ? 0 : -1; + + stream->start_wallclk += wallclk; + + return 1; /* OK, it's fine */ +} + +/* called from IRQ */ +static void stream_update(struct hdac_bus *bus, struct hdac_stream *hstr) +{ + if (azx_position_ok(bus, hstr) > 0) + snd_pcm_period_elapsed(hstr->substream); +} + +static irqreturn_t azx_interrupt(int irq, void *dev_id) +{ + struct soc_hdac_bus *sbus = dev_id; + struct hdac_bus *bus = hdac_bus(sbus); + u32 status; + +#ifdef CONFIG_PM + if (!pm_runtime_active(bus->dev)) + return IRQ_NONE; +#endif + + spin_lock(&bus->reg_lock); + + status = snd_hdac_chip_readl(bus, INTSTS); + if (status == 0 || status == 0xffffffff) { + spin_unlock(&bus->reg_lock); + return IRQ_NONE; + } + + /* clear rirb int */ + status = snd_hdac_chip_readb(bus, RIRBSTS); + if (status & RIRB_INT_MASK) { + if (status & RIRB_INT_RESPONSE) + snd_hdac_bus_update_rirb(bus); + snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK); + return IRQ_HANDLED; + } + + spin_unlock(&bus->reg_lock); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t azx_threaded_handler(int irq, void *dev_id) +{ + struct soc_hdac_bus *sbus = dev_id; + struct hdac_bus *bus = hdac_bus(sbus); + u32 status; + unsigned long cookie; + + status = snd_hdac_chip_readl(bus, INTSTS); + spin_lock_irqsave(&bus->reg_lock, cookie); + + snd_hdac_bus_handle_stream_irq(bus, status, stream_update); + + spin_unlock_irqrestore(&bus->reg_lock, cookie); + + return IRQ_HANDLED; +} + +/* initialize SD streams, use seprate streeam tag for PB and CP */ +static int azx_init_stream(struct soc_hdac_bus *sbus, int num_stream, int dir) +{ + int i, tag; + int stream_tag = 0; + + for (i = 0; i < num_stream; i++) { + struct soc_hdac_stream *stream = + kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + tag = ++stream_tag; + snd_soc_hdac_stream_init(sbus, stream, i, dir, tag); + } + return 0; +} + +static void azx_free_streams(struct soc_hdac_bus *sbus) +{ + struct hdac_stream *s; + struct soc_hdac_stream *stream; + struct hdac_bus *bus = hdac_bus(sbus); + + while (!list_empty(&bus->stream_list)) { + s = list_first_entry(&bus->stream_list, struct hdac_stream, list); + stream = stream_to_soc_hdac_stream(s); + list_del(&s->list); + kfree(stream); + } +} + +static int azx_acquire_irq(struct soc_hdac_bus *sbus, int do_disconnect) +{ + struct hda_skl *hda = to_hda_skl(sbus); + struct hdac_bus *bus = hdac_bus(sbus); + int ret = 0; + + ret = request_threaded_irq(hda->pci->irq, azx_interrupt, + azx_threaded_handler, + hda->msi ? 0 : IRQF_SHARED, + KBUILD_MODNAME, sbus); + if (ret) { + dev_err(bus->dev, + "unable to grab IRQ %d, disabling device\n", + hda->pci->irq); + return ret; + } + + bus->irq = hda->pci->irq; + pci_intx(hda->pci, !hda->msi); + return ret; +} + +#ifdef CONFIG_PM_SLEEP +/* + * power management + */ +static int azx_suspend(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct soc_hdac_bus *sbus = pci_get_drvdata(pci); + struct hdac_bus *bus = hdac_bus(sbus); + struct hda_skl *hda = to_hda_skl(sbus); + + snd_hdac_bus_stop_chip(bus); + snd_hdac_bus_enter_link_reset(bus); + if (bus->irq >= 0) { + free_irq(bus->irq, bus); + bus->irq = -1; + } + + if (hda->msi) + pci_disable_msi(pci); + pci_disable_device(pci); + pci_save_state(pci); + pci_set_power_state(pci, PCI_D3hot); + return 0; +} + +static int azx_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct soc_hdac_bus *sbus = pci_get_drvdata(pci); + struct hdac_bus *bus = hdac_bus(sbus); + struct hda_skl *hda = to_hda_skl(sbus); + + pci_set_power_state(pci, PCI_D0); + pci_restore_state(pci); + + if (pci_enable_device(pci) < 0) { + dev_err(dev, "hda-intel: pci_enable_device failed, disabling device\n"); + return -EIO; + } + pci_set_master(pci); + if (hda->msi) + if (pci_enable_msi(pci) < 0) + hda->msi = 0; + if (azx_acquire_irq(sbus, 1) < 0) + return -EIO; + azx_init_pci(hda); + + snd_hdac_bus_init_chip(bus, 1); + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int azx_runtime_suspend(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct soc_hdac_bus *sbus = pci_get_drvdata(pci); + struct hdac_bus *bus = hdac_bus(sbus); + + dev_dbg(bus->dev, "in %s\n", __func__); + + /* enable controller wake up event */ + snd_hdac_chip_updatew(bus, WAKEEN, 0, STATESTS_INT_MASK); + + snd_hdac_bus_stop_chip(bus); + snd_hdac_bus_enter_link_reset(bus); + return 0; +} + +static int azx_runtime_resume(struct device *dev) +{ + struct pci_dev *pci = to_pci_dev(dev); + struct soc_hdac_bus *sbus = pci_get_drvdata(pci); + struct hdac_bus *bus = hdac_bus(sbus); + struct hda_skl *hda = to_hda_skl(sbus); + int status; + + dev_dbg(bus->dev, "in %s\n", __func__); + + /* Read STATESTS before controller reset */ + status = snd_hdac_chip_readw(bus, STATESTS); + + azx_init_pci(hda); + snd_hdac_bus_init_chip(bus, true); + /* disable controller Wake Up event */ + snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, 0); + return 0; +} + +static const struct dev_pm_ops azx_pm = { + SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume) + SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, NULL) +}; +#endif /* CONFIG_PM */ + +/* + * destructor + */ +static int azx_free(struct soc_hdac_bus *sbus) +{ + struct hda_skl *hda = to_hda_skl(sbus); + struct hdac_bus *bus = hdac_bus(sbus); + + hda->init_failed = 1; /* to be sure */ + + snd_soc_hdac_stop_streams(sbus); + + if (bus->irq >= 0) + free_irq(bus->irq, (void *)bus); + if (hda->msi) + pci_disable_msi(hda->pci); + if (bus->remap_addr) + iounmap(bus->remap_addr); + + snd_hdac_bus_free_stream_pages(bus); + azx_free_streams(sbus); + pci_release_regions(hda->pci); + pci_disable_device(hda->pci); + + snd_hdac_bus_exit(bus); + return 0; +} + +static int hda_dmic_device_register(struct hda_skl *hda) +{ + struct hdac_bus *bus = hdac_bus(&hda->sbus); + struct platform_device *pdev; + int ret; + + pdev = platform_device_alloc("dmic-codec", -1); + if (!pdev) { + dev_err(bus->dev, "failed to allocate dmic device\n"); + return -1; + } + + ret = platform_device_add(pdev); + if (ret) { + dev_err(bus->dev, "failed to add hda codec device\n"); + platform_device_put(pdev); + return -1; + } + hda->dmic_dev = pdev; + return 0; +} + +static void hda_dmic_device_unregister(struct hda_skl *hda) +{ + + if (hda->dmic_dev) + platform_device_unregister(hda->dmic_dev); +} + +static int azx_add_codec_device(int addr, struct hdac_bus *bus) +{ + struct hdac_device *hdev = NULL; + char name[10]; + int ret; + + hdev = devm_kzalloc(bus->dev, sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + + snprintf(name, sizeof(name), "codec#%03x", addr); + + ret = snd_hdac_device_init(hdev, bus, name, addr); + if (ret < 0) { + dev_err(bus->dev, "device init failed for hdac device\n"); + return ret; + } + hdev->type = HDA_DEV_ASOC; + + ret = snd_hdac_device_register(hdev); + if (ret) { + dev_err(bus->dev, "failed to register hdac device\n"); + snd_hdac_device_exit(hdev); + return ret; + } + return 0; +} + +/* + * Probe the given codec address + */ +static int probe_codec(struct soc_hdac_bus *sbus, int addr) +{ + struct hdac_bus *bus = hdac_bus(sbus); + unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) | + (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; + unsigned int res; + + mutex_lock(&bus->cmd_mutex); + snd_hdac_bus_send_cmd(bus, cmd); + snd_hdac_bus_get_response(bus, addr, &res); + mutex_unlock(&bus->cmd_mutex); + if (res == -1) + return -EIO; + dev_dbg(bus->dev, "codec #%d probed OK\n", addr); + return azx_add_codec_device(addr, bus); +} + +/* Codec initialization */ +static int azx_codec_create(struct soc_hdac_bus *sbus) +{ + struct hdac_bus *bus = hdac_bus(sbus); + int c, max_slots; + + max_slots = HDA_MAX_CODECS; + + /* First try to probe all given codec slots */ + for (c = 0; c < max_slots; c++) { + if ((bus->codec_mask & (1 << c))) { + if (probe_codec(sbus, c) < 0) { + /* Some BIOSen give you wrong codec addresses + * that don't exist + */ + dev_warn(bus->dev, + "Codec #%d probe error; disabling it...\n", c); + bus->codec_mask &= ~(1 << c); + /* More badly, accessing to a non-existing + * codec often screws up the controller bus, + * and disturbs the further communications. + * Thus if an error occurs during probing, + * better to reset the controller bus to + * get back to the sanity state. + */ + snd_hdac_bus_stop_chip(bus); + snd_hdac_bus_init_chip(bus, true); + } + } + } + return 0; +} + +static const struct hdac_bus_ops bus_core_ops = { + .command = snd_hdac_bus_send_cmd, + .get_response = snd_hdac_bus_get_response, +}; + +/* + * constructor + */ +static int azx_create(struct pci_dev *pci, + const struct hdac_io_ops *io_ops, + struct hda_skl **rhda) +{ + struct hda_skl *hda; + struct soc_hdac_bus *sbus; + + int err; + + *rhda = NULL; + + err = pci_enable_device(pci); + if (err < 0) + return err; + + hda = devm_kzalloc(&pci->dev, sizeof(*hda), GFP_KERNEL); + if (!hda) { + pci_disable_device(pci); + return -ENOMEM; + } + sbus = &hda->sbus; + snd_hdac_bus_init(&sbus->bus, &pci->dev, &bus_core_ops, io_ops); + sbus->bus.use_posbuf = 1; + hda->pci = pci; + hda->msi = 1; + + sbus->bus.bdl_pos_adj = 0; + + *rhda = hda; + return 0; +} + +static int azx_first_init(struct soc_hdac_bus *sbus) +{ + struct hda_skl *hda = to_hda_skl(sbus); + struct hdac_bus *bus = hdac_bus(sbus); + struct pci_dev *pci = hda->pci; + int err; + unsigned short gcap; + int capture_streams, playback_streams; + + err = pci_request_regions(pci, "ICH HD audio"); + if (err < 0) + return err; + + bus->addr = pci_resource_start(pci, 0); + bus->remap_addr = pci_ioremap_bar(pci, 0); + if (bus->remap_addr == NULL) { + dev_err(bus->dev, "ioremap error\n"); + return -ENXIO; + } + + if (hda->msi) + if (pci_enable_msi(pci) < 0) + hda->msi = 0; + + if (azx_acquire_irq(sbus, 0) < 0) + return -EBUSY; + + pci_set_master(pci); + synchronize_irq(bus->irq); + + gcap = snd_hdac_chip_readw(bus, GCAP); + dev_dbg(bus->dev, "chipset global capabilities = 0x%x\n", gcap); + + /* allow 64bit DMA address if supported by H/W */ + if (!pci_set_dma_mask(pci, DMA_BIT_MASK(64))) + pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(64)); + else { + pci_set_dma_mask(pci, DMA_BIT_MASK(32)); + pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32)); + } + + /* read number of streams from GCAP register instead of using + * hardcoded value + */ + capture_streams = (gcap >> 8) & 0x0f; + playback_streams = (gcap >> 12) & 0x0f; + if (!playback_streams && !capture_streams) + return -EIO; + sbus->num_streams = capture_streams + playback_streams; + /* initialize streams */ + azx_init_stream(sbus, capture_streams, SNDRV_PCM_STREAM_CAPTURE); + azx_init_stream(sbus, playback_streams, SNDRV_PCM_STREAM_PLAYBACK); + + err = snd_hdac_bus_alloc_stream_pages(bus); + if (err < 0) + return err; + + /* initialize chip */ + azx_init_pci(hda); + + snd_hdac_bus_init_chip(bus, true); + + /* codec detection */ + if (!bus->codec_mask) { + dev_err(bus->dev, "no codecs found!\n"); + return -ENODEV; + } + + return 0; +} + +/* PCI register access. */ +static void pci_azx_writel(u32 value, u32 __iomem *addr) +{ + writel(value, addr); +} + +static u32 pci_azx_readl(u32 __iomem *addr) +{ + return readl(addr); +} + +static void pci_azx_writew(u16 value, u16 __iomem *addr) +{ + writew(value, addr); +} + +static u16 pci_azx_readw(u16 __iomem *addr) +{ + return readw(addr); +} + +static void pci_azx_writeb(u8 value, u8 __iomem *addr) +{ + writeb(value, addr); +} + +static u8 pci_azx_readb(u8 __iomem *addr) +{ + return readb(addr); +} + +/* DMA page allocation helpers. */ +static int dma_alloc_pages(struct hdac_bus *bus, + int type, + size_t size, + struct snd_dma_buffer *buf) +{ + int err; + + err = snd_dma_alloc_pages(type, + bus->dev, + size, buf); + if (err < 0) + return err; + return 0; +} + +static void dma_free_pages(struct hdac_bus *bus, struct snd_dma_buffer *buf) +{ + snd_dma_free_pages(buf); +} + +static const struct hdac_io_ops hda_io_ops = { + .reg_writel = pci_azx_writel, + .reg_readl = pci_azx_readl, + .reg_writew = pci_azx_writew, + .reg_readw = pci_azx_readw, + .reg_writeb = pci_azx_writeb, + .reg_readb = pci_azx_readb, + .dma_alloc_pages = dma_alloc_pages, + .dma_free_pages = dma_free_pages, +}; + +static int azx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + struct hda_skl *hda; + struct soc_hdac_bus *sbus = NULL; + struct hdac_bus *bus = NULL; + int err; + + err = azx_create(pci, &hda_io_ops, &hda); + if (err < 0) + return err; + + sbus = &hda->sbus; + bus = hdac_bus(sbus); + + err = azx_first_init(sbus); + if (err < 0) + goto out_free; + + /*create device for soc dmic*/ + err = hda_dmic_device_register(hda); + if (err < 0) + goto out_free; + + /* register platform dai and controls */ + err = soc_hda_platform_register(bus->dev); + if (err < 0) + goto out_dmic_free; + /* create codec instances */ + err = azx_codec_create(sbus); + if (err < 0) + goto out_unregister; + + pci_set_drvdata(hda->pci, sbus); + + /*configure PM */ + pm_runtime_set_autosuspend_delay(bus->dev, HDA_SKL_SUSPEND_DELAY); + pm_runtime_use_autosuspend(bus->dev); + pm_runtime_put_noidle(bus->dev); + pm_runtime_allow(bus->dev); + + pci_set_drvdata(hda->pci, sbus); + return 0; + +out_unregister: + soc_hda_platform_unregister(bus->dev); +out_dmic_free: + hda_dmic_device_unregister(hda); +out_free: + hda->init_failed = 1; + azx_free(sbus); + pci_set_drvdata(hda->pci, NULL); + return err; +} + +static void azx_remove(struct pci_dev *pci) +{ + struct soc_hdac_bus *sbus = pci_get_drvdata(pci); + struct hda_skl *hda = to_hda_skl(sbus); + + if (pci_dev_run_wake(pci)) + pm_runtime_get_noresume(&pci->dev); + pci_dev_put(pci); + soc_hda_platform_unregister(&pci->dev); + hda_dmic_device_unregister(hda); + azx_free(sbus); + dev_set_drvdata(&pci->dev, NULL); +} + +/* PCI IDs */ +static const struct pci_device_id azx_ids[] = { + /* Sunrise Point-LP */ + { PCI_DEVICE(0x8086, 0x9d70), 0}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, azx_ids); + +/* pci_driver definition */ +static struct pci_driver azx_driver = { + .name = KBUILD_MODNAME, + .id_table = azx_ids, + .probe = azx_probe, + .remove = azx_remove, + .driver = { + .pm = &azx_pm, + }, +}; + +module_pci_driver(azx_driver); diff --git a/sound/soc/intel/skylake/hda-skl.h b/sound/soc/intel/skylake/hda-skl.h new file mode 100644 index 000000000000..ed6ccd31b198 --- /dev/null +++ b/sound/soc/intel/skylake/hda-skl.h @@ -0,0 +1,73 @@ +/* + * hda-skl.h - HD Audio skylake defintions. + * + * Copyright (C) 2015 Intel Corp + * Author: Jeeja KP jeeja.kp@intel.com + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef __SOUND_SOC_HDA_SKL_H +#define __SOUND_SOC_HDA_SKL_H + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/hda_register.h> +#include <sound/hdaudio.h> +#include <sound/soc-hdaudio.h> + +#define HDA_SKL_SUSPEND_DELAY 2000 + +/* Vendor Specific Registers */ +#define AZX_REG_VS_EM1 0x1000 +#define AZX_REG_VS_INRC 0x1004 +#define AZX_REG_VS_OUTRC 0x1008 +#define AZX_REG_VS_FIFOTRK 0x100C +#define AZX_REG_VS_FIFOTRK2 0x1010 +#define AZX_REG_VS_EM2 0x1030 +#define AZX_REG_VS_EM3L 0x1038 +#define AZX_REG_VS_EM3U 0x103C +#define AZX_REG_VS_EM4L 0x1040 +#define AZX_REG_VS_EM4U 0x1044 +#define AZX_REG_VS_LTRC 0x1048 +#define AZX_REG_VS_D0I3C 0x104A +#define AZX_REG_VS_PCE 0x104B +#define AZX_REG_VS_L2MAGC 0x1050 +#define AZX_REG_VS_L2LAHPT 0x1054 +#define AZX_REG_VS_SDXDPIB_XBASE 0x1084 +#define AZX_REG_VS_SDXDPIB_XINTERVAL 0x20 +#define AZX_REG_VS_SDXEFIFOS_XBASE 0x1094 +#define AZX_REG_VS_SDXEFIFOS_XINTERVAL 0x20 +struct hda_skl { + struct soc_hdac_bus sbus; + struct pci_dev *pci; + + unsigned int init_failed:1; /* delayed init failed */ + unsigned int msi:1; + struct platform_device *dmic_dev; +}; + +#define soc_hdac_bus(s) (&(s)->sbus) +#define to_hda_skl(sbus) \ + container_of(sbus, struct hda_skl, sbus) + +/* to pass dai dma data */ +struct soc_hda_dma_params { + u32 format; + u8 stream_tag; +}; + +int soc_hda_platform_unregister(struct device *dev); +int soc_hda_platform_register(struct device *dev); +#endif /* __SOUND_SOC_HDA_SKL_H */
At Wed, 29 Apr 2015 01:24:28 +0530, Vinod Koul wrote:
+MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{Intel, PCH},"); +MODULE_DESCRIPTION("Intel aDSP HDA driver");
Are the descriptions correct?
+/*
- Check whether the current DMA position is acceptable for updating
- periods. Returns non-zero if it's OK.
- */
+static int azx_position_ok(struct hdac_bus *bus, struct hdac_stream *stream) +{
- u32 wallclk;
- unsigned int pos = 0;
- wallclk = snd_hdac_chip_readl(bus, WALLCLK) - stream->start_wallclk;
- if (wallclk < (stream->period_wallclk * 2) / 3)
return -1; /* bogus (too early) interrupt */
- if (bus->use_posbuf) {
/* use the position buffer as default */
pos = snd_hdac_stream_get_pos_posbuf(stream);
if (!pos || pos == (u32)-1) {
dev_info(bus->dev,
"Invalid pos buffer, using LPIB read method instead.\n");
pos = snd_hdac_stream_get_pos_lpib(stream);
}
- }
- if (pos >= stream->bufsize)
pos = 0;
- if (WARN_ONCE(!stream->period_bytes,
"hda-skl: zero stream->period_bytes"))
return -1; /* this shouldn't happen! */
- if (wallclk < (stream->period_wallclk * 5) / 4 &&
pos % stream->period_bytes > stream->period_bytes / 2)
/* NG - it's below the first next period boundary */
return bus->bdl_pos_adj ? 0 : -1;
- stream->start_wallclk += wallclk;
- return 1; /* OK, it's fine */
I'd love to drop this messy trick if SKL doesn't need it. Could you check whether it works without this?
+static irqreturn_t azx_interrupt(int irq, void *dev_id) +{
- struct soc_hdac_bus *sbus = dev_id;
- struct hdac_bus *bus = hdac_bus(sbus);
- u32 status;
+#ifdef CONFIG_PM
- if (!pm_runtime_active(bus->dev))
return IRQ_NONE;
+#endif
- spin_lock(&bus->reg_lock);
- status = snd_hdac_chip_readl(bus, INTSTS);
- if (status == 0 || status == 0xffffffff) {
spin_unlock(&bus->reg_lock);
return IRQ_NONE;
- }
- /* clear rirb int */
- status = snd_hdac_chip_readb(bus, RIRBSTS);
- if (status & RIRB_INT_MASK) {
if (status & RIRB_INT_RESPONSE)
snd_hdac_bus_update_rirb(bus);
snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
return IRQ_HANDLED;
An irq may have both RIRB and stream irq bits set. If we return IRQ_HANDLED here, I suppose the stream irqs won't be handled.
- }
- spin_unlock(&bus->reg_lock);
- return IRQ_WAKE_THREAD;
+}
The threaded irq handler checks only the stream irqs, so better to check INTSTS here. That is, something like: return snd_hdac_chip_readl(bus, INTSTS) ? IRQ_WAKE_THREAD : IRQ_HANDLED;
+#ifdef CONFIG_PM_SLEEP +/*
- power management
- */
+static int azx_suspend(struct device *dev) +{
- struct pci_dev *pci = to_pci_dev(dev);
- struct soc_hdac_bus *sbus = pci_get_drvdata(pci);
- struct hdac_bus *bus = hdac_bus(sbus);
- struct hda_skl *hda = to_hda_skl(sbus);
- snd_hdac_bus_stop_chip(bus);
- snd_hdac_bus_enter_link_reset(bus);
- if (bus->irq >= 0) {
free_irq(bus->irq, bus);
bus->irq = -1;
- }
- if (hda->msi)
pci_disable_msi(pci);
- pci_disable_device(pci);
- pci_save_state(pci);
- pci_set_power_state(pci, PCI_D3hot);
Drop the last three lines, they are done in PCI core nowadays.
- return 0;
+}
+static int azx_resume(struct device *dev) +{
- struct pci_dev *pci = to_pci_dev(dev);
- struct soc_hdac_bus *sbus = pci_get_drvdata(pci);
- struct hdac_bus *bus = hdac_bus(sbus);
- struct hda_skl *hda = to_hda_skl(sbus);
- pci_set_power_state(pci, PCI_D0);
- pci_restore_state(pci);
- if (pci_enable_device(pci) < 0) {
dev_err(dev, "hda-intel: pci_enable_device failed, disabling device\n");
return -EIO;
- }
These three calls should be dropped, too.
- pci_set_master(pci);
- if (hda->msi)
if (pci_enable_msi(pci) < 0)
hda->msi = 0;
- if (azx_acquire_irq(sbus, 1) < 0)
return -EIO;
- azx_init_pci(hda);
- snd_hdac_bus_init_chip(bus, 1);
- return 0;
+} +#endif /* CONFIG_PM_SLEEP */
+#ifdef CONFIG_PM +static int azx_runtime_suspend(struct device *dev) +{
- struct pci_dev *pci = to_pci_dev(dev);
- struct soc_hdac_bus *sbus = pci_get_drvdata(pci);
- struct hdac_bus *bus = hdac_bus(sbus);
- dev_dbg(bus->dev, "in %s\n", __func__);
- /* enable controller wake up event */
- snd_hdac_chip_updatew(bus, WAKEEN, 0, STATESTS_INT_MASK);
- snd_hdac_bus_stop_chip(bus);
- snd_hdac_bus_enter_link_reset(bus);
- return 0;
+}
+static int azx_runtime_resume(struct device *dev) +{
- struct pci_dev *pci = to_pci_dev(dev);
- struct soc_hdac_bus *sbus = pci_get_drvdata(pci);
- struct hdac_bus *bus = hdac_bus(sbus);
- struct hda_skl *hda = to_hda_skl(sbus);
- int status;
- dev_dbg(bus->dev, "in %s\n", __func__);
- /* Read STATESTS before controller reset */
- status = snd_hdac_chip_readw(bus, STATESTS);
- azx_init_pci(hda);
- snd_hdac_bus_init_chip(bus, true);
- /* disable controller Wake Up event */
- snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, 0);
- return 0;
+}
+static const struct dev_pm_ops azx_pm = {
- SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume)
- SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, NULL)
+}; +#endif /* CONFIG_PM */
+/*
- destructor
- */
+static int azx_free(struct soc_hdac_bus *sbus) +{
- struct hda_skl *hda = to_hda_skl(sbus);
- struct hdac_bus *bus = hdac_bus(sbus);
- hda->init_failed = 1; /* to be sure */
- snd_soc_hdac_stop_streams(sbus);
- if (bus->irq >= 0)
free_irq(bus->irq, (void *)bus);
- if (hda->msi)
pci_disable_msi(hda->pci);
- if (bus->remap_addr)
iounmap(bus->remap_addr);
- snd_hdac_bus_free_stream_pages(bus);
- azx_free_streams(sbus);
- pci_release_regions(hda->pci);
- pci_disable_device(hda->pci);
- snd_hdac_bus_exit(bus);
- return 0;
+}
+static int hda_dmic_device_register(struct hda_skl *hda) +{
- struct hdac_bus *bus = hdac_bus(&hda->sbus);
- struct platform_device *pdev;
- int ret;
- pdev = platform_device_alloc("dmic-codec", -1);
- if (!pdev) {
dev_err(bus->dev, "failed to allocate dmic device\n");
return -1;
- }
- ret = platform_device_add(pdev);
- if (ret) {
dev_err(bus->dev, "failed to add hda codec device\n");
platform_device_put(pdev);
return -1;
- }
- hda->dmic_dev = pdev;
- return 0;
+}
+static void hda_dmic_device_unregister(struct hda_skl *hda) +{
- if (hda->dmic_dev)
platform_device_unregister(hda->dmic_dev);
+}
+static int azx_add_codec_device(int addr, struct hdac_bus *bus) +{
- struct hdac_device *hdev = NULL;
- char name[10];
- int ret;
- hdev = devm_kzalloc(bus->dev, sizeof(*hdev), GFP_KERNEL);
- if (!hdev)
return -ENOMEM;
- snprintf(name, sizeof(name), "codec#%03x", addr);
The name should be unique, and this would conflict when there are multiple sound cards with the same codec address. The legacy driver, for example, encodes the name with the card index and the codec address.
+static int azx_first_init(struct soc_hdac_bus *sbus) +{
- struct hda_skl *hda = to_hda_skl(sbus);
- struct hdac_bus *bus = hdac_bus(sbus);
- struct pci_dev *pci = hda->pci;
- int err;
- unsigned short gcap;
- int capture_streams, playback_streams;
- err = pci_request_regions(pci, "ICH HD audio");
Choose a more sensible name for a new driver :)
- if (err < 0)
return err;
- bus->addr = pci_resource_start(pci, 0);
- bus->remap_addr = pci_ioremap_bar(pci, 0);
- if (bus->remap_addr == NULL) {
dev_err(bus->dev, "ioremap error\n");
return -ENXIO;
- }
- if (hda->msi)
if (pci_enable_msi(pci) < 0)
hda->msi = 0;
- if (azx_acquire_irq(sbus, 0) < 0)
return -EBUSY;
- pci_set_master(pci);
- synchronize_irq(bus->irq);
- gcap = snd_hdac_chip_readw(bus, GCAP);
- dev_dbg(bus->dev, "chipset global capabilities = 0x%x\n", gcap);
- /* allow 64bit DMA address if supported by H/W */
- if (!pci_set_dma_mask(pci, DMA_BIT_MASK(64)))
pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(64));
- else {
pci_set_dma_mask(pci, DMA_BIT_MASK(32));
pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32));
- }
These are replaced with dma_set_mask(). See the codes in for-next branch.
+/* PCI IDs */ +static const struct pci_device_id azx_ids[] = {
- /* Sunrise Point-LP */
- { PCI_DEVICE(0x8086, 0x9d70), 0},
- { 0, }
+}; +MODULE_DEVICE_TABLE(pci, azx_ids);
We wanted to have a mechanism to work around the PCI ID conflict between asoc and legacy drivers. Will it be included in the next series?
Takashi
On Wed, Apr 29, 2015 at 02:49:05PM +0200, Takashi Iwai wrote:
At Wed, 29 Apr 2015 01:24:28 +0530, Vinod Koul wrote:
+MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{Intel, PCH},"); +MODULE_DESCRIPTION("Intel aDSP HDA driver");
Are the descriptions correct?
What am i missing?
+/*
- Check whether the current DMA position is acceptable for updating
- periods. Returns non-zero if it's OK.
- */
+static int azx_position_ok(struct hdac_bus *bus, struct hdac_stream *stream) +{
- u32 wallclk;
- unsigned int pos = 0;
- wallclk = snd_hdac_chip_readl(bus, WALLCLK) - stream->start_wallclk;
- if (wallclk < (stream->period_wallclk * 2) / 3)
return -1; /* bogus (too early) interrupt */
- if (bus->use_posbuf) {
/* use the position buffer as default */
pos = snd_hdac_stream_get_pos_posbuf(stream);
if (!pos || pos == (u32)-1) {
dev_info(bus->dev,
"Invalid pos buffer, using LPIB read method instead.\n");
pos = snd_hdac_stream_get_pos_lpib(stream);
}
- }
- if (pos >= stream->bufsize)
pos = 0;
- if (WARN_ONCE(!stream->period_bytes,
"hda-skl: zero stream->period_bytes"))
return -1; /* this shouldn't happen! */
- if (wallclk < (stream->period_wallclk * 5) / 4 &&
pos % stream->period_bytes > stream->period_bytes / 2)
/* NG - it's below the first next period boundary */
return bus->bdl_pos_adj ? 0 : -1;
- stream->start_wallclk += wallclk;
- return 1; /* OK, it's fine */
I'd love to drop this messy trick if SKL doesn't need it. Could you check whether it works without this?
Sure let me test this
+static irqreturn_t azx_interrupt(int irq, void *dev_id) +{
- struct soc_hdac_bus *sbus = dev_id;
- struct hdac_bus *bus = hdac_bus(sbus);
- u32 status;
+#ifdef CONFIG_PM
- if (!pm_runtime_active(bus->dev))
return IRQ_NONE;
+#endif
- spin_lock(&bus->reg_lock);
- status = snd_hdac_chip_readl(bus, INTSTS);
- if (status == 0 || status == 0xffffffff) {
spin_unlock(&bus->reg_lock);
return IRQ_NONE;
- }
- /* clear rirb int */
- status = snd_hdac_chip_readb(bus, RIRBSTS);
- if (status & RIRB_INT_MASK) {
if (status & RIRB_INT_RESPONSE)
snd_hdac_bus_update_rirb(bus);
snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
return IRQ_HANDLED;
An irq may have both RIRB and stream irq bits set. If we return IRQ_HANDLED here, I suppose the stream irqs won't be handled.
They will be in the thread handler. Also worth mentioning here is that the aDSP interrupts will be handled by IPC code (shared interrupt)
Now yes we are missing fallback to coupled mode, if aDSP fails then stream should be handled here, will add that
- }
- spin_unlock(&bus->reg_lock);
- return IRQ_WAKE_THREAD;
+}
The threaded irq handler checks only the stream irqs, so better to check INTSTS here. That is, something like: return snd_hdac_chip_readl(bus, INTSTS) ? IRQ_WAKE_THREAD : IRQ_HANDLED;
Yes that makese sense
+#ifdef CONFIG_PM_SLEEP +/*
- power management
- */
+static int azx_suspend(struct device *dev) +{
- struct pci_dev *pci = to_pci_dev(dev);
- struct soc_hdac_bus *sbus = pci_get_drvdata(pci);
- struct hdac_bus *bus = hdac_bus(sbus);
- struct hda_skl *hda = to_hda_skl(sbus);
- snd_hdac_bus_stop_chip(bus);
- snd_hdac_bus_enter_link_reset(bus);
- if (bus->irq >= 0) {
free_irq(bus->irq, bus);
bus->irq = -1;
- }
- if (hda->msi)
pci_disable_msi(pci);
- pci_disable_device(pci);
- pci_save_state(pci);
- pci_set_power_state(pci, PCI_D3hot);
Drop the last three lines, they are done in PCI core nowadays.
ok
- return 0;
+}
+static int azx_resume(struct device *dev) +{
- struct pci_dev *pci = to_pci_dev(dev);
- struct soc_hdac_bus *sbus = pci_get_drvdata(pci);
- struct hdac_bus *bus = hdac_bus(sbus);
- struct hda_skl *hda = to_hda_skl(sbus);
- pci_set_power_state(pci, PCI_D0);
- pci_restore_state(pci);
- if (pci_enable_device(pci) < 0) {
dev_err(dev, "hda-intel: pci_enable_device failed, disabling device\n");
return -EIO;
- }
These three calls should be dropped, too.
ok
- pci_set_master(pci);
- if (hda->msi)
if (pci_enable_msi(pci) < 0)
hda->msi = 0;
- if (azx_acquire_irq(sbus, 1) < 0)
return -EIO;
- azx_init_pci(hda);
- snd_hdac_bus_init_chip(bus, 1);
- return 0;
+} +#endif /* CONFIG_PM_SLEEP */
+#ifdef CONFIG_PM +static int azx_runtime_suspend(struct device *dev) +{
- struct pci_dev *pci = to_pci_dev(dev);
- struct soc_hdac_bus *sbus = pci_get_drvdata(pci);
- struct hdac_bus *bus = hdac_bus(sbus);
- dev_dbg(bus->dev, "in %s\n", __func__);
- /* enable controller wake up event */
- snd_hdac_chip_updatew(bus, WAKEEN, 0, STATESTS_INT_MASK);
- snd_hdac_bus_stop_chip(bus);
- snd_hdac_bus_enter_link_reset(bus);
- return 0;
+}
+static int azx_runtime_resume(struct device *dev) +{
- struct pci_dev *pci = to_pci_dev(dev);
- struct soc_hdac_bus *sbus = pci_get_drvdata(pci);
- struct hdac_bus *bus = hdac_bus(sbus);
- struct hda_skl *hda = to_hda_skl(sbus);
- int status;
- dev_dbg(bus->dev, "in %s\n", __func__);
- /* Read STATESTS before controller reset */
- status = snd_hdac_chip_readw(bus, STATESTS);
- azx_init_pci(hda);
- snd_hdac_bus_init_chip(bus, true);
- /* disable controller Wake Up event */
- snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, 0);
- return 0;
+}
+static const struct dev_pm_ops azx_pm = {
- SET_SYSTEM_SLEEP_PM_OPS(azx_suspend, azx_resume)
- SET_RUNTIME_PM_OPS(azx_runtime_suspend, azx_runtime_resume, NULL)
+}; +#endif /* CONFIG_PM */
+/*
- destructor
- */
+static int azx_free(struct soc_hdac_bus *sbus) +{
- struct hda_skl *hda = to_hda_skl(sbus);
- struct hdac_bus *bus = hdac_bus(sbus);
- hda->init_failed = 1; /* to be sure */
- snd_soc_hdac_stop_streams(sbus);
- if (bus->irq >= 0)
free_irq(bus->irq, (void *)bus);
- if (hda->msi)
pci_disable_msi(hda->pci);
- if (bus->remap_addr)
iounmap(bus->remap_addr);
- snd_hdac_bus_free_stream_pages(bus);
- azx_free_streams(sbus);
- pci_release_regions(hda->pci);
- pci_disable_device(hda->pci);
- snd_hdac_bus_exit(bus);
- return 0;
+}
+static int hda_dmic_device_register(struct hda_skl *hda) +{
- struct hdac_bus *bus = hdac_bus(&hda->sbus);
- struct platform_device *pdev;
- int ret;
- pdev = platform_device_alloc("dmic-codec", -1);
- if (!pdev) {
dev_err(bus->dev, "failed to allocate dmic device\n");
return -1;
- }
- ret = platform_device_add(pdev);
- if (ret) {
dev_err(bus->dev, "failed to add hda codec device\n");
platform_device_put(pdev);
return -1;
- }
- hda->dmic_dev = pdev;
- return 0;
+}
+static void hda_dmic_device_unregister(struct hda_skl *hda) +{
- if (hda->dmic_dev)
platform_device_unregister(hda->dmic_dev);
+}
+static int azx_add_codec_device(int addr, struct hdac_bus *bus) +{
- struct hdac_device *hdev = NULL;
- char name[10];
- int ret;
- hdev = devm_kzalloc(bus->dev, sizeof(*hdev), GFP_KERNEL);
- if (!hdev)
return -ENOMEM;
- snprintf(name, sizeof(name), "codec#%03x", addr);
The name should be unique, and this would conflict when there are multiple sound cards with the same codec address. The legacy driver, for example, encodes the name with the card index and the codec address.
would we have multiple cards on a system with hda codecs? I am not too sure, but yes lets add this
+static int azx_first_init(struct soc_hdac_bus *sbus) +{
- struct hda_skl *hda = to_hda_skl(sbus);
- struct hdac_bus *bus = hdac_bus(sbus);
- struct pci_dev *pci = hda->pci;
- int err;
- unsigned short gcap;
- int capture_streams, playback_streams;
- err = pci_request_regions(pci, "ICH HD audio");
Choose a more sensible name for a new driver :)
Ah yes :) I am think "SKL HDA aDSP driver" or generically "HDA aDSP driver"
- if (err < 0)
return err;
- bus->addr = pci_resource_start(pci, 0);
- bus->remap_addr = pci_ioremap_bar(pci, 0);
- if (bus->remap_addr == NULL) {
dev_err(bus->dev, "ioremap error\n");
return -ENXIO;
- }
- if (hda->msi)
if (pci_enable_msi(pci) < 0)
hda->msi = 0;
- if (azx_acquire_irq(sbus, 0) < 0)
return -EBUSY;
- pci_set_master(pci);
- synchronize_irq(bus->irq);
- gcap = snd_hdac_chip_readw(bus, GCAP);
- dev_dbg(bus->dev, "chipset global capabilities = 0x%x\n", gcap);
- /* allow 64bit DMA address if supported by H/W */
- if (!pci_set_dma_mask(pci, DMA_BIT_MASK(64)))
pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(64));
- else {
pci_set_dma_mask(pci, DMA_BIT_MASK(32));
pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(32));
- }
These are replaced with dma_set_mask(). See the codes in for-next branch.
Sure, will use that
+/* PCI IDs */ +static const struct pci_device_id azx_ids[] = {
- /* Sunrise Point-LP */
- { PCI_DEVICE(0x8086, 0x9d70), 0},
- { 0, }
+}; +MODULE_DEVICE_TABLE(pci, azx_ids);
We wanted to have a mechanism to work around the PCI ID conflict between asoc and legacy drivers. Will it be included in the next series?
I will send that out first before this is merged, possiblu later today so that you can see them before you leave :)
At Thu, 30 Apr 2015 15:41:40 +0530, Vinod Koul wrote:
On Wed, Apr 29, 2015 at 02:49:05PM +0200, Takashi Iwai wrote:
At Wed, 29 Apr 2015 01:24:28 +0530, Vinod Koul wrote:
+MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{Intel, PCH},"); +MODULE_DESCRIPTION("Intel aDSP HDA driver");
Are the descriptions correct?
What am i missing?
Is it for PCH?
Well, we've had entries for MODULE_SUPPORTED_DEVICE() that have been used for alsaconf script. But nowadays it's really obsoleted, and I think we should get rid of MODULE_SUPPORTED_DEVICE() line for avoiding confusion.
+static irqreturn_t azx_interrupt(int irq, void *dev_id) +{
- struct soc_hdac_bus *sbus = dev_id;
- struct hdac_bus *bus = hdac_bus(sbus);
- u32 status;
+#ifdef CONFIG_PM
- if (!pm_runtime_active(bus->dev))
return IRQ_NONE;
+#endif
- spin_lock(&bus->reg_lock);
- status = snd_hdac_chip_readl(bus, INTSTS);
- if (status == 0 || status == 0xffffffff) {
spin_unlock(&bus->reg_lock);
return IRQ_NONE;
- }
- /* clear rirb int */
- status = snd_hdac_chip_readb(bus, RIRBSTS);
- if (status & RIRB_INT_MASK) {
if (status & RIRB_INT_RESPONSE)
snd_hdac_bus_update_rirb(bus);
snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
return IRQ_HANDLED;
An irq may have both RIRB and stream irq bits set. If we return IRQ_HANDLED here, I suppose the stream irqs won't be handled.
They will be in the thread handler.
... and it's woken up only when IRQ_WAKE_THREAD is returned.
Also worth mentioning here is that the aDSP interrupts will be handled by IPC code (shared interrupt)
Now yes we are missing fallback to coupled mode, if aDSP fails then stream should be handled here, will add that
- }
- spin_unlock(&bus->reg_lock);
- return IRQ_WAKE_THREAD;
+}
The threaded irq handler checks only the stream irqs, so better to check INTSTS here. That is, something like: return snd_hdac_chip_readl(bus, INTSTS) ? IRQ_WAKE_THREAD : IRQ_HANDLED;
Yes that makese sense
(snip)
+static int azx_add_codec_device(int addr, struct hdac_bus *bus) +{
- struct hdac_device *hdev = NULL;
- char name[10];
- int ret;
- hdev = devm_kzalloc(bus->dev, sizeof(*hdev), GFP_KERNEL);
- if (!hdev)
return -ENOMEM;
- snprintf(name, sizeof(name), "codec#%03x", addr);
The name should be unique, and this would conflict when there are multiple sound cards with the same codec address. The legacy driver, for example, encodes the name with the card index and the codec address.
would we have multiple cards on a system with hda codecs? I am not too sure, but yes lets add this
Yes, typically with discrete graphics (AMD or Nvidia).
Takashi
From: Jeeja KP jeeja.kp@intel.com
Add makefile and Kconfig to enable Skylake HD audio driver
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Subhransu S. Prusty subhransu.s.prusty@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/Kconfig | 20 ++++++++++++++++++++ sound/soc/intel/Makefile | 1 + sound/soc/intel/skylake/Makefile | 3 +++ 3 files changed, 24 insertions(+) create mode 100644 sound/soc/intel/skylake/Makefile
diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index ee03dbdda235..4ceca88de356 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -121,3 +121,23 @@ config SND_SOC_INTEL_CHT_BSW_RT5645_MACH This adds support for ASoC machine driver for Intel(R) Cherrytrail & Braswell platforms with RT5645 audio codec. If unsure select "N". + +config SND_SOC_HDA_PREALLOC_SIZE + int "Pre-allocated buffer size for skylake HD-audio driver" + range 0 32768 + default 64 + help + Specifies the default pre-allocated buffer-size in kB for the + HD-audio driver. A larger buffer (e.g. 2048) is preferred + for systems using PulseAudio. The default 64 is chosen just + for compatibility reasons. + +config SND_SOC_INTEL_HDA_SKYLAKE + tristate "ASoC Intel HD Audio" + select SND_SOC_HDA_CORE + help + Say Y here to include support for ASoC Intel "High Definition + Audio" (Skylake) and its compatible devices. + + To compile this driver as a module, choose M here: the module + will be called snd-soc-hda-skl. diff --git a/sound/soc/intel/Makefile b/sound/soc/intel/Makefile index cd9aee9871a3..564a8d6e2195 100644 --- a/sound/soc/intel/Makefile +++ b/sound/soc/intel/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_SND_SOC_INTEL_SST) += common/ obj-$(CONFIG_SND_SOC_INTEL_HASWELL) += haswell/ obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += baytrail/ obj-$(CONFIG_SND_SOC_INTEL_BAYTRAIL) += atom/ +obj-$(CONFIG_SND_SOC_INTEL_HDA_SKYLAKE) += skylake/
# Machine support obj-$(CONFIG_SND_SOC_INTEL_SST) += boards/ diff --git a/sound/soc/intel/skylake/Makefile b/sound/soc/intel/skylake/Makefile new file mode 100644 index 000000000000..1848ac809519 --- /dev/null +++ b/sound/soc/intel/skylake/Makefile @@ -0,0 +1,3 @@ +snd-soc-hda-skl-objs := hda-skl.o hda-skl-pcm.o + +obj-$(CONFIG_SND_SOC_INTEL_HDA_SKYLAKE) += snd-soc-hda-skl.o
From: Jeeja KP jeeja.kp@intel.com
Decoupled mode is where audio link is broken to frontend HDA and backend (hda/i2s/dmic/hdmi) links. This patch adds support for decoupled mode and then adds dais, dai ops for be/fe cpu dais and interrupt handler change to support decoupled mode
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- sound/soc/intel/skylake/hda-skl-pcm.c | 365 ++++++++++++++++++++++++++++++++- sound/soc/intel/skylake/hda-skl.c | 24 ++- sound/soc/intel/skylake/hda-skl.h | 1 + 3 files changed, 381 insertions(+), 9 deletions(-)
diff --git a/sound/soc/intel/skylake/hda-skl-pcm.c b/sound/soc/intel/skylake/hda-skl-pcm.c index e4d1650b49f5..38d322615e16 100644 --- a/sound/soc/intel/skylake/hda-skl-pcm.c +++ b/sound/soc/intel/skylake/hda-skl-pcm.c @@ -102,6 +102,14 @@ static void soc_hda_set_pcm_constrains(struct soc_hdac_bus *sbus, 20, 178000000); }
+static enum hdac_stream_type hda_get_host_stream_type(struct soc_hdac_bus *sbus) +{ + if (sbus->ppcap) + return HDAC_STREAM_TYPE_HOST; + else + return HDAC_STREAM_TYPE_COUPLED; +} + static int soc_hda_pcm_open(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -114,7 +122,7 @@ static int soc_hda_pcm_open(struct snd_pcm_substream *substream, pm_runtime_get_sync(dai->dev);
stream = snd_soc_hdac_stream_assign(sbus, substream, - HDAC_STREAM_TYPE_COUPLED); + hda_get_host_stream_type(sbus)); if (stream == NULL) return -EBUSY;
@@ -149,15 +157,27 @@ static int hda_get_format(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); struct soc_hda_dma_params *dma_params; int format_val = 0; - struct snd_soc_dai *codec_dai = rtd->codec_dai;
- dma_params = (struct soc_hda_dma_params *) + if (sbus->ppcap) { + struct snd_pcm_runtime *runtime = substream->runtime; + + format_val = snd_hdac_calc_stream_format(runtime->rate, + runtime->channels, + runtime->format, + 32, 0); + + } else { + struct snd_soc_dai *codec_dai = rtd->codec_dai; + + dma_params = (struct soc_hda_dma_params *) snd_soc_dai_get_dma_data(codec_dai, substream);
- if (!dma_params) - format_val = dma_params->format; + if (!dma_params) + format_val = dma_params->format; + }
return format_val; } @@ -195,8 +215,9 @@ static int soc_hda_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); + struct soc_hdac_stream *stream = get_hdac_stream(substream); struct snd_pcm_runtime *runtime = substream->runtime; - int ret = 0; + int ret, dma_id;
dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); ret = substream_alloc_pages(sbus, substream, @@ -212,17 +233,22 @@ static int soc_hda_pcm_hw_params(struct snd_pcm_substream *substream, dev_dbg(dai->dev, "format_val, rate=%d, ch=%d, format=%d\n", runtime->rate, runtime->channels, runtime->format);
+ dma_id = hdac_stream(stream)->stream_tag; + dev_dbg(dai->dev, "dma_id=%d\n", dma_id); + return ret; }
static void soc_hda_pcm_close(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); struct soc_hdac_stream *stream = get_hdac_stream(substream); struct soc_hda_dma_params *dma_params = NULL;
dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); - snd_soc_hdac_stream_release(stream, HDAC_STREAM_TYPE_COUPLED); + + snd_soc_hdac_stream_release(stream, hda_get_host_stream_type(sbus));
dma_params = (struct soc_hda_dma_params *) snd_soc_dai_get_dma_data(dai, substream); @@ -245,6 +271,167 @@ static int soc_hda_pcm_hw_free(struct snd_pcm_substream *substream, return substream_free_pages(hdac_bus(sbus), substream); }
+static int hda_be_dmic_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_hw_params params = {0}; + struct snd_interval *channels, *rate; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + channels = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS); + channels->min = channels->max = substream->runtime->channels; + rate = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE); + rate->min = rate->max = substream->runtime->rate; + snd_mask_set(¶ms.masks[SNDRV_PCM_HW_PARAM_FORMAT - + SNDRV_PCM_HW_PARAM_FIRST_MASK], + substream->runtime->format); + + return 0; +} + +static int soc_hda_be_link_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); + struct soc_hdac_stream *link_dev; + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct soc_hda_dma_params *dma_params; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int dma_id; + + pr_debug("%s\n", __func__); + link_dev = snd_soc_hdac_stream_assign(sbus, substream, + HDAC_STREAM_TYPE_LINK); + if (!link_dev) + return -EBUSY; + + snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev); + + /*set the stream tag in the codec dai dma params */ + dma_params = (struct soc_hda_dma_params *) + snd_soc_dai_get_dma_data(codec_dai, substream); + if (dma_params) + dma_params->stream_tag = hdac_stream(link_dev)->stream_tag; + snd_soc_dai_set_dma_data(codec_dai, substream, (void *)dma_params); + dma_id = hdac_stream(link_dev)->stream_tag; + + return 0; +} + +static int soc_hda_be_link_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); + struct soc_hdac_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + unsigned int format_val = 0; + int ret = 0; + struct soc_hda_dma_params *dma_params; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_pcm_hw_params *params; + struct snd_interval *channels, *rate; + struct soc_hdac_link *link; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + if (link_dev->link_prepared) { + dev_dbg(dai->dev, "already stream is prepared - returning\n"); + return 0; + } + params = devm_kzalloc(dai->dev, sizeof(*params), GFP_KERNEL); + if (params == NULL) + return -ENOMEM; + + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + channels->min = channels->max = substream->runtime->channels; + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->min = rate->max = substream->runtime->rate; + snd_mask_set(¶ms->masks[SNDRV_PCM_HW_PARAM_FORMAT - + SNDRV_PCM_HW_PARAM_FIRST_MASK], + substream->runtime->format); + + + dma_params = (struct soc_hda_dma_params *) + snd_soc_dai_get_dma_data(codec_dai, substream); + if (dma_params) + format_val = dma_params->format; + dev_dbg(dai->dev, "stream_tag=%d formatvalue=%d codec_dai_name=%s\n", + hdac_stream(link_dev)->stream_tag, format_val, + codec_dai->name); + snd_soc_hdac_link_stream_reset(link_dev); + + snd_soc_hdac_link_stream_setup(link_dev, format_val); + + link = snd_soc_hdac_bus_get_link(sbus, rtd->codec->component.name); + if (!link) + return -EINVAL; + snd_soc_hdac_link_set_stream_id(link, hdac_stream(link_dev)->stream_tag); + link_dev->link_prepared = 1; + return ret; +} + +static int soc_hda_be_link_pcm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct soc_hdac_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + + dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + snd_soc_hdac_link_stream_start(link_dev); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + snd_soc_hdac_link_stream_clear(link_dev); + break; + + default: + return -EINVAL; + } + return 0; +} + +static int soc_hda_be_link_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct soc_hdac_bus *sbus = dev_get_drvdata(dai->dev); + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct soc_hdac_stream *link_dev = + snd_soc_dai_get_dma_data(dai, substream); + struct soc_hdac_link *link; + + dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name); + + link_dev->link_prepared = 0; + + link = snd_soc_hdac_bus_get_link(sbus, rtd->codec->component.name); + if (!link) + return -EINVAL; + snd_soc_hdac_link_clear_stream_id(link, hdac_stream(link_dev)->stream_tag); + snd_soc_hdac_stream_release(link_dev, HDAC_STREAM_TYPE_LINK); + return 0; +} + +static int soc_hda_be_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + return pm_runtime_get_sync(dai->dev); +} + +static void soc_hda_be_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + pm_runtime_mark_last_busy(dai->dev); + pm_runtime_put_autosuspend(dai->dev); +} + static struct snd_soc_dai_ops hda_pcm_dai_ops = { .startup = soc_hda_pcm_open, .shutdown = soc_hda_pcm_close, @@ -253,6 +440,21 @@ static struct snd_soc_dai_ops hda_pcm_dai_ops = { .hw_free = soc_hda_pcm_hw_free, };
+static struct snd_soc_dai_ops hda_be_dmic_dai_ops = { + .startup = soc_hda_be_startup, + .prepare = hda_be_dmic_prepare, + .shutdown = soc_hda_be_shutdown, +}; + +static struct snd_soc_dai_ops hda_be_link_dai_ops = { + .startup = soc_hda_be_startup, + .prepare = soc_hda_be_link_pcm_prepare, + .hw_params = soc_hda_be_link_hw_params, + .hw_free = soc_hda_be_link_hw_free, + .trigger = soc_hda_be_link_pcm_trigger, + .shutdown = soc_hda_be_shutdown, +}; + static struct snd_soc_dai_driver soc_hda_platform_dai[] = { { .name = "System Pin", @@ -273,6 +475,17 @@ static struct snd_soc_dai_driver soc_hda_platform_dai[] = { }, }, { + .name = "Reference Pin", + .ops = &hda_pcm_dai_ops, + .capture = { + .stream_name = "Reference Capture", + .channels_min = HDA_MONO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ .name = "Deepbuffer Pin", .ops = &hda_pcm_dai_ops, .playback = { @@ -294,6 +507,80 @@ static struct snd_soc_dai_driver soc_hda_platform_dai[] = { .formats = SNDRV_PCM_FMTBIT_S16_LE, }, }, +/*BE CPU Dais */ +{ + .name = "iDisp Pin", + .ops = &hda_be_link_dai_ops, + .playback = { + .stream_name = "iDisp Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "DMIC01 Pin", + .ops = &hda_be_dmic_dai_ops, + .capture = { + .stream_name = "DMIC01 Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "DMIC23 Pin", + .ops = &hda_be_dmic_dai_ops, + .capture = { + .stream_name = "DMIC23 Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "HD-Codec Pin", + .ops = &hda_be_link_dai_ops, + .playback = { + .stream_name = "HD-Codec Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "HD-Codec Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "HD-Codec-SPK Pin", + .ops = &hda_be_link_dai_ops, + .playback = { + .stream_name = "HD-Codec-SPK Tx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "HD-Codec-AMIC Pin", + .ops = &hda_be_link_dai_ops, + .capture = { + .stream_name = "HD-Codec-AMIC Rx", + .channels_min = HDA_STEREO, + .channels_max = HDA_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, };
static int soc_hda_platform_open(struct snd_pcm_substream *substream) @@ -310,7 +597,7 @@ static int soc_hda_platform_open(struct snd_pcm_substream *substream) return 0; }
-static int soc_hda_platform_pcm_trigger(struct snd_pcm_substream *substream, +static int soc_hda_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct soc_hdac_bus *sbus = get_bus_ctx(substream); @@ -383,6 +670,68 @@ static int soc_hda_platform_pcm_trigger(struct snd_pcm_substream *substream, return 0; }
+static int soc_hda_dsp_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct soc_hdac_bus *sbus = get_bus_ctx(substream); + struct hdac_bus *bus = hdac_bus(sbus); + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct soc_hdac_stream *stream; + int start; + unsigned long cookie; + struct hdac_stream *hstr; + + dev_dbg(bus->dev, "In %s cmd=%d streamname=%s\n", __func__, cmd, cpu_dai->name); + + stream = get_hdac_stream(substream); + hstr = hdac_stream(stream); + + if (!hstr->prepared) + return -EPIPE; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + start = 1; + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + start = 0; + break; + + default: + return -EINVAL; + } + + spin_lock_irqsave(&bus->reg_lock, cookie); + + if (start) + snd_hdac_stream_start(hdac_stream(stream), true); + else + snd_hdac_stream_stop(hdac_stream(stream)); + + if (start) + snd_hdac_stream_timecounter_init(hstr, 0); + + spin_unlock_irqrestore(&bus->reg_lock, cookie); + + return 0; +} +static int soc_hda_platform_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct soc_hdac_bus *sbus = get_bus_ctx(substream); + + if (sbus->ppcap) + return soc_hda_dsp_trigger(substream, cmd); + else + return soc_hda_pcm_trigger(substream, cmd); +} + /* calculate runtime delay from LPIB */ static int hda_get_delay_from_lpib(struct soc_hdac_bus *sbus, struct soc_hdac_stream *sstream, diff --git a/sound/soc/intel/skylake/hda-skl.c b/sound/soc/intel/skylake/hda-skl.c index 24502f8e796d..51aa07574dd3 100644 --- a/sound/soc/intel/skylake/hda-skl.c +++ b/sound/soc/intel/skylake/hda-skl.c @@ -205,10 +205,16 @@ static int azx_acquire_irq(struct soc_hdac_bus *sbus, int do_disconnect) struct hda_skl *hda = to_hda_skl(sbus); struct hdac_bus *bus = hdac_bus(sbus); int ret = 0; + unsigned long flags = 0; + + if (sbus->ppcap) + flags = IRQF_SHARED; + else + flags = hda->msi ? 0 : IRQF_SHARED;
ret = request_threaded_irq(hda->pci->irq, azx_interrupt, azx_threaded_handler, - hda->msi ? 0 : IRQF_SHARED, + flags, KBUILD_MODNAME, sbus); if (ret) { dev_err(bus->dev, @@ -281,6 +287,7 @@ static int azx_runtime_suspend(struct device *dev) struct pci_dev *pci = to_pci_dev(dev); struct soc_hdac_bus *sbus = pci_get_drvdata(pci); struct hdac_bus *bus = hdac_bus(sbus); + struct hda_skl *hda = to_hda_skl(sbus);
dev_dbg(bus->dev, "in %s\n", __func__);
@@ -420,6 +427,10 @@ static int probe_codec(struct soc_hdac_bus *sbus, int addr) if (res == -1) return -EIO; dev_dbg(bus->dev, "codec #%d probed OK\n", addr); + + if (sbus->mlcap) + snd_soc_hdac_bus_map_codec_to_link(sbus, addr); + return azx_add_codec_device(addr, bus); }
@@ -520,6 +531,8 @@ static int azx_first_init(struct soc_hdac_bus *sbus) if (pci_enable_msi(pci) < 0) hda->msi = 0;
+ snd_soc_hdac_bus_parse_capabilities(sbus); + if (azx_acquire_irq(sbus, 0) < 0) return -EBUSY;
@@ -649,6 +662,15 @@ static int azx_probe(struct pci_dev *pci, if (err < 0) goto out_free;
+ /* check if dsp is there */ + if (sbus->ppcap) { + /* TODO register with dsp lib */ + dev_dbg(bus->dev, "Register dsp\n"); + } + + if (sbus->mlcap) + snd_soc_hdac_bus_get_ml_capablities(sbus); + /*create device for soc dmic*/ err = hda_dmic_device_register(hda); if (err < 0) diff --git a/sound/soc/intel/skylake/hda-skl.h b/sound/soc/intel/skylake/hda-skl.h index ed6ccd31b198..7793111e42e5 100644 --- a/sound/soc/intel/skylake/hda-skl.h +++ b/sound/soc/intel/skylake/hda-skl.h @@ -23,6 +23,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/soc.h> #include <sound/hda_register.h> #include <sound/hdaudio.h> #include <sound/soc-hdaudio.h>
participants (3)
-
Mark Brown
-
Takashi Iwai
-
Vinod Koul