summaryrefslogtreecommitdiffstats
path: root/drivers/pci/setup-cardbus.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/setup-cardbus.c')
-rw-r--r--drivers/pci/setup-cardbus.c305
1 files changed, 305 insertions, 0 deletions
diff --git a/drivers/pci/setup-cardbus.c b/drivers/pci/setup-cardbus.c
new file mode 100644
index 000000000..dcb8e2b3f
--- /dev/null
+++ b/drivers/pci/setup-cardbus.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cardbus bridge setup routines.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/pci.h>
+#include <linux/sizes.h>
+#include <linux/sprintf.h>
+#include <linux/types.h>
+
+#include "pci.h"
+
+#define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */
+#define CARDBUS_RESERVE_BUSNR 3
+
+#define DEFAULT_CARDBUS_IO_SIZE SZ_256
+#define DEFAULT_CARDBUS_MEM_SIZE SZ_64M
+/* pci=cbmemsize=nnM,cbiosize=nn can override this */
+static unsigned long pci_cardbus_io_size = DEFAULT_CARDBUS_IO_SIZE;
+static unsigned long pci_cardbus_mem_size = DEFAULT_CARDBUS_MEM_SIZE;
+
+unsigned long pci_cardbus_resource_alignment(const struct resource *res)
+{
+ if (res->flags & IORESOURCE_IO)
+ return pci_cardbus_io_size;
+ if (res->flags & IORESOURCE_MEM)
+ return pci_cardbus_mem_size;
+ return 0;
+}
+
+int pci_bus_size_cardbus_bridge(struct pci_bus *bus,
+ struct list_head *realloc_head)
+{
+ struct pci_dev *bridge = bus->self;
+ struct resource *b_res;
+ resource_size_t b_res_3_size = pci_cardbus_mem_size * 2;
+ u16 ctrl;
+
+ b_res = &bridge->resource[PCI_CB_BRIDGE_IO_0_WINDOW];
+ if (resource_assigned(b_res))
+ goto handle_b_res_1;
+ /*
+ * Reserve some resources for CardBus. We reserve a fixed amount
+ * of bus space for CardBus bridges.
+ */
+ resource_set_range(b_res, pci_cardbus_io_size, pci_cardbus_io_size);
+ b_res->flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN;
+ if (realloc_head) {
+ b_res->end -= pci_cardbus_io_size;
+ pci_dev_res_add_to_list(realloc_head, bridge, b_res,
+ pci_cardbus_io_size,
+ pci_cardbus_io_size);
+ }
+
+handle_b_res_1:
+ b_res = &bridge->resource[PCI_CB_BRIDGE_IO_1_WINDOW];
+ if (resource_assigned(b_res))
+ goto handle_b_res_2;
+ resource_set_range(b_res, pci_cardbus_io_size, pci_cardbus_io_size);
+ b_res->flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN;
+ if (realloc_head) {
+ b_res->end -= pci_cardbus_io_size;
+ pci_dev_res_add_to_list(realloc_head, bridge, b_res,
+ pci_cardbus_io_size,
+ pci_cardbus_io_size);
+ }
+
+handle_b_res_2:
+ /* MEM1 must not be pref MMIO */
+ pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl);
+ if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM1) {
+ ctrl &= ~PCI_CB_BRIDGE_CTL_PREFETCH_MEM1;
+ pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl);
+ pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl);
+ }
+
+ /* Check whether prefetchable memory is supported by this bridge. */
+ pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl);
+ if (!(ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0)) {
+ ctrl |= PCI_CB_BRIDGE_CTL_PREFETCH_MEM0;
+ pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl);
+ pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl);
+ }
+
+ b_res = &bridge->resource[PCI_CB_BRIDGE_MEM_0_WINDOW];
+ if (resource_assigned(b_res))
+ goto handle_b_res_3;
+ /*
+ * If we have prefetchable memory support, allocate two regions.
+ * Otherwise, allocate one region of twice the size.
+ */
+ if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0) {
+ resource_set_range(b_res, pci_cardbus_mem_size,
+ pci_cardbus_mem_size);
+ b_res->flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH |
+ IORESOURCE_STARTALIGN;
+ if (realloc_head) {
+ b_res->end -= pci_cardbus_mem_size;
+ pci_dev_res_add_to_list(realloc_head, bridge, b_res,
+ pci_cardbus_mem_size,
+ pci_cardbus_mem_size);
+ }
+
+ /* Reduce that to half */
+ b_res_3_size = pci_cardbus_mem_size;
+ }
+
+handle_b_res_3:
+ b_res = &bridge->resource[PCI_CB_BRIDGE_MEM_1_WINDOW];
+ if (resource_assigned(b_res))
+ goto handle_done;
+ resource_set_range(b_res, pci_cardbus_mem_size, b_res_3_size);
+ b_res->flags |= IORESOURCE_MEM | IORESOURCE_STARTALIGN;
+ if (realloc_head) {
+ b_res->end -= b_res_3_size;
+ pci_dev_res_add_to_list(realloc_head, bridge, b_res,
+ b_res_3_size, pci_cardbus_mem_size);
+ }
+
+handle_done:
+ return 0;
+}
+
+void pci_setup_cardbus_bridge(struct pci_bus *bus)
+{
+ struct pci_dev *bridge = bus->self;
+ struct resource *res;
+ struct pci_bus_region region;
+
+ pci_info(bridge, "CardBus bridge to %pR\n",
+ &bus->busn_res);
+
+ res = bus->resource[0];
+ pcibios_resource_to_bus(bridge->bus, &region, res);
+ if (resource_assigned(res) && res->flags & IORESOURCE_IO) {
+ /*
+ * The IO resource is allocated a range twice as large as it
+ * would normally need. This allows us to set both IO regs.
+ */
+ pci_info(bridge, " bridge window %pR\n", res);
+ pci_write_config_dword(bridge, PCI_CB_IO_BASE_0,
+ region.start);
+ pci_write_config_dword(bridge, PCI_CB_IO_LIMIT_0,
+ region.end);
+ }
+
+ res = bus->resource[1];
+ pcibios_resource_to_bus(bridge->bus, &region, res);
+ if (resource_assigned(res) && res->flags & IORESOURCE_IO) {
+ pci_info(bridge, " bridge window %pR\n", res);
+ pci_write_config_dword(bridge, PCI_CB_IO_BASE_1,
+ region.start);
+ pci_write_config_dword(bridge, PCI_CB_IO_LIMIT_1,
+ region.end);
+ }
+
+ res = bus->resource[2];
+ pcibios_resource_to_bus(bridge->bus, &region, res);
+ if (resource_assigned(res) && res->flags & IORESOURCE_MEM) {
+ pci_info(bridge, " bridge window %pR\n", res);
+ pci_write_config_dword(bridge, PCI_CB_MEMORY_BASE_0,
+ region.start);
+ pci_write_config_dword(bridge, PCI_CB_MEMORY_LIMIT_0,
+ region.end);
+ }
+
+ res = bus->resource[3];
+ pcibios_resource_to_bus(bridge->bus, &region, res);
+ if (resource_assigned(res) && res->flags & IORESOURCE_MEM) {
+ pci_info(bridge, " bridge window %pR\n", res);
+ pci_write_config_dword(bridge, PCI_CB_MEMORY_BASE_1,
+ region.start);
+ pci_write_config_dword(bridge, PCI_CB_MEMORY_LIMIT_1,
+ region.end);
+ }
+}
+EXPORT_SYMBOL(pci_setup_cardbus_bridge);
+
+int pci_setup_cardbus(char *str)
+{
+ if (!strncmp(str, "cbiosize=", 9)) {
+ pci_cardbus_io_size = memparse(str + 9, &str);
+ return 0;
+ } else if (!strncmp(str, "cbmemsize=", 10)) {
+ pci_cardbus_mem_size = memparse(str + 10, &str);
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+int pci_cardbus_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev,
+ u32 buses, int max,
+ unsigned int available_buses, int pass)
+{
+ struct pci_bus *child;
+ bool fixed_buses;
+ u8 fixed_sec, fixed_sub;
+ int next_busnr;
+ u32 i, j = 0;
+
+ /*
+ * We need to assign a number to this bus which we always do in the
+ * second pass.
+ */
+ if (!pass) {
+ /*
+ * Temporarily disable forwarding of the configuration
+ * cycles on all bridges in this bus segment to avoid
+ * possible conflicts in the second pass between two bridges
+ * programmed with overlapping bus ranges.
+ */
+ pci_write_config_dword(dev, PCI_PRIMARY_BUS,
+ buses & PCI_SEC_LATENCY_TIMER_MASK);
+ return max;
+ }
+
+ /* Clear errors */
+ pci_write_config_word(dev, PCI_STATUS, 0xffff);
+
+ /* Read bus numbers from EA Capability (if present) */
+ fixed_buses = pci_ea_fixed_busnrs(dev, &fixed_sec, &fixed_sub);
+ if (fixed_buses)
+ next_busnr = fixed_sec;
+ else
+ next_busnr = max + 1;
+
+ /*
+ * Prevent assigning a bus number that already exists. This can
+ * happen when a bridge is hot-plugged, so in this case we only
+ * re-scan this bus.
+ */
+ child = pci_find_bus(pci_domain_nr(bus), next_busnr);
+ if (!child) {
+ child = pci_add_new_bus(bus, dev, next_busnr);
+ if (!child)
+ return max;
+ pci_bus_insert_busn_res(child, next_busnr, bus->busn_res.end);
+ }
+ max++;
+ if (available_buses)
+ available_buses--;
+
+ buses = (buses & PCI_SEC_LATENCY_TIMER_MASK) |
+ FIELD_PREP(PCI_PRIMARY_BUS_MASK, child->primary) |
+ FIELD_PREP(PCI_SECONDARY_BUS_MASK, child->busn_res.start) |
+ FIELD_PREP(PCI_SUBORDINATE_BUS_MASK, child->busn_res.end);
+
+ /*
+ * yenta.c forces a secondary latency timer of 176.
+ * Copy that behaviour here.
+ */
+ FIELD_MODIFY(PCI_SEC_LATENCY_TIMER_MASK, &buses, CARDBUS_LATENCY_TIMER);
+
+ /* We need to blast all three values with a single write */
+ pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses);
+
+ /*
+ * For CardBus bridges, we leave 4 bus numbers as cards with a
+ * PCI-to-PCI bridge can be inserted later.
+ */
+ for (i = 0; i < CARDBUS_RESERVE_BUSNR; i++) {
+ struct pci_bus *parent = bus;
+
+ if (pci_find_bus(pci_domain_nr(bus), max + i + 1))
+ break;
+
+ while (parent->parent) {
+ if (!pcibios_assign_all_busses() &&
+ (parent->busn_res.end > max) &&
+ (parent->busn_res.end <= max + i)) {
+ j = 1;
+ }
+ parent = parent->parent;
+ }
+ if (j) {
+ /*
+ * Often, there are two CardBus bridges -- try to
+ * leave one valid bus number for each one.
+ */
+ i /= 2;
+ break;
+ }
+ }
+ max += i;
+
+ /*
+ * Set subordinate bus number to its real value. If fixed
+ * subordinate bus number exists from EA capability then use it.
+ */
+ if (fixed_buses)
+ max = fixed_sub;
+ pci_bus_update_busn_res_end(child, max);
+ pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, max);
+
+ scnprintf(child->name, sizeof(child->name), "PCI CardBus %04x:%02x",
+ pci_domain_nr(bus), child->number);
+
+ pbus_validate_busn(child);
+
+ return max;
+}