Commit | Line | Data |
---|---|---|
06fb0137 KD |
1 | /* |
2 | * Samsung SoC USB 1.1/2.0 PHY driver | |
3 | * | |
4 | * Copyright (C) 2013 Samsung Electronics Co., Ltd. | |
5 | * Author: Kamil Debski <k.debski@samsung.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | ||
12 | #include <linux/clk.h> | |
13 | #include <linux/mfd/syscon.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of.h> | |
16 | #include <linux/of_address.h> | |
17 | #include <linux/phy/phy.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/spinlock.h> | |
20 | #include "phy-samsung-usb2.h" | |
21 | ||
22 | static int samsung_usb2_phy_power_on(struct phy *phy) | |
23 | { | |
24 | struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); | |
25 | struct samsung_usb2_phy_driver *drv = inst->drv; | |
26 | int ret; | |
27 | ||
28 | dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n", | |
29 | inst->cfg->label); | |
a007ddba MS |
30 | |
31 | if (drv->vbus) { | |
32 | ret = regulator_enable(drv->vbus); | |
33 | if (ret) | |
34 | goto err_regulator; | |
35 | } | |
36 | ||
06fb0137 KD |
37 | ret = clk_prepare_enable(drv->clk); |
38 | if (ret) | |
39 | goto err_main_clk; | |
40 | ret = clk_prepare_enable(drv->ref_clk); | |
41 | if (ret) | |
42 | goto err_instance_clk; | |
43 | if (inst->cfg->power_on) { | |
44 | spin_lock(&drv->lock); | |
45 | ret = inst->cfg->power_on(inst); | |
46 | spin_unlock(&drv->lock); | |
7a504c93 AL |
47 | if (ret) |
48 | goto err_power_on; | |
06fb0137 KD |
49 | } |
50 | ||
51 | return 0; | |
52 | ||
7a504c93 AL |
53 | err_power_on: |
54 | clk_disable_unprepare(drv->ref_clk); | |
06fb0137 KD |
55 | err_instance_clk: |
56 | clk_disable_unprepare(drv->clk); | |
57 | err_main_clk: | |
a007ddba MS |
58 | if (drv->vbus) |
59 | regulator_disable(drv->vbus); | |
60 | err_regulator: | |
06fb0137 KD |
61 | return ret; |
62 | } | |
63 | ||
64 | static int samsung_usb2_phy_power_off(struct phy *phy) | |
65 | { | |
66 | struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); | |
67 | struct samsung_usb2_phy_driver *drv = inst->drv; | |
a007ddba | 68 | int ret = 0; |
06fb0137 KD |
69 | |
70 | dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n", | |
71 | inst->cfg->label); | |
72 | if (inst->cfg->power_off) { | |
73 | spin_lock(&drv->lock); | |
74 | ret = inst->cfg->power_off(inst); | |
75 | spin_unlock(&drv->lock); | |
7a504c93 AL |
76 | if (ret) |
77 | return ret; | |
06fb0137 KD |
78 | } |
79 | clk_disable_unprepare(drv->ref_clk); | |
80 | clk_disable_unprepare(drv->clk); | |
a007ddba MS |
81 | if (drv->vbus) |
82 | ret = regulator_disable(drv->vbus); | |
83 | ||
84 | return ret; | |
06fb0137 KD |
85 | } |
86 | ||
4a9e5ca1 | 87 | static const struct phy_ops samsung_usb2_phy_ops = { |
06fb0137 KD |
88 | .power_on = samsung_usb2_phy_power_on, |
89 | .power_off = samsung_usb2_phy_power_off, | |
90 | .owner = THIS_MODULE, | |
91 | }; | |
92 | ||
93 | static struct phy *samsung_usb2_phy_xlate(struct device *dev, | |
94 | struct of_phandle_args *args) | |
95 | { | |
96 | struct samsung_usb2_phy_driver *drv; | |
97 | ||
98 | drv = dev_get_drvdata(dev); | |
99 | if (!drv) | |
100 | return ERR_PTR(-EINVAL); | |
101 | ||
102 | if (WARN_ON(args->args[0] >= drv->cfg->num_phys)) | |
103 | return ERR_PTR(-ENODEV); | |
104 | ||
105 | return drv->instances[args->args[0]].phy; | |
106 | } | |
107 | ||
108 | static const struct of_device_id samsung_usb2_phy_of_match[] = { | |
016e0d3c MS |
109 | #ifdef CONFIG_PHY_EXYNOS4X12_USB2 |
110 | { | |
111 | .compatible = "samsung,exynos3250-usb2-phy", | |
112 | .data = &exynos3250_usb2_phy_config, | |
113 | }, | |
114 | #endif | |
06fb0137 KD |
115 | #ifdef CONFIG_PHY_EXYNOS4210_USB2 |
116 | { | |
117 | .compatible = "samsung,exynos4210-usb2-phy", | |
118 | .data = &exynos4210_usb2_phy_config, | |
119 | }, | |
120 | #endif | |
121 | #ifdef CONFIG_PHY_EXYNOS4X12_USB2 | |
122 | { | |
123 | .compatible = "samsung,exynos4x12-usb2-phy", | |
124 | .data = &exynos4x12_usb2_phy_config, | |
125 | }, | |
64bf2b23 KD |
126 | #endif |
127 | #ifdef CONFIG_PHY_EXYNOS5250_USB2 | |
128 | { | |
129 | .compatible = "samsung,exynos5250-usb2-phy", | |
130 | .data = &exynos5250_usb2_phy_config, | |
131 | }, | |
b3345d7c LT |
132 | #endif |
133 | #ifdef CONFIG_PHY_S5PV210_USB2 | |
134 | { | |
135 | .compatible = "samsung,s5pv210-usb2-phy", | |
136 | .data = &s5pv210_usb2_phy_config, | |
137 | }, | |
06fb0137 KD |
138 | #endif |
139 | { }, | |
140 | }; | |
bf5baf95 | 141 | MODULE_DEVICE_TABLE(of, samsung_usb2_phy_of_match); |
06fb0137 KD |
142 | |
143 | static int samsung_usb2_phy_probe(struct platform_device *pdev) | |
144 | { | |
145 | const struct of_device_id *match; | |
146 | const struct samsung_usb2_phy_config *cfg; | |
147 | struct device *dev = &pdev->dev; | |
148 | struct phy_provider *phy_provider; | |
149 | struct resource *mem; | |
150 | struct samsung_usb2_phy_driver *drv; | |
151 | int i, ret; | |
152 | ||
153 | if (!pdev->dev.of_node) { | |
154 | dev_err(dev, "This driver is required to be instantiated from device tree\n"); | |
155 | return -EINVAL; | |
156 | } | |
157 | ||
158 | match = of_match_node(samsung_usb2_phy_of_match, pdev->dev.of_node); | |
159 | if (!match) { | |
160 | dev_err(dev, "of_match_node() failed\n"); | |
161 | return -EINVAL; | |
162 | } | |
163 | cfg = match->data; | |
164 | ||
165 | drv = devm_kzalloc(dev, sizeof(struct samsung_usb2_phy_driver) + | |
166 | cfg->num_phys * sizeof(struct samsung_usb2_phy_instance), | |
167 | GFP_KERNEL); | |
168 | if (!drv) | |
169 | return -ENOMEM; | |
170 | ||
171 | dev_set_drvdata(dev, drv); | |
172 | spin_lock_init(&drv->lock); | |
173 | ||
174 | drv->cfg = cfg; | |
175 | drv->dev = dev; | |
176 | ||
177 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
178 | drv->reg_phy = devm_ioremap_resource(dev, mem); | |
179 | if (IS_ERR(drv->reg_phy)) { | |
180 | dev_err(dev, "Failed to map register memory (phy)\n"); | |
181 | return PTR_ERR(drv->reg_phy); | |
182 | } | |
183 | ||
184 | drv->reg_pmu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | |
185 | "samsung,pmureg-phandle"); | |
186 | if (IS_ERR(drv->reg_pmu)) { | |
187 | dev_err(dev, "Failed to map PMU registers (via syscon)\n"); | |
188 | return PTR_ERR(drv->reg_pmu); | |
189 | } | |
190 | ||
191 | if (drv->cfg->has_mode_switch) { | |
192 | drv->reg_sys = syscon_regmap_lookup_by_phandle( | |
193 | pdev->dev.of_node, "samsung,sysreg-phandle"); | |
194 | if (IS_ERR(drv->reg_sys)) { | |
195 | dev_err(dev, "Failed to map system registers (via syscon)\n"); | |
196 | return PTR_ERR(drv->reg_sys); | |
197 | } | |
198 | } | |
199 | ||
200 | drv->clk = devm_clk_get(dev, "phy"); | |
201 | if (IS_ERR(drv->clk)) { | |
202 | dev_err(dev, "Failed to get clock of phy controller\n"); | |
203 | return PTR_ERR(drv->clk); | |
204 | } | |
205 | ||
206 | drv->ref_clk = devm_clk_get(dev, "ref"); | |
207 | if (IS_ERR(drv->ref_clk)) { | |
208 | dev_err(dev, "Failed to get reference clock for the phy controller\n"); | |
209 | return PTR_ERR(drv->ref_clk); | |
210 | } | |
211 | ||
212 | drv->ref_rate = clk_get_rate(drv->ref_clk); | |
213 | if (drv->cfg->rate_to_clk) { | |
214 | ret = drv->cfg->rate_to_clk(drv->ref_rate, &drv->ref_reg_val); | |
215 | if (ret) | |
216 | return ret; | |
217 | } | |
218 | ||
a007ddba MS |
219 | drv->vbus = devm_regulator_get(dev, "vbus"); |
220 | if (IS_ERR(drv->vbus)) { | |
221 | ret = PTR_ERR(drv->vbus); | |
222 | if (ret == -EPROBE_DEFER) | |
223 | return ret; | |
224 | drv->vbus = NULL; | |
225 | } | |
226 | ||
06fb0137 KD |
227 | for (i = 0; i < drv->cfg->num_phys; i++) { |
228 | char *label = drv->cfg->phys[i].label; | |
229 | struct samsung_usb2_phy_instance *p = &drv->instances[i]; | |
230 | ||
231 | dev_dbg(dev, "Creating phy \"%s\"\n", label); | |
dbc98635 | 232 | p->phy = devm_phy_create(dev, NULL, &samsung_usb2_phy_ops); |
06fb0137 KD |
233 | if (IS_ERR(p->phy)) { |
234 | dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n", | |
235 | label); | |
236 | return PTR_ERR(p->phy); | |
237 | } | |
238 | ||
239 | p->cfg = &drv->cfg->phys[i]; | |
240 | p->drv = drv; | |
241 | phy_set_bus_width(p->phy, 8); | |
242 | phy_set_drvdata(p->phy, p); | |
243 | } | |
244 | ||
245 | phy_provider = devm_of_phy_provider_register(dev, | |
246 | samsung_usb2_phy_xlate); | |
247 | if (IS_ERR(phy_provider)) { | |
248 | dev_err(drv->dev, "Failed to register phy provider\n"); | |
249 | return PTR_ERR(phy_provider); | |
250 | } | |
251 | ||
252 | return 0; | |
253 | } | |
254 | ||
255 | static struct platform_driver samsung_usb2_phy_driver = { | |
256 | .probe = samsung_usb2_phy_probe, | |
257 | .driver = { | |
258 | .of_match_table = samsung_usb2_phy_of_match, | |
259 | .name = "samsung-usb2-phy", | |
06fb0137 KD |
260 | } |
261 | }; | |
262 | ||
263 | module_platform_driver(samsung_usb2_phy_driver); | |
264 | MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver"); | |
265 | MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>"); | |
266 | MODULE_LICENSE("GPL v2"); | |
267 | MODULE_ALIAS("platform:samsung-usb2-phy"); |