From 370d48b29a69a2e38d3092058413b22f040203de Mon Sep 17 00:00:00 2001 From: Arindam Nath Date: Mon, 4 Aug 2014 19:16:53 +0530 Subject: [PATCH 1/2] yocto: amd: staging: add support to enable and disable IMC to fetch BIOS code The patch essentially adds support for two functions: amd_imc_enter_scratch_ram() and amd_imc_exit_scratch_ram(). These functions instruct IMC to stop and start fetching code from BIOS ROM respectively. These functions are needed where IMC is trying to fetch code on a shared bus when some other transaction is already occuring. To prevent IMC to fetch incorrect data from ROM while it is still being updated, we instruct IMC to temporarily stop fetching code from BIOS, and then start fetching again when it is safe to do so. Upstream Status: None Signed-off-by: Arindam Nath --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 1 + drivers/staging/amd_imc/Kconfig | 9 ++ drivers/staging/amd_imc/Makefile | 1 + drivers/staging/amd_imc/amd_imc.c | 298 ++++++++++++++++++++++++++++++++++++++ include/linux/amd_imc.h | 68 +++++++++ 6 files changed, 379 insertions(+) create mode 100644 drivers/staging/amd_imc/Kconfig create mode 100644 drivers/staging/amd_imc/Makefile create mode 100644 drivers/staging/amd_imc/amd_imc.c create mode 100644 include/linux/amd_imc.h diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 3626dbc8..0a95d6d 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -148,4 +148,6 @@ source "drivers/staging/dgnc/Kconfig" source "drivers/staging/dgap/Kconfig" +source "drivers/staging/amd_imc/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index d1b4b80..2be3a91 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -66,3 +66,4 @@ obj-$(CONFIG_USB_BTMTK) += btmtk_usb/ obj-$(CONFIG_XILLYBUS) += xillybus/ obj-$(CONFIG_DGNC) += dgnc/ obj-$(CONFIG_DGAP) += dgap/ +obj-$(CONFIG_AMD_IMC) += amd_imc/ diff --git a/drivers/staging/amd_imc/Kconfig b/drivers/staging/amd_imc/Kconfig new file mode 100644 index 0000000..abfb724 --- /dev/null +++ b/drivers/staging/amd_imc/Kconfig @@ -0,0 +1,9 @@ +config AMD_IMC + bool "AMD Integrated Micro Controller support" + depends on PCI && X86_64 + default y + ---help--- + This driver supports AMD Integrated Micro Controller. + + To compile this driver as a module, choose M here. The module + will be called amd_imc. diff --git a/drivers/staging/amd_imc/Makefile b/drivers/staging/amd_imc/Makefile new file mode 100644 index 0000000..c4837f8 --- /dev/null +++ b/drivers/staging/amd_imc/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_AMD_IMC) += amd_imc.o diff --git a/drivers/staging/amd_imc/amd_imc.c b/drivers/staging/amd_imc/amd_imc.c new file mode 100644 index 0000000..c6c6074 --- /dev/null +++ b/drivers/staging/amd_imc/amd_imc.c @@ -0,0 +1,298 @@ +/***************************************************************************** +* +* Copyright (c) 2014, Advanced Micro Devices, Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of Advanced Micro Devices, Inc. nor the names of +* its contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL ADVANCED MICRO DEVICES, INC. BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* +***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int imc_enabled; +static u16 imc_port_addr; +static u8 msg_reg_base_hi; +static u8 msg_reg_base_lo; +static u16 msg_reg_base; + +static struct pci_dev *amd_imc_pci; +static struct platform_device *amd_imc_platform_device; + +static DEFINE_PCI_DEVICE_TABLE(amd_lpc_pci_tbl) = { + {PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_LPC_BRIDGE, PCI_ANY_ID, + PCI_ANY_ID,}, + {} +}; + +void amd_imc_enter_scratch_ram(void) +{ + u8 byte; + + if (!imc_enabled) + return; + + /* Instruct IMC to enter scratch RAM */ + outb(AMD_MSG_REG0, msg_reg_base + AMD_MSG_INDEX_REG_OFFSET); + outb(0, msg_reg_base + AMD_MSG_DATA_REG_OFFSET); + + outb(AMD_MSG_REG1, msg_reg_base + AMD_MSG_INDEX_REG_OFFSET); + outb(AMD_IMC_ENTER_SCRATCH_RAM, msg_reg_base + AMD_MSG_DATA_REG_OFFSET); + + outb(AMD_MSG_SYS_TO_IMC, msg_reg_base + AMD_MSG_INDEX_REG_OFFSET); + outb(AMD_IMC_ROM_OWNERSHIP_SEM, msg_reg_base + + AMD_MSG_DATA_REG_OFFSET); + + /* As per the spec, the firmware may take up to 50ms */ + msleep(50); + + /* read message registger 0 to confirm function completion */ + outb(AMD_MSG_REG0, msg_reg_base + AMD_MSG_INDEX_REG_OFFSET); + byte = inb(msg_reg_base + AMD_MSG_DATA_REG_OFFSET); + + if (byte == AMD_IMC_FUNC_NOT_SUPP) + pr_info("amd_imc: %s not supported\n", __func__); + else if (byte == AMD_IMC_FUNC_COMPLETED) + pr_info("amd_imc: %s completed\n", __func__); +} +EXPORT_SYMBOL_GPL(amd_imc_enter_scratch_ram); + +void amd_imc_exit_scratch_ram(void) +{ + u8 byte; + + if (!imc_enabled) + return; + + /* Instruct IMC to exit scratch RAM */ + outb(AMD_MSG_REG0, msg_reg_base + AMD_MSG_INDEX_REG_OFFSET); + outb(0, msg_reg_base + AMD_MSG_DATA_REG_OFFSET); + + outb(AMD_MSG_REG1, msg_reg_base + AMD_MSG_INDEX_REG_OFFSET); + outb(AMD_IMC_ENTER_SCRATCH_RAM, msg_reg_base + AMD_MSG_DATA_REG_OFFSET); + + outb(AMD_MSG_SYS_TO_IMC, msg_reg_base + AMD_MSG_INDEX_REG_OFFSET); + outb(AMD_IMC_ROM_OWNERSHIP_SEM, msg_reg_base + + AMD_MSG_DATA_REG_OFFSET); + + /* As per the spec, the firmware may take up to 50ms */ + msleep(50); + + /* read message registger 0 to confirm function completion */ + outb(AMD_MSG_REG0, msg_reg_base + AMD_MSG_INDEX_REG_OFFSET); + byte = inb(msg_reg_base + AMD_MSG_DATA_REG_OFFSET); + + if (byte == AMD_IMC_FUNC_NOT_SUPP) + pr_info("amd_imc: %s not supported\n", __func__); + else if (byte == AMD_IMC_FUNC_COMPLETED) + pr_info("amd_imc: %s completed\n", __func__); +} +EXPORT_SYMBOL_GPL(amd_imc_exit_scratch_ram); + +/* +* The PCI Device ID table below is used to identify the platform +* the driver is supposed to work for. Since this is a platform +* driver, we need a way for us to be able to find the correct +* platform when the driver gets loaded, otherwise we should +* bail out. +*/ +static DEFINE_PCI_DEVICE_TABLE(amd_imc_pci_tbl) = { + { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID, + PCI_ANY_ID, }, + { 0, }, +}; + +static int amd_imc_init(struct platform_device *pdev) +{ + struct pci_dev *dev = NULL; + static u32 imc_strap_status_phys; + void __iomem *imcstrapstatus; + u32 val; + u8 *byte; + + /* Match the PCI device */ + for_each_pci_dev(dev) { + if (pci_match_id(amd_imc_pci_tbl, dev) != NULL) { + amd_imc_pci = dev; + break; + } + } + + if (!amd_imc_pci) + return -ENODEV; + + /* Locate ACPI MMIO Base Address. */ + byte = (u8 *)&val; + + outb(AMD_PM_ACPI_MMIO_BASE0, AMD_IO_PM_INDEX_REG); + byte[0] = inb(AMD_IO_PM_DATA_REG); + outb(AMD_PM_ACPI_MMIO_BASE1, AMD_IO_PM_INDEX_REG); + byte[1] = inb(AMD_IO_PM_DATA_REG); + outb(AMD_PM_ACPI_MMIO_BASE2, AMD_IO_PM_INDEX_REG); + byte[2] = inb(AMD_IO_PM_DATA_REG); + outb(AMD_PM_ACPI_MMIO_BASE3, AMD_IO_PM_INDEX_REG); + byte[3] = inb(AMD_IO_PM_DATA_REG); + + /* Bits 31:13 is the actual ACPI MMIO Base Address */ + val &= AMD_ACPI_MMIO_ADDR_MASK; + + /* IMCStrapStatus is located at ACPI MMIO Base Address + 0xE80 */ + if (!request_mem_region_exclusive(val + AMD_IMC_STRAP_STATUS_OFFSET, + AMD_IMC_STRAP_STATUS_SIZE, "IMC Strap Status")) { + pr_err("amd_imc: MMIO address 0x%04x already in use\n", + val + AMD_IMC_STRAP_STATUS_OFFSET); + goto exit; + } + + imc_strap_status_phys = val + AMD_IMC_STRAP_STATUS_OFFSET; + + imcstrapstatus = ioremap(imc_strap_status_phys, + AMD_IMC_STRAP_STATUS_SIZE); + if (!imcstrapstatus) { + pr_err("amd_imc: failed to get IMC Strap Status address\n"); + goto unreg_imc_region; + } + + /* Check if IMC is enabled */ + val = ioread32(imcstrapstatus); + if ((val & AMD_IMC_ENABLED) == AMD_IMC_ENABLED) { + struct pci_dev *pdev = NULL; + + pr_info("amd_imc: IMC is enabled\n"); + imc_enabled = 1; + + /* + * In case IMC is enabled, we need to find the IMC port address + * which will be used to send messages to the IMC. The IMC port + * address is stored in bits 1:15 of PCI device 20, function 3, + * offset 0xA4. PCI device 20, function 3 is actually the LPC + * ISA bridge. + */ + for_each_pci_dev(pdev) { + if (pci_match_id(amd_lpc_pci_tbl, pdev) != NULL) + break; + } + + /* Match found. Get the IMC port address */ + if (pdev) { + pci_read_config_word(pdev, AMD_PCI_IMC_PORT_ADDR_REG, + &imc_port_addr); + + /* The actual IMC port address has bit 0 masked out */ + imc_port_addr &= ~AMD_IMC_PORT_ACTIVE; + } + + /* Put device into configuration state */ + outb(AMD_DEVICE_ENTER_CONFIG_STATE, imc_port_addr + + AMD_IMC_INDEX_REG_OFFSET); + + /* Select logical device number 9 */ + outb(AMD_SET_LOGICAL_DEVICE, imc_port_addr + + AMD_IMC_INDEX_REG_OFFSET); + outb(AMD_SET_DEVICE_9, imc_port_addr + + AMD_IMC_DATA_REG_OFFSET); + + /* read high byte of message register base address */ + outb(AMD_MSG_REG_HIGH, imc_port_addr + + AMD_IMC_INDEX_REG_OFFSET); + msg_reg_base_hi = inb(imc_port_addr + AMD_IMC_DATA_REG_OFFSET); + + /* read low byte of message register base address */ + outb(AMD_MSG_REG_LOW, imc_port_addr + + AMD_IMC_INDEX_REG_OFFSET); + msg_reg_base_lo = inb(imc_port_addr + AMD_IMC_DATA_REG_OFFSET); + + msg_reg_base = msg_reg_base_lo | (msg_reg_base_hi << 8); + + /* Get device out of configuration state */ + outb(AMD_DEVICE_EXIT_CONFIG_STATE, imc_port_addr + + AMD_IMC_INDEX_REG_OFFSET); + } else { + pr_info("amd_imc: IMC is disabled\n"); + imc_enabled = 0; + } + + /* Release the region occupied by IMC Strap Status register */ + iounmap(imcstrapstatus); + release_mem_region(imc_strap_status_phys, AMD_IMC_STRAP_STATUS_SIZE); + + return 0; + +unreg_imc_region: + release_mem_region(imc_strap_status_phys, AMD_IMC_STRAP_STATUS_SIZE); +exit: + return -ENODEV; +} + +static struct platform_driver amd_imc_driver = { + .probe = amd_imc_init, + .driver = { + .owner = THIS_MODULE, + .name = IMC_MODULE_NAME, + }, +}; + +static int __init amd_imc_init_module(void) +{ + int err; + + pr_info("AMD IMC Driver v%s\n", IMC_VERSION); + + err = platform_driver_register(&amd_imc_driver); + if (err) + return err; + + amd_imc_platform_device = platform_device_register_simple( + IMC_MODULE_NAME, -1, NULL, 0); + if (IS_ERR(amd_imc_platform_device)) { + err = PTR_ERR(amd_imc_platform_device); + goto unreg_platform_driver; + } + + return 0; + +unreg_platform_driver: + platform_driver_unregister(&amd_imc_driver); + return err; +} + +static void __exit amd_imc_cleanup_module(void) +{ + platform_device_unregister(amd_imc_platform_device); + platform_driver_unregister(&amd_imc_driver); + pr_info("AMD IMC Module Unloaded\n"); +} + +module_init(amd_imc_init_module); +module_exit(amd_imc_cleanup_module); + +MODULE_AUTHOR("Arindam Nath "); +MODULE_DESCRIPTION("AMD IMC driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/include/linux/amd_imc.h b/include/linux/amd_imc.h new file mode 100644 index 0000000..4b4b7b8 --- /dev/null +++ b/include/linux/amd_imc.h @@ -0,0 +1,68 @@ +#ifndef _AMD_IMC_H_ +#define _AMD_IMC_H_ + +/* Module and version information */ +#define IMC_VERSION "0.1" +#define IMC_MODULE_NAME "AMD IMC" +#define IMC_DRIVER_NAME IMC_MODULE_NAME ", v" IMC_VERSION + +#define DRV_NAME "amd_imc" + +/* IO port address for indirect access using the ACPI PM registers */ +#define AMD_IO_PM_INDEX_REG 0xCD6 +#define AMD_IO_PM_DATA_REG 0xCD7 + +#define AMD_PM_ACPI_MMIO_BASE0 0x24 +#define AMD_PM_ACPI_MMIO_BASE1 0x25 +#define AMD_PM_ACPI_MMIO_BASE2 0x26 +#define AMD_PM_ACPI_MMIO_BASE3 0x27 + +#define AMD_ACPI_MMIO_ADDR_MASK ~0x1FFF + +/* Offset of IMC Strap Status register in the ACPI MMIO region */ +#define AMD_IMC_STRAP_STATUS_OFFSET 0xE80 + #define AMD_IMC_ENABLED 0x4 +#define AMD_IMC_STRAP_STATUS_SIZE 4 + +#define PCI_DEVICE_ID_AMD_LPC_BRIDGE 0x780E + #define AMD_PCI_IMC_PORT_ADDR_REG 0xA4 + #define AMD_IMC_PORT_ACTIVE 0x0001 + +/* Device configuration state fields */ +#define AMD_DEVICE_ENTER_CONFIG_STATE 0x5A +#define AMD_DEVICE_EXIT_CONFIG_STATE 0xA5 + +/* Global configuration registers */ +#define AMD_SET_LOGICAL_DEVICE 0x07 + #define AMD_SET_DEVICE_9 0x09 +#define AMD_MSG_REG_HIGH 0x60 +#define AMD_MSG_REG_LOW 0x61 + +/* IMC index and data port offsets for indirect access */ +#define AMD_IMC_INDEX_REG_OFFSET 0x00 +#define AMD_IMC_DATA_REG_OFFSET 0x01 + +/* Message register index and data port offsets for indirect access */ +#define AMD_MSG_INDEX_REG_OFFSET 0x00 +#define AMD_MSG_DATA_REG_OFFSET 0x01 + +/* IMC message registers */ +#define AMD_MSG_SYS_TO_IMC 0x80 + #define AMD_IMC_ROM_OWNERSHIP_SEM 0x96 +#define AMD_MSG_REG0 0x82 + #define AMD_IMC_FUNC_NOT_SUPP 0x00 + #define AMD_IMC_FUNC_COMPLETED 0xFA +#define AMD_MSG_REG1 0x83 + #define AMD_IMC_ENTER_SCRATCH_RAM 0xB4 + #define AMD_IMC_EXIT_SCRATCH_RAM 0xB5 + +/* Extern functions */ +#ifdef CONFIG_AMD_IMC +extern void amd_imc_enter_scratch_ram(void); +extern void amd_imc_exit_scratch_ram(void); +#else +void amd_imc_enter_scratch_ram(void) {} +void amd_imc_exit_scratch_ram(void) {} +#endif + +#endif /* _AMD_IMC_H_ */ -- 1.9.1