162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * P2U (PIPE to UPHY) driver for Tegra T194 SoC 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2019-2022 NVIDIA Corporation. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Vidya Sagar <vidyas@nvidia.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/err.h> 1162306a36Sopenharmony_ci#include <linux/io.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/phy/phy.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define P2U_CONTROL_CMN 0x74 1862306a36Sopenharmony_ci#define P2U_CONTROL_CMN_ENABLE_L2_EXIT_RATE_CHANGE BIT(13) 1962306a36Sopenharmony_ci#define P2U_CONTROL_CMN_SKP_SIZE_PROTECTION_EN BIT(20) 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define P2U_PERIODIC_EQ_CTRL_GEN3 0xc0 2262306a36Sopenharmony_ci#define P2U_PERIODIC_EQ_CTRL_GEN3_PERIODIC_EQ_EN BIT(0) 2362306a36Sopenharmony_ci#define P2U_PERIODIC_EQ_CTRL_GEN3_INIT_PRESET_EQ_TRAIN_EN BIT(1) 2462306a36Sopenharmony_ci#define P2U_PERIODIC_EQ_CTRL_GEN4 0xc4 2562306a36Sopenharmony_ci#define P2U_PERIODIC_EQ_CTRL_GEN4_INIT_PRESET_EQ_TRAIN_EN BIT(1) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define P2U_RX_DEBOUNCE_TIME 0xa4 2862306a36Sopenharmony_ci#define P2U_RX_DEBOUNCE_TIME_DEBOUNCE_TIMER_MASK 0xffff 2962306a36Sopenharmony_ci#define P2U_RX_DEBOUNCE_TIME_DEBOUNCE_TIMER_VAL 160 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define P2U_DIR_SEARCH_CTRL 0xd4 3262306a36Sopenharmony_ci#define P2U_DIR_SEARCH_CTRL_GEN4_FINE_GRAIN_SEARCH_TWICE BIT(18) 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistruct tegra_p2u_of_data { 3562306a36Sopenharmony_ci bool one_dir_search; 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistruct tegra_p2u { 3962306a36Sopenharmony_ci void __iomem *base; 4062306a36Sopenharmony_ci bool skip_sz_protection_en; /* Needed to support two retimers */ 4162306a36Sopenharmony_ci struct tegra_p2u_of_data *of_data; 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic inline void p2u_writel(struct tegra_p2u *phy, const u32 value, 4562306a36Sopenharmony_ci const u32 reg) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci writel_relaxed(value, phy->base + reg); 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic inline u32 p2u_readl(struct tegra_p2u *phy, const u32 reg) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci return readl_relaxed(phy->base + reg); 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic int tegra_p2u_power_on(struct phy *x) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci struct tegra_p2u *phy = phy_get_drvdata(x); 5862306a36Sopenharmony_ci u32 val; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci if (phy->skip_sz_protection_en) { 6162306a36Sopenharmony_ci val = p2u_readl(phy, P2U_CONTROL_CMN); 6262306a36Sopenharmony_ci val |= P2U_CONTROL_CMN_SKP_SIZE_PROTECTION_EN; 6362306a36Sopenharmony_ci p2u_writel(phy, val, P2U_CONTROL_CMN); 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci val = p2u_readl(phy, P2U_PERIODIC_EQ_CTRL_GEN3); 6762306a36Sopenharmony_ci val &= ~P2U_PERIODIC_EQ_CTRL_GEN3_PERIODIC_EQ_EN; 6862306a36Sopenharmony_ci val |= P2U_PERIODIC_EQ_CTRL_GEN3_INIT_PRESET_EQ_TRAIN_EN; 6962306a36Sopenharmony_ci p2u_writel(phy, val, P2U_PERIODIC_EQ_CTRL_GEN3); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci val = p2u_readl(phy, P2U_PERIODIC_EQ_CTRL_GEN4); 7262306a36Sopenharmony_ci val |= P2U_PERIODIC_EQ_CTRL_GEN4_INIT_PRESET_EQ_TRAIN_EN; 7362306a36Sopenharmony_ci p2u_writel(phy, val, P2U_PERIODIC_EQ_CTRL_GEN4); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci val = p2u_readl(phy, P2U_RX_DEBOUNCE_TIME); 7662306a36Sopenharmony_ci val &= ~P2U_RX_DEBOUNCE_TIME_DEBOUNCE_TIMER_MASK; 7762306a36Sopenharmony_ci val |= P2U_RX_DEBOUNCE_TIME_DEBOUNCE_TIMER_VAL; 7862306a36Sopenharmony_ci p2u_writel(phy, val, P2U_RX_DEBOUNCE_TIME); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (phy->of_data->one_dir_search) { 8162306a36Sopenharmony_ci val = p2u_readl(phy, P2U_DIR_SEARCH_CTRL); 8262306a36Sopenharmony_ci val &= ~P2U_DIR_SEARCH_CTRL_GEN4_FINE_GRAIN_SEARCH_TWICE; 8362306a36Sopenharmony_ci p2u_writel(phy, val, P2U_DIR_SEARCH_CTRL); 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci return 0; 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int tegra_p2u_calibrate(struct phy *x) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct tegra_p2u *phy = phy_get_drvdata(x); 9262306a36Sopenharmony_ci u32 val; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci val = p2u_readl(phy, P2U_CONTROL_CMN); 9562306a36Sopenharmony_ci val |= P2U_CONTROL_CMN_ENABLE_L2_EXIT_RATE_CHANGE; 9662306a36Sopenharmony_ci p2u_writel(phy, val, P2U_CONTROL_CMN); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci return 0; 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic const struct phy_ops ops = { 10262306a36Sopenharmony_ci .power_on = tegra_p2u_power_on, 10362306a36Sopenharmony_ci .calibrate = tegra_p2u_calibrate, 10462306a36Sopenharmony_ci .owner = THIS_MODULE, 10562306a36Sopenharmony_ci}; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic int tegra_p2u_probe(struct platform_device *pdev) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct phy_provider *phy_provider; 11062306a36Sopenharmony_ci struct device *dev = &pdev->dev; 11162306a36Sopenharmony_ci struct phy *generic_phy; 11262306a36Sopenharmony_ci struct tegra_p2u *phy; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); 11562306a36Sopenharmony_ci if (!phy) 11662306a36Sopenharmony_ci return -ENOMEM; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci phy->of_data = 11962306a36Sopenharmony_ci (struct tegra_p2u_of_data *)of_device_get_match_data(dev); 12062306a36Sopenharmony_ci if (!phy->of_data) 12162306a36Sopenharmony_ci return -EINVAL; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci phy->base = devm_platform_ioremap_resource_byname(pdev, "ctl"); 12462306a36Sopenharmony_ci if (IS_ERR(phy->base)) 12562306a36Sopenharmony_ci return PTR_ERR(phy->base); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci phy->skip_sz_protection_en = 12862306a36Sopenharmony_ci of_property_read_bool(dev->of_node, 12962306a36Sopenharmony_ci "nvidia,skip-sz-protect-en"); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci platform_set_drvdata(pdev, phy); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci generic_phy = devm_phy_create(dev, NULL, &ops); 13462306a36Sopenharmony_ci if (IS_ERR(generic_phy)) 13562306a36Sopenharmony_ci return PTR_ERR(generic_phy); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci phy_set_drvdata(generic_phy, phy); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); 14062306a36Sopenharmony_ci if (IS_ERR(phy_provider)) 14162306a36Sopenharmony_ci return PTR_ERR(phy_provider); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return 0; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic const struct tegra_p2u_of_data tegra194_p2u_of_data = { 14762306a36Sopenharmony_ci .one_dir_search = false, 14862306a36Sopenharmony_ci}; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic const struct tegra_p2u_of_data tegra234_p2u_of_data = { 15162306a36Sopenharmony_ci .one_dir_search = true, 15262306a36Sopenharmony_ci}; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic const struct of_device_id tegra_p2u_id_table[] = { 15562306a36Sopenharmony_ci { 15662306a36Sopenharmony_ci .compatible = "nvidia,tegra194-p2u", 15762306a36Sopenharmony_ci .data = &tegra194_p2u_of_data, 15862306a36Sopenharmony_ci }, 15962306a36Sopenharmony_ci { 16062306a36Sopenharmony_ci .compatible = "nvidia,tegra234-p2u", 16162306a36Sopenharmony_ci .data = &tegra234_p2u_of_data, 16262306a36Sopenharmony_ci }, 16362306a36Sopenharmony_ci {} 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, tegra_p2u_id_table); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic struct platform_driver tegra_p2u_driver = { 16862306a36Sopenharmony_ci .probe = tegra_p2u_probe, 16962306a36Sopenharmony_ci .driver = { 17062306a36Sopenharmony_ci .name = "tegra194-p2u", 17162306a36Sopenharmony_ci .of_match_table = tegra_p2u_id_table, 17262306a36Sopenharmony_ci }, 17362306a36Sopenharmony_ci}; 17462306a36Sopenharmony_cimodule_platform_driver(tegra_p2u_driver); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ciMODULE_AUTHOR("Vidya Sagar <vidyas@nvidia.com>"); 17762306a36Sopenharmony_ciMODULE_DESCRIPTION("NVIDIA Tegra194 PIPE2UPHY PHY driver"); 17862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 179