| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright 2019 Google LLC |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <dt-structs.h> |
| #include <log.h> |
| #include <spl.h> |
| #include <asm/intel_pinctrl.h> |
| #include <asm/intel_regs.h> |
| #include <asm/pci.h> |
| #include <asm/arch/systemagent.h> |
| |
| /** |
| * struct apl_hostbridge_platdata - platform data for hostbridge |
| * |
| * @dtplat: Platform data for of-platdata |
| * @early_pads: Early pad data to set up, each (pad, cfg0, cfg1) |
| * @early_pads_count: Number of pads to process |
| * @pciex_region_size: BAR length in bytes |
| * @bdf: Bus/device/function of hostbridge |
| */ |
| struct apl_hostbridge_platdata { |
| #if CONFIG_IS_ENABLED(OF_PLATDATA) |
| struct dtd_intel_apl_hostbridge dtplat; |
| #endif |
| u32 *early_pads; |
| int early_pads_count; |
| uint pciex_region_size; |
| pci_dev_t bdf; |
| }; |
| |
| 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, |
| }; |
| |
| static int apl_hostbridge_early_init_pinctrl(struct udevice *dev) |
| { |
| struct apl_hostbridge_platdata *plat = dev_get_platdata(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_platdata *plat = dev_get_platdata(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_ofdata_to_platdata(struct udevice *dev) |
| { |
| struct apl_hostbridge_platdata *plat = dev_get_platdata(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_PLATDATA) |
| 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 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); |
| } |
| |
| static const struct udevice_id apl_hostbridge_ids[] = { |
| { .compatible = "intel,apl-hostbridge" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(apl_hostbridge_drv) = { |
| .name = "intel_apl_hostbridge", |
| .id = UCLASS_NORTHBRIDGE, |
| .of_match = apl_hostbridge_ids, |
| .ofdata_to_platdata = apl_hostbridge_ofdata_to_platdata, |
| .probe = apl_hostbridge_probe, |
| .platdata_auto_alloc_size = sizeof(struct apl_hostbridge_platdata), |
| }; |