linux/sound/hda/ext/hdac_ext_controller.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  hdac-ext-controller.c - HD-audio extended controller functions.
   4 *
   5 *  Copyright (C) 2014-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/hda_register.h>
  15#include <sound/hdaudio_ext.h>
  16
  17/*
  18 * maximum HDAC capablities we should parse to avoid endless looping:
  19 * currently we have 4 extended caps, so this is future proof for now.
  20 * extend when this limit is seen meeting in real HW
  21 */
  22#define HDAC_MAX_CAPS 10
  23
  24/*
  25 * processing pipe helpers - these helpers are useful for dealing with HDA
  26 * new capability of processing pipelines
  27 */
  28
  29/**
  30 * snd_hdac_ext_bus_ppcap_enable - enable/disable processing pipe capability
  31 * @bus: the pointer to HDAC bus object
  32 * @enable: flag to turn on/off the capability
  33 */
  34void snd_hdac_ext_bus_ppcap_enable(struct hdac_bus *bus, bool enable)
  35{
  36
  37        if (!bus->ppcap) {
  38                dev_err(bus->dev, "Address of PP capability is NULL");
  39                return;
  40        }
  41
  42        if (enable)
  43                snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
  44                                 AZX_PPCTL_GPROCEN, AZX_PPCTL_GPROCEN);
  45        else
  46                snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
  47                                 AZX_PPCTL_GPROCEN, 0);
  48}
  49EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_enable);
  50
  51/**
  52 * snd_hdac_ext_bus_ppcap_int_enable - ppcap interrupt enable/disable
  53 * @bus: the pointer to HDAC bus object
  54 * @enable: flag to enable/disable interrupt
  55 */
  56void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_bus *bus, bool enable)
  57{
  58
  59        if (!bus->ppcap) {
  60                dev_err(bus->dev, "Address of PP capability is NULL\n");
  61                return;
  62        }
  63
  64        if (enable)
  65                snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
  66                                 AZX_PPCTL_PIE, AZX_PPCTL_PIE);
  67        else
  68                snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
  69                                 AZX_PPCTL_PIE, 0);
  70}
  71EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_int_enable);
  72
  73/*
  74 * Multilink helpers - these helpers are useful for dealing with HDA
  75 * new multilink capability
  76 */
  77
  78/**
  79 * snd_hdac_ext_bus_get_ml_capabilities - get multilink capability
  80 * @bus: the pointer to HDAC bus object
  81 *
  82 * This will parse all links and read the mlink capabilities and add them
  83 * in hlink_list of extended hdac bus
  84 * Note: this will be freed on bus exit by driver
  85 */
  86int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus)
  87{
  88        int idx;
  89        u32 link_count;
  90        struct hdac_ext_link *hlink;
  91
  92        link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
  93
  94        dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count);
  95
  96        for (idx = 0; idx < link_count; idx++) {
  97                hlink  = kzalloc(sizeof(*hlink), GFP_KERNEL);
  98                if (!hlink)
  99                        return -ENOMEM;
 100                hlink->index = idx;
 101                hlink->bus = bus;
 102                hlink->ml_addr = bus->mlcap + AZX_ML_BASE +
 103                                        (AZX_ML_INTERVAL * idx);
 104                hlink->lcaps  = readl(hlink->ml_addr + AZX_REG_ML_LCAP);
 105                hlink->lsdiid = readw(hlink->ml_addr + AZX_REG_ML_LSDIID);
 106
 107                /* since link in On, update the ref */
 108                hlink->ref_count = 1;
 109
 110                list_add_tail(&hlink->list, &bus->hlink_list);
 111        }
 112
 113        return 0;
 114}
 115EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_ml_capabilities);
 116
 117/**
 118 * snd_hdac_link_free_all- free hdac extended link objects
 119 *
 120 * @bus: the pointer to HDAC bus object
 121 */
 122
 123void snd_hdac_link_free_all(struct hdac_bus *bus)
 124{
 125        struct hdac_ext_link *l;
 126
 127        while (!list_empty(&bus->hlink_list)) {
 128                l = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list);
 129                list_del(&l->list);
 130                kfree(l);
 131        }
 132}
 133EXPORT_SYMBOL_GPL(snd_hdac_link_free_all);
 134
 135/**
 136 * snd_hdac_ext_bus_get_link - get link based on codec name
 137 * @bus: the pointer to HDAC bus object
 138 * @codec_name: codec name
 139 */
 140struct hdac_ext_link *snd_hdac_ext_bus_get_link(struct hdac_bus *bus,
 141                                                 const char *codec_name)
 142{
 143        int i;
 144        struct hdac_ext_link *hlink = NULL;
 145        int bus_idx, addr;
 146
 147        if (sscanf(codec_name, "ehdaudio%dD%d", &bus_idx, &addr) != 2)
 148                return NULL;
 149        if (bus->idx != bus_idx)
 150                return NULL;
 151        if (addr < 0 || addr > 31)
 152                return NULL;
 153
 154        list_for_each_entry(hlink, &bus->hlink_list, list) {
 155                for (i = 0; i < HDA_MAX_CODECS; i++) {
 156                        if (hlink->lsdiid & (0x1 << addr))
 157                                return hlink;
 158                }
 159        }
 160
 161        return NULL;
 162}
 163EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_link);
 164
 165static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable)
 166{
 167        int timeout;
 168        u32 val;
 169        int mask = (1 << AZX_MLCTL_CPA_SHIFT);
 170
 171        udelay(3);
 172        timeout = 150;
 173
 174        do {
 175                val = readl(link->ml_addr + AZX_REG_ML_LCTL);
 176                if (enable) {
 177                        if (((val & mask) >> AZX_MLCTL_CPA_SHIFT))
 178                                return 0;
 179                } else {
 180                        if (!((val & mask) >> AZX_MLCTL_CPA_SHIFT))
 181                                return 0;
 182                }
 183                udelay(3);
 184        } while (--timeout);
 185
 186        return -EIO;
 187}
 188
 189/**
 190 * snd_hdac_ext_bus_link_power_up -power up hda link
 191 * @link: HD-audio extended link
 192 */
 193int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *link)
 194{
 195        snd_hdac_updatel(link->ml_addr, AZX_REG_ML_LCTL,
 196                         AZX_MLCTL_SPA, AZX_MLCTL_SPA);
 197
 198        return check_hdac_link_power_active(link, true);
 199}
 200EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up);
 201
 202/**
 203 * snd_hdac_ext_bus_link_power_down -power down hda link
 204 * @link: HD-audio extended link
 205 */
 206int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *link)
 207{
 208        snd_hdac_updatel(link->ml_addr, AZX_REG_ML_LCTL, AZX_MLCTL_SPA, 0);
 209
 210        return check_hdac_link_power_active(link, false);
 211}
 212EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down);
 213
 214/**
 215 * snd_hdac_ext_bus_link_power_up_all -power up all hda link
 216 * @bus: the pointer to HDAC bus object
 217 */
 218int snd_hdac_ext_bus_link_power_up_all(struct hdac_bus *bus)
 219{
 220        struct hdac_ext_link *hlink = NULL;
 221        int ret;
 222
 223        list_for_each_entry(hlink, &bus->hlink_list, list) {
 224                snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL,
 225                                 AZX_MLCTL_SPA, AZX_MLCTL_SPA);
 226                ret = check_hdac_link_power_active(hlink, true);
 227                if (ret < 0)
 228                        return ret;
 229        }
 230
 231        return 0;
 232}
 233EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up_all);
 234
 235/**
 236 * snd_hdac_ext_bus_link_power_down_all -power down all hda link
 237 * @bus: the pointer to HDAC bus object
 238 */
 239int snd_hdac_ext_bus_link_power_down_all(struct hdac_bus *bus)
 240{
 241        struct hdac_ext_link *hlink = NULL;
 242        int ret;
 243
 244        list_for_each_entry(hlink, &bus->hlink_list, list) {
 245                snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL,
 246                                 AZX_MLCTL_SPA, 0);
 247                ret = check_hdac_link_power_active(hlink, false);
 248                if (ret < 0)
 249                        return ret;
 250        }
 251
 252        return 0;
 253}
 254EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down_all);
 255
 256int snd_hdac_ext_bus_link_get(struct hdac_bus *bus,
 257                                struct hdac_ext_link *link)
 258{
 259        unsigned long codec_mask;
 260        int ret = 0;
 261
 262        mutex_lock(&bus->lock);
 263
 264        /*
 265         * if we move from 0 to 1, count will be 1 so power up this link
 266         * as well, also check the dma status and trigger that
 267         */
 268        if (++link->ref_count == 1) {
 269                if (!bus->cmd_dma_state) {
 270                        snd_hdac_bus_init_cmd_io(bus);
 271                        bus->cmd_dma_state = true;
 272                }
 273
 274                ret = snd_hdac_ext_bus_link_power_up(link);
 275
 276                /*
 277                 * clear the register to invalidate all the output streams
 278                 */
 279                snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV,
 280                                 ML_LOSIDV_STREAM_MASK, 0);
 281                /*
 282                 *  wait for 521usec for codec to report status
 283                 *  HDA spec section 4.3 - Codec Discovery
 284                 */
 285                udelay(521);
 286                codec_mask = snd_hdac_chip_readw(bus, STATESTS);
 287                dev_dbg(bus->dev, "codec_mask = 0x%lx\n", codec_mask);
 288                snd_hdac_chip_writew(bus, STATESTS, codec_mask);
 289                if (!bus->codec_mask)
 290                        bus->codec_mask = codec_mask;
 291        }
 292
 293        mutex_unlock(&bus->lock);
 294        return ret;
 295}
 296EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_get);
 297
 298int snd_hdac_ext_bus_link_put(struct hdac_bus *bus,
 299                                struct hdac_ext_link *link)
 300{
 301        int ret = 0;
 302        struct hdac_ext_link *hlink;
 303        bool link_up = false;
 304
 305        mutex_lock(&bus->lock);
 306
 307        /*
 308         * if we move from 1 to 0, count will be 0
 309         * so power down this link as well
 310         */
 311        if (--link->ref_count == 0) {
 312                ret = snd_hdac_ext_bus_link_power_down(link);
 313
 314                /*
 315                 * now check if all links are off, if so turn off
 316                 * cmd dma as well
 317                 */
 318                list_for_each_entry(hlink, &bus->hlink_list, list) {
 319                        if (hlink->ref_count) {
 320                                link_up = true;
 321                                break;
 322                        }
 323                }
 324
 325                if (!link_up) {
 326                        snd_hdac_bus_stop_cmd_io(bus);
 327                        bus->cmd_dma_state = false;
 328                }
 329        }
 330
 331        mutex_unlock(&bus->lock);
 332        return ret;
 333}
 334EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_put);
 335
 336static void hdac_ext_codec_link_up(struct hdac_device *codec)
 337{
 338        const char *devname = dev_name(&codec->dev);
 339        struct hdac_ext_link *hlink =
 340                snd_hdac_ext_bus_get_link(codec->bus, devname);
 341
 342        if (hlink)
 343                snd_hdac_ext_bus_link_get(codec->bus, hlink);
 344}
 345
 346static void hdac_ext_codec_link_down(struct hdac_device *codec)
 347{
 348        const char *devname = dev_name(&codec->dev);
 349        struct hdac_ext_link *hlink =
 350                snd_hdac_ext_bus_get_link(codec->bus, devname);
 351
 352        if (hlink)
 353                snd_hdac_ext_bus_link_put(codec->bus, hlink);
 354}
 355
 356void snd_hdac_ext_bus_link_power(struct hdac_device *codec, bool enable)
 357{
 358        struct hdac_bus *bus = codec->bus;
 359        bool oldstate = test_bit(codec->addr, &bus->codec_powered);
 360
 361        if (enable == oldstate)
 362                return;
 363
 364        snd_hdac_bus_link_power(codec, enable);
 365
 366        if (enable)
 367                hdac_ext_codec_link_up(codec);
 368        else
 369                hdac_ext_codec_link_down(codec);
 370}
 371EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power);
 372