forked from luck/tmp_suning_uos_patched
619a1f195f
The HD-audio core allocates and releases pages via driver's specific dma_alloc_pages and dma_free_pages ops defined in bus->io_ops. This was because some platforms require the uncached pages and the handling of page flags had to be done locally in the driver code. Since the recent change in ALSA core memory allocator, we can simply pass SNDRV_DMA_TYPE_DEV_UC for the uncached pages, and the only difference became about this type to be passed to the core allocator. That is, it's good time for cleaning up the mess. This patch changes the allocation code in HD-audio core to call the core allocator directly so that we get rid of dma_alloc_pages and dma_free_pages io_ops. If a driver needs the uncached pages, it has to set bus->dma_type right after the bus initialization. This is merely a code refactoring and shouldn't bring any behavior changes. Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Signed-off-by: Takashi Iwai <tiwai@suse.de>
221 lines
5.6 KiB
C
221 lines
5.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* HD-audio core bus driver
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/device.h>
|
|
#include <linux/module.h>
|
|
#include <linux/export.h>
|
|
#include <sound/hdaudio.h>
|
|
#include "trace.h"
|
|
|
|
static const struct hdac_bus_ops default_ops = {
|
|
.command = snd_hdac_bus_send_cmd,
|
|
.get_response = snd_hdac_bus_get_response,
|
|
};
|
|
|
|
/**
|
|
* snd_hdac_bus_init - initialize a HD-audio bas bus
|
|
* @bus: the pointer to bus object
|
|
* @ops: bus verb operators
|
|
* @io_ops: lowlevel I/O operators
|
|
*
|
|
* Returns 0 if successful, or a negative error code.
|
|
*/
|
|
int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev,
|
|
const struct hdac_bus_ops *ops,
|
|
const struct hdac_io_ops *io_ops)
|
|
{
|
|
memset(bus, 0, sizeof(*bus));
|
|
bus->dev = dev;
|
|
if (ops)
|
|
bus->ops = ops;
|
|
else
|
|
bus->ops = &default_ops;
|
|
bus->io_ops = io_ops;
|
|
bus->dma_type = SNDRV_DMA_TYPE_DEV;
|
|
INIT_LIST_HEAD(&bus->stream_list);
|
|
INIT_LIST_HEAD(&bus->codec_list);
|
|
INIT_WORK(&bus->unsol_work, snd_hdac_bus_process_unsol_events);
|
|
spin_lock_init(&bus->reg_lock);
|
|
mutex_init(&bus->cmd_mutex);
|
|
mutex_init(&bus->lock);
|
|
INIT_LIST_HEAD(&bus->hlink_list);
|
|
bus->irq = -1;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_init);
|
|
|
|
/**
|
|
* snd_hdac_bus_exit - clean up a HD-audio bas bus
|
|
* @bus: the pointer to bus object
|
|
*/
|
|
void snd_hdac_bus_exit(struct hdac_bus *bus)
|
|
{
|
|
WARN_ON(!list_empty(&bus->stream_list));
|
|
WARN_ON(!list_empty(&bus->codec_list));
|
|
cancel_work_sync(&bus->unsol_work);
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_exit);
|
|
|
|
/**
|
|
* snd_hdac_bus_exec_verb - execute a HD-audio verb on the given bus
|
|
* @bus: bus object
|
|
* @cmd: HD-audio encoded verb
|
|
* @res: pointer to store the response, NULL if performing asynchronously
|
|
*
|
|
* Returns 0 if successful, or a negative error code.
|
|
*/
|
|
int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr,
|
|
unsigned int cmd, unsigned int *res)
|
|
{
|
|
int err;
|
|
|
|
mutex_lock(&bus->cmd_mutex);
|
|
err = snd_hdac_bus_exec_verb_unlocked(bus, addr, cmd, res);
|
|
mutex_unlock(&bus->cmd_mutex);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb);
|
|
|
|
/**
|
|
* snd_hdac_bus_exec_verb_unlocked - unlocked version
|
|
* @bus: bus object
|
|
* @cmd: HD-audio encoded verb
|
|
* @res: pointer to store the response, NULL if performing asynchronously
|
|
*
|
|
* Returns 0 if successful, or a negative error code.
|
|
*/
|
|
int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr,
|
|
unsigned int cmd, unsigned int *res)
|
|
{
|
|
unsigned int tmp;
|
|
int err;
|
|
|
|
if (cmd == ~0)
|
|
return -EINVAL;
|
|
|
|
if (res)
|
|
*res = -1;
|
|
else if (bus->sync_write)
|
|
res = &tmp;
|
|
for (;;) {
|
|
trace_hda_send_cmd(bus, cmd);
|
|
err = bus->ops->command(bus, cmd);
|
|
if (err != -EAGAIN)
|
|
break;
|
|
/* process pending verbs */
|
|
err = bus->ops->get_response(bus, addr, &tmp);
|
|
if (err)
|
|
break;
|
|
}
|
|
if (!err && res) {
|
|
err = bus->ops->get_response(bus, addr, res);
|
|
trace_hda_get_response(bus, addr, *res);
|
|
}
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_exec_verb_unlocked);
|
|
|
|
/**
|
|
* snd_hdac_bus_queue_event - add an unsolicited event to queue
|
|
* @bus: the BUS
|
|
* @res: unsolicited event (lower 32bit of RIRB entry)
|
|
* @res_ex: codec addr and flags (upper 32bit or RIRB entry)
|
|
*
|
|
* Adds the given event to the queue. The events are processed in
|
|
* the workqueue asynchronously. Call this function in the interrupt
|
|
* hanlder when RIRB receives an unsolicited event.
|
|
*/
|
|
void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex)
|
|
{
|
|
unsigned int wp;
|
|
|
|
if (!bus)
|
|
return;
|
|
|
|
trace_hda_unsol_event(bus, res, res_ex);
|
|
wp = (bus->unsol_wp + 1) % HDA_UNSOL_QUEUE_SIZE;
|
|
bus->unsol_wp = wp;
|
|
|
|
wp <<= 1;
|
|
bus->unsol_queue[wp] = res;
|
|
bus->unsol_queue[wp + 1] = res_ex;
|
|
|
|
schedule_work(&bus->unsol_work);
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_queue_event);
|
|
|
|
/*
|
|
* process queued unsolicited events
|
|
*/
|
|
void snd_hdac_bus_process_unsol_events(struct work_struct *work)
|
|
{
|
|
struct hdac_bus *bus = container_of(work, struct hdac_bus, unsol_work);
|
|
struct hdac_device *codec;
|
|
struct hdac_driver *drv;
|
|
unsigned int rp, caddr, res;
|
|
|
|
while (bus->unsol_rp != bus->unsol_wp) {
|
|
rp = (bus->unsol_rp + 1) % HDA_UNSOL_QUEUE_SIZE;
|
|
bus->unsol_rp = rp;
|
|
rp <<= 1;
|
|
res = bus->unsol_queue[rp];
|
|
caddr = bus->unsol_queue[rp + 1];
|
|
if (!(caddr & (1 << 4))) /* no unsolicited event? */
|
|
continue;
|
|
codec = bus->caddr_tbl[caddr & 0x0f];
|
|
if (!codec || !codec->dev.driver)
|
|
continue;
|
|
drv = drv_to_hdac_driver(codec->dev.driver);
|
|
if (drv->unsol_event)
|
|
drv->unsol_event(codec, res);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_process_unsol_events);
|
|
|
|
/**
|
|
* snd_hdac_bus_add_device - Add a codec to bus
|
|
* @bus: HDA core bus
|
|
* @codec: HDA core device to add
|
|
*
|
|
* Adds the given codec to the list in the bus. The caddr_tbl array
|
|
* and codec_powered bits are updated, as well.
|
|
* Returns zero if success, or a negative error code.
|
|
*/
|
|
int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec)
|
|
{
|
|
if (bus->caddr_tbl[codec->addr]) {
|
|
dev_err(bus->dev, "address 0x%x is already occupied\n",
|
|
codec->addr);
|
|
return -EBUSY;
|
|
}
|
|
|
|
list_add_tail(&codec->list, &bus->codec_list);
|
|
bus->caddr_tbl[codec->addr] = codec;
|
|
set_bit(codec->addr, &bus->codec_powered);
|
|
bus->num_codecs++;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_add_device);
|
|
|
|
/**
|
|
* snd_hdac_bus_remove_device - Remove a codec from bus
|
|
* @bus: HDA core bus
|
|
* @codec: HDA core device to remove
|
|
*/
|
|
void snd_hdac_bus_remove_device(struct hdac_bus *bus,
|
|
struct hdac_device *codec)
|
|
{
|
|
WARN_ON(bus != codec->bus);
|
|
if (list_empty(&codec->list))
|
|
return;
|
|
list_del_init(&codec->list);
|
|
bus->caddr_tbl[codec->addr] = NULL;
|
|
clear_bit(codec->addr, &bus->codec_powered);
|
|
bus->num_codecs--;
|
|
flush_work(&bus->unsol_work);
|
|
}
|
|
EXPORT_SYMBOL_GPL(snd_hdac_bus_remove_device);
|