1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Broadcom Northstar USB 2.0 PHY Driver |
4 | * |
5 | * Copyright (C) 2016 Rafał Miłecki <zajec5@gmail.com> |
6 | */ |
7 | |
8 | #include <linux/bcma/bcma.h> |
9 | #include <linux/clk.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/err.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/of_platform.h> |
16 | #include <linux/phy/phy.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/regmap.h> |
19 | #include <linux/slab.h> |
20 | |
21 | struct bcm_ns_usb2 { |
22 | struct device *dev; |
23 | struct clk *ref_clk; |
24 | struct phy *phy; |
25 | struct regmap *clkset; |
26 | void __iomem *base; |
27 | |
28 | /* Deprecated binding */ |
29 | void __iomem *dmu; |
30 | }; |
31 | |
32 | static int bcm_ns_usb2_phy_init(struct phy *phy) |
33 | { |
34 | struct bcm_ns_usb2 *usb2 = phy_get_drvdata(phy); |
35 | struct device *dev = usb2->dev; |
36 | u32 ref_clk_rate, usb2ctl, usb_pll_ndiv, usb_pll_pdiv; |
37 | int err = 0; |
38 | |
39 | err = clk_prepare_enable(clk: usb2->ref_clk); |
40 | if (err < 0) { |
41 | dev_err(dev, "Failed to prepare ref clock: %d\n" , err); |
42 | goto err_out; |
43 | } |
44 | |
45 | ref_clk_rate = clk_get_rate(clk: usb2->ref_clk); |
46 | if (!ref_clk_rate) { |
47 | dev_err(dev, "Failed to get ref clock rate\n" ); |
48 | err = -EINVAL; |
49 | goto err_clk_off; |
50 | } |
51 | |
52 | if (usb2->base) |
53 | usb2ctl = readl(addr: usb2->base); |
54 | else |
55 | usb2ctl = readl(addr: usb2->dmu + BCMA_DMU_CRU_USB2_CONTROL); |
56 | |
57 | if (usb2ctl & BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK) { |
58 | usb_pll_pdiv = usb2ctl; |
59 | usb_pll_pdiv &= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK; |
60 | usb_pll_pdiv >>= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_SHIFT; |
61 | } else { |
62 | usb_pll_pdiv = 1 << 3; |
63 | } |
64 | |
65 | /* Calculate ndiv based on a solid 1920 MHz that is for USB2 PHY */ |
66 | usb_pll_ndiv = (1920000000 * usb_pll_pdiv) / ref_clk_rate; |
67 | |
68 | /* Unlock DMU PLL settings with some magic value */ |
69 | if (usb2->clkset) |
70 | regmap_write(map: usb2->clkset, reg: 0, val: 0x0000ea68); |
71 | else |
72 | writel(val: 0x0000ea68, addr: usb2->dmu + BCMA_DMU_CRU_CLKSET_KEY); |
73 | |
74 | /* Write USB 2.0 PLL control setting */ |
75 | usb2ctl &= ~BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_MASK; |
76 | usb2ctl |= usb_pll_ndiv << BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_SHIFT; |
77 | if (usb2->base) |
78 | writel(val: usb2ctl, addr: usb2->base); |
79 | else |
80 | writel(val: usb2ctl, addr: usb2->dmu + BCMA_DMU_CRU_USB2_CONTROL); |
81 | |
82 | /* Lock DMU PLL settings */ |
83 | if (usb2->clkset) |
84 | regmap_write(map: usb2->clkset, reg: 0, val: 0x00000000); |
85 | else |
86 | writel(val: 0x00000000, addr: usb2->dmu + BCMA_DMU_CRU_CLKSET_KEY); |
87 | |
88 | err_clk_off: |
89 | clk_disable_unprepare(clk: usb2->ref_clk); |
90 | err_out: |
91 | return err; |
92 | } |
93 | |
94 | static const struct phy_ops ops = { |
95 | .init = bcm_ns_usb2_phy_init, |
96 | .owner = THIS_MODULE, |
97 | }; |
98 | |
99 | static int bcm_ns_usb2_probe(struct platform_device *pdev) |
100 | { |
101 | struct device *dev = &pdev->dev; |
102 | struct bcm_ns_usb2 *usb2; |
103 | struct phy_provider *phy_provider; |
104 | |
105 | usb2 = devm_kzalloc(dev: &pdev->dev, size: sizeof(*usb2), GFP_KERNEL); |
106 | if (!usb2) |
107 | return -ENOMEM; |
108 | usb2->dev = dev; |
109 | |
110 | if (of_property_present(np: dev->of_node, propname: "brcm,syscon-clkset" )) { |
111 | usb2->base = devm_platform_ioremap_resource(pdev, index: 0); |
112 | if (IS_ERR(ptr: usb2->base)) { |
113 | dev_err(dev, "Failed to map control reg\n" ); |
114 | return PTR_ERR(ptr: usb2->base); |
115 | } |
116 | |
117 | usb2->clkset = syscon_regmap_lookup_by_phandle(np: dev->of_node, |
118 | property: "brcm,syscon-clkset" ); |
119 | if (IS_ERR(ptr: usb2->clkset)) { |
120 | dev_err(dev, "Failed to lookup clkset regmap\n" ); |
121 | return PTR_ERR(ptr: usb2->clkset); |
122 | } |
123 | } else { |
124 | usb2->dmu = devm_platform_ioremap_resource_byname(pdev, name: "dmu" ); |
125 | if (IS_ERR(ptr: usb2->dmu)) { |
126 | dev_err(dev, "Failed to map DMU regs\n" ); |
127 | return PTR_ERR(ptr: usb2->dmu); |
128 | } |
129 | |
130 | dev_warn(dev, "using deprecated DT binding\n" ); |
131 | } |
132 | |
133 | usb2->ref_clk = devm_clk_get(dev, id: "phy-ref-clk" ); |
134 | if (IS_ERR(ptr: usb2->ref_clk)) { |
135 | dev_err_probe(dev, err: PTR_ERR(ptr: usb2->ref_clk), fmt: "failed to get ref clk\n" ); |
136 | return PTR_ERR(ptr: usb2->ref_clk); |
137 | } |
138 | |
139 | usb2->phy = devm_phy_create(dev, NULL, ops: &ops); |
140 | if (IS_ERR(ptr: usb2->phy)) |
141 | return PTR_ERR(ptr: usb2->phy); |
142 | |
143 | phy_set_drvdata(phy: usb2->phy, data: usb2); |
144 | platform_set_drvdata(pdev, data: usb2); |
145 | |
146 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
147 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
148 | } |
149 | |
150 | static const struct of_device_id bcm_ns_usb2_id_table[] = { |
151 | { .compatible = "brcm,ns-usb2-phy" , }, |
152 | {}, |
153 | }; |
154 | MODULE_DEVICE_TABLE(of, bcm_ns_usb2_id_table); |
155 | |
156 | static struct platform_driver bcm_ns_usb2_driver = { |
157 | .probe = bcm_ns_usb2_probe, |
158 | .driver = { |
159 | .name = "bcm_ns_usb2" , |
160 | .of_match_table = bcm_ns_usb2_id_table, |
161 | }, |
162 | }; |
163 | module_platform_driver(bcm_ns_usb2_driver); |
164 | |
165 | MODULE_LICENSE("GPL v2" ); |
166 | |