linux/sound/hda/ext/hdac_ext_stream.c
<<
>>
Prefs
   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 * @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 */
  29void snd_hdac_ext_stream_init(struct hdac_bus *bus,
  30                                struct hdac_ext_stream *stream,
  31                                int idx, int direction, int tag)
  32{
  33        if (bus->ppcap) {
  34                stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE +
  35                                AZX_PPHC_INTERVAL * idx;
  36
  37                stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE +
  38                                AZX_PPLC_MULTI * bus->num_streams +
  39                                AZX_PPLC_INTERVAL * idx;
  40        }
  41
  42        if (bus->spbcap) {
  43                stream->spib_addr = bus->spbcap + AZX_SPB_BASE +
  44                                        AZX_SPB_INTERVAL * idx +
  45                                        AZX_SPB_SPIB;
  46
  47                stream->fifo_addr = bus->spbcap + AZX_SPB_BASE +
  48                                        AZX_SPB_INTERVAL * idx +
  49                                        AZX_SPB_MAXFIFO;
  50        }
  51
  52        if (bus->drsmcap)
  53                stream->dpibr_addr = bus->drsmcap + AZX_DRSM_BASE +
  54                                        AZX_DRSM_INTERVAL * idx;
  55
  56        stream->decoupled = false;
  57        snd_hdac_stream_init(bus, &stream->hstream, idx, direction, tag);
  58}
  59EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init);
  60
  61/**
  62 * snd_hdac_ext_stream_init_all - create and initialize the stream objects
  63 *   for an extended hda bus
  64 * @bus: HD-audio core bus
  65 * @start_idx: start index for streams
  66 * @num_stream: number of streams to initialize
  67 * @dir: direction of streams
  68 */
  69int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx,
  70                int num_stream, int dir)
  71{
  72        int stream_tag = 0;
  73        int i, tag, idx = start_idx;
  74
  75        for (i = 0; i < num_stream; i++) {
  76                struct hdac_ext_stream *stream =
  77                                kzalloc(sizeof(*stream), GFP_KERNEL);
  78                if (!stream)
  79                        return -ENOMEM;
  80                tag = ++stream_tag;
  81                snd_hdac_ext_stream_init(bus, stream, idx, dir, tag);
  82                idx++;
  83        }
  84
  85        return 0;
  86
  87}
  88EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all);
  89
  90/**
  91 * snd_hdac_stream_free_all - free hdac extended stream objects
  92 *
  93 * @bus: HD-audio core bus
  94 */
  95void snd_hdac_stream_free_all(struct hdac_bus *bus)
  96{
  97        struct hdac_stream *s, *_s;
  98        struct hdac_ext_stream *stream;
  99
 100        list_for_each_entry_safe(s, _s, &bus->stream_list, list) {
 101                stream = stream_to_hdac_ext_stream(s);
 102                snd_hdac_ext_stream_decouple(bus, stream, false);
 103                list_del(&s->list);
 104                kfree(stream);
 105        }
 106}
 107EXPORT_SYMBOL_GPL(snd_hdac_stream_free_all);
 108
 109/**
 110 * snd_hdac_ext_stream_decouple - decouple the hdac stream
 111 * @bus: HD-audio core bus
 112 * @stream: HD-audio ext core stream object to initialize
 113 * @decouple: flag to decouple
 114 */
 115void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
 116                                struct hdac_ext_stream *stream, bool decouple)
 117{
 118        struct hdac_stream *hstream = &stream->hstream;
 119        u32 val;
 120        int mask = AZX_PPCTL_PROCEN(hstream->index);
 121
 122        spin_lock_irq(&bus->reg_lock);
 123        val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
 124
 125        if (decouple && !val)
 126                snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);
 127        else if (!decouple && val)
 128                snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
 129
 130        stream->decoupled = decouple;
 131        spin_unlock_irq(&bus->reg_lock);
 132}
 133EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
 134
 135/**
 136 * snd_hdac_ext_link_stream_start - start a stream
 137 * @stream: HD-audio ext core stream to start
 138 */
 139void snd_hdac_ext_link_stream_start(struct hdac_ext_stream *stream)
 140{
 141        snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL,
 142                         AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN);
 143}
 144EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_start);
 145
 146/**
 147 * snd_hdac_ext_link_stream_clear - stop a stream DMA
 148 * @stream: HD-audio ext core stream to stop
 149 */
 150void snd_hdac_ext_link_stream_clear(struct hdac_ext_stream *stream)
 151{
 152        snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0);
 153}
 154EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_clear);
 155
 156/**
 157 * snd_hdac_ext_link_stream_reset - reset a stream
 158 * @stream: HD-audio ext core stream to reset
 159 */
 160void snd_hdac_ext_link_stream_reset(struct hdac_ext_stream *stream)
 161{
 162        unsigned char val;
 163        int timeout;
 164
 165        snd_hdac_ext_link_stream_clear(stream);
 166
 167        snd_hdac_updatel(stream->pplc_addr, AZX_REG_PPLCCTL,
 168                         AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST);
 169        udelay(3);
 170        timeout = 50;
 171        do {
 172                val = readl(stream->pplc_addr + AZX_REG_PPLCCTL) &
 173                                AZX_PPLCCTL_STRST;
 174                if (val)
 175                        break;
 176                udelay(3);
 177        } while (--timeout);
 178        val &= ~AZX_PPLCCTL_STRST;
 179        writel(val, stream->pplc_addr + AZX_REG_PPLCCTL);
 180        udelay(3);
 181
 182        timeout = 50;
 183        /* waiting for hardware to report that the stream is out of reset */
 184        do {
 185                val = readl(stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST;
 186                if (!val)
 187                        break;
 188                udelay(3);
 189        } while (--timeout);
 190
 191}
 192EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_reset);
 193
 194/**
 195 * snd_hdac_ext_link_stream_setup -  set up the SD for streaming
 196 * @stream: HD-audio ext core stream to set up
 197 * @fmt: stream format
 198 */
 199int snd_hdac_ext_link_stream_setup(struct hdac_ext_stream *stream, int fmt)
 200{
 201        struct hdac_stream *hstream = &stream->hstream;
 202        unsigned int val;
 203
 204        /* make sure the run bit is zero for SD */
 205        snd_hdac_ext_link_stream_clear(stream);
 206        /* program the stream_tag */
 207        val = readl(stream->pplc_addr + AZX_REG_PPLCCTL);
 208        val = (val & ~AZX_PPLCCTL_STRM_MASK) |
 209                (hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT);
 210        writel(val, stream->pplc_addr + AZX_REG_PPLCCTL);
 211
 212        /* program the stream format */
 213        writew(fmt, stream->pplc_addr + AZX_REG_PPLCFMT);
 214
 215        return 0;
 216}
 217EXPORT_SYMBOL_GPL(snd_hdac_ext_link_stream_setup);
 218
 219/**
 220 * snd_hdac_ext_link_set_stream_id - maps stream id to link output
 221 * @link: HD-audio ext link to set up
 222 * @stream: stream id
 223 */
 224void snd_hdac_ext_link_set_stream_id(struct hdac_ext_link *link,
 225                                 int stream)
 226{
 227        snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 1 << stream);
 228}
 229EXPORT_SYMBOL_GPL(snd_hdac_ext_link_set_stream_id);
 230
 231/**
 232 * snd_hdac_ext_link_clear_stream_id - maps stream id to link output
 233 * @link: HD-audio ext link to set up
 234 * @stream: stream id
 235 */
 236void snd_hdac_ext_link_clear_stream_id(struct hdac_ext_link *link,
 237                                 int stream)
 238{
 239        snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 0);
 240}
 241EXPORT_SYMBOL_GPL(snd_hdac_ext_link_clear_stream_id);
 242
 243static struct hdac_ext_stream *
 244hdac_ext_link_stream_assign(struct hdac_bus *bus,
 245                                struct snd_pcm_substream *substream)
 246{
 247        struct hdac_ext_stream *res = NULL;
 248        struct hdac_stream *stream = NULL;
 249
 250        if (!bus->ppcap) {
 251                dev_err(bus->dev, "stream type not supported\n");
 252                return NULL;
 253        }
 254
 255        list_for_each_entry(stream, &bus->stream_list, list) {
 256                struct hdac_ext_stream *hstream = container_of(stream,
 257                                                struct hdac_ext_stream,
 258                                                hstream);
 259                if (stream->direction != substream->stream)
 260                        continue;
 261
 262                /* check if decoupled stream and not in use is available */
 263                if (hstream->decoupled && !hstream->link_locked) {
 264                        res = hstream;
 265                        break;
 266                }
 267
 268                if (!hstream->link_locked) {
 269                        snd_hdac_ext_stream_decouple(bus, hstream, true);
 270                        res = hstream;
 271                        break;
 272                }
 273        }
 274        if (res) {
 275                spin_lock_irq(&bus->reg_lock);
 276                res->link_locked = 1;
 277                res->link_substream = substream;
 278                spin_unlock_irq(&bus->reg_lock);
 279        }
 280        return res;
 281}
 282
 283static struct hdac_ext_stream *
 284hdac_ext_host_stream_assign(struct hdac_bus *bus,
 285                                struct snd_pcm_substream *substream)
 286{
 287        struct hdac_ext_stream *res = NULL;
 288        struct hdac_stream *stream = NULL;
 289
 290        if (!bus->ppcap) {
 291                dev_err(bus->dev, "stream type not supported\n");
 292                return NULL;
 293        }
 294
 295        list_for_each_entry(stream, &bus->stream_list, list) {
 296                struct hdac_ext_stream *hstream = container_of(stream,
 297                                                struct hdac_ext_stream,
 298                                                hstream);
 299                if (stream->direction != substream->stream)
 300                        continue;
 301
 302                if (!stream->opened) {
 303                        if (!hstream->decoupled)
 304                                snd_hdac_ext_stream_decouple(bus, hstream, true);
 305                        res = hstream;
 306                        break;
 307                }
 308        }
 309        if (res) {
 310                spin_lock_irq(&bus->reg_lock);
 311                res->hstream.opened = 1;
 312                res->hstream.running = 0;
 313                res->hstream.substream = substream;
 314                spin_unlock_irq(&bus->reg_lock);
 315        }
 316
 317        return res;
 318}
 319
 320/**
 321 * snd_hdac_ext_stream_assign - assign a stream for the PCM
 322 * @bus: HD-audio core bus
 323 * @substream: PCM substream to assign
 324 * @type: type of stream (coupled, host or link stream)
 325 *
 326 * This assigns the stream based on the type (coupled/host/link), for the
 327 * given PCM substream, assigns it and returns the stream object
 328 *
 329 * coupled: Looks for an unused stream
 330 * host: Looks for an unused decoupled host stream
 331 * link: Looks for an unused decoupled link stream
 332 *
 333 * If no stream is free, returns NULL. The function tries to keep using
 334 * the same stream object when it's used beforehand.  when a stream is
 335 * decoupled, it becomes a host stream and link stream.
 336 */
 337struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
 338                                           struct snd_pcm_substream *substream,
 339                                           int type)
 340{
 341        struct hdac_ext_stream *hstream = NULL;
 342        struct hdac_stream *stream = NULL;
 343
 344        switch (type) {
 345        case HDAC_EXT_STREAM_TYPE_COUPLED:
 346                stream = snd_hdac_stream_assign(bus, substream);
 347                if (stream)
 348                        hstream = container_of(stream,
 349                                        struct hdac_ext_stream, hstream);
 350                return hstream;
 351
 352        case HDAC_EXT_STREAM_TYPE_HOST:
 353                return hdac_ext_host_stream_assign(bus, substream);
 354
 355        case HDAC_EXT_STREAM_TYPE_LINK:
 356                return hdac_ext_link_stream_assign(bus, substream);
 357
 358        default:
 359                return NULL;
 360        }
 361}
 362EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign);
 363
 364/**
 365 * snd_hdac_ext_stream_release - release the assigned stream
 366 * @stream: HD-audio ext core stream to release
 367 * @type: type of stream (coupled, host or link stream)
 368 *
 369 * Release the stream that has been assigned by snd_hdac_ext_stream_assign().
 370 */
 371void snd_hdac_ext_stream_release(struct hdac_ext_stream *stream, int type)
 372{
 373        struct hdac_bus *bus = stream->hstream.bus;
 374
 375        switch (type) {
 376        case HDAC_EXT_STREAM_TYPE_COUPLED:
 377                snd_hdac_stream_release(&stream->hstream);
 378                break;
 379
 380        case HDAC_EXT_STREAM_TYPE_HOST:
 381                if (stream->decoupled && !stream->link_locked)
 382                        snd_hdac_ext_stream_decouple(bus, stream, false);
 383                snd_hdac_stream_release(&stream->hstream);
 384                break;
 385
 386        case HDAC_EXT_STREAM_TYPE_LINK:
 387                if (stream->decoupled && !stream->hstream.opened)
 388                        snd_hdac_ext_stream_decouple(bus, stream, false);
 389                spin_lock_irq(&bus->reg_lock);
 390                stream->link_locked = 0;
 391                stream->link_substream = NULL;
 392                spin_unlock_irq(&bus->reg_lock);
 393                break;
 394
 395        default:
 396                dev_dbg(bus->dev, "Invalid type %d\n", type);
 397        }
 398
 399}
 400EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release);
 401
 402/**
 403 * snd_hdac_ext_stream_spbcap_enable - enable SPIB for a stream
 404 * @bus: HD-audio core bus
 405 * @enable: flag to enable/disable SPIB
 406 * @index: stream index for which SPIB need to be enabled
 407 */
 408void snd_hdac_ext_stream_spbcap_enable(struct hdac_bus *bus,
 409                                 bool enable, int index)
 410{
 411        u32 mask = 0;
 412
 413        if (!bus->spbcap) {
 414                dev_err(bus->dev, "Address of SPB capability is NULL\n");
 415                return;
 416        }
 417
 418        mask |= (1 << index);
 419
 420        if (enable)
 421                snd_hdac_updatel(bus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, mask);
 422        else
 423                snd_hdac_updatel(bus->spbcap, AZX_REG_SPB_SPBFCCTL, mask, 0);
 424}
 425EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_spbcap_enable);
 426
 427/**
 428 * snd_hdac_ext_stream_set_spib - sets the spib value of a stream
 429 * @bus: HD-audio core bus
 430 * @stream: hdac_ext_stream
 431 * @value: spib value to set
 432 */
 433int snd_hdac_ext_stream_set_spib(struct hdac_bus *bus,
 434                                 struct hdac_ext_stream *stream, u32 value)
 435{
 436
 437        if (!bus->spbcap) {
 438                dev_err(bus->dev, "Address of SPB capability is NULL\n");
 439                return -EINVAL;
 440        }
 441
 442        writel(value, stream->spib_addr);
 443
 444        return 0;
 445}
 446EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_spib);
 447
 448/**
 449 * snd_hdac_ext_stream_get_spbmaxfifo - gets the spib value of a stream
 450 * @bus: HD-audio core bus
 451 * @stream: hdac_ext_stream
 452 *
 453 * Return maxfifo for the stream
 454 */
 455int snd_hdac_ext_stream_get_spbmaxfifo(struct hdac_bus *bus,
 456                                 struct hdac_ext_stream *stream)
 457{
 458
 459        if (!bus->spbcap) {
 460                dev_err(bus->dev, "Address of SPB capability is NULL\n");
 461                return -EINVAL;
 462        }
 463
 464        return readl(stream->fifo_addr);
 465}
 466EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_get_spbmaxfifo);
 467
 468
 469/**
 470 * snd_hdac_ext_stop_streams - stop all stream if running
 471 * @bus: HD-audio core bus
 472 */
 473void snd_hdac_ext_stop_streams(struct hdac_bus *bus)
 474{
 475        struct hdac_stream *stream;
 476
 477        if (bus->chip_init) {
 478                list_for_each_entry(stream, &bus->stream_list, list)
 479                        snd_hdac_stream_stop(stream);
 480                snd_hdac_bus_stop_chip(bus);
 481        }
 482}
 483EXPORT_SYMBOL_GPL(snd_hdac_ext_stop_streams);
 484
 485/**
 486 * snd_hdac_ext_stream_drsm_enable - enable DMA resume for a stream
 487 * @bus: HD-audio core bus
 488 * @enable: flag to enable/disable DRSM
 489 * @index: stream index for which DRSM need to be enabled
 490 */
 491void snd_hdac_ext_stream_drsm_enable(struct hdac_bus *bus,
 492                                bool enable, int index)
 493{
 494        u32 mask = 0;
 495
 496        if (!bus->drsmcap) {
 497                dev_err(bus->dev, "Address of DRSM capability is NULL\n");
 498                return;
 499        }
 500
 501        mask |= (1 << index);
 502
 503        if (enable)
 504                snd_hdac_updatel(bus->drsmcap, AZX_REG_DRSM_CTL, mask, mask);
 505        else
 506                snd_hdac_updatel(bus->drsmcap, AZX_REG_DRSM_CTL, mask, 0);
 507}
 508EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_drsm_enable);
 509
 510/**
 511 * snd_hdac_ext_stream_set_dpibr - sets the dpibr value of a stream
 512 * @bus: HD-audio core bus
 513 * @stream: hdac_ext_stream
 514 * @value: dpib value to set
 515 */
 516int snd_hdac_ext_stream_set_dpibr(struct hdac_bus *bus,
 517                                 struct hdac_ext_stream *stream, u32 value)
 518{
 519
 520        if (!bus->drsmcap) {
 521                dev_err(bus->dev, "Address of DRSM capability is NULL\n");
 522                return -EINVAL;
 523        }
 524
 525        writel(value, stream->dpibr_addr);
 526
 527        return 0;
 528}
 529EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_dpibr);
 530
 531/**
 532 * snd_hdac_ext_stream_set_lpib - sets the lpib value of a stream
 533 * @stream: hdac_ext_stream
 534 * @value: lpib value to set
 535 */
 536int snd_hdac_ext_stream_set_lpib(struct hdac_ext_stream *stream, u32 value)
 537{
 538        snd_hdac_stream_writel(&stream->hstream, SD_LPIB, value);
 539
 540        return 0;
 541}
 542EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_set_lpib);
 543