mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-05 11:00:15 +00:00
460 lines
11 KiB
C
460 lines
11 KiB
C
|
// SPDX-License-Identifier: GPL-2.0+
|
||
|
/*
|
||
|
* Amlogic DesignWare based PCIe host controller driver
|
||
|
*
|
||
|
* Copyright (c) 2021 BayLibre, SAS
|
||
|
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||
|
*
|
||
|
* Based on pcie_dw_rockchip.c
|
||
|
* Copyright (c) 2021 Rockchip, Inc.
|
||
|
*/
|
||
|
|
||
|
#include <common.h>
|
||
|
#include <clk.h>
|
||
|
#include <dm.h>
|
||
|
#include <generic-phy.h>
|
||
|
#include <pci.h>
|
||
|
#include <power-domain.h>
|
||
|
#include <reset.h>
|
||
|
#include <syscon.h>
|
||
|
#include <asm/global_data.h>
|
||
|
#include <asm/io.h>
|
||
|
#include <asm-generic/gpio.h>
|
||
|
#include <dm/device_compat.h>
|
||
|
#include <linux/iopoll.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/log2.h>
|
||
|
#include <linux/bitfield.h>
|
||
|
|
||
|
#include "pcie_dw_common.h"
|
||
|
|
||
|
DECLARE_GLOBAL_DATA_PTR;
|
||
|
|
||
|
/**
|
||
|
* struct meson_pcie - Amlogic Meson DW PCIe controller state
|
||
|
*
|
||
|
* @pci: The common PCIe DW structure
|
||
|
* @meson_cfg_base: The base address of vendor regs
|
||
|
* @phy
|
||
|
* @clk_port
|
||
|
* @clk_general
|
||
|
* @clk_pclk
|
||
|
* @rsts
|
||
|
* @rst_gpio: The #PERST signal for slot
|
||
|
*/
|
||
|
struct meson_pcie {
|
||
|
/* Must be first member of the struct */
|
||
|
struct pcie_dw dw;
|
||
|
void *meson_cfg_base;
|
||
|
struct phy phy;
|
||
|
struct clk clk_port;
|
||
|
struct clk clk_general;
|
||
|
struct clk clk_pclk;
|
||
|
struct reset_ctl_bulk rsts;
|
||
|
struct gpio_desc rst_gpio;
|
||
|
};
|
||
|
|
||
|
#define PCI_EXP_DEVCTL_PAYLOAD 0x00e0 /* Max_Payload_Size */
|
||
|
|
||
|
#define PCIE_CAP_MAX_PAYLOAD_SIZE(x) ((x) << 5)
|
||
|
#define PCIE_CAP_MAX_READ_REQ_SIZE(x) ((x) << 12)
|
||
|
|
||
|
/* PCIe specific config registers */
|
||
|
#define PCIE_CFG0 0x0
|
||
|
#define APP_LTSSM_ENABLE BIT(7)
|
||
|
|
||
|
#define PCIE_CFG_STATUS12 0x30
|
||
|
#define IS_SMLH_LINK_UP(x) ((x) & (1 << 6))
|
||
|
#define IS_RDLH_LINK_UP(x) ((x) & (1 << 16))
|
||
|
#define IS_LTSSM_UP(x) ((((x) >> 10) & 0x1f) == 0x11)
|
||
|
|
||
|
#define PCIE_CFG_STATUS17 0x44
|
||
|
#define PM_CURRENT_STATE(x) (((x) >> 7) & 0x1)
|
||
|
|
||
|
#define WAIT_LINKUP_TIMEOUT 4000
|
||
|
#define PORT_CLK_RATE 100000000UL
|
||
|
#define MAX_PAYLOAD_SIZE 256
|
||
|
#define MAX_READ_REQ_SIZE 256
|
||
|
#define PCIE_RESET_DELAY 500
|
||
|
#define PCIE_SHARED_RESET 1
|
||
|
#define PCIE_NORMAL_RESET 0
|
||
|
|
||
|
enum pcie_data_rate {
|
||
|
PCIE_GEN1,
|
||
|
PCIE_GEN2,
|
||
|
PCIE_GEN3,
|
||
|
PCIE_GEN4
|
||
|
};
|
||
|
|
||
|
/* Parameters for the waiting for #perst signal */
|
||
|
#define PERST_WAIT_US 1000000
|
||
|
|
||
|
static inline u32 meson_cfg_readl(struct meson_pcie *priv, u32 reg)
|
||
|
{
|
||
|
return readl(priv->meson_cfg_base + reg);
|
||
|
}
|
||
|
|
||
|
static inline void meson_cfg_writel(struct meson_pcie *priv, u32 val, u32 reg)
|
||
|
{
|
||
|
writel(val, priv->meson_cfg_base + reg);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* meson_pcie_configure() - Configure link
|
||
|
*
|
||
|
* @meson_pcie: Pointer to the PCI controller state
|
||
|
*
|
||
|
* Configure the link mode and width
|
||
|
*/
|
||
|
static void meson_pcie_configure(struct meson_pcie *priv)
|
||
|
{
|
||
|
u32 val;
|
||
|
|
||
|
dw_pcie_dbi_write_enable(&priv->dw, true);
|
||
|
|
||
|
val = readl(priv->dw.dbi_base + PCIE_PORT_LINK_CONTROL);
|
||
|
val &= ~PORT_LINK_FAST_LINK_MODE;
|
||
|
val |= PORT_LINK_DLL_LINK_EN;
|
||
|
val &= ~PORT_LINK_MODE_MASK;
|
||
|
val |= PORT_LINK_MODE_1_LANES;
|
||
|
writel(val, priv->dw.dbi_base + PCIE_PORT_LINK_CONTROL);
|
||
|
|
||
|
val = readl(priv->dw.dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL);
|
||
|
val &= ~PORT_LOGIC_LINK_WIDTH_MASK;
|
||
|
val |= PORT_LOGIC_LINK_WIDTH_1_LANES;
|
||
|
writel(val, priv->dw.dbi_base + PCIE_LINK_WIDTH_SPEED_CONTROL);
|
||
|
|
||
|
dw_pcie_dbi_write_enable(&priv->dw, false);
|
||
|
}
|
||
|
|
||
|
static inline void meson_pcie_enable_ltssm(struct meson_pcie *priv)
|
||
|
{
|
||
|
u32 val;
|
||
|
|
||
|
val = meson_cfg_readl(priv, PCIE_CFG0);
|
||
|
val |= APP_LTSSM_ENABLE;
|
||
|
meson_cfg_writel(priv, val, PCIE_CFG0);
|
||
|
}
|
||
|
|
||
|
static int meson_pcie_wait_link_up(struct meson_pcie *priv)
|
||
|
{
|
||
|
u32 speed_okay = 0;
|
||
|
u32 cnt = 0;
|
||
|
u32 state12, state17, smlh_up, ltssm_up, rdlh_up;
|
||
|
|
||
|
do {
|
||
|
state12 = meson_cfg_readl(priv, PCIE_CFG_STATUS12);
|
||
|
state17 = meson_cfg_readl(priv, PCIE_CFG_STATUS17);
|
||
|
smlh_up = IS_SMLH_LINK_UP(state12);
|
||
|
rdlh_up = IS_RDLH_LINK_UP(state12);
|
||
|
ltssm_up = IS_LTSSM_UP(state12);
|
||
|
|
||
|
if (PM_CURRENT_STATE(state17) < PCIE_GEN3)
|
||
|
speed_okay = 1;
|
||
|
|
||
|
if (smlh_up)
|
||
|
debug("%s: smlh_link_up is on\n", __func__);
|
||
|
if (rdlh_up)
|
||
|
debug("%s: rdlh_link_up is on\n", __func__);
|
||
|
if (ltssm_up)
|
||
|
debug("%s: ltssm_up is on\n", __func__);
|
||
|
if (speed_okay)
|
||
|
debug("%s: speed_okay\n", __func__);
|
||
|
|
||
|
if (smlh_up && rdlh_up && ltssm_up && speed_okay)
|
||
|
return 0;
|
||
|
|
||
|
cnt++;
|
||
|
|
||
|
udelay(10);
|
||
|
} while (cnt < WAIT_LINKUP_TIMEOUT);
|
||
|
|
||
|
printf("%s: error: wait linkup timeout\n", __func__);
|
||
|
return -EIO;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* meson_pcie_link_up() - Wait for the link to come up
|
||
|
*
|
||
|
* @meson_pcie: Pointer to the PCI controller state
|
||
|
* @cap_speed: Desired link speed
|
||
|
*
|
||
|
* Return: 1 (true) for active line and negative (false) for no link (timeout)
|
||
|
*/
|
||
|
static int meson_pcie_link_up(struct meson_pcie *priv, u32 cap_speed)
|
||
|
{
|
||
|
/* DW link configurations */
|
||
|
meson_pcie_configure(priv);
|
||
|
|
||
|
/* Reset the device */
|
||
|
if (dm_gpio_is_valid(&priv->rst_gpio)) {
|
||
|
dm_gpio_set_value(&priv->rst_gpio, 1);
|
||
|
/*
|
||
|
* Minimal is 100ms from spec but we see
|
||
|
* some wired devices need much more, such as 600ms.
|
||
|
* Add a enough delay to cover all cases.
|
||
|
*/
|
||
|
udelay(PERST_WAIT_US);
|
||
|
dm_gpio_set_value(&priv->rst_gpio, 0);
|
||
|
}
|
||
|
|
||
|
/* Enable LTSSM */
|
||
|
meson_pcie_enable_ltssm(priv);
|
||
|
|
||
|
return meson_pcie_wait_link_up(priv);
|
||
|
}
|
||
|
|
||
|
static int meson_size_to_payload(int size)
|
||
|
{
|
||
|
/*
|
||
|
* dwc supports 2^(val+7) payload size, which val is 0~5 default to 1.
|
||
|
* So if input size is not 2^order alignment or less than 2^7 or bigger
|
||
|
* than 2^12, just set to default size 2^(1+7).
|
||
|
*/
|
||
|
if (!is_power_of_2(size) || size < 128 || size > 4096) {
|
||
|
debug("%s: payload size %d, set to default 256\n", __func__, size);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return fls(size) - 8;
|
||
|
}
|
||
|
|
||
|
static void meson_set_max_payload(struct meson_pcie *priv, int size)
|
||
|
{
|
||
|
u32 val;
|
||
|
u16 offset = dm_pci_find_capability(priv->dw.dev, PCI_CAP_ID_EXP);
|
||
|
int max_payload_size = meson_size_to_payload(size);
|
||
|
|
||
|
dw_pcie_dbi_write_enable(&priv->dw, true);
|
||
|
|
||
|
val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
|
||
|
val &= ~PCI_EXP_DEVCTL_PAYLOAD;
|
||
|
writel(val, priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
|
||
|
|
||
|
val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
|
||
|
val |= PCIE_CAP_MAX_PAYLOAD_SIZE(max_payload_size);
|
||
|
writel(val, priv->dw.dbi_base + PCI_EXP_DEVCTL);
|
||
|
|
||
|
dw_pcie_dbi_write_enable(&priv->dw, false);
|
||
|
}
|
||
|
|
||
|
static void meson_set_max_rd_req_size(struct meson_pcie *priv, int size)
|
||
|
{
|
||
|
u32 val;
|
||
|
u16 offset = dm_pci_find_capability(priv->dw.dev, PCI_CAP_ID_EXP);
|
||
|
int max_rd_req_size = meson_size_to_payload(size);
|
||
|
|
||
|
dw_pcie_dbi_write_enable(&priv->dw, true);
|
||
|
|
||
|
val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
|
||
|
val &= ~PCI_EXP_DEVCTL_PAYLOAD;
|
||
|
writel(val, priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
|
||
|
|
||
|
val = readl(priv->dw.dbi_base + offset + PCI_EXP_DEVCTL);
|
||
|
val |= PCIE_CAP_MAX_READ_REQ_SIZE(max_rd_req_size);
|
||
|
writel(val, priv->dw.dbi_base + PCI_EXP_DEVCTL);
|
||
|
|
||
|
dw_pcie_dbi_write_enable(&priv->dw, false);
|
||
|
}
|
||
|
|
||
|
static int meson_pcie_init_port(struct udevice *dev)
|
||
|
{
|
||
|
int ret;
|
||
|
struct meson_pcie *priv = dev_get_priv(dev);
|
||
|
|
||
|
ret = generic_phy_init(&priv->phy);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to init phy (ret=%d)\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = generic_phy_power_on(&priv->phy);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to power on phy (ret=%d)\n", ret);
|
||
|
goto err_exit_phy;
|
||
|
}
|
||
|
|
||
|
ret = generic_phy_reset(&priv->phy);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to reset phy (ret=%d)\n", ret);
|
||
|
goto err_exit_phy;
|
||
|
}
|
||
|
|
||
|
ret = reset_assert_bulk(&priv->rsts);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to assert resets (ret=%d)\n", ret);
|
||
|
goto err_power_off_phy;
|
||
|
}
|
||
|
|
||
|
udelay(PCIE_RESET_DELAY);
|
||
|
|
||
|
ret = reset_deassert_bulk(&priv->rsts);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to deassert resets (ret=%d)\n", ret);
|
||
|
goto err_power_off_phy;
|
||
|
}
|
||
|
|
||
|
udelay(PCIE_RESET_DELAY);
|
||
|
|
||
|
ret = clk_set_rate(&priv->clk_port, PORT_CLK_RATE);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to set port clk rate (ret=%d)\n", ret);
|
||
|
goto err_deassert_bulk;
|
||
|
}
|
||
|
|
||
|
ret = clk_enable(&priv->clk_general);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to enable clk general (ret=%d)\n", ret);
|
||
|
goto err_deassert_bulk;
|
||
|
}
|
||
|
|
||
|
ret = clk_enable(&priv->clk_pclk);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to enable pclk (ret=%d)\n", ret);
|
||
|
goto err_deassert_bulk;
|
||
|
}
|
||
|
|
||
|
meson_set_max_payload(priv, MAX_PAYLOAD_SIZE);
|
||
|
meson_set_max_rd_req_size(priv, MAX_READ_REQ_SIZE);
|
||
|
|
||
|
pcie_dw_setup_host(&priv->dw);
|
||
|
|
||
|
ret = meson_pcie_link_up(priv, LINK_SPEED_GEN_2);
|
||
|
if (ret < 0)
|
||
|
goto err_link_up;
|
||
|
|
||
|
return 0;
|
||
|
err_link_up:
|
||
|
clk_disable(&priv->clk_port);
|
||
|
clk_disable(&priv->clk_general);
|
||
|
clk_disable(&priv->clk_pclk);
|
||
|
err_deassert_bulk:
|
||
|
reset_assert_bulk(&priv->rsts);
|
||
|
err_power_off_phy:
|
||
|
generic_phy_power_off(&priv->phy);
|
||
|
err_exit_phy:
|
||
|
generic_phy_exit(&priv->phy);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int meson_pcie_parse_dt(struct udevice *dev)
|
||
|
{
|
||
|
struct meson_pcie *priv = dev_get_priv(dev);
|
||
|
int ret;
|
||
|
|
||
|
priv->dw.dbi_base = (void *)dev_read_addr_index(dev, 0);
|
||
|
if (!priv->dw.dbi_base)
|
||
|
return -ENODEV;
|
||
|
|
||
|
dev_dbg(dev, "ELBI address is 0x%p\n", priv->dw.dbi_base);
|
||
|
|
||
|
priv->meson_cfg_base = (void *)dev_read_addr_index(dev, 1);
|
||
|
if (!priv->meson_cfg_base)
|
||
|
return -ENODEV;
|
||
|
|
||
|
dev_dbg(dev, "CFG address is 0x%p\n", priv->meson_cfg_base);
|
||
|
|
||
|
ret = gpio_request_by_name(dev, "reset-gpios", 0,
|
||
|
&priv->rst_gpio, GPIOD_IS_OUT);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to find reset-gpios property\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = reset_get_bulk(dev, &priv->rsts);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Can't get reset: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = clk_get_by_name(dev, "port", &priv->clk_port);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Can't get port clock: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = clk_get_by_name(dev, "general", &priv->clk_general);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Can't get port clock: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = clk_get_by_name(dev, "pclk", &priv->clk_pclk);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "Can't get port clock: %d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = generic_phy_get_by_index(dev, 0, &priv->phy);
|
||
|
if (ret) {
|
||
|
dev_err(dev, "failed to get pcie phy (ret=%d)\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* meson_pcie_probe() - Probe the PCIe bus for active link
|
||
|
*
|
||
|
* @dev: A pointer to the device being operated on
|
||
|
*
|
||
|
* Probe for an active link on the PCIe bus and configure the controller
|
||
|
* to enable this port.
|
||
|
*
|
||
|
* Return: 0 on success, else -ENODEV
|
||
|
*/
|
||
|
static int meson_pcie_probe(struct udevice *dev)
|
||
|
{
|
||
|
struct meson_pcie *priv = dev_get_priv(dev);
|
||
|
struct udevice *ctlr = pci_get_controller(dev);
|
||
|
struct pci_controller *hose = dev_get_uclass_priv(ctlr);
|
||
|
int ret = 0;
|
||
|
|
||
|
priv->dw.first_busno = dev_seq(dev);
|
||
|
priv->dw.dev = dev;
|
||
|
|
||
|
ret = meson_pcie_parse_dt(dev);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = meson_pcie_init_port(dev);
|
||
|
if (ret) {
|
||
|
dm_gpio_free(dev, &priv->rst_gpio);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
printf("PCIE-%d: Link up (Gen%d-x%d, Bus%d)\n",
|
||
|
dev_seq(dev), pcie_dw_get_link_speed(&priv->dw),
|
||
|
pcie_dw_get_link_width(&priv->dw),
|
||
|
hose->first_busno);
|
||
|
|
||
|
return pcie_dw_prog_outbound_atu_unroll(&priv->dw,
|
||
|
PCIE_ATU_REGION_INDEX0,
|
||
|
PCIE_ATU_TYPE_MEM,
|
||
|
priv->dw.mem.phys_start,
|
||
|
priv->dw.mem.bus_start,
|
||
|
priv->dw.mem.size);
|
||
|
}
|
||
|
|
||
|
static const struct dm_pci_ops meson_pcie_ops = {
|
||
|
.read_config = pcie_dw_read_config,
|
||
|
.write_config = pcie_dw_write_config,
|
||
|
};
|
||
|
|
||
|
static const struct udevice_id meson_pcie_ids[] = {
|
||
|
{ .compatible = "amlogic,axg-pcie" },
|
||
|
{ .compatible = "amlogic,g12a-pcie" },
|
||
|
{ }
|
||
|
};
|
||
|
|
||
|
U_BOOT_DRIVER(meson_dw_pcie) = {
|
||
|
.name = "pcie_dw_meson",
|
||
|
.id = UCLASS_PCI,
|
||
|
.of_match = meson_pcie_ids,
|
||
|
.ops = &meson_pcie_ops,
|
||
|
.probe = meson_pcie_probe,
|
||
|
.priv_auto = sizeof(struct meson_pcie),
|
||
|
};
|