aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/phy
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2014-03-01 12:09:26 -0500
committerKishon Vijay Abraham I <kishon@ti.com>2014-03-03 08:20:09 -0500
commitba4bdc9e1dc01300490e4f5315b0ac8576bd4c7a (patch)
treeb6dee3632741ffcc29bbe095736d36f5c826b8ec /drivers/phy
parentbcff4cba41bcd7dfe535a2b7278b9fb8214a2a8f (diff)
PHY: sunxi: Add driver for sunxi usb phy
The Allwinner A1x / A2x SoCs have 2 or 3 usb phys which are all accessed through a single set of registers. Besides this there are also some other phy related bits which need poking, which are per phy, but shared between the ohci and ehci controllers, so these are also controlled from this new phy driver. Signed-off-by: Hans de Goede <hdegoede@redhat.com> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/Kconfig11
-rw-r--r--drivers/phy/Makefile1
-rw-r--r--drivers/phy/phy-sun4i-usb.c331
3 files changed, 343 insertions, 0 deletions
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
88config 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
88endmenu 99endmenu
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
10obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o 10obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o
11obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o 11obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o
12obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o 12obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o
13obj-$(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
63struct 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
81static 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
123static 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
143static 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
170static 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
182static 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
193static 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
203static 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
211static 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
222static 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
311static 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};
317MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match);
318
319static 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};
327module_platform_driver(sun4i_usb_phy_driver);
328
329MODULE_DESCRIPTION("Allwinner sun4i USB phy driver");
330MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
331MODULE_LICENSE("GPL v2");