imx6q contains one Synopsys AHCI SATA controller, But it can't shares ahci_platform driver with other controllers. Because there are some misalignments of the bits definitions of the HBA registers and the Vendor Specific registers - CAP_SSS(bit20) of the HOST_CAP is writable, default value is '0', should be configured to be '1' - bit0 (only one AHCI SATA port on imx6q) of the HOST_PORTS_IMPL should be set to be '1'.(default 0) - One Vendor Specific register HOST_TIMER1MS(offset:0xe0) should be configured regarding to the frequency of AHB bus clock. Setup its own ahci sata driver, enable the imx6q ahci sata support, and update the ahci sata binding document. Signed-off-by: Richard Zhu <r65037@xxxxxxxxxxxxx> --- .../devicetree/bindings/ata/ahci-platform.txt | 2 +- drivers/ata/Kconfig | 8 + drivers/ata/Makefile | 1 + drivers/ata/sata_imx.c | 349 ++++++++++++++++++++ 4 files changed, 359 insertions(+), 1 deletions(-) create mode 100644 drivers/ata/sata_imx.c diff --git a/Documentation/devicetree/bindings/ata/ahci-platform.txt b/Documentation/devicetree/bindings/ata/ahci-platform.txt index b519f9b..e252620 100644 --- a/Documentation/devicetree/bindings/ata/ahci-platform.txt +++ b/Documentation/devicetree/bindings/ata/ahci-platform.txt @@ -4,7 +4,7 @@ SATA nodes are defined to describe on-chip Serial ATA controllers. Each SATA controller should have its own node. Required properties: -- compatible : compatible list, contains "calxeda,hb-ahci" or "snps,spear-ahci" +- compatible : compatible list, contains "calxeda,hb-ahci", "snps,spear-ahci" or "snps, imx-ahci" - interrupts : <interrupt mapping for SATA IRQ> - reg : <registers mapping> diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index a5a3ebc..893fa0b 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -236,6 +236,14 @@ config SATA_HIGHBANK If unsure, say N. +config SATA_IMX + tristate "Freescale iMX AHCI SATA support" + help + This option enables support for the Freescale iMX SoC's + onboard AHCI SATA. + + If unsure, say N. + config SATA_MV tristate "Marvell SATA support" help diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index c04d0fd..c40b328 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_SATA_INIC162X) += sata_inic162x.o obj-$(CONFIG_SATA_SIL24) += sata_sil24.o obj-$(CONFIG_SATA_DWC) += sata_dwc_460ex.o obj-$(CONFIG_SATA_HIGHBANK) += sata_highbank.o libahci.o +obj-$(CONFIG_SATA_IMX) += sata_imx.o libahci.o # SFF w/ custom DMA obj-$(CONFIG_PDC_ADMA) += pdc_adma.o diff --git a/drivers/ata/sata_imx.c b/drivers/ata/sata_imx.c new file mode 100644 index 0000000..2be92e8 --- /dev/null +++ b/drivers/ata/sata_imx.c @@ -0,0 +1,349 @@ +/* + * Freescale IMX AHCI SATA platform driver + * Copyright 2013 Freescale Semiconductor, Inc. + * + * based on the AHCI SATA platform driver by Jeff Garzik and Anton Vorontsov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/gfp.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/libata.h> +#include <linux/ahci_platform.h> +#include "ahci.h" + +enum { + HOST_TIMER1MS = 0xe0, /* Timer 1-ms */ +}; + +static void ahci_imx_host_stop(struct ata_host *host); + +static struct ata_port_operations ahci_imx_platform_ops = { + .inherits = &ahci_ops, + .host_stop = ahci_imx_host_stop, +}; + +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_platform_ops, +}; + +static struct scsi_host_template ahci_imx_platform_sht = { + AHCI_SHT("sata_imx"), +}; + +/* + * Configure the HWINIT bits of the HOST_CAP and HOST_PORTS_IMPL, + * and IP vendor specific register HOST_TIMER1MS. + * + * Configure CAP_SSS (support stagered spin up). + * Implement the port0. + * Get the ahb clock rate, and configure the TIMER1MS register. + */ +static int imx_sata_init(void __iomem *mmio) +{ + int ret; + struct clk *ahb_clk; + + ret = readl(mmio + HOST_CAP); + if (!(ret & HOST_CAP_SSS)) + writel(ret |= HOST_CAP_SSS, mmio + HOST_CAP); + ret = readl(mmio + HOST_PORTS_IMPL); + if (!(ret & 0x1)) + writel((ret | 0x1), mmio + HOST_PORTS_IMPL); + ahb_clk = clk_get_sys(NULL, "ahb"); + if (IS_ERR(ahb_clk)) { + pr_err("no ahb clock.\n"); + ret = PTR_ERR(ahb_clk); + return ret; + } + ret = clk_get_rate(ahb_clk) / 1000; + clk_put(ahb_clk); + writel(ret, mmio + HOST_TIMER1MS); + + return ret; +} + +static int ahci_imx_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ahci_platform_data *pdata = dev_get_platdata(dev); + struct ata_port_info pi = ahci_imx_port_info; + const struct ata_port_info *ppi[] = { &pi, NULL }; + struct ahci_host_priv *hpriv; + struct ata_host *host; + struct resource *mem; + int irq; + int n_ports; + int i; + int rc; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(dev, "no mmio space\n"); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(dev, "no irq\n"); + return -EINVAL; + } + + if (pdata && pdata->ata_port_info) + pi = *pdata->ata_port_info; + + hpriv = devm_kzalloc(dev, sizeof(*hpriv), GFP_KERNEL); + if (!hpriv) { + dev_err(dev, "can't alloc ahci_host_priv\n"); + return -ENOMEM; + } + + hpriv->flags |= (unsigned long)pi.private_data; + + hpriv->mmio = devm_ioremap(dev, mem->start, resource_size(mem)); + if (!hpriv->mmio) { + dev_err(dev, "can't map %pR\n", mem); + return -ENOMEM; + } + + hpriv->clk = clk_get(dev, NULL); + if (IS_ERR(hpriv->clk)) { + dev_err(dev, "can't get clock\n"); + } else { + rc = clk_prepare_enable(hpriv->clk); + if (rc) { + dev_err(dev, "clock prepare enable failed"); + goto free_clk; + } + } + + /* + * Some platforms might need to prepare for mmio region access, + * which could be done in the following init call. So, the mmio + * region shouldn't be accessed before init (if provided) has + * returned successfully. + */ + if (pdata && pdata->init) { + rc = pdata->init(dev, hpriv->mmio); + if (rc) + goto disable_unprepare_clk; + } + + rc = imx_sata_init(hpriv->mmio); + if (rc < 0) + goto pdata_exit; + + ahci_save_initial_config(dev, hpriv, + pdata ? pdata->force_port_map : 0, + pdata ? pdata->mask_port_map : 0); + + /* prepare host */ + if (hpriv->cap & HOST_CAP_NCQ) + pi.flags |= ATA_FLAG_NCQ; + + if (hpriv->cap & HOST_CAP_PMP) + pi.flags |= ATA_FLAG_PMP; + + ahci_set_em_messages(hpriv, &pi); + + /* CAP.NP sometimes indicate the index of the last enabled + * port, at other times, that of the last possible port, so + * determining the maximum port number requires looking at + * both CAP.NP and port_map. + */ + n_ports = max(ahci_nr_ports(hpriv->cap), fls(hpriv->port_map)); + + host = ata_host_alloc_pinfo(dev, ppi, n_ports); + if (!host) { + rc = -ENOMEM; + goto pdata_exit; + } + + host->private_data = hpriv; + + if (!(hpriv->cap & HOST_CAP_SSS) || ahci_ignore_sss) + host->flags |= ATA_HOST_PARALLEL_SCAN; + else + dev_info(dev, "ahci: SSS flag set, parallel bus scan disabled\n"); + + if (pi.flags & ATA_FLAG_EM) + ahci_reset_em(host); + + for (i = 0; i < host->n_ports; i++) { + struct ata_port *ap = host->ports[i]; + + ata_port_desc(ap, "mmio %pR", mem); + ata_port_desc(ap, "port 0x%x", 0x100 + ap->port_no * 0x80); + + /* set enclosure management message type */ + if (ap->flags & ATA_FLAG_EM) + ap->em_message_type = hpriv->em_msg_type; + + /* disabled/not-implemented port */ + if (!(hpriv->port_map & (1 << i))) + ap->ops = &ata_dummy_port_ops; + } + + rc = ahci_reset_controller(host); + if (rc) + goto pdata_exit; + + ahci_init_controller(host); + ahci_print_info(host, "platform"); + + rc = ata_host_activate(host, irq, ahci_interrupt, IRQF_SHARED, + &ahci_imx_platform_sht); + if (rc) + goto pdata_exit; + + return 0; +pdata_exit: + if (pdata && pdata->exit) + pdata->exit(dev); +disable_unprepare_clk: + if (!IS_ERR(hpriv->clk)) + clk_disable_unprepare(hpriv->clk); +free_clk: + if (!IS_ERR(hpriv->clk)) + clk_put(hpriv->clk); + return rc; +} + +static void ahci_imx_host_stop(struct ata_host *host) +{ + struct device *dev = host->dev; + struct ahci_platform_data *pdata = dev_get_platdata(dev); + struct ahci_host_priv *hpriv = host->private_data; + + if (pdata && pdata->exit) + pdata->exit(dev); + + if (!IS_ERR(hpriv->clk)) { + clk_disable_unprepare(hpriv->clk); + clk_put(hpriv->clk); + } +} + +#ifdef CONFIG_PM_SLEEP +static int ahci_imx_suspend(struct device *dev) +{ + struct ahci_platform_data *pdata = dev_get_platdata(dev); + struct ata_host *host = dev_get_drvdata(dev); + struct ahci_host_priv *hpriv = host->private_data; + void __iomem *mmio = hpriv->mmio; + u32 ctl; + int rc; + + if (hpriv->flags & AHCI_HFLAG_NO_SUSPEND) { + dev_err(dev, "firmware update required for suspend/resume\n"); + return -EIO; + } + + /* + * AHCI spec rev1.1 section 8.3.3: + * Software must disable interrupts prior to requesting a + * transition of the HBA to D3 state. + */ + ctl = readl(mmio + HOST_CTL); + ctl &= ~HOST_IRQ_EN; + writel(ctl, mmio + HOST_CTL); + readl(mmio + HOST_CTL); /* flush */ + + rc = ata_host_suspend(host, PMSG_SUSPEND); + if (rc) + return rc; + + if (pdata && pdata->suspend) + return pdata->suspend(dev); + + if (!IS_ERR(hpriv->clk)) + clk_disable_unprepare(hpriv->clk); + + return 0; +} + +static int ahci_imx_resume(struct device *dev) +{ + struct ahci_platform_data *pdata = dev_get_platdata(dev); + struct ata_host *host = dev_get_drvdata(dev); + struct ahci_host_priv *hpriv = host->private_data; + int rc; + + if (!IS_ERR(hpriv->clk)) { + rc = clk_prepare_enable(hpriv->clk); + if (rc) { + dev_err(dev, "clock prepare enable failed"); + return rc; + } + } + + if (pdata && pdata->resume) { + rc = pdata->resume(dev); + if (rc) + goto disable_unprepare_clk; + } + + if (dev->power.power_state.event == PM_EVENT_SUSPEND) { + rc = ahci_reset_controller(host); + if (rc) + goto disable_unprepare_clk; + + ahci_init_controller(host); + } + + ata_host_resume(host); + + return 0; + +disable_unprepare_clk: + if (!IS_ERR(hpriv->clk)) + clk_disable_unprepare(hpriv->clk); + + return rc; +} +#endif + +static SIMPLE_DEV_PM_OPS(ahci_imx_pm_ops, ahci_imx_suspend, ahci_imx_resume); + +static const struct of_device_id ahci_of_match[] = { + { .compatible = "snps,imx-ahci", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ahci_of_match); + +static struct platform_driver ahci_imx_driver = { + .probe = ahci_imx_probe, + .remove = ata_platform_remove_one, + .driver = { + .name = "imx-ahci", + .owner = THIS_MODULE, + .of_match_table = ahci_of_match, + .pm = &ahci_imx_pm_ops, + }, +}; +module_platform_driver(ahci_imx_driver); + +MODULE_DESCRIPTION("FREESCALE IMX AHCI SATA platform driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("sata:imx"); -- 1.7.5.4 -- To unsubscribe from this list: send the line "unsubscribe linux-ide" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html