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