Message ID | 1373017942-12583-4-git-send-email-Hong-Xing.Zhu@freescale.com |
---|---|
State | Not Applicable |
Delegated to: | David Miller |
Headers | show |
On Fri, Jul 05, 2013 at 05:52:22PM +0800, Richard Zhu wrote: > From: Richard Zhu <r65037@freescale.com> > > 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. > - Configurations of the AHCI PHY clock, and the signal > parameters of the GPR13 > > Setup its own ahci sata driver, enable the imx6q ahci > sata support. > > Signed-off-by: Richard Zhu <r65037@freescale.com> > --- > drivers/ata/Kconfig | 9 ++ > drivers/ata/Makefile | 1 + > drivers/ata/sata_imx.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 240 insertions(+), 0 deletions(-) > create mode 100644 drivers/ata/sata_imx.c > > diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig > index a5a3ebc..45ed2f0 100644 > --- a/drivers/ata/Kconfig > +++ b/drivers/ata/Kconfig > @@ -97,6 +97,15 @@ config SATA_AHCI_PLATFORM > > If unsure, say N. > > +config SATA_IMX > + tristate "Freescale iMX AHCI SATA support" It's rather untypical to write iMX. Please write i.MX. > + depends on SATA_AHCI_PLATFORM > + help > + This option enables support for the Freescale iMX SoC's > + onboard AHCI SATA. > + > + If unsure, say N. > + > config SATA_FSL > tristate "Freescale 3.0Gbps SATA support" > depends on FSL_SOC > diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile > index c04d0fd..04b1c6c 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 > > # 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..a129efb > --- /dev/null > +++ b/drivers/ata/sata_imx.c > @@ -0,0 +1,230 @@ > +/* > + * 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/regmap.h> > +#include <linux/slab.h> > +#include <linux/module.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 <linux/of_device.h> > +#include <linux/mfd/syscon.h> > +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> > +#include "ahci.h" > +#include <linux/clk-private.h> Do you need anything from clk-private.h? You shouldn't. > + > +enum { > + HOST_TIMER1MS = 0xe0, /* Timer 1-ms */ > +}; > + > +/* imx6q ahci module initialization. */ > +static int imx6q_sata_phy_clk(struct device *dev, int enable) > +{ > + int ret = 0; > + struct clk *sata_ref_clk; > + > + sata_ref_clk = devm_clk_get(dev, "sata_ref_100m"); > + if (IS_ERR(sata_ref_clk)) { > + dev_err(dev, "can't get sata_ref clock.\n"); > + return PTR_ERR(sata_ref_clk); > + } clk_get must be balanced with clk_put. You need to have some private data and keep a pointer to the clock. > + if (enable) { > + /* Enable PHY clock */ > + ret = clk_prepare_enable(sata_ref_clk); > + if (ret < 0) > + dev_err(dev, "can't prepare-enable sata_ref clock\n"); When printing error messages it's always helpful to print the error code aswell. > + } else { > + /* Disable PHY clock */ This comment (also the one above enable) doesn't contain any useful information. > + clk_disable_unprepare(sata_ref_clk); > + } > + > + return ret; > +} > + > +static int imx6q_sata_init(struct device *dev, void __iomem *mmio) > +{ > + int ret = 0; > + struct regmap *gpr; > + struct clk *ahb_clk; > + > + ret = imx6q_sata_phy_clk(dev, true); > + if (ret < 0) > + return ret; > + > + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); > + if (IS_ERR(gpr)) { > + dev_err(dev, "failed to find fsl,imx6q-iomux-gpr regmap\n"); > + return PTR_ERR(gpr); > + } > + > + /* > + * set PHY Paremeters, two steps to configure the GPR13, > + * one write for rest of parameters, mask of first write > + * is 0x07fffffd, and the other one write for setting > + * the mpll_clk_en. > + */ > + regmap_update_bits(gpr, 0x34, IMX6Q_GPR13_SATA_RX_EQ_VAL_MASK > + | IMX6Q_GPR13_SATA_RX_LOS_LVL_MASK > + | IMX6Q_GPR13_SATA_RX_DPLL_MODE_MASK > + | IMX6Q_GPR13_SATA_SPD_MODE_MASK > + | IMX6Q_GPR13_SATA_MPLL_SS_EN > + | IMX6Q_GPR13_SATA_TX_ATTEN_MASK > + | IMX6Q_GPR13_SATA_TX_BOOST_MASK > + | IMX6Q_GPR13_SATA_TX_LVL_MASK > + | IMX6Q_GPR13_SATA_TX_EDGE_RATE > + , IMX6Q_GPR13_SATA_RX_EQ_VAL_3_0_DB > + | IMX6Q_GPR13_SATA_RX_LOS_LVL_SATA2M > + | IMX6Q_GPR13_SATA_RX_DPLL_MODE_2P_4F > + | IMX6Q_GPR13_SATA_SPD_MODE_3P0G > + | IMX6Q_GPR13_SATA_MPLL_SS_EN > + | IMX6Q_GPR13_SATA_TX_ATTEN_9_16 > + | IMX6Q_GPR13_SATA_TX_BOOST_3_33_DB > + | IMX6Q_GPR13_SATA_TX_LVL_1_025_V); > + regmap_update_bits(gpr, 0x34, IMX6Q_GPR13_SATA_MPLL_CLK_EN, > + IMX6Q_GPR13_SATA_MPLL_CLK_EN); > + usleep_range(100, 200); > + > + /* > + * 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. > + */ > + ret = readl(mmio + HOST_CAP); You should use a u32 type for storing readl results. > + if (!(ret & HOST_CAP_SSS)) > + writel(ret |= HOST_CAP_SSS, mmio + HOST_CAP); Why do you write this conditionally? Just write it unconditionally. > + ret = readl(mmio + HOST_PORTS_IMPL); > + if (!(ret & 0x1)) > + writel((ret | 0x1), mmio + HOST_PORTS_IMPL); ditto. > + > + ahb_clk = devm_clk_get(dev, "ahb"); > + if (IS_ERR(ahb_clk)) { > + dev_err(dev, "no ahb clock.\n"); > + return PTR_ERR(ahb_clk); > + } This leaves the phy clock enabled in the error case. > + ret = clk_get_rate(ahb_clk) / 1000; > + writel(ret, mmio + HOST_TIMER1MS); > + devm_clk_put(dev, ahb_clk); > + > + return 0; > +} > + > +static void imx6q_sata_exit(struct device *dev) > +{ > + struct regmap *gpr; > + > + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); > + if (IS_ERR(gpr)) > + dev_err(dev, "failed to find fsl,imx6q-iomux-gpr regmap\n"); > + > + regmap_update_bits(gpr, 0x34, IMX6Q_GPR13_SATA_MPLL_CLK_EN, > + !IMX6Q_GPR13_SATA_MPLL_CLK_EN); > + > + imx6q_sata_phy_clk(dev, false); > +} > + > +static struct ahci_platform_data imx6q_sata_pdata = { > + .init = imx6q_sata_init, > + .exit = imx6q_sata_exit, > +}; > +static const struct of_device_id imx_ahci_of_match[] = { > + { .compatible = "fsl,imx6q-ahci", .data = &imx6q_sata_pdata}, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, imx_ahci_of_match); > + > +static void imx_ahci_platform_release(struct device *dev) > +{ > + return; > +} > + > +static struct platform_device ahci_pdev = { > + .name = "ahci", > + .id = -1, > + .dev = { > + .release = imx_ahci_platform_release, > + } > +}; > + > +static int imx_ahci_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct resource *mem, *irq, **res; > + const struct of_device_id *of_id; > + const struct ahci_platform_data *pdata = NULL; > + int ret; > + > + of_id = of_match_device(imx_ahci_of_match, &pdev->dev); > + if (of_id) > + pdata = of_id->data; > + else > + return -EINVAL; > + > + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); > + if (!mem || !irq) { > + dev_err(dev, "no mmio/irq resource\n"); > + return -EINVAL; > + } > + > + ahci_pdev.dev.coherent_dma_mask = DMA_BIT_MASK(32); > + ahci_pdev.dev.dma_mask = &ahci_pdev.dev.coherent_dma_mask; > + ahci_pdev.dev.of_node = pdev->dev.of_node; No need to make ahci_pdev global. platform_device_add_data makes a copy anyway and making it global makes this driver broken for multiple instances. > + > + res[0] = mem; > + res[1] = irq; You have an uninitalized pointer to a pointer to struct resource which you treat as an array here. Doesn't the compiler warn you? > + platform_device_add_resources(&ahci_pdev, *res, 2); > + platform_device_add_data(&ahci_pdev, pdata, sizeof(*pdata)); > + > + ret = platform_device_register(&ahci_pdev); > + if (!ret) > + return ret; s/!ret/ret/ Sascha
> On Fri, Jul 05, 2013 at 05:52:22PM +0800, Richard Zhu wrote: > > From: Richard Zhu <r65037@freescale.com> ... > > + if (!(ret & HOST_CAP_SSS)) > > + writel(ret |= HOST_CAP_SSS, mmio + HOST_CAP); > > Why do you write this conditionally? Just write it unconditionally. "writel" with assignment is incorrect in any case. ---
Hello, On Fri, Jul 05, 2013 at 05:52:22PM +0800, Richard Zhu wrote: > - 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. > - Configurations of the AHCI PHY clock, and the signal > parameters of the GPR13 Can't you just add a platform specific init callback to ahci_platform? I don't see how these small differences in init sequence justify a separate driver. Thanks.
Hi Sascha: Thanks for your comments. Best Regards Richard Zhu
Hi Alexander: Thanks for your comments. Best Regards Richard Zhu
Hi Tejun: Thanks for your comments. Best Regards Richard Zhu
Hello, On Sat, Jul 06, 2013 at 02:03:30AM +0000, Zhu Richard-R65037 wrote: > [Richard] Just like what we dicussed in the previous v1/v2 > patch-set. Shawn has the concerns that the IP speicific codes > shouldn't be put in the platform level. So he suggested that setup > a stand-alone driver, contained the imx6q ahci sata specific codes, > and re-use the generic ahci_platform driver as much as possible. > This imx6q standalone ahci sata driver just registers the platform > data, and the others would be handled by ahci_platform driver. Oh, I'm not objecting to the ahci specific part not being in platform code. I'm wondering whether specific handling for imx6q can be included into ahci_platform rather than being in its own driver. It just seems that there aren't too many differences. I could be misreading it so if there are enough differences to warrant a separate driver, please enlighten me. Thanks!
Hi Tejun: Re-send this email to you and community. I'm afraid that you may be missed or don't receive it. BTW, can you help to pick up the #2 and #3(reviewed and acked by Shawn) of the v7 patch-set of this serial? Thanks in advance. 2013/7/10 HongXing Zhu <richard.zhuhongxing@gmail.com> Hi Tejun: There are some differences between generic AHCI controller and imx AHCI controller. The bits definitions of the HBA registers, the Vendor Specific registers, and the AHCI PHY clock, and the PHY signal adjustment window. - 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. - Configurations of the AHCI PHY clock, and the signal parameters of the GPR13 I'm afraid that the generic ahci_platform driver is not a good place to contain imx6q ahci specific differences. As I know that, the AHCI sata on imx53 doesn't have the PHY signal parameters adjustment window, but the AHCI sata on imx6q does has one. On imx6q, the standard .port_ops = &ahci_platform_ops, is used, but .port_ops = &ahci_platform_retry_srst_ops, would be required by imx53 AHCI controller. We can put all the imx AHCI(imx6q, imx53) specific difference into the sata_imx driver, and keep ahci_platform as clean as possible, if the separate sata_imx driver can be created. How do you think about that? BTW, the version4 patch-set would be sent out a moment later, any suggests and comments are appreciated. Best Regards. 2013/7/6 Tejun Heo <tj@kernel.org> Hello, On Sat, Jul 06, 2013 at 02:03:30AM +0000, Zhu Richard-R65037 wrote: > [Richard] Just like what we dicussed in the previous v1/v2 > patch-set. Shawn has the concerns that the IP speicific codes > shouldn't be put in the platform level. So he suggested that setup > a stand-alone driver, contained the imx6q ahci sata specific codes, > and re-use the generic ahci_platform driver as much as possible. > This imx6q standalone ahci sata driver just registers the platform > data, and the others would be handled by ahci_platform driver. Oh, I'm not objecting to the ahci specific part not being in platform code. I'm wondering whether specific handling for imx6q can be included into ahci_platform rather than being in its own driver. It just seems that there aren't too many differences. I could be misreading it so if there are enough differences to warrant a separate driver, please enlighten me. Thanks! -- tejun Best Regards Richard Zhu -----Original Message----- From: Tejun Heo [mailto:htejun@gmail.com] On Behalf Of Tejun Heo Sent: Saturday, July 06, 2013 10:08 AM To: Zhu Richard-R65037 Cc: Richard Zhu; shawn.guo@linaro.org; linux-arm-kernel@lists.infradead.org; jgarzik@pobox.com; avorontsov@ru.mvista.com; rob.herring@calxeda.com; s.hauer@pengutronix.de; linux-ide@vger.kernel.org Subject: Re: [v3 3/3] sata: imx: add ahci sata support on imx platforms Hello, On Sat, Jul 06, 2013 at 02:03:30AM +0000, Zhu Richard-R65037 wrote: > [Richard] Just like what we dicussed in the previous v1/v2 patch-set. > Shawn has the concerns that the IP speicific codes shouldn't be put in > the platform level. So he suggested that setup a stand-alone driver, > contained the imx6q ahci sata specific codes, and re-use the generic > ahci_platform driver as much as possible. > This imx6q standalone ahci sata driver just registers the platform > data, and the others would be handled by ahci_platform driver. Oh, I'm not objecting to the ahci specific part not being in platform code. I'm wondering whether specific handling for imx6q can be included into ahci_platform rather than being in its own driver. It just seems that there aren't too many differences. I could be misreading it so if there are enough differences to warrant a separate driver, please enlighten me. Thanks! -- tejun -- To unsubscribe from this list: send the line "unsubscribe linux-ide" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hello, On Mon, Jul 22, 2013 at 07:02:56AM +0000, Zhu Richard-R65037 wrote: > Re-send this email to you and community. > I'm afraid that you may be missed or don't receive it. Sorry about the delay. I was on the road. > BTW, can you help to pick up the #2 and #3(reviewed and acked by Shawn) of the v7 patch-set of this serial? > More majordomo info at http://vger.kernel.org/majordomo-info.html Just one thing, can you please rename the driver to ahci_imx? I'd like to keep all ahci drivers with ahci prefix (yeah, there's already an exception, I know). Thanks.
Hi Tejun: Thanks a lot. No problem, I can rename the driver from sata_imx to ahci_imx. Best Regards Richard Zhu -----Original Message----- From: Tejun Heo [mailto:htejun@gmail.com] On Behalf Of Tejun Heo Sent: Wednesday, July 24, 2013 12:26 AM To: Zhu Richard-R65037 Cc: Richard Zhu; shawn.guo@linaro.org; linux-arm-kernel@lists.infradead.org; jgarzik@pobox.com; avorontsov@ru.mvista.com; rob.herring@calxeda.com; s.hauer@pengutronix.de; linux-ide@vger.kernel.org Subject: Re: [v3 3/3] sata: imx: add ahci sata support on imx platforms Hello, On Mon, Jul 22, 2013 at 07:02:56AM +0000, Zhu Richard-R65037 wrote: > Re-send this email to you and community. > I'm afraid that you may be missed or don't receive it. Sorry about the delay. I was on the road. > BTW, can you help to pick up the #2 and #3(reviewed and acked by Shawn) of the v7 patch-set of this serial? > More majordomo info at http://vger.kernel.org/majordomo-info.html Just one thing, can you please rename the driver to ahci_imx? I'd like to keep all ahci drivers with ahci prefix (yeah, there's already an exception, I know). Thanks. -- tejun -- To unsubscribe from this list: send the line "unsubscribe linux-ide" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index a5a3ebc..45ed2f0 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -97,6 +97,15 @@ config SATA_AHCI_PLATFORM If unsure, say N. +config SATA_IMX + tristate "Freescale iMX AHCI SATA support" + depends on SATA_AHCI_PLATFORM + help + This option enables support for the Freescale iMX SoC's + onboard AHCI SATA. + + If unsure, say N. + config SATA_FSL tristate "Freescale 3.0Gbps SATA support" depends on FSL_SOC diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index c04d0fd..04b1c6c 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 # 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..a129efb --- /dev/null +++ b/drivers/ata/sata_imx.c @@ -0,0 +1,230 @@ +/* + * 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/regmap.h> +#include <linux/slab.h> +#include <linux/module.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 <linux/of_device.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include "ahci.h" +#include <linux/clk-private.h> + +enum { + HOST_TIMER1MS = 0xe0, /* Timer 1-ms */ +}; + +/* imx6q ahci module initialization. */ +static int imx6q_sata_phy_clk(struct device *dev, int enable) +{ + int ret = 0; + struct clk *sata_ref_clk; + + sata_ref_clk = devm_clk_get(dev, "sata_ref_100m"); + if (IS_ERR(sata_ref_clk)) { + dev_err(dev, "can't get sata_ref clock.\n"); + return PTR_ERR(sata_ref_clk); + } + if (enable) { + /* Enable PHY clock */ + ret = clk_prepare_enable(sata_ref_clk); + if (ret < 0) + dev_err(dev, "can't prepare-enable sata_ref clock\n"); + } else { + /* Disable PHY clock */ + clk_disable_unprepare(sata_ref_clk); + } + + return ret; +} + +static int imx6q_sata_init(struct device *dev, void __iomem *mmio) +{ + int ret = 0; + struct regmap *gpr; + struct clk *ahb_clk; + + ret = imx6q_sata_phy_clk(dev, true); + if (ret < 0) + return ret; + + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (IS_ERR(gpr)) { + dev_err(dev, "failed to find fsl,imx6q-iomux-gpr regmap\n"); + return PTR_ERR(gpr); + } + + /* + * set PHY Paremeters, two steps to configure the GPR13, + * one write for rest of parameters, mask of first write + * is 0x07fffffd, and the other one write for setting + * the mpll_clk_en. + */ + regmap_update_bits(gpr, 0x34, IMX6Q_GPR13_SATA_RX_EQ_VAL_MASK + | IMX6Q_GPR13_SATA_RX_LOS_LVL_MASK + | IMX6Q_GPR13_SATA_RX_DPLL_MODE_MASK + | IMX6Q_GPR13_SATA_SPD_MODE_MASK + | IMX6Q_GPR13_SATA_MPLL_SS_EN + | IMX6Q_GPR13_SATA_TX_ATTEN_MASK + | IMX6Q_GPR13_SATA_TX_BOOST_MASK + | IMX6Q_GPR13_SATA_TX_LVL_MASK + | IMX6Q_GPR13_SATA_TX_EDGE_RATE + , IMX6Q_GPR13_SATA_RX_EQ_VAL_3_0_DB + | IMX6Q_GPR13_SATA_RX_LOS_LVL_SATA2M + | IMX6Q_GPR13_SATA_RX_DPLL_MODE_2P_4F + | IMX6Q_GPR13_SATA_SPD_MODE_3P0G + | IMX6Q_GPR13_SATA_MPLL_SS_EN + | IMX6Q_GPR13_SATA_TX_ATTEN_9_16 + | IMX6Q_GPR13_SATA_TX_BOOST_3_33_DB + | IMX6Q_GPR13_SATA_TX_LVL_1_025_V); + regmap_update_bits(gpr, 0x34, IMX6Q_GPR13_SATA_MPLL_CLK_EN, + IMX6Q_GPR13_SATA_MPLL_CLK_EN); + usleep_range(100, 200); + + /* + * 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. + */ + 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 = devm_clk_get(dev, "ahb"); + if (IS_ERR(ahb_clk)) { + dev_err(dev, "no ahb clock.\n"); + return PTR_ERR(ahb_clk); + } + ret = clk_get_rate(ahb_clk) / 1000; + writel(ret, mmio + HOST_TIMER1MS); + devm_clk_put(dev, ahb_clk); + + return 0; +} + +static void imx6q_sata_exit(struct device *dev) +{ + struct regmap *gpr; + + gpr = syscon_regmap_lookup_by_compatible("fsl,imx6q-iomuxc-gpr"); + if (IS_ERR(gpr)) + dev_err(dev, "failed to find fsl,imx6q-iomux-gpr regmap\n"); + + regmap_update_bits(gpr, 0x34, IMX6Q_GPR13_SATA_MPLL_CLK_EN, + !IMX6Q_GPR13_SATA_MPLL_CLK_EN); + + imx6q_sata_phy_clk(dev, false); +} + +static struct ahci_platform_data imx6q_sata_pdata = { + .init = imx6q_sata_init, + .exit = imx6q_sata_exit, +}; +static const struct of_device_id imx_ahci_of_match[] = { + { .compatible = "fsl,imx6q-ahci", .data = &imx6q_sata_pdata}, + {}, +}; +MODULE_DEVICE_TABLE(of, imx_ahci_of_match); + +static void imx_ahci_platform_release(struct device *dev) +{ + return; +} + +static struct platform_device ahci_pdev = { + .name = "ahci", + .id = -1, + .dev = { + .release = imx_ahci_platform_release, + } +}; + +static int imx_ahci_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *mem, *irq, **res; + const struct of_device_id *of_id; + const struct ahci_platform_data *pdata = NULL; + int ret; + + of_id = of_match_device(imx_ahci_of_match, &pdev->dev); + if (of_id) + pdata = of_id->data; + else + return -EINVAL; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!mem || !irq) { + dev_err(dev, "no mmio/irq resource\n"); + return -EINVAL; + } + + ahci_pdev.dev.coherent_dma_mask = DMA_BIT_MASK(32); + ahci_pdev.dev.dma_mask = &ahci_pdev.dev.coherent_dma_mask; + ahci_pdev.dev.of_node = pdev->dev.of_node; + + res[0] = mem; + res[1] = irq; + platform_device_add_resources(&ahci_pdev, *res, 2); + platform_device_add_data(&ahci_pdev, pdata, sizeof(*pdata)); + + ret = platform_device_register(&ahci_pdev); + if (!ret) + return ret; + + return 0; +} + +static int imx_ahci_remove(struct platform_device *pdev) +{ + platform_device_unregister(&ahci_pdev); + return 0; +} + +static struct platform_driver imx_ahci_driver = { + .probe = imx_ahci_probe, + .remove = imx_ahci_remove, + .driver = { + .name = "sata-imx", + .owner = THIS_MODULE, + .of_match_table = imx_ahci_of_match, + }, +}; +module_platform_driver(imx_ahci_driver); + +MODULE_DESCRIPTION("Freescale iMX AHCI SATA platform driver"); +MODULE_AUTHOR("Richard Zhu <Hong-Xing.Zhu@freescale.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("sata:imx");