// 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 for Intel Corp.) * (Written by Andrey Petrov for Intel Corp.) * * Portions from coreboot soc/intel/apollolake/chip.c */ #define LOG_CATEGORY UCLASS_NORTHBRIDGE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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, };