Files
kernel-zhihe-a210/drivers/pci/controller/dwc/pcie-zh.c
2026-01-04 12:22:11 +08:00

429 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* PCIe RC driver for zh P100
*
* Copyright (C) 2025 zh computing, Inc.
*
* Author: Ya.Huang
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/pci.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/resource.h>
#include <linux/types.h>
#include <linux/reset.h>
#include <linux/gpio/consumer.h>
#include "pcie-designware.h"
#define PCIE_X4_TYPE 0x0
#define PCIE_X1_TYPE 0x1
#define PCIE_GEN3X4_DBI_BADDR 0x0b000000
#define PCIE_GEN3X4_CTRL_REG 0x00000000
#define PCIE_GEN3X4_DBG_INFO_REG0 0x00000430
#define PCIE_EXTENDED_REG0 0x00000154
#define PCIE_EXTENDED_REG1 0x00000158
#define PCIE_EXTENDED_REG2 0x0000015c
#define PCIE_EXTENDED_REG3 0x00000160
#define TYPE1_STATUS_COMMAND_REG 0x00000004
#define SEC_STAT_IO_LIMIT_IO_BASE_REG 0x0000001c
#define MEM_LIMIT_MEM_BASE_REG 0x00000020
#define PREF_MEM_LIMIT_PREF_MEM_BASE_REG 0x00000024
#define PREF_BASE_UPPER_REG 0x00000028
#define PREF_LIMIT_UPPER_REG 0x0000002c
#define IO_LIMIT_UPPER_IO_BASE_UPPER_REG 0x00000030
#define TRGT_MAP_CTRL_OFF 0x0000081c
#define DEVICE_CONTROL_DEVICE_STATUS 0x00000078
#define LINK_CONTROL2_LINK_STATUS2_REG 0x000000a0
#define LINK_UP_IS_OK 0x11
struct zhihe_pcie {
struct dw_pcie pci;
enum dw_pcie_device_mode mode;
unsigned int ip_type;
void __iomem *cfg_base;
struct gpio_desc *pcie_bat_en;
struct gpio_desc *pcie_3v3_en;
struct gpio_desc *pcie_12v_en;
struct gpio_desc *pcie_clk_en;
struct gpio_desc *minipcie_1v5_pwren;
struct gpio_desc *minipcie_3v3_pwren;
struct gpio_desc *minipcie_perst;
struct gpio_desc *pcie_clk_pwren;
struct phy *phy;
};
#define to_zhihe_pcie(x) (struct zhihe_pcie*)dev_get_drvdata((x)->dev)
struct zhihe_pcie_of_data {
enum dw_pcie_device_mode mode;
};
static inline int zhihe_pcie_cfg_readl(struct zhihe_pcie *pcie,
u32 reg)
{
return readl(pcie->cfg_base + reg);
}
static inline void zhihe_pcie_cfg_writel(struct zhihe_pcie *pcie,
u32 reg, u32 val)
{
writel(val, pcie->cfg_base + reg);
}
static void __maybe_unused zhihe_pcie_ltssm_enable(struct dw_pcie *pci)
{
struct zhihe_pcie *pcie = to_zhihe_pcie(pci);
writel(0x1114, pcie->cfg_base + PCIE_GEN3X4_CTRL_REG);
}
static void __maybe_unused zhihe_pcie_ltssm_disable(struct dw_pcie *pci)
{
struct zhihe_pcie *pcie = to_zhihe_pcie(pci);
writel(0x1014, pcie->cfg_base + PCIE_GEN3X4_CTRL_REG);
}
static void zhihe_pcie_wait_linkup(struct zhihe_pcie *pcie)
{
u32 ltssm_stat = 0;
unsigned long cnt = 0;
unsigned int TIME_OUT_CNT = 20;
unsigned int DELAY_MS = 50;
do {
mdelay(DELAY_MS);
ltssm_stat = zhihe_pcie_cfg_readl(pcie, \
PCIE_GEN3X4_DBG_INFO_REG0);
ltssm_stat &= 0x3f;
if (ltssm_stat == LINK_UP_IS_OK) {
dev_info(pcie->pci.dev, "ltssm:link up ok!\n");
break;
}
if (cnt > TIME_OUT_CNT) {
dev_err(pcie->pci.dev, "ltssm_stat = 0x%x,link up fail!\n",ltssm_stat);
break;
}
cnt++;
} while (ltssm_stat != LINK_UP_IS_OK);
}
static int zhihe_pcie_get_resources(struct platform_device *pdev,
struct zhihe_pcie *pcie)
{
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Failed to get PCIe DBI resource\n");
return -ENODEV;
}
if(res->start == PCIE_GEN3X4_DBI_BADDR)
pcie->ip_type = PCIE_X4_TYPE;
else
pcie->ip_type = PCIE_X1_TYPE;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcie_sysreg");
pcie->cfg_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(pcie->cfg_base))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->cfg_base),
"Failed to get pcie_sysreg resource");
/* Get GPIO descriptors for PCIe power control */
pcie->pcie_bat_en = devm_gpiod_get_optional(&pdev->dev,
"pcie-bat-en", GPIOD_OUT_LOW);
if (IS_ERR(pcie->pcie_bat_en))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->pcie_bat_en),
"Failed to get pcie-bat-en GPIO");
pcie->pcie_3v3_en = devm_gpiod_get_optional(&pdev->dev,
"pcie-3v3-en", GPIOD_OUT_LOW);
if (IS_ERR(pcie->pcie_3v3_en))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->pcie_3v3_en),
"Failed to get pcie-3v3-en GPIO");
pcie->pcie_12v_en = devm_gpiod_get_optional(&pdev->dev,
"pcie-12v-en", GPIOD_OUT_LOW);
if (IS_ERR(pcie->pcie_12v_en))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->pcie_12v_en),
"Failed to get pcie-12v-en GPIO");
pcie->pcie_clk_en = devm_gpiod_get_optional(&pdev->dev,
"pcie-clk-en", GPIOD_OUT_LOW);
if (IS_ERR(pcie->pcie_clk_en))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->pcie_clk_en),
"Failed to get pcie-clk-en GPIO");
pcie->minipcie_1v5_pwren = devm_gpiod_get_optional(&pdev->dev,
"minipcie-1v5-pwren", GPIOD_OUT_LOW);
if (IS_ERR(pcie->minipcie_1v5_pwren))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->minipcie_1v5_pwren),
"Failed to get minipcie-1v5-pwren GPIO");
pcie->minipcie_3v3_pwren = devm_gpiod_get_optional(&pdev->dev,
"minipcie-3v3-pwren", GPIOD_OUT_LOW);
if (IS_ERR(pcie->minipcie_3v3_pwren))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->minipcie_3v3_pwren),
"Failed to get minipcie-3v3-pwren GPIO");
pcie->minipcie_perst = devm_gpiod_get_optional(&pdev->dev,
"minipcie-perst", GPIOD_OUT_LOW);
if (IS_ERR(pcie->minipcie_perst))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->minipcie_perst),
"Failed to get minipcie-perst GPIO");
pcie->pcie_clk_pwren = devm_gpiod_get_optional(&pdev->dev,
"pcie-clk-pwren", GPIOD_OUT_LOW);
if (IS_ERR(pcie->pcie_clk_pwren))
return dev_err_probe(&pdev->dev, PTR_ERR(pcie->pcie_clk_pwren),
"Failed to get pcie-clk-pwren GPIO");
return 0;
}
static int __maybe_unused zhihe_pcie_phy_init(struct zhihe_pcie *pcie)
{
struct device *dev = pcie->pci.dev;
int ret;
pcie->phy = devm_phy_get(dev, "pcie-phy");
if (IS_ERR(pcie->phy))
return dev_err_probe(dev, PTR_ERR(pcie->phy),
"missing PHY\n");
ret = phy_init(pcie->phy);
if (ret < 0)
return ret;
dev_info(dev, "phy_init======================================================\n");
ret = phy_power_on(pcie->phy);
if (ret)
phy_exit(pcie->phy);
return ret;
}
static void __maybe_unused zhihe_pcie_phy_deinit(struct zhihe_pcie *pcie)
{
phy_exit(pcie->phy);
phy_power_off(pcie->phy);
}
static void zhihe_pcie_stop_link(struct dw_pcie *pci)
{
zhihe_pcie_ltssm_disable(pci);
}
static const struct dw_pcie_ops dw_pcie_ops = {
.stop_link = zhihe_pcie_stop_link,
};
static int zhihe_pcie_ipctrl_init(struct dw_pcie_rp *pp)
{
int rdata = 0;
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
struct zhihe_pcie *pcie = to_zhihe_pcie(pci);
/*disable ltssm*/
zhihe_pcie_ltssm_disable(pci);
/*enable rp && sata*/
if (pcie->ip_type == PCIE_X4_TYPE) {
/*cfg x4 lane*/
dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, 0x70120);
dw_pcie_writel_dbi(pci, PCIE_PORT_LANE_SKEW, 0x1c000000);
dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, 0xc020071);
dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, 0x304be);
} else {
/*cfg x1 lane*/
dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, 0x10120);
dw_pcie_writel_dbi(pci, PCIE_PORT_LANE_SKEW, 0x4000000);
dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, 0xc020071);
dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, 0x101be);
}
/*ip ctrl cfg*/
dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, GEN3_RELATED_OFF_RXEQ_RGRDLESS_RXTS |
GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL);
dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, 0x2a00); // ?
dw_pcie_writel_dbi(pci, PCIE_EXTENDED_REG0, 0x21614536);
dw_pcie_writel_dbi(pci, PCIE_EXTENDED_REG1, 0x6337451);
dw_pcie_writel_dbi(pci, PCIE_EXTENDED_REG2, 0x8553824);
dw_pcie_writel_dbi(pci, PCIE_EXTENDED_REG3, 0x47373650);
dw_pcie_writel_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE_OFF, 0x0);
dw_pcie_writel_dbi(pci, DEVICE_CONTROL_DEVICE_STATUS, 0x2130);
/*cfg Gen3*/
rdata = dw_pcie_readl_dbi(pci, LINK_CONTROL2_LINK_STATUS2_REG);
rdata &= 0xfffffff0;
rdata |= 0x3;
dw_pcie_writel_dbi(pci, LINK_CONTROL2_LINK_STATUS2_REG, rdata);
/*config space setup*/
dw_pcie_writel_dbi2(pci, PCI_BASE_ADDRESS_0, 0);
dw_pcie_writel_dbi2(pci, PCI_BASE_ADDRESS_1, 0);
dw_pcie_writel_dbi(pci, TRGT_MAP_CTRL_OFF, 0x40);
dw_pcie_writel_dbi(pci, SEC_STAT_IO_LIMIT_IO_BASE_REG, 0x4f40);
dw_pcie_writel_dbi(pci, IO_LIMIT_UPPER_IO_BASE_UPPER_REG, 0x0);
dw_pcie_writel_dbi(pci, MEM_LIMIT_MEM_BASE_REG, 0xc91fc800);
dw_pcie_writel_dbi(pci, PREF_MEM_LIMIT_PREF_MEM_BASE_REG, 0xfff0);
dw_pcie_writel_dbi(pci, PREF_BASE_UPPER_REG, 0x0);
dw_pcie_writel_dbi(pci, PREF_LIMIT_UPPER_REG, 0x0);
dw_pcie_writel_dbi(pci, TYPE1_STATUS_COMMAND_REG, 0x100007);
/*enable ltssm*/
zhihe_pcie_ltssm_enable(pci);
zhihe_pcie_wait_linkup(pcie);
return 0;
}
static const struct dw_pcie_host_ops zhihe_pcie_host_ops = {
.host_init = zhihe_pcie_ipctrl_init,
};
static int zhihe_add_pcie_port(struct zhihe_pcie *pcie,
struct platform_device *pdev)
{
struct dw_pcie *pci = &pcie->pci;
struct dw_pcie_rp *pp = &pci->pp;
struct device *dev = &pdev->dev;
int ret;
if (pcie->pcie_bat_en)
gpiod_set_value(pcie->pcie_bat_en, 1);
if (pcie->pcie_3v3_en)
gpiod_set_value(pcie->pcie_3v3_en, 1);
if (pcie->pcie_12v_en)
gpiod_set_value(pcie->pcie_12v_en, 1);
if (pcie->pcie_clk_en)
gpiod_set_value(pcie->pcie_clk_en, 1);
if (pcie->minipcie_1v5_pwren)
gpiod_set_value(pcie->minipcie_1v5_pwren, 1);
if (pcie->minipcie_3v3_pwren)
gpiod_set_value(pcie->minipcie_3v3_pwren, 1);
if (pcie->minipcie_perst)
gpiod_set_value(pcie->minipcie_perst, 1);
if (pcie->pcie_clk_pwren)
gpiod_set_value(pcie->pcie_clk_pwren, 1);
pp->irq = platform_get_irq(pdev, 0);
if (pp->irq < 0)
return pp->irq;
pp->num_vectors = MAX_MSI_IRQS;
pp->ops = &zhihe_pcie_host_ops;
ret = dw_pcie_host_init(pp);
if (ret) {
dev_err(dev, "Failed to initialize host\n");
return ret;
}
return 0;
}
static int zhihe_pcie_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct zhihe_pcie *pcie;
struct dw_pcie *pci;
int ret;
const struct zhihe_pcie_of_data *data;
enum dw_pcie_device_mode mode;
dev_info(dev, "Enter into zh_pci platform!\n");
data = of_device_get_match_data(dev);
if (!data)
goto deinit_phy;
pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
if (!pcie)
return -ENOMEM;
pci = &pcie->pci;
pci->dev = dev;
mode = pcie->mode =(enum dw_pcie_device_mode)data->mode;;
pci->ops = &dw_pcie_ops;
platform_set_drvdata(pdev, pcie);
ret = zhihe_pcie_get_resources(pdev, pcie);
if (ret)
return ret;
ret = zhihe_pcie_phy_init(pcie);
if (ret)
return ret;
switch (pcie->mode) {
case DW_PCIE_RC_TYPE:
ret = zhihe_add_pcie_port(pcie, pdev);
break;
default:
dev_err(dev, "Invalid device type %d\n", pcie->mode);
ret = -ENODEV;
break;
}
if (!ret)
return 0;
deinit_phy:
zhihe_pcie_phy_deinit(pcie);
return ret;
}
static int zhihe_pcie_remove(struct platform_device *pdev)
{
struct zhihe_pcie *pcie = platform_get_drvdata(pdev);
dw_pcie_host_deinit(&pcie->pci.pp);
return 0;
}
static const struct zhihe_pcie_of_data zhihe_pcie_rc_of_data = {
.mode = DW_PCIE_RC_TYPE,
};
static const struct of_device_id zhihe_pcie_of_match[] = {
{
.compatible = "zh,p100-pcie",
.data = &zhihe_pcie_rc_of_data,
},
{},
};
static struct platform_driver zhihe_pcie_driver = {
.driver = {
.name = "zh-pcie",
.of_match_table = zhihe_pcie_of_match,
.suppress_bind_attrs = true,
},
.probe = zhihe_pcie_probe,
.remove = zhihe_pcie_remove,
};
module_platform_driver(zhihe_pcie_driver);
MODULE_LICENSE("GPL v2");