diff options
Diffstat (limited to 'kernel/sound/hda/ext')
-rw-r--r-- | kernel/sound/hda/ext/Makefile | 3 | ||||
-rw-r--r-- | kernel/sound/hda/ext/hdac_ext_bus.c | 265 | ||||
-rw-r--r-- | kernel/sound/hda/ext/hdac_ext_controller.c | 302 | ||||
-rw-r--r-- | kernel/sound/hda/ext/hdac_ext_stream.c | 499 |
4 files changed, 1069 insertions, 0 deletions
diff --git a/kernel/sound/hda/ext/Makefile b/kernel/sound/hda/ext/Makefile new file mode 100644 index 000000000..9b6f641c7 --- /dev/null +++ b/kernel/sound/hda/ext/Makefile @@ -0,0 +1,3 @@ +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/kernel/sound/hda/ext/hdac_ext_bus.c b/kernel/sound/hda/ext/hdac_ext_bus.c new file mode 100644 index 000000000..2433f7c81 --- /dev/null +++ b/kernel/sound/hda/ext/hdac_ext_bus.c @@ -0,0 +1,265 @@ +/* + * 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 <linux/io.h> +#include <sound/hdaudio_ext.h> + +MODULE_DESCRIPTION("HDA extended core"); +MODULE_LICENSE("GPL v2"); + +static void hdac_ext_writel(u32 value, u32 __iomem *addr) +{ + writel(value, addr); +} + +static u32 hdac_ext_readl(u32 __iomem *addr) +{ + return readl(addr); +} + +static void hdac_ext_writew(u16 value, u16 __iomem *addr) +{ + writew(value, addr); +} + +static u16 hdac_ext_readw(u16 __iomem *addr) +{ + return readw(addr); +} + +static void hdac_ext_writeb(u8 value, u8 __iomem *addr) +{ + writeb(value, addr); +} + +static u8 hdac_ext_readb(u8 __iomem *addr) +{ + return readb(addr); +} + +static int hdac_ext_dma_alloc_pages(struct hdac_bus *bus, int type, + size_t size, struct snd_dma_buffer *buf) +{ + return snd_dma_alloc_pages(type, bus->dev, size, buf); +} + +static void hdac_ext_dma_free_pages(struct hdac_bus *bus, struct snd_dma_buffer *buf) +{ + snd_dma_free_pages(buf); +} + +static const struct hdac_io_ops hdac_ext_default_io = { + .reg_writel = hdac_ext_writel, + .reg_readl = hdac_ext_readl, + .reg_writew = hdac_ext_writew, + .reg_readw = hdac_ext_readw, + .reg_writeb = hdac_ext_writeb, + .reg_readb = hdac_ext_readb, + .dma_alloc_pages = hdac_ext_dma_alloc_pages, + .dma_free_pages = hdac_ext_dma_free_pages, +}; + +/** + * 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, can be NULL. If NULL core will use + * default ops + * + * 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; + static int idx; + + /* check if io ops are provided, if not load the defaults */ + if (io_ops == NULL) + io_ops = &hdac_ext_default_io; + + ret = snd_hdac_bus_init(&ebus->bus, dev, ops, io_ops); + if (ret < 0) + return ret; + + INIT_LIST_HEAD(&ebus->hlink_list); + ebus->idx = idx++; + + 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_bus_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_ext_device *edev; + struct hdac_device *hdev = NULL; + struct hdac_bus *bus = ebus_to_hbus(ebus); + char name[15]; + int ret; + + edev = kzalloc(sizeof(*edev), GFP_KERNEL); + if (!edev) + return -ENOMEM; + hdev = &edev->hdac; + + snprintf(name, sizeof(name), "ehdaudio%dD%d", ebus->idx, 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; + 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) +{ + struct hdac_ext_device *edev = to_ehdac_device(hdev); + + snd_hdac_device_exit(hdev); + kfree(edev); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_exit); + +/** + * snd_hdac_ext_bus_device_remove - remove HD-audio extended codec base devices + * + * @ebus: HD-audio extended bus + */ +void snd_hdac_ext_bus_device_remove(struct hdac_ext_bus *ebus) +{ + struct hdac_device *codec, *__codec; + /* + * we need to remove all the codec devices objects created in the + * snd_hdac_ext_bus_device_init + */ + list_for_each_entry_safe(codec, __codec, &ebus->bus.codec_list, list) { + snd_hdac_device_unregister(codec); + put_device(&codec->dev); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_remove); +#define dev_to_hdac(dev) (container_of((dev), \ + struct hdac_device, dev)) + +static inline struct hdac_ext_driver *get_edrv(struct device *dev) +{ + struct hdac_driver *hdrv = drv_to_hdac_driver(dev->driver); + struct hdac_ext_driver *edrv = to_ehdac_driver(hdrv); + + return edrv; +} + +static inline struct hdac_ext_device *get_edev(struct device *dev) +{ + struct hdac_device *hdev = dev_to_hdac_dev(dev); + struct hdac_ext_device *edev = to_ehdac_device(hdev); + + return edev; +} + +static int hda_ext_drv_probe(struct device *dev) +{ + return (get_edrv(dev))->probe(get_edev(dev)); +} + +static int hdac_ext_drv_remove(struct device *dev) +{ + return (get_edrv(dev))->remove(get_edev(dev)); +} + +static void hdac_ext_drv_shutdown(struct device *dev) +{ + return (get_edrv(dev))->shutdown(get_edev(dev)); +} + +/** + * snd_hda_ext_driver_register - register a driver for ext hda devices + * + * @drv: ext hda driver structure + */ +int snd_hda_ext_driver_register(struct hdac_ext_driver *drv) +{ + drv->hdac.type = HDA_DEV_ASOC; + drv->hdac.driver.bus = &snd_hda_bus_type; + /* we use default match */ + + if (drv->probe) + drv->hdac.driver.probe = hda_ext_drv_probe; + if (drv->remove) + drv->hdac.driver.remove = hdac_ext_drv_remove; + if (drv->shutdown) + drv->hdac.driver.shutdown = hdac_ext_drv_shutdown; + + return driver_register(&drv->hdac.driver); +} +EXPORT_SYMBOL_GPL(snd_hda_ext_driver_register); + +/** + * snd_hda_ext_driver_unregister - unregister a driver for ext hda devices + * + * @drv: ext hda driver structure + */ +void snd_hda_ext_driver_unregister(struct hdac_ext_driver *drv) +{ + driver_unregister(&drv->hdac.driver); +} +EXPORT_SYMBOL_GPL(snd_hda_ext_driver_unregister); diff --git a/kernel/sound/hda/ext/hdac_ext_controller.c b/kernel/sound/hda/ext/hdac_ext_controller.c new file mode 100644 index 000000000..63215b172 --- /dev/null +++ b/kernel/sound/hda/ext/hdac_ext_controller.c @@ -0,0 +1,302 @@ +/* + * 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); + + /* Lets walk the linked capabilities list */ + do { + cur_cap = _snd_hdac_chip_read(l, bus, offset); + + 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; + + if (!ebus->ppcap) { + dev_err(bus->dev, "Address of PP capability is NULL"); + return; + } + + 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 = readl(hlink->ml_addr + AZX_REG_ML_LCAP); + hlink->lsdiid = readw(hlink->ml_addr + AZX_REG_ML_LSDIID); + + list_add_tail(&hlink->list, &ebus->hlink_list); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_ml_capabilities); + +/** + * snd_hdac_link_free_all- free hdac extended link objects + * + * @ebus: HD-audio ext core bus + */ + +void snd_hdac_link_free_all(struct hdac_ext_bus *ebus) +{ + struct hdac_ext_link *l; + + while (!list_empty(&ebus->hlink_list)) { + l = list_first_entry(&ebus->hlink_list, struct hdac_ext_link, list); + list_del(&l->list); + kfree(l); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_link_free_all); + +/** + * 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; + int bus_idx, addr; + + if (sscanf(codec_name, "ehdaudio%dD%d", &bus_idx, &addr) != 2) + return NULL; + if (ebus->idx != bus_idx) + return NULL; + + list_for_each_entry(hlink, &ebus->hlink_list, list) { + for (i = 0; i < HDA_MAX_CODECS; i++) { + if (hlink->lsdiid & (0x1 << addr)) + 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 = readl(link->ml_addr + AZX_REG_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_updatel(link->ml_addr, AZX_REG_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_updatel(link->ml_addr, AZX_REG_ML_LCTL, AZX_MLCTL_SPA, 0); + + return check_hdac_link_power_active(link, false); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down); + +/** + * snd_hdac_ext_bus_link_power_down_all -power down all hda link + * @ebus: HD-audio extended bus + */ +int snd_hdac_ext_bus_link_power_down_all(struct hdac_ext_bus *ebus) +{ + struct hdac_ext_link *hlink = NULL; + int ret; + + list_for_each_entry(hlink, &ebus->hlink_list, list) { + snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL, AZX_MLCTL_SPA, 0); + ret = check_hdac_link_power_active(hlink, false); + if (ret < 0) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down_all); diff --git a/kernel/sound/hda/ext/hdac_ext_stream.c b/kernel/sound/hda/ext/hdac_ext_stream.c new file mode 100644 index 000000000..cb89ec7c8 --- /dev/null +++ b/kernel/sound/hda/ext/hdac_ext_stream.c @@ -0,0 +1,499 @@ +/* + * 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 <linux/slab.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; + } + + if (ebus->spbcap) { + stream->spib_addr = ebus->spbcap + AZX_SPB_BASE + + AZX_SPB_INTERVAL * idx + + AZX_SPB_SPIB; + + stream->fifo_addr = ebus->spbcap + AZX_SPB_BASE + + AZX_SPB_INTERVAL * idx + + AZX_SPB_MAXFIFO; + } + + 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_init_all - create and initialize the stream objects + * for an extended hda bus + * @ebus: HD-audio ext core bus + * @start_idx: start index for streams + * @num_stream: number of streams to initialize + * @dir: direction of streams + */ +int snd_hdac_ext_stream_init_all(struct hdac_ext_bus *ebus, int start_idx, + int num_stream, int dir) +{ + int stream_tag = 0; + int i, tag, idx = start_idx; + + for (i = 0; i < num_stream; i++) { + struct hdac_ext_stream *stream = + kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + tag = ++stream_tag; + snd_hdac_ext_stream_init(ebus, stream, idx, dir, tag); + idx++; + } + + return 0; + +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); + +/** + * snd_hdac_stream_free_all - free hdac extended stream objects + * + * @ebus: HD-audio ext core bus + */ +void snd_hdac_stream_free_all(struct hdac_ext_bus *ebus) +{ + struct hdac_stream *s; + struct hdac_ext_stream *stream; + struct hdac_bus *bus = ebus_to_hbus(ebus); + + while (!list_empty(&bus->stream_list)) { + s = list_first_entry(&bus->stream_list, struct hdac_stream, list); + stream = stream_to_hdac_ext_stream(s); + list_del(&s->list); + kfree(stream); + } +} +EXPORT_SYMBOL_GPL(snd_hdac_stream_free_all); + +/** + * 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), 1 << stream); +} +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; + + 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; + + 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.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 && !stream->link_locked) + snd_hdac_ext_stream_decouple(ebus, stream, false); + snd_hdac_stream_release(&stream->hstream); + break; + + case HDAC_EXT_STREAM_TYPE_LINK: + if (stream->decoupled && !stream->hstream.opened) + 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 = readl(ebus->spbcap + AZX_REG_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_stream_set_spib - sets the spib value of a stream + * @ebus: HD-audio ext core bus + * @stream: hdac_ext_stream + * @value: spib value to set + */ +int snd_hdac_ext_stream_set_spib(struct hdac_ext_bus *ebus, + struct hdac_ext_stream *stream, u32 value) +{ + struct hdac_bus *bus = &ebus->bus; + + if (!ebus->spbcap) { + dev_err(bus->dev, "Address of SPB capability is NULL"); + return -EINVAL; + } + + writel(value, stream->spib_addr); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_spib); + +/** + * snd_hdac_ext_stream_get_spbmaxfifo - gets the spib value of a stream + * @ebus: HD-audio ext core bus + * @stream: hdac_ext_stream + * + * Return maxfifo for the stream + */ +int snd_hdac_ext_stream_get_spbmaxfifo(struct hdac_ext_bus *ebus, + struct hdac_ext_stream *stream) +{ + struct hdac_bus *bus = &ebus->bus; + + if (!ebus->spbcap) { + dev_err(bus->dev, "Address of SPB capability is NULL"); + return -EINVAL; + } + + return readl(stream->fifo_addr); +} +EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_get_spbmaxfifo); + + +/** + * 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); |