Commit | Line | Data |
---|---|---|
d3feb406 RM |
1 | /* |
2 | * Broadcom Northstar USB 2.0 PHY Driver | |
3 | * | |
4 | * Copyright (C) 2016 Rafał Miłecki <zajec5@gmail.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | */ | |
11 | ||
12 | #include <linux/bcma/bcma.h> | |
13 | #include <linux/clk.h> | |
14 | #include <linux/delay.h> | |
15 | #include <linux/err.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/of_address.h> | |
18 | #include <linux/of_platform.h> | |
19 | #include <linux/phy/phy.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/slab.h> | |
22 | ||
23 | struct bcm_ns_usb2 { | |
24 | struct device *dev; | |
25 | struct clk *ref_clk; | |
26 | struct phy *phy; | |
27 | void __iomem *dmu; | |
28 | }; | |
29 | ||
30 | static int bcm_ns_usb2_phy_init(struct phy *phy) | |
31 | { | |
32 | struct bcm_ns_usb2 *usb2 = phy_get_drvdata(phy); | |
33 | struct device *dev = usb2->dev; | |
34 | void __iomem *dmu = usb2->dmu; | |
35 | u32 ref_clk_rate, usb2ctl, usb_pll_ndiv, usb_pll_pdiv; | |
36 | int err = 0; | |
37 | ||
38 | err = clk_prepare_enable(usb2->ref_clk); | |
39 | if (err < 0) { | |
40 | dev_err(dev, "Failed to prepare ref clock: %d\n", err); | |
41 | goto err_out; | |
42 | } | |
43 | ||
44 | ref_clk_rate = clk_get_rate(usb2->ref_clk); | |
45 | if (!ref_clk_rate) { | |
46 | dev_err(dev, "Failed to get ref clock rate\n"); | |
47 | err = -EINVAL; | |
48 | goto err_clk_off; | |
49 | } | |
50 | ||
51 | usb2ctl = readl(dmu + BCMA_DMU_CRU_USB2_CONTROL); | |
52 | ||
53 | if (usb2ctl & BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK) { | |
54 | usb_pll_pdiv = usb2ctl; | |
55 | usb_pll_pdiv &= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK; | |
56 | usb_pll_pdiv >>= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_SHIFT; | |
57 | } else { | |
58 | usb_pll_pdiv = 1 << 3; | |
59 | } | |
60 | ||
61 | /* Calculate ndiv based on a solid 1920 MHz that is for USB2 PHY */ | |
62 | usb_pll_ndiv = (1920000000 * usb_pll_pdiv) / ref_clk_rate; | |
63 | ||
64 | /* Unlock DMU PLL settings with some magic value */ | |
65 | writel(0x0000ea68, dmu + BCMA_DMU_CRU_CLKSET_KEY); | |
66 | ||
67 | /* Write USB 2.0 PLL control setting */ | |
68 | usb2ctl &= ~BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_MASK; | |
69 | usb2ctl |= usb_pll_ndiv << BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_SHIFT; | |
70 | writel(usb2ctl, dmu + BCMA_DMU_CRU_USB2_CONTROL); | |
71 | ||
72 | /* Lock DMU PLL settings */ | |
73 | writel(0x00000000, dmu + BCMA_DMU_CRU_CLKSET_KEY); | |
74 | ||
75 | err_clk_off: | |
76 | clk_disable_unprepare(usb2->ref_clk); | |
77 | err_out: | |
78 | return err; | |
79 | } | |
80 | ||
81 | static const struct phy_ops ops = { | |
82 | .init = bcm_ns_usb2_phy_init, | |
83 | .owner = THIS_MODULE, | |
84 | }; | |
85 | ||
86 | static int bcm_ns_usb2_probe(struct platform_device *pdev) | |
87 | { | |
88 | struct device *dev = &pdev->dev; | |
89 | struct bcm_ns_usb2 *usb2; | |
90 | struct resource *res; | |
91 | struct phy_provider *phy_provider; | |
92 | ||
93 | usb2 = devm_kzalloc(&pdev->dev, sizeof(*usb2), GFP_KERNEL); | |
94 | if (!usb2) | |
95 | return -ENOMEM; | |
96 | usb2->dev = dev; | |
97 | ||
98 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmu"); | |
99 | usb2->dmu = devm_ioremap_resource(dev, res); | |
100 | if (IS_ERR(usb2->dmu)) { | |
101 | dev_err(dev, "Failed to map DMU regs\n"); | |
102 | return PTR_ERR(usb2->dmu); | |
103 | } | |
104 | ||
105 | usb2->ref_clk = devm_clk_get(dev, "phy-ref-clk"); | |
106 | if (IS_ERR(usb2->ref_clk)) { | |
107 | dev_err(dev, "Clock not defined\n"); | |
108 | return PTR_ERR(usb2->ref_clk); | |
109 | } | |
110 | ||
111 | usb2->phy = devm_phy_create(dev, NULL, &ops); | |
6c081ff6 DC |
112 | if (IS_ERR(usb2->phy)) |
113 | return PTR_ERR(usb2->phy); | |
d3feb406 RM |
114 | |
115 | phy_set_drvdata(usb2->phy, usb2); | |
116 | platform_set_drvdata(pdev, usb2); | |
117 | ||
118 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | |
119 | return PTR_ERR_OR_ZERO(phy_provider); | |
120 | } | |
121 | ||
122 | static const struct of_device_id bcm_ns_usb2_id_table[] = { | |
123 | { .compatible = "brcm,ns-usb2-phy", }, | |
124 | {}, | |
125 | }; | |
126 | MODULE_DEVICE_TABLE(of, bcm_ns_usb2_id_table); | |
127 | ||
128 | static struct platform_driver bcm_ns_usb2_driver = { | |
129 | .probe = bcm_ns_usb2_probe, | |
130 | .driver = { | |
131 | .name = "bcm_ns_usb2", | |
132 | .of_match_table = bcm_ns_usb2_id_table, | |
133 | }, | |
134 | }; | |
135 | module_platform_driver(bcm_ns_usb2_driver); | |
136 | ||
137 | MODULE_LICENSE("GPL v2"); |