Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/ata/ahci_mvebu.c
26278 views
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>
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(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));
53
writel(cs->base >> 16, hpriv->mmio + AHCI_WINDOW_BASE(i));
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
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(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
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(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
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(&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(&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(&pdev->dev);
192
if (!pdata)
193
return -EINVAL;
194
195
hpriv = ahci_platform_get_resources(pdev, 0);
196
if (IS_ERR(hpriv))
197
return PTR_ERR(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, &ahci_mvebu_port_info,
213
&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 = 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 <[email protected]>, Marcin Wojtas <[email protected]>");
260
MODULE_LICENSE("GPL");
261
MODULE_ALIAS("platform:ahci_mvebu");
262
263