msm: pcie: add snapshot of PCIe root complex bus driver

This is a snapshot of MSM PCIe root complex as of msm-4.19
commit 6e2242593d4a ("drm/msm/dp: check for invalid seqno before
clearing a txmsg slot").

Changes made:
  - remove or switch to sysfs for all module params
  - move from MSM bus to ICC framework
  - replace all snprintf with scnprintf
  - update debugfs remove
  - update license for msm_pcie.h
  - misc syntax improvements

Change-Id: I310fdfd136346f74c20806bd5dcdbc74c7d894a3
Signed-off-by: Tony Truong <truong@codeaurora.org>
This commit is contained in:
Tony Truong 2019-09-18 17:47:28 -07:00
commit eb3beebb57
5 changed files with 8014 additions and 0 deletions

View file

@ -288,5 +288,30 @@ config PCI_HYPERV_INTERFACE
The Hyper-V PCI Interface is a helper driver allows other drivers to
have a common interface with the Hyper-V PCI frontend driver.
config PCI_MSM
tristate "MSM PCIe Controller driver"
depends on ARCH_QCOM && PCI
select PCI_DOMAINS
select PCI_DOMAINS_GENERIC
select PCI_MSI
select CRC8
help
Enables the PCIe functionality by configuring PCIe core on
MSM chipset and by enabling the ARM PCI framework extension.
The PCIe core is essential for communication between the host
and an endpoint.
If unsure, say N.
config PCI_MSM_MSI
tristate "MSM PCIe MSI support"
depends on PCI_MSM
depends on PCI_MSI_IRQ_DOMAIN
help
Say Y here if you want to enable MSI support for PCIe
controller and its devices. This controller offers
message signaled interrupts (MSI) allocation, routing,
masking, and affinity assigning for PCIe devices.
source "drivers/pci/controller/dwc/Kconfig"
endmenu

View file

@ -30,6 +30,8 @@ obj-$(CONFIG_PCIE_MEDIATEK) += pcie-mediatek.o
obj-$(CONFIG_PCIE_MOBIVEIL) += pcie-mobiveil.o
obj-$(CONFIG_PCIE_TANGO_SMP8759) += pcie-tango.o
obj-$(CONFIG_VMD) += vmd.o
obj-$(CONFIG_PCI_MSM) += pci-msm.o
obj-$(CONFIG_PCI_MSM_MSI) += pci-msm-msi.o
# pcie-hisi.o quirks are needed even without CONFIG_PCIE_DW
obj-y += dwc/

View file

@ -0,0 +1,430 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.*/
#include <linux/interrupt.h>
#include <linux/iommu.h>
#include <linux/ipc_logging.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/msi.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_pci.h>
#include <linux/pci.h>
struct msm_msi_irq {
unsigned int hwirq; /* MSI controller hwirq */
unsigned int virq; /* MSI controller virq */
};
struct msm_msi {
struct list_head clients;
struct device *dev;
struct device_node *of_node;
int nr_irqs;
struct msm_msi_irq *irqs;
unsigned long *bitmap; /* tracks used/unused MSI */
struct mutex mutex; /* mutex for modifying MSI client list and bitmap */
struct irq_domain *inner_domain; /* parent domain; gen irq related */
struct irq_domain *msi_domain; /* child domain; pci related */
phys_addr_t msi_addr;
};
/* structure for each client of MSI controller */
struct msm_msi_client {
struct list_head node;
struct msm_msi *msi;
struct device *dev; /* client's dev of pci_dev */
u32 nr_irqs; /* nr_irqs allocated for client */
dma_addr_t msi_addr;
};
static void msm_msi_handler(struct irq_desc *desc)
{
struct irq_chip *chip = irq_desc_get_chip(desc);
struct msm_msi *msi;
unsigned int virq;
chained_irq_enter(chip, desc);
msi = irq_desc_get_handler_data(desc);
virq = irq_find_mapping(msi->inner_domain, irq_desc_get_irq(desc));
generic_handle_irq(virq);
chained_irq_exit(chip, desc);
}
static void msm_msi_mask_irq(struct irq_data *data)
{
struct irq_data *parent_data;
if (!data->parent_data)
return;
parent_data = irq_get_irq_data(data->parent_data->hwirq);
if (!parent_data || !parent_data->chip)
return;
pci_msi_mask_irq(data);
parent_data->chip->irq_mask(parent_data);
}
static void msm_msi_unmask_irq(struct irq_data *data)
{
struct irq_data *parent_data;
if (!data->parent_data)
return;
parent_data = irq_get_irq_data(data->parent_data->hwirq);
if (!parent_data || !parent_data->chip)
return;
parent_data->chip->irq_unmask(parent_data);
pci_msi_unmask_irq(data);
}
static struct irq_chip msm_msi_irq_chip = {
.name = "msm_pci_msi",
.irq_enable = msm_msi_unmask_irq,
.irq_disable = msm_msi_mask_irq,
.irq_mask = msm_msi_mask_irq,
.irq_unmask = msm_msi_unmask_irq,
};
static int msm_msi_domain_prepare(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *arg)
{
struct msm_msi *msi = domain->parent->host_data;
struct msm_msi_client *client;
client = devm_kzalloc(msi->dev, sizeof(*client), GFP_KERNEL);
if (!client)
return -ENOMEM;
client->msi = msi;
client->dev = dev;
client->msi_addr = dma_map_resource(client->dev, msi->msi_addr,
PAGE_SIZE, DMA_FROM_DEVICE, 0);
if (dma_mapping_error(client->dev, client->msi_addr)) {
dev_err(msi->dev, "MSI: failed to map msi address\n");
client->msi_addr = 0;
return -ENOMEM;
}
mutex_lock(&msi->mutex);
list_add_tail(&client->node, &msi->clients);
mutex_unlock(&msi->mutex);
/* zero out struct for framework */
memset(arg, 0, sizeof(*arg));
return 0;
}
static void msm_msi_domain_finish(msi_alloc_info_t *arg, int retval)
{
struct device *dev = arg->desc->dev;
struct irq_domain *domain = dev_get_msi_domain(dev);
struct msm_msi *msi = domain->parent->host_data;
/* if prepare or alloc fails, then clean up */
if (retval) {
struct msm_msi_client *tmp, *client = NULL;
mutex_lock(&msi->mutex);
list_for_each_entry(tmp, &msi->clients, node) {
if (tmp->dev == dev) {
client = tmp;
list_del(&client->node);
break;
}
}
mutex_unlock(&msi->mutex);
if (!client)
return;
if (client->msi_addr)
dma_unmap_resource(client->dev, client->msi_addr,
PAGE_SIZE, DMA_FROM_DEVICE, 0);
devm_kfree(msi->dev, client);
return;
}
}
static struct msi_domain_ops msm_msi_domain_ops = {
.msi_prepare = msm_msi_domain_prepare,
.msi_finish = msm_msi_domain_finish,
};
static struct msi_domain_info msm_msi_domain_info = {
.flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
.ops = &msm_msi_domain_ops,
.chip = &msm_msi_irq_chip,
};
static int msm_msi_irq_set_affinity(struct irq_data *data,
const struct cpumask *mask, bool force)
{
struct irq_data *parent_data = irq_get_irq_data(irqd_to_hwirq(data));
if (!parent_data)
return -ENODEV;
/* set affinity for MSM MSI HW IRQ */
if (parent_data->chip->irq_set_affinity)
return parent_data->chip->irq_set_affinity(parent_data,
mask, force);
return -EINVAL;
}
static void msm_msi_irq_compose_msi_msg(struct irq_data *data,
struct msi_msg *msg)
{
struct msm_msi_client *client = irq_data_get_irq_chip_data(data);
struct irq_data *parent_data = irq_get_irq_data(irqd_to_hwirq(data));
if (!parent_data)
return;
msg->address_lo = lower_32_bits(client->msi_addr);
msg->address_hi = upper_32_bits(client->msi_addr);
/* GIC HW IRQ */
msg->data = irqd_to_hwirq(parent_data);
}
static struct irq_chip msm_msi_bottom_irq_chip = {
.name = "msm_msi",
.irq_set_affinity = msm_msi_irq_set_affinity,
.irq_compose_msi_msg = msm_msi_irq_compose_msi_msg,
};
static int msm_msi_irq_domain_alloc(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs,
void *args)
{
struct msm_msi *msi = domain->host_data;
struct msm_msi_client *tmp, *client = NULL;
struct device *dev = ((msi_alloc_info_t *)args)->desc->dev;
int i, ret = 0;
int pos;
mutex_lock(&msi->mutex);
list_for_each_entry(tmp, &msi->clients, node) {
if (tmp->dev == dev) {
client = tmp;
break;
}
}
if (!client) {
dev_err(msi->dev, "MSI: failed to find client dev\n");
ret = -ENODEV;
goto out;
}
pos = bitmap_find_next_zero_area(msi->bitmap, msi->nr_irqs, 0,
nr_irqs, 0);
if (pos < msi->nr_irqs) {
bitmap_set(msi->bitmap, pos, nr_irqs);
} else {
ret = -ENOSPC;
goto out;
}
for (i = 0; i < nr_irqs; i++) {
msi->irqs[pos].virq = virq + i;
irq_domain_set_info(domain, msi->irqs[pos].virq,
msi->irqs[pos].hwirq,
&msm_msi_bottom_irq_chip, client,
handle_simple_irq, NULL, NULL);
client->nr_irqs++;
pos++;
}
out:
mutex_unlock(&msi->mutex);
return ret;
}
static void msm_msi_irq_domain_free(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs)
{
struct irq_data *data = irq_domain_get_irq_data(domain, virq);
struct msm_msi_client *client;
struct msm_msi *msi;
int i;
if (!data)
return;
client = irq_data_get_irq_chip_data(data);
msi = client->msi;
mutex_lock(&msi->mutex);
for (i = 0; i < nr_irqs; i++)
if (msi->irqs[i].virq == virq)
break;
bitmap_clear(msi->bitmap, i, nr_irqs);
client->nr_irqs -= nr_irqs;
if (!client->nr_irqs) {
dma_unmap_resource(client->dev, client->msi_addr, PAGE_SIZE,
DMA_FROM_DEVICE, 0);
list_del(&client->node);
devm_kfree(msi->dev, client);
}
mutex_unlock(&msi->mutex);
irq_domain_free_irqs_parent(domain, virq, nr_irqs);
}
static const struct irq_domain_ops msi_domain_ops = {
.alloc = msm_msi_irq_domain_alloc,
.free = msm_msi_irq_domain_free,
};
static int msm_msi_alloc_domains(struct msm_msi *msi)
{
msi->inner_domain = irq_domain_add_linear(NULL, msi->nr_irqs,
&msi_domain_ops, msi);
if (!msi->inner_domain) {
dev_err(msi->dev, "MSI: failed to create IRQ domain\n");
return -ENOMEM;
}
msi->msi_domain = pci_msi_create_irq_domain(
of_node_to_fwnode(msi->of_node),
&msm_msi_domain_info,
msi->inner_domain);
if (!msi->msi_domain) {
dev_err(msi->dev, "MSI: failed to create MSI domain\n");
irq_domain_remove(msi->inner_domain);
return -ENOMEM;
}
return 0;
}
int msm_msi_init(struct device *dev)
{
int i, ret;
struct msm_msi *msi;
struct device_node *of_node;
const __be32 *prop_val;
if (!dev->of_node) {
dev_err(dev, "MSI: missing DT node\n");
return -EINVAL;
}
of_node = of_parse_phandle(dev->of_node, "msi-parent", 0);
if (!of_node) {
dev_err(dev, "MSI: no phandle for MSI found\n");
return -ENODEV;
}
if (!of_device_is_compatible(of_node, "qcom,pci-msi")) {
dev_err(dev, "MSI: no compatible qcom,pci-msi found\n");
ret = -ENODEV;
goto err;
}
if (!of_find_property(of_node, "msi-controller", NULL)) {
ret = -ENODEV;
goto err;
}
msi = devm_kzalloc(dev, sizeof(*msi), GFP_KERNEL);
if (!msi) {
ret = -ENOMEM;
goto err;
}
msi->dev = dev;
msi->of_node = of_node;
mutex_init(&msi->mutex);
INIT_LIST_HEAD(&msi->clients);
prop_val = of_get_address(msi->of_node, 0, NULL, NULL);
if (!prop_val) {
dev_err(msi->dev, "MSI: missing 'reg' devicetree\n");
ret = -EINVAL;
goto err;
}
msi->msi_addr = be32_to_cpup(prop_val);
if (!msi->msi_addr) {
dev_err(msi->dev, "MSI: failed to get MSI address\n");
ret = -EINVAL;
goto err;
}
msi->nr_irqs = of_irq_count(msi->of_node);
if (!msi->nr_irqs) {
dev_err(msi->dev, "MSI: found no MSI interrupts\n");
ret = -ENODEV;
goto err;
}
msi->irqs = devm_kcalloc(msi->dev, msi->nr_irqs,
sizeof(*msi->irqs), GFP_KERNEL);
if (!msi->irqs) {
ret = -ENOMEM;
goto err;
}
msi->bitmap = devm_kcalloc(msi->dev, BITS_TO_LONGS(msi->nr_irqs),
sizeof(*msi->bitmap), GFP_KERNEL);
if (!msi->bitmap) {
ret = -ENOMEM;
goto err;
}
ret = msm_msi_alloc_domains(msi);
if (ret) {
dev_err(msi->dev, "MSI: failed to allocate MSI domains\n");
goto err;
}
for (i = 0; i < msi->nr_irqs; i++) {
unsigned int irq = irq_of_parse_and_map(msi->of_node, i);
if (!irq) {
dev_err(msi->dev,
"MSI: failed to parse/map interrupt\n");
ret = -ENODEV;
goto free_irqs;
}
msi->irqs[i].hwirq = irq;
irq_set_chained_handler_and_data(msi->irqs[i].hwirq,
msm_msi_handler, msi);
}
return 0;
free_irqs:
for (--i; i >= 0; i--) {
irq_set_chained_handler_and_data(msi->irqs[i].hwirq,
NULL, NULL);
irq_dispose_mapping(msi->irqs[i].hwirq);
}
irq_domain_remove(msi->msi_domain);
irq_domain_remove(msi->inner_domain);
err:
of_node_put(of_node);
return ret;
}
EXPORT_SYMBOL(msm_msi_init);

File diff suppressed because it is too large Load diff

247
include/linux/msm_pcie.h Normal file
View file

@ -0,0 +1,247 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2014-2019, The Linux Foundation. All rights reserved.*/
#ifndef __MSM_PCIE_H
#define __MSM_PCIE_H
#include <linux/types.h>
#include <linux/pci.h>
enum msm_pcie_config {
MSM_PCIE_CONFIG_INVALID = 0,
MSM_PCIE_CONFIG_NO_CFG_RESTORE = 0x1,
MSM_PCIE_CONFIG_LINKDOWN = 0x2,
MSM_PCIE_CONFIG_NO_RECOVERY = 0x4,
};
enum msm_pcie_pm_opt {
MSM_PCIE_DRV_SUSPEND,
MSM_PCIE_SUSPEND,
MSM_PCIE_RESUME,
MSM_PCIE_DISABLE_PC,
MSM_PCIE_ENABLE_PC,
};
enum msm_pcie_event {
MSM_PCIE_EVENT_INVALID = 0,
MSM_PCIE_EVENT_LINKDOWN = 0x1,
MSM_PCIE_EVENT_LINKUP = 0x2,
MSM_PCIE_EVENT_WAKEUP = 0x4,
MSM_PCIE_EVENT_L1SS_TIMEOUT = BIT(3),
MSM_PCIE_EVENT_DRV_CONNECT = BIT(4),
MSM_PCIE_EVENT_DRV_DISCONNECT = BIT(5),
};
enum msm_pcie_trigger {
MSM_PCIE_TRIGGER_CALLBACK,
MSM_PCIE_TRIGGER_COMPLETION,
};
struct msm_pcie_notify {
enum msm_pcie_event event;
void *user;
void *data;
u32 options;
};
struct msm_pcie_register_event {
u32 events;
void *user;
enum msm_pcie_trigger mode;
void (*callback)(struct msm_pcie_notify *notify);
struct msm_pcie_notify notify;
struct completion *completion;
u32 options;
};
#if IS_ENABLED(CONFIG_PCI_MSM_MSI)
int msm_msi_init(struct device *dev);
#else
static inline int msm_msi_init(struct device *dev)
{
return -EINVAL;
}
#endif
#if IS_ENABLED(CONFIG_PCI_MSM)
/**
* msm_pcie_set_link_bandwidth - updates the number of lanes and speed of PCIe
* link.
* @pci_dev: client's pci device structure
* @target_link_speed: gen speed
* @target_link_width: number of lanes
*
* This function gives PCIe clients the control to update the number of lanes
* and gen speed of the link.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_set_link_bandwidth(struct pci_dev *pci_dev, u16 target_link_speed,
u16 target_link_width);
/**
* msm_pcie_l1ss_timeout_disable - disable L1ss timeout feature
* @pci_dev: client's pci device structure
*
* This function gives PCIe clients the control to disable L1ss timeout
* feature.
*/
void msm_pcie_l1ss_timeout_disable(struct pci_dev *pci_dev);
/**
* msm_pcie_l1ss_timeout_enable - enable L1ss timeout feature
* @pci_dev: client's pci device structure
*
* This function gives PCIe clients the control to enable L1ss timeout
* feature.
*/
void msm_pcie_l1ss_timeout_enable(struct pci_dev *pci_dev);
/**
* msm_pcie_pm_control - control the power state of a PCIe link.
* @pm_opt: power management operation
* @busnr: bus number of PCIe endpoint
* @user: handle of the caller
* @data: private data from the caller
* @options: options for pm control
*
* This function gives PCIe endpoint device drivers the control to change
* the power state of a PCIe link for their device.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 busnr, void *user,
void *data, u32 options);
/**
* msm_pcie_register_event - register an event with PCIe bus driver.
* @reg: event structure
*
* This function gives PCIe endpoint device drivers an option to register
* events with PCIe bus driver.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_register_event(struct msm_pcie_register_event *reg);
/**
* msm_pcie_deregister_event - deregister an event with PCIe bus driver.
* @reg: event structure
*
* This function gives PCIe endpoint device drivers an option to deregister
* events with PCIe bus driver.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_deregister_event(struct msm_pcie_register_event *reg);
/**
* msm_pcie_recover_config - recover config space.
* @dev: pci device structure
*
* This function recovers the config space of both RC and Endpoint.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_recover_config(struct pci_dev *dev);
/**
* msm_pcie_enumerate - enumerate Endpoints.
* @rc_idx: RC that Endpoints connect to.
*
* This function enumerates Endpoints connected to RC.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_enumerate(u32 rc_idx);
/**
* msm_pcie_recover_config - recover config space.
* @dev: pci device structure
*
* This function recovers the config space of both RC and Endpoint.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_recover_config(struct pci_dev *dev);
/**
* msm_pcie_shadow_control - control the shadowing of PCIe config space.
* @dev: pci device structure
* @enable: shadowing should be enabled or disabled
*
* This function gives PCIe endpoint device drivers the control to enable
* or disable the shadowing of PCIe config space.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_shadow_control(struct pci_dev *dev, bool enable);
/*
* msm_pcie_debug_info - run a PCIe specific debug testcase.
* @dev: pci device structure
* @option: specifies which PCIe debug testcase to execute
* @base: PCIe specific range
* @offset: offset of destination register
* @mask: mask the bit(s) of destination register
* @value: value to be written to destination register
*
* This function gives PCIe endpoint device drivers the control to
* run a debug testcase.
*
* Return: 0 on success, negative value on error
*/
int msm_pcie_debug_info(struct pci_dev *dev, u32 option, u32 base,
u32 offset, u32 mask, u32 value);
#else /* !CONFIG_PCI_MSM */
static inline int msm_pcie_pm_control(enum msm_pcie_pm_opt pm_opt, u32 busnr,
void *user, void *data, u32 options)
{
return -ENODEV;
}
static inline int msm_pcie_l1ss_timeout_disable(struct pci_dev *pci_dev)
{
return -ENODEV;
}
static inline int msm_pcie_l1ss_timeout_enable(struct pci_dev *pci_dev)
{
return -ENODEV;
}
static inline int msm_pcie_register_event(struct msm_pcie_register_event *reg)
{
return -ENODEV;
}
static inline int msm_pcie_deregister_event(struct msm_pcie_register_event *reg)
{
return -ENODEV;
}
static inline int msm_pcie_recover_config(struct pci_dev *dev)
{
return -ENODEV;
}
static inline int msm_pcie_enumerate(u32 rc_idx)
{
return -ENODEV;
}
static inline int msm_pcie_shadow_control(struct pci_dev *dev, bool enable)
{
return -ENODEV;
}
static inline int msm_pcie_debug_info(struct pci_dev *dev, u32 option, u32 base,
u32 offset, u32 mask, u32 value)
{
return -ENODEV;
}
#endif /* CONFIG_PCI_MSM */
#endif /* __MSM_PCIE_H */