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