| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright 2019 Google LLC |
| * Copyright (C) 2015 - 2017 Intel Corp. |
| * Copyright (C) 2017 - 2019 Siemens AG |
| * (Written by Alexandru Gagniuc <alexandrux.gagniuc@intel.com> for Intel Corp.) |
| * (Written by Andrey Petrov <andrey.petrov@intel.com> for Intel Corp.) |
| * |
| * Portions from coreboot soc/intel/apollolake/chip.c |
| */ |
| |
| #define LOG_CATEGORY UCLASS_NORTHBRIDGE |
| |
| #include <dm.h> |
| #include <dt-structs.h> |
| #include <log.h> |
| #include <spl.h> |
| #include <tables_csum.h> |
| #include <acpi/acpi_table.h> |
| #include <asm/acpi_nhlt.h> |
| #include <asm/intel_pinctrl.h> |
| #include <asm/intel_regs.h> |
| #include <asm/io.h> |
| #include <asm/pci.h> |
| #include <asm/arch/acpi.h> |
| #include <asm/arch/hostbridge.h> |
| #include <asm/arch/systemagent.h> |
| #include <dt-bindings/sound/nhlt.h> |
| #include <dm/acpi.h> |
| |
| enum { |
| PCIEXBAR = 0x60, |
| PCIEXBAR_LENGTH_256MB = 0, |
| PCIEXBAR_LENGTH_128MB, |
| PCIEXBAR_LENGTH_64MB, |
| |
| PCIEXBAR_PCIEXBAREN = 1 << 0, |
| |
| BGSM = 0xb4, /* Base GTT Stolen Memory */ |
| TSEG = 0xb8, /* TSEG base */ |
| TOLUD = 0xbc, |
| }; |
| |
| #if CONFIG_IS_ENABLED(GENERATE_ACPI_TABLE) |
| static const struct nhlt_format_config dmic_1ch_formats[] = { |
| /* 48 KHz 16-bits per sample. */ |
| { |
| .num_channels = 1, |
| .sample_freq_khz = 48, |
| .container_bits_per_sample = 16, |
| .valid_bits_per_sample = 16, |
| .settings_file = "dmic-1ch-48khz-16b.dat", |
| }, |
| }; |
| |
| static const struct nhlt_dmic_array_config dmic_1ch_mic_config = { |
| .tdm_config = { |
| .config_type = NHLT_TDM_MIC_ARRAY, |
| }, |
| .array_type = NHLT_MIC_ARRAY_VENDOR_DEFINED, |
| }; |
| |
| static const struct nhlt_endp_descriptor dmic_1ch_descriptors[] = { |
| { |
| .link = NHLT_LINK_PDM, |
| .device = NHLT_PDM_DEV, |
| .direction = NHLT_DIR_CAPTURE, |
| .vid = NHLT_VID, |
| .did = NHLT_DID_DMIC, |
| .cfg = &dmic_1ch_mic_config, |
| .cfg_size = sizeof(dmic_1ch_mic_config), |
| .formats = dmic_1ch_formats, |
| .num_formats = ARRAY_SIZE(dmic_1ch_formats), |
| }, |
| }; |
| |
| static const struct nhlt_format_config dmic_2ch_formats[] = { |
| /* 48 KHz 16-bits per sample. */ |
| { |
| .num_channels = 2, |
| .sample_freq_khz = 48, |
| .container_bits_per_sample = 16, |
| .valid_bits_per_sample = 16, |
| .settings_file = "dmic-2ch-48khz-16b.dat", |
| }, |
| }; |
| |
| static const struct nhlt_dmic_array_config dmic_2ch_mic_config = { |
| .tdm_config = { |
| .config_type = NHLT_TDM_MIC_ARRAY, |
| }, |
| .array_type = NHLT_MIC_ARRAY_2CH_SMALL, |
| }; |
| |
| static const struct nhlt_endp_descriptor dmic_2ch_descriptors[] = { |
| { |
| .link = NHLT_LINK_PDM, |
| .device = NHLT_PDM_DEV, |
| .direction = NHLT_DIR_CAPTURE, |
| .vid = NHLT_VID, |
| .did = NHLT_DID_DMIC, |
| .cfg = &dmic_2ch_mic_config, |
| .cfg_size = sizeof(dmic_2ch_mic_config), |
| .formats = dmic_2ch_formats, |
| .num_formats = ARRAY_SIZE(dmic_2ch_formats), |
| }, |
| }; |
| |
| static const struct nhlt_format_config dmic_4ch_formats[] = { |
| /* 48 KHz 16-bits per sample. */ |
| { |
| .num_channels = 4, |
| .sample_freq_khz = 48, |
| .container_bits_per_sample = 16, |
| .valid_bits_per_sample = 16, |
| .settings_file = "dmic-4ch-48khz-16b.dat", |
| }, |
| }; |
| |
| static const struct nhlt_dmic_array_config dmic_4ch_mic_config = { |
| .tdm_config = { |
| .config_type = NHLT_TDM_MIC_ARRAY, |
| }, |
| .array_type = NHLT_MIC_ARRAY_4CH_L_SHAPED, |
| }; |
| |
| static const struct nhlt_endp_descriptor dmic_4ch_descriptors[] = { |
| { |
| .link = NHLT_LINK_PDM, |
| .device = NHLT_PDM_DEV, |
| .direction = NHLT_DIR_CAPTURE, |
| .vid = NHLT_VID, |
| .did = NHLT_DID_DMIC, |
| .cfg = &dmic_4ch_mic_config, |
| .cfg_size = sizeof(dmic_4ch_mic_config), |
| .formats = dmic_4ch_formats, |
| .num_formats = ARRAY_SIZE(dmic_4ch_formats), |
| }, |
| }; |
| #endif |
| |
| static int apl_hostbridge_early_init_pinctrl(struct udevice *dev) |
| { |
| struct apl_hostbridge_plat *plat = dev_get_plat(dev); |
| struct udevice *pinctrl; |
| int ret; |
| |
| ret = uclass_first_device_err(UCLASS_PINCTRL, &pinctrl); |
| if (ret) |
| return log_msg_ret("no hostbridge pinctrl", ret); |
| |
| return pinctrl_config_pads(pinctrl, plat->early_pads, |
| plat->early_pads_count); |
| } |
| |
| static int apl_hostbridge_early_init(struct udevice *dev) |
| { |
| struct apl_hostbridge_plat *plat = dev_get_plat(dev); |
| u32 region_size; |
| ulong base; |
| u32 reg; |
| int ret; |
| |
| /* Set up the MCHBAR */ |
| pci_x86_read_config(plat->bdf, MCHBAR, &base, PCI_SIZE_32); |
| base = MCH_BASE_ADDRESS; |
| pci_x86_write_config(plat->bdf, MCHBAR, base | 1, PCI_SIZE_32); |
| |
| /* |
| * The PCIEXBAR is assumed to live in the memory mapped IO space under |
| * 4GiB |
| */ |
| pci_x86_write_config(plat->bdf, PCIEXBAR + 4, 0, PCI_SIZE_32); |
| |
| switch (plat->pciex_region_size >> 20) { |
| default: |
| case 256: |
| region_size = PCIEXBAR_LENGTH_256MB; |
| break; |
| case 128: |
| region_size = PCIEXBAR_LENGTH_128MB; |
| break; |
| case 64: |
| region_size = PCIEXBAR_LENGTH_64MB; |
| break; |
| } |
| |
| reg = CONFIG_MMCONF_BASE_ADDRESS | (region_size << 1) |
| | PCIEXBAR_PCIEXBAREN; |
| pci_x86_write_config(plat->bdf, PCIEXBAR, reg, PCI_SIZE_32); |
| |
| /* |
| * TSEG defines the base of SMM range. BIOS determines the base |
| * of TSEG memory which must be at or below Graphics base of GTT |
| * Stolen memory, hence its better to clear TSEG register early |
| * to avoid power on default non-zero value (if any). |
| */ |
| pci_x86_write_config(plat->bdf, TSEG, 0, PCI_SIZE_32); |
| |
| ret = apl_hostbridge_early_init_pinctrl(dev); |
| if (ret) |
| return log_msg_ret("pinctrl", ret); |
| |
| return 0; |
| } |
| |
| static int apl_hostbridge_of_to_plat(struct udevice *dev) |
| { |
| struct apl_hostbridge_plat *plat = dev_get_plat(dev); |
| struct udevice *pinctrl; |
| int ret; |
| |
| /* |
| * The host bridge holds the early pad data needed to get through TPL. |
| * This is a small amount of data, enough to fit in TPL, so we keep it |
| * separate from the full pad data, stored in the fsp-s subnode. That |
| * subnode is not present in TPL, to save space. |
| */ |
| ret = uclass_first_device_err(UCLASS_PINCTRL, &pinctrl); |
| if (ret) |
| return log_msg_ret("no hostbridge PINCTRL", ret); |
| #if CONFIG_IS_ENABLED(OF_REAL) |
| int root; |
| |
| /* Get length of PCI Express Region */ |
| plat->pciex_region_size = dev_read_u32_default(dev, "pciex-region-size", |
| 256 << 20); |
| |
| root = pci_get_devfn(dev); |
| if (root < 0) |
| return log_msg_ret("Cannot get host-bridge PCI address", root); |
| plat->bdf = root; |
| |
| ret = pinctrl_read_pads(pinctrl, dev_ofnode(dev), "early-pads", |
| &plat->early_pads, &plat->early_pads_count); |
| if (ret) |
| return log_msg_ret("early-pads", ret); |
| #else |
| struct dtd_intel_apl_hostbridge *dtplat = &plat->dtplat; |
| int size; |
| |
| plat->pciex_region_size = dtplat->pciex_region_size; |
| plat->bdf = pci_ofplat_get_devfn(dtplat->reg[0]); |
| |
| /* Assume that if everything is 0, it is empty */ |
| plat->early_pads = dtplat->early_pads; |
| size = ARRAY_SIZE(dtplat->early_pads); |
| plat->early_pads_count = pinctrl_count_pads(pinctrl, plat->early_pads, |
| size); |
| |
| #endif |
| |
| return 0; |
| } |
| |
| static int apl_hostbridge_probe(struct udevice *dev) |
| { |
| if (spl_phase() == PHASE_TPL) |
| return apl_hostbridge_early_init(dev); |
| |
| return 0; |
| } |
| |
| static int apl_acpi_hb_get_name(const struct udevice *dev, char *out_name) |
| { |
| return acpi_copy_name(out_name, "RHUB"); |
| } |
| |
| #if CONFIG_IS_ENABLED(GENERATE_ACPI_TABLE) |
| static int apl_acpi_hb_write_tables(const struct udevice *dev, |
| struct acpi_ctx *ctx) |
| { |
| struct acpi_table_header *header; |
| struct acpi_dmar *dmar; |
| u32 val; |
| |
| /* |
| * Create DMAR table only if virtualization is enabled. Due to some |
| * constraints on Apollo Lake SoC (some stepping affected), VTD could |
| * not be enabled together with IPU. Doing so will override and disable |
| * VTD while leaving CAPID0_A still reporting that VTD is available. |
| * As in this case FSP will lock VTD to disabled state, we need to make |
| * sure that DMAR table generation only happens when at least DEFVTBAR |
| * is enabled. Otherwise the DMAR header will be generated while the |
| * content of the table will be missing. |
| */ |
| dm_pci_read_config32(dev, CAPID0_A, &val); |
| if ((val & VTD_DISABLE) || |
| !(readl(MCHBAR_REG(DEFVTBAR)) & VTBAR_ENABLED)) |
| return 0; |
| |
| log_debug("ACPI: * DMAR\n"); |
| dmar = (struct acpi_dmar *)ctx->current; |
| header = &dmar->header; |
| acpi_create_dmar(dmar, DMAR_INTR_REMAP); |
| ctx->current += sizeof(struct acpi_dmar); |
| apl_acpi_fill_dmar(ctx); |
| |
| /* (Re)calculate length and checksum */ |
| header->length = ctx->current - (void *)dmar; |
| header->checksum = table_compute_checksum((void *)dmar, header->length); |
| |
| acpi_align(ctx); |
| acpi_add_table(ctx, dmar); |
| |
| return 0; |
| } |
| |
| static int apl_acpi_setup_nhlt(const struct udevice *dev, struct acpi_ctx *ctx) |
| { |
| struct nhlt *nhlt = ctx->nhlt; |
| u32 channels; |
| ofnode node; |
| |
| node = ofnode_find_subnode(dev_ofnode(dev), "nhlt"); |
| if (ofnode_read_u32(node, "intel,dmic-channels", &channels)) |
| return log_msg_ret("channels", -EINVAL); |
| switch (channels) { |
| case 1: |
| return nhlt_add_endpoints(nhlt, dmic_1ch_descriptors, |
| ARRAY_SIZE(dmic_1ch_descriptors)); |
| case 2: |
| return nhlt_add_endpoints(nhlt, dmic_2ch_descriptors, |
| ARRAY_SIZE(dmic_2ch_descriptors)); |
| case 4: |
| return nhlt_add_endpoints(nhlt, dmic_4ch_descriptors, |
| ARRAY_SIZE(dmic_4ch_descriptors)); |
| } |
| |
| return log_msg_ret("channels", -EINVAL); |
| } |
| #endif |
| |
| static int apl_hostbridge_remove(struct udevice *dev) |
| { |
| /* |
| * TODO(sjg@chromium.org): Consider adding code from coreboot's |
| * platform_fsp_notify_status() |
| */ |
| |
| return 0; |
| } |
| |
| static ulong sa_read_reg(struct udevice *dev, int reg) |
| { |
| u32 val; |
| |
| /* All regions concerned for have 1 MiB alignment */ |
| dm_pci_read_config32(dev, BGSM, &val); |
| |
| return ALIGN_DOWN(val, 1 << 20); |
| } |
| |
| ulong sa_get_tolud_base(struct udevice *dev) |
| { |
| return sa_read_reg(dev, TOLUD); |
| } |
| |
| ulong sa_get_gsm_base(struct udevice *dev) |
| { |
| return sa_read_reg(dev, BGSM); |
| } |
| |
| ulong sa_get_tseg_base(struct udevice *dev) |
| { |
| return sa_read_reg(dev, TSEG); |
| } |
| |
| struct acpi_ops apl_hostbridge_acpi_ops = { |
| .get_name = apl_acpi_hb_get_name, |
| #if CONFIG_IS_ENABLED(GENERATE_ACPI_TABLE) |
| .write_tables = apl_acpi_hb_write_tables, |
| .setup_nhlt = apl_acpi_setup_nhlt, |
| #endif |
| }; |
| |
| #if CONFIG_IS_ENABLED(OF_REAL) |
| static const struct udevice_id apl_hostbridge_ids[] = { |
| { .compatible = "intel,apl-hostbridge" }, |
| { } |
| }; |
| #endif |
| |
| U_BOOT_DRIVER(intel_apl_hostbridge) = { |
| .name = "intel_apl_hostbridge", |
| .id = UCLASS_NORTHBRIDGE, |
| .of_match = of_match_ptr(apl_hostbridge_ids), |
| .of_to_plat = apl_hostbridge_of_to_plat, |
| .probe = apl_hostbridge_probe, |
| .remove = apl_hostbridge_remove, |
| .plat_auto = sizeof(struct apl_hostbridge_plat), |
| ACPI_OPS_PTR(&apl_hostbridge_acpi_ops) |
| .flags = DM_FLAG_OS_PREPARE, |
| }; |