// SPDX-License-Identifier: GPL-2.0
/**
* xhci-dbc.c - xHCI debug capability early driver
*
* Copyright (C) 2016 Intel Corporation
*
* Author: Lu Baolu <baolu.lu@linux.intel.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include <linux/console.h>
#include <linux/pci_regs.h>
#include <linux/pci_ids.h>
#include <linux/memblock.h>
#include <linux/io.h>
#include <asm/pci-direct.h>
#include <asm/fixmap.h>
#include <linux/bcd.h>
#include <linux/export.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include "../host/xhci.h"
#include "xhci-dbc.h"
static struct xdbc_state xdbc;
static bool early_console_keep;
#ifdef XDBC_TRACE
#define xdbc_trace trace_printk
#else
static inline void xdbc_trace(const char *fmt, ...) { }
#endif /* XDBC_TRACE */
static void __iomem * __init xdbc_map_pci_mmio(u32 bus, u32 dev, u32 func)
{
u64 val64, sz64, mask64;
void __iomem *base;
u32 val, sz;
u8 byte;
val = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0);
write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0, ~0);
sz = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0);
write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0, val);
if (val == 0xffffffff || sz == 0xffffffff) {
pr_notice("invalid mmio bar\n");
return NULL;
}
val64 = val & PCI_BASE_ADDRESS_MEM_MASK;
sz64 = sz & PCI_BASE_ADDRESS_MEM_MASK;
mask64 = PCI_BASE_ADDRESS_MEM_MASK;
if ((val & PCI_BASE_ADDRESS_MEM_TYPE_MASK) == PCI_BASE_ADDRESS_MEM_TYPE_64) {
val = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4);
write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4, ~0);
sz = read_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4);
write_pci_config(bus, dev, func, PCI_BASE_ADDRESS_0 + 4, val);
val64 |= (u64)val << 32;
sz64 |= (u64)sz << 32;
mask64 |= ~0ULL << 32;
}
sz64 &= mask64;
if (!sz64) {
pr_notice("invalid mmio address\n");
return NULL;
}
sz64 = 1ULL << __ffs64(sz64);
/* Check if the mem space is enabled: */
byte = read_pci_config_byte(bus, dev, func, PCI_COMMAND);
if (!(byte & PCI_COMMAND_MEMORY)) {
byte |= PCI_COMMAND_MEMORY;
write_pci_config_byte(bus, dev, func, PCI_COMMAND, byte);
}
xdbc.xhci_start = val64;
xdbc.xhci_length = sz64;
base = early_ioremap(val64, sz64);
return base;
}
static void * __init xdbc_get_page(dma_addr_t *dma_addr)
{
void *virt;
virt = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
if (!virt)
return NULL;
if (dma_addr)
*dma_addr = (dma_addr_t)__pa(virt);
return virt;
}
static u32 __init xdbc_find_dbgp(int xdbc_num, u32 *b, u32 *d, u32 *f)
{
u32 bus, dev, func, class;
for (bus = 0; bus < XDBC_PCI_MAX_BUSES; bus++) {
for (dev = 0; dev < XDBC_PCI_MAX_DEVICES; dev++) {
for (func = 0; func < XDBC_PCI_MAX_FUNCTION; func++) {
class = read_pci_config(bus, dev, func, PCI_CLASS_REVISION);
if ((class >> 8) != PCI_CLASS_SERIAL_USB_XHCI)
continue;
if (xdbc_num-- != 0)
continue;
*b = bus;
*d = dev;
*f = func;
return 0;
}
}
}
return -1;
}
static int handshake(void __iomem *ptr, u32 mask, u32 done, int wait, int delay)
{
u32 result;
do {
result = readl(ptr);
result &= mask;
if (result == done)
return 0;
udelay(delay);
wait -= delay;
} while (wait > 0);
return -ETIMEDOUT;
}
static void __init xdbc_bios_handoff(void)
{
int offset, timeout;
u32 val;
offset = xhci_find_next_ext_cap(xdbc.xhci_base, 0, XHCI_EXT_CAPS_LEGACY);
val = readl(xdbc.xhci_base + offset);
if (val & XHCI_HC_BIOS_OWNED) {
writel(val | XHCI_HC_OS_OWNED, xdbc.xhci_base + offset);
timeout = handshake(xdbc.xhci_base + offset, XHCI_HC_BIOS_OWNED, 0, 5000, 10);
if (timeout) {
pr_notice("failed to hand over xHCI control from BIOS\n");
writel(val & ~XHCI_HC_BIOS_OWNED, xdbc.xhci_base + offset);
}
}
/* Disable BIOS SMIs and clear all SMI events: */
val = readl(xdbc.xhci_base + offset + XHCI_LEGACY_CONTROL_OFFSET);
val &= XHCI_LEGACY_DISABLE_SMI;
val |= XHCI_LEGACY_SMI_EVENTS;
writel(val, xdbc.xhci_base + offset + XHCI_LEGACY_CONTROL_OFFSET);
}
static int __init
xdbc_alloc_ring(struct xdbc_segment *seg, struct xdbc_ring *ring)