diff options
Diffstat (limited to 'src/soc')
-rw-r--r-- | src/soc/intel/apollolake/Makefile.inc | 3 | ||||
-rw-r--r-- | src/soc/intel/apollolake/include/soc/lpc.h | 67 | ||||
-rw-r--r-- | src/soc/intel/apollolake/include/soc/pci_devs.h | 1 | ||||
-rw-r--r-- | src/soc/intel/apollolake/lpc.c | 72 | ||||
-rw-r--r-- | src/soc/intel/apollolake/lpc_lib.c | 171 |
5 files changed, 313 insertions, 1 deletions
diff --git a/src/soc/intel/apollolake/Makefile.inc b/src/soc/intel/apollolake/Makefile.inc index e162a01a13..6b934a891a 100644 --- a/src/soc/intel/apollolake/Makefile.inc +++ b/src/soc/intel/apollolake/Makefile.inc @@ -13,6 +13,7 @@ bootblock-y += bootblock/cache_as_ram.S bootblock-y += bootblock/bootblock.c bootblock-y += car.c bootblock-y += gpio.c +bootblock-y += lpc_lib.c bootblock-y += mmap_boot.c bootblock-y += placeholders.c bootblock-y += tsc_freq.c @@ -23,6 +24,7 @@ romstage-y += car.c romstage-$(CONFIG_PLATFORM_USES_FSP2_0) += romstage.c romstage-y += gpio.c romstage-$(CONFIG_SOC_UART_DEBUG) += uart_early.c +romstage-y += lpc_lib.c romstage-y += memmap.c romstage-y += mmap_boot.c romstage-y += tsc_freq.c @@ -38,6 +40,7 @@ ramstage-y += placeholders.c ramstage-y += gpio.c ramstage-$(CONFIG_SOC_UART_DEBUG) += uart_early.c ramstage-y += lpc.c +ramstage-y += lpc_lib.c ramstage-y += memmap.c ramstage-y += mmap_boot.c ramstage-y += uart.c diff --git a/src/soc/intel/apollolake/include/soc/lpc.h b/src/soc/intel/apollolake/include/soc/lpc.h new file mode 100644 index 0000000000..9a1c397d8e --- /dev/null +++ b/src/soc/intel/apollolake/include/soc/lpc.h @@ -0,0 +1,67 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2016 Intel Corp. + * (Written by Alexandru Gagniuc <alexandrux.gagniuc@intel.com> for Intel Corp.) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _SOC_APOLLOLAKE_LPC_H_ +#define _SOC_APOLLOLAKE_LPC_H_ + +#include <stddef.h> +#include <stdint.h> + +#define REG_SERIRQ_CTL 0x64 +#define SCNT_EN (1 << 7) +#define SCNT_MODE (1 << 6) + +/* + * IO decode enable macros are in the format IO_<peripheral>_<IO port>. + * For example, to open ports 0x60, 0x64 for the keyboard controller, + * use IOE_KBC_60_64 macro. For IOE_ macros that do not specify a port range, + * the port range is selectable via the IO decodes register (not referenced). + */ +#define REG_IO_ENABLES 0x82 +#define IOE_EC_4E_4F (1 << 13) +#define IOE_SUPERIO_2E_2F (1 << 12) +#define IOE_EC_62_66 (1 << 11) +#define IOE_KBC_60_64 (1 << 10) +#define IOE_HGE_208 (1 << 9) +#define IOE_LGE_200 (1 << 8) +#define IOE_FDD_EN (1 << 3) +#define IOE_LPT_EN (1 << 2) +#define IOE_COMB_EN (1 << 1) +#define IOE_COMA_EN (1 << 0) +#define REG_GENERIC_IO_RANGE(n) ((((n) & 0x3) * 4) + 0x84) +#define LGIR_AMASK_MASK (0xfc << 16) +#define LGIR_ADDR_MASK 0xfffc +#define LGIR_EN (1 << 0) +#define LGIR_MAX_WINDOW_SIZE 256 +#define NUM_GENERIC_IO_RANGES 4 +#define REG_GENERIC_MEM_RANGE 0x98 +#define LGMR_ADDR_MASK 0xffff0000 +#define LGMR_EN (1 << 0) +#define LGMR_WINDOW_SIZE (64 * KiB) + +/* Enable fixed IO ranges to LPC. IOE_* macros can be OR'ed together. */ +void lpc_enable_fixed_io_ranges(uint16_t io_enables); +/* Open a generic IO window to the LPC bus. Four windows are available. */ +void lpc_open_pmio_window(uint16_t base, uint16_t size); +/* Close all generic IO windows to the LPC bus. */ +void lpc_close_pmio_windows(void); +/* Open a generic MMIO window to the LPC bus. One window is available. */ +void lpc_open_mmio_window(uintptr_t base, size_t size); +/* Returns true if given window is decoded to LPC via a fixed range. */ +bool lpc_fits_fixed_mmio_window(uintptr_t base, size_t size); + +#endif /* _SOC_APOLLOLAKE_LPC_H_ */ diff --git a/src/soc/intel/apollolake/include/soc/pci_devs.h b/src/soc/intel/apollolake/include/soc/pci_devs.h index 7b8caafd69..ec550e8810 100644 --- a/src/soc/intel/apollolake/include/soc/pci_devs.h +++ b/src/soc/intel/apollolake/include/soc/pci_devs.h @@ -51,5 +51,6 @@ #define P2SB_DEV PCI_DEV(0, 0xd, 0) #define PMC_DEV PCI_DEV(0, 0xd, 1) #define SPI_DEV PCI_DEV(0, 0xd, 2) +#define LPC_DEV PCI_DEV(0, 0x1f, 0) #endif diff --git a/src/soc/intel/apollolake/lpc.c b/src/soc/intel/apollolake/lpc.c index 10677df503..5b552d8491 100644 --- a/src/soc/intel/apollolake/lpc.c +++ b/src/soc/intel/apollolake/lpc.c @@ -19,8 +19,27 @@ #include <device/pci.h> #include <device/pci_ids.h> #include <soc/acpi.h> +#include <soc/lpc.h> #include <soc/pci_ids.h> +/* + * SCOPE: + * The purpose of this driver is to eliminate manual resource allocation for + * devices under the LPC bridge. + * + * BACKGROUND: + * The resource allocator reserves IO and memory resources to devices on the + * LPC bus, but it is up to the hardware driver to make sure that those + * resources are decoded to the LPC bus. This is what this driver does. + * + * THEORY OF OPERATION: + * The .scan_bus member of the driver's ops will scan the static device tree + * (devicetree.cb) and invoke drivers of devices on the LPC bus. This creates + * a list of child devices, along with their resources. set_child_resources() + * parses that list and looks for resources needed by the child devices. It + * opens up IO and memory windows as needed. + */ + static void soc_lpc_add_io_resources(device_t dev) { struct resource *res; @@ -41,12 +60,63 @@ static void soc_lpc_read_resources(device_t dev) soc_lpc_add_io_resources(dev); } +static void set_child_resources(struct device *dev); + +static void loop_resources(struct device *dev) +{ + struct resource *res; + + for (res = dev->resource_list; res; res = res->next) { + + if (res->flags & IORESOURCE_IO) { + lpc_open_pmio_window(res->base, res->size); + } + + if (res->flags & IORESOURCE_MEM) { + /* Check if this is already decoded. */ + if (lpc_fits_fixed_mmio_window(res->base, res->size)) + continue; + + lpc_open_mmio_window(res->base, res->size); + } + + } + set_child_resources(dev); +} + +/* + * Loop through all the child devices' resources, and open up windows to the + * LPC bus, as appropriate. + */ +static void set_child_resources(struct device *dev) +{ + struct bus *link; + struct device *child; + + for (link = dev->link_list; link; link = link->next) { + for (child = link->children; child; child = child->sibling) { + loop_resources(child); + } + } +} + +static void set_resources(device_t dev) +{ + pci_dev_set_resources(dev); + + /* Close all previously opened windows and allocate from scratch. */ + lpc_close_pmio_windows(); + /* Now open up windows to devices which have declared resources. */ + set_child_resources(dev); +} + static struct device_operations device_ops = { .read_resources = &soc_lpc_read_resources, - .set_resources = &pci_dev_set_resources, + .set_resources = set_resources, .enable_resources = &pci_dev_enable_resources, .write_acpi_tables = southbridge_write_acpi_tables, .acpi_inject_dsdt_generator = southbridge_inject_dsdt, + .scan_bus = scan_lpc_bus, }; static const struct pci_driver soc_lpc __pci_driver = { diff --git a/src/soc/intel/apollolake/lpc_lib.c b/src/soc/intel/apollolake/lpc_lib.c new file mode 100644 index 0000000000..14e052eace --- /dev/null +++ b/src/soc/intel/apollolake/lpc_lib.c @@ -0,0 +1,171 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2016 Intel Corp. + * (Written by Alexandru Gagniuc <alexandrux.gagniuc@intel.com> for Intel Corp.) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define __SIMPLE_DEVICE__ + +#include <console/console.h> +#include <device/pci.h> +#include <lib.h> +#include <soc/lpc.h> +#include <soc/pci_devs.h> + +/* + * These are MMIO ranges that the silicon designers decided are always going to + * be decoded to LPC. + */ +static const struct lpc_mmio_range { + uintptr_t base; + size_t size; +} lpc_fixed_mmio_ranges[] = { + { 0xfed40000, 0x8000 }, + { 0xfedc0000, 0x4000 }, + { 0xfed20800, 16 }, + { 0xfed20880, 8 }, + { 0xfed208e0, 16 }, + { 0xfed208f0, 8 }, + { 0xfed30800, 16 }, + { 0xfed30880, 8 }, + { 0xfed308e0, 16 }, + { 0xfed308f0, 8 }, + { 0, 0 } +}; + +void lpc_enable_fixed_io_ranges(uint16_t io_enables) +{ + uint16_t reg_io_enables; + + reg_io_enables = pci_read_config16(LPC_DEV, REG_IO_ENABLES); + io_enables |= reg_io_enables; + pci_write_config16(LPC_DEV, REG_IO_ENABLES, io_enables); +} + +/* + * Find the first unused IO window. + * Returns -1 if not found, 0 for reg 0x84, 1 for reg 0x88 ... + */ +static int find_unused_pmio_window(void) +{ + int i; + uint32_t lgir; + + for (i = 0; i < NUM_GENERIC_IO_RANGES; i++) { + lgir = pci_read_config32(LPC_DEV, REG_GENERIC_IO_RANGE(i)); + + if (!(lgir & LGIR_EN)) + return i; + } + + return -1; +} + +void lpc_close_pmio_windows(void) +{ + size_t i; + + for (i = 0; i < NUM_GENERIC_IO_RANGES; i++) + pci_write_config32(LPC_DEV, REG_GENERIC_IO_RANGE(i), 0); +} + +void lpc_open_pmio_window(uint16_t base, uint16_t size) +{ + int lgir_reg_num; + uint32_t lgir_reg_offset, lgir, window_size, alignment; + resource_t bridged_size, bridge_base; + + printk(BIOS_SPEW, "LPC: Trying to open IO window from %x size %x\n", + base, size); + + bridged_size = 0; + bridge_base = base; + + while (bridged_size < size) { + lgir_reg_num = find_unused_pmio_window(); + if (lgir_reg_num < 0) { + printk(BIOS_ERR, + "LPC: Cannot open IO window: %llx size %llx\n", + bridge_base, size - bridged_size); + printk(BIOS_ERR, "No more IO windows\n"); + return; + } + lgir_reg_offset = REG_GENERIC_IO_RANGE(lgir_reg_num); + + /* Each IO range register can only open a 256-byte window. */ + window_size = MIN(size, LGIR_MAX_WINDOW_SIZE); + + /* Window size must be a power of two for the AMASK to work. */ + alignment = 1 << (log2_ceil(window_size)); + window_size = ALIGN_UP(window_size, alignment); + + /* Address[15:2] in LGIR[15:12] and Mask[7:2] in LGIR[23:18]. */ + lgir = (bridge_base & LGIR_ADDR_MASK) | LGIR_EN; + lgir |= ((window_size - 1) << 16) & LGIR_AMASK_MASK; + + pci_write_config32(LPC_DEV, lgir_reg_offset, lgir); + + printk(BIOS_DEBUG, + "LPC: Opened IO window LGIR%d: base %llx size %x\n", + lgir_reg_num, bridge_base, window_size); + + bridged_size += window_size; + bridge_base += window_size; + } +} + +void lpc_open_mmio_window(uintptr_t base, size_t size) +{ + uint32_t lgmr; + + lgmr = pci_read_config32(LPC_DEV, REG_GENERIC_MEM_RANGE); + + if (lgmr & LGMR_EN) { + printk(BIOS_ERR, + "LPC: Cannot open window to resource %lx size %lx\n", + base, size); + printk(BIOS_ERR, "LPC: MMIO window already in use\n"); + return; + } + + if (size > LGMR_WINDOW_SIZE) { + printk(BIOS_WARNING, + "LPC: Resource %lx size %lx larger than window(%x)\n", + base, size, LGMR_WINDOW_SIZE); + } + + lgmr = (base & LGMR_ADDR_MASK) | LGMR_EN; + + pci_write_config32(LPC_DEV, REG_GENERIC_MEM_RANGE, lgmr); +} + +bool lpc_fits_fixed_mmio_window(uintptr_t base, size_t size) +{ + resource_t res_end, range_end; + const struct lpc_mmio_range *range; + + for (range = lpc_fixed_mmio_ranges; range->size; range++) { + range_end = range->base + range->size; + res_end = base + size; + + if ((base >= range->base) && (res_end <= range_end)) { + printk(BIOS_DEBUG, + "Resource %lx size %lx fits in fixed window" + " %lx size %lx\n", + base, size, range->base, range->size); + return true; + } + } + return false; +} |