ahci_mvebu.c (6817B)
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 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 31struct ahci_mvebu_plat_data { 32 int (*plat_config)(struct ahci_host_priv *hpriv); 33 unsigned int flags; 34}; 35 36static 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 59static 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 70static 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 86static 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 */ 119static 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 154static int ahci_mvebu_suspend(struct platform_device *pdev, pm_message_t state) 155{ 156 return ahci_platform_suspend_host(&pdev->dev); 157} 158 159static 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 174static 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 181static struct scsi_host_template ahci_platform_sht = { 182 AHCI_SHT(DRV_NAME), 183}; 184 185static 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 219disable_resources: 220 ahci_platform_disable_resources(hpriv); 221 return rc; 222} 223 224static const struct ahci_mvebu_plat_data ahci_mvebu_armada_380_plat_data = { 225 .plat_config = ahci_mvebu_armada_380_config, 226}; 227 228static 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 233static 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}; 244MODULE_DEVICE_TABLE(of, ahci_mvebu_of_match); 245 246static 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}; 256module_platform_driver(ahci_mvebu_driver); 257 258MODULE_DESCRIPTION("Marvell EBU AHCI SATA driver"); 259MODULE_AUTHOR("Thomas Petazzoni <thomas.petazzoni@free-electrons.com>, Marcin Wojtas <mw@semihalf.com>"); 260MODULE_LICENSE("GPL"); 261MODULE_ALIAS("platform:ahci_mvebu");