linux/sound/hda/hdac_bus.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * HD-audio core bus driver
   4 */
   5
   6#include <linux/init.h>
   7#include <linux/io.h>
   8#include <linux/device.h>
   9#include <linux/module.h>
  10#include <linux/export.h>
  11#include <sound/hdaudio.h>
  12#include "local.h"
  13#include "trace.h"
  14
  15static void snd_hdac_bus_process_unsol_events(struct work_struct *work);
  16
  17static const struct hdac_bus_ops default_ops = {
  18        .command = snd_hdac_bus_send_cmd,
  19        .get_response = snd_hdac_bus_get_response,
  20};
  21
  22/**
  23 * snd_hdac_bus_init - initialize a HD-audio bas bus
  24 * @bus: the pointer to bus object
  25 * @ops: bus verb operators
  26 *
  27 * Returns 0 if successful, or a negative error code.
  28 */
  29int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev,
  30                      const struct hdac_bus_ops *ops)
  31{
  32        memset(bus, 0, sizeof(*bus));
  33        bus->dev = dev;
  34        if (ops)
  35                bus->ops = ops;
  36        else
  37                bus->ops = &default_ops;
  38        bus->dma_type = SNDRV_DMA_TYPE_DEV;
  39        INIT_LIST_HEAD(&bus->stream_list);
  40        INIT_LIST_HEAD(&bus->codec_list);
  41        INIT_WORK(&bus->unsol_work, snd_hdac_bus_process_unsol_events);
  42        spin_lock_init(&bus->reg_lock);
  43        mutex_init(&bus->cmd_mutex);
  44        mutex_init(&bus->lock);
  45        INIT_LIST_HEAD(&bus->hlink_list);
  46        bus->irq = -1;
  47        return 0;
  48}
  49EXPORT_SYMBOL_GPL(snd_hdac_bus_init);
  50
  51/**
  52 * snd_hdac_bus_exit - clean up a HD-audio bas bus
  53 * @bus: the pointer to bus object
  54 */
  55void snd_hdac_bus_exit(struct hdac_bus *bus)
  56{
  57        WARN_ON(!list_empty(&bus->stream_list));
  58        WARN_ON(!list_empty(&bus->codec_list));
  59        cancel_work_sync(&bus->unsol_work);
  60}
  61EXPORT_SYMBOL_GPL(snd_hdac_bus_exit);
  62
  63/**
  64 * snd_hdac_bus_exec_verb - execute a HD-audio verb on the given bus
  65 * @bus: bus object
  66 * @cmd: HD-audio encoded verb
  67 * @res: pointer to store the response, NULL if performing asynchronously
  68 *
  69 * Returns 0 if successful, or a negative error code.
  70 */
  71int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr,
  72                           unsigned int cmd, unsigned int *res)
  73{
  74        int err;
  75
  76        mutex_lock(&bus->cmd_mutex);
  77        err = snd_hdac_bus_exec_verb_unlocked(bus, addr, cmd, res);
  78        mutex_unlock(&bus->cmd_mutex);
  79        return err;
  80}
  81EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb);
  82
  83/**
  84 * snd_hdac_bus_exec_verb_unlocked - unlocked version
  85 * @bus: bus object
  86 * @cmd: HD-audio encoded verb
  87 * @res: pointer to store the response, NULL if performing asynchronously
  88 *
  89 * Returns 0 if successful, or a negative error code.
  90 */
  91int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr,
  92                                    unsigned int cmd, unsigned int *res)
  93{
  94        unsigned int tmp;
  95        int err;
  96
  97        if (cmd == ~0)
  98                return -EINVAL;
  99
 100        if (res)
 101                *res = -1;
 102        else if (bus->sync_write)
 103                res = &tmp;
 104        for (;;) {
 105                trace_hda_send_cmd(bus, cmd);
 106                err = bus->ops->command(bus, cmd);
 107                if (err != -EAGAIN)
 108                        break;
 109                /* process pending verbs */
 110                err = bus->ops->get_response(bus, addr, &tmp);
 111                if (err)
 112                        break;
 113        }
 114        if (!err && res) {
 115                err = bus->ops->get_response(bus, addr, res);
 116                trace_hda_get_response(bus, addr, *res);
 117        }
 118        return err;
 119}
 120EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb_unlocked);
 121
 122/**
 123 * snd_hdac_bus_queue_event - add an unsolicited event to queue
 124 * @bus: the BUS
 125 * @res: unsolicited event (lower 32bit of RIRB entry)
 126 * @res_ex: codec addr and flags (upper 32bit or RIRB entry)
 127 *
 128 * Adds the given event to the queue.  The events are processed in
 129 * the workqueue asynchronously.  Call this function in the interrupt
 130 * hanlder when RIRB receives an unsolicited event.
 131 */
 132void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex)
 133{
 134        unsigned int wp;
 135
 136        if (!bus)
 137                return;
 138
 139        trace_hda_unsol_event(bus, res, res_ex);
 140        wp = (bus->unsol_wp + 1) % HDA_UNSOL_QUEUE_SIZE;
 141        bus->unsol_wp = wp;
 142
 143        wp <<= 1;
 144        bus->unsol_queue[wp] = res;
 145        bus->unsol_queue[wp + 1] = res_ex;
 146
 147        schedule_work(&bus->unsol_work);
 148}
 149EXPORT_SYMBOL_GPL(snd_hdac_bus_queue_event);
 150
 151/*
 152 * process queued unsolicited events
 153 */
 154static void snd_hdac_bus_process_unsol_events(struct work_struct *work)
 155{
 156        struct hdac_bus *bus = container_of(work, struct hdac_bus, unsol_work);
 157        struct hdac_device *codec;
 158        struct hdac_driver *drv;
 159        unsigned int rp, caddr, res;
 160
 161        while (bus->unsol_rp != bus->unsol_wp) {
 162                rp = (bus->unsol_rp + 1) % HDA_UNSOL_QUEUE_SIZE;
 163                bus->unsol_rp = rp;
 164                rp <<= 1;
 165                res = bus->unsol_queue[rp];
 166                caddr = bus->unsol_queue[rp + 1];
 167                if (!(caddr & (1 << 4))) /* no unsolicited event? */
 168                        continue;
 169                codec = bus->caddr_tbl[caddr & 0x0f];
 170                if (!codec || !codec->dev.driver)
 171                        continue;
 172                drv = drv_to_hdac_driver(codec->dev.driver);
 173                if (drv->unsol_event)
 174                        drv->unsol_event(codec, res);
 175        }
 176}
 177
 178/**
 179 * snd_hdac_bus_add_device - Add a codec to bus
 180 * @bus: HDA core bus
 181 * @codec: HDA core device to add
 182 *
 183 * Adds the given codec to the list in the bus.  The caddr_tbl array
 184 * and codec_powered bits are updated, as well.
 185 * Returns zero if success, or a negative error code.
 186 */
 187int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec)
 188{
 189        if (bus->caddr_tbl[codec->addr]) {
 190                dev_err(bus->dev, "address 0x%x is already occupied\n",
 191                        codec->addr);
 192                return -EBUSY;
 193        }
 194
 195        list_add_tail(&codec->list, &bus->codec_list);
 196        bus->caddr_tbl[codec->addr] = codec;
 197        set_bit(codec->addr, &bus->codec_powered);
 198        bus->num_codecs++;
 199        return 0;
 200}
 201
 202/**
 203 * snd_hdac_bus_remove_device - Remove a codec from bus
 204 * @bus: HDA core bus
 205 * @codec: HDA core device to remove
 206 */
 207void snd_hdac_bus_remove_device(struct hdac_bus *bus,
 208                                struct hdac_device *codec)
 209{
 210        WARN_ON(bus != codec->bus);
 211        if (list_empty(&codec->list))
 212                return;
 213        list_del_init(&codec->list);
 214        bus->caddr_tbl[codec->addr] = NULL;
 215        clear_bit(codec->addr, &bus->codec_powered);
 216        bus->num_codecs--;
 217        flush_work(&bus->unsol_work);
 218}
 219
 220#ifdef CONFIG_SND_HDA_ALIGNED_MMIO
 221/* Helpers for aligned read/write of mmio space, for Tegra */
 222unsigned int snd_hdac_aligned_read(void __iomem *addr, unsigned int mask)
 223{
 224        void __iomem *aligned_addr =
 225                (void __iomem *)((unsigned long)(addr) & ~0x3);
 226        unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
 227        unsigned int v;
 228
 229        v = readl(aligned_addr);
 230        return (v >> shift) & mask;
 231}
 232EXPORT_SYMBOL_GPL(snd_hdac_aligned_read);
 233
 234void snd_hdac_aligned_write(unsigned int val, void __iomem *addr,
 235                            unsigned int mask)
 236{
 237        void __iomem *aligned_addr =
 238                (void __iomem *)((unsigned long)(addr) & ~0x3);
 239        unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
 240        unsigned int v;
 241
 242        v = readl(aligned_addr);
 243        v &= ~(mask << shift);
 244        v |= val << shift;
 245        writel(v, aligned_addr);
 246}
 247EXPORT_SYMBOL_GPL(snd_hdac_aligned_write);
 248#endif /* CONFIG_SND_HDA_ALIGNED_MMIO */
 249