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/slab.h> 14 #include <sound/pcm.h> 15 #include <sound/hda_register.h> 16 #include <sound/hdaudio_ext.h> 17 #include <sound/compress_driver.h> 18 19 /** 20 * snd_hdac_ext_stream_init - initialize each stream (aka device) 21 * @bus: HD-audio core bus 22 * @hext_stream: HD-audio ext core stream object to initialize 23 * @idx: stream index number 24 * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE) 25 * @tag: the tag id to assign 26 * 27 * initialize the stream, if ppcap is enabled then init those and then 28 * invoke hdac stream initialization routine 29 */ 30 static void snd_hdac_ext_stream_init(struct hdac_bus *bus, 31 struct hdac_ext_stream *hext_stream, 32 int idx, int direction, int tag) 33 { 34 if (bus->ppcap) { 35 hext_stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE + 36 AZX_PPHC_INTERVAL * idx; 37 38 hext_stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE + 39 AZX_PPLC_MULTI * bus->num_streams + 40 AZX_PPLC_INTERVAL * idx; 41 } 42 43 hext_stream->decoupled = false; 44 snd_hdac_stream_init(bus, &hext_stream->hstream, idx, direction, tag); 45 } 46 47 /** 48 * snd_hdac_ext_stream_init_all - create and initialize the stream objects 49 * for an extended hda bus 50 * @bus: HD-audio core bus 51 * @start_idx: start index for streams 52 * @num_stream: number of streams to initialize 53 * @dir: direction of streams 54 */ 55 int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx, 56 int num_stream, int dir) 57 { 58 int stream_tag = 0; 59 int i, tag, idx = start_idx; 60 61 for (i = 0; i < num_stream; i++) { 62 struct hdac_ext_stream *hext_stream = 63 kzalloc(sizeof(*hext_stream), GFP_KERNEL); 64 if (!hext_stream) 65 return -ENOMEM; 66 tag = ++stream_tag; 67 snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag); 68 idx++; 69 } 70 71 return 0; 72 73 } 74 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all); 75 76 /** 77 * snd_hdac_ext_stream_free_all - free hdac extended stream objects 78 * 79 * @bus: HD-audio core bus 80 */ 81 void snd_hdac_ext_stream_free_all(struct hdac_bus *bus) 82 { 83 struct hdac_stream *s, *_s; 84 struct hdac_ext_stream *hext_stream; 85 86 list_for_each_entry_safe(s, _s, &bus->stream_list, list) { 87 hext_stream = stream_to_hdac_ext_stream(s); 88 snd_hdac_ext_stream_decouple(bus, hext_stream, false); 89 list_del(&s->list); 90 kfree(hext_stream); 91 } 92 } 93 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all); 94 95 void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus, 96 struct hdac_ext_stream *hext_stream, 97 bool decouple) 98 { 99 struct hdac_stream *hstream = &hext_stream->hstream; 100 u32 val; 101 int mask = AZX_PPCTL_PROCEN(hstream->index); 102 103 val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask; 104 105 if (decouple && !val) 106 snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask); 107 else if (!decouple && val) 108 snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0); 109 110 hext_stream->decoupled = decouple; 111 } 112 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked); 113 114 /** 115 * snd_hdac_ext_stream_decouple - decouple the hdac stream 116 * @bus: HD-audio core bus 117 * @hext_stream: HD-audio ext core stream object to initialize 118 * @decouple: flag to decouple 119 */ 120 void snd_hdac_ext_stream_decouple(struct hdac_bus *bus, 121 struct hdac_ext_stream *hext_stream, bool decouple) 122 { 123 spin_lock_irq(&bus->reg_lock); 124 snd_hdac_ext_stream_decouple_locked(bus, hext_stream, decouple); 125 spin_unlock_irq(&bus->reg_lock); 126 } 127 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple); 128 129 /** 130 * snd_hdac_ext_stream_start - start a stream 131 * @hext_stream: HD-audio ext core stream to start 132 */ 133 void snd_hdac_ext_stream_start(struct hdac_ext_stream *hext_stream) 134 { 135 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, 136 AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN); 137 } 138 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start); 139 140 /** 141 * snd_hdac_ext_stream_clear - stop a stream DMA 142 * @hext_stream: HD-audio ext core stream to stop 143 */ 144 void snd_hdac_ext_stream_clear(struct hdac_ext_stream *hext_stream) 145 { 146 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0); 147 } 148 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear); 149 150 /** 151 * snd_hdac_ext_stream_reset - reset a stream 152 * @hext_stream: HD-audio ext core stream to reset 153 */ 154 void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream) 155 { 156 unsigned char val; 157 int timeout; 158 159 snd_hdac_ext_stream_clear(hext_stream); 160 161 snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, 162 AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST); 163 udelay(3); 164 timeout = 50; 165 do { 166 val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & 167 AZX_PPLCCTL_STRST; 168 if (val) 169 break; 170 udelay(3); 171 } while (--timeout); 172 val &= ~AZX_PPLCCTL_STRST; 173 writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL); 174 udelay(3); 175 176 timeout = 50; 177 /* waiting for hardware to report that the stream is out of reset */ 178 do { 179 val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST; 180 if (!val) 181 break; 182 udelay(3); 183 } while (--timeout); 184 185 } 186 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset); 187 188 /** 189 * snd_hdac_ext_stream_setup - set up the SD for streaming 190 * @hext_stream: HD-audio ext core stream to set up 191 * @fmt: stream format 192 */ 193 int snd_hdac_ext_stream_setup(struct hdac_ext_stream *hext_stream, int fmt) 194 { 195 struct hdac_stream *hstream = &hext_stream->hstream; 196 unsigned int val; 197 198 /* make sure the run bit is zero for SD */ 199 snd_hdac_ext_stream_clear(hext_stream); 200 /* program the stream_tag */ 201 val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL); 202 val = (val & ~AZX_PPLCCTL_STRM_MASK) | 203 (hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT); 204 writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL); 205 206 /* program the stream format */ 207 writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT); 208 209 return 0; 210 } 211 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup); 212 213 static struct hdac_ext_stream * 214 hdac_ext_link_dma_stream_assign(struct hdac_bus *bus, 215 struct snd_pcm_substream *substream) 216 { 217 struct hdac_ext_stream *res = NULL; 218 struct hdac_stream *hstream = NULL; 219 220 if (!bus->ppcap) { 221 dev_err(bus->dev, "stream type not supported\n"); 222 return NULL; 223 } 224 225 spin_lock_irq(&bus->reg_lock); 226 list_for_each_entry(hstream, &bus->stream_list, list) { 227 struct hdac_ext_stream *hext_stream = container_of(hstream, 228 struct hdac_ext_stream, 229 hstream); 230 if (hstream->direction != substream->stream) 231 continue; 232 233 /* check if link stream is available */ 234 if (!hext_stream->link_locked) { 235 res = hext_stream; 236 break; 237 } 238 239 } 240 if (res) { 241 snd_hdac_ext_stream_decouple_locked(bus, res, true); 242 res->link_locked = 1; 243 res->link_substream = substream; 244 } 245 spin_unlock_irq(&bus->reg_lock); 246 return res; 247 } 248 249 static struct hdac_ext_stream * 250 hdac_ext_host_dma_stream_assign(struct hdac_bus *bus, 251 struct snd_pcm_substream *substream) 252 { 253 struct hdac_ext_stream *res = NULL; 254 struct hdac_stream *hstream = NULL; 255 256 if (!bus->ppcap) { 257 dev_err(bus->dev, "stream type not supported\n"); 258 return NULL; 259 } 260 261 spin_lock_irq(&bus->reg_lock); 262 list_for_each_entry(hstream, &bus->stream_list, list) { 263 struct hdac_ext_stream *hext_stream = container_of(hstream, 264 struct hdac_ext_stream, 265 hstream); 266 if (hstream->direction != substream->stream) 267 continue; 268 269 if (!hstream->opened) { 270 res = hext_stream; 271 break; 272 } 273 } 274 if (res) { 275 snd_hdac_ext_stream_decouple_locked(bus, res, true); 276 res->hstream.opened = 1; 277 res->hstream.running = 0; 278 res->hstream.substream = substream; 279 } 280 spin_unlock_irq(&bus->reg_lock); 281 282 return res; 283 } 284 285 /** 286 * snd_hdac_ext_stream_assign - assign a stream for the PCM 287 * @bus: HD-audio core bus 288 * @substream: PCM substream to assign 289 * @type: type of stream (coupled, host or link stream) 290 * 291 * This assigns the stream based on the type (coupled/host/link), for the 292 * given PCM substream, assigns it and returns the stream object 293 * 294 * coupled: Looks for an unused stream 295 * host: Looks for an unused decoupled host stream 296 * link: Looks for an unused decoupled link stream 297 * 298 * If no stream is free, returns NULL. The function tries to keep using 299 * the same stream object when it's used beforehand. when a stream is 300 * decoupled, it becomes a host stream and link stream. 301 */ 302 struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus, 303 struct snd_pcm_substream *substream, 304 int type) 305 { 306 struct hdac_ext_stream *hext_stream = NULL; 307 struct hdac_stream *hstream = NULL; 308 309 switch (type) { 310 case HDAC_EXT_STREAM_TYPE_COUPLED: 311 hstream = snd_hdac_stream_assign(bus, substream); 312 if (hstream) 313 hext_stream = container_of(hstream, 314 struct hdac_ext_stream, 315 hstream); 316 return hext_stream; 317 318 case HDAC_EXT_STREAM_TYPE_HOST: 319 return hdac_ext_host_dma_stream_assign(bus, substream); 320 321 case HDAC_EXT_STREAM_TYPE_LINK: 322 return hdac_ext_link_dma_stream_assign(bus, substream); 323 324 default: 325 return NULL; 326 } 327 } 328 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign); 329 330 /** 331 * snd_hdac_ext_stream_release - release the assigned stream 332 * @hext_stream: HD-audio ext core stream to release 333 * @type: type of stream (coupled, host or link stream) 334 * 335 * Release the stream that has been assigned by snd_hdac_ext_stream_assign(). 336 */ 337 void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type) 338 { 339 struct hdac_bus *bus = hext_stream->hstream.bus; 340 341 switch (type) { 342 case HDAC_EXT_STREAM_TYPE_COUPLED: 343 snd_hdac_stream_release(&hext_stream->hstream); 344 break; 345 346 case HDAC_EXT_STREAM_TYPE_HOST: 347 spin_lock_irq(&bus->reg_lock); 348 /* couple link only if not in use */ 349 if (!hext_stream->link_locked) 350 snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false); 351 snd_hdac_stream_release_locked(&hext_stream->hstream); 352 spin_unlock_irq(&bus->reg_lock); 353 break; 354 355 case HDAC_EXT_STREAM_TYPE_LINK: 356 spin_lock_irq(&bus->reg_lock); 357 /* couple host only if not in use */ 358 if (!hext_stream->hstream.opened) 359 snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false); 360 hext_stream->link_locked = 0; 361 hext_stream->link_substream = NULL; 362 spin_unlock_irq(&bus->reg_lock); 363 break; 364 365 default: 366 dev_dbg(bus->dev, "Invalid type %d\n", type); 367 } 368 369 } 370 EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release); 371 372 /** 373 * snd_hdac_ext_cstream_assign - assign a host stream for compress 374 * @bus: HD-audio core bus 375 * @cstream: Compress stream to assign 376 * 377 * Assign an unused host stream for the given compress stream. 378 * If no stream is free, NULL is returned. Stream is decoupled 379 * before assignment. 380 */ 381 struct hdac_ext_stream *snd_hdac_ext_cstream_assign(struct hdac_bus *bus, 382 struct snd_compr_stream *cstream) 383 { 384 struct hdac_ext_stream *res = NULL; 385 struct hdac_stream *hstream; 386 387 spin_lock_irq(&bus->reg_lock); 388 list_for_each_entry(hstream, &bus->stream_list, list) { 389 struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); 390 391 if (hstream->direction != cstream->direction) 392 continue; 393 394 if (!hstream->opened) { 395 res = hext_stream; 396 break; 397 } 398 } 399 400 if (res) { 401 snd_hdac_ext_stream_decouple_locked(bus, res, true); 402 res->hstream.opened = 1; 403 res->hstream.running = 0; 404 res->hstream.cstream = cstream; 405 } 406 spin_unlock_irq(&bus->reg_lock); 407 408 return res; 409 } 410 EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign); 411