Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ALSA: hdac_ext: add extended stream capabilities

Now we have the bus and controller code added to find and initialize
the extended capabilities. Now we need to use them in stream code to
decouple stream, manage links etc

So this patch adds the stream handling code for extended capabilities
introduced in preceding patches

Signed-off-by: Jeeja KP <jeeja.kp@intel.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

authored by

Jeeja KP and committed by
Takashi Iwai
df203a4e 0b00a561

+451 -1
+48
include/sound/hdaudio_ext.h
··· 57 57 HDAC_EXT_STREAM_TYPE_LINK 58 58 }; 59 59 60 + /** 61 + * hdac_ext_stream: HDAC extended stream for extended HDA caps 62 + * 63 + * @hstream: hdac_stream 64 + * @pphc_addr: processing pipe host stream pointer 65 + * @pplc_addr: processing pipe link stream pointer 66 + * @decoupled: stream host and link is decoupled 67 + * @link_locked: link is locked 68 + * @link_prepared: link is prepared 69 + * link_substream: link substream 70 + */ 71 + struct hdac_ext_stream { 72 + struct hdac_stream hstream; 73 + 74 + void __iomem *pphc_addr; 75 + void __iomem *pplc_addr; 76 + 77 + bool decoupled:1; 78 + bool link_locked:1; 79 + bool link_prepared; 80 + 81 + struct snd_pcm_substream *link_substream; 82 + }; 83 + 84 + #define hdac_stream(s) (&(s)->hstream) 85 + #define stream_to_hdac_ext_stream(s) \ 86 + container_of(s, struct hdac_ext_stream, hstream) 87 + 88 + void snd_hdac_ext_stream_init(struct hdac_ext_bus *bus, 89 + struct hdac_ext_stream *stream, int idx, 90 + int direction, int tag); 91 + struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_ext_bus *bus, 92 + struct snd_pcm_substream *substream, 93 + int type); 94 + void snd_hdac_ext_stream_release(struct hdac_ext_stream *azx_dev, int type); 95 + void snd_hdac_ext_stream_decouple(struct hdac_ext_bus *bus, 96 + struct hdac_ext_stream *azx_dev, bool decouple); 97 + void snd_hdac_ext_stop_streams(struct hdac_ext_bus *sbus); 98 + 99 + void snd_hdac_ext_link_stream_start(struct hdac_ext_stream *hstream); 100 + void snd_hdac_ext_link_stream_clear(struct hdac_ext_stream *hstream); 101 + void snd_hdac_ext_link_stream_reset(struct hdac_ext_stream *hstream); 102 + int snd_hdac_ext_link_stream_setup(struct hdac_ext_stream *stream, int fmt); 103 + 60 104 struct hdac_ext_link { 61 105 struct hdac_bus *bus; 62 106 int index; ··· 120 76 /* update register macro */ 121 77 #define snd_hdac_updatel(addr, reg, mask, val) \ 122 78 writel(((readl(addr + reg) & ~(mask)) | (val)), \ 79 + addr + reg) 80 + 81 + #define snd_hdac_updatew(addr, reg, mask, val) \ 82 + writew(((readw(addr + reg) & ~(mask)) | (val)), \ 123 83 addr + reg) 124 84 125 85 #endif /* __SOUND_HDAUDIO_EXT_H */
+1 -1
sound/hda/ext/Makefile
··· 1 - snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o 1 + snd-hda-ext-core-objs := hdac_ext_bus.o hdac_ext_controller.o hdac_ext_stream.o 2 2 3 3 obj-$(CONFIG_SND_HDA_EXT_CORE) += snd-hda-ext-core.o
+402
sound/hda/ext/hdac_ext_stream.c
··· 1 + /* 2 + * hdac-ext-stream.c - HD-audio extended stream operations. 3 + * 4 + * Copyright (C) 2015 Intel Corp 5 + * Author: Jeeja KP <jeeja.kp@intel.com> 6 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7 + * 8 + * This program is free software; you can redistribute it and/or modify 9 + * it under the terms of the GNU General Public License as published by 10 + * the Free Software Foundation; version 2 of the License. 11 + * 12 + * This program is distributed in the hope that it will be useful, but 13 + * WITHOUT ANY WARRANTY; without even the implied warranty of 14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 + * General Public License for more details. 16 + * 17 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 + */ 19 + 20 + #include <linux/delay.h> 21 + #include <sound/pcm.h> 22 + #include <sound/hda_register.h> 23 + #include <sound/hdaudio_ext.h> 24 + 25 + /** 26 + * snd_hdac_ext_stream_init - initialize each stream (aka device) 27 + * @ebus: HD-audio ext core bus 28 + * @stream: HD-audio ext core stream object to initialize 29 + * @idx: stream index number 30 + * @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE) 31 + * @tag: the tag id to assign 32 + * 33 + * initialize the stream, if ppcap is enabled then init those and then 34 + * invoke hdac stream initialization routine 35 + */ 36 + void snd_hdac_ext_stream_init(struct hdac_ext_bus *ebus, 37 + struct hdac_ext_stream *stream, 38 + int idx, int direction, int tag) 39 + { 40 + struct hdac_bus *bus = &ebus->bus; 41 + 42 + if (ebus->ppcap) { 43 + stream->pphc_addr = ebus->ppcap + AZX_PPHC_BASE + 44 + AZX_PPHC_INTERVAL * idx; 45 + 46 + stream->pplc_addr = ebus->ppcap + AZX_PPLC_BASE + 47 + AZX_PPLC_MULTI * ebus->num_streams + 48 + AZX_PPLC_INTERVAL * idx; 49 + } 50 + 51 + stream->decoupled = false; 52 + snd_hdac_stream_init(bus, &stream->hstream, idx, direction, tag); 53 + } 54 + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init); 55 + 56 + /** 57 + * snd_hdac_ext_stream_decouple - decouple the hdac stream 58 + * @ebus: HD-audio ext core bus 59 + * @stream: HD-audio ext core stream object to initialize 60 + * @decouple: flag to decouple 61 + */ 62 + void snd_hdac_ext_stream_decouple(struct hdac_ext_bus *ebus, 63 + struct hdac_ext_stream *stream, bool decouple) 64 + { 65 + struct hdac_stream *hstream = &stream->hstream; 66 + struct hdac_bus *bus = &ebus->bus; 67 + 68 + spin_lock_irq(&bus->reg_lock); 69 + if (decouple) 70 + snd_hdac_updatel(ebus->ppcap, AZX_REG_PP_PPCTL, 0, 71 + AZX_PPCTL_PROCEN(hstream->index)); 72 + else 73 + snd_hdac_updatel(ebus->ppcap, AZX_REG_PP_PPCTL, 74 + AZX_PPCTL_PROCEN(hstream->index), 0); 75 + stream->decoupled = decouple; 76 + spin_unlock_irq(&bus->reg_lock); 77 + } 78 + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple); 79 + 80 + /** 81 + * snd_hdac_ext_linkstream_start - start a stream 82 + * @stream: HD-audio ext core stream to start 83 + */ 84 + void snd_hdac_ext_link_stream_start(struct hdac_ext_stream *stream) 85 + { 86 + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, 0, AZX_PPLCCTL_RUN); 87 + } 88 + EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_start); 89 + 90 + /** 91 + * snd_hdac_ext_link_stream_clear - stop a stream DMA 92 + * @stream: HD-audio ext core stream to stop 93 + */ 94 + void snd_hdac_ext_link_stream_clear(struct hdac_ext_stream *stream) 95 + { 96 + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0); 97 + } 98 + EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_clear); 99 + 100 + /** 101 + * snd_hdac_ext_link_stream_reset - reset a stream 102 + * @stream: HD-audio ext core stream to reset 103 + */ 104 + void snd_hdac_ext_link_stream_reset(struct hdac_ext_stream *stream) 105 + { 106 + unsigned char val; 107 + int timeout; 108 + 109 + snd_hdac_ext_link_stream_clear(stream); 110 + 111 + snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, 0, AZX_PPLCCTL_STRST); 112 + udelay(3); 113 + timeout = 50; 114 + do { 115 + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL) & 116 + AZX_PPLCCTL_STRST; 117 + if (val) 118 + break; 119 + udelay(3); 120 + } while (--timeout); 121 + val &= ~AZX_PPLCCTL_STRST; 122 + writel(val, stream->pplc_addr + AZX_REG_PPLCCTL); 123 + udelay(3); 124 + 125 + timeout = 50; 126 + /* waiting for hardware to report that the stream is out of reset */ 127 + do { 128 + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST; 129 + if (!val) 130 + break; 131 + udelay(3); 132 + } while (--timeout); 133 + 134 + } 135 + EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_reset); 136 + 137 + /** 138 + * snd_hdac_ext_link_stream_setup - set up the SD for streaming 139 + * @stream: HD-audio ext core stream to set up 140 + * @fmt: stream format 141 + */ 142 + int snd_hdac_ext_link_stream_setup(struct hdac_ext_stream *stream, int fmt) 143 + { 144 + struct hdac_stream *hstream = &stream->hstream; 145 + unsigned int val; 146 + 147 + /* make sure the run bit is zero for SD */ 148 + snd_hdac_ext_link_stream_clear(stream); 149 + /* program the stream_tag */ 150 + val = readl(stream->pplc_addr + AZX_REG_PPLCCTL); 151 + val = (val & ~AZX_PPLCCTL_STRM_MASK) | 152 + (hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT); 153 + writel(val, stream->pplc_addr + AZX_REG_PPLCCTL); 154 + 155 + /* program the stream format */ 156 + writew(fmt, stream->pplc_addr + AZX_REG_PPLCFMT); 157 + 158 + return 0; 159 + } 160 + EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_setup); 161 + 162 + /** 163 + * snd_hdac_ext_link_set_stream_id - maps stream id to link output 164 + * @link: HD-audio ext link to set up 165 + * @stream: stream id 166 + */ 167 + void snd_hdac_ext_link_set_stream_id(struct hdac_ext_link *link, 168 + int stream) 169 + { 170 + snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 0); 171 + } 172 + EXPORT_SYMBOL_GPL(snd_hdac_ext_link_set_stream_id); 173 + 174 + /** 175 + * snd_hdac_ext_link_clear_stream_id - maps stream id to link output 176 + * @link: HD-audio ext link to set up 177 + * @stream: stream id 178 + */ 179 + void snd_hdac_ext_link_clear_stream_id(struct hdac_ext_link *link, 180 + int stream) 181 + { 182 + snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, 0, (1 << stream)); 183 + } 184 + EXPORT_SYMBOL_GPL(snd_hdac_ext_link_clear_stream_id); 185 + 186 + static struct hdac_ext_stream * 187 + hdac_ext_link_stream_assign(struct hdac_ext_bus *ebus, 188 + struct snd_pcm_substream *substream) 189 + { 190 + struct hdac_ext_stream *res = NULL; 191 + struct hdac_stream *stream = NULL; 192 + struct hdac_bus *hbus = &ebus->bus; 193 + 194 + if (!ebus->ppcap) { 195 + dev_err(hbus->dev, "stream type not supported\n"); 196 + return NULL; 197 + } 198 + 199 + list_for_each_entry(stream, &hbus->stream_list, list) { 200 + struct hdac_ext_stream *hstream = container_of(stream, 201 + struct hdac_ext_stream, 202 + hstream); 203 + if (stream->direction != substream->stream) 204 + continue; 205 + 206 + /* check if decoupled stream and not in use is available */ 207 + if (hstream->decoupled && !hstream->link_locked) { 208 + res = hstream; 209 + break; 210 + } 211 + 212 + if (!hstream->link_locked) { 213 + snd_hdac_ext_stream_decouple(ebus, hstream, true); 214 + res = hstream; 215 + break; 216 + } 217 + } 218 + if (res) { 219 + spin_lock_irq(&hbus->reg_lock); 220 + res->link_locked = 1; 221 + res->link_substream = substream; 222 + spin_unlock_irq(&hbus->reg_lock); 223 + } 224 + return res; 225 + } 226 + 227 + static struct hdac_ext_stream * 228 + hdac_ext_host_stream_assign(struct hdac_ext_bus *ebus, 229 + struct snd_pcm_substream *substream) 230 + { 231 + struct hdac_ext_stream *res = NULL; 232 + struct hdac_stream *stream = NULL; 233 + struct hdac_bus *hbus = &ebus->bus; 234 + int key; 235 + 236 + if (!ebus->ppcap) { 237 + dev_err(hbus->dev, "stream type not supported\n"); 238 + return NULL; 239 + } 240 + 241 + /* make a non-zero unique key for the substream */ 242 + key = (substream->pcm->device << 16) | (substream->number << 2) | 243 + (substream->stream + 1); 244 + 245 + list_for_each_entry(stream, &hbus->stream_list, list) { 246 + struct hdac_ext_stream *hstream = container_of(stream, 247 + struct hdac_ext_stream, 248 + hstream); 249 + if (stream->direction != substream->stream) 250 + continue; 251 + 252 + if (stream->opened) { 253 + if (!hstream->decoupled) 254 + snd_hdac_ext_stream_decouple(ebus, hstream, true); 255 + res = hstream; 256 + break; 257 + } 258 + } 259 + if (res) { 260 + spin_lock_irq(&hbus->reg_lock); 261 + res->hstream.opened = 1; 262 + res->hstream.running = 0; 263 + res->hstream.assigned_key = key; 264 + res->hstream.substream = substream; 265 + spin_unlock_irq(&hbus->reg_lock); 266 + } 267 + 268 + return res; 269 + } 270 + 271 + /** 272 + * snd_hdac_ext_stream_assign - assign a stream for the PCM 273 + * @ebus: HD-audio ext core bus 274 + * @substream: PCM substream to assign 275 + * @type: type of stream (coupled, host or link stream) 276 + * 277 + * This assigns the stream based on the type (coupled/host/link), for the 278 + * given PCM substream, assigns it and returns the stream object 279 + * 280 + * coupled: Looks for an unused stream 281 + * host: Looks for an unused decoupled host stream 282 + * link: Looks for an unused decoupled link stream 283 + * 284 + * If no stream is free, returns NULL. The function tries to keep using 285 + * the same stream object when it's used beforehand. when a stream is 286 + * decoupled, it becomes a host stream and link stream. 287 + */ 288 + struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_ext_bus *ebus, 289 + struct snd_pcm_substream *substream, 290 + int type) 291 + { 292 + struct hdac_ext_stream *hstream = NULL; 293 + struct hdac_stream *stream = NULL; 294 + struct hdac_bus *hbus = &ebus->bus; 295 + 296 + switch (type) { 297 + case HDAC_EXT_STREAM_TYPE_COUPLED: 298 + stream = snd_hdac_stream_assign(hbus, substream); 299 + if (stream) 300 + hstream = container_of(stream, 301 + struct hdac_ext_stream, hstream); 302 + return hstream; 303 + 304 + case HDAC_EXT_STREAM_TYPE_HOST: 305 + return hdac_ext_host_stream_assign(ebus, substream); 306 + 307 + case HDAC_EXT_STREAM_TYPE_LINK: 308 + return hdac_ext_link_stream_assign(ebus, substream); 309 + 310 + default: 311 + return NULL; 312 + } 313 + } 314 + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign); 315 + 316 + /** 317 + * snd_hdac_ext_stream_release - release the assigned stream 318 + * @stream: HD-audio ext core stream to release 319 + * @type: type of stream (coupled, host or link stream) 320 + * 321 + * Release the stream that has been assigned by snd_hdac_ext_stream_assign(). 322 + */ 323 + void snd_hdac_ext_stream_release(struct hdac_ext_stream *stream, int type) 324 + { 325 + struct hdac_bus *bus = stream->hstream.bus; 326 + struct hdac_ext_bus *ebus = hbus_to_ebus(bus); 327 + 328 + switch (type) { 329 + case HDAC_EXT_STREAM_TYPE_COUPLED: 330 + snd_hdac_stream_release(&stream->hstream); 331 + break; 332 + 333 + case HDAC_EXT_STREAM_TYPE_HOST: 334 + if (stream->decoupled) { 335 + snd_hdac_ext_stream_decouple(ebus, stream, false); 336 + snd_hdac_stream_release(&stream->hstream); 337 + } 338 + break; 339 + 340 + case HDAC_EXT_STREAM_TYPE_LINK: 341 + if (stream->decoupled) 342 + snd_hdac_ext_stream_decouple(ebus, stream, false); 343 + spin_lock_irq(&bus->reg_lock); 344 + stream->link_locked = 0; 345 + stream->link_substream = NULL; 346 + spin_unlock_irq(&bus->reg_lock); 347 + break; 348 + 349 + default: 350 + dev_dbg(bus->dev, "Invalid type %d\n", type); 351 + } 352 + 353 + } 354 + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release); 355 + 356 + /** 357 + * snd_hdac_ext_stream_spbcap_enable - enable SPIB for a stream 358 + * @ebus: HD-audio ext core bus 359 + * @enable: flag to enable/disable SPIB 360 + * @index: stream index for which SPIB need to be enabled 361 + */ 362 + void snd_hdac_ext_stream_spbcap_enable(struct hdac_ext_bus *ebus, 363 + bool enable, int index) 364 + { 365 + u32 mask = 0; 366 + u32 register_mask = 0; 367 + struct hdac_bus *bus = &ebus->bus; 368 + 369 + if (!ebus->spbcap) { 370 + dev_err(bus->dev, "Address of SPB capability is NULL"); 371 + return; 372 + } 373 + 374 + mask |= (1 << index); 375 + 376 + register_mask = snd_hdac_chip_readl(bus, SPB_SPBFCCTL); 377 + 378 + mask |= register_mask; 379 + 380 + if (enable) 381 + snd_hdac_updatel(ebus->spbcap, AZX_REG_SPB_SPBFCCTL, 0, mask); 382 + else 383 + snd_hdac_updatel(ebus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, 0); 384 + } 385 + EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_spbcap_enable); 386 + 387 + /** 388 + * snd_hdac_ext_stop_streams - stop all stream if running 389 + * @ebus: HD-audio ext core bus 390 + */ 391 + void snd_hdac_ext_stop_streams(struct hdac_ext_bus *ebus) 392 + { 393 + struct hdac_bus *bus = ebus_to_hbus(ebus); 394 + struct hdac_stream *stream; 395 + 396 + if (bus->chip_init) { 397 + list_for_each_entry(stream, &bus->stream_list, list) 398 + snd_hdac_stream_stop(stream); 399 + snd_hdac_bus_stop_chip(bus); 400 + } 401 + } 402 + EXPORT_SYMBOL_GPL(snd_hdac_ext_stop_streams);