brintos

brintos / linux-shallow public Read only

0
0
Text · 12.5 KiB · a3ac738 Raw
454 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 *  hdac-ext-stream.c - HD-audio extended stream operations.4 *5 *  Copyright (C) 2015 Intel Corp6 *  Author: Jeeja KP <jeeja.kp@intel.com>7 *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~8 *9 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~10 */11 12#include <linux/delay.h>13#include <linux/pci.h>14#include <linux/pci_ids.h>15#include <linux/slab.h>16#include <sound/pcm.h>17#include <sound/hda_register.h>18#include <sound/hdaudio_ext.h>19#include <sound/compress_driver.h>20 21/**22 * snd_hdac_ext_host_stream_setup - Setup a HOST stream.23 * @hext_stream: HDAudio stream to set up.24 * @code_loading: Whether the stream is for PCM or code-loading.25 *26 * Return: Zero on success or negative error code.27 */28int snd_hdac_ext_host_stream_setup(struct hdac_ext_stream *hext_stream, bool code_loading)29{30	return hext_stream->host_setup(hdac_stream(hext_stream), code_loading);31}32EXPORT_SYMBOL_GPL(snd_hdac_ext_host_stream_setup);33 34/**35 * snd_hdac_apl_host_stream_setup - Setup a HOST stream following procedure36 *                                  recommended for ApolloLake devices.37 * @hstream: HDAudio stream to set up.38 * @code_loading: Whether the stream is for PCM or code-loading.39 *40 * Return: Zero on success or negative error code.41 */42static int snd_hdac_apl_host_stream_setup(struct hdac_stream *hstream, bool code_loading)43{44	struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);45	int ret;46 47	snd_hdac_ext_stream_decouple(hstream->bus, hext_stream, false);48	ret = snd_hdac_stream_setup(hstream, code_loading);49	snd_hdac_ext_stream_decouple(hstream->bus, hext_stream, true);50 51	return ret;52}53 54/**55 * snd_hdac_ext_stream_init - initialize each stream (aka device)56 * @bus: HD-audio core bus57 * @hext_stream: HD-audio ext core stream object to initialize58 * @idx: stream index number59 * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE)60 * @tag: the tag id to assign61 *62 * initialize the stream, if ppcap is enabled then init those and then63 * invoke hdac stream initialization routine64 */65static void snd_hdac_ext_stream_init(struct hdac_bus *bus,66				     struct hdac_ext_stream *hext_stream,67				     int idx, int direction, int tag)68{69	if (bus->ppcap) {70		hext_stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE +71				AZX_PPHC_INTERVAL * idx;72 73		hext_stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE +74				AZX_PPLC_MULTI * bus->num_streams +75				AZX_PPLC_INTERVAL * idx;76	}77 78	hext_stream->decoupled = false;79	snd_hdac_stream_init(bus, &hext_stream->hstream, idx, direction, tag);80}81 82/**83 * snd_hdac_ext_stream_init_all - create and initialize the stream objects84 *   for an extended hda bus85 * @bus: HD-audio core bus86 * @start_idx: start index for streams87 * @num_stream: number of streams to initialize88 * @dir: direction of streams89 */90int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx,91				 int num_stream, int dir)92{93	struct pci_dev *pci = to_pci_dev(bus->dev);94	int (*setup_op)(struct hdac_stream *, bool);95	int stream_tag = 0;96	int i, tag, idx = start_idx;97 98	if (pci->device == PCI_DEVICE_ID_INTEL_HDA_APL)99		setup_op = snd_hdac_apl_host_stream_setup;100	else101		setup_op = snd_hdac_stream_setup;102 103	for (i = 0; i < num_stream; i++) {104		struct hdac_ext_stream *hext_stream =105				kzalloc(sizeof(*hext_stream), GFP_KERNEL);106		if (!hext_stream)107			return -ENOMEM;108		tag = ++stream_tag;109		snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag);110		idx++;111		hext_stream->host_setup = setup_op;112	}113 114	return 0;115 116}117EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all);118 119/**120 * snd_hdac_ext_stream_free_all - free hdac extended stream objects121 *122 * @bus: HD-audio core bus123 */124void snd_hdac_ext_stream_free_all(struct hdac_bus *bus)125{126	struct hdac_stream *s, *_s;127	struct hdac_ext_stream *hext_stream;128 129	list_for_each_entry_safe(s, _s, &bus->stream_list, list) {130		hext_stream = stream_to_hdac_ext_stream(s);131		snd_hdac_ext_stream_decouple(bus, hext_stream, false);132		list_del(&s->list);133		kfree(hext_stream);134	}135}136EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all);137 138void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,139					 struct hdac_ext_stream *hext_stream,140					 bool decouple)141{142	struct hdac_stream *hstream = &hext_stream->hstream;143	u32 val;144	int mask = AZX_PPCTL_PROCEN(hstream->index);145 146	val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;147 148	if (decouple && !val)149		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);150	else if (!decouple && val)151		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);152 153	hext_stream->decoupled = decouple;154}155EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);156 157/**158 * snd_hdac_ext_stream_decouple - decouple the hdac stream159 * @bus: HD-audio core bus160 * @hext_stream: HD-audio ext core stream object to initialize161 * @decouple: flag to decouple162 */163void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,164				  struct hdac_ext_stream *hext_stream, bool decouple)165{166	spin_lock_irq(&bus->reg_lock);167	snd_hdac_ext_stream_decouple_locked(bus, hext_stream, decouple);168	spin_unlock_irq(&bus->reg_lock);169}170EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);171 172/**173 * snd_hdac_ext_stream_start - start a stream174 * @hext_stream: HD-audio ext core stream to start175 */176void snd_hdac_ext_stream_start(struct hdac_ext_stream *hext_stream)177{178	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,179			 AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN);180}181EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start);182 183/**184 * snd_hdac_ext_stream_clear - stop a stream DMA185 * @hext_stream: HD-audio ext core stream to stop186 */187void snd_hdac_ext_stream_clear(struct hdac_ext_stream *hext_stream)188{189	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0);190}191EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear);192 193/**194 * snd_hdac_ext_stream_reset - reset a stream195 * @hext_stream: HD-audio ext core stream to reset196 */197void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream)198{199	unsigned char val;200	int timeout;201 202	snd_hdac_ext_stream_clear(hext_stream);203 204	snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,205			 AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST);206	udelay(3);207	timeout = 50;208	do {209		val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) &210				AZX_PPLCCTL_STRST;211		if (val)212			break;213		udelay(3);214	} while (--timeout);215	val &= ~AZX_PPLCCTL_STRST;216	writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);217	udelay(3);218 219	timeout = 50;220	/* waiting for hardware to report that the stream is out of reset */221	do {222		val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST;223		if (!val)224			break;225		udelay(3);226	} while (--timeout);227 228}229EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset);230 231/**232 * snd_hdac_ext_stream_setup -  set up the SD for streaming233 * @hext_stream: HD-audio ext core stream to set up234 * @fmt: stream format235 */236int snd_hdac_ext_stream_setup(struct hdac_ext_stream *hext_stream, int fmt)237{238	struct hdac_stream *hstream = &hext_stream->hstream;239	unsigned int val;240 241	/* make sure the run bit is zero for SD */242	snd_hdac_ext_stream_clear(hext_stream);243	/* program the stream_tag */244	val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL);245	val = (val & ~AZX_PPLCCTL_STRM_MASK) |246		(hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT);247	writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);248 249	/* program the stream format */250	writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT);251 252	return 0;253}254EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup);255 256static struct hdac_ext_stream *257hdac_ext_link_dma_stream_assign(struct hdac_bus *bus,258				struct snd_pcm_substream *substream)259{260	struct hdac_ext_stream *res = NULL;261	struct hdac_stream *hstream = NULL;262 263	if (!bus->ppcap) {264		dev_err(bus->dev, "stream type not supported\n");265		return NULL;266	}267 268	spin_lock_irq(&bus->reg_lock);269	list_for_each_entry(hstream, &bus->stream_list, list) {270		struct hdac_ext_stream *hext_stream = container_of(hstream,271								 struct hdac_ext_stream,272								 hstream);273		if (hstream->direction != substream->stream)274			continue;275 276		/* check if link stream is available */277		if (!hext_stream->link_locked) {278			res = hext_stream;279			break;280		}281 282	}283	if (res) {284		snd_hdac_ext_stream_decouple_locked(bus, res, true);285		res->link_locked = 1;286		res->link_substream = substream;287	}288	spin_unlock_irq(&bus->reg_lock);289	return res;290}291 292static struct hdac_ext_stream *293hdac_ext_host_dma_stream_assign(struct hdac_bus *bus,294				struct snd_pcm_substream *substream)295{296	struct hdac_ext_stream *res = NULL;297	struct hdac_stream *hstream = NULL;298 299	if (!bus->ppcap) {300		dev_err(bus->dev, "stream type not supported\n");301		return NULL;302	}303 304	spin_lock_irq(&bus->reg_lock);305	list_for_each_entry(hstream, &bus->stream_list, list) {306		struct hdac_ext_stream *hext_stream = container_of(hstream,307								 struct hdac_ext_stream,308								 hstream);309		if (hstream->direction != substream->stream)310			continue;311 312		if (!hstream->opened) {313			res = hext_stream;314			break;315		}316	}317	if (res) {318		snd_hdac_ext_stream_decouple_locked(bus, res, true);319		res->hstream.opened = 1;320		res->hstream.running = 0;321		res->hstream.substream = substream;322	}323	spin_unlock_irq(&bus->reg_lock);324 325	return res;326}327 328/**329 * snd_hdac_ext_stream_assign - assign a stream for the PCM330 * @bus: HD-audio core bus331 * @substream: PCM substream to assign332 * @type: type of stream (coupled, host or link stream)333 *334 * This assigns the stream based on the type (coupled/host/link), for the335 * given PCM substream, assigns it and returns the stream object336 *337 * coupled: Looks for an unused stream338 * host: Looks for an unused decoupled host stream339 * link: Looks for an unused decoupled link stream340 *341 * If no stream is free, returns NULL. The function tries to keep using342 * the same stream object when it's used beforehand.  when a stream is343 * decoupled, it becomes a host stream and link stream.344 */345struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,346					   struct snd_pcm_substream *substream,347					   int type)348{349	struct hdac_ext_stream *hext_stream = NULL;350	struct hdac_stream *hstream = NULL;351 352	switch (type) {353	case HDAC_EXT_STREAM_TYPE_COUPLED:354		hstream = snd_hdac_stream_assign(bus, substream);355		if (hstream)356			hext_stream = container_of(hstream,357						   struct hdac_ext_stream,358						   hstream);359		return hext_stream;360 361	case HDAC_EXT_STREAM_TYPE_HOST:362		return hdac_ext_host_dma_stream_assign(bus, substream);363 364	case HDAC_EXT_STREAM_TYPE_LINK:365		return hdac_ext_link_dma_stream_assign(bus, substream);366 367	default:368		return NULL;369	}370}371EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign);372 373/**374 * snd_hdac_ext_stream_release - release the assigned stream375 * @hext_stream: HD-audio ext core stream to release376 * @type: type of stream (coupled, host or link stream)377 *378 * Release the stream that has been assigned by snd_hdac_ext_stream_assign().379 */380void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type)381{382	struct hdac_bus *bus = hext_stream->hstream.bus;383 384	switch (type) {385	case HDAC_EXT_STREAM_TYPE_COUPLED:386		snd_hdac_stream_release(&hext_stream->hstream);387		break;388 389	case HDAC_EXT_STREAM_TYPE_HOST:390		spin_lock_irq(&bus->reg_lock);391		/* couple link only if not in use */392		if (!hext_stream->link_locked)393			snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);394		snd_hdac_stream_release_locked(&hext_stream->hstream);395		spin_unlock_irq(&bus->reg_lock);396		break;397 398	case HDAC_EXT_STREAM_TYPE_LINK:399		spin_lock_irq(&bus->reg_lock);400		/* couple host only if not in use */401		if (!hext_stream->hstream.opened)402			snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);403		hext_stream->link_locked = 0;404		hext_stream->link_substream = NULL;405		spin_unlock_irq(&bus->reg_lock);406		break;407 408	default:409		dev_dbg(bus->dev, "Invalid type %d\n", type);410	}411 412}413EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release);414 415/**416 * snd_hdac_ext_cstream_assign - assign a host stream for compress417 * @bus: HD-audio core bus418 * @cstream: Compress stream to assign419 *420 * Assign an unused host stream for the given compress stream.421 * If no stream is free, NULL is returned. Stream is decoupled422 * before assignment.423 */424struct hdac_ext_stream *snd_hdac_ext_cstream_assign(struct hdac_bus *bus,425						    struct snd_compr_stream *cstream)426{427	struct hdac_ext_stream *res = NULL;428	struct hdac_stream *hstream;429 430	spin_lock_irq(&bus->reg_lock);431	list_for_each_entry(hstream, &bus->stream_list, list) {432		struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);433 434		if (hstream->direction != cstream->direction)435			continue;436 437		if (!hstream->opened) {438			res = hext_stream;439			break;440		}441	}442 443	if (res) {444		snd_hdac_ext_stream_decouple_locked(bus, res, true);445		res->hstream.opened = 1;446		res->hstream.running = 0;447		res->hstream.cstream = cstream;448	}449	spin_unlock_irq(&bus->reg_lock);450 451	return res;452}453EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign);454