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