|
@@ -1,6 +1,6 @@
|
|
|
/*
|
|
|
+ * copyright (c) 2013 Freescale Semiconductor, Inc.
|
|
|
* Freescale IMX AHCI SATA platform driver
|
|
|
- * Copyright 2013 Freescale Semiconductor, Inc.
|
|
|
*
|
|
|
* based on the AHCI SATA platform driver by Jeff Garzik and Anton Vorontsov
|
|
|
*
|
|
@@ -25,10 +25,13 @@
|
|
|
#include <linux/of_device.h>
|
|
|
#include <linux/mfd/syscon.h>
|
|
|
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
|
|
|
+#include <linux/libata.h>
|
|
|
#include "ahci.h"
|
|
|
|
|
|
enum {
|
|
|
- HOST_TIMER1MS = 0xe0, /* Timer 1-ms */
|
|
|
+ PORT_PHY_CTL = 0x178, /* Port0 PHY Control */
|
|
|
+ PORT_PHY_CTL_PDDQ_LOC = 0x100000, /* PORT_PHY_CTL bits */
|
|
|
+ HOST_TIMER1MS = 0xe0, /* Timer 1-ms */
|
|
|
};
|
|
|
|
|
|
struct imx_ahci_priv {
|
|
@@ -36,6 +39,56 @@ struct imx_ahci_priv {
|
|
|
struct clk *sata_ref_clk;
|
|
|
struct clk *ahb_clk;
|
|
|
struct regmap *gpr;
|
|
|
+ bool no_device;
|
|
|
+ bool first_time;
|
|
|
+};
|
|
|
+
|
|
|
+static int ahci_imx_hotplug;
|
|
|
+module_param_named(hotplug, ahci_imx_hotplug, int, 0644);
|
|
|
+MODULE_PARM_DESC(hotplug, "AHCI IMX hot-plug support (0=Don't support, 1=support)");
|
|
|
+
|
|
|
+static void ahci_imx_error_handler(struct ata_port *ap)
|
|
|
+{
|
|
|
+ u32 reg_val;
|
|
|
+ struct ata_device *dev;
|
|
|
+ struct ata_host *host = dev_get_drvdata(ap->dev);
|
|
|
+ struct ahci_host_priv *hpriv = host->private_data;
|
|
|
+ void __iomem *mmio = hpriv->mmio;
|
|
|
+ struct imx_ahci_priv *imxpriv = dev_get_drvdata(ap->dev->parent);
|
|
|
+
|
|
|
+ ahci_error_handler(ap);
|
|
|
+
|
|
|
+ if (!(imxpriv->first_time) || ahci_imx_hotplug)
|
|
|
+ return;
|
|
|
+
|
|
|
+ imxpriv->first_time = false;
|
|
|
+
|
|
|
+ ata_for_each_dev(dev, &ap->link, ENABLED)
|
|
|
+ return;
|
|
|
+ /*
|
|
|
+ * Disable link to save power. An imx ahci port can't be recovered
|
|
|
+ * without full reset once the pddq mode is enabled making it
|
|
|
+ * impossible to use as part of libata LPM.
|
|
|
+ */
|
|
|
+ reg_val = readl(mmio + PORT_PHY_CTL);
|
|
|
+ writel(reg_val | PORT_PHY_CTL_PDDQ_LOC, mmio + PORT_PHY_CTL);
|
|
|
+ regmap_update_bits(imxpriv->gpr, IOMUXC_GPR13,
|
|
|
+ IMX6Q_GPR13_SATA_MPLL_CLK_EN,
|
|
|
+ !IMX6Q_GPR13_SATA_MPLL_CLK_EN);
|
|
|
+ clk_disable_unprepare(imxpriv->sata_ref_clk);
|
|
|
+ imxpriv->no_device = true;
|
|
|
+}
|
|
|
+
|
|
|
+static struct ata_port_operations ahci_imx_ops = {
|
|
|
+ .inherits = &ahci_platform_ops,
|
|
|
+ .error_handler = ahci_imx_error_handler,
|
|
|
+};
|
|
|
+
|
|
|
+static const struct ata_port_info ahci_imx_port_info = {
|
|
|
+ .flags = AHCI_FLAG_COMMON,
|
|
|
+ .pio_mask = ATA_PIO4,
|
|
|
+ .udma_mask = ATA_UDMA6,
|
|
|
+ .port_ops = &ahci_imx_ops,
|
|
|
};
|
|
|
|
|
|
static int imx6q_sata_init(struct device *dev, void __iomem *mmio)
|
|
@@ -117,9 +170,51 @@ static void imx6q_sata_exit(struct device *dev)
|
|
|
clk_disable_unprepare(imxpriv->sata_ref_clk);
|
|
|
}
|
|
|
|
|
|
+static int imx_ahci_suspend(struct device *dev)
|
|
|
+{
|
|
|
+ struct imx_ahci_priv *imxpriv = dev_get_drvdata(dev->parent);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If no_device is set, The CLKs had been gated off in the
|
|
|
+ * initialization so don't do it again here.
|
|
|
+ */
|
|
|
+ if (!imxpriv->no_device) {
|
|
|
+ regmap_update_bits(imxpriv->gpr, IOMUXC_GPR13,
|
|
|
+ IMX6Q_GPR13_SATA_MPLL_CLK_EN,
|
|
|
+ !IMX6Q_GPR13_SATA_MPLL_CLK_EN);
|
|
|
+ clk_disable_unprepare(imxpriv->sata_ref_clk);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int imx_ahci_resume(struct device *dev)
|
|
|
+{
|
|
|
+ struct imx_ahci_priv *imxpriv = dev_get_drvdata(dev->parent);
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!imxpriv->no_device) {
|
|
|
+ ret = clk_prepare_enable(imxpriv->sata_ref_clk);
|
|
|
+ if (ret < 0) {
|
|
|
+ dev_err(dev, "pre-enable sata_ref clock err:%d\n", ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ regmap_update_bits(imxpriv->gpr, IOMUXC_GPR13,
|
|
|
+ IMX6Q_GPR13_SATA_MPLL_CLK_EN,
|
|
|
+ IMX6Q_GPR13_SATA_MPLL_CLK_EN);
|
|
|
+ usleep_range(1000, 2000);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static struct ahci_platform_data imx6q_sata_pdata = {
|
|
|
.init = imx6q_sata_init,
|
|
|
.exit = imx6q_sata_exit,
|
|
|
+ .ata_port_info = &ahci_imx_port_info,
|
|
|
+ .suspend = imx_ahci_suspend,
|
|
|
+ .resume = imx_ahci_resume,
|
|
|
};
|
|
|
|
|
|
static const struct of_device_id imx_ahci_of_match[] = {
|
|
@@ -152,6 +247,8 @@ static int imx_ahci_probe(struct platform_device *pdev)
|
|
|
ahci_dev = &ahci_pdev->dev;
|
|
|
ahci_dev->parent = dev;
|
|
|
|
|
|
+ imxpriv->no_device = false;
|
|
|
+ imxpriv->first_time = true;
|
|
|
imxpriv->ahb_clk = devm_clk_get(dev, "ahb");
|
|
|
if (IS_ERR(imxpriv->ahb_clk)) {
|
|
|
dev_err(dev, "can't get ahb clock.\n");
|