Commit | Line | Data |
---|---|---|
0d486806 BN |
1 | /* |
2 | * Broadcom SATA3 AHCI Controller PHY Driver | |
3 | * | |
037c4189 | 4 | * Copyright (C) 2016 Broadcom |
0d486806 BN |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2, or (at your option) | |
9 | * any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | */ | |
16 | ||
4faee9a4 | 17 | #include <linux/delay.h> |
0d486806 BN |
18 | #include <linux/device.h> |
19 | #include <linux/init.h> | |
20 | #include <linux/interrupt.h> | |
21 | #include <linux/io.h> | |
22 | #include <linux/kernel.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/of.h> | |
25 | #include <linux/phy/phy.h> | |
26 | #include <linux/platform_device.h> | |
27 | ||
4faee9a4 AP |
28 | #define SATA_PCB_BANK_OFFSET 0x23c |
29 | #define SATA_PCB_REG_OFFSET(ofs) ((ofs) * 4) | |
0d486806 BN |
30 | |
31 | #define MAX_PORTS 2 | |
32 | ||
33 | /* Register offset between PHYs in PCB space */ | |
4faee9a4 | 34 | #define SATA_PCB_REG_28NM_SPACE_SIZE 0x1000 |
810c6f16 | 35 | |
c1602a1a JS |
36 | /* The older SATA PHY registers duplicated per port registers within the map, |
37 | * rather than having a separate map per port. | |
38 | */ | |
4faee9a4 AP |
39 | #define SATA_PCB_REG_40NM_SPACE_SIZE 0x10 |
40 | ||
41 | /* Register offset between PHYs in PHY control space */ | |
42 | #define SATA_PHY_CTRL_REG_28NM_SPACE_SIZE 0x8 | |
c1602a1a | 43 | |
810c6f16 | 44 | enum brcm_sata_phy_version { |
4faee9a4 AP |
45 | BRCM_SATA_PHY_STB_28NM, |
46 | BRCM_SATA_PHY_STB_40NM, | |
47 | BRCM_SATA_PHY_IPROC_NS2, | |
810c6f16 | 48 | }; |
0d486806 BN |
49 | |
50 | struct brcm_sata_port { | |
51 | int portnum; | |
52 | struct phy *phy; | |
53 | struct brcm_sata_phy *phy_priv; | |
54 | bool ssc_en; | |
55 | }; | |
56 | ||
57 | struct brcm_sata_phy { | |
58 | struct device *dev; | |
59 | void __iomem *phy_base; | |
4faee9a4 | 60 | void __iomem *ctrl_base; |
810c6f16 | 61 | enum brcm_sata_phy_version version; |
0d486806 BN |
62 | |
63 | struct brcm_sata_port phys[MAX_PORTS]; | |
64 | }; | |
65 | ||
4faee9a4 AP |
66 | enum sata_phy_regs { |
67 | BLOCK0_REG_BANK = 0x000, | |
68 | BLOCK0_XGXSSTATUS = 0x81, | |
69 | BLOCK0_XGXSSTATUS_PLL_LOCK = BIT(12), | |
70 | BLOCK0_SPARE = 0x8d, | |
71 | BLOCK0_SPARE_OOB_CLK_SEL_MASK = 0x3, | |
72 | BLOCK0_SPARE_OOB_CLK_SEL_REFBY2 = 0x1, | |
73 | ||
74 | PLL_REG_BANK_0 = 0x050, | |
0d486806 BN |
75 | PLL_REG_BANK_0_PLLCONTROL_0 = 0x81, |
76 | ||
4faee9a4 AP |
77 | PLL1_REG_BANK = 0x060, |
78 | PLL1_ACTRL2 = 0x82, | |
79 | PLL1_ACTRL3 = 0x83, | |
80 | PLL1_ACTRL4 = 0x84, | |
81 | ||
82 | OOB_REG_BANK = 0x150, | |
83 | OOB_CTRL1 = 0x80, | |
84 | OOB_CTRL1_BURST_MAX_MASK = 0xf, | |
85 | OOB_CTRL1_BURST_MAX_SHIFT = 12, | |
86 | OOB_CTRL1_BURST_MIN_MASK = 0xf, | |
87 | OOB_CTRL1_BURST_MIN_SHIFT = 8, | |
88 | OOB_CTRL1_WAKE_IDLE_MAX_MASK = 0xf, | |
89 | OOB_CTRL1_WAKE_IDLE_MAX_SHIFT = 4, | |
90 | OOB_CTRL1_WAKE_IDLE_MIN_MASK = 0xf, | |
91 | OOB_CTRL1_WAKE_IDLE_MIN_SHIFT = 0, | |
92 | OOB_CTRL2 = 0x81, | |
93 | OOB_CTRL2_SEL_ENA_SHIFT = 15, | |
94 | OOB_CTRL2_SEL_ENA_RC_SHIFT = 14, | |
95 | OOB_CTRL2_RESET_IDLE_MAX_MASK = 0x3f, | |
96 | OOB_CTRL2_RESET_IDLE_MAX_SHIFT = 8, | |
97 | OOB_CTRL2_BURST_CNT_MASK = 0x3, | |
98 | OOB_CTRL2_BURST_CNT_SHIFT = 6, | |
99 | OOB_CTRL2_RESET_IDLE_MIN_MASK = 0x3f, | |
100 | OOB_CTRL2_RESET_IDLE_MIN_SHIFT = 0, | |
101 | ||
0d486806 BN |
102 | TXPMD_REG_BANK = 0x1a0, |
103 | TXPMD_CONTROL1 = 0x81, | |
104 | TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0), | |
105 | TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1), | |
106 | TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82, | |
107 | TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83, | |
108 | TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff, | |
109 | TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84, | |
110 | TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff, | |
111 | }; | |
112 | ||
4faee9a4 AP |
113 | enum sata_phy_ctrl_regs { |
114 | PHY_CTRL_1 = 0x0, | |
115 | PHY_CTRL_1_RESET = BIT(0), | |
116 | }; | |
117 | ||
118 | static inline void __iomem *brcm_sata_pcb_base(struct brcm_sata_port *port) | |
0d486806 BN |
119 | { |
120 | struct brcm_sata_phy *priv = port->phy_priv; | |
4faee9a4 AP |
121 | u32 size = 0; |
122 | ||
123 | switch (priv->version) { | |
124 | case BRCM_SATA_PHY_STB_28NM: | |
125 | case BRCM_SATA_PHY_IPROC_NS2: | |
126 | size = SATA_PCB_REG_28NM_SPACE_SIZE; | |
127 | break; | |
128 | case BRCM_SATA_PHY_STB_40NM: | |
129 | size = SATA_PCB_REG_40NM_SPACE_SIZE; | |
130 | break; | |
131 | default: | |
132 | dev_err(priv->dev, "invalid phy version\n"); | |
133 | break; | |
134 | }; | |
0d486806 | 135 | |
4faee9a4 AP |
136 | return priv->phy_base + (port->portnum * size); |
137 | } | |
138 | ||
139 | static inline void __iomem *brcm_sata_ctrl_base(struct brcm_sata_port *port) | |
140 | { | |
141 | struct brcm_sata_phy *priv = port->phy_priv; | |
142 | u32 size = 0; | |
143 | ||
144 | switch (priv->version) { | |
145 | case BRCM_SATA_PHY_IPROC_NS2: | |
146 | size = SATA_PHY_CTRL_REG_28NM_SPACE_SIZE; | |
147 | break; | |
148 | default: | |
c1602a1a | 149 | dev_err(priv->dev, "invalid phy version\n"); |
4faee9a4 AP |
150 | break; |
151 | }; | |
810c6f16 | 152 | |
4faee9a4 | 153 | return priv->ctrl_base + (port->portnum * size); |
0d486806 BN |
154 | } |
155 | ||
4faee9a4 AP |
156 | static void brcm_sata_phy_wr(void __iomem *pcb_base, u32 bank, |
157 | u32 ofs, u32 msk, u32 value) | |
0d486806 BN |
158 | { |
159 | u32 tmp; | |
160 | ||
4faee9a4 AP |
161 | writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); |
162 | tmp = readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); | |
0d486806 | 163 | tmp = (tmp & msk) | value; |
4faee9a4 AP |
164 | writel(tmp, pcb_base + SATA_PCB_REG_OFFSET(ofs)); |
165 | } | |
166 | ||
167 | static u32 brcm_sata_phy_rd(void __iomem *pcb_base, u32 bank, u32 ofs) | |
168 | { | |
169 | writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); | |
170 | return readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); | |
0d486806 BN |
171 | } |
172 | ||
173 | /* These defaults were characterized by H/W group */ | |
4faee9a4 AP |
174 | #define STB_FMIN_VAL_DEFAULT 0x3df |
175 | #define STB_FMAX_VAL_DEFAULT 0x3df | |
176 | #define STB_FMAX_VAL_SSC 0x83 | |
0d486806 | 177 | |
4faee9a4 | 178 | static int brcm_stb_sata_init(struct brcm_sata_port *port) |
0d486806 | 179 | { |
4faee9a4 | 180 | void __iomem *base = brcm_sata_pcb_base(port); |
0d486806 BN |
181 | struct brcm_sata_phy *priv = port->phy_priv; |
182 | u32 tmp; | |
183 | ||
184 | /* override the TX spread spectrum setting */ | |
185 | tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC; | |
4faee9a4 | 186 | brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp); |
0d486806 BN |
187 | |
188 | /* set fixed min freq */ | |
4faee9a4 AP |
189 | brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2, |
190 | ~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK, | |
191 | STB_FMIN_VAL_DEFAULT); | |
0d486806 BN |
192 | |
193 | /* set fixed max freq depending on SSC config */ | |
194 | if (port->ssc_en) { | |
4faee9a4 AP |
195 | dev_info(priv->dev, "enabling SSC on port%d\n", port->portnum); |
196 | tmp = STB_FMAX_VAL_SSC; | |
0d486806 | 197 | } else { |
4faee9a4 | 198 | tmp = STB_FMAX_VAL_DEFAULT; |
0d486806 BN |
199 | } |
200 | ||
4faee9a4 | 201 | brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3, |
0d486806 | 202 | ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp); |
4faee9a4 AP |
203 | |
204 | return 0; | |
205 | } | |
206 | ||
207 | /* NS2 SATA PLL1 defaults were characterized by H/W group */ | |
208 | #define NS2_PLL1_ACTRL2_MAGIC 0x1df8 | |
209 | #define NS2_PLL1_ACTRL3_MAGIC 0x2b00 | |
210 | #define NS2_PLL1_ACTRL4_MAGIC 0x8824 | |
211 | ||
212 | static int brcm_ns2_sata_init(struct brcm_sata_port *port) | |
213 | { | |
214 | int try; | |
215 | unsigned int val; | |
216 | void __iomem *base = brcm_sata_pcb_base(port); | |
217 | void __iomem *ctrl_base = brcm_sata_ctrl_base(port); | |
218 | struct device *dev = port->phy_priv->dev; | |
219 | ||
220 | /* Configure OOB control */ | |
221 | val = 0x0; | |
222 | val |= (0xc << OOB_CTRL1_BURST_MAX_SHIFT); | |
223 | val |= (0x4 << OOB_CTRL1_BURST_MIN_SHIFT); | |
224 | val |= (0x9 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT); | |
225 | val |= (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT); | |
226 | brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val); | |
227 | val = 0x0; | |
228 | val |= (0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT); | |
229 | val |= (0x2 << OOB_CTRL2_BURST_CNT_SHIFT); | |
230 | val |= (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT); | |
231 | brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val); | |
232 | ||
233 | /* Configure PHY PLL register bank 1 */ | |
234 | val = NS2_PLL1_ACTRL2_MAGIC; | |
235 | brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val); | |
236 | val = NS2_PLL1_ACTRL3_MAGIC; | |
237 | brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val); | |
238 | val = NS2_PLL1_ACTRL4_MAGIC; | |
239 | brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val); | |
240 | ||
241 | /* Configure PHY BLOCK0 register bank */ | |
242 | /* Set oob_clk_sel to refclk/2 */ | |
243 | brcm_sata_phy_wr(base, BLOCK0_REG_BANK, BLOCK0_SPARE, | |
244 | ~BLOCK0_SPARE_OOB_CLK_SEL_MASK, | |
245 | BLOCK0_SPARE_OOB_CLK_SEL_REFBY2); | |
246 | ||
247 | /* Strobe PHY reset using PHY control register */ | |
248 | writel(PHY_CTRL_1_RESET, ctrl_base + PHY_CTRL_1); | |
249 | mdelay(1); | |
250 | writel(0x0, ctrl_base + PHY_CTRL_1); | |
251 | mdelay(1); | |
252 | ||
253 | /* Wait for PHY PLL lock by polling pll_lock bit */ | |
254 | try = 50; | |
255 | while (try) { | |
256 | val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, | |
257 | BLOCK0_XGXSSTATUS); | |
258 | if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) | |
259 | break; | |
260 | msleep(20); | |
261 | try--; | |
262 | } | |
263 | if (!try) { | |
264 | /* PLL did not lock; give up */ | |
265 | dev_err(dev, "port%d PLL did not lock\n", port->portnum); | |
266 | return -ETIMEDOUT; | |
267 | } | |
268 | ||
269 | dev_dbg(dev, "port%d initialized\n", port->portnum); | |
270 | ||
271 | return 0; | |
0d486806 BN |
272 | } |
273 | ||
274 | static int brcm_sata_phy_init(struct phy *phy) | |
275 | { | |
4faee9a4 | 276 | int rc; |
0d486806 BN |
277 | struct brcm_sata_port *port = phy_get_drvdata(phy); |
278 | ||
4faee9a4 AP |
279 | switch (port->phy_priv->version) { |
280 | case BRCM_SATA_PHY_STB_28NM: | |
281 | case BRCM_SATA_PHY_STB_40NM: | |
282 | rc = brcm_stb_sata_init(port); | |
283 | break; | |
284 | case BRCM_SATA_PHY_IPROC_NS2: | |
285 | rc = brcm_ns2_sata_init(port); | |
286 | break; | |
287 | default: | |
288 | rc = -ENODEV; | |
289 | }; | |
0d486806 BN |
290 | |
291 | return 0; | |
292 | } | |
293 | ||
c1602a1a | 294 | static const struct phy_ops phy_ops = { |
0d486806 BN |
295 | .init = brcm_sata_phy_init, |
296 | .owner = THIS_MODULE, | |
297 | }; | |
298 | ||
299 | static const struct of_device_id brcm_sata_phy_of_match[] = { | |
810c6f16 | 300 | { .compatible = "brcm,bcm7445-sata-phy", |
4faee9a4 | 301 | .data = (void *)BRCM_SATA_PHY_STB_28NM }, |
c1602a1a | 302 | { .compatible = "brcm,bcm7425-sata-phy", |
4faee9a4 AP |
303 | .data = (void *)BRCM_SATA_PHY_STB_40NM }, |
304 | { .compatible = "brcm,iproc-ns2-sata-phy", | |
305 | .data = (void *)BRCM_SATA_PHY_IPROC_NS2 }, | |
0d486806 BN |
306 | {}, |
307 | }; | |
308 | MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); | |
309 | ||
310 | static int brcm_sata_phy_probe(struct platform_device *pdev) | |
311 | { | |
312 | struct device *dev = &pdev->dev; | |
313 | struct device_node *dn = dev->of_node, *child; | |
810c6f16 | 314 | const struct of_device_id *of_id; |
0d486806 BN |
315 | struct brcm_sata_phy *priv; |
316 | struct resource *res; | |
317 | struct phy_provider *provider; | |
0b25ff86 | 318 | int ret, count = 0; |
0d486806 BN |
319 | |
320 | if (of_get_child_count(dn) == 0) | |
321 | return -ENODEV; | |
322 | ||
323 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
324 | if (!priv) | |
325 | return -ENOMEM; | |
326 | dev_set_drvdata(dev, priv); | |
327 | priv->dev = dev; | |
328 | ||
329 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); | |
330 | priv->phy_base = devm_ioremap_resource(dev, res); | |
331 | if (IS_ERR(priv->phy_base)) | |
332 | return PTR_ERR(priv->phy_base); | |
333 | ||
810c6f16 JS |
334 | of_id = of_match_node(brcm_sata_phy_of_match, dn); |
335 | if (of_id) | |
336 | priv->version = (enum brcm_sata_phy_version)of_id->data; | |
337 | else | |
4faee9a4 AP |
338 | priv->version = BRCM_SATA_PHY_STB_28NM; |
339 | ||
340 | if (priv->version == BRCM_SATA_PHY_IPROC_NS2) { | |
341 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, | |
342 | "phy-ctrl"); | |
343 | priv->ctrl_base = devm_ioremap_resource(dev, res); | |
344 | if (IS_ERR(priv->ctrl_base)) | |
345 | return PTR_ERR(priv->ctrl_base); | |
346 | } | |
810c6f16 | 347 | |
0d486806 BN |
348 | for_each_available_child_of_node(dn, child) { |
349 | unsigned int id; | |
350 | struct brcm_sata_port *port; | |
351 | ||
352 | if (of_property_read_u32(child, "reg", &id)) { | |
353 | dev_err(dev, "missing reg property in node %s\n", | |
354 | child->name); | |
0b25ff86 JL |
355 | ret = -EINVAL; |
356 | goto put_child; | |
0d486806 BN |
357 | } |
358 | ||
359 | if (id >= MAX_PORTS) { | |
360 | dev_err(dev, "invalid reg: %u\n", id); | |
0b25ff86 JL |
361 | ret = -EINVAL; |
362 | goto put_child; | |
0d486806 BN |
363 | } |
364 | if (priv->phys[id].phy) { | |
365 | dev_err(dev, "already registered port %u\n", id); | |
0b25ff86 JL |
366 | ret = -EINVAL; |
367 | goto put_child; | |
0d486806 BN |
368 | } |
369 | ||
370 | port = &priv->phys[id]; | |
371 | port->portnum = id; | |
372 | port->phy_priv = priv; | |
c1602a1a | 373 | port->phy = devm_phy_create(dev, child, &phy_ops); |
0d486806 BN |
374 | port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); |
375 | if (IS_ERR(port->phy)) { | |
376 | dev_err(dev, "failed to create PHY\n"); | |
0b25ff86 JL |
377 | ret = PTR_ERR(port->phy); |
378 | goto put_child; | |
0d486806 BN |
379 | } |
380 | ||
381 | phy_set_drvdata(port->phy, port); | |
382 | count++; | |
383 | } | |
384 | ||
385 | provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | |
386 | if (IS_ERR(provider)) { | |
387 | dev_err(dev, "could not register PHY provider\n"); | |
388 | return PTR_ERR(provider); | |
389 | } | |
390 | ||
391 | dev_info(dev, "registered %d port(s)\n", count); | |
392 | ||
393 | return 0; | |
0b25ff86 JL |
394 | put_child: |
395 | of_node_put(child); | |
396 | return ret; | |
0d486806 BN |
397 | } |
398 | ||
399 | static struct platform_driver brcm_sata_phy_driver = { | |
400 | .probe = brcm_sata_phy_probe, | |
401 | .driver = { | |
402 | .of_match_table = brcm_sata_phy_of_match, | |
037c4189 | 403 | .name = "brcm-sata-phy", |
0d486806 BN |
404 | } |
405 | }; | |
406 | module_platform_driver(brcm_sata_phy_driver); | |
407 | ||
037c4189 | 408 | MODULE_DESCRIPTION("Broadcom SATA PHY driver"); |
0d486806 BN |
409 | MODULE_LICENSE("GPL"); |
410 | MODULE_AUTHOR("Marc Carino"); | |
411 | MODULE_AUTHOR("Brian Norris"); | |
037c4189 | 412 | MODULE_ALIAS("platform:phy-brcm-sata"); |