Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
# SPDX-License-Identifier: GPL-2.0-only
config SND_HDA_CORE
tristate
select REGMAP
config SND_HDA_DSP_LOADER
bool
config SND_HDA_ALIGNED_MMIO
bool
config SND_HDA_COMPONENT
bool
config SND_HDA_I915
bool
select SND_HDA_COMPONENT
config SND_HDA_EXT_CORE
tristate
select SND_HDA_CORE
config SND_INTEL_NHLT
bool
# this config should be selected only for Intel ACPI platforms.
# A fallback is provided so that the code compiles in all cases.
config SND_INTEL_DSP_CONFIG
tristate
select ACPI_NHLT if ACPI
select SND_INTEL_NHLT if ACPI
select SND_INTEL_SOUNDWIRE_ACPI if ACPI
# this config should be selected only for Intel DSP platforms.
# A fallback is provided so that the code compiles in all cases.
config SND_INTEL_SOUNDWIRE_ACPI
tristate
config SND_INTEL_BYT_PREFER_SOF
bool "Prefer SOF driver over SST on BY/CHT platforms"
depends on SND_SST_ATOM_HIFI2_PLATFORM_ACPI && SND_SOC_SOF_BAYTRAIL
default n
help
The kernel has 2 drivers for the Low Power Engine audio-block on
Bay- and Cherry-Trail SoCs. The old SST driver and the new SOF
driver. If both drivers are enabled then the kernel will default
to using the old SST driver, unless told otherwise through the
snd_intel_dspcfg.dsp_driver module-parameter.
Set this option to Y to make the kernel default to the new SOF
driver instead.
+22
View File
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0
snd-hda-core-y := hda_bus_type.o bus.o device.o sysfs.o \
regmap.o controller.o stream.o array.o hdmi_chmap.o
snd-hda-core-y += trace.o
CFLAGS_trace.o := -I$(src)
# for sync with i915 gfx driver
snd-hda-core-$(CONFIG_SND_HDA_COMPONENT) += component.o
snd-hda-core-$(CONFIG_SND_HDA_I915) += i915.o
obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o
#extended hda
obj-$(CONFIG_SND_HDA_EXT_CORE) += ext/
snd-intel-dspcfg-y := intel-dsp-config.o
snd-intel-dspcfg-$(CONFIG_SND_INTEL_NHLT) += intel-nhlt.o
obj-$(CONFIG_SND_INTEL_DSP_CONFIG) += snd-intel-dspcfg.o
snd-intel-sdw-acpi-y := intel-sdw-acpi.o
obj-$(CONFIG_SND_INTEL_SOUNDWIRE_ACPI) += snd-intel-sdw-acpi.o
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* generic arrays
*/
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/hdaudio.h>
/**
* snd_array_new - get a new element from the given array
* @array: the array object
*
* Get a new element from the given array. If it exceeds the
* pre-allocated array size, re-allocate the array.
*
* Returns NULL if allocation failed.
*/
void *snd_array_new(struct snd_array *array)
{
if (snd_BUG_ON(!array->elem_size))
return NULL;
if (array->used >= array->alloced) {
int num = array->alloced + array->alloc_align;
int oldsize = array->alloced * array->elem_size;
int size = (num + 1) * array->elem_size;
void *nlist;
if (snd_BUG_ON(num >= 4096))
return NULL;
nlist = krealloc(array->list, size, GFP_KERNEL);
if (!nlist)
return NULL;
memset(nlist + oldsize, 0, size - oldsize);
array->list = nlist;
array->alloced = num;
}
return snd_array_elem(array, array->used++);
}
EXPORT_SYMBOL_GPL(snd_array_new);
/**
* snd_array_free - free the given array elements
* @array: the array object
*/
void snd_array_free(struct snd_array *array)
{
kfree(array->list);
array->used = 0;
array->alloced = 0;
array->list = NULL;
}
EXPORT_SYMBOL_GPL(snd_array_free);
+286
View File
@@ -0,0 +1,286 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* HD-audio core bus driver
*/
#include <linux/init.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/export.h>
#include <sound/hdaudio.h>
#include "local.h"
#include "trace.h"
static void snd_hdac_bus_process_unsol_events(struct work_struct *work);
static const struct hdac_bus_ops default_ops = {
.command = snd_hdac_bus_send_cmd,
.get_response = snd_hdac_bus_get_response,
.link_power = snd_hdac_bus_link_power,
};
/**
* snd_hdac_bus_init - initialize a HD-audio bas bus
* @bus: the pointer to bus object
* @dev: device pointer
* @ops: bus verb 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)
{
memset(bus, 0, sizeof(*bus));
bus->dev = dev;
if (ops)
bus->ops = ops;
else
bus->ops = &default_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);
init_waitqueue_head(&bus->rirb_wq);
bus->irq = -1;
bus->addr_offset = 0;
/*
* Default value of '8' is as per the HD audio specification (Rev 1.0a).
* Following relation is used to derive STRIPE control value.
* For sample rate <= 48K:
* { ((num_channels * bits_per_sample) / number of SDOs) >= 8 }
* For sample rate > 48K:
* { ((num_channels * bits_per_sample * rate/48000) /
* number of SDOs) >= 8 }
*/
bus->sdo_limit = 8;
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
* @addr: the HDAC device address
* @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)
{
guard(mutex)(&bus->cmd_mutex);
return snd_hdac_bus_exec_verb_unlocked(bus, addr, cmd, res);
}
/**
* snd_hdac_bus_exec_verb_unlocked - unlocked version
* @bus: bus object
* @addr: the HDAC device address
* @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);
}
/*
* process queued unsolicited events
*/
static 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;
spin_lock_irq(&bus->reg_lock);
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->registered)
continue;
spin_unlock_irq(&bus->reg_lock);
drv = drv_to_hdac_driver(codec->dev.driver);
if (drv->unsol_event)
drv->unsol_event(codec, res);
spin_lock_irq(&bus->reg_lock);
}
spin_unlock_irq(&bus->reg_lock);
}
/**
* 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;
}
/**
* 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);
}
#ifdef CONFIG_SND_HDA_ALIGNED_MMIO
/* Helpers for aligned read/write of mmio space, for Tegra */
unsigned int snd_hdac_aligned_read(void __iomem *addr, unsigned int mask)
{
void __iomem *aligned_addr =
(void __iomem *)((unsigned long)(addr) & ~0x3);
unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
unsigned int v;
v = readl(aligned_addr);
return (v >> shift) & mask;
}
EXPORT_SYMBOL_GPL(snd_hdac_aligned_read);
void snd_hdac_aligned_write(unsigned int val, void __iomem *addr,
unsigned int mask)
{
void __iomem *aligned_addr =
(void __iomem *)((unsigned long)(addr) & ~0x3);
unsigned int shift = ((unsigned long)(addr) & 0x3) << 3;
unsigned int v;
v = readl(aligned_addr);
v &= ~(mask << shift);
v |= val << shift;
writel(v, aligned_addr);
}
EXPORT_SYMBOL_GPL(snd_hdac_aligned_write);
#endif /* CONFIG_SND_HDA_ALIGNED_MMIO */
void snd_hdac_codec_link_up(struct hdac_device *codec)
{
struct hdac_bus *bus = codec->bus;
if (bus->ops->link_power)
bus->ops->link_power(codec, true);
else
snd_hdac_bus_link_power(codec, true);
}
EXPORT_SYMBOL_GPL(snd_hdac_codec_link_up);
void snd_hdac_codec_link_down(struct hdac_device *codec)
{
struct hdac_bus *bus = codec->bus;
if (bus->ops->link_power)
bus->ops->link_power(codec, false);
else
snd_hdac_bus_link_power(codec, false);
}
EXPORT_SYMBOL_GPL(snd_hdac_codec_link_down);
+351
View File
@@ -0,0 +1,351 @@
// SPDX-License-Identifier: GPL-2.0
// hdac_component.c - routines for sync between HD-A core and DRM driver
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/component.h>
#include <linux/string_choices.h>
#include <sound/core.h>
#include <sound/hdaudio.h>
#include <sound/hda_component.h>
#include <sound/hda_register.h>
static void hdac_acomp_release(struct device *dev, void *res)
{
}
static struct drm_audio_component *hdac_get_acomp(struct device *dev)
{
return devres_find(dev, hdac_acomp_release, NULL, NULL);
}
/**
* snd_hdac_set_codec_wakeup - Enable / disable HDMI/DP codec wakeup
* @bus: HDA core bus
* @enable: enable or disable the wakeup
*
* This function is supposed to be used only by a HD-audio controller
* driver that needs the interaction with graphics driver.
*
* This function should be called during the chip reset, also called at
* resume for updating STATESTS register read.
*
* Returns zero for success or a negative error code.
*/
int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable)
{
struct drm_audio_component *acomp = bus->audio_component;
if (!acomp || !acomp->ops)
return -ENODEV;
if (!acomp->ops->codec_wake_override)
return 0;
dev_dbg(bus->dev, "%s codec wakeup\n", str_enable_disable(enable));
acomp->ops->codec_wake_override(acomp->dev, enable);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_set_codec_wakeup);
/**
* snd_hdac_display_power - Power up / down the power refcount
* @bus: HDA core bus
* @idx: HDA codec address, pass HDA_CODEC_IDX_CONTROLLER for controller
* @enable: power up or down
*
* This function is used by either HD-audio controller or codec driver that
* needs the interaction with graphics driver.
*
* This function updates the power status, and calls the get_power() and
* put_power() ops accordingly, toggling the codec wakeup, too.
*/
void snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable)
{
struct drm_audio_component *acomp = bus->audio_component;
dev_dbg(bus->dev, "display power %s\n", str_enable_disable(enable));
guard(mutex)(&bus->lock);
if (enable)
set_bit(idx, &bus->display_power_status);
else
clear_bit(idx, &bus->display_power_status);
if (!acomp || !acomp->ops)
return;
if (bus->display_power_status) {
if (!bus->display_power_active) {
unsigned long cookie = -1;
if (acomp->ops->get_power)
cookie = acomp->ops->get_power(acomp->dev);
snd_hdac_set_codec_wakeup(bus, true);
snd_hdac_set_codec_wakeup(bus, false);
bus->display_power_active = cookie;
}
} else {
if (bus->display_power_active) {
unsigned long cookie = bus->display_power_active;
if (acomp->ops->put_power)
acomp->ops->put_power(acomp->dev, cookie);
bus->display_power_active = 0;
}
}
}
EXPORT_SYMBOL_GPL(snd_hdac_display_power);
/**
* snd_hdac_sync_audio_rate - Set N/CTS based on the sample rate
* @codec: HDA codec
* @nid: the pin widget NID
* @dev_id: device identifier
* @rate: the sample rate to set
*
* This function is supposed to be used only by a HD-audio controller
* driver that needs the interaction with graphics driver.
*
* This function sets N/CTS value based on the given sample rate.
* Returns zero for success, or a negative error code.
*/
int snd_hdac_sync_audio_rate(struct hdac_device *codec, hda_nid_t nid,
int dev_id, int rate)
{
struct hdac_bus *bus = codec->bus;
struct drm_audio_component *acomp = bus->audio_component;
int port, pipe;
if (!acomp || !acomp->ops || !acomp->ops->sync_audio_rate)
return -ENODEV;
port = nid;
if (acomp->audio_ops && acomp->audio_ops->pin2port) {
port = acomp->audio_ops->pin2port(codec, nid);
if (port < 0)
return -EINVAL;
}
pipe = dev_id;
return acomp->ops->sync_audio_rate(acomp->dev, port, pipe, rate);
}
EXPORT_SYMBOL_GPL(snd_hdac_sync_audio_rate);
/**
* snd_hdac_acomp_get_eld - Get the audio state and ELD via component
* @codec: HDA codec
* @nid: the pin widget NID
* @dev_id: device identifier
* @audio_enabled: the pointer to store the current audio state
* @buffer: the buffer pointer to store ELD bytes
* @max_bytes: the max bytes to be stored on @buffer
*
* This function is supposed to be used only by a HD-audio controller
* driver that needs the interaction with graphics driver.
*
* This function queries the current state of the audio on the given
* digital port and fetches the ELD bytes onto the given buffer.
* It returns the number of bytes for the total ELD data, zero for
* invalid ELD, or a negative error code.
*
* The return size is the total bytes required for the whole ELD bytes,
* thus it may be over @max_bytes. If it's over @max_bytes, it implies
* that only a part of ELD bytes have been fetched.
*/
int snd_hdac_acomp_get_eld(struct hdac_device *codec, hda_nid_t nid, int dev_id,
bool *audio_enabled, char *buffer, int max_bytes)
{
struct hdac_bus *bus = codec->bus;
struct drm_audio_component *acomp = bus->audio_component;
int port, pipe;
if (!acomp || !acomp->ops || !acomp->ops->get_eld)
return -ENODEV;
port = nid;
if (acomp->audio_ops && acomp->audio_ops->pin2port) {
port = acomp->audio_ops->pin2port(codec, nid);
if (port < 0)
return -EINVAL;
}
pipe = dev_id;
return acomp->ops->get_eld(acomp->dev, port, pipe, audio_enabled,
buffer, max_bytes);
}
EXPORT_SYMBOL_GPL(snd_hdac_acomp_get_eld);
static int hdac_component_master_bind(struct device *dev)
{
struct drm_audio_component *acomp = hdac_get_acomp(dev);
int ret;
if (WARN_ON(!acomp))
return -EINVAL;
ret = component_bind_all(dev, acomp);
if (ret < 0)
return ret;
if (WARN_ON(!(acomp->dev && acomp->ops))) {
ret = -EINVAL;
goto out_unbind;
}
/* pin the module to avoid dynamic unbinding, but only if given */
if (!try_module_get(acomp->ops->owner)) {
ret = -ENODEV;
goto out_unbind;
}
if (acomp->audio_ops && acomp->audio_ops->master_bind) {
ret = acomp->audio_ops->master_bind(dev, acomp);
if (ret < 0)
goto module_put;
}
complete_all(&acomp->master_bind_complete);
return 0;
module_put:
module_put(acomp->ops->owner);
out_unbind:
component_unbind_all(dev, acomp);
complete_all(&acomp->master_bind_complete);
return ret;
}
static void hdac_component_master_unbind(struct device *dev)
{
struct drm_audio_component *acomp = hdac_get_acomp(dev);
if (acomp->audio_ops && acomp->audio_ops->master_unbind)
acomp->audio_ops->master_unbind(dev, acomp);
module_put(acomp->ops->owner);
component_unbind_all(dev, acomp);
WARN_ON(acomp->ops || acomp->dev);
}
static const struct component_master_ops hdac_component_master_ops = {
.bind = hdac_component_master_bind,
.unbind = hdac_component_master_unbind,
};
/**
* snd_hdac_acomp_register_notifier - Register audio component ops
* @bus: HDA core bus
* @aops: audio component ops
*
* This function is supposed to be used only by a HD-audio controller
* driver that needs the interaction with graphics driver.
*
* This function sets the given ops to be called by the graphics driver.
*
* Returns zero for success or a negative error code.
*/
int snd_hdac_acomp_register_notifier(struct hdac_bus *bus,
const struct drm_audio_component_audio_ops *aops)
{
if (!bus->audio_component)
return -ENODEV;
bus->audio_component->audio_ops = aops;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_acomp_register_notifier);
/**
* snd_hdac_acomp_init - Initialize audio component
* @bus: HDA core bus
* @aops: audio component ops
* @match_master: match function for finding components
* @extra_size: Extra bytes to allocate
*
* This function is supposed to be used only by a HD-audio controller
* driver that needs the interaction with graphics driver.
*
* This function initializes and sets up the audio component to communicate
* with graphics driver.
*
* Unlike snd_hdac_i915_init(), this function doesn't synchronize with the
* binding with the DRM component. Each caller needs to sync via master_bind
* audio_ops.
*
* Returns zero for success or a negative error code.
*/
int snd_hdac_acomp_init(struct hdac_bus *bus,
const struct drm_audio_component_audio_ops *aops,
int (*match_master)(struct device *, int, void *),
size_t extra_size)
{
struct component_match *match = NULL;
struct device *dev = bus->dev;
struct drm_audio_component *acomp;
int ret;
if (WARN_ON(hdac_get_acomp(dev)))
return -EBUSY;
acomp = devres_alloc(hdac_acomp_release, sizeof(*acomp) + extra_size,
GFP_KERNEL);
if (!acomp)
return -ENOMEM;
acomp->audio_ops = aops;
init_completion(&acomp->master_bind_complete);
bus->audio_component = acomp;
devres_add(dev, acomp);
component_match_add_typed(dev, &match, match_master, bus);
ret = component_master_add_with_match(dev, &hdac_component_master_ops,
match);
if (ret < 0)
goto out_err;
return 0;
out_err:
bus->audio_component = NULL;
devres_destroy(dev, hdac_acomp_release, NULL, NULL);
dev_info(dev, "failed to add audio component master (%d)\n", ret);
return ret;
}
EXPORT_SYMBOL_GPL(snd_hdac_acomp_init);
/**
* snd_hdac_acomp_exit - Finalize audio component
* @bus: HDA core bus
*
* This function is supposed to be used only by a HD-audio controller
* driver that needs the interaction with graphics driver.
*
* This function releases the audio component that has been used.
*
* Returns zero for success or a negative error code.
*/
int snd_hdac_acomp_exit(struct hdac_bus *bus)
{
struct device *dev = bus->dev;
struct drm_audio_component *acomp = bus->audio_component;
if (!acomp)
return 0;
if (WARN_ON(bus->display_power_active) && acomp->ops)
acomp->ops->put_power(acomp->dev, bus->display_power_active);
bus->display_power_active = 0;
bus->display_power_status = 0;
component_master_del(dev, &hdac_component_master_ops);
bus->audio_component = NULL;
devres_destroy(dev, hdac_acomp_release, NULL, NULL);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_acomp_exit);
+766
View File
@@ -0,0 +1,766 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* HD-audio controller helpers
*/
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/hdaudio.h>
#include <sound/hda_register.h>
#include "local.h"
/* clear CORB read pointer properly */
static void azx_clear_corbrp(struct hdac_bus *bus)
{
int timeout;
for (timeout = 1000; timeout > 0; timeout--) {
if (snd_hdac_chip_readw(bus, CORBRP) & AZX_CORBRP_RST)
break;
udelay(1);
}
if (timeout <= 0)
dev_err(bus->dev, "CORB reset timeout#1, CORBRP = %d\n",
snd_hdac_chip_readw(bus, CORBRP));
snd_hdac_chip_writew(bus, CORBRP, 0);
for (timeout = 1000; timeout > 0; timeout--) {
if (snd_hdac_chip_readw(bus, CORBRP) == 0)
break;
udelay(1);
}
if (timeout <= 0)
dev_err(bus->dev, "CORB reset timeout#2, CORBRP = %d\n",
snd_hdac_chip_readw(bus, CORBRP));
}
/**
* snd_hdac_bus_init_cmd_io - set up CORB/RIRB buffers
* @bus: HD-audio core bus
*/
void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus)
{
WARN_ON_ONCE(!bus->rb.area);
guard(spinlock_irq)(&bus->reg_lock);
/* CORB set up */
bus->corb.addr = bus->rb.addr;
bus->corb.buf = (__le32 *)bus->rb.area;
snd_hdac_chip_writel(bus, CORBLBASE, (u32)(bus->corb.addr + bus->addr_offset));
snd_hdac_chip_writel(bus, CORBUBASE, upper_32_bits(bus->corb.addr + bus->addr_offset));
/* set the corb size to 256 entries (ULI requires explicitly) */
snd_hdac_chip_writeb(bus, CORBSIZE, 0x02);
/* set the corb write pointer to 0 */
snd_hdac_chip_writew(bus, CORBWP, 0);
/* reset the corb hw read pointer */
snd_hdac_chip_writew(bus, CORBRP, AZX_CORBRP_RST);
if (!bus->corbrp_self_clear)
azx_clear_corbrp(bus);
/* enable corb dma */
if (!bus->use_pio_for_commands)
snd_hdac_chip_writeb(bus, CORBCTL, AZX_CORBCTL_RUN);
/* RIRB set up */
bus->rirb.addr = bus->rb.addr + 2048;
bus->rirb.buf = (__le32 *)(bus->rb.area + 2048);
bus->rirb.wp = bus->rirb.rp = 0;
memset(bus->rirb.cmds, 0, sizeof(bus->rirb.cmds));
snd_hdac_chip_writel(bus, RIRBLBASE, (u32)(bus->rirb.addr + bus->addr_offset));
snd_hdac_chip_writel(bus, RIRBUBASE, upper_32_bits(bus->rirb.addr + bus->addr_offset));
/* set the rirb size to 256 entries (ULI requires explicitly) */
snd_hdac_chip_writeb(bus, RIRBSIZE, 0x02);
/* reset the rirb hw write pointer */
snd_hdac_chip_writew(bus, RIRBWP, AZX_RIRBWP_RST);
/* set N=1, get RIRB response interrupt for new entry */
snd_hdac_chip_writew(bus, RINTCNT, 1);
/* enable rirb dma and response irq */
if (bus->not_use_interrupts)
snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN);
else
snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
/* Accept unsolicited responses */
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_init_cmd_io);
/* wait for cmd dmas till they are stopped */
static void hdac_wait_for_cmd_dmas(struct hdac_bus *bus)
{
unsigned long timeout;
timeout = jiffies + msecs_to_jiffies(100);
while ((snd_hdac_chip_readb(bus, RIRBCTL) & AZX_RBCTL_DMA_EN)
&& time_before(jiffies, timeout))
udelay(10);
timeout = jiffies + msecs_to_jiffies(100);
while ((snd_hdac_chip_readb(bus, CORBCTL) & AZX_CORBCTL_RUN)
&& time_before(jiffies, timeout))
udelay(10);
}
/**
* snd_hdac_bus_stop_cmd_io - clean up CORB/RIRB buffers
* @bus: HD-audio core bus
*/
void snd_hdac_bus_stop_cmd_io(struct hdac_bus *bus)
{
scoped_guard(spinlock_irq, &bus->reg_lock) {
/* disable ringbuffer DMAs */
snd_hdac_chip_writeb(bus, RIRBCTL, 0);
snd_hdac_chip_writeb(bus, CORBCTL, 0);
}
hdac_wait_for_cmd_dmas(bus);
guard(spinlock_irq)(&bus->reg_lock);
/* disable unsolicited responses */
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, 0);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_stop_cmd_io);
static unsigned int azx_command_addr(u32 cmd)
{
unsigned int addr = cmd >> 28;
if (snd_BUG_ON(addr >= HDA_MAX_CODECS))
addr = 0;
return addr;
}
/* receive an Immediate Response with PIO */
static int snd_hdac_bus_wait_for_pio_response(struct hdac_bus *bus,
unsigned int addr)
{
int timeout = 50;
while (timeout--) {
/* check IRV bit */
if (snd_hdac_chip_readw(bus, IRS) & AZX_IRS_VALID) {
/* reuse rirb.res as the response return value */
bus->rirb.res[addr] = snd_hdac_chip_readl(bus, IR);
return 0;
}
udelay(1);
}
dev_dbg_ratelimited(bus->dev, "get_response_pio timeout: IRS=%#x\n",
snd_hdac_chip_readw(bus, IRS));
bus->rirb.res[addr] = -1;
return -EIO;
}
/**
* snd_hdac_bus_send_cmd_pio - send a command verb via Immediate Command
* @bus: HD-audio core bus
* @val: encoded verb value to send
*
* Returns zero for success or a negative error code.
*/
static int snd_hdac_bus_send_cmd_pio(struct hdac_bus *bus, unsigned int val)
{
unsigned int addr = azx_command_addr(val);
int timeout = 50;
guard(spinlock_irq)(&bus->reg_lock);
while (timeout--) {
/* check ICB bit */
if (!((snd_hdac_chip_readw(bus, IRS) & AZX_IRS_BUSY))) {
/* Clear IRV bit */
snd_hdac_chip_updatew(bus, IRS, AZX_IRS_VALID, AZX_IRS_VALID);
snd_hdac_chip_writel(bus, IC, val);
/* Set ICB bit */
snd_hdac_chip_updatew(bus, IRS, AZX_IRS_BUSY, AZX_IRS_BUSY);
return snd_hdac_bus_wait_for_pio_response(bus, addr);
}
udelay(1);
}
dev_dbg_ratelimited(bus->dev, "send_cmd_pio timeout: IRS=%#x, val=%#x\n",
snd_hdac_chip_readw(bus, IRS), val);
return -EIO;
}
/**
* snd_hdac_bus_get_response_pio - receive a response via Immediate Response
* @bus: HD-audio core bus
* @addr: codec address
* @res: pointer to store the value, NULL when not needed
*
* Returns zero if a value is read, or a negative error code.
*/
static int snd_hdac_bus_get_response_pio(struct hdac_bus *bus,
unsigned int addr, unsigned int *res)
{
if (res)
*res = bus->rirb.res[addr];
return 0;
}
/**
* snd_hdac_bus_send_cmd_corb - send a command verb via CORB
* @bus: HD-audio core bus
* @val: encoded verb value to send
*
* Returns zero for success or a negative error code.
*/
static int snd_hdac_bus_send_cmd_corb(struct hdac_bus *bus, unsigned int val)
{
unsigned int addr = azx_command_addr(val);
unsigned int wp, rp;
guard(spinlock_irq)(&bus->reg_lock);
bus->last_cmd[azx_command_addr(val)] = val;
/* add command to corb */
wp = snd_hdac_chip_readw(bus, CORBWP);
if (wp == 0xffff) {
/* something wrong, controller likely turned to D3 */
return -EIO;
}
wp++;
wp %= AZX_MAX_CORB_ENTRIES;
rp = snd_hdac_chip_readw(bus, CORBRP);
if (wp == rp) {
/* oops, it's full */
return -EAGAIN;
}
bus->rirb.cmds[addr]++;
bus->corb.buf[wp] = cpu_to_le32(val);
snd_hdac_chip_writew(bus, CORBWP, wp);
return 0;
}
#define AZX_RIRB_EX_UNSOL_EV (1<<4)
/**
* snd_hdac_bus_update_rirb - retrieve RIRB entries
* @bus: HD-audio core bus
*
* Usually called from interrupt handler.
* The caller needs bus->reg_lock spinlock before calling this.
*/
void snd_hdac_bus_update_rirb(struct hdac_bus *bus)
{
unsigned int rp, wp;
unsigned int addr;
u32 res, res_ex;
wp = snd_hdac_chip_readw(bus, RIRBWP);
if (wp == 0xffff) {
/* something wrong, controller likely turned to D3 */
return;
}
if (wp == bus->rirb.wp)
return;
bus->rirb.wp = wp;
while (bus->rirb.rp != wp) {
bus->rirb.rp++;
bus->rirb.rp %= AZX_MAX_RIRB_ENTRIES;
rp = bus->rirb.rp << 1; /* an RIRB entry is 8-bytes */
res_ex = le32_to_cpu(bus->rirb.buf[rp + 1]);
res = le32_to_cpu(bus->rirb.buf[rp]);
addr = res_ex & 0xf;
if (addr >= HDA_MAX_CODECS) {
dev_err(bus->dev,
"spurious response %#x:%#x, rp = %d, wp = %d",
res, res_ex, bus->rirb.rp, wp);
snd_BUG();
} else if (res_ex & AZX_RIRB_EX_UNSOL_EV)
snd_hdac_bus_queue_event(bus, res, res_ex);
else if (bus->rirb.cmds[addr]) {
bus->rirb.res[addr] = res;
bus->rirb.cmds[addr]--;
if (!bus->rirb.cmds[addr] &&
waitqueue_active(&bus->rirb_wq))
wake_up(&bus->rirb_wq);
} else {
dev_err_ratelimited(bus->dev,
"spurious response %#x:%#x, last cmd=%#08x\n",
res, res_ex, bus->last_cmd[addr]);
}
}
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_update_rirb);
/**
* snd_hdac_bus_get_response_rirb - receive a response via RIRB
* @bus: HD-audio core bus
* @addr: codec address
* @res: pointer to store the value, NULL when not needed
*
* Returns zero if a value is read, or a negative error code.
*/
static int snd_hdac_bus_get_response_rirb(struct hdac_bus *bus,
unsigned int addr, unsigned int *res)
{
unsigned long timeout;
unsigned long loopcounter;
wait_queue_entry_t wait;
bool warned = false;
init_wait_entry(&wait, 0);
timeout = jiffies + msecs_to_jiffies(1000);
for (loopcounter = 0;; loopcounter++) {
scoped_guard(spinlock_irq, &bus->reg_lock) {
if (!bus->polling_mode)
prepare_to_wait(&bus->rirb_wq, &wait,
TASK_UNINTERRUPTIBLE);
if (bus->polling_mode)
snd_hdac_bus_update_rirb(bus);
if (!bus->rirb.cmds[addr]) {
if (res)
*res = bus->rirb.res[addr]; /* the last value */
if (!bus->polling_mode)
finish_wait(&bus->rirb_wq, &wait);
return 0;
}
}
if (time_after(jiffies, timeout))
break;
#define LOOP_COUNT_MAX 3000
if (!bus->polling_mode) {
schedule_timeout(msecs_to_jiffies(2));
} else if (bus->needs_damn_long_delay ||
loopcounter > LOOP_COUNT_MAX) {
if (loopcounter > LOOP_COUNT_MAX && !warned) {
dev_dbg_ratelimited(bus->dev,
"too slow response, last cmd=%#08x\n",
bus->last_cmd[addr]);
warned = true;
}
msleep(2); /* temporary workaround */
} else {
udelay(10);
cond_resched();
}
}
if (!bus->polling_mode)
finish_wait(&bus->rirb_wq, &wait);
return -EIO;
}
/**
* snd_hdac_bus_send_cmd - send a command verb via CORB or PIO
* @bus: HD-audio core bus
* @val: encoded verb value to send
*
* Returns zero for success or a negative error code.
*/
int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val)
{
if (bus->use_pio_for_commands)
return snd_hdac_bus_send_cmd_pio(bus, val);
return snd_hdac_bus_send_cmd_corb(bus, val);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_send_cmd);
/**
* snd_hdac_bus_get_response - receive a response via RIRB or PIO
* @bus: HD-audio core bus
* @addr: codec address
* @res: pointer to store the value, NULL when not needed
*
* Returns zero if a value is read, or a negative error code.
*/
int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr,
unsigned int *res)
{
if (bus->use_pio_for_commands)
return snd_hdac_bus_get_response_pio(bus, addr, res);
return snd_hdac_bus_get_response_rirb(bus, addr, res);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_get_response);
#define HDAC_MAX_CAPS 10
/**
* snd_hdac_bus_parse_capabilities - parse capability structure
* @bus: the pointer to bus object
*
* Returns 0 if successful, or a negative error code.
*/
int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus)
{
unsigned int cur_cap;
unsigned int offset;
unsigned int counter = 0;
offset = snd_hdac_chip_readw(bus, LLCH);
/* Lets walk the linked capabilities list */
do {
cur_cap = _snd_hdac_chip_readl(bus, offset);
dev_dbg(bus->dev, "Capability version: 0x%x\n",
(cur_cap & AZX_CAP_HDR_VER_MASK) >> AZX_CAP_HDR_VER_OFF);
dev_dbg(bus->dev, "HDA capability ID: 0x%x\n",
(cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF);
if (cur_cap == -1) {
dev_dbg(bus->dev, "Invalid capability reg read\n");
break;
}
switch ((cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF) {
case AZX_ML_CAP_ID:
dev_dbg(bus->dev, "Found ML capability\n");
bus->mlcap = bus->remap_addr + offset;
break;
case AZX_GTS_CAP_ID:
dev_dbg(bus->dev, "Found GTS capability offset=%x\n", offset);
bus->gtscap = bus->remap_addr + offset;
break;
case AZX_PP_CAP_ID:
/* PP capability found, the Audio DSP is present */
dev_dbg(bus->dev, "Found PP capability offset=%x\n", offset);
bus->ppcap = bus->remap_addr + offset;
break;
case AZX_SPB_CAP_ID:
/* SPIB capability found, handler function */
dev_dbg(bus->dev, "Found SPB capability\n");
bus->spbcap = bus->remap_addr + offset;
break;
case AZX_DRSM_CAP_ID:
/* DMA resume capability found, handler function */
dev_dbg(bus->dev, "Found DRSM capability\n");
bus->drsmcap = bus->remap_addr + offset;
break;
default:
dev_err(bus->dev, "Unknown capability %d\n", cur_cap);
cur_cap = 0;
break;
}
counter++;
if (counter > HDAC_MAX_CAPS) {
dev_err(bus->dev, "We exceeded HDAC capabilities!!!\n");
break;
}
/* read the offset of next capability */
offset = cur_cap & AZX_CAP_HDR_NXT_PTR_MASK;
} while (offset);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_parse_capabilities);
/*
* Lowlevel interface
*/
/**
* snd_hdac_bus_enter_link_reset - enter link reset
* @bus: HD-audio core bus
*
* Enter to the link reset state.
*/
void snd_hdac_bus_enter_link_reset(struct hdac_bus *bus)
{
unsigned long timeout;
/* reset controller */
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_RESET, 0);
timeout = jiffies + msecs_to_jiffies(100);
while ((snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET) &&
time_before(jiffies, timeout))
usleep_range(500, 1000);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_enter_link_reset);
/**
* snd_hdac_bus_exit_link_reset - exit link reset
* @bus: HD-audio core bus
*
* Exit from the link reset state.
*/
void snd_hdac_bus_exit_link_reset(struct hdac_bus *bus)
{
unsigned long timeout;
snd_hdac_chip_updateb(bus, GCTL, AZX_GCTL_RESET, AZX_GCTL_RESET);
timeout = jiffies + msecs_to_jiffies(100);
while (!snd_hdac_chip_readb(bus, GCTL) && time_before(jiffies, timeout))
usleep_range(500, 1000);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_exit_link_reset);
/* reset codec link */
int snd_hdac_bus_reset_link(struct hdac_bus *bus, bool full_reset)
{
if (!full_reset)
goto skip_reset;
/* clear STATESTS if not in reset */
if (snd_hdac_chip_readb(bus, GCTL) & AZX_GCTL_RESET)
snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK);
/* reset controller */
snd_hdac_bus_enter_link_reset(bus);
/* delay for >= 100us for codec PLL to settle per spec
* Rev 0.9 section 5.5.1
*/
usleep_range(500, 1000);
/* Bring controller out of reset */
snd_hdac_bus_exit_link_reset(bus);
/* Brent Chartrand said to wait >= 540us for codecs to initialize */
usleep_range(1000, 1200);
skip_reset:
/* check to see if controller is ready */
if (!snd_hdac_chip_readb(bus, GCTL)) {
dev_dbg(bus->dev, "controller not ready!\n");
return -EBUSY;
}
/* detect codecs */
if (!bus->codec_mask) {
bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS);
dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask);
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_reset_link);
/* enable interrupts */
static void azx_int_enable(struct hdac_bus *bus)
{
/* enable controller CIE and GIE */
snd_hdac_chip_updatel(bus, INTCTL,
AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN,
AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN);
}
/* disable interrupts */
static void azx_int_disable(struct hdac_bus *bus)
{
struct hdac_stream *azx_dev;
/* disable interrupts in stream descriptor */
list_for_each_entry(azx_dev, &bus->stream_list, list)
snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0);
/* disable SIE for all streams & disable controller CIE and GIE */
snd_hdac_chip_writel(bus, INTCTL, 0);
}
/* clear interrupts */
static void azx_int_clear(struct hdac_bus *bus)
{
struct hdac_stream *azx_dev;
/* clear stream status */
list_for_each_entry(azx_dev, &bus->stream_list, list)
snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
/* clear STATESTS */
snd_hdac_chip_writew(bus, STATESTS, STATESTS_INT_MASK);
/* clear rirb status */
snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
/* clear int status */
snd_hdac_chip_writel(bus, INTSTS, AZX_INT_CTRL_EN | AZX_INT_ALL_STREAM);
}
/**
* snd_hdac_bus_init_chip - reset and start the controller registers
* @bus: HD-audio core bus
* @full_reset: Do full reset
*/
bool snd_hdac_bus_init_chip(struct hdac_bus *bus, bool full_reset)
{
if (bus->chip_init)
return false;
/* reset controller */
snd_hdac_bus_reset_link(bus, full_reset);
/* clear interrupts */
azx_int_clear(bus);
/* initialize the codec command I/O */
snd_hdac_bus_init_cmd_io(bus);
/* enable interrupts after CORB/RIRB buffers are initialized above */
azx_int_enable(bus);
/* program the position buffer */
if (bus->use_posbuf && bus->posbuf.addr) {
snd_hdac_chip_writel(bus, DPLBASE, (u32)(bus->posbuf.addr + bus->addr_offset));
snd_hdac_chip_writel(bus, DPUBASE, upper_32_bits(bus->posbuf.addr + bus->addr_offset));
}
bus->chip_init = true;
return true;
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_init_chip);
/**
* snd_hdac_bus_stop_chip - disable the whole IRQ and I/Os
* @bus: HD-audio core bus
*/
void snd_hdac_bus_stop_chip(struct hdac_bus *bus)
{
if (!bus->chip_init)
return;
/* disable interrupts */
azx_int_disable(bus);
azx_int_clear(bus);
/* disable CORB/RIRB */
snd_hdac_bus_stop_cmd_io(bus);
/* disable position buffer */
if (bus->posbuf.addr) {
snd_hdac_chip_writel(bus, DPLBASE, 0);
snd_hdac_chip_writel(bus, DPUBASE, 0);
}
bus->chip_init = false;
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_stop_chip);
/**
* snd_hdac_bus_handle_stream_irq - interrupt handler for streams
* @bus: HD-audio core bus
* @status: INTSTS register value
* @ack: callback to be called for woken streams
*
* Returns the bits of handled streams, or zero if no stream is handled.
*/
int snd_hdac_bus_handle_stream_irq(struct hdac_bus *bus, unsigned int status,
void (*ack)(struct hdac_bus *,
struct hdac_stream *))
{
struct hdac_stream *azx_dev;
u8 sd_status;
int handled = 0;
list_for_each_entry(azx_dev, &bus->stream_list, list) {
if (status & azx_dev->sd_int_sta_mask) {
sd_status = snd_hdac_stream_readb(azx_dev, SD_STS);
snd_hdac_stream_writeb(azx_dev, SD_STS, SD_INT_MASK);
handled |= 1 << azx_dev->index;
if ((!azx_dev->substream && !azx_dev->cstream) ||
!azx_dev->running || !(sd_status & SD_INT_COMPLETE))
continue;
if (ack)
ack(bus, azx_dev);
}
}
return handled;
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_handle_stream_irq);
/**
* snd_hdac_bus_alloc_stream_pages - allocate BDL and other buffers
* @bus: HD-audio core bus
*
* Call this after assigning the all streams.
* Returns zero for success, or a negative error code.
*/
int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus)
{
struct hdac_stream *s;
int num_streams = 0;
int dma_type = bus->dma_type ? bus->dma_type : SNDRV_DMA_TYPE_DEV;
int err;
list_for_each_entry(s, &bus->stream_list, list) {
/* allocate memory for the BDL for each stream */
err = snd_dma_alloc_pages(dma_type, bus->dev,
BDL_SIZE, &s->bdl);
num_streams++;
if (err < 0)
return -ENOMEM;
}
if (WARN_ON(!num_streams))
return -EINVAL;
/* allocate memory for the position buffer */
err = snd_dma_alloc_pages(dma_type, bus->dev,
num_streams * 8, &bus->posbuf);
if (err < 0)
return -ENOMEM;
list_for_each_entry(s, &bus->stream_list, list)
s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8);
/* single page (at least 4096 bytes) must suffice for both ringbuffes */
return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_alloc_stream_pages);
/**
* snd_hdac_bus_free_stream_pages - release BDL and other buffers
* @bus: HD-audio core bus
*/
void snd_hdac_bus_free_stream_pages(struct hdac_bus *bus)
{
struct hdac_stream *s;
list_for_each_entry(s, &bus->stream_list, list) {
if (s->bdl.area)
snd_dma_free_pages(&s->bdl);
}
if (bus->rb.area)
snd_dma_free_pages(&bus->rb);
if (bus->posbuf.area)
snd_dma_free_pages(&bus->posbuf);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages);
/**
* snd_hdac_bus_link_power - power up/down codec link
* @codec: HD-audio device
* @enable: whether to power-up the link
*/
void snd_hdac_bus_link_power(struct hdac_device *codec, bool enable)
{
if (enable)
set_bit(codec->addr, &codec->bus->codec_powered);
else
clear_bit(codec->addr, &codec->bus->codec_powered);
}
EXPORT_SYMBOL_GPL(snd_hdac_bus_link_power);
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
snd-hda-ext-core-y := bus.o controller.o stream.o
obj-$(CONFIG_SND_HDA_EXT_CORE) += snd-hda-ext-core.o
+142
View File
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* hdac-ext-bus.c - HD-audio extended core bus functions.
*
* Copyright (C) 2014-2015 Intel Corp
* Author: Jeeja KP <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <sound/hdaudio_ext.h>
MODULE_DESCRIPTION("HDA extended core");
MODULE_LICENSE("GPL v2");
/**
* snd_hdac_ext_bus_init - initialize a HD-audio extended bus
* @bus: the pointer to HDAC bus object
* @dev: device pointer
* @ops: bus verb operators
* @ext_ops: operators used for ASoC HDA codec drivers
*
* Returns 0 if successful, or a negative error code.
*/
int snd_hdac_ext_bus_init(struct hdac_bus *bus, struct device *dev,
const struct hdac_bus_ops *ops,
const struct hdac_ext_bus_ops *ext_ops)
{
int ret;
ret = snd_hdac_bus_init(bus, dev, ops);
if (ret < 0)
return ret;
bus->ext_ops = ext_ops;
/* FIXME:
* Currently only one bus is supported, if there is device with more
* buses, bus->idx should be greater than 0, but there needs to be a
* reliable way to always assign same number.
*/
bus->idx = 0;
bus->cmd_dma_state = true;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_init);
/**
* snd_hdac_ext_bus_exit - clean up a HD-audio extended bus
* @bus: the pointer to HDAC bus object
*/
void snd_hdac_ext_bus_exit(struct hdac_bus *bus)
{
snd_hdac_bus_exit(bus);
WARN_ON(!list_empty(&bus->hlink_list));
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_exit);
/**
* snd_hdac_ext_bus_device_remove - remove HD-audio extended codec base devices
*
* @bus: the pointer to HDAC bus object
*/
void snd_hdac_ext_bus_device_remove(struct hdac_bus *bus)
{
struct hdac_device *codec, *__codec;
/*
* we need to remove all the codec devices objects created in the
* snd_hdac_ext_bus_device_init
*/
list_for_each_entry_safe(codec, __codec, &bus->codec_list, list) {
snd_hdac_device_unregister(codec);
put_device(&codec->dev);
}
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_remove);
#define dev_to_hdac(dev) (container_of((dev), \
struct hdac_device, dev))
static inline struct hdac_driver *get_hdrv(struct device *dev)
{
struct hdac_driver *hdrv = drv_to_hdac_driver(dev->driver);
return hdrv;
}
static inline struct hdac_device *get_hdev(struct device *dev)
{
struct hdac_device *hdev = dev_to_hdac_dev(dev);
return hdev;
}
static int hda_ext_drv_probe(struct device *dev)
{
return (get_hdrv(dev))->probe(get_hdev(dev));
}
static int hdac_ext_drv_remove(struct device *dev)
{
return (get_hdrv(dev))->remove(get_hdev(dev));
}
static void hdac_ext_drv_shutdown(struct device *dev)
{
return (get_hdrv(dev))->shutdown(get_hdev(dev));
}
/**
* snd_hda_ext_driver_register - register a driver for ext hda devices
*
* @drv: ext hda driver structure
*/
int snd_hda_ext_driver_register(struct hdac_driver *drv)
{
drv->type = HDA_DEV_ASOC;
drv->driver.bus = &snd_hda_bus_type;
/* we use default match */
if (drv->probe)
drv->driver.probe = hda_ext_drv_probe;
if (drv->remove)
drv->driver.remove = hdac_ext_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = hdac_ext_drv_shutdown;
return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(snd_hda_ext_driver_register);
/**
* snd_hda_ext_driver_unregister - unregister a driver for ext hda devices
*
* @drv: ext hda driver structure
*/
void snd_hda_ext_driver_unregister(struct hdac_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(snd_hda_ext_driver_unregister);
+410
View File
@@ -0,0 +1,410 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* hdac-ext-controller.c - HD-audio extended controller functions.
*
* Copyright (C) 2014-2015 Intel Corp
* Author: Jeeja KP <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <sound/hda_register.h>
#include <sound/hdaudio_ext.h>
/*
* processing pipe helpers - these helpers are useful for dealing with HDA
* new capability of processing pipelines
*/
/**
* snd_hdac_ext_bus_ppcap_enable - enable/disable processing pipe capability
* @bus: the pointer to HDAC bus object
* @enable: flag to turn on/off the capability
*/
void snd_hdac_ext_bus_ppcap_enable(struct hdac_bus *bus, bool enable)
{
if (!bus->ppcap) {
dev_err(bus->dev, "Address of PP capability is NULL");
return;
}
if (enable)
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
AZX_PPCTL_GPROCEN, AZX_PPCTL_GPROCEN);
else
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
AZX_PPCTL_GPROCEN, 0);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_enable);
/**
* snd_hdac_ext_bus_ppcap_int_enable - ppcap interrupt enable/disable
* @bus: the pointer to HDAC bus object
* @enable: flag to enable/disable interrupt
*/
void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_bus *bus, bool enable)
{
if (!bus->ppcap) {
dev_err(bus->dev, "Address of PP capability is NULL\n");
return;
}
if (enable)
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
AZX_PPCTL_PIE, AZX_PPCTL_PIE);
else
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
AZX_PPCTL_PIE, 0);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_int_enable);
/*
* Multilink helpers - these helpers are useful for dealing with HDA
* new multilink capability
*/
/**
* snd_hdac_ext_bus_get_ml_capabilities - get multilink capability
* @bus: the pointer to HDAC bus object
*
* This will parse all links and read the mlink capabilities and add them
* in hlink_list of extended hdac bus
* Note: this will be freed on bus exit by driver
*/
int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus)
{
int idx;
u32 link_count;
struct hdac_ext_link *hlink;
u32 leptr;
link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count);
for (idx = 0; idx < link_count; idx++) {
hlink = kzalloc_obj(*hlink);
if (!hlink)
return -ENOMEM;
hlink->index = idx;
hlink->bus = bus;
hlink->ml_addr = bus->mlcap + AZX_ML_BASE +
(AZX_ML_INTERVAL * idx);
hlink->lcaps = readl(hlink->ml_addr + AZX_REG_ML_LCAP);
hlink->lsdiid = readw(hlink->ml_addr + AZX_REG_ML_LSDIID);
hlink->slcount = FIELD_GET(AZX_ML_HDA_LCAP_SLCOUNT, hlink->lcaps) + 1;
if (hdac_ext_link_alt(hlink)) {
leptr = readl(hlink->ml_addr + AZX_REG_ML_LEPTR);
hlink->id = FIELD_GET(AZX_REG_ML_LEPTR_ID, leptr);
}
/* since link in On, update the ref */
hlink->ref_count = 1;
list_add_tail(&hlink->list, &bus->hlink_list);
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_ml_capabilities);
/**
* snd_hdac_ext_link_free_all- free hdac extended link objects
*
* @bus: the pointer to HDAC bus object
*/
void snd_hdac_ext_link_free_all(struct hdac_bus *bus)
{
struct hdac_ext_link *hlink;
while (!list_empty(&bus->hlink_list)) {
hlink = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list);
list_del(&hlink->list);
kfree(hlink);
}
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_link_free_all);
struct hdac_ext_link *snd_hdac_ext_bus_get_hlink_by_id(struct hdac_bus *bus, u32 id)
{
struct hdac_ext_link *hlink;
list_for_each_entry(hlink, &bus->hlink_list, list)
if (hdac_ext_link_alt(hlink) && hlink->id == id)
return hlink;
return NULL;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_hlink_by_id);
/**
* snd_hdac_ext_bus_get_hlink_by_addr - get hlink at specified address
* @bus: hlink's parent bus device
* @addr: codec device address
*
* Returns hlink object or NULL if matching hlink is not found.
*/
struct hdac_ext_link *snd_hdac_ext_bus_get_hlink_by_addr(struct hdac_bus *bus, int addr)
{
struct hdac_ext_link *hlink;
list_for_each_entry(hlink, &bus->hlink_list, list)
if (hlink->lsdiid & (0x1 << addr))
return hlink;
return NULL;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_hlink_by_addr);
/**
* snd_hdac_ext_bus_get_hlink_by_name - get hlink based on codec name
* @bus: the pointer to HDAC bus object
* @codec_name: codec name
*/
struct hdac_ext_link *snd_hdac_ext_bus_get_hlink_by_name(struct hdac_bus *bus,
const char *codec_name)
{
int bus_idx, addr;
if (sscanf(codec_name, "ehdaudio%dD%d", &bus_idx, &addr) != 2)
return NULL;
if (bus->idx != bus_idx)
return NULL;
if (addr < 0 || addr > 31)
return NULL;
return snd_hdac_ext_bus_get_hlink_by_addr(bus, addr);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_hlink_by_name);
static int check_hdac_link_power_active(struct hdac_ext_link *hlink, bool enable)
{
int timeout;
u32 val;
int mask = (1 << AZX_ML_LCTL_CPA_SHIFT);
udelay(3);
timeout = 150;
do {
val = readl(hlink->ml_addr + AZX_REG_ML_LCTL);
if (enable) {
if (((val & mask) >> AZX_ML_LCTL_CPA_SHIFT))
return 0;
} else {
if (!((val & mask) >> AZX_ML_LCTL_CPA_SHIFT))
return 0;
}
udelay(3);
} while (--timeout);
return -EIO;
}
/**
* snd_hdac_ext_bus_link_power_up -power up hda link
* @hlink: HD-audio extended link
*/
int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *hlink)
{
snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL,
AZX_ML_LCTL_SPA, AZX_ML_LCTL_SPA);
return check_hdac_link_power_active(hlink, true);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up);
/**
* snd_hdac_ext_bus_link_power_down -power down hda link
* @hlink: HD-audio extended link
*/
int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *hlink)
{
snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL, AZX_ML_LCTL_SPA, 0);
return check_hdac_link_power_active(hlink, false);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down);
/**
* snd_hdac_ext_bus_link_power_up_all -power up all hda link
* @bus: the pointer to HDAC bus object
*/
int snd_hdac_ext_bus_link_power_up_all(struct hdac_bus *bus)
{
struct hdac_ext_link *hlink = NULL;
int ret;
list_for_each_entry(hlink, &bus->hlink_list, list) {
ret = snd_hdac_ext_bus_link_power_up(hlink);
if (ret < 0)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up_all);
/**
* snd_hdac_ext_bus_link_power_down_all -power down all hda link
* @bus: the pointer to HDAC bus object
*/
int snd_hdac_ext_bus_link_power_down_all(struct hdac_bus *bus)
{
struct hdac_ext_link *hlink = NULL;
int ret;
list_for_each_entry(hlink, &bus->hlink_list, list) {
ret = snd_hdac_ext_bus_link_power_down(hlink);
if (ret < 0)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down_all);
/**
* snd_hdac_ext_bus_link_set_stream_id - maps stream id to link output
* @link: HD-audio ext link to set up
* @stream: stream id
*/
void snd_hdac_ext_bus_link_set_stream_id(struct hdac_ext_link *link,
int stream)
{
snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 1 << stream);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_set_stream_id);
/**
* snd_hdac_ext_bus_link_clear_stream_id - maps stream id to link output
* @link: HD-audio ext link to set up
* @stream: stream id
*/
void snd_hdac_ext_bus_link_clear_stream_id(struct hdac_ext_link *link,
int stream)
{
snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV, (1 << stream), 0);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_clear_stream_id);
int snd_hdac_ext_bus_link_get(struct hdac_bus *bus,
struct hdac_ext_link *hlink)
{
unsigned long codec_mask;
int ret = 0;
guard(mutex)(&bus->lock);
/*
* if we move from 0 to 1, count will be 1 so power up this link
* as well, also check the dma status and trigger that
*/
if (++hlink->ref_count == 1) {
if (!bus->cmd_dma_state) {
snd_hdac_bus_init_cmd_io(bus);
bus->cmd_dma_state = true;
}
ret = snd_hdac_ext_bus_link_power_up(hlink);
/*
* clear the register to invalidate all the output streams
*/
snd_hdac_updatew(hlink->ml_addr, AZX_REG_ML_LOSIDV,
AZX_ML_LOSIDV_STREAM_MASK, 0);
/*
* wait for 521usec for codec to report status
* HDA spec section 4.3 - Codec Discovery
*/
udelay(521);
codec_mask = snd_hdac_chip_readw(bus, STATESTS);
dev_dbg(bus->dev, "codec_mask = 0x%lx\n", codec_mask);
snd_hdac_chip_writew(bus, STATESTS, codec_mask);
if (!bus->codec_mask)
bus->codec_mask = codec_mask;
}
return ret;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_get);
int snd_hdac_ext_bus_link_put(struct hdac_bus *bus,
struct hdac_ext_link *hlink)
{
int ret = 0;
struct hdac_ext_link *hlink_tmp;
bool link_up = false;
guard(mutex)(&bus->lock);
/*
* if we move from 1 to 0, count will be 0
* so power down this link as well
*/
if (--hlink->ref_count == 0) {
ret = snd_hdac_ext_bus_link_power_down(hlink);
/*
* now check if all links are off, if so turn off
* cmd dma as well
*/
list_for_each_entry(hlink_tmp, &bus->hlink_list, list) {
if (hlink_tmp->ref_count) {
link_up = true;
break;
}
}
if (!link_up) {
snd_hdac_bus_stop_cmd_io(bus);
bus->cmd_dma_state = false;
}
}
return ret;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_put);
static void hdac_ext_codec_link_up(struct hdac_device *codec)
{
const char *devname = dev_name(&codec->dev);
struct hdac_ext_link *hlink =
snd_hdac_ext_bus_get_hlink_by_name(codec->bus, devname);
if (hlink)
snd_hdac_ext_bus_link_get(codec->bus, hlink);
}
static void hdac_ext_codec_link_down(struct hdac_device *codec)
{
const char *devname = dev_name(&codec->dev);
struct hdac_ext_link *hlink =
snd_hdac_ext_bus_get_hlink_by_name(codec->bus, devname);
if (hlink)
snd_hdac_ext_bus_link_put(codec->bus, hlink);
}
void snd_hdac_ext_bus_link_power(struct hdac_device *codec, bool enable)
{
struct hdac_bus *bus = codec->bus;
bool oldstate = test_bit(codec->addr, &bus->codec_powered);
if (enable == oldstate)
return;
snd_hdac_bus_link_power(codec, enable);
if (enable)
hdac_ext_codec_link_up(codec);
else
hdac_ext_codec_link_down(codec);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power);
+448
View File
@@ -0,0 +1,448 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* hdac-ext-stream.c - HD-audio extended stream operations.
*
* Copyright (C) 2015 Intel Corp
* Author: Jeeja KP <jeeja.kp@intel.com>
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/hda_register.h>
#include <sound/hdaudio_ext.h>
#include <sound/compress_driver.h>
/**
* snd_hdac_ext_host_stream_setup - Setup a HOST stream.
* @hext_stream: HDAudio stream to set up.
* @code_loading: Whether the stream is for PCM or code-loading.
*
* Return: Zero on success or negative error code.
*/
int snd_hdac_ext_host_stream_setup(struct hdac_ext_stream *hext_stream, bool code_loading)
{
return hext_stream->host_setup(hdac_stream(hext_stream), code_loading);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_host_stream_setup);
/**
* snd_hdac_apl_host_stream_setup - Setup a HOST stream following procedure
* recommended for ApolloLake devices.
* @hstream: HDAudio stream to set up.
* @code_loading: Whether the stream is for PCM or code-loading.
*
* Return: Zero on success or negative error code.
*/
static int snd_hdac_apl_host_stream_setup(struct hdac_stream *hstream, bool code_loading)
{
struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
int ret;
snd_hdac_ext_stream_decouple(hstream->bus, hext_stream, false);
ret = snd_hdac_stream_setup(hstream, code_loading);
snd_hdac_ext_stream_decouple(hstream->bus, hext_stream, true);
return ret;
}
/**
* snd_hdac_ext_stream_init - initialize each stream (aka device)
* @bus: HD-audio core bus
* @hext_stream: HD-audio ext core stream object to initialize
* @idx: stream index number
* @direction: stream direction (SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE)
* @tag: the tag id to assign
*
* initialize the stream, if ppcap is enabled then init those and then
* invoke hdac stream initialization routine
*/
static void snd_hdac_ext_stream_init(struct hdac_bus *bus,
struct hdac_ext_stream *hext_stream,
int idx, int direction, int tag)
{
if (bus->ppcap) {
hext_stream->pphc_addr = bus->ppcap + AZX_PPHC_BASE +
AZX_PPHC_INTERVAL * idx;
hext_stream->pplc_addr = bus->ppcap + AZX_PPLC_BASE +
AZX_PPLC_MULTI * bus->num_streams +
AZX_PPLC_INTERVAL * idx;
}
hext_stream->decoupled = false;
snd_hdac_stream_init(bus, &hext_stream->hstream, idx, direction, tag);
}
/**
* snd_hdac_ext_stream_init_all - create and initialize the stream objects
* for an extended hda bus
* @bus: HD-audio core bus
* @start_idx: start index for streams
* @num_stream: number of streams to initialize
* @dir: direction of streams
*/
int snd_hdac_ext_stream_init_all(struct hdac_bus *bus, int start_idx,
int num_stream, int dir)
{
struct pci_dev *pci = to_pci_dev(bus->dev);
int (*setup_op)(struct hdac_stream *, bool);
int stream_tag = 0;
int i, tag, idx = start_idx;
if (pci->device == PCI_DEVICE_ID_INTEL_HDA_APL)
setup_op = snd_hdac_apl_host_stream_setup;
else
setup_op = snd_hdac_stream_setup;
for (i = 0; i < num_stream; i++) {
struct hdac_ext_stream *hext_stream = kzalloc_obj(*hext_stream);
if (!hext_stream)
return -ENOMEM;
tag = ++stream_tag;
snd_hdac_ext_stream_init(bus, hext_stream, idx, dir, tag);
idx++;
hext_stream->host_setup = setup_op;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_init_all);
/**
* snd_hdac_ext_stream_free_all - free hdac extended stream objects
*
* @bus: HD-audio core bus
*/
void snd_hdac_ext_stream_free_all(struct hdac_bus *bus)
{
struct hdac_stream *s, *_s;
struct hdac_ext_stream *hext_stream;
list_for_each_entry_safe(s, _s, &bus->stream_list, list) {
hext_stream = stream_to_hdac_ext_stream(s);
snd_hdac_ext_stream_decouple(bus, hext_stream, false);
list_del(&s->list);
kfree(hext_stream);
}
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_free_all);
void snd_hdac_ext_stream_decouple_locked(struct hdac_bus *bus,
struct hdac_ext_stream *hext_stream,
bool decouple)
{
struct hdac_stream *hstream = &hext_stream->hstream;
u32 val;
int mask = AZX_PPCTL_PROCEN(hstream->index);
val = readw(bus->ppcap + AZX_REG_PP_PPCTL) & mask;
if (decouple && !val)
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, mask);
else if (!decouple && val)
snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL, mask, 0);
hext_stream->decoupled = decouple;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple_locked);
/**
* snd_hdac_ext_stream_decouple - decouple the hdac stream
* @bus: HD-audio core bus
* @hext_stream: HD-audio ext core stream object to initialize
* @decouple: flag to decouple
*/
void snd_hdac_ext_stream_decouple(struct hdac_bus *bus,
struct hdac_ext_stream *hext_stream, bool decouple)
{
guard(spinlock_irq)(&bus->reg_lock);
snd_hdac_ext_stream_decouple_locked(bus, hext_stream, decouple);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_decouple);
/**
* snd_hdac_ext_stream_start - start a stream
* @hext_stream: HD-audio ext core stream to start
*/
void snd_hdac_ext_stream_start(struct hdac_ext_stream *hext_stream)
{
snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
AZX_PPLCCTL_RUN, AZX_PPLCCTL_RUN);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_start);
/**
* snd_hdac_ext_stream_clear - stop a stream DMA
* @hext_stream: HD-audio ext core stream to stop
*/
void snd_hdac_ext_stream_clear(struct hdac_ext_stream *hext_stream)
{
snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL, AZX_PPLCCTL_RUN, 0);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_clear);
/**
* snd_hdac_ext_stream_reset - reset a stream
* @hext_stream: HD-audio ext core stream to reset
*/
void snd_hdac_ext_stream_reset(struct hdac_ext_stream *hext_stream)
{
unsigned char val;
int timeout;
snd_hdac_ext_stream_clear(hext_stream);
snd_hdac_updatel(hext_stream->pplc_addr, AZX_REG_PPLCCTL,
AZX_PPLCCTL_STRST, AZX_PPLCCTL_STRST);
udelay(3);
timeout = 50;
do {
val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) &
AZX_PPLCCTL_STRST;
if (val)
break;
udelay(3);
} while (--timeout);
val &= ~AZX_PPLCCTL_STRST;
writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
udelay(3);
timeout = 50;
/* waiting for hardware to report that the stream is out of reset */
do {
val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL) & AZX_PPLCCTL_STRST;
if (!val)
break;
udelay(3);
} while (--timeout);
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_reset);
/**
* snd_hdac_ext_stream_setup - set up the SD for streaming
* @hext_stream: HD-audio ext core stream to set up
* @fmt: stream format
*/
int snd_hdac_ext_stream_setup(struct hdac_ext_stream *hext_stream, int fmt)
{
struct hdac_stream *hstream = &hext_stream->hstream;
unsigned int val;
/* make sure the run bit is zero for SD */
snd_hdac_ext_stream_clear(hext_stream);
/* program the stream_tag */
val = readl(hext_stream->pplc_addr + AZX_REG_PPLCCTL);
val = (val & ~AZX_PPLCCTL_STRM_MASK) |
(hstream->stream_tag << AZX_PPLCCTL_STRM_SHIFT);
writel(val, hext_stream->pplc_addr + AZX_REG_PPLCCTL);
/* program the stream format */
writew(fmt, hext_stream->pplc_addr + AZX_REG_PPLCFMT);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_setup);
static struct hdac_ext_stream *
hdac_ext_link_dma_stream_assign(struct hdac_bus *bus,
struct snd_pcm_substream *substream)
{
struct hdac_ext_stream *res = NULL;
struct hdac_stream *hstream = NULL;
if (!bus->ppcap) {
dev_err(bus->dev, "stream type not supported\n");
return NULL;
}
guard(spinlock_irq)(&bus->reg_lock);
list_for_each_entry(hstream, &bus->stream_list, list) {
struct hdac_ext_stream *hext_stream = container_of(hstream,
struct hdac_ext_stream,
hstream);
if (hstream->direction != substream->stream)
continue;
/* check if link stream is available */
if (!hext_stream->link_locked) {
res = hext_stream;
break;
}
}
if (res) {
snd_hdac_ext_stream_decouple_locked(bus, res, true);
res->link_locked = 1;
res->link_substream = substream;
}
return res;
}
static struct hdac_ext_stream *
hdac_ext_host_dma_stream_assign(struct hdac_bus *bus,
struct snd_pcm_substream *substream)
{
struct hdac_ext_stream *res = NULL;
struct hdac_stream *hstream = NULL;
if (!bus->ppcap) {
dev_err(bus->dev, "stream type not supported\n");
return NULL;
}
guard(spinlock_irq)(&bus->reg_lock);
list_for_each_entry(hstream, &bus->stream_list, list) {
struct hdac_ext_stream *hext_stream = container_of(hstream,
struct hdac_ext_stream,
hstream);
if (hstream->direction != substream->stream)
continue;
if (!hstream->opened) {
res = hext_stream;
break;
}
}
if (res) {
snd_hdac_ext_stream_decouple_locked(bus, res, true);
res->hstream.opened = 1;
res->hstream.running = 0;
res->hstream.substream = substream;
}
return res;
}
/**
* snd_hdac_ext_stream_assign - assign a stream for the PCM
* @bus: HD-audio core bus
* @substream: PCM substream to assign
* @type: type of stream (coupled, host or link stream)
*
* This assigns the stream based on the type (coupled/host/link), for the
* given PCM substream, assigns it and returns the stream object
*
* coupled: Looks for an unused stream
* host: Looks for an unused decoupled host stream
* link: Looks for an unused decoupled link stream
*
* If no stream is free, returns NULL. The function tries to keep using
* the same stream object when it's used beforehand. when a stream is
* decoupled, it becomes a host stream and link stream.
*/
struct hdac_ext_stream *snd_hdac_ext_stream_assign(struct hdac_bus *bus,
struct snd_pcm_substream *substream,
int type)
{
struct hdac_ext_stream *hext_stream = NULL;
struct hdac_stream *hstream = NULL;
switch (type) {
case HDAC_EXT_STREAM_TYPE_COUPLED:
hstream = snd_hdac_stream_assign(bus, substream);
if (hstream)
hext_stream = container_of(hstream,
struct hdac_ext_stream,
hstream);
return hext_stream;
case HDAC_EXT_STREAM_TYPE_HOST:
return hdac_ext_host_dma_stream_assign(bus, substream);
case HDAC_EXT_STREAM_TYPE_LINK:
return hdac_ext_link_dma_stream_assign(bus, substream);
default:
return NULL;
}
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_assign);
/**
* snd_hdac_ext_stream_release - release the assigned stream
* @hext_stream: HD-audio ext core stream to release
* @type: type of stream (coupled, host or link stream)
*
* Release the stream that has been assigned by snd_hdac_ext_stream_assign().
*/
void snd_hdac_ext_stream_release(struct hdac_ext_stream *hext_stream, int type)
{
struct hdac_bus *bus = hext_stream->hstream.bus;
switch (type) {
case HDAC_EXT_STREAM_TYPE_COUPLED:
snd_hdac_stream_release(&hext_stream->hstream);
break;
case HDAC_EXT_STREAM_TYPE_HOST:
scoped_guard(spinlock_irq, &bus->reg_lock) {
/* couple link only if not in use */
if (!hext_stream->link_locked)
snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
snd_hdac_stream_release_locked(&hext_stream->hstream);
}
break;
case HDAC_EXT_STREAM_TYPE_LINK:
scoped_guard(spinlock_irq, &bus->reg_lock) {
/* couple host only if not in use */
if (!hext_stream->hstream.opened)
snd_hdac_ext_stream_decouple_locked(bus, hext_stream, false);
hext_stream->link_locked = 0;
hext_stream->link_substream = NULL;
}
break;
default:
dev_dbg(bus->dev, "Invalid type %d\n", type);
}
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_stream_release);
/**
* snd_hdac_ext_cstream_assign - assign a host stream for compress
* @bus: HD-audio core bus
* @cstream: Compress stream to assign
*
* Assign an unused host stream for the given compress stream.
* If no stream is free, NULL is returned. Stream is decoupled
* before assignment.
*/
struct hdac_ext_stream *snd_hdac_ext_cstream_assign(struct hdac_bus *bus,
struct snd_compr_stream *cstream)
{
struct hdac_ext_stream *res = NULL;
struct hdac_stream *hstream;
guard(spinlock_irq)(&bus->reg_lock);
list_for_each_entry(hstream, &bus->stream_list, list) {
struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
if (hstream->direction != cstream->direction)
continue;
if (!hstream->opened) {
res = hext_stream;
break;
}
}
if (res) {
snd_hdac_ext_stream_decouple_locked(bus, res, true);
res->hstream.opened = 1;
res->hstream.running = 0;
res->hstream.cstream = cstream;
}
return res;
}
EXPORT_SYMBOL_GPL(snd_hdac_ext_cstream_assign);
+96
View File
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* HD-audio bus
*/
#include <linux/init.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/export.h>
#include <sound/hdaudio.h>
MODULE_DESCRIPTION("HD-audio bus");
MODULE_LICENSE("GPL");
/**
* hdac_get_device_id - gets the hdac device id entry
* @hdev: HD-audio core device
* @drv: HD-audio codec driver
*
* Compares the hdac device vendor_id and revision_id to the hdac_device
* driver id_table and returns the matching device id entry.
*/
const struct hda_device_id *
hdac_get_device_id(struct hdac_device *hdev, const struct hdac_driver *drv)
{
if (drv->id_table) {
const struct hda_device_id *id = drv->id_table;
while (id->vendor_id) {
if (hdev->vendor_id == id->vendor_id &&
(!id->rev_id || id->rev_id == hdev->revision_id))
return id;
id++;
}
}
return NULL;
}
EXPORT_SYMBOL_GPL(hdac_get_device_id);
static int hdac_codec_match(struct hdac_device *dev, const struct hdac_driver *drv)
{
if (hdac_get_device_id(dev, drv))
return 1;
else
return 0;
}
static int hda_bus_match(struct device *dev, const struct device_driver *drv)
{
struct hdac_device *hdev = dev_to_hdac_dev(dev);
const struct hdac_driver *hdrv = drv_to_hdac_driver(drv);
if (hdev->type != hdrv->type)
return 0;
/*
* if driver provided a match function use that otherwise we will
* use hdac_codec_match function
*/
if (hdrv->match)
return hdrv->match(hdev, hdrv);
else
return hdac_codec_match(hdev, hdrv);
return 1;
}
static int hda_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
char modalias[32];
snd_hdac_codec_modalias(dev_to_hdac_dev(dev), modalias,
sizeof(modalias));
if (add_uevent_var(env, "MODALIAS=%s", modalias))
return -ENOMEM;
return 0;
}
const struct bus_type snd_hda_bus_type = {
.name = "hdaudio",
.match = hda_bus_match,
.uevent = hda_uevent,
};
EXPORT_SYMBOL_GPL(snd_hda_bus_type);
static int __init hda_bus_init(void)
{
return bus_register(&snd_hda_bus_type);
}
static void __exit hda_bus_exit(void)
{
bus_unregister(&snd_hda_bus_type);
}
subsys_initcall(hda_bus_init);
module_exit(hda_bus_exit);
+871
View File
@@ -0,0 +1,871 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* HDMI Channel map support helpers
*/
#include <linux/module.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include <sound/hda_chmap.h>
/*
* CEA speaker placement:
*
* FLH FCH FRH
* FLW FL FLC FC FRC FR FRW
*
* LFE
* TC
*
* RL RLC RC RRC RR
*
* The Left/Right Surround channel _notions_ LS/RS in SMPTE 320M corresponds to
* CEA RL/RR; The SMPTE channel _assignment_ C/LFE is swapped to CEA LFE/FC.
*/
enum cea_speaker_placement {
FL = (1 << 0), /* Front Left */
FC = (1 << 1), /* Front Center */
FR = (1 << 2), /* Front Right */
FLC = (1 << 3), /* Front Left Center */
FRC = (1 << 4), /* Front Right Center */
RL = (1 << 5), /* Rear Left */
RC = (1 << 6), /* Rear Center */
RR = (1 << 7), /* Rear Right */
RLC = (1 << 8), /* Rear Left Center */
RRC = (1 << 9), /* Rear Right Center */
LFE = (1 << 10), /* Low Frequency Effect */
FLW = (1 << 11), /* Front Left Wide */
FRW = (1 << 12), /* Front Right Wide */
FLH = (1 << 13), /* Front Left High */
FCH = (1 << 14), /* Front Center High */
FRH = (1 << 15), /* Front Right High */
TC = (1 << 16), /* Top Center */
};
static const char * const cea_speaker_allocation_names[] = {
/* 0 */ "FL/FR",
/* 1 */ "LFE",
/* 2 */ "FC",
/* 3 */ "RL/RR",
/* 4 */ "RC",
/* 5 */ "FLC/FRC",
/* 6 */ "RLC/RRC",
/* 7 */ "FLW/FRW",
/* 8 */ "FLH/FRH",
/* 9 */ "TC",
/* 10 */ "FCH",
};
/*
* ELD SA bits in the CEA Speaker Allocation data block
*/
static const int eld_speaker_allocation_bits[] = {
[0] = FL | FR,
[1] = LFE,
[2] = FC,
[3] = RL | RR,
[4] = RC,
[5] = FLC | FRC,
[6] = RLC | RRC,
/* the following are not defined in ELD yet */
[7] = FLW | FRW,
[8] = FLH | FRH,
[9] = TC,
[10] = FCH,
};
/*
* ALSA sequence is:
*
* surround40 surround41 surround50 surround51 surround71
* ch0 front left = = = =
* ch1 front right = = = =
* ch2 rear left = = = =
* ch3 rear right = = = =
* ch4 LFE center center center
* ch5 LFE LFE
* ch6 side left
* ch7 side right
*
* surround71 = {FL, FR, RLC, RRC, FC, LFE, RL, RR}
*/
static int hdmi_channel_mapping[0x32][8] = {
/* stereo */
[0x00] = { 0x00, 0x11, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 },
/* 2.1 */
[0x01] = { 0x00, 0x11, 0x22, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7 },
/* Dolby Surround */
[0x02] = { 0x00, 0x11, 0x23, 0xf2, 0xf4, 0xf5, 0xf6, 0xf7 },
/* surround40 */
[0x08] = { 0x00, 0x11, 0x24, 0x35, 0xf3, 0xf2, 0xf6, 0xf7 },
/* 4ch */
[0x03] = { 0x00, 0x11, 0x23, 0x32, 0x44, 0xf5, 0xf6, 0xf7 },
/* surround41 */
[0x09] = { 0x00, 0x11, 0x24, 0x35, 0x42, 0xf3, 0xf6, 0xf7 },
/* surround50 */
[0x0a] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0xf2, 0xf6, 0xf7 },
/* surround51 */
[0x0b] = { 0x00, 0x11, 0x24, 0x35, 0x43, 0x52, 0xf6, 0xf7 },
/* 7.1 */
[0x13] = { 0x00, 0x11, 0x26, 0x37, 0x43, 0x52, 0x64, 0x75 },
};
/*
* This is an ordered list!
*
* The preceding ones have better chances to be selected by
* hdmi_channel_allocation().
*/
static struct hdac_cea_channel_speaker_allocation channel_allocations[] = {
/* channel: 7 6 5 4 3 2 1 0 */
{ .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL } },
/* 2.1 */
{ .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL } },
/* Dolby Surround */
{ .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL } },
/* surround40 */
{ .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL } },
/* surround41 */
{ .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL } },
/* surround50 */
{ .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL } },
/* surround51 */
{ .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL } },
/* 6.1 */
{ .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL } },
/* surround71 */
{ .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL } },
{ .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL } },
{ .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL } },
{ .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL } },
{ .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL } },
{ .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL } },
{ .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL } },
{ .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL } },
{ .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL } },
{ .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL } },
{ .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL } },
{ .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL } },
{ .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL } },
{ .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL } },
{ .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL } },
{ .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL } },
{ .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL } },
{ .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL } },
{ .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL } },
{ .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL } },
{ .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL } },
{ .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL } },
{ .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL } },
{ .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL } },
{ .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL } },
{ .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL } },
{ .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL } },
{ .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL } },
{ .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL } },
{ .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL } },
{ .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL } },
{ .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL } },
{ .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL } },
};
static int hdmi_pin_set_slot_channel(struct hdac_device *codec,
hda_nid_t pin_nid, int asp_slot, int channel)
{
return snd_hdac_codec_write(codec, pin_nid, 0,
AC_VERB_SET_HDMI_CHAN_SLOT,
(channel << 4) | asp_slot);
}
static int hdmi_pin_get_slot_channel(struct hdac_device *codec,
hda_nid_t pin_nid, int asp_slot)
{
return (snd_hdac_codec_read(codec, pin_nid, 0,
AC_VERB_GET_HDMI_CHAN_SLOT,
asp_slot) & 0xf0) >> 4;
}
static int hdmi_get_channel_count(struct hdac_device *codec, hda_nid_t cvt_nid)
{
return 1 + snd_hdac_codec_read(codec, cvt_nid, 0,
AC_VERB_GET_CVT_CHAN_COUNT, 0);
}
static void hdmi_set_channel_count(struct hdac_device *codec,
hda_nid_t cvt_nid, int chs)
{
if (chs != hdmi_get_channel_count(codec, cvt_nid))
snd_hdac_codec_write(codec, cvt_nid, 0,
AC_VERB_SET_CVT_CHAN_COUNT, chs - 1);
}
/*
* Channel mapping routines
*/
/*
* Compute derived values in channel_allocations[].
*/
static void init_channel_allocations(void)
{
int i, j;
struct hdac_cea_channel_speaker_allocation *p;
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
p = channel_allocations + i;
p->channels = 0;
p->spk_mask = 0;
for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
if (p->speakers[j]) {
p->channels++;
p->spk_mask |= p->speakers[j];
}
}
}
static int get_channel_allocation_order(int ca)
{
int i;
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
if (channel_allocations[i].ca_index == ca)
break;
}
return i;
}
void snd_hdac_print_channel_allocation(int spk_alloc, char *buf, int buflen)
{
int i, j;
for (i = 0, j = 0; i < ARRAY_SIZE(cea_speaker_allocation_names); i++) {
if (spk_alloc & (1 << i))
j += scnprintf(buf + j, buflen - j, " %s",
cea_speaker_allocation_names[i]);
}
buf[j] = '\0'; /* necessary when j == 0 */
}
EXPORT_SYMBOL_GPL(snd_hdac_print_channel_allocation);
/*
* The transformation takes two steps:
*
* eld->spk_alloc => (eld_speaker_allocation_bits[]) => spk_mask
* spk_mask => (channel_allocations[]) => ai->CA
*
* TODO: it could select the wrong CA from multiple candidates.
*/
static int hdmi_channel_allocation_spk_alloc_blk(struct hdac_device *codec,
int spk_alloc, int channels)
{
int i;
int ca = 0;
int spk_mask = 0;
char buf[SND_PRINT_CHANNEL_ALLOCATION_ADVISED_BUFSIZE];
/*
* CA defaults to 0 for basic stereo audio
*/
if (channels <= 2)
return 0;
/*
* expand ELD's speaker allocation mask
*
* ELD tells the speaker mask in a compact(paired) form,
* expand ELD's notions to match the ones used by Audio InfoFrame.
*/
for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) {
if (spk_alloc & (1 << i))
spk_mask |= eld_speaker_allocation_bits[i];
}
/* search for the first working match in the CA table */
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
if (channels == channel_allocations[i].channels &&
(spk_mask & channel_allocations[i].spk_mask) ==
channel_allocations[i].spk_mask) {
ca = channel_allocations[i].ca_index;
break;
}
}
if (!ca) {
/*
* if there was no match, select the regular ALSA channel
* allocation with the matching number of channels
*/
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
if (channels == channel_allocations[i].channels) {
ca = channel_allocations[i].ca_index;
break;
}
}
}
snd_hdac_print_channel_allocation(spk_alloc, buf, sizeof(buf));
dev_dbg(&codec->dev, "HDMI: select CA 0x%x for %d-channel allocation: %s\n",
ca, channels, buf);
return ca;
}
static void hdmi_debug_channel_mapping(struct hdac_chmap *chmap,
hda_nid_t pin_nid)
{
#ifdef CONFIG_SND_DEBUG_VERBOSE
int i;
int channel;
for (i = 0; i < 8; i++) {
channel = chmap->ops.pin_get_slot_channel(
chmap->hdac, pin_nid, i);
dev_dbg(&chmap->hdac->dev, "HDMI: ASP channel %d => slot %d\n",
channel, i);
}
#endif
}
static void hdmi_std_setup_channel_mapping(struct hdac_chmap *chmap,
hda_nid_t pin_nid,
bool non_pcm,
int ca)
{
struct hdac_cea_channel_speaker_allocation *ch_alloc;
int i;
int err;
int order;
int non_pcm_mapping[8];
order = get_channel_allocation_order(ca);
ch_alloc = &channel_allocations[order];
if (hdmi_channel_mapping[ca][1] == 0) {
int hdmi_slot = 0;
/* fill actual channel mappings in ALSA channel (i) order */
for (i = 0; i < ch_alloc->channels && hdmi_slot < 8; i++) {
while (!ch_alloc->speakers[7 - hdmi_slot]) {
/* skip zero slots */
if (++hdmi_slot >= 8)
goto out;
}
hdmi_channel_mapping[ca][i] = (i << 4) | hdmi_slot++;
}
out:
/* fill the rest of the slots with ALSA channel 0xf */
for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++)
if (!ch_alloc->speakers[7 - hdmi_slot])
hdmi_channel_mapping[ca][i++] = (0xf << 4) | hdmi_slot;
}
if (non_pcm) {
for (i = 0; i < ch_alloc->channels; i++)
non_pcm_mapping[i] = (i << 4) | i;
for (; i < 8; i++)
non_pcm_mapping[i] = (0xf << 4) | i;
}
for (i = 0; i < 8; i++) {
int slotsetup = non_pcm ? non_pcm_mapping[i] : hdmi_channel_mapping[ca][i];
int hdmi_slot = slotsetup & 0x0f;
int channel = (slotsetup & 0xf0) >> 4;
err = chmap->ops.pin_set_slot_channel(chmap->hdac,
pin_nid, hdmi_slot, channel);
if (err) {
dev_dbg(&chmap->hdac->dev, "HDMI: channel mapping failed\n");
break;
}
}
}
struct channel_map_table {
unsigned char map; /* ALSA API channel map position */
int spk_mask; /* speaker position bit mask */
};
static struct channel_map_table map_tables[] = {
{ SNDRV_CHMAP_FL, FL },
{ SNDRV_CHMAP_FR, FR },
{ SNDRV_CHMAP_RL, RL },
{ SNDRV_CHMAP_RR, RR },
{ SNDRV_CHMAP_LFE, LFE },
{ SNDRV_CHMAP_FC, FC },
{ SNDRV_CHMAP_RLC, RLC },
{ SNDRV_CHMAP_RRC, RRC },
{ SNDRV_CHMAP_RC, RC },
{ SNDRV_CHMAP_FLC, FLC },
{ SNDRV_CHMAP_FRC, FRC },
{ SNDRV_CHMAP_TFL, FLH },
{ SNDRV_CHMAP_TFR, FRH },
{ SNDRV_CHMAP_FLW, FLW },
{ SNDRV_CHMAP_FRW, FRW },
{ SNDRV_CHMAP_TC, TC },
{ SNDRV_CHMAP_TFC, FCH },
{} /* terminator */
};
/* from ALSA API channel position to speaker bit mask */
int snd_hdac_chmap_to_spk_mask(unsigned char c)
{
struct channel_map_table *t = map_tables;
for (; t->map; t++) {
if (t->map == c)
return t->spk_mask;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_chmap_to_spk_mask);
/* from ALSA API channel position to CEA slot */
static int to_cea_slot(int ordered_ca, unsigned char pos)
{
int mask = snd_hdac_chmap_to_spk_mask(pos);
int i;
/* Add sanity check to pass klockwork check.
* This should never happen.
*/
if (ordered_ca >= ARRAY_SIZE(channel_allocations))
return -1;
if (mask) {
for (i = 0; i < 8; i++) {
if (channel_allocations[ordered_ca].speakers[7 - i] == mask)
return i;
}
}
return -1;
}
/* from speaker bit mask to ALSA API channel position */
int snd_hdac_spk_to_chmap(int spk)
{
struct channel_map_table *t = map_tables;
for (; t->map; t++) {
if (t->spk_mask == spk)
return t->map;
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_spk_to_chmap);
/* from CEA slot to ALSA API channel position */
static int from_cea_slot(int ordered_ca, unsigned char slot)
{
int mask;
/* Add sanity check to pass klockwork check.
* This should never happen.
*/
if (slot >= 8)
return 0;
mask = channel_allocations[ordered_ca].speakers[7 - slot];
return snd_hdac_spk_to_chmap(mask);
}
/* get the CA index corresponding to the given ALSA API channel map */
static int hdmi_manual_channel_allocation(int chs, unsigned char *map)
{
int i, spks = 0, spk_mask = 0;
for (i = 0; i < chs; i++) {
int mask = snd_hdac_chmap_to_spk_mask(map[i]);
if (mask) {
spk_mask |= mask;
spks++;
}
}
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
if ((chs == channel_allocations[i].channels ||
spks == channel_allocations[i].channels) &&
(spk_mask & channel_allocations[i].spk_mask) ==
channel_allocations[i].spk_mask)
return channel_allocations[i].ca_index;
}
return -1;
}
/* set up the channel slots for the given ALSA API channel map */
static int hdmi_manual_setup_channel_mapping(struct hdac_chmap *chmap,
hda_nid_t pin_nid,
int chs, unsigned char *map,
int ca)
{
int ordered_ca = get_channel_allocation_order(ca);
int alsa_pos, hdmi_slot;
int assignments[8] = {[0 ... 7] = 0xf};
for (alsa_pos = 0; alsa_pos < chs; alsa_pos++) {
hdmi_slot = to_cea_slot(ordered_ca, map[alsa_pos]);
if (hdmi_slot < 0)
continue; /* unassigned channel */
assignments[hdmi_slot] = alsa_pos;
}
for (hdmi_slot = 0; hdmi_slot < 8; hdmi_slot++) {
int err;
err = chmap->ops.pin_set_slot_channel(chmap->hdac,
pin_nid, hdmi_slot, assignments[hdmi_slot]);
if (err)
return -EINVAL;
}
return 0;
}
/* store ALSA API channel map from the current default map */
static void hdmi_setup_fake_chmap(unsigned char *map, int ca)
{
int i;
int ordered_ca = get_channel_allocation_order(ca);
for (i = 0; i < 8; i++) {
if (ordered_ca < ARRAY_SIZE(channel_allocations) &&
i < channel_allocations[ordered_ca].channels)
map[i] = from_cea_slot(ordered_ca, hdmi_channel_mapping[ca][i] & 0x0f);
else
map[i] = 0;
}
}
void snd_hdac_setup_channel_mapping(struct hdac_chmap *chmap,
hda_nid_t pin_nid, bool non_pcm, int ca,
int channels, unsigned char *map,
bool chmap_set)
{
if (!non_pcm && chmap_set) {
hdmi_manual_setup_channel_mapping(chmap, pin_nid,
channels, map, ca);
} else {
hdmi_std_setup_channel_mapping(chmap, pin_nid, non_pcm, ca);
hdmi_setup_fake_chmap(map, ca);
}
hdmi_debug_channel_mapping(chmap, pin_nid);
}
EXPORT_SYMBOL_GPL(snd_hdac_setup_channel_mapping);
int snd_hdac_get_active_channels(int ca)
{
int ordered_ca = get_channel_allocation_order(ca);
/* Add sanity check to pass klockwork check.
* This should never happen.
*/
if (ordered_ca >= ARRAY_SIZE(channel_allocations))
ordered_ca = 0;
return channel_allocations[ordered_ca].channels;
}
EXPORT_SYMBOL_GPL(snd_hdac_get_active_channels);
struct hdac_cea_channel_speaker_allocation *snd_hdac_get_ch_alloc_from_ca(int ca)
{
return &channel_allocations[get_channel_allocation_order(ca)];
}
EXPORT_SYMBOL_GPL(snd_hdac_get_ch_alloc_from_ca);
int snd_hdac_channel_allocation(struct hdac_device *hdac, int spk_alloc,
int channels, bool chmap_set, bool non_pcm, unsigned char *map)
{
int ca;
if (!non_pcm && chmap_set)
ca = hdmi_manual_channel_allocation(channels, map);
else
ca = hdmi_channel_allocation_spk_alloc_blk(hdac,
spk_alloc, channels);
if (ca < 0)
ca = 0;
return ca;
}
EXPORT_SYMBOL_GPL(snd_hdac_channel_allocation);
/*
* ALSA API channel-map control callbacks
*/
static int hdmi_chmap_ctl_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
struct hdac_chmap *chmap = info->private_data;
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = chmap->channels_max;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = SNDRV_CHMAP_LAST;
return 0;
}
static int hdmi_chmap_cea_alloc_validate_get_type(struct hdac_chmap *chmap,
struct hdac_cea_channel_speaker_allocation *cap, int channels)
{
/* If the speaker allocation matches the channel count, it is OK.*/
if (cap->channels != channels)
return -1;
/* all channels are remappable freely */
return SNDRV_CTL_TLVT_CHMAP_VAR;
}
static void hdmi_cea_alloc_to_tlv_chmap(struct hdac_chmap *hchmap,
struct hdac_cea_channel_speaker_allocation *cap,
unsigned int *chmap, int channels)
{
int count = 0;
int c;
for (c = 7; c >= 0; c--) {
int spk = cap->speakers[c];
if (!spk)
continue;
chmap[count++] = snd_hdac_spk_to_chmap(spk);
}
WARN_ON(count != channels);
}
static int spk_mask_from_spk_alloc(int spk_alloc)
{
int i;
int spk_mask = eld_speaker_allocation_bits[0];
for (i = 0; i < ARRAY_SIZE(eld_speaker_allocation_bits); i++) {
if (spk_alloc & (1 << i))
spk_mask |= eld_speaker_allocation_bits[i];
}
return spk_mask;
}
static int hdmi_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
unsigned int size, unsigned int __user *tlv)
{
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
struct hdac_chmap *chmap = info->private_data;
int pcm_idx = kcontrol->private_value;
unsigned int __user *dst;
int chs, count = 0;
unsigned long max_chs;
int type;
int spk_alloc, spk_mask;
if (size < 8)
return -ENOMEM;
if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
return -EFAULT;
size -= 8;
dst = tlv + 2;
spk_alloc = chmap->ops.get_spk_alloc(chmap->hdac, pcm_idx);
spk_mask = spk_mask_from_spk_alloc(spk_alloc);
max_chs = hweight_long(spk_mask);
for (chs = 2; chs <= max_chs; chs++) {
int i;
struct hdac_cea_channel_speaker_allocation *cap;
cap = channel_allocations;
for (i = 0; i < ARRAY_SIZE(channel_allocations); i++, cap++) {
int chs_bytes = chs * 4;
unsigned int tlv_chmap[8];
if (cap->channels != chs)
continue;
if (!(cap->spk_mask == (spk_mask & cap->spk_mask)))
continue;
type = chmap->ops.chmap_cea_alloc_validate_get_type(
chmap, cap, chs);
if (type < 0)
return -ENODEV;
if (size < 8)
return -ENOMEM;
if (put_user(type, dst) ||
put_user(chs_bytes, dst + 1))
return -EFAULT;
dst += 2;
size -= 8;
count += 8;
if (size < chs_bytes)
return -ENOMEM;
size -= chs_bytes;
count += chs_bytes;
chmap->ops.cea_alloc_to_tlv_chmap(chmap, cap,
tlv_chmap, chs);
if (copy_to_user(dst, tlv_chmap, chs_bytes))
return -EFAULT;
dst += chs;
}
}
if (put_user(count, tlv + 1))
return -EFAULT;
return 0;
}
static int hdmi_chmap_ctl_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
struct hdac_chmap *chmap = info->private_data;
int pcm_idx = kcontrol->private_value;
unsigned char pcm_chmap[8];
int i;
memset(pcm_chmap, 0, sizeof(pcm_chmap));
chmap->ops.get_chmap(chmap->hdac, pcm_idx, pcm_chmap);
for (i = 0; i < ARRAY_SIZE(pcm_chmap); i++)
ucontrol->value.integer.value[i] = pcm_chmap[i];
return 0;
}
/* a simple sanity check for input values to chmap kcontrol */
static int chmap_value_check(struct hdac_chmap *hchmap,
const struct snd_ctl_elem_value *ucontrol)
{
int i;
for (i = 0; i < hchmap->channels_max; i++) {
if (ucontrol->value.integer.value[i] < 0 ||
ucontrol->value.integer.value[i] > SNDRV_CHMAP_LAST)
return -EINVAL;
}
return 0;
}
static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
struct hdac_chmap *hchmap = info->private_data;
int pcm_idx = kcontrol->private_value;
unsigned int ctl_idx;
struct snd_pcm_substream *substream;
unsigned char chmap[8], per_pin_chmap[8];
int i, err, ca, prepared = 0;
err = chmap_value_check(hchmap, ucontrol);
if (err < 0)
return err;
/* No monitor is connected in dyn_pcm_assign.
* It's invalid to setup the chmap
*/
if (!hchmap->ops.is_pcm_attached(hchmap->hdac, pcm_idx))
return 0;
ctl_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
substream = snd_pcm_chmap_substream(info, ctl_idx);
if (!substream || !substream->runtime)
return 0; /* just for avoiding error from alsactl restore */
switch (substream->runtime->state) {
case SNDRV_PCM_STATE_OPEN:
case SNDRV_PCM_STATE_SETUP:
break;
case SNDRV_PCM_STATE_PREPARED:
prepared = 1;
break;
default:
return -EBUSY;
}
memset(chmap, 0, sizeof(chmap));
for (i = 0; i < ARRAY_SIZE(chmap); i++)
chmap[i] = ucontrol->value.integer.value[i];
hchmap->ops.get_chmap(hchmap->hdac, pcm_idx, per_pin_chmap);
if (!memcmp(chmap, per_pin_chmap, sizeof(chmap)))
return 0;
ca = hdmi_manual_channel_allocation(ARRAY_SIZE(chmap), chmap);
if (ca < 0)
return -EINVAL;
if (hchmap->ops.chmap_validate) {
err = hchmap->ops.chmap_validate(hchmap, ca,
ARRAY_SIZE(chmap), chmap);
if (err)
return err;
}
hchmap->ops.set_chmap(hchmap->hdac, pcm_idx, chmap, prepared);
return 0;
}
static const struct hdac_chmap_ops chmap_ops = {
.chmap_cea_alloc_validate_get_type = hdmi_chmap_cea_alloc_validate_get_type,
.cea_alloc_to_tlv_chmap = hdmi_cea_alloc_to_tlv_chmap,
.pin_get_slot_channel = hdmi_pin_get_slot_channel,
.pin_set_slot_channel = hdmi_pin_set_slot_channel,
.set_channel_count = hdmi_set_channel_count,
};
void snd_hdac_register_chmap_ops(struct hdac_device *hdac,
struct hdac_chmap *chmap)
{
chmap->ops = chmap_ops;
chmap->hdac = hdac;
init_channel_allocations();
}
EXPORT_SYMBOL_GPL(snd_hdac_register_chmap_ops);
int snd_hdac_add_chmap_ctls(struct snd_pcm *pcm, int pcm_idx,
struct hdac_chmap *hchmap)
{
struct snd_pcm_chmap *chmap;
struct snd_kcontrol *kctl;
int err, i;
err = snd_pcm_add_chmap_ctls(pcm,
SNDRV_PCM_STREAM_PLAYBACK,
NULL, 0, pcm_idx, &chmap);
if (err < 0)
return err;
/* override handlers */
chmap->private_data = hchmap;
kctl = chmap->kctl;
for (i = 0; i < kctl->count; i++)
kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
kctl->info = hdmi_chmap_ctl_info;
kctl->get = hdmi_chmap_ctl_get;
kctl->put = hdmi_chmap_ctl_put;
kctl->tlv.c = hdmi_chmap_ctl_tlv;
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_add_chmap_ctls);
+208
View File
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* hdac_i915.c - routines for sync between HD-A core and i915 display driver
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <sound/core.h>
#include <sound/hdaudio.h>
#include <sound/hda_i915.h>
#include <sound/hda_register.h>
#include <video/nomodeset.h>
static int gpu_bind = -1;
module_param(gpu_bind, int, 0644);
MODULE_PARM_DESC(gpu_bind, "Whether to bind sound component to GPU "
"(1=always, 0=never, -1=on nomodeset(default))");
/**
* snd_hdac_i915_set_bclk - Reprogram BCLK for HSW/BDW
* @bus: HDA core bus
*
* Intel HSW/BDW display HDA controller is in GPU. Both its power and link BCLK
* depends on GPU. Two Extended Mode registers EM4 (M value) and EM5 (N Value)
* are used to convert CDClk (Core Display Clock) to 24MHz BCLK:
* BCLK = CDCLK * M / N
* The values will be lost when the display power well is disabled and need to
* be restored to avoid abnormal playback speed.
*
* Call this function at initializing and changing power well, as well as
* at ELD notifier for the hotplug.
*/
void snd_hdac_i915_set_bclk(struct hdac_bus *bus)
{
struct drm_audio_component *acomp = bus->audio_component;
struct pci_dev *pci = to_pci_dev(bus->dev);
int cdclk_freq;
unsigned int bclk_m, bclk_n;
if (!acomp || !acomp->ops || !acomp->ops->get_cdclk_freq)
return; /* only for i915 binding */
if (!HDA_CONTROLLER_IS_HSW(pci))
return; /* only HSW/BDW */
cdclk_freq = acomp->ops->get_cdclk_freq(acomp->dev);
switch (cdclk_freq) {
case 337500:
bclk_m = 16;
bclk_n = 225;
break;
case 450000:
default: /* default CDCLK 450MHz */
bclk_m = 4;
bclk_n = 75;
break;
case 540000:
bclk_m = 4;
bclk_n = 90;
break;
case 675000:
bclk_m = 8;
bclk_n = 225;
break;
}
snd_hdac_chip_writew(bus, HSW_EM4, bclk_m);
snd_hdac_chip_writew(bus, HSW_EM5, bclk_n);
}
EXPORT_SYMBOL_GPL(snd_hdac_i915_set_bclk);
/* returns true if the devices can be connected for audio */
static bool connectivity_check(struct pci_dev *i915, struct pci_dev *hdac)
{
struct pci_bus *bus_a = i915->bus, *bus_b = hdac->bus;
/* directly connected on the same bus */
if (bus_a == bus_b)
return true;
bus_a = bus_a->parent;
bus_b = bus_b->parent;
/* connected via parent bus (may be NULL!) */
if (bus_a == bus_b)
return true;
if (!bus_a || !bus_b)
return false;
/*
* on i915 discrete GPUs with embedded HDA audio, the two
* devices are connected via 2nd level PCI bridge
*/
bus_a = bus_a->parent;
bus_b = bus_b->parent;
if (bus_a && bus_a == bus_b)
return true;
return false;
}
static int i915_component_master_match(struct device *dev, int subcomponent,
void *data)
{
struct pci_dev *hdac_pci, *i915_pci;
struct hdac_bus *bus = data;
if (!dev_is_pci(dev))
return 0;
hdac_pci = to_pci_dev(bus->dev);
i915_pci = to_pci_dev(dev);
if ((!strcmp(dev->driver->name, "i915") ||
!strcmp(dev->driver->name, "xe")) &&
subcomponent == I915_COMPONENT_AUDIO &&
connectivity_check(i915_pci, hdac_pci))
return 1;
return 0;
}
/* check whether Intel graphics is present and reachable */
static int i915_gfx_present(struct pci_dev *hdac_pci)
{
/* List of known platforms with no i915 support. */
static const struct pci_device_id denylist[] = {
/* CNL */
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a40), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a41), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a42), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a44), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a49), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a4a), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a4c), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a50), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a51), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a52), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a54), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a59), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a5a), .class = 0x030000, .class_mask = 0xff0000 },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x5a5c), .class = 0x030000, .class_mask = 0xff0000 },
/* LKF */
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x9840), .class = 0x030000, .class_mask = 0xff0000 },
{}
};
struct pci_dev *display_dev = NULL;
if (!gpu_bind || (gpu_bind < 0 && video_firmware_drivers_only()))
return false;
for_each_pci_dev(display_dev) {
if (display_dev->vendor != PCI_VENDOR_ID_INTEL ||
!pci_is_display(display_dev))
continue;
if (pci_match_id(denylist, display_dev))
continue;
if (connectivity_check(display_dev, hdac_pci)) {
pci_dev_put(display_dev);
return true;
}
}
return false;
}
/**
* snd_hdac_i915_init - Initialize i915 audio component
* @bus: HDA core bus
*
* This function is supposed to be used only by a HD-audio controller
* driver that needs the interaction with i915 graphics.
*
* This function initializes and sets up the audio component to communicate
* with i915 graphics driver.
*
* Returns zero for success or a negative error code.
*/
int snd_hdac_i915_init(struct hdac_bus *bus)
{
struct drm_audio_component *acomp;
int err;
if (!i915_gfx_present(to_pci_dev(bus->dev)))
return -ENODEV;
err = snd_hdac_acomp_init(bus, NULL,
i915_component_master_match,
sizeof(struct i915_audio_component) - sizeof(*acomp));
if (err < 0)
return err;
acomp = bus->audio_component;
if (!acomp)
return -ENODEV;
if (!acomp->ops) {
snd_hdac_acomp_exit(bus);
return dev_err_probe(bus->dev, -EPROBE_DEFER,
"couldn't bind with audio component\n");
}
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_i915_init);
@@ -0,0 +1,856 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Jaroslav Kysela <perex@perex.cz>
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/dmi.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
#include <sound/core.h>
#include <sound/intel-dsp-config.h>
#include <sound/intel-nhlt.h>
#include <sound/soc-acpi.h>
#include <acpi/nhlt.h>
static int dsp_driver;
module_param(dsp_driver, int, 0444);
MODULE_PARM_DESC(dsp_driver, "Force the DSP driver for Intel DSP (0=auto, 1=legacy, 2=SST, 3=SOF, 4=AVS)");
#define FLAG_SST BIT(0)
#define FLAG_SOF BIT(1)
#define FLAG_SST_ONLY_IF_DMIC BIT(15)
#define FLAG_SOF_ONLY_IF_DMIC BIT(16)
#define FLAG_SOF_ONLY_IF_SOUNDWIRE BIT(17)
#define FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE (FLAG_SOF_ONLY_IF_DMIC | \
FLAG_SOF_ONLY_IF_SOUNDWIRE)
struct config_entry {
u32 flags;
u16 device;
u8 acpi_hid[ACPI_ID_LEN];
const struct dmi_system_id *dmi_table;
const struct snd_soc_acpi_codecs *codec_hid;
};
static const struct snd_soc_acpi_codecs __maybe_unused essx_83x6 = {
.num_codecs = 3,
.codecs = { "ESSX8316", "ESSX8326", "ESSX8336"},
};
/*
* configuration table
* - the order of similar PCI ID entries is important!
* - the first successful match will win
*/
static const struct config_entry config_table[] = {
/* Merrifield */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD)
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_SST_TNG,
},
#endif
/*
* Skylake, Kabylake, Apollolake
* the legacy HDAudio driver is used except on Up Squared (SOF) and
* Chromebooks (SST), as well as devices based on the ES8336 codec
*/
#if IS_ENABLED(CONFIG_SND_SOC_INTEL_AVS)
{
.flags = FLAG_SST,
.device = PCI_DEVICE_ID_INTEL_HDA_SKL_LP,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SST | FLAG_SST_ONLY_IF_DMIC,
.device = PCI_DEVICE_ID_INTEL_HDA_SKL_LP,
},
{
.flags = FLAG_SST,
.device = PCI_DEVICE_ID_INTEL_HDA_KBL_LP,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SST | FLAG_SST_ONLY_IF_DMIC,
.device = PCI_DEVICE_ID_INTEL_HDA_KBL_LP,
},
{
.flags = FLAG_SST,
.device = PCI_DEVICE_ID_INTEL_HDA_APL,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SST,
.device = PCI_DEVICE_ID_INTEL_HDA_RPL_M,
},
{
.flags = FLAG_SST,
.device = PCI_DEVICE_ID_INTEL_HDA_FCL,
},
#else /* AVS disabled; force to legacy as SOF doesn't work for SKL or KBL */
{
.device = PCI_DEVICE_ID_INTEL_HDA_SKL_LP,
},
{
.device = PCI_DEVICE_ID_INTEL_HDA_KBL_LP,
},
#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE)
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_APL,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Up Squared",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
DMI_MATCH(DMI_BOARD_NAME, "UP-APL01"),
}
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_APL,
.codec_hid = &essx_83x6,
},
#endif
/*
* Geminilake uses legacy HDAudio driver except for Google
* Chromebooks and devices based on the ES8336 codec
*/
/* Geminilake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE)
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_GLK,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_GLK,
.codec_hid = &essx_83x6,
},
#endif
/*
* CoffeeLake, CannonLake, CometLake, IceLake, TigerLake, AlderLake,
* RaptorLake, MeteorLake use legacy HDAudio driver except for Google
* Chromebooks and when DMICs are present. Two cases are required since
* Coreboot does not expose NHLT tables.
*
* When the Chromebook quirk is not present, it's based on information
* that no such device exists. When the quirk is present, it could be
* either based on product information or a placeholder.
*/
/* Cannonlake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE)
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_CNL_LP,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{
.ident = "UP-WHL",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
}
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_CNL_LP,
.codec_hid = &essx_83x6,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_CNL_LP,
},
#endif
/* Coffelake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE)
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_CNL_H,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_CNL_H,
},
#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE)
/* Cometlake-LP */
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_CML_LP,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "09C6")
},
},
{
/* early version of SKU 09C6 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0983")
},
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_CML_LP,
.codec_hid = &essx_83x6,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_CML_LP,
},
/* Cometlake-H */
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_CML_H,
.dmi_table = (const struct dmi_system_id []) {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "098F"),
},
},
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0990"),
},
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_CML_H,
.codec_hid = &essx_83x6,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_CML_H,
},
#endif
/* Icelake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE)
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_ICL_LP,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_ICL_LP,
.codec_hid = &essx_83x6,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ICL_LP,
},
#endif
/* Jasper Lake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_JASPERLAKE)
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_JSL_N,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{
.ident = "Google firmware",
.matches = {
DMI_MATCH(DMI_BIOS_VERSION, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_JSL_N,
.codec_hid = &essx_83x6,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC,
.device = PCI_DEVICE_ID_INTEL_HDA_JSL_N,
},
#endif
/* Tigerlake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE)
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_TGL_LP,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{
.ident = "UPX-TGL",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
}
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_TGL_LP,
.codec_hid = &essx_83x6,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_TGL_LP,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_TGL_H,
},
#endif
/* Elkhart Lake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE)
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC,
.device = PCI_DEVICE_ID_INTEL_HDA_EHL_0,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC,
.device = PCI_DEVICE_ID_INTEL_HDA_EHL_3,
},
#endif
/* Alder Lake / Raptor Lake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_ALDERLAKE)
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_S,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_RPL_S,
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_P,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_P,
.codec_hid = &essx_83x6,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_P,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_PX,
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_PS,
.codec_hid = &essx_83x6,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_PS,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_M,
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_N,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ADL_N,
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_RPL_P_0,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_RPL_P_0,
},
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_RPL_P_1,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_RPL_P_1,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_RPL_M,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_RPL_PX,
},
#endif
/* Meteor Lake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_METEORLAKE)
/* Meteorlake-P */
{
.flags = FLAG_SOF,
.device = PCI_DEVICE_ID_INTEL_HDA_MTL,
.dmi_table = (const struct dmi_system_id []) {
{
.ident = "Google Chromebooks",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
}
},
{}
}
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_MTL,
},
/* ArrowLake-S */
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ARL_S,
},
/* ArrowLake */
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_ARL,
},
#endif
/* Lunar Lake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_LUNARLAKE)
/* Lunarlake-P */
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_LNL_P,
},
#endif
/* Panther Lake, Wildcat Lake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_PANTHERLAKE)
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_PTL,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_PTL_H,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_WCL,
},
#endif
/* Nova Lake */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_NOVALAKE)
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_NVL,
},
{
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
.device = PCI_DEVICE_ID_INTEL_HDA_NVL_S,
},
#endif
};
static const struct config_entry *snd_intel_dsp_find_config
(struct pci_dev *pci, const struct config_entry *table, u32 len)
{
u16 device;
device = pci->device;
for (; len > 0; len--, table++) {
if (table->device != device)
continue;
if (table->dmi_table && !dmi_check_system(table->dmi_table))
continue;
if (table->codec_hid) {
int i;
for (i = 0; i < table->codec_hid->num_codecs; i++) {
struct nhlt_acpi_table *nhlt;
bool ssp_found = false;
if (!acpi_dev_present(table->codec_hid->codecs[i], NULL, -1))
continue;
nhlt = intel_nhlt_init(&pci->dev);
if (!nhlt) {
dev_warn(&pci->dev, "%s: NHLT table not found, skipped HID %s\n",
__func__, table->codec_hid->codecs[i]);
continue;
}
if (intel_nhlt_has_endpoint_type(nhlt, NHLT_LINK_SSP) &&
intel_nhlt_ssp_endpoint_mask(nhlt, NHLT_DEVICE_I2S))
ssp_found = true;
intel_nhlt_free(nhlt);
if (ssp_found)
break;
dev_warn(&pci->dev, "%s: no valid SSP found for HID %s, skipped\n",
__func__, table->codec_hid->codecs[i]);
}
if (i == table->codec_hid->num_codecs)
continue;
}
return table;
}
return NULL;
}
static int snd_intel_dsp_check_dmic(struct pci_dev *pci)
{
int ret = 0;
acpi_nhlt_get_gbl_table();
if (acpi_nhlt_find_endpoint(ACPI_NHLT_LINKTYPE_PDM, -1, -1, -1))
ret = 1;
acpi_nhlt_put_gbl_table();
return ret;
}
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
static int snd_intel_dsp_check_soundwire(struct pci_dev *pci)
{
struct sdw_intel_acpi_info info;
acpi_handle handle;
int ret;
handle = ACPI_HANDLE(&pci->dev);
if (!handle)
return -ENODEV;
ret = sdw_intel_acpi_scan(handle, &info);
if (ret < 0)
return ret;
return info.link_mask;
}
#else
static int snd_intel_dsp_check_soundwire(struct pci_dev *pci)
{
return 0;
}
#endif
int snd_intel_dsp_driver_probe(struct pci_dev *pci)
{
const struct config_entry *cfg;
/* Intel vendor only */
if (pci->vendor != PCI_VENDOR_ID_INTEL)
return SND_INTEL_DSP_DRIVER_ANY;
/*
* Legacy devices don't have a PCI-based DSP and use HDaudio
* for HDMI/DP support, ignore kernel parameter
*/
switch (pci->device) {
case PCI_DEVICE_ID_INTEL_HDA_BDW:
case PCI_DEVICE_ID_INTEL_HDA_HSW_0:
case PCI_DEVICE_ID_INTEL_HDA_HSW_2:
case PCI_DEVICE_ID_INTEL_HDA_HSW_3:
case PCI_DEVICE_ID_INTEL_HDA_BYT:
case PCI_DEVICE_ID_INTEL_HDA_BSW:
return SND_INTEL_DSP_DRIVER_ANY;
}
if (dsp_driver > 0 && dsp_driver <= SND_INTEL_DSP_DRIVER_LAST)
return dsp_driver;
/*
* detect DSP by checking class/subclass/prog-id information
* class=04 subclass 03 prog-if 00: no DSP, use legacy driver
* class=04 subclass 01 prog-if 00: DSP is present
* (and may be required e.g. for DMIC or SSP support)
* class=04 subclass 03 prog-if 80: use DSP or legacy mode
*/
if (pci->class == 0x040300)
return SND_INTEL_DSP_DRIVER_LEGACY;
if (pci->class != 0x040100 && pci->class != 0x040380) {
dev_err(&pci->dev, "Unknown PCI class/subclass/prog-if information (0x%06x) found, selecting HDAudio legacy driver\n", pci->class);
return SND_INTEL_DSP_DRIVER_LEGACY;
}
dev_dbg(&pci->dev, "DSP detected with PCI class/subclass/prog-if info 0x%06x\n", pci->class);
/* find the configuration for the specific device */
cfg = snd_intel_dsp_find_config(pci, config_table, ARRAY_SIZE(config_table));
if (!cfg)
return IS_ENABLED(CONFIG_SND_HDA_INTEL) ?
SND_INTEL_DSP_DRIVER_LEGACY : SND_INTEL_DSP_DRIVER_ANY;
if (cfg->flags & FLAG_SOF) {
if (cfg->flags & FLAG_SOF_ONLY_IF_SOUNDWIRE &&
snd_intel_dsp_check_soundwire(pci) > 0) {
dev_info_once(&pci->dev, "SoundWire enabled on CannonLake+ platform, using SOF driver\n");
return SND_INTEL_DSP_DRIVER_SOF;
}
if (cfg->flags & FLAG_SOF_ONLY_IF_DMIC &&
snd_intel_dsp_check_dmic(pci)) {
dev_info_once(&pci->dev, "Digital mics found on Skylake+ platform, using SOF driver\n");
return SND_INTEL_DSP_DRIVER_SOF;
}
if (!(cfg->flags & FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE))
return SND_INTEL_DSP_DRIVER_SOF;
}
if (cfg->flags & FLAG_SST) {
if (cfg->flags & FLAG_SST_ONLY_IF_DMIC) {
if (snd_intel_dsp_check_dmic(pci)) {
dev_info_once(&pci->dev, "Digital mics found on Skylake+ platform, using SST driver\n");
return SND_INTEL_DSP_DRIVER_SST;
}
} else {
return SND_INTEL_DSP_DRIVER_SST;
}
}
return SND_INTEL_DSP_DRIVER_LEGACY;
}
EXPORT_SYMBOL_GPL(snd_intel_dsp_driver_probe);
/* Should we default to SOF or SST for BYT/CHT ? */
#if IS_ENABLED(CONFIG_SND_INTEL_BYT_PREFER_SOF) || \
!IS_ENABLED(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI)
#define FLAG_SST_OR_SOF_BYT FLAG_SOF
#else
#define FLAG_SST_OR_SOF_BYT FLAG_SST
#endif
/*
* configuration table
* - the order of similar ACPI ID entries is important!
* - the first successful match will win
*/
static const struct config_entry acpi_config_table[] = {
#if IS_ENABLED(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI) || \
IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
/* BayTrail */
{
.flags = FLAG_SST_OR_SOF_BYT,
.acpi_hid = "LPE0F28",
},
{
.flags = FLAG_SST_OR_SOF_BYT,
.acpi_hid = "80860F28",
},
/* CherryTrail */
{
.flags = FLAG_SST_OR_SOF_BYT,
.acpi_hid = "808622A8",
},
#endif
/* Broadwell */
#if IS_ENABLED(CONFIG_SND_SOC_INTEL_CATPT)
{
.flags = FLAG_SST,
.acpi_hid = "INT3438"
},
#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
{
.flags = FLAG_SOF,
.acpi_hid = "INT3438"
},
#endif
/* Haswell - not supported by SOF but added for consistency */
#if IS_ENABLED(CONFIG_SND_SOC_INTEL_CATPT)
{
.flags = FLAG_SST,
.acpi_hid = "INT33C8"
},
#endif
};
static const struct config_entry *snd_intel_acpi_dsp_find_config(const u8 acpi_hid[ACPI_ID_LEN],
const struct config_entry *table,
u32 len)
{
for (; len > 0; len--, table++) {
if (memcmp(table->acpi_hid, acpi_hid, ACPI_ID_LEN))
continue;
if (table->dmi_table && !dmi_check_system(table->dmi_table))
continue;
return table;
}
return NULL;
}
int snd_intel_acpi_dsp_driver_probe(struct device *dev, const u8 acpi_hid[ACPI_ID_LEN])
{
const struct config_entry *cfg;
if (dsp_driver > SND_INTEL_DSP_DRIVER_LEGACY && dsp_driver <= SND_INTEL_DSP_DRIVER_LAST)
return dsp_driver;
if (dsp_driver == SND_INTEL_DSP_DRIVER_LEGACY) {
dev_warn(dev, "dsp_driver parameter %d not supported, using automatic detection\n",
SND_INTEL_DSP_DRIVER_LEGACY);
}
/* find the configuration for the specific device */
cfg = snd_intel_acpi_dsp_find_config(acpi_hid, acpi_config_table,
ARRAY_SIZE(acpi_config_table));
if (!cfg)
return SND_INTEL_DSP_DRIVER_ANY;
if (cfg->flags & FLAG_SST)
return SND_INTEL_DSP_DRIVER_SST;
if (cfg->flags & FLAG_SOF)
return SND_INTEL_DSP_DRIVER_SOF;
return SND_INTEL_DSP_DRIVER_SST;
}
EXPORT_SYMBOL_GPL(snd_intel_acpi_dsp_driver_probe);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Intel DSP config driver");
MODULE_IMPORT_NS("SND_INTEL_SOUNDWIRE_ACPI");
+388
View File
@@ -0,0 +1,388 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2015-2019 Intel Corporation
#include <linux/acpi.h>
#include <sound/intel-nhlt.h>
struct nhlt_acpi_table *intel_nhlt_init(struct device *dev)
{
struct nhlt_acpi_table *nhlt;
acpi_status status;
status = acpi_get_table(ACPI_SIG_NHLT, 0,
(struct acpi_table_header **)&nhlt);
if (ACPI_FAILURE(status)) {
dev_warn(dev, "NHLT table not found\n");
return NULL;
}
return nhlt;
}
EXPORT_SYMBOL_GPL(intel_nhlt_init);
void intel_nhlt_free(struct nhlt_acpi_table *nhlt)
{
acpi_put_table((struct acpi_table_header *)nhlt);
}
EXPORT_SYMBOL_GPL(intel_nhlt_free);
int intel_nhlt_get_dmic_geo(struct device *dev, struct nhlt_acpi_table *nhlt)
{
struct nhlt_endpoint *epnt;
struct nhlt_dmic_array_config *cfg;
struct nhlt_vendor_dmic_array_config *cfg_vendor;
struct nhlt_fmt *fmt_configs;
unsigned int dmic_geo = 0;
u16 max_ch = 0;
u8 i, j;
if (!nhlt)
return 0;
if (nhlt->header.length <= sizeof(struct acpi_table_header)) {
dev_warn(dev, "Invalid DMIC description table\n");
return 0;
}
for (j = 0, epnt = nhlt->desc; j < nhlt->endpoint_count; j++,
epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length)) {
if (epnt->linktype != NHLT_LINK_DMIC)
continue;
cfg = (struct nhlt_dmic_array_config *)(epnt->config.caps);
fmt_configs = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size);
/* find max number of channels based on format_configuration */
if (fmt_configs->fmt_count) {
struct nhlt_fmt_cfg *fmt_cfg = fmt_configs->fmt_config;
dev_dbg(dev, "found %d format definitions\n",
fmt_configs->fmt_count);
for (i = 0; i < fmt_configs->fmt_count; i++) {
struct wav_fmt_ext *fmt_ext;
fmt_ext = &fmt_cfg->fmt_ext;
if (fmt_ext->fmt.channels > max_ch)
max_ch = fmt_ext->fmt.channels;
/* Move to the next nhlt_fmt_cfg */
fmt_cfg = (struct nhlt_fmt_cfg *)(fmt_cfg->config.caps +
fmt_cfg->config.size);
}
dev_dbg(dev, "max channels found %d\n", max_ch);
} else {
dev_dbg(dev, "No format information found\n");
}
if (cfg->device_config.config_type != NHLT_CONFIG_TYPE_MIC_ARRAY) {
dmic_geo = max_ch;
} else {
switch (cfg->array_type) {
case NHLT_MIC_ARRAY_2CH_SMALL:
case NHLT_MIC_ARRAY_2CH_BIG:
dmic_geo = MIC_ARRAY_2CH;
break;
case NHLT_MIC_ARRAY_4CH_1ST_GEOM:
case NHLT_MIC_ARRAY_4CH_L_SHAPED:
case NHLT_MIC_ARRAY_4CH_2ND_GEOM:
dmic_geo = MIC_ARRAY_4CH;
break;
case NHLT_MIC_ARRAY_VENDOR_DEFINED:
cfg_vendor = (struct nhlt_vendor_dmic_array_config *)cfg;
dmic_geo = cfg_vendor->nb_mics;
break;
default:
dev_warn(dev, "%s: undefined DMIC array_type 0x%0x\n",
__func__, cfg->array_type);
}
if (dmic_geo > 0) {
dev_dbg(dev, "Array with %d dmics\n", dmic_geo);
}
if (max_ch > dmic_geo) {
dev_dbg(dev, "max channels %d exceed dmic number %d\n",
max_ch, dmic_geo);
}
}
}
dev_dbg(dev, "dmic number %d max_ch %d\n", dmic_geo, max_ch);
return dmic_geo;
}
EXPORT_SYMBOL_GPL(intel_nhlt_get_dmic_geo);
bool intel_nhlt_has_endpoint_type(struct nhlt_acpi_table *nhlt, u8 link_type)
{
struct nhlt_endpoint *epnt;
int i;
if (!nhlt)
return false;
epnt = (struct nhlt_endpoint *)nhlt->desc;
for (i = 0; i < nhlt->endpoint_count; i++) {
if (epnt->linktype == link_type)
return true;
epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
}
return false;
}
EXPORT_SYMBOL(intel_nhlt_has_endpoint_type);
int intel_nhlt_ssp_endpoint_mask(struct nhlt_acpi_table *nhlt, u8 device_type)
{
struct nhlt_endpoint *epnt;
int ssp_mask = 0;
int i;
if (!nhlt || (device_type != NHLT_DEVICE_BT && device_type != NHLT_DEVICE_I2S))
return 0;
epnt = (struct nhlt_endpoint *)nhlt->desc;
for (i = 0; i < nhlt->endpoint_count; i++) {
if (epnt->linktype == NHLT_LINK_SSP && epnt->device_type == device_type) {
/* for SSP the virtual bus id is the SSP port */
ssp_mask |= BIT(epnt->virtual_bus_id);
}
epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
}
return ssp_mask;
}
EXPORT_SYMBOL(intel_nhlt_ssp_endpoint_mask);
#define SSP_BLOB_V1_0_SIZE 84
#define SSP_BLOB_V1_0_MDIVC_OFFSET 19 /* offset in u32 */
#define SSP_BLOB_V1_5_SIZE 96
#define SSP_BLOB_V1_5_MDIVC_OFFSET 21 /* offset in u32 */
#define SSP_BLOB_VER_1_5 0xEE000105
#define SSP_BLOB_V2_0_SIZE 88
#define SSP_BLOB_V2_0_MDIVC_OFFSET 20 /* offset in u32 */
#define SSP_BLOB_VER_2_0 0xEE000200
int intel_nhlt_ssp_mclk_mask(struct nhlt_acpi_table *nhlt, int ssp_num)
{
struct nhlt_endpoint *epnt;
struct nhlt_fmt *fmt;
struct nhlt_fmt_cfg *cfg;
int mclk_mask = 0;
int i, j;
if (!nhlt)
return 0;
epnt = (struct nhlt_endpoint *)nhlt->desc;
for (i = 0; i < nhlt->endpoint_count; i++) {
/* we only care about endpoints connected to an audio codec over SSP */
if (epnt->linktype == NHLT_LINK_SSP &&
epnt->device_type == NHLT_DEVICE_I2S &&
epnt->virtual_bus_id == ssp_num) {
fmt = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size);
cfg = fmt->fmt_config;
/*
* In theory all formats should use the same MCLK but it doesn't hurt to
* double-check that the configuration is consistent
*/
for (j = 0; j < fmt->fmt_count; j++) {
u32 *blob;
int mdivc_offset;
int size;
/* first check we have enough data to read the blob type */
if (cfg->config.size < 8)
return -EINVAL;
blob = (u32 *)cfg->config.caps;
if (blob[1] == SSP_BLOB_VER_2_0) {
mdivc_offset = SSP_BLOB_V2_0_MDIVC_OFFSET;
size = SSP_BLOB_V2_0_SIZE;
} else if (blob[1] == SSP_BLOB_VER_1_5) {
mdivc_offset = SSP_BLOB_V1_5_MDIVC_OFFSET;
size = SSP_BLOB_V1_5_SIZE;
} else {
mdivc_offset = SSP_BLOB_V1_0_MDIVC_OFFSET;
size = SSP_BLOB_V1_0_SIZE;
}
/* make sure we have enough data for the fixed part of the blob */
if (cfg->config.size < size)
return -EINVAL;
mclk_mask |= blob[mdivc_offset] & GENMASK(1, 0);
cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + cfg->config.size);
}
}
epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
}
/* make sure only one MCLK is used */
if (hweight_long(mclk_mask) != 1)
return -EINVAL;
return mclk_mask;
}
EXPORT_SYMBOL(intel_nhlt_ssp_mclk_mask);
static struct nhlt_specific_cfg *
nhlt_get_specific_cfg(struct device *dev, struct nhlt_fmt *fmt, u8 num_ch,
u32 rate, u8 vbps, u8 bps, bool ignore_vbps)
{
struct nhlt_fmt_cfg *cfg = fmt->fmt_config;
struct wav_fmt *wfmt;
u16 _bps, _vbps;
int i;
dev_dbg(dev, "Endpoint format count=%d\n", fmt->fmt_count);
for (i = 0; i < fmt->fmt_count; i++) {
wfmt = &cfg->fmt_ext.fmt;
_bps = wfmt->bits_per_sample;
_vbps = cfg->fmt_ext.sample.valid_bits_per_sample;
dev_dbg(dev, "Endpoint format: ch=%d fmt=%d/%d rate=%d\n",
wfmt->channels, _vbps, _bps, wfmt->samples_per_sec);
/*
* When looking for exact match of configuration ignore the vbps
* from NHLT table when ignore_vbps is true
*/
if (wfmt->channels == num_ch && wfmt->samples_per_sec == rate &&
(ignore_vbps || vbps == _vbps) && bps == _bps)
return &cfg->config;
cfg = (struct nhlt_fmt_cfg *)(cfg->config.caps + cfg->config.size);
}
return NULL;
}
static bool nhlt_check_ep_match(struct device *dev, struct nhlt_endpoint *epnt,
u32 bus_id, u8 link_type, u8 dir, u8 dev_type)
{
dev_dbg(dev, "Endpoint: vbus_id=%d link_type=%d dir=%d dev_type = %d\n",
epnt->virtual_bus_id, epnt->linktype,
epnt->direction, epnt->device_type);
if ((epnt->virtual_bus_id != bus_id) ||
(epnt->linktype != link_type) ||
(epnt->direction != dir))
return false;
/* link of type DMIC bypasses device_type check */
return epnt->linktype == NHLT_LINK_DMIC ||
epnt->device_type == dev_type;
}
struct nhlt_specific_cfg *
intel_nhlt_get_endpoint_blob(struct device *dev, struct nhlt_acpi_table *nhlt,
u32 bus_id, u8 link_type, u8 vbps, u8 bps,
u8 num_ch, u32 rate, u8 dir, u8 dev_type)
{
struct nhlt_specific_cfg *cfg;
struct nhlt_endpoint *epnt;
bool ignore_vbps = false;
struct nhlt_fmt *fmt;
int i;
if (!nhlt)
return NULL;
dev_dbg(dev, "Looking for configuration:\n");
dev_dbg(dev, " vbus_id=%d link_type=%d dir=%d, dev_type=%d\n",
bus_id, link_type, dir, dev_type);
if (link_type == NHLT_LINK_DMIC && bps == 32 && (vbps == 24 || vbps == 32)) {
/*
* The DMIC hardware supports only one type of 32 bits sample
* size, which is 24 bit sampling on the MSB side and bits[1:0]
* are used for indicating the channel number.
* It has been observed that some NHLT tables have the vbps
* specified as 32 while some uses 24.
* The format these variations describe are identical, the
* hardware is configured and behaves the same way.
* Note: when the samples assumed to be vbps=32 then the 'noise'
* introduced by the lower two bits (channel number) have no
* real life implication on audio quality.
*/
dev_dbg(dev,
" ch=%d fmt=%d rate=%d (vbps is ignored for DMIC 32bit format)\n",
num_ch, bps, rate);
ignore_vbps = true;
} else {
dev_dbg(dev, " ch=%d fmt=%d/%d rate=%d\n", num_ch, vbps, bps, rate);
}
dev_dbg(dev, "Endpoint count=%d\n", nhlt->endpoint_count);
epnt = (struct nhlt_endpoint *)nhlt->desc;
for (i = 0; i < nhlt->endpoint_count; i++) {
if (nhlt_check_ep_match(dev, epnt, bus_id, link_type, dir, dev_type)) {
fmt = (struct nhlt_fmt *)(epnt->config.caps + epnt->config.size);
cfg = nhlt_get_specific_cfg(dev, fmt, num_ch, rate,
vbps, bps, ignore_vbps);
if (cfg)
return cfg;
}
epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
}
return NULL;
}
EXPORT_SYMBOL(intel_nhlt_get_endpoint_blob);
int intel_nhlt_ssp_device_type(struct device *dev, struct nhlt_acpi_table *nhlt,
u8 virtual_bus_id)
{
struct nhlt_endpoint *epnt;
int i;
if (!nhlt) {
dev_err(dev, "%s: NHLT table is missing (query for SSP%d)\n",
__func__, virtual_bus_id);
return -EINVAL;
}
epnt = (struct nhlt_endpoint *)nhlt->desc;
for (i = 0; i < nhlt->endpoint_count; i++) {
/* for SSP link the virtual bus id is the SSP port number */
if (epnt->linktype == NHLT_LINK_SSP &&
epnt->virtual_bus_id == virtual_bus_id) {
dev_dbg(dev, "SSP%d: dev_type=%d\n", virtual_bus_id,
epnt->device_type);
return epnt->device_type;
}
epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
}
dev_err(dev, "%s: No match for SSP%d in NHLT table\n", __func__,
virtual_bus_id);
dev_dbg(dev, "Available endpoints:\n");
epnt = (struct nhlt_endpoint *)nhlt->desc;
for (i = 0; i < nhlt->endpoint_count; i++) {
dev_dbg(dev,
"%d: link_type: %d, vbus_id: %d, dir: %d, dev_type: %d\n",
i, epnt->linktype, epnt->virtual_bus_id,
epnt->direction, epnt->device_type);
epnt = (struct nhlt_endpoint *)((u8 *)epnt + epnt->length);
}
return -EINVAL;
}
EXPORT_SYMBOL(intel_nhlt_ssp_device_type);
+204
View File
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2015-2021 Intel Corporation.
/*
* SDW Intel ACPI scan helpers
*/
#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/soundwire/sdw_intel.h>
#include <linux/string.h>
#define SDW_LINK_TYPE 4 /* from Intel ACPI documentation */
static int ctrl_link_mask;
module_param_named(sdw_link_mask, ctrl_link_mask, int, 0444);
MODULE_PARM_DESC(sdw_link_mask, "Intel link mask (one bit per link)");
static ulong ctrl_addr = 0x40000000;
module_param_named(sdw_ctrl_addr, ctrl_addr, ulong, 0444);
MODULE_PARM_DESC(sdw_ctrl_addr, "Intel SoundWire Controller _ADR");
static bool is_link_enabled(struct fwnode_handle *fw_node, u8 idx)
{
struct fwnode_handle *link;
char name[32];
u32 quirk_mask = 0;
/* Find master handle */
snprintf(name, sizeof(name),
"mipi-sdw-link-%hhu-subproperties", idx);
link = fwnode_get_named_child_node(fw_node, name);
if (!link)
return false;
fwnode_property_read_u32(link,
"intel-quirk-mask",
&quirk_mask);
fwnode_handle_put(link);
if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE)
return false;
return true;
}
static int
sdw_intel_scan_controller(struct sdw_intel_acpi_info *info)
{
struct acpi_device *adev = acpi_fetch_acpi_dev(info->handle);
struct fwnode_handle *fwnode;
unsigned long list;
unsigned int i;
u32 count;
u32 tmp;
int ret;
if (!adev)
return -EINVAL;
fwnode = acpi_fwnode_handle(adev);
/*
* Found controller, find links supported
*
* In theory we could check the number of links supported in
* hardware, but in that step we cannot assume SoundWire IP is
* powered.
*
* In addition, if the BIOS doesn't even provide this
* 'master-count' property then all the inits based on link
* masks will fail as well.
*
* We will check the hardware capabilities in the startup() step
*/
ret = fwnode_property_read_u32(fwnode, "mipi-sdw-manager-list", &tmp);
if (ret) {
ret = fwnode_property_read_u32(fwnode, "mipi-sdw-master-count", &count);
if (ret) {
dev_err(&adev->dev,
"Failed to read mipi-sdw-master-count: %d\n",
ret);
return ret;
}
list = GENMASK(count - 1, 0);
} else {
list = tmp;
count = hweight32(list);
}
/* Check count is within bounds */
if (count > SDW_INTEL_MAX_LINKS) {
dev_err(&adev->dev, "Link count %d exceeds max %d\n",
count, SDW_INTEL_MAX_LINKS);
return -EINVAL;
}
if (!count) {
dev_warn(&adev->dev, "No SoundWire links detected\n");
return -EINVAL;
}
dev_dbg(&adev->dev, "ACPI reports %d SDW Link devices\n", count);
info->count = count;
info->link_mask = 0;
for_each_set_bit(i, &list, SDW_INTEL_MAX_LINKS) {
if (ctrl_link_mask && !(ctrl_link_mask & BIT(i))) {
dev_dbg(&adev->dev,
"Link %d masked, will not be enabled\n", i);
continue;
}
if (!is_link_enabled(fwnode, i)) {
dev_dbg(&adev->dev,
"Link %d not selected in firmware\n", i);
continue;
}
info->link_mask |= BIT(i);
}
return 0;
}
static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level,
void *cdata, void **return_value)
{
struct sdw_intel_acpi_info *info = cdata;
u64 adr;
int ret;
ret = acpi_get_local_u64_address(handle, &adr);
if (ret < 0)
return AE_OK; /* keep going */
if (!acpi_fetch_acpi_dev(handle)) {
pr_err("%s: Couldn't find ACPI handle\n", __func__);
return AE_NOT_FOUND;
}
/*
* On some Intel platforms, multiple children of the HDAS
* device can be found, but only one of them is the SoundWire
* controller. The SNDW device is always exposed with
* Name(_ADR, 0x40000000), with bits 31..28 representing the
* SoundWire link so filter accordingly
*/
if (FIELD_GET(GENMASK(31, 28), adr) != SDW_LINK_TYPE)
return AE_OK; /* keep going */
if (adr != ctrl_addr)
return AE_OK; /* keep going */
/* found the correct SoundWire controller */
info->handle = handle;
/* device found, stop namespace walk */
return AE_CTRL_TERMINATE;
}
/**
* sdw_intel_acpi_scan() - SoundWire Intel init routine
* @parent_handle: ACPI parent handle
* @info: description of what firmware/DSDT tables expose
*
* This scans the namespace and queries firmware to figure out which
* links to enable. A follow-up use of sdw_intel_probe() and
* sdw_intel_startup() is required for creation of devices and bus
* startup
*/
int sdw_intel_acpi_scan(acpi_handle parent_handle,
struct sdw_intel_acpi_info *info)
{
acpi_status status;
info->handle = NULL;
/*
* In the HDAS ACPI scope, 'SNDW' may be either the child of
* 'HDAS' or the grandchild of 'HDAS'. So let's go through
* the ACPI from 'HDAS' at max depth of 2 to find the 'SNDW'
* device.
*/
status = acpi_walk_namespace(ACPI_TYPE_DEVICE,
parent_handle, 2,
sdw_intel_acpi_cb,
NULL, info, NULL);
if (ACPI_FAILURE(status) || info->handle == NULL)
return -ENODEV;
return sdw_intel_scan_controller(info);
}
EXPORT_SYMBOL_NS(sdw_intel_acpi_scan, "SND_INTEL_SOUNDWIRE_ACPI");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Intel Soundwire ACPI helpers");
+25
View File
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Local helper macros and functions for HD-audio core drivers
*/
#ifndef __HDAC_LOCAL_H
#define __HDAC_LOCAL_H
extern const struct attribute_group *hdac_dev_attr_groups[];
int hda_widget_sysfs_init(struct hdac_device *codec);
int hda_widget_sysfs_reinit(struct hdac_device *codec, hda_nid_t start_nid,
int num_nodes);
void hda_widget_sysfs_exit(struct hdac_device *codec);
int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec);
void snd_hdac_bus_remove_device(struct hdac_bus *bus,
struct hdac_device *codec);
void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex);
int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr,
unsigned int cmd, unsigned int *res);
int snd_hdac_exec_verb(struct hdac_device *codec, unsigned int cmd,
unsigned int flags, unsigned int *res);
#endif /* __HDAC_LOCAL_H */
+588
View File
@@ -0,0 +1,588 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Regmap support for HD-audio verbs
*
* A virtual register is translated to one or more hda verbs for write,
* vice versa for read.
*
* A few limitations:
* - Provided for not all verbs but only subset standard non-volatile verbs.
* - For reading, only AC_VERB_GET_* variants can be used.
* - For writing, mapped to the *corresponding* AC_VERB_SET_* variants,
* so can't handle asymmetric verbs for read and write
*/
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/export.h>
#include <linux/pm.h>
#include <sound/core.h>
#include <sound/hdaudio.h>
#include <sound/hda_regmap.h>
#include "local.h"
static int codec_pm_lock(struct hdac_device *codec)
{
return snd_hdac_keep_power_up(codec);
}
static void codec_pm_unlock(struct hdac_device *codec, int lock)
{
if (lock == 1)
snd_hdac_power_down_pm(codec);
}
#define get_verb(reg) (((reg) >> 8) & 0xfff)
static bool hda_volatile_reg(struct device *dev, unsigned int reg)
{
struct hdac_device *codec = dev_to_hdac_dev(dev);
unsigned int verb = get_verb(reg);
switch (verb) {
case AC_VERB_GET_PROC_COEF:
return !codec->cache_coef;
case AC_VERB_GET_COEF_INDEX:
case AC_VERB_GET_PROC_STATE:
case AC_VERB_GET_POWER_STATE:
case AC_VERB_GET_PIN_SENSE:
case AC_VERB_GET_HDMI_DIP_SIZE:
case AC_VERB_GET_HDMI_ELDD:
case AC_VERB_GET_HDMI_DIP_INDEX:
case AC_VERB_GET_HDMI_DIP_DATA:
case AC_VERB_GET_HDMI_DIP_XMIT:
case AC_VERB_GET_HDMI_CP_CTRL:
case AC_VERB_GET_HDMI_CHAN_SLOT:
case AC_VERB_GET_DEVICE_SEL:
case AC_VERB_GET_DEVICE_LIST: /* read-only volatile */
return true;
}
return false;
}
static bool hda_writeable_reg(struct device *dev, unsigned int reg)
{
struct hdac_device *codec = dev_to_hdac_dev(dev);
unsigned int verb = get_verb(reg);
const unsigned int *v;
int i;
snd_array_for_each(&codec->vendor_verbs, i, v) {
if (verb == *v)
return true;
}
if (codec->caps_overwriting)
return true;
switch (verb & 0xf00) {
case AC_VERB_GET_STREAM_FORMAT:
case AC_VERB_GET_AMP_GAIN_MUTE:
return true;
case AC_VERB_GET_PROC_COEF:
return codec->cache_coef;
case 0xf00:
break;
default:
return false;
}
switch (verb) {
case AC_VERB_GET_CONNECT_SEL:
case AC_VERB_GET_SDI_SELECT:
case AC_VERB_GET_PIN_WIDGET_CONTROL:
case AC_VERB_GET_UNSOLICITED_RESPONSE: /* only as SET_UNSOLICITED_ENABLE */
case AC_VERB_GET_BEEP_CONTROL:
case AC_VERB_GET_EAPD_BTLENABLE:
case AC_VERB_GET_DIGI_CONVERT_1:
case AC_VERB_GET_DIGI_CONVERT_2: /* only for beep control */
case AC_VERB_GET_VOLUME_KNOB_CONTROL:
case AC_VERB_GET_GPIO_MASK:
case AC_VERB_GET_GPIO_DIRECTION:
case AC_VERB_GET_GPIO_DATA: /* not for volatile read */
case AC_VERB_GET_GPIO_WAKE_MASK:
case AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK:
case AC_VERB_GET_GPIO_STICKY_MASK:
return true;
}
return false;
}
static bool hda_readable_reg(struct device *dev, unsigned int reg)
{
struct hdac_device *codec = dev_to_hdac_dev(dev);
unsigned int verb = get_verb(reg);
if (codec->caps_overwriting)
return true;
switch (verb) {
case AC_VERB_PARAMETERS:
case AC_VERB_GET_CONNECT_LIST:
case AC_VERB_GET_SUBSYSTEM_ID:
return true;
/* below are basically writable, but disabled for reducing unnecessary
* writes at sync
*/
case AC_VERB_GET_CONFIG_DEFAULT: /* usually just read */
case AC_VERB_GET_CONV: /* managed in PCM code */
case AC_VERB_GET_CVT_CHAN_COUNT: /* managed in HDMI CA code */
return true;
}
return hda_writeable_reg(dev, reg);
}
/*
* Stereo amp pseudo register:
* for making easier to handle the stereo volume control, we provide a
* fake register to deal both left and right channels by a single
* (pseudo) register access. A verb consisting of SET_AMP_GAIN with
* *both* SET_LEFT and SET_RIGHT bits takes a 16bit value, the lower 8bit
* for the left and the upper 8bit for the right channel.
*/
static bool is_stereo_amp_verb(unsigned int reg)
{
if (((reg >> 8) & 0x700) != AC_VERB_SET_AMP_GAIN_MUTE)
return false;
return (reg & (AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT)) ==
(AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT);
}
/* read a pseudo stereo amp register (16bit left+right) */
static int hda_reg_read_stereo_amp(struct hdac_device *codec,
unsigned int reg, unsigned int *val)
{
unsigned int left, right;
int err;
reg &= ~(AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT);
err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_LEFT, 0, &left);
if (err < 0)
return err;
err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_RIGHT, 0, &right);
if (err < 0)
return err;
*val = left | (right << 8);
return 0;
}
/* write a pseudo stereo amp register (16bit left+right) */
static int hda_reg_write_stereo_amp(struct hdac_device *codec,
unsigned int reg, unsigned int val)
{
int err;
unsigned int verb, left, right;
verb = AC_VERB_SET_AMP_GAIN_MUTE << 8;
if (reg & AC_AMP_GET_OUTPUT)
verb |= AC_AMP_SET_OUTPUT;
else
verb |= AC_AMP_SET_INPUT | ((reg & 0xf) << 8);
reg = (reg & ~0xfffff) | verb;
left = val & 0xff;
right = (val >> 8) & 0xff;
if (left == right) {
reg |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
return snd_hdac_exec_verb(codec, reg | left, 0, NULL);
}
err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_LEFT | left, 0, NULL);
if (err < 0)
return err;
err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_RIGHT | right, 0, NULL);
if (err < 0)
return err;
return 0;
}
/* read a pseudo coef register (16bit) */
static int hda_reg_read_coef(struct hdac_device *codec, unsigned int reg,
unsigned int *val)
{
unsigned int verb;
int err;
if (!codec->cache_coef)
return -EINVAL;
/* LSB 8bit = coef index */
verb = (reg & ~0xfff00) | (AC_VERB_SET_COEF_INDEX << 8);
err = snd_hdac_exec_verb(codec, verb, 0, NULL);
if (err < 0)
return err;
verb = (reg & ~0xfffff) | (AC_VERB_GET_PROC_COEF << 8);
return snd_hdac_exec_verb(codec, verb, 0, val);
}
/* write a pseudo coef register (16bit) */
static int hda_reg_write_coef(struct hdac_device *codec, unsigned int reg,
unsigned int val)
{
unsigned int verb;
int err;
if (!codec->cache_coef)
return -EINVAL;
/* LSB 8bit = coef index */
verb = (reg & ~0xfff00) | (AC_VERB_SET_COEF_INDEX << 8);
err = snd_hdac_exec_verb(codec, verb, 0, NULL);
if (err < 0)
return err;
verb = (reg & ~0xfffff) | (AC_VERB_SET_PROC_COEF << 8) |
(val & 0xffff);
return snd_hdac_exec_verb(codec, verb, 0, NULL);
}
static int hda_reg_read(void *context, unsigned int reg, unsigned int *val)
{
struct hdac_device *codec = context;
int verb = get_verb(reg);
int err;
int pm_lock = 0;
if (verb != AC_VERB_GET_POWER_STATE) {
pm_lock = codec_pm_lock(codec);
if (pm_lock < 0)
return -EAGAIN;
}
reg |= (codec->addr << 28);
if (is_stereo_amp_verb(reg)) {
err = hda_reg_read_stereo_amp(codec, reg, val);
goto out;
}
if (verb == AC_VERB_GET_PROC_COEF) {
err = hda_reg_read_coef(codec, reg, val);
goto out;
}
if ((verb & 0x700) == AC_VERB_SET_AMP_GAIN_MUTE)
reg &= ~AC_AMP_FAKE_MUTE;
err = snd_hdac_exec_verb(codec, reg, 0, val);
if (err < 0)
goto out;
/* special handling for asymmetric reads */
if (verb == AC_VERB_GET_POWER_STATE) {
if (*val & AC_PWRST_ERROR)
*val = -1;
else /* take only the actual state */
*val = (*val >> 4) & 0x0f;
}
out:
codec_pm_unlock(codec, pm_lock);
return err;
}
static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
{
struct hdac_device *codec = context;
unsigned int verb;
int i, bytes, err;
int pm_lock = 0;
if (codec->caps_overwriting)
return 0;
reg &= ~0x00080000U; /* drop GET bit */
reg |= (codec->addr << 28);
verb = get_verb(reg);
if (verb != AC_VERB_SET_POWER_STATE) {
pm_lock = codec_pm_lock(codec);
if (pm_lock < 0)
return codec->lazy_cache ? 0 : -EAGAIN;
}
if (is_stereo_amp_verb(reg)) {
err = hda_reg_write_stereo_amp(codec, reg, val);
goto out;
}
if (verb == AC_VERB_SET_PROC_COEF) {
err = hda_reg_write_coef(codec, reg, val);
goto out;
}
switch (verb & 0xf00) {
case AC_VERB_SET_AMP_GAIN_MUTE:
if ((reg & AC_AMP_FAKE_MUTE) && (val & AC_AMP_MUTE))
val = 0;
verb = AC_VERB_SET_AMP_GAIN_MUTE;
if (reg & AC_AMP_GET_LEFT)
verb |= AC_AMP_SET_LEFT >> 8;
else
verb |= AC_AMP_SET_RIGHT >> 8;
if (reg & AC_AMP_GET_OUTPUT) {
verb |= AC_AMP_SET_OUTPUT >> 8;
} else {
verb |= AC_AMP_SET_INPUT >> 8;
verb |= reg & 0xf;
}
break;
}
switch (verb) {
case AC_VERB_SET_DIGI_CONVERT_1:
bytes = 2;
break;
case AC_VERB_SET_CONFIG_DEFAULT_BYTES_0:
bytes = 4;
break;
default:
bytes = 1;
break;
}
for (i = 0; i < bytes; i++) {
reg &= ~0xfffff;
reg |= (verb + i) << 8 | ((val >> (8 * i)) & 0xff);
err = snd_hdac_exec_verb(codec, reg, 0, NULL);
if (err < 0)
goto out;
}
out:
codec_pm_unlock(codec, pm_lock);
return err;
}
static const struct regmap_config hda_regmap_cfg = {
.name = "hdaudio",
.reg_bits = 32,
.val_bits = 32,
.max_register = 0xfffffff,
.writeable_reg = hda_writeable_reg,
.readable_reg = hda_readable_reg,
.volatile_reg = hda_volatile_reg,
.cache_type = REGCACHE_MAPLE,
.reg_read = hda_reg_read,
.reg_write = hda_reg_write,
.use_single_read = true,
.use_single_write = true,
.disable_locking = true,
};
/**
* snd_hdac_regmap_init - Initialize regmap for HDA register accesses
* @codec: the codec object
*
* Returns zero for success or a negative error code.
*/
int snd_hdac_regmap_init(struct hdac_device *codec)
{
struct regmap *regmap;
regmap = regmap_init(&codec->dev, NULL, codec, &hda_regmap_cfg);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
codec->regmap = regmap;
snd_array_init(&codec->vendor_verbs, sizeof(unsigned int), 8);
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_regmap_init);
/**
* snd_hdac_regmap_exit - Release the regmap from HDA codec
* @codec: the codec object
*/
void snd_hdac_regmap_exit(struct hdac_device *codec)
{
if (codec->regmap) {
regmap_exit(codec->regmap);
codec->regmap = NULL;
snd_array_free(&codec->vendor_verbs);
}
}
EXPORT_SYMBOL_GPL(snd_hdac_regmap_exit);
/**
* snd_hdac_regmap_add_vendor_verb - add a vendor-specific verb to regmap
* @codec: the codec object
* @verb: verb to allow accessing via regmap
*
* Returns zero for success or a negative error code.
*/
int snd_hdac_regmap_add_vendor_verb(struct hdac_device *codec,
unsigned int verb)
{
unsigned int *p = snd_array_new(&codec->vendor_verbs);
if (!p)
return -ENOMEM;
*p = verb | 0x800; /* set GET bit */
return 0;
}
EXPORT_SYMBOL_GPL(snd_hdac_regmap_add_vendor_verb);
/*
* helper functions
*/
/* write a pseudo-register value (w/o power sequence) */
static int reg_raw_write(struct hdac_device *codec, unsigned int reg,
unsigned int val)
{
guard(mutex)(&codec->regmap_lock);
if (!codec->regmap)
return hda_reg_write(codec, reg, val);
else
return regmap_write(codec->regmap, reg, val);
}
/* a helper macro to call @func_call; retry with power-up if failed */
#define CALL_RAW_FUNC(codec, func_call) \
({ \
int _err = func_call; \
if (_err == -EAGAIN) { \
_err = snd_hdac_power_up_pm(codec); \
if (_err >= 0) \
_err = func_call; \
snd_hdac_power_down_pm(codec); \
} \
_err;})
/**
* snd_hdac_regmap_write_raw - write a pseudo register with power mgmt
* @codec: the codec object
* @reg: pseudo register
* @val: value to write
*
* Returns zero if successful or a negative error code.
*/
int snd_hdac_regmap_write_raw(struct hdac_device *codec, unsigned int reg,
unsigned int val)
{
return CALL_RAW_FUNC(codec, reg_raw_write(codec, reg, val));
}
EXPORT_SYMBOL_GPL(snd_hdac_regmap_write_raw);
static int reg_raw_read(struct hdac_device *codec, unsigned int reg,
unsigned int *val, bool uncached)
{
guard(mutex)(&codec->regmap_lock);
if (uncached || !codec->regmap)
return hda_reg_read(codec, reg, val);
else
return regmap_read(codec->regmap, reg, val);
}
static int __snd_hdac_regmap_read_raw(struct hdac_device *codec,
unsigned int reg, unsigned int *val,
bool uncached)
{
return CALL_RAW_FUNC(codec, reg_raw_read(codec, reg, val, uncached));
}
/**
* snd_hdac_regmap_read_raw - read a pseudo register with power mgmt
* @codec: the codec object
* @reg: pseudo register
* @val: pointer to store the read value
*
* Returns zero if successful or a negative error code.
*/
int snd_hdac_regmap_read_raw(struct hdac_device *codec, unsigned int reg,
unsigned int *val)
{
return __snd_hdac_regmap_read_raw(codec, reg, val, false);
}
EXPORT_SYMBOL_GPL(snd_hdac_regmap_read_raw);
/* Works like snd_hdac_regmap_read_raw(), but this doesn't read from the
* cache but always via hda verbs.
*/
int snd_hdac_regmap_read_raw_uncached(struct hdac_device *codec,
unsigned int reg, unsigned int *val)
{
return __snd_hdac_regmap_read_raw(codec, reg, val, true);
}
static int reg_raw_update(struct hdac_device *codec, unsigned int reg,
unsigned int mask, unsigned int val)
{
unsigned int orig;
bool change;
int err;
guard(mutex)(&codec->regmap_lock);
if (codec->regmap) {
err = regmap_update_bits_check(codec->regmap, reg, mask, val,
&change);
if (!err)
err = change ? 1 : 0;
} else {
err = hda_reg_read(codec, reg, &orig);
if (!err) {
val &= mask;
val |= orig & ~mask;
if (val != orig) {
err = hda_reg_write(codec, reg, val);
if (!err)
err = 1;
}
}
}
return err;
}
/**
* snd_hdac_regmap_update_raw - update a pseudo register with power mgmt
* @codec: the codec object
* @reg: pseudo register
* @mask: bit mask to update
* @val: value to update
*
* Returns zero if successful or a negative error code.
*/
int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg,
unsigned int mask, unsigned int val)
{
return CALL_RAW_FUNC(codec, reg_raw_update(codec, reg, mask, val));
}
EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw);
static int reg_raw_update_once(struct hdac_device *codec, unsigned int reg,
unsigned int mask, unsigned int val)
{
if (!codec->regmap)
return reg_raw_update(codec, reg, mask, val);
guard(mutex)(&codec->regmap_lock);
/* Discard any updates to already initialised registers. */
if (!regcache_reg_cached(codec->regmap, reg))
return regmap_update_bits(codec->regmap, reg, mask, val);
return 0;
}
/**
* snd_hdac_regmap_update_raw_once - initialize the register value only once
* @codec: the codec object
* @reg: pseudo register
* @mask: bit mask to update
* @val: value to update
*
* Performs the update of the register bits only once when the register
* hasn't been initialized yet. Used in HD-audio legacy driver.
* Returns zero if successful or a negative error code
*/
int snd_hdac_regmap_update_raw_once(struct hdac_device *codec, unsigned int reg,
unsigned int mask, unsigned int val)
{
return CALL_RAW_FUNC(codec, reg_raw_update_once(codec, reg, mask, val));
}
EXPORT_SYMBOL_GPL(snd_hdac_regmap_update_raw_once);
/**
* snd_hdac_regmap_sync - sync out the cached values for PM resume
* @codec: the codec object
*/
void snd_hdac_regmap_sync(struct hdac_device *codec)
{
guard(mutex)(&codec->regmap_lock);
if (codec->regmap)
regcache_sync(codec->regmap);
}
EXPORT_SYMBOL_GPL(snd_hdac_regmap_sync);
File diff suppressed because it is too large Load Diff
+468
View File
@@ -0,0 +1,468 @@
// SPDX-License-Identifier: GPL-2.0
/*
* sysfs support for HD-audio core device
*/
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/hdaudio.h>
#include "local.h"
struct hdac_widget_tree {
struct kobject *root;
struct kobject *afg;
struct kobject **nodes;
};
#define CODEC_ATTR(type) \
static ssize_t type##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct hdac_device *codec = dev_to_hdac_dev(dev); \
return sysfs_emit(buf, "0x%x\n", codec->type); \
} \
static DEVICE_ATTR_RO(type)
#define CODEC_ATTR_STR(type) \
static ssize_t type##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct hdac_device *codec = dev_to_hdac_dev(dev); \
return sysfs_emit(buf, "%s\n", \
codec->type ? codec->type : ""); \
} \
static DEVICE_ATTR_RO(type)
CODEC_ATTR(type);
CODEC_ATTR(vendor_id);
CODEC_ATTR(subsystem_id);
CODEC_ATTR(revision_id);
CODEC_ATTR(afg);
CODEC_ATTR(mfg);
CODEC_ATTR_STR(vendor_name);
CODEC_ATTR_STR(chip_name);
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return snd_hdac_codec_modalias(dev_to_hdac_dev(dev), buf, 256);
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *hdac_dev_attrs[] = {
&dev_attr_type.attr,
&dev_attr_vendor_id.attr,
&dev_attr_subsystem_id.attr,
&dev_attr_revision_id.attr,
&dev_attr_afg.attr,
&dev_attr_mfg.attr,
&dev_attr_vendor_name.attr,
&dev_attr_chip_name.attr,
&dev_attr_modalias.attr,
NULL
};
static const struct attribute_group hdac_dev_attr_group = {
.attrs = hdac_dev_attrs,
};
const struct attribute_group *hdac_dev_attr_groups[] = {
&hdac_dev_attr_group,
NULL
};
/*
* Widget tree sysfs
*
* This is a tree showing the attributes of each widget. It appears like
* /sys/bus/hdaudioC0D0/widgets/04/caps
*/
struct widget_attribute;
struct widget_attribute {
struct attribute attr;
ssize_t (*show)(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf);
ssize_t (*store)(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr,
const char *buf, size_t count);
};
static int get_codec_nid(struct kobject *kobj, struct hdac_device **codecp)
{
struct device *dev = kobj_to_dev(kobj->parent->parent);
int nid;
ssize_t ret;
ret = kstrtoint(kobj->name, 16, &nid);
if (ret < 0)
return ret;
*codecp = dev_to_hdac_dev(dev);
return nid;
}
static ssize_t widget_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct widget_attribute *wid_attr =
container_of(attr, struct widget_attribute, attr);
struct hdac_device *codec;
int nid;
if (!wid_attr->show)
return -EIO;
nid = get_codec_nid(kobj, &codec);
if (nid < 0)
return nid;
return wid_attr->show(codec, nid, wid_attr, buf);
}
static ssize_t widget_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct widget_attribute *wid_attr =
container_of(attr, struct widget_attribute, attr);
struct hdac_device *codec;
int nid;
if (!wid_attr->store)
return -EIO;
nid = get_codec_nid(kobj, &codec);
if (nid < 0)
return nid;
return wid_attr->store(codec, nid, wid_attr, buf, count);
}
static const struct sysfs_ops widget_sysfs_ops = {
.show = widget_attr_show,
.store = widget_attr_store,
};
static void widget_release(struct kobject *kobj)
{
kfree(kobj);
}
static const struct kobj_type widget_ktype = {
.release = widget_release,
.sysfs_ops = &widget_sysfs_ops,
};
#define WIDGET_ATTR_RO(_name) \
struct widget_attribute wid_attr_##_name = __ATTR_RO(_name)
#define WIDGET_ATTR_RW(_name) \
struct widget_attribute wid_attr_##_name = __ATTR_RW(_name)
static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
return sysfs_emit(buf, "0x%08x\n", snd_hdac_get_wcaps(codec, nid));
}
static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
if (snd_hdac_get_wcaps_type(snd_hdac_get_wcaps(codec, nid)) != AC_WID_PIN)
return 0;
return sysfs_emit(buf, "0x%08x\n",
snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP));
}
static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
unsigned int val;
if (snd_hdac_get_wcaps_type(snd_hdac_get_wcaps(codec, nid)) != AC_WID_PIN)
return 0;
if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val))
return 0;
return sysfs_emit(buf, "0x%08x\n", val);
}
static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid)
{
if (nid == codec->afg || nid == codec->mfg)
return true;
switch (snd_hdac_get_wcaps_type(snd_hdac_get_wcaps(codec, nid))) {
case AC_WID_AUD_OUT:
case AC_WID_AUD_IN:
return true;
default:
return false;
}
}
static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
if (!has_pcm_cap(codec, nid))
return 0;
return sysfs_emit(buf, "0x%08x\n",
snd_hdac_read_parm(codec, nid, AC_PAR_PCM));
}
static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
if (!has_pcm_cap(codec, nid))
return 0;
return sysfs_emit(buf, "0x%08x\n",
snd_hdac_read_parm(codec, nid, AC_PAR_STREAM));
}
static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
if (nid != codec->afg && !(snd_hdac_get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
return 0;
return sysfs_emit(buf, "0x%08x\n",
snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP));
}
static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
if (nid != codec->afg && !(snd_hdac_get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
return 0;
return sysfs_emit(buf, "0x%08x\n",
snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP));
}
static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
if (nid != codec->afg && !(snd_hdac_get_wcaps(codec, nid) & AC_WCAP_POWER))
return 0;
return sysfs_emit(buf, "0x%08x\n",
snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE));
}
static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
return sysfs_emit(buf, "0x%08x\n",
snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP));
}
static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid,
struct widget_attribute *attr, char *buf)
{
hda_nid_t list[32];
int i, nconns;
ssize_t ret = 0;
nconns = snd_hdac_get_connections(codec, nid, list, ARRAY_SIZE(list));
if (nconns <= 0)
return nconns;
for (i = 0; i < nconns; i++)
ret += sysfs_emit_at(buf, ret, "%s0x%02x", i ? " " : "", list[i]);
ret += sysfs_emit_at(buf, ret, "\n");
return ret;
}
static WIDGET_ATTR_RO(caps);
static WIDGET_ATTR_RO(pin_caps);
static WIDGET_ATTR_RO(pin_cfg);
static WIDGET_ATTR_RO(pcm_caps);
static WIDGET_ATTR_RO(pcm_formats);
static WIDGET_ATTR_RO(amp_in_caps);
static WIDGET_ATTR_RO(amp_out_caps);
static WIDGET_ATTR_RO(power_caps);
static WIDGET_ATTR_RO(gpio_caps);
static WIDGET_ATTR_RO(connections);
static struct attribute *widget_node_attrs[] = {
&wid_attr_caps.attr,
&wid_attr_pin_caps.attr,
&wid_attr_pin_cfg.attr,
&wid_attr_pcm_caps.attr,
&wid_attr_pcm_formats.attr,
&wid_attr_amp_in_caps.attr,
&wid_attr_amp_out_caps.attr,
&wid_attr_power_caps.attr,
&wid_attr_connections.attr,
NULL,
};
static struct attribute *widget_afg_attrs[] = {
&wid_attr_pcm_caps.attr,
&wid_attr_pcm_formats.attr,
&wid_attr_amp_in_caps.attr,
&wid_attr_amp_out_caps.attr,
&wid_attr_power_caps.attr,
&wid_attr_gpio_caps.attr,
NULL,
};
static const struct attribute_group widget_node_group = {
.attrs = widget_node_attrs,
};
static const struct attribute_group widget_afg_group = {
.attrs = widget_afg_attrs,
};
static void free_widget_node(struct kobject *kobj,
const struct attribute_group *group)
{
if (kobj) {
sysfs_remove_group(kobj, group);
kobject_put(kobj);
}
}
static void widget_tree_free(struct hdac_device *codec)
{
struct hdac_widget_tree *tree = codec->widgets;
struct kobject **p;
if (!tree)
return;
free_widget_node(tree->afg, &widget_afg_group);
if (tree->nodes) {
for (p = tree->nodes; *p; p++)
free_widget_node(*p, &widget_node_group);
kfree(tree->nodes);
}
kobject_put(tree->root);
kfree(tree);
codec->widgets = NULL;
}
static int add_widget_node(struct kobject *parent, hda_nid_t nid,
const struct attribute_group *group,
struct kobject **res)
{
struct kobject *kobj = kzalloc_obj(*kobj);
int err;
if (!kobj)
return -ENOMEM;
kobject_init(kobj, &widget_ktype);
err = kobject_add(kobj, parent, "%02x", nid);
if (err < 0) {
kobject_put(kobj);
return err;
}
err = sysfs_create_group(kobj, group);
if (err < 0) {
kobject_put(kobj);
return err;
}
*res = kobj;
return 0;
}
static int widget_tree_create(struct hdac_device *codec)
{
struct hdac_widget_tree *tree;
int i, err;
hda_nid_t nid;
tree = codec->widgets = kzalloc_obj(*tree);
if (!tree)
return -ENOMEM;
tree->root = kobject_create_and_add("widgets", &codec->dev.kobj);
if (!tree->root)
return -ENOMEM;
tree->nodes = kzalloc_objs(*tree->nodes, codec->num_nodes + 1);
if (!tree->nodes)
return -ENOMEM;
for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) {
err = add_widget_node(tree->root, nid, &widget_node_group,
&tree->nodes[i]);
if (err < 0)
return err;
}
if (codec->afg) {
err = add_widget_node(tree->root, codec->afg,
&widget_afg_group, &tree->afg);
if (err < 0)
return err;
}
kobject_uevent(tree->root, KOBJ_CHANGE);
return 0;
}
/* call with codec->widget_lock held */
int hda_widget_sysfs_init(struct hdac_device *codec)
{
int err;
if (codec->widgets)
return 0; /* already created */
err = widget_tree_create(codec);
if (err < 0) {
widget_tree_free(codec);
return err;
}
return 0;
}
/* call with codec->widget_lock held */
void hda_widget_sysfs_exit(struct hdac_device *codec)
{
widget_tree_free(codec);
}
/* call with codec->widget_lock held */
int hda_widget_sysfs_reinit(struct hdac_device *codec,
hda_nid_t start_nid, int num_nodes)
{
struct hdac_widget_tree *tree;
hda_nid_t end_nid = start_nid + num_nodes;
hda_nid_t nid;
int i;
if (!codec->widgets)
return 0;
tree = kmemdup(codec->widgets, sizeof(*tree), GFP_KERNEL);
if (!tree)
return -ENOMEM;
tree->nodes = kzalloc_objs(*tree->nodes, num_nodes + 1);
if (!tree->nodes) {
kfree(tree);
return -ENOMEM;
}
/* prune non-existing nodes */
for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) {
if (nid < start_nid || nid >= end_nid)
free_widget_node(codec->widgets->nodes[i],
&widget_node_group);
}
/* add new nodes */
for (i = 0, nid = start_nid; i < num_nodes; i++, nid++) {
if (nid < codec->start_nid || nid >= codec->end_nid)
add_widget_node(tree->root, nid, &widget_node_group,
&tree->nodes[i]);
else
tree->nodes[i] =
codec->widgets->nodes[nid - codec->start_nid];
}
/* replace with the new tree */
kfree(codec->widgets->nodes);
kfree(codec->widgets);
codec->widgets = tree;
kobject_uevent(tree->root, KOBJ_CHANGE);
return 0;
}
+7
View File
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/*
* tracepoint definitions for HD-audio core drivers
*/
#define CREATE_TRACE_POINTS
#include "trace.h"
+101
View File
@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM hda
#if !defined(__HDAC_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define __HDAC_TRACE_H
#include <linux/tracepoint.h>
#include <linux/device.h>
#include <sound/hdaudio.h>
#ifndef HDAC_MSG_MAX
#define HDAC_MSG_MAX 500
#endif
struct hdac_bus;
struct hdac_codec;
TRACE_EVENT(hda_send_cmd,
TP_PROTO(struct hdac_bus *bus, unsigned int cmd),
TP_ARGS(bus, cmd),
TP_STRUCT__entry(
__string(name, dev_name((bus)->dev))
__field(u32, cmd)
),
TP_fast_assign(
__assign_str(name);
__entry->cmd = cmd;
),
TP_printk("[%s:%d] val=0x%08x", __get_str(name), __entry->cmd >> 28, __entry->cmd)
);
TRACE_EVENT(hda_get_response,
TP_PROTO(struct hdac_bus *bus, unsigned int addr, unsigned int res),
TP_ARGS(bus, addr, res),
TP_STRUCT__entry(
__string(name, dev_name((bus)->dev))
__field(u32, addr)
__field(u32, res)
),
TP_fast_assign(
__assign_str(name);
__entry->addr = addr;
__entry->res = res;
),
TP_printk("[%s:%d] val=0x%08x", __get_str(name), __entry->addr, __entry->res)
);
TRACE_EVENT(hda_unsol_event,
TP_PROTO(struct hdac_bus *bus, u32 res, u32 res_ex),
TP_ARGS(bus, res, res_ex),
TP_STRUCT__entry(
__string(name, dev_name((bus)->dev))
__field(u32, res)
__field(u32, res_ex)
),
TP_fast_assign(
__assign_str(name);
__entry->res = res;
__entry->res_ex = res_ex;
),
TP_printk("[%s:%d] res=0x%08x, res_ex=0x%08x", __get_str(name),
__entry->res_ex & 0x0f, __entry->res, __entry->res_ex)
);
DECLARE_EVENT_CLASS(hdac_stream,
TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev),
TP_ARGS(bus, azx_dev),
TP_STRUCT__entry(
__field(unsigned char, stream_tag)
),
TP_fast_assign(
__entry->stream_tag = (azx_dev)->stream_tag;
),
TP_printk("stream_tag: %d", __entry->stream_tag)
);
DEFINE_EVENT(hdac_stream, snd_hdac_stream_start,
TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev),
TP_ARGS(bus, azx_dev)
);
DEFINE_EVENT(hdac_stream, snd_hdac_stream_stop,
TP_PROTO(struct hdac_bus *bus, struct hdac_stream *azx_dev),
TP_ARGS(bus, azx_dev)
);
#endif /* __HDAC_TRACE_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
#include <trace/define_trace.h>