]>
Commit | Line | Data |
---|---|---|
a3464ed2 TP |
1 | /* |
2 | * AHCI glue platform driver for Marvell EBU SOCs | |
3 | * | |
4 | * Copyright (C) 2014 Marvell | |
5 | * | |
6 | * Thomas Petazzoni <[email protected]> | |
7 | * Marcin Wojtas <[email protected]> | |
8 | * | |
9 | * This file is licensed under the terms of the GNU General Public | |
10 | * License version 2. This program is licensed "as is" without any | |
11 | * warranty of any kind, whether express or implied. | |
12 | */ | |
13 | ||
14 | #include <linux/ahci_platform.h> | |
15 | #include <linux/kernel.h> | |
16 | #include <linux/mbus.h> | |
17 | #include <linux/module.h> | |
61e6ae71 | 18 | #include <linux/of.h> |
a3464ed2 TP |
19 | #include <linux/platform_device.h> |
20 | #include "ahci.h" | |
21 | ||
018d5ef2 AM |
22 | #define DRV_NAME "ahci-mvebu" |
23 | ||
a3464ed2 TP |
24 | #define AHCI_VENDOR_SPECIFIC_0_ADDR 0xa0 |
25 | #define AHCI_VENDOR_SPECIFIC_0_DATA 0xa4 | |
26 | ||
27 | #define AHCI_WINDOW_CTRL(win) (0x60 + ((win) << 4)) | |
28 | #define AHCI_WINDOW_BASE(win) (0x64 + ((win) << 4)) | |
29 | #define AHCI_WINDOW_SIZE(win) (0x68 + ((win) << 4)) | |
30 | ||
96dbcb40 MR |
31 | struct ahci_mvebu_plat_data { |
32 | int (*plat_config)(struct ahci_host_priv *hpriv); | |
bde0b5c1 | 33 | unsigned int flags; |
96dbcb40 MR |
34 | }; |
35 | ||
a3464ed2 TP |
36 | static void ahci_mvebu_mbus_config(struct ahci_host_priv *hpriv, |
37 | const struct mbus_dram_target_info *dram) | |
38 | { | |
39 | int i; | |
40 | ||
41 | for (i = 0; i < 4; i++) { | |
42 | writel(0, hpriv->mmio + AHCI_WINDOW_CTRL(i)); | |
43 | writel(0, hpriv->mmio + AHCI_WINDOW_BASE(i)); | |
44 | writel(0, hpriv->mmio + AHCI_WINDOW_SIZE(i)); | |
45 | } | |
46 | ||
47 | for (i = 0; i < dram->num_cs; i++) { | |
48 | const struct mbus_dram_window *cs = dram->cs + i; | |
49 | ||
50 | writel((cs->mbus_attr << 8) | | |
51 | (dram->mbus_dram_target_id << 4) | 1, | |
52 | hpriv->mmio + AHCI_WINDOW_CTRL(i)); | |
e96998fc | 53 | writel(cs->base >> 16, hpriv->mmio + AHCI_WINDOW_BASE(i)); |
a3464ed2 TP |
54 | writel(((cs->size - 1) & 0xffff0000), |
55 | hpriv->mmio + AHCI_WINDOW_SIZE(i)); | |
56 | } | |
57 | } | |
58 | ||
59 | static void ahci_mvebu_regret_option(struct ahci_host_priv *hpriv) | |
60 | { | |
61 | /* | |
62 | * Enable the regret bit to allow the SATA unit to regret a | |
63 | * request that didn't receive an acknowlegde and avoid a | |
64 | * deadlock | |
65 | */ | |
66 | writel(0x4, hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_ADDR); | |
67 | writel(0x80, hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_DATA); | |
68 | } | |
69 | ||
96dbcb40 MR |
70 | static int ahci_mvebu_armada_380_config(struct ahci_host_priv *hpriv) |
71 | { | |
72 | const struct mbus_dram_target_info *dram; | |
73 | int rc = 0; | |
74 | ||
75 | dram = mv_mbus_dram_info(); | |
76 | if (dram) | |
77 | ahci_mvebu_mbus_config(hpriv, dram); | |
78 | else | |
79 | rc = -ENODEV; | |
80 | ||
81 | ahci_mvebu_regret_option(hpriv); | |
82 | ||
83 | return rc; | |
84 | } | |
85 | ||
2f558bc3 MR |
86 | static int ahci_mvebu_armada_3700_config(struct ahci_host_priv *hpriv) |
87 | { | |
88 | u32 reg; | |
89 | ||
90 | writel(0, hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_ADDR); | |
91 | ||
92 | reg = readl(hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_DATA); | |
93 | reg |= BIT(6); | |
94 | writel(reg, hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_DATA); | |
95 | ||
96 | return 0; | |
97 | } | |
98 | ||
daa2e3bd EW |
99 | /** |
100 | * ahci_mvebu_stop_engine | |
101 | * | |
102 | * @ap: Target ata port | |
103 | * | |
104 | * Errata Ref#226 - SATA Disk HOT swap issue when connected through | |
105 | * Port Multiplier in FIS-based Switching mode. | |
106 | * | |
107 | * To avoid the issue, according to design, the bits[11:8, 0] of | |
108 | * register PxFBS are cleared when Port Command and Status (0x18) bit[0] | |
109 | * changes its value from 1 to 0, i.e. falling edge of Port | |
110 | * Command and Status bit[0] sends PULSE that resets PxFBS | |
111 | * bits[11:8; 0]. | |
112 | * | |
113 | * This function is used to override function of "ahci_stop_engine" | |
114 | * from libahci.c by adding the mvebu work around(WA) to save PxFBS | |
115 | * value before the PxCMD ST write of 0, then restore PxFBS value. | |
116 | * | |
117 | * Return: 0 on success; Error code otherwise. | |
118 | */ | |
95ffcf47 | 119 | static int ahci_mvebu_stop_engine(struct ata_port *ap) |
daa2e3bd EW |
120 | { |
121 | void __iomem *port_mmio = ahci_port_base(ap); | |
122 | u32 tmp, port_fbs; | |
123 | ||
124 | tmp = readl(port_mmio + PORT_CMD); | |
125 | ||
126 | /* check if the HBA is idle */ | |
127 | if ((tmp & (PORT_CMD_START | PORT_CMD_LIST_ON)) == 0) | |
128 | return 0; | |
129 | ||
130 | /* save the port PxFBS register for later restore */ | |
131 | port_fbs = readl(port_mmio + PORT_FBS); | |
132 | ||
133 | /* setting HBA to idle */ | |
134 | tmp &= ~PORT_CMD_START; | |
135 | writel(tmp, port_mmio + PORT_CMD); | |
136 | ||
137 | /* | |
138 | * bit #15 PxCMD signal doesn't clear PxFBS, | |
139 | * restore the PxFBS register right after clearing the PxCMD ST, | |
140 | * no need to wait for the PxCMD bit #15. | |
141 | */ | |
142 | writel(port_fbs, port_mmio + PORT_FBS); | |
143 | ||
144 | /* wait for engine to stop. This could be as long as 500 msec */ | |
145 | tmp = ata_wait_register(ap, port_mmio + PORT_CMD, | |
146 | PORT_CMD_LIST_ON, PORT_CMD_LIST_ON, 1, 500); | |
147 | if (tmp & PORT_CMD_LIST_ON) | |
148 | return -EIO; | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
4f1dd973 | 153 | #ifdef CONFIG_PM_SLEEP |
d6ecf158 TP |
154 | static int ahci_mvebu_suspend(struct platform_device *pdev, pm_message_t state) |
155 | { | |
156 | return ahci_platform_suspend_host(&pdev->dev); | |
157 | } | |
158 | ||
159 | static int ahci_mvebu_resume(struct platform_device *pdev) | |
160 | { | |
161 | struct ata_host *host = platform_get_drvdata(pdev); | |
162 | struct ahci_host_priv *hpriv = host->private_data; | |
96dbcb40 | 163 | const struct ahci_mvebu_plat_data *pdata = hpriv->plat_data; |
d6ecf158 | 164 | |
2f558bc3 | 165 | pdata->plat_config(hpriv); |
d6ecf158 TP |
166 | |
167 | return ahci_platform_resume_host(&pdev->dev); | |
168 | } | |
4f1dd973 AB |
169 | #else |
170 | #define ahci_mvebu_suspend NULL | |
171 | #define ahci_mvebu_resume NULL | |
172 | #endif | |
d6ecf158 | 173 | |
a3464ed2 TP |
174 | static const struct ata_port_info ahci_mvebu_port_info = { |
175 | .flags = AHCI_FLAG_COMMON, | |
176 | .pio_mask = ATA_PIO4, | |
177 | .udma_mask = ATA_UDMA6, | |
178 | .port_ops = &ahci_platform_ops, | |
179 | }; | |
180 | ||
25df73d9 | 181 | static const struct scsi_host_template ahci_platform_sht = { |
018d5ef2 AM |
182 | AHCI_SHT(DRV_NAME), |
183 | }; | |
184 | ||
a3464ed2 TP |
185 | static int ahci_mvebu_probe(struct platform_device *pdev) |
186 | { | |
96dbcb40 | 187 | const struct ahci_mvebu_plat_data *pdata; |
a3464ed2 | 188 | struct ahci_host_priv *hpriv; |
a3464ed2 TP |
189 | int rc; |
190 | ||
96dbcb40 MR |
191 | pdata = of_device_get_match_data(&pdev->dev); |
192 | if (!pdata) | |
193 | return -EINVAL; | |
194 | ||
16af2d65 | 195 | hpriv = ahci_platform_get_resources(pdev, 0); |
a3464ed2 TP |
196 | if (IS_ERR(hpriv)) |
197 | return PTR_ERR(hpriv); | |
198 | ||
bde0b5c1 | 199 | hpriv->flags |= pdata->flags; |
96dbcb40 MR |
200 | hpriv->plat_data = (void *)pdata; |
201 | ||
a3464ed2 TP |
202 | rc = ahci_platform_enable_resources(hpriv); |
203 | if (rc) | |
204 | return rc; | |
205 | ||
daa2e3bd EW |
206 | hpriv->stop_engine = ahci_mvebu_stop_engine; |
207 | ||
2f558bc3 MR |
208 | rc = pdata->plat_config(hpriv); |
209 | if (rc) | |
210 | goto disable_resources; | |
a3464ed2 | 211 | |
018d5ef2 AM |
212 | rc = ahci_platform_init_host(pdev, hpriv, &ahci_mvebu_port_info, |
213 | &ahci_platform_sht); | |
a3464ed2 TP |
214 | if (rc) |
215 | goto disable_resources; | |
216 | ||
217 | return 0; | |
218 | ||
219 | disable_resources: | |
220 | ahci_platform_disable_resources(hpriv); | |
221 | return rc; | |
222 | } | |
223 | ||
96dbcb40 MR |
224 | static const struct ahci_mvebu_plat_data ahci_mvebu_armada_380_plat_data = { |
225 | .plat_config = ahci_mvebu_armada_380_config, | |
226 | }; | |
227 | ||
228 | static const struct ahci_mvebu_plat_data ahci_mvebu_armada_3700_plat_data = { | |
2f558bc3 | 229 | .plat_config = ahci_mvebu_armada_3700_config, |
ee995101 | 230 | .flags = AHCI_HFLAG_SUSPEND_PHYS, |
96dbcb40 MR |
231 | }; |
232 | ||
a3464ed2 | 233 | static const struct of_device_id ahci_mvebu_of_match[] = { |
96dbcb40 MR |
234 | { |
235 | .compatible = "marvell,armada-380-ahci", | |
236 | .data = &ahci_mvebu_armada_380_plat_data, | |
237 | }, | |
238 | { | |
239 | .compatible = "marvell,armada-3700-ahci", | |
240 | .data = &ahci_mvebu_armada_3700_plat_data, | |
241 | }, | |
5e776d7b | 242 | { /* sentinel */ } |
a3464ed2 TP |
243 | }; |
244 | MODULE_DEVICE_TABLE(of, ahci_mvebu_of_match); | |
245 | ||
a3464ed2 TP |
246 | static struct platform_driver ahci_mvebu_driver = { |
247 | .probe = ahci_mvebu_probe, | |
2d910fe1 | 248 | .remove = ata_platform_remove_one, |
d6ecf158 TP |
249 | .suspend = ahci_mvebu_suspend, |
250 | .resume = ahci_mvebu_resume, | |
a3464ed2 | 251 | .driver = { |
018d5ef2 | 252 | .name = DRV_NAME, |
a3464ed2 TP |
253 | .of_match_table = ahci_mvebu_of_match, |
254 | }, | |
255 | }; | |
256 | module_platform_driver(ahci_mvebu_driver); | |
257 | ||
258 | MODULE_DESCRIPTION("Marvell EBU AHCI SATA driver"); | |
259 | MODULE_AUTHOR("Thomas Petazzoni <[email protected]>, Marcin Wojtas <[email protected]>"); | |
260 | MODULE_LICENSE("GPL"); | |
261 | MODULE_ALIAS("platform:ahci_mvebu"); |