diff options
-rw-r--r-- | Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt | 26 | ||||
-rw-r--r-- | drivers/phy/Kconfig | 11 | ||||
-rw-r--r-- | drivers/phy/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/phy-sun4i-usb.c | 331 |
4 files changed, 369 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt new file mode 100644 index 000000000000..a82361b62015 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/sun4i-usb-phy.txt | |||
@@ -0,0 +1,26 @@ | |||
1 | Allwinner sun4i USB PHY | ||
2 | ----------------------- | ||
3 | |||
4 | Required properties: | ||
5 | - compatible : should be one of "allwinner,sun4i-a10-usb-phy", | ||
6 | "allwinner,sun5i-a13-usb-phy" or "allwinner,sun7i-a20-usb-phy" | ||
7 | - reg : a list of offset + length pairs | ||
8 | - reg-names : "phy_ctrl", "pmu1" and for sun4i or sun7i "pmu2" | ||
9 | - #phy-cells : from the generic phy bindings, must be 1 | ||
10 | - clocks : phandle + clock specifier for the phy clock | ||
11 | - clock-names : "usb_phy" | ||
12 | - resets : a list of phandle + reset specifier pairs | ||
13 | - reset-names : "usb0_reset", "usb1_reset" and for sun4i or sun7i "usb2_reset" | ||
14 | |||
15 | Example: | ||
16 | usbphy: phy@0x01c13400 { | ||
17 | #phy-cells = <1>; | ||
18 | compatible = "allwinner,sun4i-a10-usb-phy"; | ||
19 | /* phy base regs, phy1 pmu reg, phy2 pmu reg */ | ||
20 | reg = <0x01c13400 0x10 0x01c14800 0x4 0x01c1c800 0x4>; | ||
21 | reg-names = "phy_ctrl", "pmu1", "pmu2"; | ||
22 | clocks = <&usb_clk 8>; | ||
23 | clock-names = "usb_phy"; | ||
24 | resets = <&usb_clk 1>, <&usb_clk 2>; | ||
25 | reset-names = "usb1_reset", "usb2_reset"; | ||
26 | }; | ||
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 04312849501d..e677ee01fe8a 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig | |||
@@ -85,4 +85,15 @@ config PHY_EXYNOS5250_SATA | |||
85 | SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host | 85 | SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host |
86 | port to accept one SATA device. | 86 | port to accept one SATA device. |
87 | 87 | ||
88 | config PHY_SUN4I_USB | ||
89 | tristate "Allwinner sunxi SoC USB PHY driver" | ||
90 | depends on ARCH_SUNXI && HAS_IOMEM && OF | ||
91 | select GENERIC_PHY | ||
92 | help | ||
93 | Enable this to support the transceiver that is part of Allwinner | ||
94 | sunxi SoCs. | ||
95 | |||
96 | This driver controls the entire USB PHY block, both the USB OTG | ||
97 | parts, as well as the 2 regular USB 2 host PHYs. | ||
98 | |||
88 | endmenu | 99 | endmenu |
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index 0d038224a102..5d0b59edaf88 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile | |||
@@ -10,3 +10,4 @@ obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o | |||
10 | obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o | 10 | obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o |
11 | obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o | 11 | obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o |
12 | obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o | 12 | obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o |
13 | obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o | ||
diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c new file mode 100644 index 000000000000..e6e6c4ba7145 --- /dev/null +++ b/drivers/phy/phy-sun4i-usb.c | |||
@@ -0,0 +1,331 @@ | |||
1 | /* | ||
2 | * Allwinner sun4i USB phy driver | ||
3 | * | ||
4 | * Copyright (C) 2014 Hans de Goede <hdegoede@redhat.com> | ||
5 | * | ||
6 | * Based on code from | ||
7 | * Allwinner Technology Co., Ltd. <www.allwinnertech.com> | ||
8 | * | ||
9 | * Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver | ||
10 | * Copyright (C) 2013 Samsung Electronics Co., Ltd. | ||
11 | * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify | ||
14 | * it under the terms of the GNU General Public License as published by | ||
15 | * the Free Software Foundation; either version 2 of the License, or | ||
16 | * (at your option) any later version. | ||
17 | * | ||
18 | * This program is distributed in the hope that it will be useful, | ||
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
21 | * GNU General Public License for more details. | ||
22 | */ | ||
23 | |||
24 | #include <linux/clk.h> | ||
25 | #include <linux/io.h> | ||
26 | #include <linux/kernel.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/mutex.h> | ||
29 | #include <linux/of.h> | ||
30 | #include <linux/of_address.h> | ||
31 | #include <linux/phy/phy.h> | ||
32 | #include <linux/platform_device.h> | ||
33 | #include <linux/regulator/consumer.h> | ||
34 | #include <linux/reset.h> | ||
35 | |||
36 | #define REG_ISCR 0x00 | ||
37 | #define REG_PHYCTL 0x04 | ||
38 | #define REG_PHYBIST 0x08 | ||
39 | #define REG_PHYTUNE 0x0c | ||
40 | |||
41 | #define PHYCTL_DATA BIT(7) | ||
42 | |||
43 | #define SUNXI_AHB_ICHR8_EN BIT(10) | ||
44 | #define SUNXI_AHB_INCR4_BURST_EN BIT(9) | ||
45 | #define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) | ||
46 | #define SUNXI_ULPI_BYPASS_EN BIT(0) | ||
47 | |||
48 | /* Common Control Bits for Both PHYs */ | ||
49 | #define PHY_PLL_BW 0x03 | ||
50 | #define PHY_RES45_CAL_EN 0x0c | ||
51 | |||
52 | /* Private Control Bits for Each PHY */ | ||
53 | #define PHY_TX_AMPLITUDE_TUNE 0x20 | ||
54 | #define PHY_TX_SLEWRATE_TUNE 0x22 | ||
55 | #define PHY_VBUSVALID_TH_SEL 0x25 | ||
56 | #define PHY_PULLUP_RES_SEL 0x27 | ||
57 | #define PHY_OTG_FUNC_EN 0x28 | ||
58 | #define PHY_VBUS_DET_EN 0x29 | ||
59 | #define PHY_DISCON_TH_SEL 0x2a | ||
60 | |||
61 | #define MAX_PHYS 3 | ||
62 | |||
63 | struct sun4i_usb_phy_data { | ||
64 | struct clk *clk; | ||
65 | void __iomem *base; | ||
66 | struct mutex mutex; | ||
67 | int num_phys; | ||
68 | u32 disc_thresh; | ||
69 | struct sun4i_usb_phy { | ||
70 | struct phy *phy; | ||
71 | void __iomem *pmu; | ||
72 | struct regulator *vbus; | ||
73 | struct reset_control *reset; | ||
74 | int index; | ||
75 | } phys[MAX_PHYS]; | ||
76 | }; | ||
77 | |||
78 | #define to_sun4i_usb_phy_data(phy) \ | ||
79 | container_of((phy), struct sun4i_usb_phy_data, phys[(phy)->index]) | ||
80 | |||
81 | static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, | ||
82 | int len) | ||
83 | { | ||
84 | struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); | ||
85 | u32 temp, usbc_bit = BIT(phy->index * 2); | ||
86 | int i; | ||
87 | |||
88 | mutex_lock(&phy_data->mutex); | ||
89 | |||
90 | for (i = 0; i < len; i++) { | ||
91 | temp = readl(phy_data->base + REG_PHYCTL); | ||
92 | |||
93 | /* clear the address portion */ | ||
94 | temp &= ~(0xff << 8); | ||
95 | |||
96 | /* set the address */ | ||
97 | temp |= ((addr + i) << 8); | ||
98 | writel(temp, phy_data->base + REG_PHYCTL); | ||
99 | |||
100 | /* set the data bit and clear usbc bit*/ | ||
101 | temp = readb(phy_data->base + REG_PHYCTL); | ||
102 | if (data & 0x1) | ||
103 | temp |= PHYCTL_DATA; | ||
104 | else | ||
105 | temp &= ~PHYCTL_DATA; | ||
106 | temp &= ~usbc_bit; | ||
107 | writeb(temp, phy_data->base + REG_PHYCTL); | ||
108 | |||
109 | /* pulse usbc_bit */ | ||
110 | temp = readb(phy_data->base + REG_PHYCTL); | ||
111 | temp |= usbc_bit; | ||
112 | writeb(temp, phy_data->base + REG_PHYCTL); | ||
113 | |||
114 | temp = readb(phy_data->base + REG_PHYCTL); | ||
115 | temp &= ~usbc_bit; | ||
116 | writeb(temp, phy_data->base + REG_PHYCTL); | ||
117 | |||
118 | data >>= 1; | ||
119 | } | ||
120 | mutex_unlock(&phy_data->mutex); | ||
121 | } | ||
122 | |||
123 | static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable) | ||
124 | { | ||
125 | u32 bits, reg_value; | ||
126 | |||
127 | if (!phy->pmu) | ||
128 | return; | ||
129 | |||
130 | bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN | | ||
131 | SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN; | ||
132 | |||
133 | reg_value = readl(phy->pmu); | ||
134 | |||
135 | if (enable) | ||
136 | reg_value |= bits; | ||
137 | else | ||
138 | reg_value &= ~bits; | ||
139 | |||
140 | writel(reg_value, phy->pmu); | ||
141 | } | ||
142 | |||
143 | static int sun4i_usb_phy_init(struct phy *_phy) | ||
144 | { | ||
145 | struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); | ||
146 | struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); | ||
147 | int ret; | ||
148 | |||
149 | ret = clk_prepare_enable(data->clk); | ||
150 | if (ret) | ||
151 | return ret; | ||
152 | |||
153 | ret = reset_control_deassert(phy->reset); | ||
154 | if (ret) { | ||
155 | clk_disable_unprepare(data->clk); | ||
156 | return ret; | ||
157 | } | ||
158 | |||
159 | /* Adjust PHY's magnitude and rate */ | ||
160 | sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); | ||
161 | |||
162 | /* Disconnect threshold adjustment */ | ||
163 | sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, data->disc_thresh, 2); | ||
164 | |||
165 | sun4i_usb_phy_passby(phy, 1); | ||
166 | |||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | static int sun4i_usb_phy_exit(struct phy *_phy) | ||
171 | { | ||
172 | struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); | ||
173 | struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); | ||
174 | |||
175 | sun4i_usb_phy_passby(phy, 0); | ||
176 | reset_control_assert(phy->reset); | ||
177 | clk_disable_unprepare(data->clk); | ||
178 | |||
179 | return 0; | ||
180 | } | ||
181 | |||
182 | static int sun4i_usb_phy_power_on(struct phy *_phy) | ||
183 | { | ||
184 | struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); | ||
185 | int ret = 0; | ||
186 | |||
187 | if (phy->vbus) | ||
188 | ret = regulator_enable(phy->vbus); | ||
189 | |||
190 | return ret; | ||
191 | } | ||
192 | |||
193 | static int sun4i_usb_phy_power_off(struct phy *_phy) | ||
194 | { | ||
195 | struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); | ||
196 | |||
197 | if (phy->vbus) | ||
198 | regulator_disable(phy->vbus); | ||
199 | |||
200 | return 0; | ||
201 | } | ||
202 | |||
203 | static struct phy_ops sun4i_usb_phy_ops = { | ||
204 | .init = sun4i_usb_phy_init, | ||
205 | .exit = sun4i_usb_phy_exit, | ||
206 | .power_on = sun4i_usb_phy_power_on, | ||
207 | .power_off = sun4i_usb_phy_power_off, | ||
208 | .owner = THIS_MODULE, | ||
209 | }; | ||
210 | |||
211 | static struct phy *sun4i_usb_phy_xlate(struct device *dev, | ||
212 | struct of_phandle_args *args) | ||
213 | { | ||
214 | struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); | ||
215 | |||
216 | if (WARN_ON(args->args[0] == 0 || args->args[0] >= data->num_phys)) | ||
217 | return ERR_PTR(-ENODEV); | ||
218 | |||
219 | return data->phys[args->args[0]].phy; | ||
220 | } | ||
221 | |||
222 | static int sun4i_usb_phy_probe(struct platform_device *pdev) | ||
223 | { | ||
224 | struct sun4i_usb_phy_data *data; | ||
225 | struct device *dev = &pdev->dev; | ||
226 | struct device_node *np = dev->of_node; | ||
227 | void __iomem *pmu = NULL; | ||
228 | struct phy_provider *phy_provider; | ||
229 | struct reset_control *reset; | ||
230 | struct regulator *vbus; | ||
231 | struct resource *res; | ||
232 | struct phy *phy; | ||
233 | char name[16]; | ||
234 | int i; | ||
235 | |||
236 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | ||
237 | if (!data) | ||
238 | return -ENOMEM; | ||
239 | |||
240 | mutex_init(&data->mutex); | ||
241 | |||
242 | if (of_device_is_compatible(np, "allwinner,sun5i-a13-usb-phy")) | ||
243 | data->num_phys = 2; | ||
244 | else | ||
245 | data->num_phys = 3; | ||
246 | |||
247 | if (of_device_is_compatible(np, "allwinner,sun4i-a10-usb-phy")) | ||
248 | data->disc_thresh = 3; | ||
249 | else | ||
250 | data->disc_thresh = 2; | ||
251 | |||
252 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl"); | ||
253 | data->base = devm_ioremap_resource(dev, res); | ||
254 | if (IS_ERR(data->base)) | ||
255 | return PTR_ERR(data->base); | ||
256 | |||
257 | data->clk = devm_clk_get(dev, "usb_phy"); | ||
258 | if (IS_ERR(data->clk)) { | ||
259 | dev_err(dev, "could not get usb_phy clock\n"); | ||
260 | return PTR_ERR(data->clk); | ||
261 | } | ||
262 | |||
263 | /* Skip 0, 0 is the phy for otg which is not yet supported. */ | ||
264 | for (i = 1; i < data->num_phys; i++) { | ||
265 | snprintf(name, sizeof(name), "usb%d_vbus", i); | ||
266 | vbus = devm_regulator_get_optional(dev, name); | ||
267 | if (IS_ERR(vbus)) { | ||
268 | if (PTR_ERR(vbus) == -EPROBE_DEFER) | ||
269 | return -EPROBE_DEFER; | ||
270 | vbus = NULL; | ||
271 | } | ||
272 | |||
273 | snprintf(name, sizeof(name), "usb%d_reset", i); | ||
274 | reset = devm_reset_control_get(dev, name); | ||
275 | if (IS_ERR(reset)) { | ||
276 | dev_err(dev, "failed to get reset %s\n", name); | ||
277 | return PTR_ERR(reset); | ||
278 | } | ||
279 | |||
280 | if (i) { /* No pmu for usbc0 */ | ||
281 | snprintf(name, sizeof(name), "pmu%d", i); | ||
282 | res = platform_get_resource_byname(pdev, | ||
283 | IORESOURCE_MEM, name); | ||
284 | pmu = devm_ioremap_resource(dev, res); | ||
285 | if (IS_ERR(pmu)) | ||
286 | return PTR_ERR(pmu); | ||
287 | } | ||
288 | |||
289 | phy = devm_phy_create(dev, &sun4i_usb_phy_ops, NULL); | ||
290 | if (IS_ERR(phy)) { | ||
291 | dev_err(dev, "failed to create PHY %d\n", i); | ||
292 | return PTR_ERR(phy); | ||
293 | } | ||
294 | |||
295 | data->phys[i].phy = phy; | ||
296 | data->phys[i].pmu = pmu; | ||
297 | data->phys[i].vbus = vbus; | ||
298 | data->phys[i].reset = reset; | ||
299 | data->phys[i].index = i; | ||
300 | phy_set_drvdata(phy, &data->phys[i]); | ||
301 | } | ||
302 | |||
303 | dev_set_drvdata(dev, data); | ||
304 | phy_provider = devm_of_phy_provider_register(dev, sun4i_usb_phy_xlate); | ||
305 | if (IS_ERR(phy_provider)) | ||
306 | return PTR_ERR(phy_provider); | ||
307 | |||
308 | return 0; | ||
309 | } | ||
310 | |||
311 | static const struct of_device_id sun4i_usb_phy_of_match[] = { | ||
312 | { .compatible = "allwinner,sun4i-a10-usb-phy" }, | ||
313 | { .compatible = "allwinner,sun5i-a13-usb-phy" }, | ||
314 | { .compatible = "allwinner,sun7i-a20-usb-phy" }, | ||
315 | { }, | ||
316 | }; | ||
317 | MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); | ||
318 | |||
319 | static struct platform_driver sun4i_usb_phy_driver = { | ||
320 | .probe = sun4i_usb_phy_probe, | ||
321 | .driver = { | ||
322 | .of_match_table = sun4i_usb_phy_of_match, | ||
323 | .name = "sun4i-usb-phy", | ||
324 | .owner = THIS_MODULE, | ||
325 | } | ||
326 | }; | ||
327 | module_platform_driver(sun4i_usb_phy_driver); | ||
328 | |||
329 | MODULE_DESCRIPTION("Allwinner sun4i USB phy driver"); | ||
330 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | ||
331 | MODULE_LICENSE("GPL v2"); | ||