pci: switch: Add snapshot of PCIe switch driver

Add snapshot for switch driver stack from msm-4.14.c4 commit
ad8af976b4ab6 ("regulator: qpnp-lcdb: Disable step voltage ramp
for PM8150L V3").

Change-Id: I184823baa47f21c93e8bd6cc60c17fe790b92cfa
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
This commit is contained in:
Hemant Kumar 2021-02-01 14:54:00 -08:00
commit e056332148
3 changed files with 317 additions and 0 deletions

View file

@ -12,4 +12,13 @@ config PCI_SW_SWITCHTEC
devices. See <file:Documentation/driver-api/switchtec.rst> 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

View file

@ -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

View file

@ -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 <linux/delay.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of_pci.h>
#include <linux/pci.h>
#include <linux/types.h>
#include <linux/of.h>
#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);