| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2021 ASR Ltd |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <linux/iopoll.h> |
| #include <linux/module.h> |
| #include <linux/mfd/syscon.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_device.h> |
| #include <linux/phy/phy.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/pci.h> |
| #include <linux/cputype.h> |
| |
| #define PUPHY_CLK_CFG 0x408 |
| #define PUPHY_MODE_CFG 0x40c |
| #define PUPHY_CAL_REG 0x420 |
| #define PUPHY_PU_SEL 0x440 |
| #define PUPHY_PLL_REG1 0x448 |
| #define PUPHY_PLL_REG2 0x44c |
| #define PUPHY_RX_REG1 0x450 |
| #define PUPHY_RX_REG2 0x454 |
| |
| #define PUPHY_TX_REG 0x464 |
| #define PUPHY_READONLY_REG 0x484 |
| #define FALCON_NR_PCIE_PHYS 1 |
| |
| struct falcon_pcie_phy { |
| struct device *dev; |
| void __iomem *base; |
| int nphys; |
| |
| struct phy_pcie_instance { |
| struct phy *phy; |
| u32 index; |
| void __iomem *phy_base; |
| } phys[FALCON_NR_PCIE_PHYS]; |
| }; |
| |
| static int falcon_pcie_phy_init(struct phy *phy) |
| { |
| struct phy_pcie_instance *inst = phy_get_drvdata(phy); |
| u32 val; |
| int err; |
| #ifdef CONFIG_CPU_ASR1903 |
| int count = 1000; |
| #endif |
| |
| #ifndef CONFIG_CPU_ASR1903 |
| /* cfg_ss_mode_delay=1 */ |
| val = readl(inst->phy_base + PUPHY_MODE_CFG); |
| writel((val&(~(0x3<<14))) | (0x1<<14), inst->phy_base + PUPHY_MODE_CFG); |
| /* disable puphy ssc */ |
| val = readl(inst->phy_base + PUPHY_PLL_REG1); |
| writel((val& ~(0xf<<16)), inst->phy_base + PUPHY_PLL_REG1); |
| /* setting clock en and freq */ |
| val = readl(inst->phy_base + PUPHY_CLK_CFG); |
| writel(val|(0xf<<3), inst->phy_base + PUPHY_CLK_CFG); |
| if (cpu_is_asr1828()) { |
| val = readl(inst->phy_base + PUPHY_PU_SEL); |
| val &= 0xfffffeff; |
| writel(val, inst->phy_base + PUPHY_PU_SEL); |
| val = readl(inst->phy_base + PUPHY_PLL_REG2); |
| val &= 0xfffff7ff; |
| writel(val, inst->phy_base + PUPHY_PLL_REG2); |
| } |
| #else |
| /* waiting PCIe readonly_reg2[2] r_tune_done==1 */ |
| val = readl(inst->phy_base + PUPHY_READONLY_REG); |
| while (count--) { |
| val = readl(inst->phy_base + PUPHY_READONLY_REG); |
| mdelay(1); |
| if (val>>10 & 0x1) |
| break; |
| } |
| if (count < 0) { |
| pr_info("wait phy tune done timeout\n"); |
| return -EINVAL; |
| } |
| /* copy readonly1[3:0] to rx reg1[3:0], and rx_reg4[5]=0 */ |
| val = readl(inst->phy_base + PUPHY_READONLY_REG); |
| val &= ~(0xf<<8); |
| writel(val, inst->phy_base + PUPHY_RX_REG1); |
| val = readl(inst->phy_base + PUPHY_RX_REG1); |
| val = (val&0xf)<<8; |
| writel(val, inst->phy_base + PUPHY_RX_REG1); |
| |
| val = readl(inst->phy_base + PUPHY_RX_REG2); |
| val &= ~(0x1<<5); |
| writel(val, inst->phy_base + PUPHY_RX_REG2); |
| /* copy readonly[7:4] to tx reg1[7:4], and tx_reg3[1]=1 */ |
| val = readl(inst->phy_base + PUPHY_TX_REG); |
| val &= ~(0xf<<12); |
| writel(val, inst->phy_base + PUPHY_TX_REG); |
| val = readl(inst->phy_base + PUPHY_TX_REG); |
| val |= (((val>>4)&0xf)<<12); |
| writel(val, inst->phy_base + PUPHY_TX_REG); |
| val = readl(inst->phy_base + PUPHY_TX_REG); |
| val |= (0x1<<25); |
| writel(val, inst->phy_base + PUPHY_TX_REG); |
| /* rc_cal_reg2 clk sel, select input clock 38.4M */ |
| val = readl(inst->phy_base + PUPHY_CAL_REG); |
| val &= ~(0x7<<29); |
| val |= (0x7<<29); |
| writel(val, inst->phy_base + PUPHY_CAL_REG); |
| val = readl(inst->phy_base + PUPHY_CAL_REG); |
| val &= ~(0x1<<22); |
| val |= (0x1<<22); |
| writel(val, inst->phy_base + PUPHY_CAL_REG); |
| /* disable puphy ssc */ |
| val = readl(inst->phy_base + PUPHY_PLL_REG1); |
| writel((val& ~(0xf<<16)), inst->phy_base + PUPHY_PLL_REG1); |
| /* pll_reg2[7:5] freq_sel=38.4M, apply local 38.4M clock, that is the default value */ |
| val = readl(inst->phy_base + PUPHY_PLL_REG1); |
| val &= ~(0x7<<13); |
| val |= (0x6<<13); |
| writel(val, inst->phy_base + PUPHY_PLL_REG1); |
| /* en_rterm refclk receiver r termination enable, only used in refclk reciever mode */ |
| val = readl(inst->phy_base + PUPHY_RX_REG1); |
| val &= ~(0x1<<3); |
| writel(val, inst->phy_base + PUPHY_RX_REG1); |
| #endif |
| /* setting cfg_sw_phy_init done */ |
| val = readl(inst->phy_base + PUPHY_CLK_CFG); |
| writel(val|(0x1<<11), inst->phy_base + PUPHY_CLK_CFG); |
| |
| err = readl_poll_timeout_atomic(inst->phy_base + PUPHY_CLK_CFG, val, |
| (val & 0x1), 10, 10 * USEC_PER_MSEC); |
| if (err) { |
| pr_info("PCIe PLL isn't ready in time\n"); |
| return PCIBIOS_SET_FAILED; |
| } |
| pr_debug("PCIE PLL is ready\n"); |
| return PCIBIOS_SUCCESSFUL; |
| } |
| |
| static const struct phy_ops falcon_pcie_phy_ops = { |
| .init = falcon_pcie_phy_init, |
| .owner = THIS_MODULE, |
| }; |
| |
| static struct phy *falcon_pcie_phy_xlate(struct device *dev, |
| struct of_phandle_args *args) |
| { |
| struct falcon_pcie_phy *falcon_phy = dev_get_drvdata(dev); |
| |
| if (args->args_count == 0) |
| return falcon_phy->phys[0].phy; |
| |
| return falcon_phy->phys[args->args[0]].phy; |
| } |
| static int falcon_pcie_phy_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *np = dev->of_node; |
| struct device_node *child_np; |
| struct phy_provider *provider; |
| struct falcon_pcie_phy *falcon_phy; |
| struct resource res; |
| unsigned int phy_idx = 0; |
| void __iomem *base_phy; |
| int retval; |
| |
| falcon_phy = devm_kzalloc(dev, sizeof(*falcon_phy), GFP_KERNEL); |
| if (!falcon_phy) |
| return -ENOMEM; |
| |
| falcon_phy->dev = dev; |
| falcon_phy->nphys = of_get_child_count(np); |
| |
| for_each_child_of_node(np, child_np) { |
| falcon_phy->phys[phy_idx].phy = devm_phy_create(dev, dev->of_node, &falcon_pcie_phy_ops); |
| if (IS_ERR(falcon_phy->phys[phy_idx].phy)) { |
| dev_err(dev, "failed to create PCIe PHY\n"); |
| return PTR_ERR(falcon_phy->phys[phy_idx].phy); |
| } |
| |
| falcon_phy->phys[phy_idx].index = phy_idx; |
| retval = of_address_to_resource(child_np, 0, &res); |
| if (retval) { |
| dev_err(dev, "failed to get address resource\n"); |
| goto put_child; |
| } |
| |
| base_phy = devm_ioremap_resource(&(falcon_phy->phys[phy_idx].phy)->dev, &res); |
| if (IS_ERR(base_phy)) { |
| dev_err(dev, "failed to remap phy regs\n"); |
| retval = PTR_ERR(base_phy); |
| goto put_child; |
| } |
| falcon_phy->phys[phy_idx].phy_base = base_phy; |
| phy_set_drvdata(falcon_phy->phys[phy_idx].phy, &falcon_phy->phys[phy_idx]); |
| phy_idx++; |
| } |
| |
| platform_set_drvdata(pdev, falcon_phy); |
| provider = devm_of_phy_provider_register(dev, falcon_pcie_phy_xlate); |
| if (IS_ERR(provider)) { |
| dev_err(dev, "failed to register PHY provider\n"); |
| return PTR_ERR(provider); |
| } |
| |
| dev_info(dev, "Falcon PCIe PHY driver initialized\n"); |
| |
| return 0; |
| |
| put_child: |
| of_node_put(child_np); |
| return retval; |
| } |
| |
| static const struct of_device_id falcon_pcie_phy_match_table[] = { |
| { .compatible = "asr,falcon-pcie-phy" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, falcon_pcie_phy_match_table); |
| |
| static struct platform_driver falcon_pcie_phy_driver = { |
| .driver = { |
| .name = "falcon-pcie-phy", |
| .of_match_table = falcon_pcie_phy_match_table, |
| }, |
| .probe = falcon_pcie_phy_probe, |
| }; |
| module_platform_driver(falcon_pcie_phy_driver); |
| |
| MODULE_DESCRIPTION("ASR Falcon PCIe PHY driver"); |
| MODULE_LICENSE("GPL v2"); |