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