1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Allwinner sunxi AHCI SATA platform driver |
4 | * Copyright 2013 Olliver Schinagl <oliver@schinagl.nl> |
5 | * Copyright 2014 Hans de Goede <hdegoede@redhat.com> |
6 | * |
7 | * based on the AHCI SATA platform driver by Jeff Garzik and Anton Vorontsov |
8 | * Based on code from Allwinner Technology Co., Ltd. <www.allwinnertech.com>, |
9 | * Daniel Wang <danielwang@allwinnertech.com> |
10 | */ |
11 | |
12 | #include <linux/ahci_platform.h> |
13 | #include <linux/clk.h> |
14 | #include <linux/errno.h> |
15 | #include <linux/kernel.h> |
16 | #include <linux/mod_devicetable.h> |
17 | #include <linux/module.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/regulator/consumer.h> |
20 | #include "ahci.h" |
21 | |
22 | #define DRV_NAME "ahci-sunxi" |
23 | |
24 | /* Insmod parameters */ |
25 | static bool enable_pmp; |
26 | module_param(enable_pmp, bool, 0); |
27 | MODULE_PARM_DESC(enable_pmp, |
28 | "Enable support for sata port multipliers, only use if you use a pmp!" ); |
29 | |
30 | #define AHCI_BISTAFR 0x00a0 |
31 | #define AHCI_BISTCR 0x00a4 |
32 | #define AHCI_BISTFCTR 0x00a8 |
33 | #define AHCI_BISTSR 0x00ac |
34 | #define AHCI_BISTDECR 0x00b0 |
35 | #define AHCI_DIAGNR0 0x00b4 |
36 | #define AHCI_DIAGNR1 0x00b8 |
37 | #define AHCI_OOBR 0x00bc |
38 | #define AHCI_PHYCS0R 0x00c0 |
39 | #define AHCI_PHYCS1R 0x00c4 |
40 | #define AHCI_PHYCS2R 0x00c8 |
41 | #define AHCI_TIMER1MS 0x00e0 |
42 | #define AHCI_GPARAM1R 0x00e8 |
43 | #define AHCI_GPARAM2R 0x00ec |
44 | #define AHCI_PPARAMR 0x00f0 |
45 | #define AHCI_TESTR 0x00f4 |
46 | #define AHCI_VERSIONR 0x00f8 |
47 | #define AHCI_IDR 0x00fc |
48 | #define AHCI_RWCR 0x00fc |
49 | #define AHCI_P0DMACR 0x0170 |
50 | #define AHCI_P0PHYCR 0x0178 |
51 | #define AHCI_P0PHYSR 0x017c |
52 | |
53 | static void sunxi_clrbits(void __iomem *reg, u32 clr_val) |
54 | { |
55 | u32 reg_val; |
56 | |
57 | reg_val = readl(addr: reg); |
58 | reg_val &= ~(clr_val); |
59 | writel(val: reg_val, addr: reg); |
60 | } |
61 | |
62 | static void sunxi_setbits(void __iomem *reg, u32 set_val) |
63 | { |
64 | u32 reg_val; |
65 | |
66 | reg_val = readl(addr: reg); |
67 | reg_val |= set_val; |
68 | writel(val: reg_val, addr: reg); |
69 | } |
70 | |
71 | static void sunxi_clrsetbits(void __iomem *reg, u32 clr_val, u32 set_val) |
72 | { |
73 | u32 reg_val; |
74 | |
75 | reg_val = readl(addr: reg); |
76 | reg_val &= ~(clr_val); |
77 | reg_val |= set_val; |
78 | writel(val: reg_val, addr: reg); |
79 | } |
80 | |
81 | static u32 sunxi_getbits(void __iomem *reg, u8 mask, u8 shift) |
82 | { |
83 | return (readl(addr: reg) >> shift) & mask; |
84 | } |
85 | |
86 | static int ahci_sunxi_phy_init(struct device *dev, void __iomem *reg_base) |
87 | { |
88 | u32 reg_val; |
89 | int timeout; |
90 | |
91 | /* This magic is from the original code */ |
92 | writel(val: 0, addr: reg_base + AHCI_RWCR); |
93 | msleep(msecs: 5); |
94 | |
95 | sunxi_setbits(reg: reg_base + AHCI_PHYCS1R, BIT(19)); |
96 | sunxi_clrsetbits(reg: reg_base + AHCI_PHYCS0R, |
97 | clr_val: (0x7 << 24), |
98 | set_val: (0x5 << 24) | BIT(23) | BIT(18)); |
99 | sunxi_clrsetbits(reg: reg_base + AHCI_PHYCS1R, |
100 | clr_val: (0x3 << 16) | (0x1f << 8) | (0x3 << 6), |
101 | set_val: (0x2 << 16) | (0x6 << 8) | (0x2 << 6)); |
102 | sunxi_setbits(reg: reg_base + AHCI_PHYCS1R, BIT(28) | BIT(15)); |
103 | sunxi_clrbits(reg: reg_base + AHCI_PHYCS1R, BIT(19)); |
104 | sunxi_clrsetbits(reg: reg_base + AHCI_PHYCS0R, |
105 | clr_val: (0x7 << 20), set_val: (0x3 << 20)); |
106 | sunxi_clrsetbits(reg: reg_base + AHCI_PHYCS2R, |
107 | clr_val: (0x1f << 5), set_val: (0x19 << 5)); |
108 | msleep(msecs: 5); |
109 | |
110 | sunxi_setbits(reg: reg_base + AHCI_PHYCS0R, set_val: (0x1 << 19)); |
111 | |
112 | timeout = 250; /* Power up takes aprox 50 us */ |
113 | do { |
114 | reg_val = sunxi_getbits(reg: reg_base + AHCI_PHYCS0R, mask: 0x7, shift: 28); |
115 | if (reg_val == 0x02) |
116 | break; |
117 | |
118 | if (--timeout == 0) { |
119 | dev_err(dev, "PHY power up failed.\n" ); |
120 | return -EIO; |
121 | } |
122 | udelay(1); |
123 | } while (1); |
124 | |
125 | sunxi_setbits(reg: reg_base + AHCI_PHYCS2R, set_val: (0x1 << 24)); |
126 | |
127 | timeout = 100; /* Calibration takes aprox 10 us */ |
128 | do { |
129 | reg_val = sunxi_getbits(reg: reg_base + AHCI_PHYCS2R, mask: 0x1, shift: 24); |
130 | if (reg_val == 0x00) |
131 | break; |
132 | |
133 | if (--timeout == 0) { |
134 | dev_err(dev, "PHY calibration failed.\n" ); |
135 | return -EIO; |
136 | } |
137 | udelay(1); |
138 | } while (1); |
139 | |
140 | msleep(msecs: 15); |
141 | |
142 | writel(val: 0x7, addr: reg_base + AHCI_RWCR); |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static void ahci_sunxi_start_engine(struct ata_port *ap) |
148 | { |
149 | void __iomem *port_mmio = ahci_port_base(ap); |
150 | struct ahci_host_priv *hpriv = ap->host->private_data; |
151 | |
152 | /* Setup DMA before DMA start |
153 | * |
154 | * NOTE: A similar SoC with SATA/AHCI by Texas Instruments documents |
155 | * this Vendor Specific Port (P0DMACR, aka PxDMACR) in its |
156 | * User's Guide document (TMS320C674x/OMAP-L1x Processor |
157 | * Serial ATA (SATA) Controller, Literature Number: SPRUGJ8C, |
158 | * March 2011, Chapter 4.33 Port DMA Control Register (P0DMACR), |
159 | * p.68, https://www.ti.com/lit/ug/sprugj8c/sprugj8c.pdf) |
160 | * as equivalent to the following struct: |
161 | * |
162 | * struct AHCI_P0DMACR_t |
163 | * { |
164 | * unsigned TXTS : 4; |
165 | * unsigned RXTS : 4; |
166 | * unsigned TXABL : 4; |
167 | * unsigned RXABL : 4; |
168 | * unsigned Reserved : 16; |
169 | * }; |
170 | * |
171 | * TXTS: Transmit Transaction Size (TX_TRANSACTION_SIZE). |
172 | * This field defines the DMA transaction size in DWORDs for |
173 | * transmit (system bus read, device write) operation. [...] |
174 | * |
175 | * RXTS: Receive Transaction Size (RX_TRANSACTION_SIZE). |
176 | * This field defines the Port DMA transaction size in DWORDs |
177 | * for receive (system bus write, device read) operation. [...] |
178 | * |
179 | * TXABL: Transmit Burst Limit. |
180 | * This field allows software to limit the VBUSP master read |
181 | * burst size. [...] |
182 | * |
183 | * RXABL: Receive Burst Limit. |
184 | * Allows software to limit the VBUSP master write burst |
185 | * size. [...] |
186 | * |
187 | * Reserved: Reserved. |
188 | * |
189 | * |
190 | * NOTE: According to the above document, the following alternative |
191 | * to the code below could perhaps be a better option |
192 | * (or preparation) for possible further improvements later: |
193 | * sunxi_clrsetbits(hpriv->mmio + AHCI_P0DMACR, 0x0000ffff, |
194 | * 0x00000033); |
195 | */ |
196 | sunxi_clrsetbits(reg: hpriv->mmio + AHCI_P0DMACR, clr_val: 0x0000ffff, set_val: 0x00004433); |
197 | |
198 | /* Start DMA */ |
199 | sunxi_setbits(reg: port_mmio + PORT_CMD, set_val: PORT_CMD_START); |
200 | } |
201 | |
202 | static const struct ata_port_info ahci_sunxi_port_info = { |
203 | .flags = AHCI_FLAG_COMMON | ATA_FLAG_NCQ | ATA_FLAG_NO_DIPM, |
204 | .pio_mask = ATA_PIO4, |
205 | .udma_mask = ATA_UDMA6, |
206 | .port_ops = &ahci_platform_ops, |
207 | }; |
208 | |
209 | static const struct scsi_host_template ahci_platform_sht = { |
210 | AHCI_SHT(DRV_NAME), |
211 | }; |
212 | |
213 | static int ahci_sunxi_probe(struct platform_device *pdev) |
214 | { |
215 | struct device *dev = &pdev->dev; |
216 | struct ahci_host_priv *hpriv; |
217 | int rc; |
218 | |
219 | hpriv = ahci_platform_get_resources(pdev, AHCI_PLATFORM_GET_RESETS); |
220 | if (IS_ERR(ptr: hpriv)) |
221 | return PTR_ERR(ptr: hpriv); |
222 | |
223 | hpriv->start_engine = ahci_sunxi_start_engine; |
224 | |
225 | rc = ahci_platform_enable_resources(hpriv); |
226 | if (rc) |
227 | return rc; |
228 | |
229 | rc = ahci_sunxi_phy_init(dev, reg_base: hpriv->mmio); |
230 | if (rc) |
231 | goto disable_resources; |
232 | |
233 | hpriv->flags = AHCI_HFLAG_32BIT_ONLY | AHCI_HFLAG_NO_MSI | |
234 | AHCI_HFLAG_YES_NCQ; |
235 | |
236 | /* |
237 | * The sunxi sata controller seems to be unable to successfully do a |
238 | * soft reset if no pmp is attached, so disable pmp use unless |
239 | * requested, otherwise directly attached disks do not work. |
240 | */ |
241 | if (!enable_pmp) |
242 | hpriv->flags |= AHCI_HFLAG_NO_PMP; |
243 | |
244 | rc = ahci_platform_init_host(pdev, hpriv, pi_template: &ahci_sunxi_port_info, |
245 | sht: &ahci_platform_sht); |
246 | if (rc) |
247 | goto disable_resources; |
248 | |
249 | return 0; |
250 | |
251 | disable_resources: |
252 | ahci_platform_disable_resources(hpriv); |
253 | return rc; |
254 | } |
255 | |
256 | #ifdef CONFIG_PM_SLEEP |
257 | static int ahci_sunxi_resume(struct device *dev) |
258 | { |
259 | struct ata_host *host = dev_get_drvdata(dev); |
260 | struct ahci_host_priv *hpriv = host->private_data; |
261 | int rc; |
262 | |
263 | rc = ahci_platform_enable_resources(hpriv); |
264 | if (rc) |
265 | return rc; |
266 | |
267 | rc = ahci_sunxi_phy_init(dev, reg_base: hpriv->mmio); |
268 | if (rc) |
269 | goto disable_resources; |
270 | |
271 | rc = ahci_platform_resume_host(dev); |
272 | if (rc) |
273 | goto disable_resources; |
274 | |
275 | return 0; |
276 | |
277 | disable_resources: |
278 | ahci_platform_disable_resources(hpriv); |
279 | return rc; |
280 | } |
281 | #endif |
282 | |
283 | static SIMPLE_DEV_PM_OPS(ahci_sunxi_pm_ops, ahci_platform_suspend, |
284 | ahci_sunxi_resume); |
285 | |
286 | static const struct of_device_id ahci_sunxi_of_match[] = { |
287 | { .compatible = "allwinner,sun4i-a10-ahci" , }, |
288 | { .compatible = "allwinner,sun8i-r40-ahci" , }, |
289 | { /* sentinel */ } |
290 | }; |
291 | MODULE_DEVICE_TABLE(of, ahci_sunxi_of_match); |
292 | |
293 | static struct platform_driver ahci_sunxi_driver = { |
294 | .probe = ahci_sunxi_probe, |
295 | .remove_new = ata_platform_remove_one, |
296 | .driver = { |
297 | .name = DRV_NAME, |
298 | .of_match_table = ahci_sunxi_of_match, |
299 | .pm = &ahci_sunxi_pm_ops, |
300 | }, |
301 | }; |
302 | module_platform_driver(ahci_sunxi_driver); |
303 | |
304 | MODULE_DESCRIPTION("Allwinner sunxi AHCI SATA driver" ); |
305 | MODULE_AUTHOR("Olliver Schinagl <oliver@schinagl.nl>" ); |
306 | MODULE_LICENSE("GPL" ); |
307 | |