ALSA: hda - Add new driver for HDA controllers listed via ACPI
Some systems expose HD-Audio controllers via objects in the ACPI tables
which encapsulate the controller's interrupt and the base address for the
HDA registers in an ACPI _CRS object, for example, as listed in this ACPI
table dump excerpt:
Device (HDA0)
{
Name (_HID, "NVDA2014") // _HID: Hardware ID
...
Name (_CRS, ResourceTemplate ()
// _CRS: Current Resource Settings
{
Memory32Fixed (ReadWrite,
0x36078000, // Address Base
0x00008000, // Address Length
)
Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,
,, )
{
0x0000021E,
}
})
}
Add support for HDA controllers discovered through ACPI, including support
for some platforms which expose such HDA controllers on NVIDIA SoCs. This
is done with a new driver which uses existing infrastructure for extracting
resource information from _CRS objects and plumbs the parsed resource
information through to the existing HDA infrastructure to enable HD-Audio
functionality on such devices.
Although this driver is in the sound/pci/hda/ directory, it targets devices
which are not actually enumerated on the PCI bus. This is because it
depends upon the Intel "Azalia" infrastructure which has traditionally been
usedvfor PCI-based devices.
Signed-off-by: Daniel Dadap <ddadap@nvidia.com>
Link: https://patch.msgid.link/aC3ksXJUM9DlKiz6@ddadap-lakeline.nvidia.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2025-05-21 09:35:29 -05:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
/*
|
|
|
|
* ALSA driver for ACPI-based HDA Controllers.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
|
|
|
|
#include <sound/hda_codec.h>
|
|
|
|
|
|
|
|
#include "hda_controller.h"
|
|
|
|
|
|
|
|
struct hda_acpi {
|
|
|
|
struct azx azx;
|
|
|
|
struct snd_card *card;
|
|
|
|
struct platform_device *pdev;
|
|
|
|
void __iomem *regs;
|
|
|
|
struct work_struct probe_work;
|
|
|
|
const struct hda_data *data;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* struct hda_data - Optional device-specific data
|
|
|
|
* @short_name: Used for the ALSA card name; defaults to KBUILD_MODNAME
|
|
|
|
* @long_name: Used for longer description; defaults to short_name
|
|
|
|
* @flags: Passed to &azx->driver_caps
|
|
|
|
*
|
|
|
|
* A pointer to a record of this type may be stored in the
|
|
|
|
* &acpi_device_id->driver_data field of an ACPI match table entry in order to
|
|
|
|
* customize the naming and behavior of a particular device. All fields are
|
|
|
|
* optional and sensible defaults will be selected in their absence.
|
|
|
|
*/
|
|
|
|
struct hda_data {
|
|
|
|
const char *short_name;
|
|
|
|
const char *long_name;
|
|
|
|
unsigned long flags;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int hda_acpi_dev_disconnect(struct snd_device *device)
|
|
|
|
{
|
|
|
|
struct azx *chip = device->device_data;
|
|
|
|
|
|
|
|
chip->bus.shutdown = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hda_acpi_dev_free(struct snd_device *device)
|
|
|
|
{
|
|
|
|
struct azx *azx = device->device_data;
|
|
|
|
struct hda_acpi *hda = container_of(azx, struct hda_acpi, azx);
|
|
|
|
|
|
|
|
cancel_work_sync(&hda->probe_work);
|
|
|
|
if (azx_bus(azx)->chip_init) {
|
|
|
|
azx_stop_all_streams(azx);
|
|
|
|
azx_stop_chip(azx);
|
|
|
|
}
|
|
|
|
|
|
|
|
azx_free_stream_pages(azx);
|
|
|
|
azx_free_streams(azx);
|
|
|
|
snd_hdac_bus_exit(azx_bus(azx));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hda_acpi_init(struct hda_acpi *hda)
|
|
|
|
{
|
|
|
|
struct hdac_bus *bus = azx_bus(&hda->azx);
|
|
|
|
struct snd_card *card = hda->azx.card;
|
|
|
|
struct device *dev = &hda->pdev->dev;
|
|
|
|
struct azx *azx = &hda->azx;
|
|
|
|
struct resource *res;
|
|
|
|
unsigned short gcap;
|
|
|
|
const char *sname, *lname;
|
|
|
|
int err, irq;
|
|
|
|
|
|
|
|
/* The base address for the HDA registers and the interrupt are wrapped
|
|
|
|
* in an ACPI _CRS object which can be parsed by platform_get_irq() and
|
|
|
|
* devm_platform_get_and_ioremap_resource()
|
|
|
|
*/
|
|
|
|
|
|
|
|
irq = platform_get_irq(hda->pdev, 0);
|
|
|
|
if (irq < 0)
|
|
|
|
return irq;
|
|
|
|
|
|
|
|
hda->regs = devm_platform_get_and_ioremap_resource(hda->pdev, 0, &res);
|
|
|
|
if (IS_ERR(hda->regs))
|
|
|
|
return PTR_ERR(hda->regs);
|
|
|
|
|
|
|
|
bus->remap_addr = hda->regs;
|
|
|
|
bus->addr = res->start;
|
|
|
|
|
|
|
|
err = devm_request_irq(dev, irq, azx_interrupt,
|
|
|
|
IRQF_SHARED, KBUILD_MODNAME, azx);
|
|
|
|
if (err) {
|
|
|
|
dev_err(dev, "unable to request IRQ %d, disabling device\n",
|
|
|
|
irq);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
bus->irq = irq;
|
|
|
|
bus->dma_stop_delay = 100;
|
|
|
|
card->sync_irq = bus->irq;
|
|
|
|
|
|
|
|
gcap = azx_readw(azx, GCAP);
|
|
|
|
dev_dbg(dev, "chipset global capabilities = 0x%x\n", gcap);
|
|
|
|
|
|
|
|
azx->align_buffer_size = 1;
|
|
|
|
|
|
|
|
azx->capture_streams = (gcap >> 8) & 0x0f;
|
|
|
|
azx->playback_streams = (gcap >> 12) & 0x0f;
|
|
|
|
|
|
|
|
azx->capture_index_offset = 0;
|
|
|
|
azx->playback_index_offset = azx->capture_streams;
|
|
|
|
azx->num_streams = azx->playback_streams + azx->capture_streams;
|
|
|
|
|
|
|
|
err = azx_init_streams(azx);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(dev, "failed to initialize streams: %d\n", err);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = azx_alloc_stream_pages(azx);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(dev, "failed to allocate stream pages: %d\n", err);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
azx_init_chip(azx, 1);
|
|
|
|
|
|
|
|
if (!bus->codec_mask) {
|
|
|
|
dev_err(dev, "no codecs found!\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
strscpy(card->driver, "hda-acpi");
|
|
|
|
|
|
|
|
sname = hda->data->short_name ? hda->data->short_name : KBUILD_MODNAME;
|
|
|
|
|
|
|
|
if (strlen(sname) > sizeof(card->shortname))
|
|
|
|
dev_info(dev, "truncating shortname for card %s\n", sname);
|
|
|
|
strscpy(card->shortname, sname);
|
|
|
|
|
|
|
|
lname = hda->data->long_name ? hda->data->long_name : sname;
|
|
|
|
|
|
|
|
snprintf(card->longname, sizeof(card->longname),
|
|
|
|
"%s at 0x%lx irq %i", lname, bus->addr, bus->irq);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hda_acpi_probe_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct hda_acpi *hda = container_of(work, struct hda_acpi, probe_work);
|
|
|
|
struct azx *chip = &hda->azx;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = hda_acpi_init(hda);
|
|
|
|
if (err < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
err = azx_probe_codecs(chip, 8);
|
|
|
|
if (err < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
err = azx_codec_configure(chip);
|
|
|
|
if (err < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
err = snd_card_register(chip->card);
|
|
|
|
if (err < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
chip->running = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hda_acpi_create(struct hda_acpi *hda)
|
|
|
|
{
|
|
|
|
static const struct snd_device_ops ops = {
|
|
|
|
.dev_disconnect = hda_acpi_dev_disconnect,
|
|
|
|
.dev_free = hda_acpi_dev_free,
|
|
|
|
};
|
|
|
|
static const struct hda_controller_ops null_ops;
|
|
|
|
struct azx *azx = &hda->azx;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
mutex_init(&azx->open_mutex);
|
|
|
|
azx->card = hda->card;
|
|
|
|
INIT_LIST_HEAD(&azx->pcm_list);
|
|
|
|
|
|
|
|
azx->ops = &null_ops;
|
|
|
|
azx->driver_caps = hda->data->flags;
|
|
|
|
azx->driver_type = hda->data->flags & 0xff;
|
|
|
|
azx->codec_probe_mask = -1;
|
|
|
|
|
|
|
|
err = azx_bus_init(azx, NULL);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = snd_device_new(hda->card, SNDRV_DEV_LOWLEVEL, &hda->azx, &ops);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(&hda->pdev->dev, "Error creating device\n");
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hda_acpi_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct hda_acpi *hda;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL);
|
|
|
|
if (!hda)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
hda->pdev = pdev;
|
|
|
|
hda->data = acpi_device_get_match_data(&pdev->dev);
|
|
|
|
|
|
|
|
/* Fall back to defaults if the table didn't have a *struct hda_data */
|
|
|
|
if (!hda->data)
|
|
|
|
hda->data = devm_kzalloc(&pdev->dev, sizeof(*hda->data),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!hda->data)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
|
|
|
|
THIS_MODULE, 0, &hda->card);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(&pdev->dev, "Error creating card!\n");
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_WORK(&hda->probe_work, hda_acpi_probe_work);
|
|
|
|
|
|
|
|
err = hda_acpi_create(hda);
|
|
|
|
if (err < 0)
|
|
|
|
goto out_free;
|
|
|
|
hda->card->private_data = &hda->azx;
|
|
|
|
|
|
|
|
dev_set_drvdata(&pdev->dev, hda->card);
|
|
|
|
|
|
|
|
schedule_work(&hda->probe_work);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_free:
|
|
|
|
snd_card_free(hda->card);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hda_acpi_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
snd_card_free(dev_get_drvdata(&pdev->dev));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hda_acpi_shutdown(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct snd_card *card = dev_get_drvdata(&pdev->dev);
|
|
|
|
struct azx *chip;
|
|
|
|
|
|
|
|
if (!card)
|
|
|
|
return;
|
|
|
|
chip = card->private_data;
|
|
|
|
if (chip && chip->running)
|
|
|
|
azx_stop_chip(chip);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hda_acpi_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct snd_card *card = dev_get_drvdata(dev);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = pm_runtime_force_suspend(dev);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hda_acpi_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct snd_card *card = dev_get_drvdata(dev);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = pm_runtime_force_resume(dev);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dev_pm_ops hda_acpi_pm = {
|
2025-05-22 22:46:23 +02:00
|
|
|
SYSTEM_SLEEP_PM_OPS(hda_acpi_suspend, hda_acpi_resume)
|
ALSA: hda - Add new driver for HDA controllers listed via ACPI
Some systems expose HD-Audio controllers via objects in the ACPI tables
which encapsulate the controller's interrupt and the base address for the
HDA registers in an ACPI _CRS object, for example, as listed in this ACPI
table dump excerpt:
Device (HDA0)
{
Name (_HID, "NVDA2014") // _HID: Hardware ID
...
Name (_CRS, ResourceTemplate ()
// _CRS: Current Resource Settings
{
Memory32Fixed (ReadWrite,
0x36078000, // Address Base
0x00008000, // Address Length
)
Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,
,, )
{
0x0000021E,
}
})
}
Add support for HDA controllers discovered through ACPI, including support
for some platforms which expose such HDA controllers on NVIDIA SoCs. This
is done with a new driver which uses existing infrastructure for extracting
resource information from _CRS objects and plumbs the parsed resource
information through to the existing HDA infrastructure to enable HD-Audio
functionality on such devices.
Although this driver is in the sound/pci/hda/ directory, it targets devices
which are not actually enumerated on the PCI bus. This is because it
depends upon the Intel "Azalia" infrastructure which has traditionally been
usedvfor PCI-based devices.
Signed-off-by: Daniel Dadap <ddadap@nvidia.com>
Link: https://patch.msgid.link/aC3ksXJUM9DlKiz6@ddadap-lakeline.nvidia.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2025-05-21 09:35:29 -05:00
|
|
|
};
|
|
|
|
|
2025-05-22 22:52:51 +02:00
|
|
|
static const struct hda_data nvidia_hda_data = {
|
ALSA: hda - Add new driver for HDA controllers listed via ACPI
Some systems expose HD-Audio controllers via objects in the ACPI tables
which encapsulate the controller's interrupt and the base address for the
HDA registers in an ACPI _CRS object, for example, as listed in this ACPI
table dump excerpt:
Device (HDA0)
{
Name (_HID, "NVDA2014") // _HID: Hardware ID
...
Name (_CRS, ResourceTemplate ()
// _CRS: Current Resource Settings
{
Memory32Fixed (ReadWrite,
0x36078000, // Address Base
0x00008000, // Address Length
)
Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive,
,, )
{
0x0000021E,
}
})
}
Add support for HDA controllers discovered through ACPI, including support
for some platforms which expose such HDA controllers on NVIDIA SoCs. This
is done with a new driver which uses existing infrastructure for extracting
resource information from _CRS objects and plumbs the parsed resource
information through to the existing HDA infrastructure to enable HD-Audio
functionality on such devices.
Although this driver is in the sound/pci/hda/ directory, it targets devices
which are not actually enumerated on the PCI bus. This is because it
depends upon the Intel "Azalia" infrastructure which has traditionally been
usedvfor PCI-based devices.
Signed-off-by: Daniel Dadap <ddadap@nvidia.com>
Link: https://patch.msgid.link/aC3ksXJUM9DlKiz6@ddadap-lakeline.nvidia.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2025-05-21 09:35:29 -05:00
|
|
|
.short_name = "NVIDIA",
|
|
|
|
.long_name = "NVIDIA HDA Controller",
|
|
|
|
.flags = AZX_DCAPS_CORBRP_SELF_CLEAR,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct acpi_device_id hda_acpi_match[] = {
|
|
|
|
{ .id = "NVDA2014", .driver_data = (uintptr_t) &nvidia_hda_data },
|
|
|
|
{ .id = "NVDA2015", .driver_data = (uintptr_t) &nvidia_hda_data },
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(acpi, hda_acpi_match);
|
|
|
|
|
|
|
|
static struct platform_driver hda_acpi_platform_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = KBUILD_MODNAME,
|
|
|
|
.pm = &hda_acpi_pm,
|
|
|
|
.acpi_match_table = hda_acpi_match,
|
|
|
|
},
|
|
|
|
.probe = hda_acpi_probe,
|
|
|
|
.remove = hda_acpi_remove,
|
|
|
|
.shutdown = hda_acpi_shutdown,
|
|
|
|
};
|
|
|
|
module_platform_driver(hda_acpi_platform_driver);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("Driver for ACPI-based HDA Controllers");
|
|
|
|
MODULE_LICENSE("GPL");
|