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