diff --git a/drivers/phy/tegra/phy-tegra194-p2u.c b/drivers/phy/tegra/phy-tegra194-p2u.c index f49b417c9eb6..eed3054f8ea1 100644 --- a/drivers/phy/tegra/phy-tegra194-p2u.c +++ b/drivers/phy/tegra/phy-tegra194-p2u.c @@ -7,12 +7,15 @@ * Author: Vidya Sagar */ +#include #include #include #include #include #include #include +#include +#include #define P2U_CONTROL_CMN 0x74 #define P2U_CONTROL_CMN_ENABLE_L2_EXIT_RATE_CHANGE BIT(13) @@ -31,14 +34,70 @@ #define P2U_DIR_SEARCH_CTRL 0xd4 #define P2U_DIR_SEARCH_CTRL_GEN4_FINE_GRAIN_SEARCH_TWICE BIT(18) +#define P2U_RX_MARGIN_SW_INT_EN 0xe0 +#define P2U_RX_MARGIN_SW_INT_EN_READINESS BIT(0) +#define P2U_RX_MARGIN_SW_INT_EN_MARGIN_START BIT(1) +#define P2U_RX_MARGIN_SW_INT_EN_MARGIN_CHANGE BIT(2) +#define P2U_RX_MARGIN_SW_INT_EN_MARGIN_STOP BIT(3) + +#define P2U_RX_MARGIN_SW_INT 0xe4 +#define P2U_RX_MARGIN_SW_INT_MASK 0xf +#define P2U_RX_MARGIN_SW_INT_READINESS BIT(0) +#define P2U_RX_MARGIN_SW_INT_MARGIN_START BIT(1) +#define P2U_RX_MARGIN_SW_INT_MARGIN_CHANGE BIT(2) +#define P2U_RX_MARGIN_SW_INT_MARGIN_STOP BIT(3) + +#define P2U_RX_MARGIN_SW_STATUS 0xe8 +#define P2U_RX_MARGIN_SW_STATUS_MARGIN_SW_READY BIT(0) +#define P2U_RX_MARGIN_SW_STATUS_MARGIN_READY BIT(1) +#define P2U_RX_MARGIN_SW_STATUS_PHY_MARGIN_STATUS BIT(2) +#define P2U_RX_MARGIN_SW_STATUS_PHY_MARGIN_ERROR_STATUS BIT(3) + +#define P2U_RX_MARGIN_CTRL 0xec +#define P2U_RX_MARGIN_CTRL_EN BIT(0) +#define P2U_RX_MARGIN_CTRL_N_BLKS_MASK 0x7f8000 +#define P2U_RX_MARGIN_CTRL_N_BLKS_SHIFT 15 + +/* Any value between {0x80, 0xFF}, randomly selected 0x81 */ +#define N_BLKS_COUNT 0x81 + +#define P2U_RX_MARGIN_STATUS 0xf0 +#define P2U_RX_MARGIN_STATUS_ERRORS_MASK 0xffff + +#define P2U_RX_MARGIN_CONTROL 0xf4 +#define P2U_RX_MARGIN_CONTROL_START BIT(0) + +#define P2U_RX_MARGIN_CYA_CTRL 0xf8 +#define P2U_RX_MARGIN_CYA_CTRL_IND_X BIT(0) +#define P2U_RX_MARGIN_CYA_CTRL_IND_Y BIT(1) + +#define RX_MARGIN_START_CHANGE 1 +#define RX_MARGIN_STOP 2 +#define RX_MARGIN_GET_MARGIN 3 + struct tegra_p2u_of_data { bool one_dir_search; + bool lane_margin; }; struct tegra_p2u { void __iomem *base; bool skip_sz_protection_en; /* Needed to support two retimers */ struct tegra_p2u_of_data *of_data; + struct device *dev; + struct tegra_bpmp *bpmp; + u32 id; + struct work_struct rx_margin_work; + u32 next_state; + spinlock_t next_state_lock; /* lock for next_state */ +}; + +struct margin_ctrl { + u32 en:1; + u32 clr:1; + u32 x:7; + u32 y:6; + u32 n_blks:8; }; static inline void p2u_writel(struct tegra_p2u *phy, const u32 value, @@ -83,6 +142,19 @@ static int tegra_p2u_power_on(struct phy *x) p2u_writel(phy, val, P2U_DIR_SEARCH_CTRL); } + if (phy->of_data->lane_margin && phy->bpmp) { + val = P2U_RX_MARGIN_SW_INT_EN_READINESS | + P2U_RX_MARGIN_SW_INT_EN_MARGIN_START | + P2U_RX_MARGIN_SW_INT_EN_MARGIN_CHANGE | + P2U_RX_MARGIN_SW_INT_EN_MARGIN_STOP; + writel(val, phy->base + P2U_RX_MARGIN_SW_INT_EN); + + val = readl(phy->base + P2U_RX_MARGIN_CYA_CTRL); + val |= P2U_RX_MARGIN_CYA_CTRL_IND_X; + val |= P2U_RX_MARGIN_CYA_CTRL_IND_Y; + writel(val, phy->base + P2U_RX_MARGIN_CYA_CTRL); + } + return 0; } @@ -104,12 +176,204 @@ static const struct phy_ops ops = { .owner = THIS_MODULE, }; +static int set_margin_control(struct tegra_p2u *phy, u32 ctrl_data) +{ + struct tegra_bpmp_message msg; + struct mrq_uphy_response resp; + struct mrq_uphy_request req; + struct margin_ctrl ctrl; + int err; + + memcpy(&ctrl, &ctrl_data, sizeof(ctrl_data)); + + memset(&req, 0, sizeof(req)); + memset(&resp, 0, sizeof(resp)); + + req.lane = phy->id; + req.cmd = CMD_UPHY_PCIE_LANE_MARGIN_CONTROL; + req.uphy_set_margin_control.en = ctrl.en; + req.uphy_set_margin_control.clr = ctrl.clr; + req.uphy_set_margin_control.x = ctrl.x; + req.uphy_set_margin_control.y = ctrl.y; + req.uphy_set_margin_control.nblks = ctrl.n_blks; + + memset(&msg, 0, sizeof(msg)); + msg.mrq = MRQ_UPHY; + msg.tx.data = &req; + msg.tx.size = sizeof(req); + msg.rx.data = &resp; + msg.rx.size = sizeof(resp); + + err = tegra_bpmp_transfer(phy->bpmp, &msg); + if (err) + return err; + if (msg.rx.ret) + return -EINVAL; + + return 0; +} + +static int get_margin_status(struct tegra_p2u *phy, u32 *val) +{ + struct tegra_bpmp_message msg; + struct mrq_uphy_response resp; + struct mrq_uphy_request req; + int rc; + + req.lane = phy->id; + req.cmd = CMD_UPHY_PCIE_LANE_MARGIN_STATUS; + + memset(&msg, 0, sizeof(msg)); + msg.mrq = MRQ_UPHY; + msg.tx.data = &req; + msg.tx.size = sizeof(req); + msg.rx.data = &resp; + msg.rx.size = sizeof(resp); + + rc = tegra_bpmp_transfer(phy->bpmp, &msg); + if (rc) + return rc; + if (msg.rx.ret) + return -EINVAL; + + *val = resp.uphy_get_margin_status.status; + + return 0; +} + +static void rx_margin_work_fn(struct work_struct *work) +{ + struct tegra_p2u *phy = container_of(work, struct tegra_p2u, + rx_margin_work); + struct device *dev = phy->dev; + unsigned long flags; + u32 val; + int ret; + u8 state; + + do { + spin_lock_irqsave(&phy->next_state_lock, flags); + state = phy->next_state; + spin_unlock_irqrestore(&phy->next_state_lock, flags); + switch (state) { + case RX_MARGIN_START_CHANGE: + case RX_MARGIN_STOP: + val = readl(phy->base + P2U_RX_MARGIN_CTRL); + ret = set_margin_control(phy, val); + if (ret) { + dev_err(dev, "MARGIN_SET err: %d\n", ret); + break; + } + val = readl(phy->base + P2U_RX_MARGIN_SW_STATUS); + val |= P2U_RX_MARGIN_SW_STATUS_MARGIN_SW_READY; + val |= P2U_RX_MARGIN_SW_STATUS_MARGIN_READY; + val |= P2U_RX_MARGIN_SW_STATUS_PHY_MARGIN_STATUS; + val |= P2U_RX_MARGIN_SW_STATUS_PHY_MARGIN_ERROR_STATUS; + writel(val, phy->base + P2U_RX_MARGIN_SW_STATUS); + + usleep_range(10, 11); + + if (state != RX_MARGIN_STOP) { + spin_lock_irqsave(&phy->next_state_lock, flags); + phy->next_state = RX_MARGIN_GET_MARGIN; + spin_unlock_irqrestore(&phy->next_state_lock, + flags); + continue; + } + fallthrough; + + case RX_MARGIN_GET_MARGIN: + if (state != RX_MARGIN_STOP) { + ret = get_margin_status(phy, &val); + if (ret) { + dev_err(dev, "MARGIN_GET err: %d\n", + ret); + break; + } + writel(val & P2U_RX_MARGIN_STATUS_ERRORS_MASK, + phy->base + P2U_RX_MARGIN_STATUS); + } + val = readl(phy->base + P2U_RX_MARGIN_SW_STATUS); + val |= P2U_RX_MARGIN_SW_STATUS_MARGIN_SW_READY; + val |= P2U_RX_MARGIN_SW_STATUS_MARGIN_READY; + val &= ~P2U_RX_MARGIN_SW_STATUS_PHY_MARGIN_STATUS; + val |= P2U_RX_MARGIN_SW_STATUS_PHY_MARGIN_ERROR_STATUS; + writel(val, phy->base + P2U_RX_MARGIN_SW_STATUS); + + if (state != RX_MARGIN_STOP) { + msleep(20); + continue; + } else { + return; + } + break; + + default: + dev_err(dev, "Invalid margin state\n"); + return; + }; + } while (1); +} + +static irqreturn_t tegra_p2u_irq_handler(int irq, void *arg) +{ + struct tegra_p2u *phy = (struct tegra_p2u *)arg; + unsigned long flags; + u32 val = 0; + + val = readl(phy->base + P2U_RX_MARGIN_SW_INT); + writel(val, phy->base + P2U_RX_MARGIN_SW_INT); + switch (val & P2U_RX_MARGIN_SW_INT_MASK) { + case P2U_RX_MARGIN_SW_INT_READINESS: + dev_dbg(phy->dev, "Rx_Margin_intr : READINESS\n"); + val = readl(phy->base + P2U_RX_MARGIN_SW_STATUS); + val |= P2U_RX_MARGIN_SW_STATUS_MARGIN_SW_READY; + val |= P2U_RX_MARGIN_SW_STATUS_MARGIN_READY; + writel(val, phy->base + P2U_RX_MARGIN_SW_STATUS); + /* Write N_BLKS with any value between {0x80, 0xFF} */ + val = readl(phy->base + P2U_RX_MARGIN_CTRL); + val &= P2U_RX_MARGIN_CTRL_N_BLKS_MASK; + val |= (N_BLKS_COUNT << P2U_RX_MARGIN_CTRL_N_BLKS_SHIFT); + writel(val, phy->base + P2U_RX_MARGIN_CTRL); + break; + + case P2U_RX_MARGIN_SW_INT_MARGIN_STOP: + dev_dbg(phy->dev, "Rx_Margin_intr : MARGIN_STOP\n"); + spin_lock_irqsave(&phy->next_state_lock, flags); + phy->next_state = RX_MARGIN_STOP; + spin_unlock_irqrestore(&phy->next_state_lock, flags); + break; + + case P2U_RX_MARGIN_SW_INT_MARGIN_CHANGE: + case (P2U_RX_MARGIN_SW_INT_MARGIN_CHANGE | + P2U_RX_MARGIN_SW_INT_MARGIN_START): + dev_dbg(phy->dev, "Rx_Margin_intr : MARGIN_CHANGE\n"); + spin_lock_irqsave(&phy->next_state_lock, flags); + phy->next_state = RX_MARGIN_START_CHANGE; + spin_unlock_irqrestore(&phy->next_state_lock, flags); + fallthrough; + + case P2U_RX_MARGIN_SW_INT_MARGIN_START: + dev_dbg(phy->dev, "Rx_Margin_intr : MARGIN_START\n"); + schedule_work(&phy->rx_margin_work); + break; + + default: + dev_err(phy->dev, "INVALID Rx_Margin_intr : 0x%x\n", val); + break; + } + + return IRQ_HANDLED; +} + static int tegra_p2u_probe(struct platform_device *pdev) { struct phy_provider *phy_provider; struct device *dev = &pdev->dev; struct phy *generic_phy; struct tegra_p2u *phy; + int irq = -ENODEV; + int ret; phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); if (!phy) @@ -120,6 +384,9 @@ static int tegra_p2u_probe(struct platform_device *pdev) if (!phy->of_data) return -EINVAL; + phy->dev = dev; + platform_set_drvdata(pdev, phy); + phy->base = devm_platform_ioremap_resource_byname(pdev, "ctl"); if (IS_ERR(phy->base)) return PTR_ERR(phy->base); @@ -140,15 +407,55 @@ static int tegra_p2u_probe(struct platform_device *pdev) if (IS_ERR(phy_provider)) return PTR_ERR(phy_provider); + if (phy->of_data->lane_margin) + irq = platform_get_irq_byname(pdev, "intr"); + + if (irq < 0) { + dev_warn(dev, "Device tree update required to enable lane margining\n"); + } else { + spin_lock_init(&phy->next_state_lock); + INIT_WORK(&phy->rx_margin_work, rx_margin_work_fn); + + ret = devm_request_irq(&pdev->dev, irq, tegra_p2u_irq_handler, + 0, "tegra-p2u-intr", phy); + if (ret) { + dev_err(dev, "failed to request \"intr\" irq\n"); + return ret; + } + + ret = of_property_read_u32_index(dev->of_node, "nvidia,bpmp", + 1, &phy->id); + if (ret) { + dev_err(dev, "Failed to read P2U id: %d\n", ret); + return ret; + } + + phy->bpmp = tegra_bpmp_get(dev); + if (IS_ERR(phy->bpmp)) + return PTR_ERR(phy->bpmp); + } + + return 0; +} + +static int tegra_p2u_remove(struct platform_device *pdev) +{ + struct tegra_p2u *phy = platform_get_drvdata(pdev); + + if (phy->bpmp) + tegra_bpmp_put(phy->bpmp); + return 0; } static const struct tegra_p2u_of_data tegra194_p2u_of_data = { .one_dir_search = false, + .lane_margin = false, }; static const struct tegra_p2u_of_data tegra234_p2u_of_data = { .one_dir_search = true, + .lane_margin = true, }; static const struct of_device_id tegra_p2u_id_table[] = { @@ -166,6 +473,7 @@ MODULE_DEVICE_TABLE(of, tegra_p2u_id_table); static struct platform_driver tegra_p2u_driver = { .probe = tegra_p2u_probe, + .remove = tegra_p2u_remove, .driver = { .name = "tegra194-p2u", .of_match_table = tegra_p2u_id_table,