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
+56
View File
@@ -0,0 +1,56 @@
# SPDX-License-Identifier: GPL-2.0-only
config SND_HDA_INTEL
tristate "HD Audio PCI"
depends on PCI
select SND_HDA
select SND_INTEL_DSP_CONFIG
help
Say Y here to include support for Intel "High Definition
Audio" (Azalia) and its compatible devices.
This option enables the HD-audio controller. Don't forget
to choose the appropriate HD-audio codec options.
To compile this driver as a module, choose M here: the module
will be called snd-hda-intel.
config SND_HDA_TEGRA
tristate "NVIDIA Tegra HD Audio"
depends on ARCH_TEGRA
select SND_HDA
select SND_HDA_ALIGNED_MMIO
help
Say Y here to support the HDA controller present in NVIDIA
Tegra SoCs
This options enables support for the HD Audio controller
present in some NVIDIA Tegra SoCs, used to communicate audio
to the HDMI output.
To compile this driver as a module, choose M here: the module
will be called snd-hda-tegra.
config SND_HDA_CIX_IPBLOQ
tristate "CIX IPBLOQ HD Audio"
depends on ARCH_CIX || COMPILE_TEST
select SND_HDA
select SND_HDA_ALIGNED_MMIO
help
Say Y here to support the HDA controller present in CIX SoCs
This options enables support for the HD Audio controller
present in some CIX SoCs.
To compile this driver as a module, choose M here: the module
will be called snd-hda-cix-ipbloq.
config SND_HDA_ACPI
tristate "HD Audio ACPI"
depends on ACPI
select SND_HDA
help
Say Y here to include support for Azalia-compatible HDA controllers
which are advertised via ACPI objects.
To compile this driver as a module, choose M here: the module
will be called snd-hda-acpi.
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
snd-hda-intel-y := intel.o
snd-hda-tegra-y := tegra.o
snd-hda-cix-ipbloq-y := cix-ipbloq.o
snd-hda-acpi-y := acpi.o
subdir-ccflags-y += -I$(src)/../common
# for trace-points
CFLAGS_intel.o := -I$(src)
obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
obj-$(CONFIG_SND_HDA_TEGRA) += snd-hda-tegra.o
obj-$(CONFIG_SND_HDA_CIX_IPBLOQ) += snd-hda-cix-ipbloq.o
obj-$(CONFIG_SND_HDA_ACPI) += snd-hda-acpi.o
+325
View File
@@ -0,0 +1,325 @@
// 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 = {
SYSTEM_SLEEP_PM_OPS(hda_acpi_suspend, hda_acpi_resume)
};
static const struct hda_data nvidia_hda_data = {
.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");
@@ -0,0 +1,434 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright 2025 Cix Technology Group Co., Ltd.
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/string.h>
#include <sound/hda_codec.h>
#include "hda_controller.h"
#define CIX_IPBLOQ_JACKPOLL_DEFAULT_TIME_MS 1000
#define CIX_IPBLOQ_POWER_SAVE_DEFAULT_TIME_MS 100
#define CIX_IPBLOQ_SKY1_ADDR_HOST_TO_HDAC_OFFSET (-0x90000000ULL)
struct cix_ipbloq_hda {
struct azx chip;
struct device *dev;
void __iomem *regs;
struct reset_control *reset;
struct clk_bulk_data clocks[2];
unsigned int nclocks;
};
static const struct hda_controller_ops cix_ipbloq_hda_ops;
static int cix_ipbloq_hda_dev_disconnect(struct snd_device *device)
{
struct azx *chip = device->device_data;
chip->bus.shutdown = 1;
return 0;
}
static int cix_ipbloq_hda_dev_free(struct snd_device *device)
{
struct azx *chip = device->device_data;
if (azx_bus(chip)->chip_init) {
azx_stop_all_streams(chip);
azx_stop_chip(chip);
}
azx_free_stream_pages(chip);
azx_free_streams(chip);
snd_hdac_bus_exit(azx_bus(chip));
return 0;
}
static int cix_ipbloq_hda_probe_codec(struct cix_ipbloq_hda *hda)
{
struct azx *chip = &hda->chip;
struct hdac_bus *bus = azx_bus(chip);
int err;
to_hda_bus(bus)->bus_probing = 1;
/* create codec instances */
err = azx_probe_codecs(chip, 8);
if (err < 0) {
dev_err(hda->dev, "probe codecs failed: %d\n", err);
return err;
}
err = azx_codec_configure(chip);
if (err < 0) {
dev_err(hda->dev, "codec configure failed: %d\n", err);
return err;
}
err = snd_card_register(chip->card);
if (err < 0) {
dev_err(hda->dev, "card register failed: %d\n", err);
return err;
}
chip->running = 1;
to_hda_bus(bus)->bus_probing = 0;
snd_hda_set_power_save(&chip->bus, CIX_IPBLOQ_POWER_SAVE_DEFAULT_TIME_MS);
return 0;
}
static int cix_ipbloq_hda_init(struct cix_ipbloq_hda *hda,
struct azx *chip,
struct platform_device *pdev)
{
const char *sname = NULL, *drv_name = "cix-ipbloq-hda";
struct hdac_bus *bus = azx_bus(chip);
struct snd_card *card = chip->card;
struct resource *res;
unsigned short gcap;
int irq_id, err;
hda->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(hda->regs)) {
dev_err(hda->dev, "failed to get and ioremap resource\n");
return PTR_ERR(hda->regs);
}
bus->remap_addr = hda->regs;
bus->addr = res->start;
irq_id = platform_get_irq(pdev, 0);
if (irq_id < 0)
return irq_id;
err = devm_request_irq(hda->dev, irq_id, azx_interrupt,
0, KBUILD_MODNAME, chip);
if (err < 0)
return dev_err_probe(hda->dev, err,
"unable to request IRQ %d : err = %d\n", irq_id, err);
bus->irq = irq_id;
card->sync_irq = bus->irq;
gcap = azx_readw(chip, GCAP);
chip->capture_streams = (gcap >> 8) & 0x0f;
chip->playback_streams = (gcap >> 12) & 0x0f;
chip->capture_index_offset = 0;
chip->playback_index_offset = chip->capture_streams;
chip->num_streams = chip->playback_streams + chip->capture_streams;
/* initialize streams */
err = azx_init_streams(chip);
if (err < 0) {
dev_err(hda->dev, "failed to initialize streams: %d\n", err);
return err;
}
err = azx_alloc_stream_pages(chip);
if (err < 0) {
dev_err(hda->dev, "failed to allocate stream pages: %d\n", err);
return err;
}
/* initialize chip */
azx_init_chip(chip, 1);
/* codec detection */
if (!bus->codec_mask) {
dev_err(hda->dev, "no codecs found\n");
return -ENODEV;
}
dev_dbg(card->dev, "codec detection mask = 0x%lx\n", bus->codec_mask);
/* driver name */
strscpy(card->driver, drv_name, sizeof(card->driver));
/* shortname for card */
sname = of_get_property(pdev->dev.of_node, "model", NULL);
if (!sname)
sname = drv_name;
if (strlen(sname) > sizeof(card->shortname))
dev_dbg(card->dev, "truncating shortname for card\n");
strscpy(card->shortname, sname, sizeof(card->shortname));
/* longname for card */
snprintf(card->longname, sizeof(card->longname),
"%s at 0x%lx irq %i",
card->shortname, bus->addr, bus->irq);
return 0;
}
static int cix_ipbloq_hda_create(struct cix_ipbloq_hda *hda,
struct snd_card *card,
unsigned int driver_caps)
{
static const struct snd_device_ops ops = {
.dev_disconnect = cix_ipbloq_hda_dev_disconnect,
.dev_free = cix_ipbloq_hda_dev_free,
};
struct azx *chip;
int err;
chip = &hda->chip;
chip->card = card;
chip->ops = &cix_ipbloq_hda_ops;
chip->driver_caps = driver_caps;
chip->driver_type = driver_caps & 0xff;
chip->dev_index = 0;
chip->single_cmd = 0;
chip->codec_probe_mask = -1;
chip->align_buffer_size = 1;
chip->jackpoll_interval = msecs_to_jiffies(CIX_IPBLOQ_JACKPOLL_DEFAULT_TIME_MS);
mutex_init(&chip->open_mutex);
INIT_LIST_HEAD(&chip->pcm_list);
/*
* HD-audio controllers appear pretty inaccurate about the update-IRQ timing.
* The IRQ is issued before actually the data is processed. So use stream
* link position by default instead of dma position buffer.
*/
chip->get_position[0] = chip->get_position[1] = azx_get_pos_lpib;
err = azx_bus_init(chip, NULL);
if (err < 0) {
dev_err(hda->dev, "failed to init bus, err = %d\n", err);
return err;
}
/* RIRBSTS.RINTFL cannot be cleared, cause interrupt storm */
chip->bus.core.polling_mode = 1;
chip->bus.core.not_use_interrupts = 1;
chip->bus.core.aligned_mmio = 1;
chip->bus.core.dma_stop_delay = 100;
chip->bus.core.addr_offset = (dma_addr_t)CIX_IPBLOQ_SKY1_ADDR_HOST_TO_HDAC_OFFSET;
chip->bus.jackpoll_in_suspend = 1;
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
if (err < 0) {
dev_err(card->dev, "failed to create device, err = %d\n", err);
return err;
}
return 0;
}
static int cix_ipbloq_hda_probe(struct platform_device *pdev)
{
const unsigned int driver_flags = AZX_DCAPS_PM_RUNTIME;
struct cix_ipbloq_hda *hda;
struct snd_card *card;
struct azx *chip;
int err;
hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL);
if (!hda)
return -ENOMEM;
hda->dev = &pdev->dev;
hda->reset = devm_reset_control_get(hda->dev, NULL);
if (IS_ERR(hda->reset))
return dev_err_probe(hda->dev, PTR_ERR(hda->reset),
"failed to get reset, err = %ld\n", PTR_ERR(hda->reset));
hda->clocks[hda->nclocks++].id = "ipg";
hda->clocks[hda->nclocks++].id = "per";
err = devm_clk_bulk_get(hda->dev, hda->nclocks, hda->clocks);
if (err < 0)
return dev_err_probe(hda->dev, err, "failed to get clk, err = %d\n", err);
dma_set_mask_and_coherent(hda->dev, DMA_BIT_MASK(32));
err = of_reserved_mem_device_init(hda->dev);
if (err < 0 && err != -ENODEV) {
dev_err(hda->dev,
"failed to init reserved mem for DMA, err = %d\n", err);
return err;
}
err = snd_card_new(hda->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
THIS_MODULE, 0, &card);
if (err < 0)
return dev_err_probe(hda->dev, err, "failed to crate card, err = %d\n", err);
err = cix_ipbloq_hda_create(hda, card, driver_flags);
if (err < 0)
goto out_free_card;
chip = &hda->chip;
card->private_data = chip;
dev_set_drvdata(hda->dev, card);
pm_runtime_enable(hda->dev);
if (!azx_has_pm_runtime(chip))
pm_runtime_forbid(hda->dev);
err = pm_runtime_resume_and_get(hda->dev);
if (err < 0) {
dev_err(hda->dev, "runtime resume and get failed, err = %d\n", err);
goto out_free_device;
}
err = cix_ipbloq_hda_init(hda, chip, pdev);
if (err < 0)
goto out_free_device;
err = cix_ipbloq_hda_probe_codec(hda);
if (err < 0)
goto out_free_device;
pm_runtime_put(hda->dev);
return 0;
out_free_device:
snd_device_free(card, chip);
out_free_card:
snd_card_free(card);
return err;
}
static void cix_ipbloq_hda_remove(struct platform_device *pdev)
{
struct snd_card *card = dev_get_drvdata(&pdev->dev);
struct azx *chip = card->private_data;
snd_device_free(card, chip);
snd_card_free(card);
pm_runtime_disable(&pdev->dev);
}
static void cix_ipbloq_hda_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 cix_ipbloq_hda_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_D3cold);
return 0;
}
static int cix_ipbloq_hda_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 int cix_ipbloq_hda_runtime_suspend(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
struct azx *chip = card->private_data;
struct cix_ipbloq_hda *hda = container_of(chip, struct cix_ipbloq_hda, chip);
if (chip && chip->running) {
azx_stop_chip(chip);
azx_enter_link_reset(chip);
}
clk_bulk_disable_unprepare(hda->nclocks, hda->clocks);
return 0;
}
static int cix_ipbloq_hda_runtime_resume(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
struct azx *chip = card->private_data;
struct cix_ipbloq_hda *hda = container_of(chip, struct cix_ipbloq_hda, chip);
int rc;
rc = clk_bulk_prepare_enable(hda->nclocks, hda->clocks);
if (rc) {
dev_err(dev, "failed to enable clk bulk, rc: %d\n", rc);
return rc;
}
rc = reset_control_assert(hda->reset);
if (rc) {
dev_err(dev, "failed to assert reset, rc: %d\n", rc);
return rc;
}
rc = reset_control_deassert(hda->reset);
if (rc) {
dev_err(dev, "failed to deassert reset, rc: %d\n", rc);
return rc;
}
if (chip && chip->running)
azx_init_chip(chip, 1);
return 0;
}
static const struct dev_pm_ops cix_ipbloq_hda_pm = {
SYSTEM_SLEEP_PM_OPS(cix_ipbloq_hda_suspend,
cix_ipbloq_hda_resume)
RUNTIME_PM_OPS(cix_ipbloq_hda_runtime_suspend,
cix_ipbloq_hda_runtime_resume, NULL)
};
static const struct of_device_id cix_ipbloq_hda_match[] = {
{ .compatible = "cix,sky1-ipbloq-hda" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, cix_ipbloq_hda_match);
static struct platform_driver cix_ipbloq_hda_driver = {
.driver = {
.name = "cix-ipbloq-hda",
.pm = pm_ptr(&cix_ipbloq_hda_pm),
.of_match_table = cix_ipbloq_hda_match,
},
.probe = cix_ipbloq_hda_probe,
.remove = cix_ipbloq_hda_remove,
.shutdown = cix_ipbloq_hda_shutdown,
};
module_platform_driver(cix_ipbloq_hda_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CIX IPBLOQ HDA bus driver");
MODULE_AUTHOR("Joakim Zhang <joakim.zhang@cixtech.com>");
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
*/
#ifndef __SOUND_HDA_INTEL_H
#define __SOUND_HDA_INTEL_H
#include "hda_controller.h"
struct hda_intel {
struct azx chip;
/* sync probing */
struct completion probe_wait;
struct delayed_work probe_work;
/* card list (for power_save trigger) */
struct list_head list;
/* extra flags */
unsigned int irq_pending_warned:1;
unsigned int probe_continued:1;
unsigned int runtime_pm_disabled:1;
/* vga_switcheroo setup */
unsigned int use_vga_switcheroo:1;
unsigned int vga_switcheroo_registered:1;
unsigned int init_failed:1; /* delayed init failed */
unsigned int freed:1; /* resources already released */
bool need_i915_power:1; /* the hda controller needs i915 power */
int probe_retry; /* being probe-retry */
};
struct hda_intel_stream {
struct azx_dev azx_dev;
/* for pending irqs */
struct hda_intel *hda;
struct work_struct irq_pending_work;
bool irq_pending;
};
#define azx_dev_to_istream(azx_dev) \
container_of(azx_dev, struct hda_intel_stream, azx_dev)
#endif
@@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM hda_intel
#define TRACE_INCLUDE_FILE intel_trace
#if !defined(_TRACE_HDA_INTEL_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_HDA_INTEL_H
#include <linux/tracepoint.h>
DECLARE_EVENT_CLASS(hda_pm,
TP_PROTO(struct azx *chip),
TP_ARGS(chip),
TP_STRUCT__entry(
__field(int, dev_index)
),
TP_fast_assign(
__entry->dev_index = (chip)->dev_index;
),
TP_printk("card index: %d", __entry->dev_index)
);
DEFINE_EVENT(hda_pm, azx_suspend,
TP_PROTO(struct azx *chip),
TP_ARGS(chip)
);
DEFINE_EVENT(hda_pm, azx_resume,
TP_PROTO(struct azx *chip),
TP_ARGS(chip)
);
DEFINE_EVENT(hda_pm, azx_runtime_suspend,
TP_PROTO(struct azx *chip),
TP_ARGS(chip)
);
DEFINE_EVENT(hda_pm, azx_runtime_resume,
TP_PROTO(struct azx *chip),
TP_ARGS(chip)
);
#endif /* _TRACE_HDA_INTEL_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#include <trace/define_trace.h>
+648
View File
@@ -0,0 +1,648 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
*
* Implementation of primary ALSA driver code base for NVIDIA Tegra HDA.
*/
#include <linux/clk.h>
#include <linux/clocksource.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/string.h>
#include <linux/pm_runtime.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/hda_codec.h>
#include "hda_controller.h"
/* Defines for Nvidia Tegra HDA support */
#define HDA_BAR0 0x8000
#define HDA_CFG_CMD 0x1004
#define HDA_CFG_BAR0 0x1010
#define HDA_ENABLE_IO_SPACE (1 << 0)
#define HDA_ENABLE_MEM_SPACE (1 << 1)
#define HDA_ENABLE_BUS_MASTER (1 << 2)
#define HDA_ENABLE_SERR (1 << 8)
#define HDA_DISABLE_INTR (1 << 10)
#define HDA_BAR0_INIT_PROGRAM 0xFFFFFFFF
#define HDA_BAR0_FINAL_PROGRAM (1 << 14)
/* IPFS */
#define HDA_IPFS_CONFIG 0x180
#define HDA_IPFS_EN_FPCI 0x1
#define HDA_IPFS_FPCI_BAR0 0x80
#define HDA_FPCI_BAR0_START 0x40
#define HDA_IPFS_INTR_MASK 0x188
#define HDA_IPFS_EN_INTR (1 << 16)
/* FPCI */
#define FPCI_DBG_CFG_2 0x10F4
#define FPCI_GCAP_NSDO_SHIFT 18
#define FPCI_GCAP_NSDO_MASK (0x3 << FPCI_GCAP_NSDO_SHIFT)
/* max number of SDs */
#define NUM_CAPTURE_SD 1
#define NUM_PLAYBACK_SD 1
/*
* Tegra194 does not reflect correct number of SDO lines. Below macro
* is used to update the GCAP register to workaround the issue.
*/
#define TEGRA194_NUM_SDO_LINES 4
struct hda_tegra_soc {
bool has_hda2codec_2x_reset;
bool has_hda2hdmi;
bool has_hda2codec_2x;
bool input_stream;
bool always_on;
bool requires_init;
};
struct hda_tegra {
struct azx chip;
struct device *dev;
struct reset_control_bulk_data resets[3];
struct clk_bulk_data clocks[3];
unsigned int nresets;
unsigned int nclocks;
void __iomem *regs;
struct work_struct probe_work;
const struct hda_tegra_soc *soc;
};
#ifdef CONFIG_PM
static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
module_param(power_save, bint, 0644);
MODULE_PARM_DESC(power_save,
"Automatic power-saving timeout (in seconds, 0 = disable).");
#else
#define power_save 0
#endif
static const struct hda_controller_ops hda_tegra_ops; /* nothing special */
static void hda_tegra_init(struct hda_tegra *hda)
{
u32 v;
/* Enable PCI access */
v = readl(hda->regs + HDA_IPFS_CONFIG);
v |= HDA_IPFS_EN_FPCI;
writel(v, hda->regs + HDA_IPFS_CONFIG);
/* Enable MEM/IO space and bus master */
v = readl(hda->regs + HDA_CFG_CMD);
v &= ~HDA_DISABLE_INTR;
v |= HDA_ENABLE_MEM_SPACE | HDA_ENABLE_IO_SPACE |
HDA_ENABLE_BUS_MASTER | HDA_ENABLE_SERR;
writel(v, hda->regs + HDA_CFG_CMD);
writel(HDA_BAR0_INIT_PROGRAM, hda->regs + HDA_CFG_BAR0);
writel(HDA_BAR0_FINAL_PROGRAM, hda->regs + HDA_CFG_BAR0);
writel(HDA_FPCI_BAR0_START, hda->regs + HDA_IPFS_FPCI_BAR0);
v = readl(hda->regs + HDA_IPFS_INTR_MASK);
v |= HDA_IPFS_EN_INTR;
writel(v, hda->regs + HDA_IPFS_INTR_MASK);
}
/*
* power management
*/
static int hda_tegra_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_tegra_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 int hda_tegra_runtime_suspend(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
struct azx *chip = card->private_data;
struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
if (chip && chip->running) {
/* enable controller wake up event */
azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) |
STATESTS_INT_MASK);
azx_stop_chip(chip);
azx_enter_link_reset(chip);
}
clk_bulk_disable_unprepare(hda->nclocks, hda->clocks);
return 0;
}
static int hda_tegra_runtime_resume(struct device *dev)
{
struct snd_card *card = dev_get_drvdata(dev);
struct azx *chip = card->private_data;
struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
int rc;
if (!chip->running) {
rc = reset_control_bulk_assert(hda->nresets, hda->resets);
if (rc)
return rc;
}
rc = clk_bulk_prepare_enable(hda->nclocks, hda->clocks);
if (rc != 0)
return rc;
if (chip->running) {
if (hda->soc->requires_init)
hda_tegra_init(hda);
azx_init_chip(chip, 1);
/* disable controller wake up event*/
azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) &
~STATESTS_INT_MASK);
} else {
usleep_range(10, 100);
rc = reset_control_bulk_deassert(hda->nresets, hda->resets);
if (rc)
return rc;
}
return 0;
}
static const struct dev_pm_ops hda_tegra_pm = {
SYSTEM_SLEEP_PM_OPS(hda_tegra_suspend, hda_tegra_resume)
RUNTIME_PM_OPS(hda_tegra_runtime_suspend, hda_tegra_runtime_resume, NULL)
};
static int hda_tegra_dev_disconnect(struct snd_device *device)
{
struct azx *chip = device->device_data;
chip->bus.shutdown = 1;
return 0;
}
/*
* destructor
*/
static int hda_tegra_dev_free(struct snd_device *device)
{
struct azx *chip = device->device_data;
struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
cancel_work_sync(&hda->probe_work);
if (azx_bus(chip)->chip_init) {
azx_stop_all_streams(chip);
azx_stop_chip(chip);
}
azx_free_stream_pages(chip);
azx_free_streams(chip);
snd_hdac_bus_exit(azx_bus(chip));
return 0;
}
static int hda_tegra_init_chip(struct azx *chip, struct platform_device *pdev)
{
struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
struct hdac_bus *bus = azx_bus(chip);
struct resource *res;
hda->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(hda->regs))
return PTR_ERR(hda->regs);
bus->remap_addr = hda->regs + HDA_BAR0;
bus->addr = res->start + HDA_BAR0;
if (hda->soc->requires_init)
hda_tegra_init(hda);
return 0;
}
static int hda_tegra_first_init(struct azx *chip, struct platform_device *pdev)
{
struct hda_tegra *hda = container_of(chip, struct hda_tegra, chip);
struct hdac_bus *bus = azx_bus(chip);
struct snd_card *card = chip->card;
int err;
unsigned short gcap;
int irq_id = platform_get_irq(pdev, 0);
const char *sname, *drv_name = "tegra-hda";
struct device_node *np = pdev->dev.of_node;
if (irq_id < 0)
return irq_id;
err = hda_tegra_init_chip(chip, pdev);
if (err)
return err;
err = devm_request_irq(chip->card->dev, irq_id, azx_interrupt,
IRQF_SHARED, KBUILD_MODNAME, chip);
if (err) {
dev_err(chip->card->dev,
"unable to request IRQ %d, disabling device\n",
irq_id);
return err;
}
bus->irq = irq_id;
bus->dma_stop_delay = 100;
card->sync_irq = bus->irq;
/*
* Tegra194 has 4 SDO lines and the STRIPE can be used to
* indicate how many of the SDO lines the stream should be
* striped. But GCAP register does not reflect the true
* capability of HW. Below workaround helps to fix this.
*
* GCAP_NSDO is bits 19:18 in T_AZA_DBG_CFG_2,
* 0 for 1 SDO, 1 for 2 SDO, 2 for 4 SDO lines.
*/
if (of_device_is_compatible(np, "nvidia,tegra194-hda")) {
u32 val;
dev_info(card->dev, "Override SDO lines to %u\n",
TEGRA194_NUM_SDO_LINES);
val = readl(hda->regs + FPCI_DBG_CFG_2) & ~FPCI_GCAP_NSDO_MASK;
val |= (TEGRA194_NUM_SDO_LINES >> 1) << FPCI_GCAP_NSDO_SHIFT;
writel(val, hda->regs + FPCI_DBG_CFG_2);
}
gcap = azx_readw(chip, GCAP);
dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);
chip->align_buffer_size = 1;
/* read number of streams from GCAP register instead of using
* hardcoded value
*/
chip->capture_streams = (gcap >> 8) & 0x0f;
/* The GCAP register on Tegra234 implies no Input Streams(ISS) support,
* but the HW output stream descriptor programming should start with
* offset 0x20*4 from base stream descriptor address. This will be a
* problem while calculating the offset for output stream descriptor
* which will be considering input stream also. So here output stream
* starts with offset 0 which is wrong as HW register for output stream
* offset starts with 4.
*/
if (!hda->soc->input_stream)
chip->capture_streams = 4;
chip->playback_streams = (gcap >> 12) & 0x0f;
if (!chip->playback_streams && !chip->capture_streams) {
/* gcap didn't give any info, switching to old method */
chip->playback_streams = NUM_PLAYBACK_SD;
chip->capture_streams = NUM_CAPTURE_SD;
}
chip->capture_index_offset = 0;
chip->playback_index_offset = chip->capture_streams;
chip->num_streams = chip->playback_streams + chip->capture_streams;
/* initialize streams */
err = azx_init_streams(chip);
if (err < 0) {
dev_err(card->dev, "failed to initialize streams: %d\n", err);
return err;
}
err = azx_alloc_stream_pages(chip);
if (err < 0) {
dev_err(card->dev, "failed to allocate stream pages: %d\n",
err);
return err;
}
/* initialize chip */
azx_init_chip(chip, 1);
/*
* Playback (for 44.1K/48K, 2-channel, 16-bps) fails with
* 4 SDO lines due to legacy design limitation. Following
* is, from HD Audio Specification (Revision 1.0a), used to
* control striping of the stream across multiple SDO lines
* for sample rates <= 48K.
*
* { ((num_channels * bits_per_sample) / number of SDOs) >= 8 }
*
* Due to legacy design issue it is recommended that above
* ratio must be greater than 8. Since number of SDO lines is
* in powers of 2, next available ratio is 16 which can be
* used as a limiting factor here.
*/
if (of_device_is_compatible(np, "nvidia,tegra30-hda"))
chip->bus.core.sdo_limit = 16;
/* codec detection */
if (!bus->codec_mask) {
dev_err(card->dev, "no codecs found!\n");
return -ENODEV;
}
/* driver name */
strscpy(card->driver, drv_name);
/* shortname for card */
sname = of_get_property(np, "nvidia,model", NULL);
if (!sname)
sname = drv_name;
if (strlen(sname) > sizeof(card->shortname))
dev_info(card->dev, "truncating shortname for card\n");
strscpy(card->shortname, sname);
/* longname for card */
snprintf(card->longname, sizeof(card->longname),
"%s at 0x%lx irq %i",
card->shortname, bus->addr, bus->irq);
return 0;
}
/*
* constructor
*/
static void hda_tegra_probe_work(struct work_struct *work);
static int hda_tegra_create(struct snd_card *card,
unsigned int driver_caps,
struct hda_tegra *hda)
{
static const struct snd_device_ops ops = {
.dev_disconnect = hda_tegra_dev_disconnect,
.dev_free = hda_tegra_dev_free,
};
struct azx *chip;
int err;
chip = &hda->chip;
mutex_init(&chip->open_mutex);
chip->card = card;
chip->ops = &hda_tegra_ops;
chip->driver_caps = driver_caps;
chip->driver_type = driver_caps & 0xff;
chip->dev_index = 0;
INIT_LIST_HEAD(&chip->pcm_list);
chip->codec_probe_mask = -1;
chip->single_cmd = false;
chip->snoop = true;
INIT_WORK(&hda->probe_work, hda_tegra_probe_work);
err = azx_bus_init(chip, NULL);
if (err < 0)
return err;
chip->bus.core.sync_write = 0;
chip->bus.core.needs_damn_long_delay = 1;
chip->bus.core.aligned_mmio = 1;
/*
* HDA power domain and clocks are always on for Tegra264 and
* the jack detection logic would work always, so no need of
* jack polling mechanism running.
*/
if (!hda->soc->always_on) {
chip->jackpoll_interval = msecs_to_jiffies(5000);
chip->bus.jackpoll_in_suspend = 1;
}
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
if (err < 0) {
dev_err(card->dev, "Error creating device\n");
return err;
}
return 0;
}
static const struct hda_tegra_soc tegra30_data = {
.has_hda2codec_2x_reset = true,
.has_hda2hdmi = true,
.has_hda2codec_2x = true,
.input_stream = true,
.always_on = false,
.requires_init = true,
};
static const struct hda_tegra_soc tegra194_data = {
.has_hda2codec_2x_reset = false,
.has_hda2hdmi = true,
.has_hda2codec_2x = true,
.input_stream = true,
.always_on = false,
.requires_init = true,
};
static const struct hda_tegra_soc tegra234_data = {
.has_hda2codec_2x_reset = true,
.has_hda2hdmi = false,
.has_hda2codec_2x = true,
.input_stream = false,
.always_on = false,
.requires_init = true,
};
static const struct hda_tegra_soc tegra264_data = {
.has_hda2codec_2x_reset = true,
.has_hda2hdmi = false,
.has_hda2codec_2x = false,
.input_stream = false,
.always_on = true,
.requires_init = false,
};
static const struct of_device_id hda_tegra_match[] = {
{ .compatible = "nvidia,tegra30-hda", .data = &tegra30_data },
{ .compatible = "nvidia,tegra194-hda", .data = &tegra194_data },
{ .compatible = "nvidia,tegra234-hda", .data = &tegra234_data },
{ .compatible = "nvidia,tegra264-hda", .data = &tegra264_data },
{},
};
MODULE_DEVICE_TABLE(of, hda_tegra_match);
static int hda_tegra_probe(struct platform_device *pdev)
{
const unsigned int driver_flags = AZX_DCAPS_CORBRP_SELF_CLEAR |
AZX_DCAPS_PM_RUNTIME |
AZX_DCAPS_4K_BDLE_BOUNDARY;
struct snd_card *card;
struct azx *chip;
struct hda_tegra *hda;
int err;
hda = devm_kzalloc(&pdev->dev, sizeof(*hda), GFP_KERNEL);
if (!hda)
return -ENOMEM;
hda->dev = &pdev->dev;
chip = &hda->chip;
hda->soc = of_device_get_match_data(&pdev->dev);
err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
THIS_MODULE, 0, &card);
if (err < 0) {
dev_err(&pdev->dev, "Error creating card!\n");
return err;
}
hda->resets[hda->nresets++].id = "hda";
/*
* "hda2hdmi" is not applicable for Tegra234. This is because the
* codec is separate IP and not under display SOR partition now.
*/
if (hda->soc->has_hda2hdmi)
hda->resets[hda->nresets++].id = "hda2hdmi";
/*
* "hda2codec_2x" reset is not present on Tegra194. Though DT would
* be updated to reflect this, but to have backward compatibility
* below is necessary.
*/
if (hda->soc->has_hda2codec_2x_reset)
hda->resets[hda->nresets++].id = "hda2codec_2x";
err = devm_reset_control_bulk_get_exclusive(&pdev->dev, hda->nresets,
hda->resets);
if (err)
goto out_free;
hda->clocks[hda->nclocks++].id = "hda";
if (hda->soc->has_hda2hdmi)
hda->clocks[hda->nclocks++].id = "hda2hdmi";
if (hda->soc->has_hda2codec_2x)
hda->clocks[hda->nclocks++].id = "hda2codec_2x";
err = devm_clk_bulk_get(&pdev->dev, hda->nclocks, hda->clocks);
if (err < 0)
goto out_free;
err = hda_tegra_create(card, driver_flags, hda);
if (err < 0)
goto out_free;
card->private_data = chip;
dev_set_drvdata(&pdev->dev, card);
pm_runtime_enable(hda->dev);
if (!azx_has_pm_runtime(chip))
pm_runtime_forbid(hda->dev);
schedule_work(&hda->probe_work);
return 0;
out_free:
snd_card_free(card);
return err;
}
static void hda_tegra_probe_work(struct work_struct *work)
{
struct hda_tegra *hda = container_of(work, struct hda_tegra, probe_work);
struct azx *chip = &hda->chip;
struct platform_device *pdev = to_platform_device(hda->dev);
int err;
guard(pm_runtime_active)(hda->dev);
err = hda_tegra_first_init(chip, pdev);
if (err < 0)
return;
/* create codec instances */
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;
snd_hda_set_power_save(&chip->bus, power_save * 1000);
}
static void hda_tegra_remove(struct platform_device *pdev)
{
snd_card_free(dev_get_drvdata(&pdev->dev));
pm_runtime_disable(&pdev->dev);
}
static void hda_tegra_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 struct platform_driver tegra_platform_hda = {
.driver = {
.name = "tegra-hda",
.pm = pm_ptr(&hda_tegra_pm),
.of_match_table = hda_tegra_match,
},
.probe = hda_tegra_probe,
.remove = hda_tegra_remove,
.shutdown = hda_tegra_shutdown,
};
module_platform_driver(tegra_platform_hda);
MODULE_DESCRIPTION("Tegra HDA bus driver");
MODULE_LICENSE("GPL v2");