1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * hdac-ext-stream.c - HD-audio extended stream operations. 4 * 5 * Copyright (C) 2015 Intel Corp 6 * 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 */ 28 int 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 } 32 EXPORT_SYMBOL_GPL(snd_hdac_ext_host_stream_setup); 33 34 /** 35 * snd_hdac_apl_host_stream_setup - Setup a HOST stream following procedure 36 * 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 */ 42 static 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 bus 57 * @hext_stream: HD-audio ext core stream object to initialize 58 * @idx: stream index number 59 * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE) 60 * @tag: the tag id to assign 61 * 62 * initialize the stream, if ppcap is enabled then init those and then 63 * invoke hdac stream initialization routine 64 */ 65 static 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 objects 84 * for an extended hda bus 85 * @bus: HD-audio core bus 86 * @start_idx: start index for streams 87 * @num_stream: number of streams to initialize 88 * @dir: direction of streams 89 */ 90 int 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 else 101 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 } 117 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); 118 119 /** 120 * snd_hdac_ext_stream_free_all - free hdac extended stream objects 121 * 122 * @bus: HD-audio core bus 123 */ 124 void 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 } 136 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all); 137 138 void 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 } 155 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked); 156 157 /** 158 * snd_hdac_ext_stream_decouple - decouple the hdac stream 159 * @bus: HD-audio core bus 160 * @hext_stream: HD-audio ext core stream object to initialize 161 * @decouple: flag to decouple 162 */ 163 void 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 } 170 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple); 171 172 /** 173 * snd_hdac_ext_stream_start - start a stream 174 * @hext_stream: HD-audio ext core stream to start 175 */ 176 void 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 } 181 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start); 182 183 /** 184 * snd_hdac_ext_stream_clear - stop a stream DMA 185 * @hext_stream: HD-audio ext core stream to stop 186 */ 187 void 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 } 191 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear); 192 193 /** 194 * snd_hdac_ext_stream_reset - reset a stream 195 * @hext_stream: HD-audio ext core stream to reset 196 */ 197 void 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 } 229 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset); 230 231 /** 232 * snd_hdac_ext_stream_setup - set up the SD for streaming 233 * @hext_stream: HD-audio ext core stream to set up 234 * @fmt: stream format 235 */ 236 int 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 } 254 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup); 255 256 static struct hdac_ext_stream * 257 hdac_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 292 static struct hdac_ext_stream * 293 hdac_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 PCM 330 * @bus: HD-audio core bus 331 * @substream: PCM substream to assign 332 * @type: type of stream (coupled, host or link stream) 333 * 334 * This assigns the stream based on the type (coupled/host/link), for the 335 * given PCM substream, assigns it and returns the stream object 336 * 337 * coupled: Looks for an unused stream 338 * host: Looks for an unused decoupled host stream 339 * link: Looks for an unused decoupled link stream 340 * 341 * If no stream is free, returns NULL. The function tries to keep using 342 * the same stream object when it's used beforehand. when a stream is 343 * decoupled, it becomes a host stream and link stream. 344 */ 345 struct 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 } 371 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign); 372 373 /** 374 * snd_hdac_ext_stream_release - release the assigned stream 375 * @hext_stream: HD-audio ext core stream to release 376 * @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 */ 380 void 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 } 413 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release); 414 415 /** 416 * snd_hdac_ext_cstream_assign - assign a host stream for compress 417 * @bus: HD-audio core bus 418 * @cstream: Compress stream to assign 419 * 420 * Assign an unused host stream for the given compress stream. 421 * If no stream is free, NULL is returned. Stream is decoupled 422 * before assignment. 423 */ 424 struct 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 } 453 EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign); 454
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.