diff options
Diffstat (limited to 'drivers/ata/ahci_imx.c')
-rw-r--r-- | drivers/ata/ahci_imx.c | 638 |
1 files changed, 616 insertions, 22 deletions
diff --git a/drivers/ata/ahci_imx.c b/drivers/ata/ahci_imx.c index 3f3a7db208ae..05f2fdaee9b3 100644 --- a/drivers/ata/ahci_imx.c +++ b/drivers/ata/ahci_imx.c @@ -22,6 +22,8 @@ #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/ahci_platform.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> #include <linux/of_device.h> #include <linux/mfd/syscon.h> #include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> @@ -50,11 +52,82 @@ enum { /* Clock Reset Register */ IMX_CLOCK_RESET = 0x7f3f, IMX_CLOCK_RESET_RESET = 1 << 0, + /* IMX8QM HSIO AHCI definitions */ + IMX8QM_SATA_PHY_REG03_RX_IMPED_RATIO = 0x03, + IMX8QM_SATA_PHY_REG09_TX_IMPED_RATIO = 0x09, + IMX8QM_SATA_PHY_REG10_TX_POST_CURSOR_RATIO = 0x0a, + IMX8QM_SATA_PHY_GEN1_TX_POST_CURSOR_RATIO = 0x15, + IMX8QM_SATA_PHY_IMPED_RATIO_85OHM = 0x6c, + IMX8QM_SATA_PHY_REG22_TX_POST_CURSOR_RATIO = 0x16, + IMX8QM_SATA_PHY_GEN2_TX_POST_CURSOR_RATIO = 0x00, + IMX8QM_SATA_PHY_REG24_TX_AMP_RATIO_MARGIN0 = 0x18, + IMX8QM_SATA_PHY_TX_AMP_RATIO_MARGIN0 = 0x64, + IMX8QM_SATA_PHY_REG25_TX_AMP_RATIO_MARGIN1 = 0x19, + IMX8QM_SATA_PHY_TX_AMP_RATIO_MARGIN1 = 0x70, + IMX8QM_SATA_PHY_REG26_TX_AMP_RATIO_MARGIN2 = 0x1a, + IMX8QM_SATA_PHY_TX_AMP_RATIO_MARGIN2 = 0x69, + IMX8QM_SATA_PHY_REG48_PMA_STATUS = 0x30, + IMX8QM_SATA_PHY_REG48_PMA_RDY = BIT(7), + IMX8QM_SATA_PHY_REG128_UPDATE_SETTING = 0x80, + IMX8QM_SATA_PHY_UPDATE_SETTING = 0x01, + IMX8QM_LPCG_PHYX2_OFFSET = 0x00000, + IMX8QM_CSR_PHYX2_OFFSET = 0x90000, + IMX8QM_CSR_PHYX1_OFFSET = 0xa0000, + IMX8QM_CSR_PHYX_STTS0_OFFSET = 0x4, + IMX8QM_CSR_PCIEA_OFFSET = 0xb0000, + IMX8QM_CSR_PCIEB_OFFSET = 0xc0000, + IMX8QM_CSR_SATA_OFFSET = 0xd0000, + IMX8QM_CSR_PCIE_CTRL2_OFFSET = 0x8, + IMX8QM_CSR_MISC_OFFSET = 0xe0000, + /* IMX8QM SATA specific control registers */ + IMX8QM_SATA_PPCFG_OFFSET = 0xa8, + IMX8QM_SATA_PPCFG_FORCE_PHY_RDY = BIT(20), + IMX8QM_SATA_PPCFG_BIST_PATTERN_MASK = 0x7 << 21, + IMX8QM_SATA_PPCFG_BIST_PATTERN_OFFSET = 21, + IMX8QM_SATA_PPCFG_BIST_PATTERN_EN = BIT(24), + IMX8QM_SATA_PPCFG_BIST_PATTERN_NOALIGNS = BIT(26), + IMX8QM_SATA_PP2CFG_OFFSET = 0xac, + IMX8QM_SATA_PP2CFG_COMINIT_NEGATE_MIN = 0x28 << 24, + IMX8QM_SATA_PP2CFG_COMINT_BURST_GAP = 0x18 << 16, + IMX8QM_SATA_PP2CFG_COMINT_BURST_GAP_MAX = 0x2b << 8, + IMX8QM_SATA_PP2CFG_COMINT_BURST_GAP_MIN = 0x1b << 0, + IMX8QM_SATA_PP3CFG_OFFSET = 0xb0, + IMX8QM_SATA_PP3CFG_COMWAKE_NEGATE_MIN = 0x0e << 24, + IMX8QM_SATA_PP3CFG_COMWAKE_BURST_GAP = 0x08 << 16, + IMX8QM_SATA_PP3CFG_COMWAKE_BURST_GAP_MAX = 0x0f << 8, + IMX8QM_SATA_PP3CFG_COMWAKE_BURST_GAP_MIN = 0x01 << 0, + + IMX8QM_LPCG_PHYX2_PCLK0_MASK = (0x3 << 16), + IMX8QM_LPCG_PHYX2_PCLK1_MASK = (0x3 << 20), + IMX8QM_PHY_APB_RSTN_0 = BIT(0), + IMX8QM_PHY_MODE_SATA = BIT(19), + IMX8QM_PHY_MODE_MASK = (0xf << 17), + IMX8QM_PHY_PIPE_RSTN_0 = BIT(24), + IMX8QM_PHY_PIPE_RSTN_OVERRIDE_0 = BIT(25), + IMX8QM_PHY_PIPE_RSTN_1 = BIT(26), + IMX8QM_PHY_PIPE_RSTN_OVERRIDE_1 = BIT(27), + IMX8QM_STTS0_LANE0_TX_PLL_LOCK = BIT(4), + IMX8QM_MISC_IOB_RXENA = BIT(0), + IMX8QM_MISC_IOB_TXENA = BIT(1), + IMX8QM_MISC_PHYX1_EPCS_SEL = BIT(12), + IMX8QM_MISC_CLKREQN_OUT_OVERRIDE_1 = BIT(24), + IMX8QM_MISC_CLKREQN_OUT_OVERRIDE_0 = BIT(25), + IMX8QM_MISC_CLKREQN_IN_OVERRIDE_1 = BIT(28), + IMX8QM_MISC_CLKREQN_IN_OVERRIDE_0 = BIT(29), + IMX8QM_SATA_CTRL_RESET_N = BIT(12), + IMX8QM_SATA_CTRL_EPCS_PHYRESET_N = BIT(7), + IMX8QM_SATA_CTRL_EPCS_TXDEEMP_SEL = BIT(6), + IMX8QM_SATA_CTRL_EPCS_TXDEEMP = BIT(5), + IMX8QM_CTRL_BUTTON_RST_N = BIT(21), + IMX8QM_CTRL_POWER_UP_RST_N = BIT(23), + IMX8QM_CTRL_LTSSM_ENABLE = BIT(4), }; enum ahci_imx_type { AHCI_IMX53, AHCI_IMX6Q, + AHCI_IMX6QP, + AHCI_IMX8QM, }; struct imx_ahci_priv { @@ -63,16 +136,30 @@ struct imx_ahci_priv { struct clk *sata_clk; struct clk *sata_ref_clk; struct clk *ahb_clk; + struct clk *epcs_tx_clk; + struct clk *epcs_rx_clk; + struct clk *phy_apbclk; + struct clk *phy_pclk0; + struct clk *phy_pclk1; + void __iomem *phy_base; + int clkreq_gpio; struct regmap *gpr; bool no_device; bool first_time; u32 phy_params; + u32 imped_ratio; }; +void *sg_io_buffer_hack; + 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 int bist_enable; +module_param_named(bist, bist_enable, int, 0644); +MODULE_PARM_DESC(bist, "AHCI IMX bist mode enable(1 = enable)"); + static void ahci_imx_host_stop(struct ata_host *host); static int imx_phy_crbit_assert(void __iomem *mmio, u32 bit, bool assert) @@ -214,6 +301,259 @@ static int imx_sata_phy_reset(struct ahci_host_priv *hpriv) return timeout ? 0 : -ETIMEDOUT; } +static int imx8_sata_enable(struct ahci_host_priv *hpriv) +{ + u32 val, reg; + int i, ret; + struct imx_ahci_priv *imxpriv = hpriv->plat_data; + struct device *dev = &imxpriv->ahci_pdev->dev; + + /* configure the hsio for sata */ + ret = clk_prepare_enable(imxpriv->phy_pclk0); + if (ret < 0) { + dev_err(dev, "can't enable phy pclk0.\n"); + return ret; + } + ret = clk_prepare_enable(imxpriv->phy_pclk1); + if (ret < 0) { + dev_err(dev, "can't enable phy pclk1.\n"); + goto disable_phy_pclk0; + } + ret = clk_prepare_enable(imxpriv->epcs_tx_clk); + if (ret < 0) { + dev_err(dev, "can't enable epcs tx clk.\n"); + goto disable_phy_pclk1; + } + ret = clk_prepare_enable(imxpriv->epcs_rx_clk); + if (ret < 0) { + dev_err(dev, "can't enable epcs rx clk.\n"); + goto disable_epcs_tx_clk; + } + ret = clk_prepare_enable(imxpriv->phy_apbclk); + if (ret < 0) { + dev_err(dev, "can't enable phy pclk1.\n"); + goto disable_epcs_rx_clk; + } + /* Configure PHYx2 PIPE_RSTN */ + regmap_read(imxpriv->gpr, IMX8QM_CSR_PCIEA_OFFSET + + IMX8QM_CSR_PCIE_CTRL2_OFFSET, &val); + if ((val & IMX8QM_CTRL_LTSSM_ENABLE) == 0) { + /* PCIEA of HSIO is down too */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_PHYX2_OFFSET, + IMX8QM_PHY_PIPE_RSTN_0 + | IMX8QM_PHY_PIPE_RSTN_OVERRIDE_0, + IMX8QM_PHY_PIPE_RSTN_0 + | IMX8QM_PHY_PIPE_RSTN_OVERRIDE_0); + } + regmap_read(imxpriv->gpr, IMX8QM_CSR_PCIEB_OFFSET + + IMX8QM_CSR_PCIE_CTRL2_OFFSET, ®); + if ((reg & IMX8QM_CTRL_LTSSM_ENABLE) == 0) { + /* PCIEB of HSIO is down */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_PHYX2_OFFSET, + IMX8QM_PHY_PIPE_RSTN_1 + | IMX8QM_PHY_PIPE_RSTN_OVERRIDE_1, + IMX8QM_PHY_PIPE_RSTN_1 + | IMX8QM_PHY_PIPE_RSTN_OVERRIDE_1); + } + if (((reg | val) & IMX8QM_CTRL_LTSSM_ENABLE) == 0) { + /* Both PCIA and PCIEB of HSIO is down */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_LPCG_PHYX2_OFFSET, + IMX8QM_LPCG_PHYX2_PCLK0_MASK + | IMX8QM_LPCG_PHYX2_PCLK1_MASK, + 0); + } + + /* set PWR_RST and BT_RST of csr_pciea */ + val = IMX8QM_CSR_PCIEA_OFFSET + IMX8QM_CSR_PCIE_CTRL2_OFFSET; + regmap_update_bits(imxpriv->gpr, + val, + IMX8QM_CTRL_BUTTON_RST_N, + IMX8QM_CTRL_BUTTON_RST_N); + regmap_update_bits(imxpriv->gpr, + val, + IMX8QM_CTRL_POWER_UP_RST_N, + IMX8QM_CTRL_POWER_UP_RST_N); + + /* PHYX1_MODE to SATA */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_PHYX1_OFFSET, + IMX8QM_PHY_MODE_MASK, + IMX8QM_PHY_MODE_SATA); + + /* + * bit0 rx ena 1, bit1 tx ena 0 + * bit12 PHY_X1_EPCS_SEL 1. + */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_MISC_OFFSET, + IMX8QM_MISC_IOB_RXENA, + IMX8QM_MISC_IOB_RXENA); + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_MISC_OFFSET, + IMX8QM_MISC_IOB_TXENA, + 0); + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_MISC_OFFSET, + IMX8QM_MISC_PHYX1_EPCS_SEL, + IMX8QM_MISC_PHYX1_EPCS_SEL); + /* + * It is possible, for PCIe and SATA are sharing + * the same clock source, HPLL or external oscillator. + * When PCIe is in low power modes (L1.X or L2 etc), + * the clock source can be turned off. In this case, + * if this clock source is required to be toggling by + * SATA, then SATA functions will be abnormal. + */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_MISC_OFFSET, + IMX8QM_MISC_CLKREQN_OUT_OVERRIDE_1 + | IMX8QM_MISC_CLKREQN_OUT_OVERRIDE_0 + | IMX8QM_MISC_CLKREQN_IN_OVERRIDE_1 + | IMX8QM_MISC_CLKREQN_IN_OVERRIDE_0, + IMX8QM_MISC_CLKREQN_OUT_OVERRIDE_1 + | IMX8QM_MISC_CLKREQN_OUT_OVERRIDE_0 + | IMX8QM_MISC_CLKREQN_IN_OVERRIDE_1 + | IMX8QM_MISC_CLKREQN_IN_OVERRIDE_0); + + /* clear PHY RST, then set it */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_SATA_OFFSET, + IMX8QM_SATA_CTRL_EPCS_PHYRESET_N, + 0); + + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_SATA_OFFSET, + IMX8QM_SATA_CTRL_EPCS_PHYRESET_N, + IMX8QM_SATA_CTRL_EPCS_PHYRESET_N); + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_SATA_OFFSET, + IMX8QM_SATA_CTRL_EPCS_TXDEEMP, + IMX8QM_SATA_CTRL_EPCS_TXDEEMP); + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_SATA_OFFSET, + IMX8QM_SATA_CTRL_EPCS_TXDEEMP_SEL, + IMX8QM_SATA_CTRL_EPCS_TXDEEMP_SEL); + + /* CTRL RST: SET -> delay 1 us -> CLEAR -> SET */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_SATA_OFFSET, + IMX8QM_SATA_CTRL_RESET_N, + IMX8QM_SATA_CTRL_RESET_N); + udelay(1); + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_SATA_OFFSET, + IMX8QM_SATA_CTRL_RESET_N, + 0); + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_SATA_OFFSET, + IMX8QM_SATA_CTRL_RESET_N, + IMX8QM_SATA_CTRL_RESET_N); + + /* APB reset */ + regmap_update_bits(imxpriv->gpr, + IMX8QM_CSR_PHYX1_OFFSET, + IMX8QM_PHY_APB_RSTN_0, + IMX8QM_PHY_APB_RSTN_0); + + for (i = 0; i < 100; i++) { + reg = IMX8QM_CSR_PHYX1_OFFSET + + IMX8QM_CSR_PHYX_STTS0_OFFSET; + regmap_read(imxpriv->gpr, reg, &val); + val &= IMX8QM_STTS0_LANE0_TX_PLL_LOCK; + if (val == IMX8QM_STTS0_LANE0_TX_PLL_LOCK) + break; + udelay(1); + } + + if (val != IMX8QM_STTS0_LANE0_TX_PLL_LOCK) { + dev_err(dev, "TX PLL of the PHY is not locked\n"); + ret = -ENODEV; + } else { + for (i = 0; i < 1000; i++) { + reg = readb(imxpriv->phy_base + + IMX8QM_SATA_PHY_REG48_PMA_STATUS); + if (reg & IMX8QM_SATA_PHY_REG48_PMA_RDY) + break; + udelay(10); + } + if ((reg & IMX8QM_SATA_PHY_REG48_PMA_RDY) == 0) { + dev_err(dev, "Calibration is NOT finished.\n"); + ret = -ENODEV; + goto err_out; + } + + writeb(imxpriv->imped_ratio, imxpriv->phy_base + + IMX8QM_SATA_PHY_REG03_RX_IMPED_RATIO); + writeb(imxpriv->imped_ratio, imxpriv->phy_base + + IMX8QM_SATA_PHY_REG09_TX_IMPED_RATIO); + reg = readb(imxpriv->phy_base + + IMX8QM_SATA_PHY_REG03_RX_IMPED_RATIO); + if (unlikely(reg != imxpriv->imped_ratio)) + dev_info(dev, "Can't set PHY RX impedance ratio.\n"); + reg = readb(imxpriv->phy_base + + IMX8QM_SATA_PHY_REG09_TX_IMPED_RATIO); + if (unlikely(reg != imxpriv->imped_ratio)) + dev_info(dev, "Can't set PHY TX impedance ratio.\n"); + + /* Configure the tx_amplitude to pass the tests. */ + writeb(IMX8QM_SATA_PHY_TX_AMP_RATIO_MARGIN0, imxpriv->phy_base + + IMX8QM_SATA_PHY_REG24_TX_AMP_RATIO_MARGIN0); + writeb(IMX8QM_SATA_PHY_TX_AMP_RATIO_MARGIN1, imxpriv->phy_base + + IMX8QM_SATA_PHY_REG25_TX_AMP_RATIO_MARGIN1); + writeb(IMX8QM_SATA_PHY_TX_AMP_RATIO_MARGIN2, imxpriv->phy_base + + IMX8QM_SATA_PHY_REG26_TX_AMP_RATIO_MARGIN2); + + /* Adjust the OOB COMINIT/COMWAKE to pass the tests. */ + writeb(IMX8QM_SATA_PHY_GEN1_TX_POST_CURSOR_RATIO, + imxpriv->phy_base + + IMX8QM_SATA_PHY_REG10_TX_POST_CURSOR_RATIO); + writeb(IMX8QM_SATA_PHY_GEN2_TX_POST_CURSOR_RATIO, + imxpriv->phy_base + + IMX8QM_SATA_PHY_REG22_TX_POST_CURSOR_RATIO); + + writeb(IMX8QM_SATA_PHY_UPDATE_SETTING, imxpriv->phy_base + + IMX8QM_SATA_PHY_REG128_UPDATE_SETTING); + + reg = IMX8QM_SATA_PP2CFG_COMINIT_NEGATE_MIN | + IMX8QM_SATA_PP2CFG_COMINT_BURST_GAP | + IMX8QM_SATA_PP2CFG_COMINT_BURST_GAP_MAX | + IMX8QM_SATA_PP2CFG_COMINT_BURST_GAP_MIN; + writel(reg, hpriv->mmio + IMX8QM_SATA_PP2CFG_OFFSET); + reg = IMX8QM_SATA_PP3CFG_COMWAKE_NEGATE_MIN | + IMX8QM_SATA_PP3CFG_COMWAKE_BURST_GAP | + IMX8QM_SATA_PP3CFG_COMWAKE_BURST_GAP_MAX | + IMX8QM_SATA_PP3CFG_COMWAKE_BURST_GAP_MIN; + writel(reg, hpriv->mmio + IMX8QM_SATA_PP3CFG_OFFSET); + + usleep_range(50, 100); + + /* + * To reduce the power consumption, gate off + * the PHY clks + */ + clk_disable_unprepare(imxpriv->phy_apbclk); + clk_disable_unprepare(imxpriv->phy_pclk1); + clk_disable_unprepare(imxpriv->phy_pclk0); + return ret; + } + +err_out: + clk_disable_unprepare(imxpriv->phy_apbclk); +disable_epcs_rx_clk: + clk_disable_unprepare(imxpriv->epcs_rx_clk); +disable_epcs_tx_clk: + clk_disable_unprepare(imxpriv->epcs_tx_clk); +disable_phy_pclk1: + clk_disable_unprepare(imxpriv->phy_pclk1); +disable_phy_pclk0: + clk_disable_unprepare(imxpriv->phy_pclk0); + + return ret; +} + static int imx_sata_enable(struct ahci_host_priv *hpriv) { struct imx_ahci_priv *imxpriv = hpriv->plat_data; @@ -231,7 +571,7 @@ static int imx_sata_enable(struct ahci_host_priv *hpriv) if (ret < 0) goto disable_regulator; - if (imxpriv->type == AHCI_IMX6Q) { + if (imxpriv->type == AHCI_IMX6Q || imxpriv->type == AHCI_IMX6QP) { /* * set PHY Paremeters, two steps to configure the GPR13, * one write for rest of parameters, mask of first write @@ -255,12 +595,28 @@ static int imx_sata_enable(struct ahci_host_priv *hpriv) IMX6Q_GPR13_SATA_MPLL_CLK_EN); usleep_range(100, 200); + } + + if (imxpriv->type == AHCI_IMX6Q) { ret = imx_sata_phy_reset(hpriv); - if (ret) { - dev_err(dev, "failed to reset phy: %d\n", ret); - goto disable_clk; - } + } else if (imxpriv->type == AHCI_IMX6QP) { + /* 6qp adds the sata reset mechanism, use it for 6qp sata */ + regmap_update_bits(imxpriv->gpr, IOMUXC_GPR5, + BIT(10), 0); + + regmap_update_bits(imxpriv->gpr, IOMUXC_GPR5, + BIT(11), 0); + udelay(50); + regmap_update_bits(imxpriv->gpr, IOMUXC_GPR5, + BIT(11), BIT(11)); + } else if (imxpriv->type == AHCI_IMX8QM) { + ret = imx8_sata_enable(hpriv); + } + + if (ret) { + dev_err(dev, "failed to reset phy: %d\n", ret); + goto disable_clk; } usleep_range(1000, 2000); @@ -282,12 +638,20 @@ static void imx_sata_disable(struct ahci_host_priv *hpriv) if (imxpriv->no_device) return; - if (imxpriv->type == AHCI_IMX6Q) { + if (imxpriv->type == AHCI_IMX6QP) + regmap_update_bits(imxpriv->gpr, IOMUXC_GPR5, + BIT(10), BIT(10)); + + if (imxpriv->type == AHCI_IMX6Q || imxpriv->type == AHCI_IMX6QP) { regmap_update_bits(imxpriv->gpr, IOMUXC_GPR13, IMX6Q_GPR13_SATA_MPLL_CLK_EN, !IMX6Q_GPR13_SATA_MPLL_CLK_EN); } + if (imxpriv->type == AHCI_IMX8QM) { + clk_disable_unprepare(imxpriv->epcs_rx_clk); + clk_disable_unprepare(imxpriv->epcs_tx_clk); + } clk_disable_unprepare(imxpriv->sata_ref_clk); ahci_platform_disable_regulators(hpriv); @@ -304,7 +668,8 @@ static void ahci_imx_error_handler(struct ata_port *ap) ahci_error_handler(ap); - if (!(imxpriv->first_time) || ahci_imx_hotplug) + if (!(imxpriv->first_time) || ahci_imx_hotplug + || (imxpriv->type == AHCI_IMX8QM)) return; imxpriv->first_time = false; @@ -336,7 +701,7 @@ static int ahci_imx_softreset(struct ata_link *link, unsigned int *class, if (imxpriv->type == AHCI_IMX53) ret = ahci_pmp_retry_srst_ops.softreset(link, class, deadline); - else if (imxpriv->type == AHCI_IMX6Q) + else ret = ahci_ops.softreset(link, class, deadline); return ret; @@ -359,6 +724,8 @@ static const struct ata_port_info ahci_imx_port_info = { static const struct of_device_id imx_ahci_of_match[] = { { .compatible = "fsl,imx53-ahci", .data = (void *)AHCI_IMX53 }, { .compatible = "fsl,imx6q-ahci", .data = (void *)AHCI_IMX6Q }, + { .compatible = "fsl,imx6qp-ahci", .data = (void *)AHCI_IMX6QP }, + { .compatible = "fsl,imx8qm-ahci", .data = (void *)AHCI_IMX8QM }, {}, }; MODULE_DEVICE_TABLE(of, imx_ahci_of_match); @@ -526,6 +893,188 @@ static struct scsi_host_template ahci_platform_sht = { AHCI_SHT(DRV_NAME), }; +static int imx8_sata_probe(struct device *dev, struct imx_ahci_priv *imxpriv) +{ + int ret; + struct resource *phy_res; + struct platform_device *pdev = imxpriv->ahci_pdev; + struct device_node *np = dev->of_node; + + if (of_property_read_u32(np, "fsl,phy-imp", &imxpriv->imped_ratio)) { + /* + * Regarding to the differnet Hw designs, + * Set the impedance ratio to 0x6c when 85OHM is used. + * Keep it to default value 0x80, when 100OHM is used. + */ + dev_info(dev, "phy impedance ratio is not specified.\n"); + imxpriv->imped_ratio = IMX8QM_SATA_PHY_IMPED_RATIO_85OHM; + } + phy_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); + if (phy_res) { + imxpriv->phy_base = devm_ioremap(dev, phy_res->start, + resource_size(phy_res)); + if (!imxpriv->phy_base) { + dev_err(dev, "error with ioremap\n"); + return -ENOMEM; + } + } else { + dev_err(dev, "missing *phy* reg region.\n"); + return -ENOMEM; + } + imxpriv->gpr = + syscon_regmap_lookup_by_phandle(np, "hsio"); + if (IS_ERR(imxpriv->gpr)) { + dev_err(dev, "unable to find gpr registers\n"); + return PTR_ERR(imxpriv->gpr); + } + imxpriv->epcs_tx_clk = devm_clk_get(dev, "epcs_tx"); + if (IS_ERR(imxpriv->epcs_tx_clk)) { + dev_err(dev, "can't get sata_epcs tx clock.\n"); + return PTR_ERR(imxpriv->epcs_tx_clk); + } + + imxpriv->epcs_rx_clk = devm_clk_get(dev, "epcs_rx"); + if (IS_ERR(imxpriv->epcs_rx_clk)) { + dev_err(dev, "can't get sata_epcs rx clock.\n"); + return PTR_ERR(imxpriv->epcs_rx_clk); + } + + imxpriv->phy_pclk0 = devm_clk_get(dev, "phy_pclk0"); + if (IS_ERR(imxpriv->phy_pclk0)) { + dev_err(dev, "can't get sata_phy_pclk0 clock.\n"); + return PTR_ERR(imxpriv->phy_pclk0); + } + + imxpriv->phy_pclk1 = devm_clk_get(dev, "phy_pclk1"); + if (IS_ERR(imxpriv->phy_pclk1)) { + dev_err(dev, "can't get sata_phy_pclk1 clock.\n"); + return PTR_ERR(imxpriv->phy_pclk1); + } + + imxpriv->phy_apbclk = devm_clk_get(dev, "phy_apbclk"); + if (IS_ERR(imxpriv->phy_apbclk)) { + dev_err(dev, "can't get sata_phy_apbclk clock.\n"); + return PTR_ERR(imxpriv->phy_apbclk); + } + + /* Fetch GPIO, then enable the external OSC */ + imxpriv->clkreq_gpio = of_get_named_gpio(np, "clkreq-gpio", 0); + if (gpio_is_valid(imxpriv->clkreq_gpio)) { + ret = devm_gpio_request_one(dev, imxpriv->clkreq_gpio, + GPIOF_OUT_INIT_LOW, + "SATA CLKREQ"); + if (ret == -EBUSY) { + dev_info(dev, "clkreq had been initialized.\n"); + } else if (ret) { + dev_err(dev, "%d unable to get clkreq.\n", ret); + return ret; + } + } else if (imxpriv->clkreq_gpio == -EPROBE_DEFER) { + return imxpriv->clkreq_gpio; + } + + return 0; +} + +static ssize_t imx_ahci_bist_pattern_info(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + u32 bist_pattern; + struct ahci_host_priv *hpriv = dev_get_drvdata(dev); + + bist_pattern = readl(hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + bist_pattern = bist_pattern & IMX8QM_SATA_PPCFG_BIST_PATTERN_MASK; + bist_pattern = bist_pattern >> IMX8QM_SATA_PPCFG_BIST_PATTERN_OFFSET; + return sprintf(buf, "imx-ahci-bist-pattern %s%s%s%s.\n", + (BIT(0) << bist_pattern) & BIT(0) ? "LBP " : "", + (BIT(0) << bist_pattern) & BIT(1) ? "LFTP " : "", + (BIT(0) << bist_pattern) & BIT(2) ? "MFTP " : "", + (BIT(0) << bist_pattern) & BIT(3) ? "HFTP " : ""); +} + +static ssize_t imx_ahci_bist_pattern(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + u32 bist_pattern, val, timeout; + struct ahci_host_priv *hpriv = dev_get_drvdata(dev); + + ret = sscanf(buf, "%x\n", &bist_pattern); + if (ret != 1) + return -EINVAL; + if ((bist_pattern > 3)) { + dev_err(dev, "LBP 0, LFTP 1, MFTP 2, HFTP 3.\n"); + return -1; + } + dev_info(dev, "Try to enable %s%s%s%s pattern.\n", + (BIT(0) << bist_pattern) & BIT(0) ? "LBP " : "", + (BIT(0) << bist_pattern) & BIT(1) ? "LFTP " : "", + (BIT(0) << bist_pattern) & BIT(2) ? "MFTP " : "", + (BIT(0) << bist_pattern) & BIT(3) ? "HFTP " : ""); + + dev_info(dev, "Clear BIST enable.\n"); + val = readl(hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + writel(val & (~IMX8QM_SATA_PPCFG_BIST_PATTERN_EN), + hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + + /* put device into listen mode, first set PxSCTL.DET to 0 */ + dev_info(dev, "Turn off device detection.\n"); + val = readl(hpriv->mmio + 0x100 + PORT_SCR_CTL); + writel(val & ~0xf, hpriv->mmio + 0x100 + PORT_SCR_CTL); + + dev_info(dev, "Force phy ready, then wait.\n"); + val = readl(hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + writel(val | IMX8QM_SATA_PPCFG_FORCE_PHY_RDY, + hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + + timeout = 1000; + do { + val = readl(hpriv->mmio + 0x100 + PORT_SCR_STAT); + if ((val & 0xf) > 1) + break; + mdelay(1); + } while (--timeout); + if (timeout == 0) + dev_info(dev, "Error, wait for phy ready timeout.\n"); + else + dev_info(dev, "Get phy ready, and Gen%d mode is set.\n", + (val & 0xF0) >> 4); + + /* clear SError */ + dev_info(dev, "Clear error reg.\n"); + val = readl(hpriv->mmio + 0x100 + PORT_SCR_ERR); + writel(val, hpriv->mmio + 0x100 + PORT_SCR_ERR); + + dev_info(dev, "Select BIST pattern.\n"); + val = readl(hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + val &= (~IMX8QM_SATA_PPCFG_BIST_PATTERN_MASK); + val |= (bist_pattern << IMX8QM_SATA_PPCFG_BIST_PATTERN_OFFSET); + writel(val, hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + + dev_info(dev, "Set no aligns in BIST pattern.\n"); + val = readl(hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + writel(val | IMX8QM_SATA_PPCFG_BIST_PATTERN_NOALIGNS, + hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + + dev_info(dev, "BIST enable.\n"); + val = readl(hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + writel(val | IMX8QM_SATA_PPCFG_BIST_PATTERN_EN, + hpriv->mmio + IMX8QM_SATA_PPCFG_OFFSET); + + return count; +} + +static DEVICE_ATTR(ahci_bist_pattern, 0644, imx_ahci_bist_pattern_info, + imx_ahci_bist_pattern); + +static struct attribute *imx_ahci_attrs[] = { + &dev_attr_ahci_bist_pattern.attr, + NULL +}; + +static struct attribute_group imx_ahci_attrgroup = { + .attrs = imx_ahci_attrs, +}; static int imx_ahci_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -560,13 +1109,7 @@ static int imx_ahci_probe(struct platform_device *pdev) return PTR_ERR(imxpriv->sata_ref_clk); } - imxpriv->ahb_clk = devm_clk_get(dev, "ahb"); - if (IS_ERR(imxpriv->ahb_clk)) { - dev_err(dev, "can't get ahb clock.\n"); - return PTR_ERR(imxpriv->ahb_clk); - } - - if (imxpriv->type == AHCI_IMX6Q) { + if (imxpriv->type == AHCI_IMX6Q || imxpriv->type == AHCI_IMX6QP) { u32 reg_value; imxpriv->gpr = syscon_regmap_lookup_by_compatible( @@ -585,6 +1128,10 @@ static int imx_ahci_probe(struct platform_device *pdev) IMX6Q_GPR13_SATA_RX_DPLL_MODE_2P_4F | IMX6Q_GPR13_SATA_SPD_MODE_3P0G | reg_value; + } else if (imxpriv->type == AHCI_IMX8QM) { + ret = imx8_sata_probe(dev, imxpriv); + if (ret) + return ret; } hpriv = ahci_platform_get_resources(pdev); @@ -619,15 +1166,62 @@ static int imx_ahci_probe(struct platform_device *pdev) writel(reg_val, hpriv->mmio + HOST_PORTS_IMPL); } - reg_val = clk_get_rate(imxpriv->ahb_clk) / 1000; - writel(reg_val, hpriv->mmio + IMX_TIMER1MS); + imxpriv->ahb_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(imxpriv->ahb_clk)) { + dev_info(dev, "no ahb clock.\n"); + } else { + /* + * AHB clock is only used to configure the vendor specified + * TIMER1MS register. Set it if the AHB clock is defined. + */ + reg_val = clk_get_rate(imxpriv->ahb_clk) / 1000; + writel(reg_val, hpriv->mmio + IMX_TIMER1MS); + } - ret = ahci_platform_init_host(pdev, hpriv, &ahci_imx_port_info, - &ahci_platform_sht); - if (ret) - goto disable_sata; + /* + * Due to IP bug on the Synopsis 3.00 SATA version, + * which is present on mx6q, and not on mx53, + * we should use sg_tablesize = 1 for reliable operation + */ + if (imxpriv->type == AHCI_IMX6Q || imxpriv->type == AHCI_IMX6QP) { + dma_addr_t dma; + + ahci_platform_sht.sg_tablesize = 1; + + sg_io_buffer_hack = dma_alloc_coherent(NULL, 0x10000, + &dma, GFP_KERNEL); + if (!sg_io_buffer_hack) { + ret = -ENOMEM; + goto disable_sata; + } + } - return 0; + if (imxpriv->type == AHCI_IMX8QM && bist_enable) { + dev_info(dev, "AHCI SATA compliance test patterns.\n"); + ret = clk_prepare_enable(imxpriv->phy_pclk0); + if (ret < 0) + dev_err(dev, "can't enable phy pclk0.\n"); + ret = clk_prepare_enable(imxpriv->phy_pclk1); + if (ret < 0) + dev_err(dev, "can't enable phy pclk1.\n"); + ret = clk_prepare_enable(imxpriv->phy_apbclk); + if (ret < 0) + dev_err(dev, "can't get sata_phy_apbclk clock.\n"); + + dev_set_drvdata(dev, hpriv); + ret = sysfs_create_group(&pdev->dev.kobj, &imx_ahci_attrgroup); + if (ret) + ret = -EINVAL; + dev_info(dev, "Register AHCI SATA BIST sysfile callback.\n"); + } else { + + ret = ahci_platform_init_host(pdev, hpriv, &ahci_imx_port_info, + &ahci_platform_sht); + if (ret) + goto disable_sata; + } + + return ret; disable_sata: imx_sata_disable(hpriv); |