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