On 1 July 2013 18:19, Rob Herring <robherring2@xxxxxxxxx> wrote: > On 07/01/2013 05:02 AM, Richard Zhu wrote: >> 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. > > All this really belongs in the bootloader. Wouldn't you most likely be > booting the kernel from SATA as well? The first 2 are write once bits so > setting them a 2nd time would have no effect. Even though its done in bootloader. It should be initialized in kernel. For a "suspend to disk" use case. If power to SATA controller is cut during suspend. Then this init sequence should be used > > I also agree that if this added, it should be added to the existing driver. > > Rob > >> >> 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"); >> > > -- > 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 -- 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