// SPDX-License-Identifier: GPL-2.0
/*
* PCIe driver for Marvell Armada 370 and Armada XP SoCs
*
* Author: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
*/
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/mbus.h>
#include <linux/msi.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/of_pci.h>
#include <linux/of_platform.h>
#include "../pci.h"
#include "../pci-bridge-emul.h"
/*
* PCIe unit register offsets.
*/
#define PCIE_DEV_ID_OFF 0x0000
#define PCIE_CMD_OFF 0x0004
#define PCIE_DEV_REV_OFF 0x0008
#define PCIE_BAR_LO_OFF(n) (0x0010 + ((n) << 3))
#define PCIE_BAR_HI_OFF(n) (0x0014 + ((n) << 3))
#define PCIE_CAP_PCIEXP 0x0060
#define PCIE_HEADER_LOG_4_OFF 0x0128
#define PCIE_BAR_CTRL_OFF(n) (0x1804 + (((n) - 1) * 4))
#define PCIE_WIN04_CTRL_OFF(n) (0x1820 + ((n) << 4))
#define PCIE_WIN04_BASE_OFF(n) (0x1824 + ((n) << 4))
#define PCIE_WIN04_REMAP_OFF(n) (0x182c + ((n) << 4))
#define PCIE_WIN5_CTRL_OFF 0x1880
#define PCIE_WIN5_BASE_OFF 0x1884
#define PCIE_WIN5_REMAP_OFF 0x188c
#define PCIE_CONF_ADDR_OFF 0x18f8
#define PCIE_CONF_ADDR_EN 0x80000000
#define PCIE_CONF_REG(r) ((((r) & 0xf00) << 16) | ((r) & 0xfc))
#define PCIE_CONF_BUS(b) (((b) & 0xff) << 16)
#define PCIE_CONF_DEV(d) (((d) & 0x1f) << 11)
#define PCIE_CONF_FUNC(f) (((f) & 0x7) << 8)
#define PCIE_CONF_ADDR(bus, devfn, where) \
(PCIE_CONF_BUS(bus) | PCIE_CONF_DEV(PCI_SLOT(devfn)) | \
PCIE_CONF_FUNC(PCI_FUNC(devfn)) | PCIE_CONF_REG(where) | \
PCIE_CONF_ADDR_EN)
#define PCIE_CONF_DATA_OFF 0x18fc
#define PCIE_MASK_OFF 0x1910
#define PCIE_MASK_ENABLE_INTS 0x0f000000
#define PCIE_CTRL_OFF 0x1a00
#define PCIE_CTRL_X1_MODE 0x0001
#define PCIE_STAT_OFF 0x1a04
#define PCIE_STAT_BUS 0xff00
#define PCIE_STAT_DEV 0x1f0000
#define PCIE_STAT_LINK_DOWN BIT(0)
#define PCIE_RC_RTSTA 0x1a14
#define PCIE_DEBUG_CTRL 0x1a60
#define PCIE_DEBUG_SOFT_RESET BIT(20)
struct mvebu_pcie_port;
/* Structure representing all PCIe interfaces */
struct mvebu_pcie {
struct platform_device *pdev;
struct mvebu_pcie_port *ports;
struct msi_controller *msi;
struct resource io;
struct resource realio;
struct resource mem;
struct resource busn;
int nports;
};
struct mvebu_pcie_window {
phys_addr_t base;
phys_addr_t remap;
size_t size;
};
/* Structure representing one PCIe interface */
struct mvebu_pcie_port {
char *name;
void __iomem *base;
u32 port;
u32 lane;
int devfn;
unsigned int mem_target;
unsigned int mem_attr;
unsigned int io_target;
unsigned int io_attr;
struct clk *clk;
struct gpio_desc *reset_gpio;
char *reset_name;
struct pci_bridge_emul bridge;
struct device_node *dn;
struct mvebu_pcie *pcie;
struct mvebu_pcie_window memwin;
struct mvebu_pcie_window iowin;
u32 saved_pcie_stat;
};
static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
{
writel(val, port->base + reg);
}
static inline u32 mvebu_readl(struct mvebu_pcie_port *port, u32 reg)
{
return readl(port->base + reg);
}
static inline bool mvebu_has_ioport(struct mvebu_pcie_port *port)
{
return port->io_target != -1 && port->io_attr != -1;
}
static bool mvebu_pcie_link_up(struct mvebu_pcie_port *port)
{
return !(mvebu_readl(port, PCIE_STAT_OFF) & PCIE_STAT_LINK_DOWN);
}
static void mvebu_pcie_set_local_bus_nr(struct mvebu_pcie_port *port, int nr)
{
u32 stat;
stat = mvebu_readl(port, PCIE_STAT_OFF);
stat &= ~PCIE_STAT_BUS;
stat |= nr << 8;
mvebu_writel(port, stat, PCIE_STAT_OFF);
}
static void mvebu_pcie_set_local_dev_nr(struct mvebu_pcie_port *port, int nr)
{
u32 stat;
stat = mvebu_readl(port, PCIE_STAT_OFF);
stat &= ~PCIE_STAT_DEV;
stat |= nr << 16;
mvebu_writel(port, stat, PCIE_STAT_OFF);
}
/*
* Setup PCIE BARs and Address Decode Wins:
* BAR[0,2] -> disabled, BAR[1] -> covers all DRAM banks
* WIN[0-3] -> DRAM bank[0-3]
*/
static void mvebu_pcie_setup_wins(struct mvebu_pcie_port *port)
{
const struct mbus_dram_target_info *dram;
u32 size;
int i;
dram = mv_mbus_dram_info();
/* First, disable and clear BARs and windows. */
for (i = 1; i < 3; i++) {
mvebu_writel(port, 0, PCIE_BAR_CTRL_OFF(i));
mvebu_writel(port, 0, PCIE_BAR_LO_OFF(i));
mvebu_writel(port, 0, PCIE_BAR_HI_OFF(i));
}
for (i = 0; i < 5; i++) {
mvebu_writel(port, 0, PCIE_WIN04_CTRL_OFF(i));
mvebu_writel(port<