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