[alsa-devel] [PATCH v7 0/3] HDA: add extended HDA
New HDA controllers from Intel have extended HDA capabilities like multilink, pipe processing, SPIB, GTS etc In order to use them we create an extended HDA bus, controller and stream which uses the extended configuration
Changes in v7: - Feedback from Takashi - removed the macros for register read/writes - add new macros for reg update - fix bus pointer macro - header cleanup on c/h files - new device name ehdaudioiDn - removed flags for ext caps, use pointer only - moved module info to patch 1 - sparse check on series - remove codec name in link
Jeeja KP (3): ALSA: hdac_ext: add extended HDA bus ALSA: hdac_ext: add hdac extended controller ALSA: hdac_ext: add extended stream capabilities
include/sound/hdaudio_ext.h | 129 ++++++++++++ sound/hda/Kconfig | 4 + sound/hda/Makefile | 3 + sound/hda/ext/Makefile | 3 + sound/hda/ext/hdac_ext_bus.c | 116 +++++++++++ sound/hda/ext/hdac_ext_controller.c | 297 ++++++++++++++++++++++++++ sound/hda/ext/hdac_ext_stream.c | 402 ++++++++++++++++++++++++++++++++++++ 7 files changed, 954 insertions(+) create mode 100644 include/sound/hdaudio_ext.h create mode 100644 sound/hda/ext/Makefile create mode 100644 sound/hda/ext/hdac_ext_bus.c create mode 100644 sound/hda/ext/hdac_ext_controller.c create mode 100644 sound/hda/ext/hdac_ext_stream.c
There is an error in this series. Vinod will resend shortly.
On Wed, Jun 10, 2015 at 05:03:33PM +0530, Vinod Koul wrote:
New HDA controllers from Intel have extended HDA capabilities like multilink, pipe processing, SPIB, GTS etc In order to use them we create an extended HDA bus, controller and stream which uses the extended configuration
Changes in v7:
- Feedback from Takashi
- removed the macros for register read/writes
- add new macros for reg update
- fix bus pointer macro
- header cleanup on c/h files
- new device name ehdaudioiDn
- removed flags for ext caps, use pointer only
- moved module info to patch 1
- sparse check on series
- remove codec name in link
Jeeja KP (3): ALSA: hdac_ext: add extended HDA bus ALSA: hdac_ext: add hdac extended controller ALSA: hdac_ext: add extended stream capabilities
include/sound/hdaudio_ext.h | 129 ++++++++++++ sound/hda/Kconfig | 4 + sound/hda/Makefile | 3 + sound/hda/ext/Makefile | 3 + sound/hda/ext/hdac_ext_bus.c | 116 +++++++++++ sound/hda/ext/hdac_ext_controller.c | 297 ++++++++++++++++++++++++++ sound/hda/ext/hdac_ext_stream.c | 402 ++++++++++++++++++++++++++++++++++++ 7 files changed, 954 insertions(+) create mode 100644 include/sound/hdaudio_ext.h create mode 100644 sound/hda/ext/Makefile create mode 100644 sound/hda/ext/hdac_ext_bus.c create mode 100644 sound/hda/ext/hdac_ext_controller.c create mode 100644 sound/hda/ext/hdac_ext_stream.c
-- 1.9.1
--
From: Jeeja KP jeeja.kp@intel.com
The new HDA controllers from Intel support new capabilities like multilink, pipe processing, SPIB, GTS etc In order to use them we create an extended HDA bus which embed the hdac bus and contains the fields for extended configurations
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/hdaudio_ext.h | 45 +++++++++++++++++ sound/hda/Kconfig | 4 ++ sound/hda/Makefile | 3 ++ sound/hda/ext/Makefile | 3 ++ sound/hda/ext/hdac_ext_bus.c | 116 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+) create mode 100644 include/sound/hdaudio_ext.h create mode 100644 sound/hda/ext/Makefile create mode 100644 sound/hda/ext/hdac_ext_bus.c
diff --git a/include/sound/hdaudio_ext.h b/include/sound/hdaudio_ext.h new file mode 100644 index 000000000000..5b67b367367d --- /dev/null +++ b/include/sound/hdaudio_ext.h @@ -0,0 +1,45 @@ +#ifndef __SOUND_HDAUDIO_EXT_H +#define __SOUND_HDAUDIO_EXT_H + +#include <sound/hdaudio.h> + +/** + * hdac_ext_bus: HDAC extended bus for extended HDA caps + * + * @bus: hdac bus + * @num_streams: streams supported + * @ppcap: pp capabilities pointer + * @spbcap: SPIB capabilities pointer + * @mlcap: MultiLink capabilities pointer + * @gtscap: gts capabilities pointer + * @hlink_list: link list of HDA links + */ +struct hdac_ext_bus { + struct hdac_bus bus; + int num_streams; + + void __iomem *ppcap; + void __iomem *spbcap; + void __iomem *mlcap; + void __iomem *gtscap; + + struct list_head hlink_list; +}; + +int snd_hdac_ext_bus_init(struct hdac_ext_bus *sbus, struct device *dev, + const struct hdac_bus_ops *ops, + const struct hdac_io_ops *io_ops); + +void snd_hdac_ext_bus_exit(struct hdac_ext_bus *sbus); +int snd_hdac_ext_bus_device_init(struct hdac_ext_bus *sbus, int addr); +void snd_hdac_ext_bus_device_exit(struct hdac_device *hdev); + +#define ebus_to_hbus(ebus) (&(ebus)->bus) +#define hbus_to_ebus(_bus) \ + container_of(_bus, struct hdac_ext_bus, bus) + +int snd_hdac_ext_bus_parse_capabilities(struct hdac_ext_bus *sbus); +void snd_hdac_ext_bus_ppcap_enable(struct hdac_ext_bus *chip, bool enable); +void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_ext_bus *chip, bool enable); + +#endif /* __SOUND_HDAUDIO_EXT_H */ diff --git a/sound/hda/Kconfig b/sound/hda/Kconfig index ac5ffac2a272..6dc3914fd28b 100644 --- a/sound/hda/Kconfig +++ b/sound/hda/Kconfig @@ -10,3 +10,7 @@ config SND_HDA_I915 default y depends on DRM_I915 depends on SND_HDA_CORE + +config SND_HDA_EXT_CORE + tristate + select SND_HDA_CORE diff --git a/sound/hda/Makefile b/sound/hda/Makefile index 55dd465c7042..7e999c995cdc 100644 --- a/sound/hda/Makefile +++ b/sound/hda/Makefile @@ -8,3 +8,6 @@ CFLAGS_trace.o := -I$(src) snd-hda-core-$(CONFIG_SND_HDA_I915) += hdac_i915.o
obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o + +#extended hda +obj-$(CONFIG_SND_HDA_EXT_CORE) += ext/ diff --git a/sound/hda/ext/Makefile b/sound/hda/ext/Makefile new file mode 100644 index 000000000000..2e583e664916 --- /dev/null +++ b/sound/hda/ext/Makefile @@ -0,0 +1,3 @@ +snd-hda-ext-core-objs := hdac_ext_bus.o + +obj-$(CONFIG_SND_HDA_EXT_CORE) += snd-hda-ext-core.o diff --git a/sound/hda/ext/hdac_ext_bus.c b/sound/hda/ext/hdac_ext_bus.c new file mode 100644 index 000000000000..905b3e3fd7d8 --- /dev/null +++ b/sound/hda/ext/hdac_ext_bus.c @@ -0,0 +1,116 @@ +/* + * hdac-ext-bus.c - HD-audio extended core bus 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/module.h> +#include <linux/slab.h> +#include <sound/hdaudio_ext.h> + +MODULE_DESCRIPTION("HDA extended core"); +MODULE_LICENSE("GPL v2"); + +/** + * snd_hdac_ext_bus_init - initialize a HD-audio extended bus + * @ebus: the pointer to extended bus object + * @dev: device pointer + * @ops: bus verb operators + * @io_ops: lowlevel I/O operators + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_ext_bus_init(struct hdac_ext_bus *ebus, struct device *dev, + const struct hdac_bus_ops *ops, + const struct hdac_io_ops *io_ops) +{ + int ret; + + ret = snd_hdac_bus_init(&ebus->bus, dev, ops, io_ops); + if (ret < 0) + return ret; + + INIT_LIST_HEAD(&ebus->hlink_list); + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_init); + +/** + * snd_hdac_ext_bus_exit - clean up a HD-audio extended bus + * @ebus: the pointer to extended bus object + */ +void snd_hdac_ext_bus_exit(struct hdac_ext_bus *ebus) +{ + snd_hdac_bus_exit(&ebus->bus); + WARN_ON(!list_empty(&ebus->hlink_list)); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_exit); + +static void default_release(struct device *dev) +{ + snd_hdac_ext_bus_device_exit(container_of(dev, struct hdac_device, dev)); +} + +/** + * snd_hdac_ext_device_init - initialize the HDA extended codec base device + * @ebus: hdac extended bus to attach to + * @addr: codec address + * + * Returns zero for success or a negative error code. + */ +int snd_hdac_ext_bus_device_init(struct hdac_ext_bus *ebus, int addr) +{ + struct hdac_device *hdev = NULL; + struct hdac_bus *bus = ebus_to_hbus(ebus); + static unsigned int idx = 0; + char name[15]; + int ret; + + hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + + snprintf(name, sizeof(name), "ehdaudio%dD%d", idx, addr); + idx++; + + 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; + hdev->dev.release = default_release; + + ret = snd_hdac_device_register(hdev); + if (ret) { + dev_err(bus->dev, "failed to register hdac device\n"); + snd_hdac_ext_bus_device_exit(hdev); + return ret; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_init); + +/** + * snd_hdac_ext_bus_device_exit - clean up a HD-audio extended codec base device + * @hdev: hdac device to clean up + */ +void snd_hdac_ext_bus_device_exit(struct hdac_device *hdev) +{ + snd_hdac_device_exit(hdev); + kfree(hdev); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_exit);
From: Jeeja KP jeeja.kp@intel.com
The controller needs to support the new capabilities and allow reading, parsing and initializing of these capabilities, so this patch does it
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/hdaudio_ext.h | 36 +++++ sound/hda/ext/Makefile | 2 +- sound/hda/ext/hdac_ext_controller.c | 297 ++++++++++++++++++++++++++++++++++++ 3 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_ext_controller.c
diff --git a/include/sound/hdaudio_ext.h b/include/sound/hdaudio_ext.h index 5b67b367367d..5031df7cfe60 100644 --- a/include/sound/hdaudio_ext.h +++ b/include/sound/hdaudio_ext.h @@ -42,4 +42,40 @@ int snd_hdac_ext_bus_parse_capabilities(struct hdac_ext_bus *sbus); void snd_hdac_ext_bus_ppcap_enable(struct hdac_ext_bus *chip, bool enable); void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_ext_bus *chip, bool enable);
+void snd_hdac_ext_stream_spbcap_enable(struct hdac_ext_bus *chip, + bool enable, int index); + +int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_ext_bus *bus); +int snd_hdac_ext_bus_map_codec_to_link(struct hdac_ext_bus *bus, int addr); +struct hdac_ext_link *snd_hdac_ext_bus_get_link(struct hdac_ext_bus *bus, + const char *codec_name); + +enum hdac_ext_stream_type { + HDAC_EXT_STREAM_TYPE_COUPLED = 0, + HDAC_EXT_STREAM_TYPE_HOST, + HDAC_EXT_STREAM_TYPE_LINK +}; + +struct hdac_ext_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 */ + u16 codec[HDA_MAX_CODECS]; /* codecs connectes to the link */ + struct list_head list; +}; + +int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *link); +int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *link); +void snd_hdac_ext_link_set_stream_id(struct hdac_ext_link *link, + int stream); +void snd_hdac_ext_link_clear_stream_id(struct hdac_ext_link *link, + int stream); + +/* update register macro */ +#define snd_hdac_updatel(addr, reg, mask, val) \ + writel(((readl(addr + reg) & ~(mask)) | (val)), \ + addr + reg) + #endif /* __SOUND_HDAUDIO_EXT_H */ diff --git a/sound/hda/ext/Makefile b/sound/hda/ext/Makefile index 2e583e664916..934b7bf5d87f 100644 --- a/sound/hda/ext/Makefile +++ b/sound/hda/ext/Makefile @@ -1,3 +1,3 @@ -snd-hda-ext-core-objs := hdac_ext_bus.o +snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o
obj-$(CONFIG_SND_HDA_EXT_CORE) += snd-hda-ext-core.o diff --git a/sound/hda/ext/hdac_ext_controller.c b/sound/hda/ext/hdac_ext_controller.c new file mode 100644 index 000000000000..7ecc2a62ddb3 --- /dev/null +++ b/sound/hda/ext/hdac_ext_controller.c @@ -0,0 +1,297 @@ +/* + * hdac-ext-controller.c - HD-audio extended 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/delay.h> +#include <linux/slab.h> +#include <sound/hda_register.h> +#include <sound/hdaudio_ext.h> + +/* + * maximum HDAC capablities we should parse to avoid endless looping: + * currently we have 4 extended caps, so this is future proof for now. + * extend when this limit is seen meeting in real HW + */ +#define HDAC_MAX_CAPS 10 + +/** + * snd_hdac_ext_bus_parse_capabilities - parse capablity structure + * @ebus: the pointer to extended bus object + * + * Returns 0 if successful, or a negative error code. + */ +int snd_hdac_ext_bus_parse_capabilities(struct hdac_ext_bus *ebus) +{ + unsigned int cur_cap; + unsigned int offset; + struct hdac_bus *bus = &ebus->bus; + unsigned int counter = 0; + + offset = snd_hdac_chip_readl(bus, LLCH); + + if (offset < 0) + return -EIO; + + /* Lets walk the linked capabilities list */ + do { + cur_cap = _snd_hdac_chip_read(l, bus, offset); + + if (cur_cap < 0) + return -EIO; + + dev_dbg(bus->dev, "Capability version: 0x%x\n", + ((cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF)); + + dev_dbg(bus->dev, "HDA capability ID: 0x%x\n", + (cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF); + + switch ((cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF) { + case AZX_ML_CAP_ID: + dev_dbg(bus->dev, "Found ML capability\n"); + ebus->mlcap = bus->remap_addr + offset; + break; + + case AZX_GTS_CAP_ID: + dev_dbg(bus->dev, "Found GTS capability offset=%x\n", offset); + ebus->gtscap = bus->remap_addr + offset; + break; + + case AZX_PP_CAP_ID: + /* PP capability found, the Audio DSP is present */ + dev_dbg(bus->dev, "Found PP capability offset=%x\n", offset); + ebus->ppcap = bus->remap_addr + offset; + break; + + case AZX_SPB_CAP_ID: + /* SPIB capability found, handler function */ + dev_dbg(bus->dev, "Found SPB capability\n"); + ebus->spbcap = bus->remap_addr + offset; + break; + + default: + dev_dbg(bus->dev, "Unknown capability %d\n", cur_cap); + break; + } + + counter++; + + if (counter > HDAC_MAX_CAPS) { + dev_err(bus->dev, "We exceeded HDAC Ext capablities!!!\n"); + break; + } + + /* read the offset of next capabiity */ + offset = cur_cap & AZX_CAP_HDR_NXT_PTR_MASK; + + } while (offset); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_parse_capabilities); + +/* + * processing pipe helpers - these helpers are useful for dealing with HDA + * new capability of processing pipelines + */ + +/** + * snd_hdac_ext_bus_ppcap_enable - enable/disable processing pipe capability + * @ebus: HD-audio extended core bus + * @enable: flag to turn on/off the capability + */ +void snd_hdac_ext_bus_ppcap_enable(struct hdac_ext_bus *ebus, bool enable) +{ + struct hdac_bus *bus = &ebus->bus; + u32 reg; + + if (!ebus->ppcap) { + dev_err(bus->dev, "Address of PP capability is NULL"); + return; + } + + //HACK + + reg = readl(ebus->ppcap + AZX_REG_PP_PPCTL); + //writel(ebus->ppcap + AZX_REG_PP_PPCTL, ((reg & ~0) | AZX_PPCTL_GPROCEN)); + writel(((reg & ~0) | AZX_PPCTL_GPROCEN), ebus->ppcap + AZX_REG_PP_PPCTL); + + if (enable) + snd_hdac_updatel(ebus->ppcap, AZX_REG_PP_PPCTL, 0, AZX_PPCTL_GPROCEN); + else + snd_hdac_updatel(ebus->ppcap, AZX_REG_PP_PPCTL, AZX_PPCTL_GPROCEN, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_enable); + +/** + * snd_hdac_ext_bus_ppcap_int_enable - ppcap interrupt enable/disable + * @ebus: HD-audio extended core bus + * @enable: flag to enable/disable interrupt + */ +void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_ext_bus *ebus, bool enable) +{ + struct hdac_bus *bus = &ebus->bus; + + if (!ebus->ppcap) { + dev_err(bus->dev, "Address of PP capability is NULL\n"); + return; + } + + if (enable) + snd_hdac_updatel(ebus->ppcap, AZX_REG_PP_PPCTL, 0, AZX_PPCTL_PIE); + else + snd_hdac_updatel(ebus->ppcap, AZX_REG_PP_PPCTL, AZX_PPCTL_PIE, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_int_enable); + +/* + * Multilink helpers - these helpers are useful for dealing with HDA + * new multilink capability + */ + +/** + * snd_hdac_ext_bus_get_ml_capabilities - get multilink capability + * @ebus: HD-audio extended core bus + * + * This will parse all links and read the mlink capabilities and add them + * in hlink_list of extended hdac bus + * Note: this will be freed on bus exit by driver + */ +int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_ext_bus *ebus) +{ + int idx; + u32 link_count; + struct hdac_ext_link *hlink; + struct hdac_bus *bus = &ebus->bus; + + link_count = readl(ebus->mlcap + AZX_REG_ML_MLCD) + 1; + + dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count); + + for (idx = 0; idx < link_count; idx++) { + hlink = kzalloc(sizeof(*hlink), GFP_KERNEL); + if (!hlink) + return -ENOMEM; + hlink->index = idx; + hlink->bus = bus; + hlink->ml_addr = ebus->mlcap + AZX_ML_BASE + + (AZX_ML_INTERVAL * idx); + hlink->lcaps = snd_hdac_chip_readl(bus, ML_LCAP); + hlink->lsdiid = snd_hdac_chip_readw(bus, ML_LSDIID); + + list_add_tail(&hlink->list, &ebus->hlink_list); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_ml_capabilities); + +/** + * snd_hdac_ext_bus_map_codec_to_link - maps codec to link + * @ebus: HD-audio extended core bus + * @addr: codec address + */ +int snd_hdac_ext_bus_map_codec_to_link(struct hdac_ext_bus *ebus, int addr) +{ + struct hdac_ext_link *hlink; + struct hdac_bus *bus = &ebus->bus; + + list_for_each_entry(hlink, &ebus->hlink_list, list) { + /* check if SDI bit number is same as 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)) { + hlink->codec[addr] = addr; + break; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_map_codec_to_link); + +/** + * snd_hdac_ext_bus_get_link_index - get link based on codec name + * @ebus: HD-audio extended core bus + * @codec_name: codec name + */ +struct hdac_ext_link *snd_hdac_ext_bus_get_link(struct hdac_ext_bus *ebus, + const char *codec_name) +{ + int i; + struct hdac_ext_link *hlink = NULL; + char name[32]; + + list_for_each_entry(hlink, &ebus->hlink_list, list) { + for (i = 0; i < HDA_MAX_CODECS; i++) { + snprintf(name, sizeof(name), "codec#%03x", hlink->codec[i]); + if (!strncmp(name, codec_name, strlen(codec_name))) + return hlink; + } + } + return NULL; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_link); + +static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable) +{ + int timeout; + u32 val; + int mask = (1 << AZX_MLCTL_CPA); + + udelay(3); + timeout = 50; + + do { + val = snd_hdac_chip_readl(link->bus, ML_LCTL); + if (enable) { + if (((val & mask) >> AZX_MLCTL_CPA)) + return 0; + } else { + if (!((val & mask) >> AZX_MLCTL_CPA)) + return 0; + } + udelay(3); + } while (--timeout); + + return -EIO; +} + +/** + * snd_hdac_ext_bus_link_power_up -power up hda link + * @link: HD-audio extended link + */ +int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *link) +{ + snd_hdac_chip_updatel(link->bus, ML_LCTL, 0, AZX_MLCTL_SPA); + + return check_hdac_link_power_active(link, true); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up); + +/** + * snd_hdac_ext_bus_link_power_down -power down hda link + * @link: HD-audio extended link + */ +int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *link) +{ + snd_hdac_chip_updatel(link->bus, ML_LCTL, AZX_MLCTL_SPA, 0); + + return check_hdac_link_power_active(link, false); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down);
From: Jeeja KP jeeja.kp@intel.com
Now we have the bus and controller code added to find and initialize the extended capabilities. Now we need to use them in stream code to decouple stream, manage links etc
So this patch adds the stream handling code for extended capabilities introduced in preceding patches
Signed-off-by: Jeeja KP jeeja.kp@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/hdaudio_ext.h | 48 +++++ sound/hda/ext/Makefile | 2 +- sound/hda/ext/hdac_ext_stream.c | 402 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 sound/hda/ext/hdac_ext_stream.c
diff --git a/include/sound/hdaudio_ext.h b/include/sound/hdaudio_ext.h index 5031df7cfe60..b5341ca50e4e 100644 --- a/include/sound/hdaudio_ext.h +++ b/include/sound/hdaudio_ext.h @@ -56,6 +56,50 @@ enum hdac_ext_stream_type { HDAC_EXT_STREAM_TYPE_LINK };
+/** + * hdac_ext_stream: HDAC extended stream for extended HDA caps + * + * @hstream: hdac_stream + * @pphc_addr: processing pipe host stream pointer + * @pplc_addr: processing pipe link stream pointer + * @decoupled: stream host and link is decoupled + * @link_locked: link is locked + * @link_prepared: link is prepared + * link_substream: link substream + */ +struct hdac_ext_stream { + struct hdac_stream hstream; + + void __iomem *pphc_addr; + void __iomem *pplc_addr; + + bool decoupled:1; + bool link_locked:1; + bool link_prepared; + + struct snd_pcm_substream *link_substream; +}; + +#define hdac_stream(s) (&(s)->hstream) +#define stream_to_hdac_ext_stream(s) \ + container_of(s, struct hdac_ext_stream, hstream) + +void snd_hdac_ext_stream_init(struct hdac_ext_bus *bus, + struct hdac_ext_stream *stream, int idx, + int direction, int tag); +struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_ext_bus *bus, + struct snd_pcm_substream *substream, + int type); +void snd_hdac_ext_stream_release(struct hdac_ext_stream *azx_dev, int type); +void snd_hdac_ext_stream_decouple(struct hdac_ext_bus *bus, + struct hdac_ext_stream *azx_dev, bool decouple); +void snd_hdac_ext_stop_streams(struct hdac_ext_bus *sbus); + +void snd_hdac_ext_link_stream_start(struct hdac_ext_stream *hstream); +void snd_hdac_ext_link_stream_clear(struct hdac_ext_stream *hstream); +void snd_hdac_ext_link_stream_reset(struct hdac_ext_stream *hstream); +int snd_hdac_ext_link_stream_setup(struct hdac_ext_stream *stream, int fmt); + struct hdac_ext_link { struct hdac_bus *bus; int index; @@ -78,4 +122,8 @@ void snd_hdac_ext_link_clear_stream_id(struct hdac_ext_link *link, writel(((readl(addr + reg) & ~(mask)) | (val)), \ addr + reg)
+#define snd_hdac_updatew(addr, reg, mask, val) \ + writew(((readw(addr + reg) & ~(mask)) | (val)), \ + addr + reg) + #endif /* __SOUND_HDAUDIO_EXT_H */ diff --git a/sound/hda/ext/Makefile b/sound/hda/ext/Makefile index 934b7bf5d87f..9b6f641c7777 100644 --- a/sound/hda/ext/Makefile +++ b/sound/hda/ext/Makefile @@ -1,3 +1,3 @@ -snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o +snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o hdac_ext_stream.o
obj-$(CONFIG_SND_HDA_EXT_CORE) += snd-hda-ext-core.o diff --git a/sound/hda/ext/hdac_ext_stream.c b/sound/hda/ext/hdac_ext_stream.c new file mode 100644 index 000000000000..ce604e14f877 --- /dev/null +++ b/sound/hda/ext/hdac_ext_stream.c @@ -0,0 +1,402 @@ +/* + * hdac-ext-stream.c - HD-audio extended 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/delay.h> +#include <sound/pcm.h> +#include <sound/hda_register.h> +#include <sound/hdaudio_ext.h> + +/** + * snd_hdac_ext_stream_init - initialize each stream (aka device) + * @ebus: HD-audio ext core bus + * @stream: HD-audio ext 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 + * + * initialize the stream, if ppcap is enabled then init those and then + * invoke hdac stream initialization routine + */ +void snd_hdac_ext_stream_init(struct hdac_ext_bus *ebus, + struct hdac_ext_stream *stream, + int idx, int direction, int tag) +{ + struct hdac_bus *bus = &ebus->bus; + + if (ebus->ppcap) { + stream->pphc_addr = ebus->ppcap+ AZX_PPHC_BASE + + AZX_PPHC_INTERVAL * idx; + + stream->pplc_addr = ebus->ppcap + AZX_PPLC_BASE + + AZX_PPLC_MULTI * ebus->num_streams + + AZX_PPLC_INTERVAL * idx; + } + + stream->decoupled = false; + snd_hdac_stream_init(bus, &stream->hstream, idx, direction, tag); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init); + +/** + * snd_hdac_ext_stream_decouple - decouple the hdac stream + * @ebus: HD-audio ext core bus + * @stream: HD-audio ext core stream object to initialize + * @decouple: flag to decouple + */ +void snd_hdac_ext_stream_decouple(struct hdac_ext_bus *ebus, + struct hdac_ext_stream *stream, bool decouple) +{ + struct hdac_stream *hstream = &stream->hstream; + struct hdac_bus *bus = &ebus->bus; + + spin_lock_irq(&bus->reg_lock); + if (decouple) + snd_hdac_updatel(ebus->ppcap, AZX_REG_PP_PPCTL, 0, + AZX_PPCTL_PROCEN(hstream->index)); + else + snd_hdac_updatel(ebus->ppcap, AZX_REG_PP_PPCTL, + AZX_PPCTL_PROCEN(hstream->index), 0); + stream->decoupled = decouple; + spin_unlock_irq(&bus->reg_lock); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple); + +/** + * snd_hdac_ext_linkstream_start - start a stream + * @stream: HD-audio ext core stream to start + */ +void snd_hdac_ext_link_stream_start(struct hdac_ext_stream *stream) +{ + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, 0, AZX_PPLCCTL_RUN); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_start); + +/** + * snd_hdac_ext_link_stream_clear - stop a stream DMA + * @stream: HD-audio ext core stream to stop + */ +void snd_hdac_ext_link_stream_clear(struct hdac_ext_stream *stream) +{ + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_clear); + +/** + * snd_hdac_ext_link_stream_reset - reset a stream + * @stream: HD-audio ext core stream to reset + */ +void snd_hdac_ext_link_stream_reset(struct hdac_ext_stream *stream) +{ + unsigned char val; + int timeout; + + snd_hdac_ext_link_stream_clear(stream); + + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, 0, AZX_PPLCCTL_STRST); + udelay(3); + timeout = 50; + do { + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL) & + AZX_PPLCCTL_STRST; + if (val) + break; + udelay(3); + } while (--timeout); + val &= ~AZX_PPLCCTL_STRST; + writel(val, stream->pplc_addr + AZX_REG_PPLCCTL); + udelay(3); + + timeout = 50; + /* waiting for hardware to report that the stream is out of reset */ + do { + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST; + if (!val) + break; + udelay(3); + } while (--timeout); + +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_reset); + +/** + * snd_hdac_ext_link_stream_setup - set up the SD for streaming + * @stream: HD-audio ext core stream to set up + * @fmt: stream format + */ +int snd_hdac_ext_link_stream_setup(struct hdac_ext_stream *stream, int fmt) +{ + struct hdac_stream *hstream = &stream->hstream; + unsigned int val; + + /* make sure the run bit is zero for SD */ + snd_hdac_ext_link_stream_clear(stream); + /* program the stream_tag */ + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL); + val = (val & ~AZX_PPLCCTL_STRM_MASK) | + (hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT); + writel(val, stream->pplc_addr + AZX_REG_PPLCCTL); + + /* program the stream format */ + writew(fmt, stream->pplc_addr + AZX_REG_PPLCFMT); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_setup); + +/** + * snd_hdac_ext_link_set_stream_id - maps stream id to link output + * @link: HD-audio ext link to set up + * @stream: stream id + */ +void snd_hdac_ext_link_set_stream_id(struct hdac_ext_link *link, + int stream) +{ + snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_set_stream_id); + +/** + * snd_hdac_ext_link_clear_stream_id - maps stream id to link output + * @link: HD-audio ext link to set up + * @stream: stream id + */ +void snd_hdac_ext_link_clear_stream_id(struct hdac_ext_link *link, + int stream) +{ + snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, 0, (1 << stream)); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_link_clear_stream_id); + +static struct hdac_ext_stream * +hdac_ext_link_stream_assign(struct hdac_ext_bus *ebus, + struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *res = NULL; + struct hdac_stream *stream = NULL; + struct hdac_bus *hbus = &ebus->bus; + + if (!ebus->ppcap) { + dev_err(hbus->dev, "stream type not supported\n"); + return NULL; + } + + list_for_each_entry(stream, &hbus->stream_list, list) { + struct hdac_ext_stream *hstream = container_of(stream, + struct hdac_ext_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_hdac_ext_stream_decouple(ebus, 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 hdac_ext_stream * +hdac_ext_host_stream_assign(struct hdac_ext_bus *ebus, + struct snd_pcm_substream *substream) +{ + struct hdac_ext_stream *res = NULL; + struct hdac_stream *stream = NULL; + struct hdac_bus *hbus = &ebus->bus; + int key; + + if (!ebus->ppcap) { + dev_err(hbus->dev, "stream type not supported\n"); + return NULL; + } + + /* make a non-zero unique key for the substream */ + key = (substream->pcm->device << 16) | (substream->number << 2) | + (substream->stream + 1); + + list_for_each_entry(stream, &hbus->stream_list, list) { + struct hdac_ext_stream *hstream = container_of(stream, + struct hdac_ext_stream, + hstream); + if (stream->direction != substream->stream) + continue; + + if (stream->opened) { + if (!hstream->decoupled) + snd_hdac_ext_stream_decouple(ebus, 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_hdac_ext_stream_assign - assign a stream for the PCM + * @ebus: HD-audio ext core bus + * @substream: PCM substream to assign + * @type: type of stream (coupled, host or link stream) + * + * This assigns the stream based on the type (coupled/host/link), for the + * given PCM substream, assigns it and returns the stream object + * + * coupled: Looks for an unused stream + * host: Looks for an unused decoupled host stream + * link: Looks for an unused decoupled link stream + * + * 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 hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_ext_bus *ebus, + struct snd_pcm_substream *substream, + int type) +{ + struct hdac_ext_stream *hstream = NULL; + struct hdac_stream *stream = NULL; + struct hdac_bus *hbus = &ebus->bus; + + switch (type) { + case HDAC_EXT_STREAM_TYPE_COUPLED: + stream = snd_hdac_stream_assign(hbus, substream); + if (stream) + hstream = container_of(stream, + struct hdac_ext_stream, hstream); + return hstream; + + case HDAC_EXT_STREAM_TYPE_HOST: + return hdac_ext_host_stream_assign(ebus, substream); + + case HDAC_EXT_STREAM_TYPE_LINK: + return hdac_ext_link_stream_assign(ebus, substream); + + default: + return NULL; + } +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign); + +/** + * snd_hdac_ext_stream_release - release the assigned stream + * @stream: HD-audio ext core stream to release + * @type: type of stream (coupled, host or link stream) + * + * Release the stream that has been assigned by snd_hdac_ext_stream_assign(). + */ +void snd_hdac_ext_stream_release(struct hdac_ext_stream *stream, int type) +{ + struct hdac_bus *bus = stream->hstream.bus; + struct hdac_ext_bus *ebus = hbus_to_ebus(bus); + + switch (type) { + case HDAC_EXT_STREAM_TYPE_COUPLED: + snd_hdac_stream_release(&stream->hstream); + break; + + case HDAC_EXT_STREAM_TYPE_HOST: + if (stream->decoupled) { + snd_hdac_ext_stream_decouple(ebus, stream, false); + snd_hdac_stream_release(&stream->hstream); + } + break; + + case HDAC_EXT_STREAM_TYPE_LINK: + if (stream->decoupled) + snd_hdac_ext_stream_decouple(ebus, stream, false); + spin_lock_irq(&bus->reg_lock); + stream->link_locked = 0; + stream->link_substream = NULL; + spin_unlock_irq(&bus->reg_lock); + break; + + default: + dev_dbg(bus->dev, "Invalid type %d\n", type); + } + +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release); + +/** + * snd_hdac_ext_stream_spbcap_enable - enable SPIB for a stream + * @ebus: HD-audio ext core bus + * @enable: flag to enable/disable SPIB + * @index: stream index for which SPIB need to be enabled + */ +void snd_hdac_ext_stream_spbcap_enable(struct hdac_ext_bus *ebus, + bool enable, int index) +{ + u32 mask = 0; + u32 register_mask = 0; + struct hdac_bus *bus = &ebus->bus; + + if (!ebus->spbcap) { + dev_err(bus->dev, "Address of SPB capability is NULL"); + return; + } + + mask |= (1 << index); + + register_mask = snd_hdac_chip_readl(bus, SPB_SPBFCCTL); + + mask |= register_mask; + + if (enable) + snd_hdac_updatel(ebus->spbcap, AZX_REG_SPB_SPBFCCTL, 0, mask); + else + snd_hdac_updatel(ebus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, 0); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_spbcap_enable); + +/** + * snd_hdac_ext_stop_streams - stop all stream if running + * @ebus: HD-audio ext core bus + */ +void snd_hdac_ext_stop_streams(struct hdac_ext_bus *ebus) +{ + struct hdac_bus *bus = ebus_to_hbus(ebus); + 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_hdac_ext_stop_streams);
participants (2)
-
Subhransu S. Prusty
-
Vinod Koul