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.h> |
19 | #include <linux/platform_device.h> |
20 | #include "ahci.h" |
21 | |
22 | #define DRV_NAME "ahci-mvebu" |
23 | |
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 | |
31 | struct ahci_mvebu_plat_data { |
32 | int (*plat_config)(struct ahci_host_priv *hpriv); |
33 | unsigned int flags; |
34 | }; |
35 | |
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(val: 0, addr: hpriv->mmio + AHCI_WINDOW_CTRL(i)); |
43 | writel(val: 0, addr: hpriv->mmio + AHCI_WINDOW_BASE(i)); |
44 | writel(val: 0, addr: 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(val: (cs->mbus_attr << 8) | |
51 | (dram->mbus_dram_target_id << 4) | 1, |
52 | addr: hpriv->mmio + AHCI_WINDOW_CTRL(i)); |
53 | writel(val: cs->base >> 16, addr: hpriv->mmio + AHCI_WINDOW_BASE(i)); |
54 | writel(val: ((cs->size - 1) & 0xffff0000), |
55 | addr: 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(val: 0x4, addr: hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_ADDR); |
67 | writel(val: 0x80, addr: hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_DATA); |
68 | } |
69 | |
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 | |
86 | static int ahci_mvebu_armada_3700_config(struct ahci_host_priv *hpriv) |
87 | { |
88 | u32 reg; |
89 | |
90 | writel(val: 0, addr: hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_ADDR); |
91 | |
92 | reg = readl(addr: hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_DATA); |
93 | reg |= BIT(6); |
94 | writel(val: reg, addr: hpriv->mmio + AHCI_VENDOR_SPECIFIC_0_DATA); |
95 | |
96 | return 0; |
97 | } |
98 | |
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 | */ |
119 | static int ahci_mvebu_stop_engine(struct ata_port *ap) |
120 | { |
121 | void __iomem *port_mmio = ahci_port_base(ap); |
122 | u32 tmp, port_fbs; |
123 | |
124 | tmp = readl(addr: 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(addr: port_mmio + PORT_FBS); |
132 | |
133 | /* setting HBA to idle */ |
134 | tmp &= ~PORT_CMD_START; |
135 | writel(val: tmp, addr: 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(val: port_fbs, addr: 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, reg: port_mmio + PORT_CMD, |
146 | mask: PORT_CMD_LIST_ON, val: PORT_CMD_LIST_ON, interval: 1, timeout: 500); |
147 | if (tmp & PORT_CMD_LIST_ON) |
148 | return -EIO; |
149 | |
150 | return 0; |
151 | } |
152 | |
153 | #ifdef CONFIG_PM_SLEEP |
154 | static int ahci_mvebu_suspend(struct platform_device *pdev, pm_message_t state) |
155 | { |
156 | return ahci_platform_suspend_host(dev: &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; |
163 | const struct ahci_mvebu_plat_data *pdata = hpriv->plat_data; |
164 | |
165 | pdata->plat_config(hpriv); |
166 | |
167 | return ahci_platform_resume_host(dev: &pdev->dev); |
168 | } |
169 | #else |
170 | #define ahci_mvebu_suspend NULL |
171 | #define ahci_mvebu_resume NULL |
172 | #endif |
173 | |
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 | |
181 | static const struct scsi_host_template ahci_platform_sht = { |
182 | AHCI_SHT(DRV_NAME), |
183 | }; |
184 | |
185 | static int ahci_mvebu_probe(struct platform_device *pdev) |
186 | { |
187 | const struct ahci_mvebu_plat_data *pdata; |
188 | struct ahci_host_priv *hpriv; |
189 | int rc; |
190 | |
191 | pdata = of_device_get_match_data(dev: &pdev->dev); |
192 | if (!pdata) |
193 | return -EINVAL; |
194 | |
195 | hpriv = ahci_platform_get_resources(pdev, flags: 0); |
196 | if (IS_ERR(ptr: hpriv)) |
197 | return PTR_ERR(ptr: hpriv); |
198 | |
199 | hpriv->flags |= pdata->flags; |
200 | hpriv->plat_data = (void *)pdata; |
201 | |
202 | rc = ahci_platform_enable_resources(hpriv); |
203 | if (rc) |
204 | return rc; |
205 | |
206 | hpriv->stop_engine = ahci_mvebu_stop_engine; |
207 | |
208 | rc = pdata->plat_config(hpriv); |
209 | if (rc) |
210 | goto disable_resources; |
211 | |
212 | rc = ahci_platform_init_host(pdev, hpriv, pi_template: &ahci_mvebu_port_info, |
213 | sht: &ahci_platform_sht); |
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 | |
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 = { |
229 | .plat_config = ahci_mvebu_armada_3700_config, |
230 | .flags = AHCI_HFLAG_SUSPEND_PHYS, |
231 | }; |
232 | |
233 | static const struct of_device_id ahci_mvebu_of_match[] = { |
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 | }, |
242 | { /* sentinel */ } |
243 | }; |
244 | MODULE_DEVICE_TABLE(of, ahci_mvebu_of_match); |
245 | |
246 | static struct platform_driver ahci_mvebu_driver = { |
247 | .probe = ahci_mvebu_probe, |
248 | .remove_new = ata_platform_remove_one, |
249 | .suspend = ahci_mvebu_suspend, |
250 | .resume = ahci_mvebu_resume, |
251 | .driver = { |
252 | .name = DRV_NAME, |
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 <thomas.petazzoni@free-electrons.com>, Marcin Wojtas <mw@semihalf.com>" ); |
260 | MODULE_LICENSE("GPL" ); |
261 | MODULE_ALIAS("platform:ahci_mvebu" ); |
262 | |