]>
Commit | Line | Data |
---|---|---|
133add5b MR |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright (c) 2016 Allwinnertech Co., Ltd. | |
4 | * Copyright (C) 2017-2018 Bootlin | |
5 | * | |
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> | |
7 | */ | |
8 | ||
9 | #include <linux/bitops.h> | |
10 | #include <linux/clk.h> | |
bb3b6fcb | 11 | #include <linux/module.h> |
133add5b | 12 | #include <linux/of_address.h> |
bb3b6fcb | 13 | #include <linux/platform_device.h> |
133add5b MR |
14 | #include <linux/regmap.h> |
15 | #include <linux/reset.h> | |
16 | ||
bb3b6fcb MR |
17 | #include <linux/phy/phy.h> |
18 | #include <linux/phy/phy-mipi-dphy.h> | |
133add5b MR |
19 | |
20 | #define SUN6I_DPHY_GCTL_REG 0x00 | |
21 | #define SUN6I_DPHY_GCTL_LANE_NUM(n) ((((n) - 1) & 3) << 4) | |
22 | #define SUN6I_DPHY_GCTL_EN BIT(0) | |
23 | ||
24 | #define SUN6I_DPHY_TX_CTL_REG 0x04 | |
25 | #define SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT BIT(28) | |
26 | ||
27 | #define SUN6I_DPHY_TX_TIME0_REG 0x10 | |
28 | #define SUN6I_DPHY_TX_TIME0_HS_TRAIL(n) (((n) & 0xff) << 24) | |
29 | #define SUN6I_DPHY_TX_TIME0_HS_PREPARE(n) (((n) & 0xff) << 16) | |
30 | #define SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(n) ((n) & 0xff) | |
31 | ||
32 | #define SUN6I_DPHY_TX_TIME1_REG 0x14 | |
33 | #define SUN6I_DPHY_TX_TIME1_CLK_POST(n) (((n) & 0xff) << 24) | |
34 | #define SUN6I_DPHY_TX_TIME1_CLK_PRE(n) (((n) & 0xff) << 16) | |
35 | #define SUN6I_DPHY_TX_TIME1_CLK_ZERO(n) (((n) & 0xff) << 8) | |
36 | #define SUN6I_DPHY_TX_TIME1_CLK_PREPARE(n) ((n) & 0xff) | |
37 | ||
38 | #define SUN6I_DPHY_TX_TIME2_REG 0x18 | |
39 | #define SUN6I_DPHY_TX_TIME2_CLK_TRAIL(n) ((n) & 0xff) | |
40 | ||
41 | #define SUN6I_DPHY_TX_TIME3_REG 0x1c | |
42 | ||
43 | #define SUN6I_DPHY_TX_TIME4_REG 0x20 | |
44 | #define SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(n) (((n) & 0xff) << 8) | |
45 | #define SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(n) ((n) & 0xff) | |
46 | ||
47 | #define SUN6I_DPHY_ANA0_REG 0x4c | |
48 | #define SUN6I_DPHY_ANA0_REG_PWS BIT(31) | |
49 | #define SUN6I_DPHY_ANA0_REG_DMPC BIT(28) | |
50 | #define SUN6I_DPHY_ANA0_REG_DMPD(n) (((n) & 0xf) << 24) | |
51 | #define SUN6I_DPHY_ANA0_REG_SLV(n) (((n) & 7) << 12) | |
52 | #define SUN6I_DPHY_ANA0_REG_DEN(n) (((n) & 0xf) << 8) | |
53 | ||
54 | #define SUN6I_DPHY_ANA1_REG 0x50 | |
55 | #define SUN6I_DPHY_ANA1_REG_VTTMODE BIT(31) | |
56 | #define SUN6I_DPHY_ANA1_REG_CSMPS(n) (((n) & 3) << 28) | |
57 | #define SUN6I_DPHY_ANA1_REG_SVTT(n) (((n) & 0xf) << 24) | |
58 | ||
59 | #define SUN6I_DPHY_ANA2_REG 0x54 | |
60 | #define SUN6I_DPHY_ANA2_EN_P2S_CPU(n) (((n) & 0xf) << 24) | |
61 | #define SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK GENMASK(27, 24) | |
62 | #define SUN6I_DPHY_ANA2_EN_CK_CPU BIT(4) | |
63 | #define SUN6I_DPHY_ANA2_REG_ENIB BIT(1) | |
64 | ||
65 | #define SUN6I_DPHY_ANA3_REG 0x58 | |
66 | #define SUN6I_DPHY_ANA3_EN_VTTD(n) (((n) & 0xf) << 28) | |
67 | #define SUN6I_DPHY_ANA3_EN_VTTD_MASK GENMASK(31, 28) | |
68 | #define SUN6I_DPHY_ANA3_EN_VTTC BIT(27) | |
69 | #define SUN6I_DPHY_ANA3_EN_DIV BIT(26) | |
70 | #define SUN6I_DPHY_ANA3_EN_LDOC BIT(25) | |
71 | #define SUN6I_DPHY_ANA3_EN_LDOD BIT(24) | |
72 | #define SUN6I_DPHY_ANA3_EN_LDOR BIT(18) | |
73 | ||
74 | #define SUN6I_DPHY_ANA4_REG 0x5c | |
75 | #define SUN6I_DPHY_ANA4_REG_DMPLVC BIT(24) | |
76 | #define SUN6I_DPHY_ANA4_REG_DMPLVD(n) (((n) & 0xf) << 20) | |
77 | #define SUN6I_DPHY_ANA4_REG_CKDV(n) (((n) & 0x1f) << 12) | |
78 | #define SUN6I_DPHY_ANA4_REG_TMSC(n) (((n) & 3) << 10) | |
79 | #define SUN6I_DPHY_ANA4_REG_TMSD(n) (((n) & 3) << 8) | |
80 | #define SUN6I_DPHY_ANA4_REG_TXDNSC(n) (((n) & 3) << 6) | |
81 | #define SUN6I_DPHY_ANA4_REG_TXDNSD(n) (((n) & 3) << 4) | |
82 | #define SUN6I_DPHY_ANA4_REG_TXPUSC(n) (((n) & 3) << 2) | |
83 | #define SUN6I_DPHY_ANA4_REG_TXPUSD(n) ((n) & 3) | |
84 | ||
85 | #define SUN6I_DPHY_DBG5_REG 0xf4 | |
86 | ||
bb3b6fcb MR |
87 | struct sun6i_dphy { |
88 | struct clk *bus_clk; | |
89 | struct clk *mod_clk; | |
90 | struct regmap *regs; | |
91 | struct reset_control *reset; | |
92 | ||
93 | struct phy *phy; | |
94 | struct phy_configure_opts_mipi_dphy config; | |
95 | }; | |
96 | ||
97 | static int sun6i_dphy_init(struct phy *phy) | |
133add5b | 98 | { |
bb3b6fcb MR |
99 | struct sun6i_dphy *dphy = phy_get_drvdata(phy); |
100 | ||
133add5b MR |
101 | reset_control_deassert(dphy->reset); |
102 | clk_prepare_enable(dphy->mod_clk); | |
103 | clk_set_rate_exclusive(dphy->mod_clk, 150000000); | |
104 | ||
bb3b6fcb MR |
105 | return 0; |
106 | } | |
107 | ||
108 | static int sun6i_dphy_configure(struct phy *phy, union phy_configure_opts *opts) | |
109 | { | |
110 | struct sun6i_dphy *dphy = phy_get_drvdata(phy); | |
111 | int ret; | |
112 | ||
113 | ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); | |
114 | if (ret) | |
115 | return ret; | |
116 | ||
117 | memcpy(&dphy->config, opts, sizeof(dphy->config)); | |
118 | ||
119 | return 0; | |
120 | } | |
121 | ||
122 | static int sun6i_dphy_power_on(struct phy *phy) | |
123 | { | |
124 | struct sun6i_dphy *dphy = phy_get_drvdata(phy); | |
125 | u8 lanes_mask = GENMASK(dphy->config.lanes - 1, 0); | |
126 | ||
133add5b MR |
127 | regmap_write(dphy->regs, SUN6I_DPHY_TX_CTL_REG, |
128 | SUN6I_DPHY_TX_CTL_HS_TX_CLK_CONT); | |
129 | ||
130 | regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME0_REG, | |
131 | SUN6I_DPHY_TX_TIME0_LP_CLK_DIV(14) | | |
132 | SUN6I_DPHY_TX_TIME0_HS_PREPARE(6) | | |
133 | SUN6I_DPHY_TX_TIME0_HS_TRAIL(10)); | |
134 | ||
135 | regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME1_REG, | |
136 | SUN6I_DPHY_TX_TIME1_CLK_PREPARE(7) | | |
137 | SUN6I_DPHY_TX_TIME1_CLK_ZERO(50) | | |
138 | SUN6I_DPHY_TX_TIME1_CLK_PRE(3) | | |
139 | SUN6I_DPHY_TX_TIME1_CLK_POST(10)); | |
140 | ||
141 | regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME2_REG, | |
142 | SUN6I_DPHY_TX_TIME2_CLK_TRAIL(30)); | |
143 | ||
144 | regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME3_REG, 0); | |
145 | ||
146 | regmap_write(dphy->regs, SUN6I_DPHY_TX_TIME4_REG, | |
147 | SUN6I_DPHY_TX_TIME4_HS_TX_ANA0(3) | | |
148 | SUN6I_DPHY_TX_TIME4_HS_TX_ANA1(3)); | |
149 | ||
150 | regmap_write(dphy->regs, SUN6I_DPHY_GCTL_REG, | |
bb3b6fcb | 151 | SUN6I_DPHY_GCTL_LANE_NUM(dphy->config.lanes) | |
133add5b MR |
152 | SUN6I_DPHY_GCTL_EN); |
153 | ||
133add5b MR |
154 | regmap_write(dphy->regs, SUN6I_DPHY_ANA0_REG, |
155 | SUN6I_DPHY_ANA0_REG_PWS | | |
156 | SUN6I_DPHY_ANA0_REG_DMPC | | |
157 | SUN6I_DPHY_ANA0_REG_SLV(7) | | |
158 | SUN6I_DPHY_ANA0_REG_DMPD(lanes_mask) | | |
159 | SUN6I_DPHY_ANA0_REG_DEN(lanes_mask)); | |
160 | ||
161 | regmap_write(dphy->regs, SUN6I_DPHY_ANA1_REG, | |
162 | SUN6I_DPHY_ANA1_REG_CSMPS(1) | | |
163 | SUN6I_DPHY_ANA1_REG_SVTT(7)); | |
164 | ||
165 | regmap_write(dphy->regs, SUN6I_DPHY_ANA4_REG, | |
166 | SUN6I_DPHY_ANA4_REG_CKDV(1) | | |
167 | SUN6I_DPHY_ANA4_REG_TMSC(1) | | |
168 | SUN6I_DPHY_ANA4_REG_TMSD(1) | | |
169 | SUN6I_DPHY_ANA4_REG_TXDNSC(1) | | |
170 | SUN6I_DPHY_ANA4_REG_TXDNSD(1) | | |
171 | SUN6I_DPHY_ANA4_REG_TXPUSC(1) | | |
172 | SUN6I_DPHY_ANA4_REG_TXPUSD(1) | | |
173 | SUN6I_DPHY_ANA4_REG_DMPLVC | | |
174 | SUN6I_DPHY_ANA4_REG_DMPLVD(lanes_mask)); | |
175 | ||
176 | regmap_write(dphy->regs, SUN6I_DPHY_ANA2_REG, | |
177 | SUN6I_DPHY_ANA2_REG_ENIB); | |
178 | udelay(5); | |
179 | ||
180 | regmap_write(dphy->regs, SUN6I_DPHY_ANA3_REG, | |
181 | SUN6I_DPHY_ANA3_EN_LDOR | | |
182 | SUN6I_DPHY_ANA3_EN_LDOC | | |
183 | SUN6I_DPHY_ANA3_EN_LDOD); | |
184 | udelay(1); | |
185 | ||
186 | regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, | |
187 | SUN6I_DPHY_ANA3_EN_VTTC | | |
188 | SUN6I_DPHY_ANA3_EN_VTTD_MASK, | |
189 | SUN6I_DPHY_ANA3_EN_VTTC | | |
190 | SUN6I_DPHY_ANA3_EN_VTTD(lanes_mask)); | |
191 | udelay(1); | |
192 | ||
193 | regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA3_REG, | |
194 | SUN6I_DPHY_ANA3_EN_DIV, | |
195 | SUN6I_DPHY_ANA3_EN_DIV); | |
196 | udelay(1); | |
197 | ||
198 | regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, | |
199 | SUN6I_DPHY_ANA2_EN_CK_CPU, | |
200 | SUN6I_DPHY_ANA2_EN_CK_CPU); | |
201 | udelay(1); | |
202 | ||
203 | regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, | |
204 | SUN6I_DPHY_ANA1_REG_VTTMODE, | |
205 | SUN6I_DPHY_ANA1_REG_VTTMODE); | |
206 | ||
207 | regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA2_REG, | |
208 | SUN6I_DPHY_ANA2_EN_P2S_CPU_MASK, | |
209 | SUN6I_DPHY_ANA2_EN_P2S_CPU(lanes_mask)); | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
bb3b6fcb | 214 | static int sun6i_dphy_power_off(struct phy *phy) |
133add5b | 215 | { |
bb3b6fcb MR |
216 | struct sun6i_dphy *dphy = phy_get_drvdata(phy); |
217 | ||
133add5b MR |
218 | regmap_update_bits(dphy->regs, SUN6I_DPHY_ANA1_REG, |
219 | SUN6I_DPHY_ANA1_REG_VTTMODE, 0); | |
220 | ||
221 | return 0; | |
222 | } | |
223 | ||
bb3b6fcb | 224 | static int sun6i_dphy_exit(struct phy *phy) |
133add5b | 225 | { |
bb3b6fcb MR |
226 | struct sun6i_dphy *dphy = phy_get_drvdata(phy); |
227 | ||
133add5b MR |
228 | clk_rate_exclusive_put(dphy->mod_clk); |
229 | clk_disable_unprepare(dphy->mod_clk); | |
230 | reset_control_assert(dphy->reset); | |
231 | ||
232 | return 0; | |
233 | } | |
234 | ||
bb3b6fcb | 235 | |
82c8d386 | 236 | static const struct phy_ops sun6i_dphy_ops = { |
bb3b6fcb MR |
237 | .configure = sun6i_dphy_configure, |
238 | .power_on = sun6i_dphy_power_on, | |
239 | .power_off = sun6i_dphy_power_off, | |
240 | .init = sun6i_dphy_init, | |
241 | .exit = sun6i_dphy_exit, | |
242 | }; | |
243 | ||
82c8d386 | 244 | static const struct regmap_config sun6i_dphy_regmap_config = { |
133add5b MR |
245 | .reg_bits = 32, |
246 | .val_bits = 32, | |
247 | .reg_stride = 4, | |
248 | .max_register = SUN6I_DPHY_DBG5_REG, | |
249 | .name = "mipi-dphy", | |
250 | }; | |
251 | ||
bb3b6fcb | 252 | static int sun6i_dphy_probe(struct platform_device *pdev) |
133add5b | 253 | { |
bb3b6fcb | 254 | struct phy_provider *phy_provider; |
133add5b | 255 | struct sun6i_dphy *dphy; |
bb3b6fcb | 256 | struct resource *res; |
133add5b | 257 | void __iomem *regs; |
133add5b | 258 | |
bb3b6fcb | 259 | dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL); |
133add5b MR |
260 | if (!dphy) |
261 | return -ENOMEM; | |
262 | ||
bb3b6fcb MR |
263 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
264 | regs = devm_ioremap_resource(&pdev->dev, res); | |
133add5b | 265 | if (IS_ERR(regs)) { |
bb3b6fcb | 266 | dev_err(&pdev->dev, "Couldn't map the DPHY encoder registers\n"); |
133add5b MR |
267 | return PTR_ERR(regs); |
268 | } | |
269 | ||
bb3b6fcb MR |
270 | dphy->regs = devm_regmap_init_mmio_clk(&pdev->dev, "bus", |
271 | regs, &sun6i_dphy_regmap_config); | |
133add5b | 272 | if (IS_ERR(dphy->regs)) { |
bb3b6fcb | 273 | dev_err(&pdev->dev, "Couldn't create the DPHY encoder regmap\n"); |
133add5b MR |
274 | return PTR_ERR(dphy->regs); |
275 | } | |
276 | ||
bb3b6fcb | 277 | dphy->reset = devm_reset_control_get_shared(&pdev->dev, NULL); |
133add5b | 278 | if (IS_ERR(dphy->reset)) { |
bb3b6fcb | 279 | dev_err(&pdev->dev, "Couldn't get our reset line\n"); |
133add5b MR |
280 | return PTR_ERR(dphy->reset); |
281 | } | |
282 | ||
bb3b6fcb | 283 | dphy->mod_clk = devm_clk_get(&pdev->dev, "mod"); |
133add5b | 284 | if (IS_ERR(dphy->mod_clk)) { |
bb3b6fcb MR |
285 | dev_err(&pdev->dev, "Couldn't get the DPHY mod clock\n"); |
286 | return PTR_ERR(dphy->mod_clk); | |
133add5b MR |
287 | } |
288 | ||
bb3b6fcb MR |
289 | dphy->phy = devm_phy_create(&pdev->dev, NULL, &sun6i_dphy_ops); |
290 | if (IS_ERR(dphy->phy)) { | |
291 | dev_err(&pdev->dev, "failed to create PHY\n"); | |
292 | return PTR_ERR(dphy->phy); | |
293 | } | |
133add5b | 294 | |
bb3b6fcb MR |
295 | phy_set_drvdata(dphy->phy, dphy); |
296 | phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); | |
133add5b | 297 | |
bb3b6fcb | 298 | return PTR_ERR_OR_ZERO(phy_provider); |
133add5b MR |
299 | } |
300 | ||
bb3b6fcb MR |
301 | static const struct of_device_id sun6i_dphy_of_table[] = { |
302 | { .compatible = "allwinner,sun6i-a31-mipi-dphy" }, | |
303 | { } | |
304 | }; | |
305 | MODULE_DEVICE_TABLE(of, sun6i_dphy_of_table); | |
306 | ||
307 | static struct platform_driver sun6i_dphy_platform_driver = { | |
308 | .probe = sun6i_dphy_probe, | |
309 | .driver = { | |
310 | .name = "sun6i-mipi-dphy", | |
311 | .of_match_table = sun6i_dphy_of_table, | |
312 | }, | |
313 | }; | |
314 | module_platform_driver(sun6i_dphy_platform_driver); | |
133add5b | 315 | |
bb3b6fcb MR |
316 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@bootlin>"); |
317 | MODULE_DESCRIPTION("Allwinner A31 MIPI D-PHY Driver"); | |
318 | MODULE_LICENSE("GPL"); |