Angel Pons | 4b42983 | 2020-04-02 23:48:50 +0200 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 2 | |
| 3 | #include <console/console.h> |
| 4 | #include <device/device.h> |
| 5 | #include <device/pci.h> |
| 6 | #include <device/pciexp.h> |
| 7 | #include <device/pci_ids.h> |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 8 | #include <device/pci_ops.h> |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 9 | #include <assert.h> |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 10 | #include <types.h> |
| 11 | |
| 12 | #include "chip.h" |
| 13 | #include "haswell.h" |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 14 | |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 15 | #if CONFIG(HAVE_ACPI_TABLES) |
| 16 | static const char *pcie_acpi_name(const struct device *dev) |
| 17 | { |
| 18 | assert(dev); |
| 19 | |
| 20 | if (dev->path.type != DEVICE_PATH_PCI) |
| 21 | return NULL; |
| 22 | |
| 23 | assert(dev->bus); |
| 24 | if (dev->bus->secondary == 0) |
| 25 | switch (dev->path.pci.devfn) { |
| 26 | case PCI_DEVFN(1, 0): |
| 27 | return "PEGP"; |
| 28 | case PCI_DEVFN(1, 1): |
| 29 | return "PEG1"; |
| 30 | case PCI_DEVFN(1, 2): |
| 31 | return "PEG2"; |
| 32 | }; |
| 33 | |
| 34 | struct device *const port = dev->bus->dev; |
| 35 | assert(port); |
| 36 | assert(port->bus); |
| 37 | |
| 38 | if (dev->path.pci.devfn == PCI_DEVFN(0, 0) && |
| 39 | port->bus->secondary == 0 && |
| 40 | (port->path.pci.devfn == PCI_DEVFN(1, 0) || |
| 41 | port->path.pci.devfn == PCI_DEVFN(1, 1) || |
| 42 | port->path.pci.devfn == PCI_DEVFN(1, 2))) |
| 43 | return "DEV0"; |
| 44 | |
| 45 | return NULL; |
| 46 | } |
| 47 | #endif |
| 48 | |
Angel Pons | 6adbfa7 | 2021-06-20 14:15:44 +0200 | [diff] [blame] | 49 | static const struct peg_config *get_peg_config(struct device *dev, const uint8_t func) |
| 50 | { |
| 51 | static const struct peg_config default_config = { 0 }; |
| 52 | |
| 53 | if (!dev || !dev->chip_info) |
| 54 | return &default_config; |
| 55 | |
| 56 | const struct northbridge_intel_haswell_config *config = dev->chip_info; |
| 57 | |
| 58 | if (func >= ARRAY_SIZE(config->peg_cfg)) { |
| 59 | printk(BIOS_ERR, "%s: Found PEG function %u, which doesn't exist on Haswell\n", |
| 60 | __func__, func); |
| 61 | return &default_config; |
| 62 | } |
| 63 | return &config->peg_cfg[func]; |
| 64 | } |
| 65 | |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 66 | static void peg_enable(struct device *dev) |
| 67 | { |
Angel Pons | 24e14f9 | 2021-06-20 14:03:35 +0200 | [diff] [blame] | 68 | const uint8_t func = PCI_FUNC(dev->path.pci.devfn); |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 69 | |
Angel Pons | 6adbfa7 | 2021-06-20 14:15:44 +0200 | [diff] [blame] | 70 | const struct peg_config *peg_cfg = get_peg_config(dev, func); |
Angel Pons | 6e0dd4e | 2021-06-20 14:07:22 +0200 | [diff] [blame] | 71 | |
| 72 | const bool slot_implemented = !peg_cfg->is_onboard; |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 73 | |
| 74 | if (slot_implemented) { |
| 75 | /* Default is 1, but register is R/WO and needs to be written to once */ |
| 76 | pci_or_config16(dev, PEG_CAP, 1 << 8); |
| 77 | } else { |
| 78 | pci_and_config16(dev, PEG_CAP, ~(1 << 8)); |
| 79 | } |
| 80 | |
| 81 | /* Note: this register is write-once */ |
| 82 | uint32_t slotcap = pci_read_config32(dev, PEG_SLOTCAP); |
| 83 | |
| 84 | /* Physical slot number (zero for ports connected to onboard devices) */ |
Angel Pons | 08391d2 | 2023-02-07 15:03:07 +0100 | [diff] [blame] | 85 | slotcap &= ~(0x1fffU << 19); |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 86 | if (slot_implemented) { |
Angel Pons | 6e0dd4e | 2021-06-20 14:07:22 +0200 | [diff] [blame] | 87 | uint16_t slot_number = peg_cfg->phys_slot_number & 0x1fff; |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 88 | if (slot_number == 0) { |
| 89 | /* Slot number must be non-zero and unique */ |
| 90 | slot_number = func + 1; |
| 91 | } |
| 92 | slotcap |= slot_number << 19; |
| 93 | } |
| 94 | |
| 95 | /* Default to 1.0 watt scale */ |
| 96 | slotcap &= ~(3 << 15); |
Angel Pons | 6e0dd4e | 2021-06-20 14:07:22 +0200 | [diff] [blame] | 97 | slotcap |= (peg_cfg->power_limit_scale & 3) << 15; |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 98 | |
Angel Pons | 6e0dd4e | 2021-06-20 14:07:22 +0200 | [diff] [blame] | 99 | uint8_t power_limit_value = peg_cfg->power_limit_value; |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 100 | if (power_limit_value == 0) { |
| 101 | /* Default to 75 watts */ |
| 102 | power_limit_value = 75; |
| 103 | } |
| 104 | slotcap &= ~(0xff << 7); |
| 105 | slotcap |= power_limit_value << 7; |
| 106 | |
| 107 | pci_write_config32(dev, PEG_SLOTCAP, slotcap); |
| 108 | |
| 109 | /* Clear errors */ |
| 110 | pci_write_config16(dev, PCI_STATUS, 0xffff); |
| 111 | pci_write_config16(dev, PCI_SEC_STATUS, 0xffff); |
| 112 | pci_write_config16(dev, PEG_DSTS, 0xffff); |
| 113 | pci_write_config32(dev, PEG_UESTS, 0xffffffff); |
| 114 | pci_write_config32(dev, PEG_CESTS, 0xffffffff); |
| 115 | pci_write_config32(dev, 0x1f0, 0xffffffff); |
| 116 | |
| 117 | pci_or_config32(dev, PEG_VC0RCTL, 0x7f << 1); |
| 118 | |
| 119 | /* Advertise OBFF support using WAKE# signaling only */ |
| 120 | pci_or_config32(dev, PEG_DCAP2, 1 << 19); |
| 121 | |
| 122 | pci_or_config32(dev, PEG_UESEV, 1 << 14); |
| 123 | |
| 124 | /* Select -3.5 dB de-emphasis */ |
| 125 | pci_or_config32(dev, PEG_LCTL2, 1 << 6); |
| 126 | |
Angel Pons | 08391d2 | 2023-02-07 15:03:07 +0100 | [diff] [blame] | 127 | pci_or_config32(dev, PEG_L0SLAT, 1U << 31); |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 128 | |
| 129 | pci_update_config32(dev, 0x250, ~(7 << 20), 2 << 20); |
| 130 | |
| 131 | pci_or_config32(dev, 0x238, 1 << 29); |
| 132 | |
| 133 | pci_or_config32(dev, 0x1f8, 1 << 16); |
| 134 | |
| 135 | pci_update_config32(dev, PEG_AFE_PM_TMR, ~0x1f, 0x13); |
| 136 | |
| 137 | /* Lock DCAP */ |
| 138 | pci_update_config32(dev, PEG_DCAP, ~0, 0); |
| 139 | |
| 140 | if (func == 0) |
| 141 | pci_or_config32(dev, 0xcd0, 1 << 11); |
| 142 | |
| 143 | /* Enable support for L0s and L1 */ |
| 144 | pci_or_config32(dev, PEG_LCAP, 3 << 10); |
| 145 | |
| 146 | pci_and_config32(dev, 0x200, ~(3 << 26)); |
| 147 | |
| 148 | /* Other fields in this register must not be changed while writing this */ |
| 149 | pci_or_config16(dev, 0x258, 1 << 2); |
| 150 | } |
| 151 | |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 152 | static struct device_operations device_ops = { |
| 153 | .read_resources = pci_bus_read_resources, |
| 154 | .set_resources = pci_dev_set_resources, |
| 155 | .enable_resources = pci_bus_enable_resources, |
| 156 | .scan_bus = pciexp_scan_bridge, |
| 157 | .reset_bus = pci_bus_reset, |
Angel Pons | ae99950 | 2020-11-05 01:58:34 +0100 | [diff] [blame] | 158 | .enable = peg_enable, |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 159 | .init = pci_dev_init, |
Angel Pons | 1fc0edd | 2020-05-31 00:03:28 +0200 | [diff] [blame] | 160 | .ops_pci = &pci_dev_ops_pci, |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 161 | #if CONFIG(HAVE_ACPI_TABLES) |
| 162 | .acpi_name = pcie_acpi_name, |
| 163 | #endif |
| 164 | }; |
| 165 | |
Iru Cai | 12a13e1 | 2020-05-22 22:57:03 +0800 | [diff] [blame] | 166 | static const unsigned short pci_device_ids[] = { |
| 167 | 0x0c01, 0x0c05, 0x0c09, 0x0c0d, |
| 168 | 0x0d01, 0x0d05, 0x0d09, /* Crystal Well */ |
| 169 | 0 }; |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 170 | |
| 171 | static const struct pci_driver pch_pcie __pci_driver = { |
| 172 | .ops = &device_ops, |
Felix Singer | 43b7f41 | 2022-03-07 04:34:52 +0100 | [diff] [blame] | 173 | .vendor = PCI_VID_INTEL, |
Chris Morgan | 5e5e789 | 2020-02-07 09:40:42 -0600 | [diff] [blame] | 174 | .devices = pci_device_ids, |
| 175 | }; |