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 guard(spinlock_irq)(&bus->reg_lock); 167 snd_hdac_ext_stream_decouple_locked(bus, hext_stream, decouple); 168 } 169 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple); 170 171 /** 172 * snd_hdac_ext_stream_start - start a stream 173 * @hext_stream: HD-audio ext core stream to start 174 */ 175 void snd_hdac_ext_stream_start(struct hdac_ext_stream *hext_stream) 176 { 177 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, 178 AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN); 179 } 180 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start); 181 182 /** 183 * snd_hdac_ext_stream_clear - stop a stream DMA 184 * @hext_stream: HD-audio ext core stream to stop 185 */ 186 void snd_hdac_ext_stream_clear(struct hdac_ext_stream *hext_stream) 187 { 188 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0); 189 } 190 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear); 191 192 /** 193 * snd_hdac_ext_stream_reset - reset a stream 194 * @hext_stream: HD-audio ext core stream to reset 195 */ 196 void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream) 197 { 198 unsigned char val; 199 int timeout; 200 201 snd_hdac_ext_stream_clear(hext_stream); 202 203 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, 204 AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST); 205 udelay(3); 206 timeout = 50; 207 do { 208 val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & 209 AZX_PPLCCTL_STRST; 210 if (val) 211 break; 212 udelay(3); 213 } while (--timeout); 214 val &= ~AZX_PPLCCTL_STRST; 215 writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL); 216 udelay(3); 217 218 timeout = 50; 219 /* waiting for hardware to report that the stream is out of reset */ 220 do { 221 val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST; 222 if (!val) 223 break; 224 udelay(3); 225 } while (--timeout); 226 227 } 228 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset); 229 230 /** 231 * snd_hdac_ext_stream_setup - set up the SD for streaming 232 * @hext_stream: HD-audio ext core stream to set up 233 * @fmt: stream format 234 */ 235 int snd_hdac_ext_stream_setup(struct hdac_ext_stream *hext_stream, int fmt) 236 { 237 struct hdac_stream *hstream = &hext_stream->hstream; 238 unsigned int val; 239 240 /* make sure the run bit is zero for SD */ 241 snd_hdac_ext_stream_clear(hext_stream); 242 /* program the stream_tag */ 243 val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL); 244 val = (val & ~AZX_PPLCCTL_STRM_MASK) | 245 (hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT); 246 writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL); 247 248 /* program the stream format */ 249 writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT); 250 251 return 0; 252 } 253 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup); 254 255 static struct hdac_ext_stream * 256 hdac_ext_link_dma_stream_assign(struct hdac_bus *bus, 257 struct snd_pcm_substream *substream) 258 { 259 struct hdac_ext_stream *res = NULL; 260 struct hdac_stream *hstream = NULL; 261 262 if (!bus->ppcap) { 263 dev_err(bus->dev, "stream type not supported\n"); 264 return NULL; 265 } 266 267 guard(spinlock_irq)(&bus->reg_lock); 268 list_for_each_entry(hstream, &bus->stream_list, list) { 269 struct hdac_ext_stream *hext_stream = container_of(hstream, 270 struct hdac_ext_stream, 271 hstream); 272 if (hstream->direction != substream->stream) 273 continue; 274 275 /* check if link stream is available */ 276 if (!hext_stream->link_locked) { 277 res = hext_stream; 278 break; 279 } 280 281 } 282 if (res) { 283 snd_hdac_ext_stream_decouple_locked(bus, res, true); 284 res->link_locked = 1; 285 res->link_substream = substream; 286 } 287 return res; 288 } 289 290 static struct hdac_ext_stream * 291 hdac_ext_host_dma_stream_assign(struct hdac_bus *bus, 292 struct snd_pcm_substream *substream) 293 { 294 struct hdac_ext_stream *res = NULL; 295 struct hdac_stream *hstream = NULL; 296 297 if (!bus->ppcap) { 298 dev_err(bus->dev, "stream type not supported\n"); 299 return NULL; 300 } 301 302 guard(spinlock_irq)(&bus->reg_lock); 303 list_for_each_entry(hstream, &bus->stream_list, list) { 304 struct hdac_ext_stream *hext_stream = container_of(hstream, 305 struct hdac_ext_stream, 306 hstream); 307 if (hstream->direction != substream->stream) 308 continue; 309 310 if (!hstream->opened) { 311 res = hext_stream; 312 break; 313 } 314 } 315 if (res) { 316 snd_hdac_ext_stream_decouple_locked(bus, res, true); 317 res->hstream.opened = 1; 318 res->hstream.running = 0; 319 res->hstream.substream = substream; 320 } 321 322 return res; 323 } 324 325 /** 326 * snd_hdac_ext_stream_assign - assign a stream for the PCM 327 * @bus: HD-audio core bus 328 * @substream: PCM substream to assign 329 * @type: type of stream (coupled, host or link stream) 330 * 331 * This assigns the stream based on the type (coupled/host/link), for the 332 * given PCM substream, assigns it and returns the stream object 333 * 334 * coupled: Looks for an unused stream 335 * host: Looks for an unused decoupled host stream 336 * link: Looks for an unused decoupled link stream 337 * 338 * If no stream is free, returns NULL. The function tries to keep using 339 * the same stream object when it's used beforehand. when a stream is 340 * decoupled, it becomes a host stream and link stream. 341 */ 342 struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus, 343 struct snd_pcm_substream *substream, 344 int type) 345 { 346 struct hdac_ext_stream *hext_stream = NULL; 347 struct hdac_stream *hstream = NULL; 348 349 switch (type) { 350 case HDAC_EXT_STREAM_TYPE_COUPLED: 351 hstream = snd_hdac_stream_assign(bus, substream); 352 if (hstream) 353 hext_stream = container_of(hstream, 354 struct hdac_ext_stream, 355 hstream); 356 return hext_stream; 357 358 case HDAC_EXT_STREAM_TYPE_HOST: 359 return hdac_ext_host_dma_stream_assign(bus, substream); 360 361 case HDAC_EXT_STREAM_TYPE_LINK: 362 return hdac_ext_link_dma_stream_assign(bus, substream); 363 364 default: 365 return NULL; 366 } 367 } 368 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign); 369 370 /** 371 * snd_hdac_ext_stream_release - release the assigned stream 372 * @hext_stream: HD-audio ext core stream to release 373 * @type: type of stream (coupled, host or link stream) 374 * 375 * Release the stream that has been assigned by snd_hdac_ext_stream_assign(). 376 */ 377 void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type) 378 { 379 struct hdac_bus *bus = hext_stream->hstream.bus; 380 381 switch (type) { 382 case HDAC_EXT_STREAM_TYPE_COUPLED: 383 snd_hdac_stream_release(&hext_stream->hstream); 384 break; 385 386 case HDAC_EXT_STREAM_TYPE_HOST: 387 scoped_guard(spinlock_irq, &bus->reg_lock) { 388 /* couple link only if not in use */ 389 if (!hext_stream->link_locked) 390 snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false); 391 snd_hdac_stream_release_locked(&hext_stream->hstream); 392 } 393 break; 394 395 case HDAC_EXT_STREAM_TYPE_LINK: 396 scoped_guard(spinlock_irq, &bus->reg_lock) { 397 /* couple host only if not in use */ 398 if (!hext_stream->hstream.opened) 399 snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false); 400 hext_stream->link_locked = 0; 401 hext_stream->link_substream = NULL; 402 } 403 break; 404 405 default: 406 dev_dbg(bus->dev, "Invalid type %d\n", type); 407 } 408 409 } 410 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release); 411 412 /** 413 * snd_hdac_ext_cstream_assign - assign a host stream for compress 414 * @bus: HD-audio core bus 415 * @cstream: Compress stream to assign 416 * 417 * Assign an unused host stream for the given compress stream. 418 * If no stream is free, NULL is returned. Stream is decoupled 419 * before assignment. 420 */ 421 struct hdac_ext_stream *snd_hdac_ext_cstream_assign(struct hdac_bus *bus, 422 struct snd_compr_stream *cstream) 423 { 424 struct hdac_ext_stream *res = NULL; 425 struct hdac_stream *hstream; 426 427 guard(spinlock_irq)(&bus->reg_lock); 428 list_for_each_entry(hstream, &bus->stream_list, list) { 429 struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); 430 431 if (hstream->direction != cstream->direction) 432 continue; 433 434 if (!hstream->opened) { 435 res = hext_stream; 436 break; 437 } 438 } 439 440 if (res) { 441 snd_hdac_ext_stream_decouple_locked(bus, res, true); 442 res->hstream.opened = 1; 443 res->hstream.running = 0; 444 res->hstream.cstream = cstream; 445 } 446 447 return res; 448 } 449 EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign); 450