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