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