1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * IMG Pistachio USB PHY driver |
4 | * |
5 | * Copyright (C) 2015 Google, Inc. |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/io.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/phy/phy.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/regmap.h> |
18 | |
19 | #include <dt-bindings/phy/phy-pistachio-usb.h> |
20 | |
21 | #define USB_PHY_CONTROL1 0x04 |
22 | #define USB_PHY_CONTROL1_FSEL_SHIFT 2 |
23 | #define USB_PHY_CONTROL1_FSEL_MASK 0x7 |
24 | |
25 | #define USB_PHY_STRAP_CONTROL 0x10 |
26 | #define USB_PHY_STRAP_CONTROL_REFCLK_SHIFT 4 |
27 | #define USB_PHY_STRAP_CONTROL_REFCLK_MASK 0x3 |
28 | |
29 | #define USB_PHY_STATUS 0x14 |
30 | #define USB_PHY_STATUS_RX_PHY_CLK BIT(9) |
31 | #define USB_PHY_STATUS_RX_UTMI_CLK BIT(8) |
32 | #define USB_PHY_STATUS_VBUS_FAULT BIT(7) |
33 | |
34 | struct pistachio_usb_phy { |
35 | struct device *dev; |
36 | struct regmap *cr_top; |
37 | struct clk *phy_clk; |
38 | unsigned int refclk; |
39 | }; |
40 | |
41 | static const unsigned long fsel_rate_map[] = { |
42 | 9600000, |
43 | 10000000, |
44 | 12000000, |
45 | 19200000, |
46 | 20000000, |
47 | 24000000, |
48 | 0, |
49 | 50000000, |
50 | }; |
51 | |
52 | static int pistachio_usb_phy_power_on(struct phy *phy) |
53 | { |
54 | struct pistachio_usb_phy *p_phy = phy_get_drvdata(phy); |
55 | unsigned long timeout, rate; |
56 | unsigned int i; |
57 | int ret; |
58 | |
59 | ret = clk_prepare_enable(clk: p_phy->phy_clk); |
60 | if (ret < 0) { |
61 | dev_err(p_phy->dev, "Failed to enable PHY clock: %d\n" , ret); |
62 | return ret; |
63 | } |
64 | |
65 | regmap_update_bits(map: p_phy->cr_top, USB_PHY_STRAP_CONTROL, |
66 | USB_PHY_STRAP_CONTROL_REFCLK_MASK << |
67 | USB_PHY_STRAP_CONTROL_REFCLK_SHIFT, |
68 | val: p_phy->refclk << USB_PHY_STRAP_CONTROL_REFCLK_SHIFT); |
69 | |
70 | rate = clk_get_rate(clk: p_phy->phy_clk); |
71 | if (p_phy->refclk == REFCLK_XO_CRYSTAL && rate != 12000000) { |
72 | dev_err(p_phy->dev, "Unsupported rate for XO crystal: %ld\n" , |
73 | rate); |
74 | ret = -EINVAL; |
75 | goto disable_clk; |
76 | } |
77 | |
78 | for (i = 0; i < ARRAY_SIZE(fsel_rate_map); i++) { |
79 | if (rate == fsel_rate_map[i]) |
80 | break; |
81 | } |
82 | if (i == ARRAY_SIZE(fsel_rate_map)) { |
83 | dev_err(p_phy->dev, "Unsupported clock rate: %lu\n" , rate); |
84 | ret = -EINVAL; |
85 | goto disable_clk; |
86 | } |
87 | |
88 | regmap_update_bits(map: p_phy->cr_top, USB_PHY_CONTROL1, |
89 | USB_PHY_CONTROL1_FSEL_MASK << |
90 | USB_PHY_CONTROL1_FSEL_SHIFT, |
91 | val: i << USB_PHY_CONTROL1_FSEL_SHIFT); |
92 | |
93 | timeout = jiffies + msecs_to_jiffies(m: 200); |
94 | while (time_before(jiffies, timeout)) { |
95 | unsigned int val; |
96 | |
97 | regmap_read(map: p_phy->cr_top, USB_PHY_STATUS, val: &val); |
98 | if (val & USB_PHY_STATUS_VBUS_FAULT) { |
99 | dev_err(p_phy->dev, "VBUS fault detected\n" ); |
100 | ret = -EIO; |
101 | goto disable_clk; |
102 | } |
103 | if ((val & USB_PHY_STATUS_RX_PHY_CLK) && |
104 | (val & USB_PHY_STATUS_RX_UTMI_CLK)) |
105 | return 0; |
106 | usleep_range(min: 1000, max: 1500); |
107 | } |
108 | |
109 | dev_err(p_phy->dev, "Timed out waiting for PHY to power on\n" ); |
110 | ret = -ETIMEDOUT; |
111 | |
112 | disable_clk: |
113 | clk_disable_unprepare(clk: p_phy->phy_clk); |
114 | return ret; |
115 | } |
116 | |
117 | static int pistachio_usb_phy_power_off(struct phy *phy) |
118 | { |
119 | struct pistachio_usb_phy *p_phy = phy_get_drvdata(phy); |
120 | |
121 | clk_disable_unprepare(clk: p_phy->phy_clk); |
122 | |
123 | return 0; |
124 | } |
125 | |
126 | static const struct phy_ops pistachio_usb_phy_ops = { |
127 | .power_on = pistachio_usb_phy_power_on, |
128 | .power_off = pistachio_usb_phy_power_off, |
129 | .owner = THIS_MODULE, |
130 | }; |
131 | |
132 | static int pistachio_usb_phy_probe(struct platform_device *pdev) |
133 | { |
134 | struct pistachio_usb_phy *p_phy; |
135 | struct phy_provider *provider; |
136 | struct phy *phy; |
137 | int ret; |
138 | |
139 | p_phy = devm_kzalloc(dev: &pdev->dev, size: sizeof(*p_phy), GFP_KERNEL); |
140 | if (!p_phy) |
141 | return -ENOMEM; |
142 | p_phy->dev = &pdev->dev; |
143 | platform_set_drvdata(pdev, data: p_phy); |
144 | |
145 | p_phy->cr_top = syscon_regmap_lookup_by_phandle(np: p_phy->dev->of_node, |
146 | property: "img,cr-top" ); |
147 | if (IS_ERR(ptr: p_phy->cr_top)) { |
148 | dev_err(p_phy->dev, "Failed to get CR_TOP registers: %ld\n" , |
149 | PTR_ERR(p_phy->cr_top)); |
150 | return PTR_ERR(ptr: p_phy->cr_top); |
151 | } |
152 | |
153 | p_phy->phy_clk = devm_clk_get(dev: p_phy->dev, id: "usb_phy" ); |
154 | if (IS_ERR(ptr: p_phy->phy_clk)) { |
155 | dev_err(p_phy->dev, "Failed to get usb_phy clock: %ld\n" , |
156 | PTR_ERR(p_phy->phy_clk)); |
157 | return PTR_ERR(ptr: p_phy->phy_clk); |
158 | } |
159 | |
160 | ret = of_property_read_u32(np: p_phy->dev->of_node, propname: "img,refclk" , |
161 | out_value: &p_phy->refclk); |
162 | if (ret < 0) { |
163 | dev_err(p_phy->dev, "No reference clock selector specified\n" ); |
164 | return ret; |
165 | } |
166 | |
167 | phy = devm_phy_create(dev: p_phy->dev, NULL, ops: &pistachio_usb_phy_ops); |
168 | if (IS_ERR(ptr: phy)) { |
169 | dev_err(p_phy->dev, "Failed to create PHY: %ld\n" , |
170 | PTR_ERR(phy)); |
171 | return PTR_ERR(ptr: phy); |
172 | } |
173 | phy_set_drvdata(phy, data: p_phy); |
174 | |
175 | provider = devm_of_phy_provider_register(p_phy->dev, |
176 | of_phy_simple_xlate); |
177 | if (IS_ERR(ptr: provider)) { |
178 | dev_err(p_phy->dev, "Failed to register PHY provider: %ld\n" , |
179 | PTR_ERR(provider)); |
180 | return PTR_ERR(ptr: provider); |
181 | } |
182 | |
183 | return 0; |
184 | } |
185 | |
186 | static const struct of_device_id pistachio_usb_phy_of_match[] = { |
187 | { .compatible = "img,pistachio-usb-phy" , }, |
188 | { }, |
189 | }; |
190 | MODULE_DEVICE_TABLE(of, pistachio_usb_phy_of_match); |
191 | |
192 | static struct platform_driver pistachio_usb_phy_driver = { |
193 | .probe = pistachio_usb_phy_probe, |
194 | .driver = { |
195 | .name = "pistachio-usb-phy" , |
196 | .of_match_table = pistachio_usb_phy_of_match, |
197 | }, |
198 | }; |
199 | module_platform_driver(pistachio_usb_phy_driver); |
200 | |
201 | MODULE_AUTHOR("Andrew Bresticker <abrestic@chromium.org>" ); |
202 | MODULE_DESCRIPTION("IMG Pistachio USB2.0 PHY driver" ); |
203 | MODULE_LICENSE("GPL v2" ); |
204 | |