diff --git a/drivers/pci/switch/Kconfig b/drivers/pci/switch/Kconfig index d370f4ce0492..503eee6603c5 100644 --- a/drivers/pci/switch/Kconfig +++ b/drivers/pci/switch/Kconfig @@ -12,4 +12,13 @@ config PCI_SW_SWITCHTEC devices. See for more information. +config PCI_SW_QCOM_SWITCH + depends on ARCH_QCOM + tristate "Qualcomm Technologies, Inc. PCIe Switch Management Driver" + help + Enables support for switches connected to Qualcomm Technologies, Inc. + PCIe Rootport. Switches that require erratas can bind with this driver + and run errata for the corresponding port. Values can be saved and + restored using this driver during suspend and resume. + endmenu diff --git a/drivers/pci/switch/Makefile b/drivers/pci/switch/Makefile index acd56d3b4a35..197884524b4b 100644 --- a/drivers/pci/switch/Makefile +++ b/drivers/pci/switch/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PCI_SW_SWITCHTEC) += switchtec.o +obj-$(CONFIG_PCI_SW_QCOM_SWITCH) += switch-qcom.o diff --git a/drivers/pci/switch/switch-qcom.c b/drivers/pci/switch/switch-qcom.c new file mode 100644 index 000000000000..99412c37d8ae --- /dev/null +++ b/drivers/pci/switch/switch-qcom.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */ + +/* + * MSM PCIe switch controller + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DIODE_VENDOR_ID 0x12d8 +#define DIODE_DEVICE_ID 0xb304 + +/* + * @DIODE_ERRATA_0: Apply errata specific to the upstream port (USP). + * @DIODE_ERRATA_1: Apply errata configuration to downstream port1 (DSP1). + * @DIODE_ERRATA_2: Common errata applied to (DSP2) port in the switch. + */ +enum { + DIODE_ERRATA_0, + DIODE_ERRATA_1, + DIODE_ERRATA_2, + SWITCH_MAX, +}; + +struct pci_qcom_switch_errata { + int (*config_errata)(struct device *dev, void *data); +}; + +/* + * Supports configuring Port Arbitration for VC0 only. Also, + * current support is only dword updates for parb_phase_value. + */ +static int config_port_arbitration(struct device *dev, int parb_select, + u32 parb_phase_value) +{ + struct pci_dev *pcidev = to_pci_dev(dev); + int i, pos, size; + int timeout = 100; + u32 parb_offset, parb_phase, parb_size; + u32 val; + u16 status; + + pos = pci_find_ext_capability(pcidev, PCI_EXT_CAP_ID_VC); + if (!pos) { + pr_err("port %s: could not find VC capability register\n"); + return -EIO; + } + + pci_read_config_dword(pcidev, pos + PCI_VC_RES_CAP, &val); + parb_offset = ((val & PCI_VC_RES_CAP_ARB_OFF) >> 24) * 16; + + if (val & PCI_VC_RES_CAP_256_PHASE) + parb_phase = 256; + else if (val & (PCI_VC_RES_CAP_128_PHASE | + PCI_VC_RES_CAP_128_PHASE_TB)) + parb_phase = 128; + else if (val & PCI_VC_RES_CAP_64_PHASE) + parb_phase = 64; + else if (val & PCI_VC_RES_CAP_32_PHASE) + parb_phase = 32; + + pci_read_config_dword(pcidev, pos + PCI_VC_PORT_CAP1, &val); + /* Port Arbitration Table Entry Size (bits) */ + parb_size = 1 << ((val & PCI_VC_CAP1_ARB_SIZE) >> 10); + + /* parb size (b) * number of phases / bits to byte / byte per dword */ + size = (parb_size * parb_phase) / 8 / 4; + if (!size) + return -EIO; + + /* update Port Arbitration Table */ + for (i = 0; i < size; i++) + pci_write_config_dword(pcidev, pos + parb_offset + i * 4, + parb_phase_value); + + /* set Load Port Arbitration Table bit to update the port arbitration */ + pci_read_config_dword(pcidev, pos + PCI_VC_RES_CTRL, &val); + val |= PCI_VC_RES_CTRL_LOAD_TABLE; + pci_write_config_dword(pcidev, pos + PCI_VC_RES_CTRL, val); + + /* poll Port Arbitration Status until H/W completes with the update */ + do { + usleep_range(1000, 1005); + pci_read_config_word(pcidev, pos + PCI_VC_RES_STATUS, &status); + } while ((status & PCI_VC_RES_STATUS_TABLE) && --timeout); + + if (!timeout) { + pr_err("port %s failed to load Port Arbitration Table\n", + dev_name(&pcidev->dev)); + return -EIO; + } + + /* update Port Arbitration Select field to select scheme */ + pci_read_config_dword(pcidev, pos + PCI_VC_RES_CTRL, &val); + val &= ~PCI_VC_RES_CTRL_ARB_SELECT; + val |= parb_select << 17; + pci_write_config_dword(pcidev, pos + PCI_VC_RES_CTRL, val); + + return 0; +} + +static int config_common_port_diode(struct device *dev, void *data) +{ + struct pci_dev *pcidev = to_pci_dev(dev); + int ret = 0; + u32 addr, val; + + if (!pcidev) { + pr_err("%s: port not found\n", __func__); + return -ENODEV; + } + + addr = 0x3c; + ret = pci_read_config_dword(pcidev, addr, &val); + if (ret) { + pr_err("read fail: port %s read cfg addr:%x val:%x ret:%d\n", + dev_name(&pcidev->dev), addr, val, ret); + return ret; + } + + val |= 1 << 17; + ret = pci_write_config_dword(pcidev, addr, val); + if (ret) { + pr_err("write fail: port %s read cfg addr:%x val:%x ret:%d\n", + dev_name(&pcidev->dev), addr, val, ret); + return ret; + } + + return 0; +} + +static int config_upstream_port_diode(struct device *dev, void *data) +{ + struct pci_dev *pcidev = to_pci_dev(dev); + u32 addr, val; + int ret; + + if (!pcidev) { + pr_err("%s: port not found\n", __func__); + return -ENODEV; + } + + /* write cfg offset 74h.bit[7] = 1 */ + addr = 0x74; + ret = pci_read_config_dword(pcidev, addr, &val); + if (ret) { + pr_err("read fail: port %s read cfg addr:%x val:%x ret:%d\n", + dev_name(&pcidev->dev), addr, val, ret); + return ret; + } + + val |= 1 << 7; + ret = pci_write_config_dword(pcidev, addr, val); + if (ret) { + pr_err("write fail: port %s write cfg addr:%x val:%x ret:%d\n", + dev_name(&pcidev->dev), addr, val, ret); + return ret; + } + + ret = config_common_port_diode(dev, NULL); + if (ret) { + pr_err("%s: Applying common configuration failed\n", __func__); + return ret; + } + + /* + * Update Port Arbitration table + * + * Port arbitation scheme: 3: WRR 128 Phase + * + * 4-bits per phase + * + * Every 8th phase will be for Port2 while the rest is for Port1 + * ex: + * Phase[7]: Port2 Phase [6:0]: Port1 + * Phase[15]: Port2 Phase [14:8]: Port1 + * ... + * Phase[127]: Port2 Phase [126:120]: Port1 + */ + ret = config_port_arbitration(dev, 3, 0x21111111); + if (ret) { + pr_err("%s: Failed to setup Port Arbitration\n", __func__); + return ret; + } + + return 0; +} + +static int config_downstream_port_1_diode(struct device *dev, void *data) +{ + struct pci_dev *pcidev = to_pci_dev(dev); + int ret; + u32 addr, val; + + if (!pcidev) { + pr_err("%s: port not found\n", __func__); + return -ENODEV; + } + + /* write cfg offset 6ch.bit[25] = 1 */ + addr = 0x6c; + ret = pci_read_config_dword(pcidev, addr, &val); + if (ret) { + pr_err("read fail: port %s read cfg addr:%x val:%x ret:%d\n", + dev_name(&pcidev->dev), addr, val, ret); + return ret; + } + + val |= 1 << 25; + ret = pci_write_config_dword(pcidev, addr, val); + if (ret) { + pr_err("write fail: port %s write cfg addr:%x val:%x ret:%d\n", + dev_name(&pcidev->dev), addr, val, ret); + return ret; + } + + /* write cfg offset 33ch.bit[2] = 1 */ + addr = 0x33c; + ret = pci_read_config_dword(pcidev, addr, &val); + if (ret) { + pr_err("read fail: port %s read cfg addr:%x val:%x ret:%d\n", + dev_name(&pcidev->dev), addr, val, ret); + return ret; + } + + val |= 1 << 2; + ret = pci_write_config_dword(pcidev, addr, val); + if (ret) { + pr_err("write fail: port %s write cfg addr:%x val:%x ret:%d\n", + dev_name(&pcidev->dev), addr, val, ret); + return ret; + } + + ret = config_common_port_diode(dev, NULL); + if (ret) { + pr_err("%s: Applying common configuration failed\n", __func__); + return ret; + } + + return 0; +} + +struct pci_qcom_switch_errata errata[] = { + [DIODE_ERRATA_0] = {config_upstream_port_diode}, + [DIODE_ERRATA_1] = {config_downstream_port_1_diode}, + [DIODE_ERRATA_2] = {config_common_port_diode}, +}; + +static struct pci_device_id switch_qcom_pci_tbl[] = { + { + PCI_DEVICE(DIODE_VENDOR_ID, DIODE_DEVICE_ID), + }, + {0}, +}; +MODULE_DEVICE_TABLE(pci, switch_qcom_pci_tbl); + +static int switch_qcom_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret = 0, errata_num = 0; + + ret = of_property_read_u32((&pdev->dev)->of_node, "errata", + &errata_num); + if (ret) { + pr_info("No erratas needed\n"); + return 0; + } + + pr_info("Errata being requested: %d\n", errata_num); + + if (errata_num >= SWITCH_MAX) { + pr_err("Invalid errata num:%d\n", errata_num); + return -EINVAL; + } + + ret = errata[errata_num].config_errata(&pdev->dev, NULL); + if (ret) { + pr_err("Error applying errata\n"); + return ret; + } + + return 0; +} + +static struct pci_driver switch_qcom_pci_driver = { + .name = "pcie-qcom-switch", + .id_table = switch_qcom_pci_tbl, + .probe = switch_qcom_pci_probe, +}; + +static int __init switch_qcom_pci_init(void) +{ + return pci_register_driver(&switch_qcom_pci_driver); +} +module_init(switch_qcom_pci_init); + +static void __exit switch_qcom_pci_exit(void) +{ + return pci_unregister_driver(&switch_qcom_pci_driver); +} +module_exit(switch_qcom_pci_exit);