1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Allwinner sun50i(H6) USB 3.0 phy driver |
4 | * |
5 | * Copyright (C) 2017 Icenowy Zheng <icenowy@aosc.io> |
6 | * |
7 | * Based on phy-sun9i-usb.c, which is: |
8 | * |
9 | * Copyright (C) 2014-2015 Chen-Yu Tsai <wens@csie.org> |
10 | * |
11 | * Based on code from Allwinner BSP, which is: |
12 | * |
13 | * Copyright (c) 2010-2015 Allwinner Technology Co., Ltd. |
14 | */ |
15 | |
16 | #include <linux/clk.h> |
17 | #include <linux/err.h> |
18 | #include <linux/io.h> |
19 | #include <linux/mod_devicetable.h> |
20 | #include <linux/module.h> |
21 | #include <linux/phy/phy.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/reset.h> |
24 | |
25 | /* Interface Status and Control Registers */ |
26 | #define SUNXI_ISCR 0x00 |
27 | #define SUNXI_PIPE_CLOCK_CONTROL 0x14 |
28 | #define SUNXI_PHY_TUNE_LOW 0x18 |
29 | #define SUNXI_PHY_TUNE_HIGH 0x1c |
30 | #define SUNXI_PHY_EXTERNAL_CONTROL 0x20 |
31 | |
32 | /* USB2.0 Interface Status and Control Register */ |
33 | #define SUNXI_ISCR_FORCE_VBUS (3 << 12) |
34 | |
35 | /* PIPE Clock Control Register */ |
36 | #define SUNXI_PCC_PIPE_CLK_OPEN (1 << 6) |
37 | |
38 | /* PHY External Control Register */ |
39 | #define SUNXI_PEC_EXTERN_VBUS (3 << 1) |
40 | #define SUNXI_PEC_SSC_EN (1 << 24) |
41 | #define SUNXI_PEC_REF_SSP_EN (1 << 26) |
42 | |
43 | /* PHY Tune High Register */ |
44 | #define SUNXI_TX_DEEMPH_3P5DB(n) ((n) << 19) |
45 | #define SUNXI_TX_DEEMPH_3P5DB_MASK GENMASK(24, 19) |
46 | #define SUNXI_TX_DEEMPH_6DB(n) ((n) << 13) |
47 | #define SUNXI_TX_DEEMPH_6GB_MASK GENMASK(18, 13) |
48 | #define SUNXI_TX_SWING_FULL(n) ((n) << 6) |
49 | #define SUNXI_TX_SWING_FULL_MASK GENMASK(12, 6) |
50 | #define SUNXI_LOS_BIAS(n) ((n) << 3) |
51 | #define SUNXI_LOS_BIAS_MASK GENMASK(5, 3) |
52 | #define SUNXI_TXVBOOSTLVL(n) ((n) << 0) |
53 | #define SUNXI_TXVBOOSTLVL_MASK GENMASK(2, 0) |
54 | |
55 | struct sun50i_usb3_phy { |
56 | struct phy *phy; |
57 | void __iomem *regs; |
58 | struct reset_control *reset; |
59 | struct clk *clk; |
60 | }; |
61 | |
62 | static void sun50i_usb3_phy_open(struct sun50i_usb3_phy *phy) |
63 | { |
64 | u32 val; |
65 | |
66 | val = readl(addr: phy->regs + SUNXI_PHY_EXTERNAL_CONTROL); |
67 | val |= SUNXI_PEC_EXTERN_VBUS; |
68 | val |= SUNXI_PEC_SSC_EN | SUNXI_PEC_REF_SSP_EN; |
69 | writel(val, addr: phy->regs + SUNXI_PHY_EXTERNAL_CONTROL); |
70 | |
71 | val = readl(addr: phy->regs + SUNXI_PIPE_CLOCK_CONTROL); |
72 | val |= SUNXI_PCC_PIPE_CLK_OPEN; |
73 | writel(val, addr: phy->regs + SUNXI_PIPE_CLOCK_CONTROL); |
74 | |
75 | val = readl(addr: phy->regs + SUNXI_ISCR); |
76 | val |= SUNXI_ISCR_FORCE_VBUS; |
77 | writel(val, addr: phy->regs + SUNXI_ISCR); |
78 | |
79 | /* |
80 | * All the magic numbers written to the PHY_TUNE_{LOW_HIGH} |
81 | * registers are directly taken from the BSP USB3 driver from |
82 | * Allwiner. |
83 | */ |
84 | writel(val: 0x0047fc87, addr: phy->regs + SUNXI_PHY_TUNE_LOW); |
85 | |
86 | val = readl(addr: phy->regs + SUNXI_PHY_TUNE_HIGH); |
87 | val &= ~(SUNXI_TXVBOOSTLVL_MASK | SUNXI_LOS_BIAS_MASK | |
88 | SUNXI_TX_SWING_FULL_MASK | SUNXI_TX_DEEMPH_6GB_MASK | |
89 | SUNXI_TX_DEEMPH_3P5DB_MASK); |
90 | val |= SUNXI_TXVBOOSTLVL(0x7); |
91 | val |= SUNXI_LOS_BIAS(0x7); |
92 | val |= SUNXI_TX_SWING_FULL(0x55); |
93 | val |= SUNXI_TX_DEEMPH_6DB(0x20); |
94 | val |= SUNXI_TX_DEEMPH_3P5DB(0x15); |
95 | writel(val, addr: phy->regs + SUNXI_PHY_TUNE_HIGH); |
96 | } |
97 | |
98 | static int sun50i_usb3_phy_init(struct phy *_phy) |
99 | { |
100 | struct sun50i_usb3_phy *phy = phy_get_drvdata(phy: _phy); |
101 | int ret; |
102 | |
103 | ret = clk_prepare_enable(clk: phy->clk); |
104 | if (ret) |
105 | return ret; |
106 | |
107 | ret = reset_control_deassert(rstc: phy->reset); |
108 | if (ret) { |
109 | clk_disable_unprepare(clk: phy->clk); |
110 | return ret; |
111 | } |
112 | |
113 | sun50i_usb3_phy_open(phy); |
114 | return 0; |
115 | } |
116 | |
117 | static int sun50i_usb3_phy_exit(struct phy *_phy) |
118 | { |
119 | struct sun50i_usb3_phy *phy = phy_get_drvdata(phy: _phy); |
120 | |
121 | reset_control_assert(rstc: phy->reset); |
122 | clk_disable_unprepare(clk: phy->clk); |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | static const struct phy_ops sun50i_usb3_phy_ops = { |
128 | .init = sun50i_usb3_phy_init, |
129 | .exit = sun50i_usb3_phy_exit, |
130 | .owner = THIS_MODULE, |
131 | }; |
132 | |
133 | static int sun50i_usb3_phy_probe(struct platform_device *pdev) |
134 | { |
135 | struct sun50i_usb3_phy *phy; |
136 | struct device *dev = &pdev->dev; |
137 | struct phy_provider *phy_provider; |
138 | |
139 | phy = devm_kzalloc(dev, size: sizeof(*phy), GFP_KERNEL); |
140 | if (!phy) |
141 | return -ENOMEM; |
142 | |
143 | phy->clk = devm_clk_get(dev, NULL); |
144 | if (IS_ERR(ptr: phy->clk)) { |
145 | if (PTR_ERR(ptr: phy->clk) != -EPROBE_DEFER) |
146 | dev_err(dev, "failed to get phy clock\n" ); |
147 | return PTR_ERR(ptr: phy->clk); |
148 | } |
149 | |
150 | phy->reset = devm_reset_control_get(dev, NULL); |
151 | if (IS_ERR(ptr: phy->reset)) { |
152 | dev_err(dev, "failed to get reset control\n" ); |
153 | return PTR_ERR(ptr: phy->reset); |
154 | } |
155 | |
156 | phy->regs = devm_platform_ioremap_resource(pdev, index: 0); |
157 | if (IS_ERR(ptr: phy->regs)) |
158 | return PTR_ERR(ptr: phy->regs); |
159 | |
160 | phy->phy = devm_phy_create(dev, NULL, ops: &sun50i_usb3_phy_ops); |
161 | if (IS_ERR(ptr: phy->phy)) { |
162 | dev_err(dev, "failed to create PHY\n" ); |
163 | return PTR_ERR(ptr: phy->phy); |
164 | } |
165 | |
166 | phy_set_drvdata(phy: phy->phy, data: phy); |
167 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
168 | |
169 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
170 | } |
171 | |
172 | static const struct of_device_id sun50i_usb3_phy_of_match[] = { |
173 | { .compatible = "allwinner,sun50i-h6-usb3-phy" }, |
174 | { }, |
175 | }; |
176 | MODULE_DEVICE_TABLE(of, sun50i_usb3_phy_of_match); |
177 | |
178 | static struct platform_driver sun50i_usb3_phy_driver = { |
179 | .probe = sun50i_usb3_phy_probe, |
180 | .driver = { |
181 | .of_match_table = sun50i_usb3_phy_of_match, |
182 | .name = "sun50i-usb3-phy" , |
183 | } |
184 | }; |
185 | module_platform_driver(sun50i_usb3_phy_driver); |
186 | |
187 | MODULE_DESCRIPTION("Allwinner H6 USB 3.0 phy driver" ); |
188 | MODULE_AUTHOR("Icenowy Zheng <icenowy@aosc.io>" ); |
189 | MODULE_LICENSE("GPL" ); |
190 | |