554 lines
12 KiB
C
554 lines
12 KiB
C
// 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>
|
|
|
|
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)");
|
|
|
|
#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;
|
|
};
|
|
|
|
/*
|
|
* 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 = 0x119a,
|
|
},
|
|
#endif
|
|
/* Broxton-T */
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE)
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.device = 0x1a98,
|
|
},
|
|
#endif
|
|
/*
|
|
* Apollolake (Broxton-P)
|
|
* the legacy HDAudio driver is used except on Up Squared (SOF) and
|
|
* Chromebooks (SST)
|
|
*/
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE)
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.device = 0x5a98,
|
|
.dmi_table = (const struct dmi_system_id []) {
|
|
{
|
|
.ident = "Up Squared",
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
|
|
DMI_MATCH(DMI_BOARD_NAME, "UP-APL01"),
|
|
}
|
|
},
|
|
{}
|
|
}
|
|
},
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SND_SOC_INTEL_APL)
|
|
{
|
|
.flags = FLAG_SST,
|
|
.device = 0x5a98,
|
|
.dmi_table = (const struct dmi_system_id []) {
|
|
{
|
|
.ident = "Google Chromebooks",
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
|
|
}
|
|
},
|
|
{}
|
|
}
|
|
},
|
|
#endif
|
|
/*
|
|
* Skylake and Kabylake use legacy HDAudio driver except for Google
|
|
* Chromebooks (SST)
|
|
*/
|
|
|
|
/* Sunrise Point-LP */
|
|
#if IS_ENABLED(CONFIG_SND_SOC_INTEL_SKL)
|
|
{
|
|
.flags = FLAG_SST,
|
|
.device = 0x9d70,
|
|
.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 = 0x9d70,
|
|
},
|
|
#endif
|
|
/* Kabylake-LP */
|
|
#if IS_ENABLED(CONFIG_SND_SOC_INTEL_KBL)
|
|
{
|
|
.flags = FLAG_SST,
|
|
.device = 0x9d71,
|
|
.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 = 0x9d71,
|
|
},
|
|
#endif
|
|
|
|
/*
|
|
* Geminilake uses legacy HDAudio driver except for Google
|
|
* Chromebooks
|
|
*/
|
|
/* Geminilake */
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE)
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.device = 0x3198,
|
|
.dmi_table = (const struct dmi_system_id []) {
|
|
{
|
|
.ident = "Google Chromebooks",
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Google"),
|
|
}
|
|
},
|
|
{}
|
|
}
|
|
},
|
|
#endif
|
|
|
|
/*
|
|
* CoffeeLake, CannonLake, CometLake, IceLake, TigerLake 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 = 0x9dc8,
|
|
.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 = 0x9dc8,
|
|
},
|
|
#endif
|
|
|
|
/* Coffelake */
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE)
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.device = 0xa348,
|
|
.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 = 0xa348,
|
|
},
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE)
|
|
/* Cometlake-LP */
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.device = 0x02c8,
|
|
.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 | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
|
|
.device = 0x02c8,
|
|
},
|
|
/* Cometlake-H */
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.device = 0x06c8,
|
|
.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 | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
|
|
.device = 0x06c8,
|
|
},
|
|
#endif
|
|
|
|
/* Icelake */
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE)
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.device = 0x34c8,
|
|
.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 = 0x34c8,
|
|
},
|
|
#endif
|
|
|
|
/* Tigerlake */
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE)
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.device = 0xa0c8,
|
|
.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 = 0xa0c8,
|
|
},
|
|
{
|
|
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE,
|
|
.device = 0x43c8,
|
|
},
|
|
#endif
|
|
|
|
/* Elkhart Lake */
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE)
|
|
{
|
|
.flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC,
|
|
.device = 0x4b55,
|
|
},
|
|
#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;
|
|
return table;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int snd_intel_dsp_check_dmic(struct pci_dev *pci)
|
|
{
|
|
struct nhlt_acpi_table *nhlt;
|
|
int ret = 0;
|
|
|
|
nhlt = intel_nhlt_init(&pci->dev);
|
|
if (nhlt) {
|
|
if (intel_nhlt_get_dmic_geo(&pci->dev, nhlt))
|
|
ret = 1;
|
|
intel_nhlt_free(nhlt);
|
|
}
|
|
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);
|
|
|
|
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 != 0x8086)
|
|
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 0x160c: /* Broadwell */
|
|
case 0x0a0c: /* Haswell */
|
|
case 0x0c0c:
|
|
case 0x0d0c:
|
|
case 0x0f04: /* Baytrail */
|
|
case 0x2284: /* Braswell */
|
|
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_info(&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 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(&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(&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(&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);
|
|
|
|
/*
|
|
* 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[] = {
|
|
/* BayTrail */
|
|
#if IS_ENABLED(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI)
|
|
{
|
|
.flags = FLAG_SST,
|
|
.acpi_hid = "80860F28",
|
|
},
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.acpi_hid = "80860F28",
|
|
},
|
|
#endif
|
|
/* CherryTrail */
|
|
#if IS_ENABLED(CONFIG_SND_SST_ATOM_HIFI2_PLATFORM_ACPI)
|
|
{
|
|
.flags = FLAG_SST,
|
|
.acpi_hid = "808622A8",
|
|
},
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
|
|
{
|
|
.flags = FLAG_SOF,
|
|
.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(SOUNDWIRE_INTEL_INIT);
|