/* * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* QSERDES COMMON registers */ #define QSERDES_COM_SYS_CLK_CTRL 0x000 #define QSERDES_COM_PLL_IP_SETI 0x018 #define QSERDES_COM_PLL_CP_SETI 0x024 #define QSERDES_COM_PLL_IP_SETP 0x028 #define QSERDES_COM_PLL_CP_SETP 0x02c #define QSERDES_COM_SYSCLK_EN_SEL 0x038 #define QSERDES_COM_RESETSM_CNTRL 0x040 #define QSERDES_COM_PLLLOCK_CMP1 0x044 #define QSERDES_COM_PLLLOCK_CMP2 0x048 #define QSERDES_COM_PLLLOCK_CMP3 0x04c #define QSERDES_COM_PLLLOCK_CMP_EN 0x050 #define QSERDES_COM_DEC_START1 0x064 #define QSERDES_COM_SSC_EN_CENTER 0x06c #define QSERDES_COM_SSC_ADJ_PER1 0x070 #define QSERDES_COM_SSC_ADJ_PER2 0x074 #define QSERDES_COM_SSC_PER1 0x078 #define QSERDES_COM_SSC_PER2 0x07c #define QSERDES_COM_SSC_STEP_SIZE1 0x080 #define QSERDES_COM_SSC_STEP_SIZE2 0x084 #define QSERDES_COM_DIV_FRAC_START1 0x098 #define QSERDES_COM_DIV_FRAC_START2 0x09c #define QSERDES_COM_DIV_FRAC_START3 0x0a0 #define QSERDES_COM_DEC_START2 0x0a4 #define QSERDES_COM_PLL_CRCTRL 0x0ac #define QSERDES_COM_RESET_SM 0x0bc /* QSERDES TX registers */ #define QSERDES_TX_BIST_MODE_LANENO 0x100 #define QSERDES_TX_TX_EMP_POST1_LVL 0x108 #define QSERDES_TX_TX_DRV_LVL 0x10c /* QSERDES RX registers */ #define QSERDES_RX_CDR_CONTROL 0x200 #define QSERDES_RX_CDR_CONTROL2 0x210 #define QSERDES_RX_RX_EQ_GAIN12 0x230 #define QSERDES_RX_PWM_CNTRL1 0x280 #define QSERDES_RX_PWM_CNTRL2 0x284 #define QSERDES_RX_CDR_CONTROL_QUARTER 0x29c /* SATA PHY registers */ #define SATA_PHY_SERDES_START 0x300 #define SATA_PHY_CMN_PWR_CTRL 0x304 #define SATA_PHY_RX_PWR_CTRL 0x308 #define SATA_PHY_TX_PWR_CTRL 0x30c #define SATA_PHY_LANE_CTRL1 0x318 #define SATA_PHY_CDR_CTRL0 0x358 #define SATA_PHY_CDR_CTRL1 0x35c #define SATA_PHY_TX_DRV_WAKEUP 0x360 #define SATA_PHY_CLK_BUF_SETTLING 0x364 #define SATA_PHY_SPDNEG_CFG0 0x370 #define SATA_PHY_SPDNEG_CFG1 0x374 #define SATA_PHY_POW_DWN_CTRL0 0x380 #define SATA_PHY_ALIGNP 0x3a4 #define MAX_PROP_NAME 32 #define VDDA_PHY_MIN_UV 950000 #define VDDA_PHY_MAX_UV 1000000 #define VDDA_PLL_MIN_UV 1800000 #define VDDA_PLL_MAX_UV 1800000 struct msm_sata_phy_vreg { const char *name; struct regulator *reg; int max_uA; int min_uV; int max_uV; bool enabled; }; struct msm_sata_phy { struct device *dev; void __iomem *mmio; void __iomem *phy_sel; struct clk *ref_clk_src; struct clk *ref_clk_parent; struct clk *ref_clk; struct clk *rxoob_clk; bool is_ref_clk_enabled; bool is_rxoob_clk_enabled; struct msm_sata_phy_vreg vdda_pll; struct msm_sata_phy_vreg vdda_phy; bool is_powered_on; }; static int msm_sata_enable_phy_rxoob_clk(struct msm_sata_phy *phy) { int err = 0; if (phy->is_rxoob_clk_enabled) goto out; /* set max. 100MHz */ err = clk_set_rate(phy->rxoob_clk, 100000000); if (err) { dev_err(phy->dev, "%s: rxoob_clk set rate failed %d\n", __func__, err); goto out; } err = clk_prepare_enable(phy->rxoob_clk); if (err) { dev_err(phy->dev, "%s: rxoob_clk enable failed %d\n", __func__, err); goto out; } phy->is_rxoob_clk_enabled = true; out: return err; } static void msm_sata_disable_phy_rxoob_clk(struct msm_sata_phy *phy) { if (phy->is_rxoob_clk_enabled) { clk_disable_unprepare(phy->rxoob_clk); phy->is_rxoob_clk_enabled = false; } } static int msm_sata_enable_phy_ref_clk(struct msm_sata_phy *phy) { int err = 0; if (phy->is_ref_clk_enabled) goto out; /* * reference clock is propagated in a daisy-chained manner from * source to phy, so ungate them at each stage. */ err = clk_prepare_enable(phy->ref_clk_src); if (err) { dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n", __func__, err); goto out; } err = clk_prepare_enable(phy->ref_clk_parent); if (err) { dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n", __func__, err); goto out_disable_src; } err = clk_prepare_enable(phy->ref_clk); if (err) { dev_err(phy->dev, "%s: ref_clk enable failed %d\n", __func__, err); goto out_disable_parent; } phy->is_ref_clk_enabled = true; goto out; out_disable_parent: clk_disable_unprepare(phy->ref_clk_parent); out_disable_src: clk_disable_unprepare(phy->ref_clk_src); out: return err; } static void msm_sata_disable_phy_ref_clk(struct msm_sata_phy *phy) { if (phy->is_ref_clk_enabled) { clk_disable_unprepare(phy->ref_clk); clk_disable_unprepare(phy->ref_clk_parent); clk_disable_unprepare(phy->ref_clk_src); phy->is_ref_clk_enabled = false; } } static int msm_sata_phy_cfg_vreg(struct device *dev, struct msm_sata_phy_vreg *vreg, bool on) { int err = 0; struct regulator *reg = vreg->reg; const char *name = vreg->name; int min_uV, uA_load; BUG_ON(!vreg); if (regulator_count_voltages(reg) > 0) { min_uV = on ? vreg->min_uV : 0; err = regulator_set_voltage(reg, min_uV, vreg->max_uV); if (err) { dev_err(dev, "%s: %s set voltage failed, err=%d\n", __func__, name, err); goto out; } uA_load = on ? vreg->max_uA : 0; err = regulator_set_optimum_mode(reg, uA_load); if (err >= 0) { /* * regulator_set_optimum_mode() returns new regulator * mode upon success. */ err = 0; } else { dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n", __func__, name, uA_load, err); goto out; } } out: return err; } static int msm_sata_phy_enable_vreg(struct msm_sata_phy *phy, struct msm_sata_phy_vreg *vreg) { struct device *dev = phy->dev; int err = 0; if (!vreg || vreg->enabled) goto out; err = msm_sata_phy_cfg_vreg(dev, vreg, true); if (!err) err = regulator_enable(vreg->reg); if (!err) vreg->enabled = true; else dev_err(dev, "%s: %s enable failed, err=%d\n", __func__, vreg->name, err); out: return err; } static int msm_sata_phy_disable_vreg(struct msm_sata_phy *phy, struct msm_sata_phy_vreg *vreg) { struct device *dev = phy->dev; int err = 0; if (!vreg || !vreg->enabled) goto out; err = regulator_disable(vreg->reg); if (!err) { /* ignore errors on applying disable config */ msm_sata_phy_cfg_vreg(dev, vreg, false); vreg->enabled = false; } else { dev_err(dev, "%s: %s disable failed, err=%d\n", __func__, vreg->name, err); } out: return err; } static int msm_sata_phy_init_vreg(struct device *dev, struct msm_sata_phy_vreg *vreg, const char *name) { int err = 0; char prop_name[MAX_PROP_NAME]; vreg->name = kstrdup(name, GFP_KERNEL); if (!vreg->name) { err = -ENOMEM; goto out; } vreg->reg = devm_regulator_get(dev, name); if (IS_ERR(vreg->reg)) { err = PTR_ERR(vreg->reg); dev_err(dev, "failed to get %s, %d\n", name, err); goto out; } if (dev->of_node) { snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name); err = of_property_read_u32(dev->of_node, prop_name, &vreg->max_uA); if (err && err != -EINVAL) { dev_err(dev, "%s: failed to read %s\n", __func__, prop_name); goto out; } else if (err == -EINVAL || !vreg->max_uA) { if (regulator_count_voltages(vreg->reg) > 0) { dev_err(dev, "%s: %s is mandatory\n", __func__, prop_name); goto out; } err = 0; } } if (!strcmp(name, "vdda-pll")) { vreg->max_uV = VDDA_PLL_MAX_UV; vreg->min_uV = VDDA_PLL_MIN_UV; } else if (!strcmp(name, "vdda-phy")) { vreg->max_uV = VDDA_PHY_MAX_UV; vreg->min_uV = VDDA_PHY_MIN_UV; } out: if (err) kfree(vreg->name); return err; } static int msm_sata_phy_clk_get(struct device *dev, const char *name, struct clk **clk_out) { struct clk *clk; int err = 0; clk = devm_clk_get(dev, name); if (IS_ERR(clk)) { err = PTR_ERR(clk); dev_err(dev, "failed to get %s err %d", name, err); } else { *clk_out = clk; } return err; } static int msm_sata_phy_power_up(struct msm_sata_phy *phy) { int err = 0; u32 reg; struct device *dev = phy->dev; if (phy->phy_sel) { /* Select SATA PHY */ writel_relaxed(0x0, phy->phy_sel); /* * SATA PHY must be selected before configuring the PHY. * The phy_sel and phy_mmio may be in different register space * and *_relaxed version doesn't ensure ordering in such case. */ mb(); } /* SATA PHY powerup sequence */ /* PWM configurations */ writel_relaxed(0x08, phy->mmio + QSERDES_RX_PWM_CNTRL1); writel_relaxed(0x40, phy->mmio + QSERDES_RX_PWM_CNTRL2); /* Configure PHY power control to operate in mission mode */ writel_relaxed(0x01, phy->mmio + SATA_PHY_POW_DWN_CTRL0); /* CDR counter selected between 30-40 */ writel_relaxed(0x25, phy->mmio + SATA_PHY_CDR_CTRL0); /* Wakeup counter values are set to Maximum */ writel_relaxed(0x0f, phy->mmio + SATA_PHY_CLK_BUF_SETTLING); writel_relaxed(0xff, phy->mmio + SATA_PHY_TX_DRV_WAKEUP); writel_relaxed(0xff, phy->mmio + SATA_PHY_SPDNEG_CFG0); writel_relaxed(0x25, phy->mmio + SATA_PHY_CDR_CTRL0); /* PLL register settings */ writel_relaxed(0xec, phy->mmio + QSERDES_COM_PLL_CRCTRL); writel_relaxed(0x01, phy->mmio + QSERDES_COM_PLL_IP_SETI); writel_relaxed(0x3f, phy->mmio + QSERDES_COM_PLL_CP_SETI); writel_relaxed(0x0f, phy->mmio + QSERDES_COM_PLL_IP_SETP); writel_relaxed(0x13, phy->mmio + QSERDES_COM_PLL_CP_SETP); /* PCS settings for COMMON, TX, RX paths */ writel_relaxed(0x5b, phy->mmio + SATA_PHY_CMN_PWR_CTRL); writel_relaxed(0x32, phy->mmio + SATA_PHY_TX_PWR_CTRL); writel_relaxed(0x83, phy->mmio + SATA_PHY_RX_PWR_CTRL); writel_relaxed(0x7b, phy->mmio + SATA_PHY_CMN_PWR_CTRL); /* Ref clk frequency select - 19.2Mhz selected */ writel_relaxed(0x08, phy->mmio + QSERDES_COM_SYSCLK_EN_SEL); writel_relaxed(0x06, phy->mmio + QSERDES_COM_SYS_CLK_CTRL); /* Decimal and Fractional dividers configuration */ writel_relaxed(0x9c, phy->mmio + QSERDES_COM_DEC_START1); writel_relaxed(0x03, phy->mmio + QSERDES_COM_DEC_START2); writel_relaxed(0xff, phy->mmio + QSERDES_COM_DIV_FRAC_START1); writel_relaxed(0xff, phy->mmio + QSERDES_COM_DIV_FRAC_START2); writel_relaxed(0x13, phy->mmio + QSERDES_COM_DIV_FRAC_START3); /* PLL configurations */ writel_relaxed(0xff, phy->mmio + QSERDES_COM_PLLLOCK_CMP1); writel_relaxed(0x7c, phy->mmio + QSERDES_COM_PLLLOCK_CMP2); writel_relaxed(0x00, phy->mmio + QSERDES_COM_PLLLOCK_CMP3); writel_relaxed(0x01, phy->mmio + QSERDES_COM_PLLLOCK_CMP_EN); /* Other Resetsm configurations - FAST_VCO_TUNE */ writel_relaxed(0x10, phy->mmio + QSERDES_COM_RESETSM_CNTRL); /* PI configurations- First Order threshold and Second order gain */ writel_relaxed(0xeb, phy->mmio + QSERDES_RX_CDR_CONTROL); writel_relaxed(0x5a, phy->mmio + QSERDES_RX_CDR_CONTROL2); /* Config required only on reference boards with shared PHY */ if (phy->phy_sel) writel_relaxed(0x1a, phy->mmio + QSERDES_RX_CDR_CONTROL_QUARTER); /* TX configurations */ /* TX config differences between shared & dedicated PHY */ if (phy->phy_sel) writel_relaxed(0x1f, phy->mmio + QSERDES_TX_TX_DRV_LVL); else writel_relaxed(0x16, phy->mmio + QSERDES_TX_TX_DRV_LVL); writel_relaxed(0x00, phy->mmio + QSERDES_TX_BIST_MODE_LANENO); writel_relaxed(0x30, phy->mmio + QSERDES_TX_TX_EMP_POST1_LVL); /* RX config differences between shared & dedicated PHY */ if (!phy->phy_sel) { writel_relaxed(0x44, phy->mmio + QSERDES_RX_RX_EQ_GAIN12); writel_relaxed(0x01, phy->mmio + SATA_PHY_CDR_CTRL1); } /* SSC Configurations and Serdes start */ writel_relaxed(0x00, phy->mmio + QSERDES_COM_SSC_EN_CENTER); writel_relaxed(0x31, phy->mmio + QSERDES_COM_SSC_PER1); writel_relaxed(0x01, phy->mmio + QSERDES_COM_SSC_PER2); writel_relaxed(0x01, phy->mmio + QSERDES_COM_SSC_ADJ_PER1); writel_relaxed(0x00, phy->mmio + QSERDES_COM_SSC_ADJ_PER2); writel_relaxed(0x3f, phy->mmio + QSERDES_COM_SSC_STEP_SIZE1); writel_relaxed(0x05, phy->mmio + QSERDES_COM_SSC_STEP_SIZE2); writel_relaxed(0x01, phy->mmio + QSERDES_COM_SSC_EN_CENTER); /* * Flush all delayed writes before sleeping to ensure that PHY * configuration is applied. */ mb(); /* Sleep for 1ms before starting serdes */ usleep(1000); /* Start serdes */ writel_relaxed(0x01, phy->mmio + SATA_PHY_SERDES_START); /* * Read RESETSM status until SERDES is ready, * timeout after 1 sec */ err = readl_poll_timeout(phy->mmio + QSERDES_COM_RESET_SM, reg, (reg & (1 << 5)), 100, 1000000); if (err) { dev_err(dev, "%s: poll timeout QSERDES_COM_RESET_SM, status: 0x%x\n", __func__, readl_relaxed(phy->mmio + QSERDES_COM_RESET_SM)); goto out; } /* RX configurations */ writel_relaxed(0x5f, phy->mmio + SATA_PHY_LANE_CTRL1); writel_relaxed(0x43, phy->mmio + SATA_PHY_ALIGNP); dev_dbg(dev, "SATA PHY powered up in functional mode\n"); out: /* power down PHY in case of failure */ if (err) writel_relaxed(0x0, phy->mmio + SATA_PHY_POW_DWN_CTRL0); return err; } static int msm_sata_phy_power_off(struct phy *generic_phy) { struct msm_sata_phy *phy = phy_get_drvdata(generic_phy); writel_relaxed(0x0, phy->mmio + SATA_PHY_POW_DWN_CTRL0); /* * Ensure that the PHY is power down before gating power. * It is possible that PHY regulators might be turned on if * other PHY's share the same regulators. */ mb(); msm_sata_disable_phy_rxoob_clk(phy); msm_sata_disable_phy_ref_clk(phy); msm_sata_phy_disable_vreg(phy, &phy->vdda_pll); msm_sata_phy_disable_vreg(phy, &phy->vdda_phy); phy->is_powered_on = false; return 0; } static int msm_sata_phy_power_on(struct phy *generic_phy) { int err; struct msm_sata_phy *phy = phy_get_drvdata(generic_phy); err = msm_sata_phy_enable_vreg(phy, &phy->vdda_phy); if (err) goto out; /* vdda_pll also enables ref clock LDOs so enable it first */ err = msm_sata_phy_enable_vreg(phy, &phy->vdda_pll); if (err) goto out_disable_phy; err = msm_sata_enable_phy_ref_clk(phy); if (err) goto out_disable_pll; err = msm_sata_enable_phy_rxoob_clk(phy); if (err) goto out_disable_ref; err = msm_sata_phy_power_up(phy); if (err) goto out_disable_rxoob; phy->is_powered_on = true; goto out; out_disable_rxoob: msm_sata_disable_phy_rxoob_clk(phy); out_disable_ref: msm_sata_disable_phy_ref_clk(phy); out_disable_pll: msm_sata_phy_disable_vreg(phy, &phy->vdda_pll); out_disable_phy: msm_sata_phy_disable_vreg(phy, &phy->vdda_phy); out: return err; } static int msm_sata_phy_init(struct phy *generic_phy) { int err; struct msm_sata_phy *phy = phy_get_drvdata(generic_phy); struct device *dev = phy->dev; err = msm_sata_phy_clk_get(dev, "ref_clk_src", &phy->ref_clk_src); if (err) goto out; err = msm_sata_phy_clk_get(dev, "ref_clk_parent", &phy->ref_clk_parent); if (err) goto out; err = msm_sata_phy_clk_get(dev, "ref_clk", &phy->ref_clk); if (err) goto out; err = msm_sata_phy_clk_get(dev, "rxoob_clk", &phy->rxoob_clk); if (err) goto out; err = msm_sata_phy_init_vreg(dev, &phy->vdda_pll, "vdda-pll"); if (err) goto out; err = msm_sata_phy_init_vreg(dev, &phy->vdda_phy, "vdda-phy"); if (err) goto out; out: return err; } static int msm_sata_phy_exit(struct phy *generic_phy) { struct msm_sata_phy *phy = phy_get_drvdata(generic_phy); if (phy->is_powered_on) msm_sata_phy_power_off(generic_phy); return 0; } static struct phy_ops msm_sata_phy_ops = { .init = msm_sata_phy_init, .exit = msm_sata_phy_exit, .power_on = msm_sata_phy_power_on, .power_off = msm_sata_phy_power_off, .owner = THIS_MODULE, }; static int msm_sata_phy_probe(struct platform_device *pdev) { int err = 0; struct msm_sata_phy *phy; struct device *dev = &pdev->dev; struct resource *res; struct phy_provider *phy_provider; struct phy *generic_phy; phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); if (!phy) { err = -ENOMEM; dev_err(dev, "%s: failed to allocate phy\n", __func__); goto out; } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_sel"); phy->phy_sel = devm_ioremap_resource(dev, res); if (IS_ERR(phy->phy_sel)) { err = PTR_ERR(phy->phy_sel); /* phy_sel resource is optional */ phy->phy_sel = 0; dev_dbg(dev, "%s: phy select resource get failed %d\n", __func__, err); } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem"); phy->mmio = devm_ioremap_resource(dev, res); if (IS_ERR(phy->mmio)) { err = PTR_ERR(phy->mmio); dev_err(dev, "%s: phy mmio get resource failed %d\n", __func__, err); goto out; } phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); if (IS_ERR(phy_provider)) { err = PTR_ERR(phy_provider); dev_err(dev, "%s: failed to register phy %d\n", __func__, err); goto out; } generic_phy = devm_phy_create(dev, NULL, &msm_sata_phy_ops, NULL); if (IS_ERR(generic_phy)) { err = PTR_ERR(generic_phy); dev_err(dev, "%s: failed to create phy %d\n", __func__, err); goto out; } phy->dev = dev; phy_set_drvdata(generic_phy, phy); return 0; out: return err; } static const struct of_device_id msm_sata_phy_of_match[] = { { .compatible = "qcom,sataphy" }, { }, }; MODULE_DEVICE_TABLE(of, msm_sata_phy_of_match); static struct platform_driver msm_sata_phy_driver = { .probe = msm_sata_phy_probe, .driver = { .name = "msm-sata-phy", .owner = THIS_MODULE, .of_match_table = msm_sata_phy_of_match, } }; module_platform_driver(msm_sata_phy_driver); MODULE_DESCRIPTION("MSM 6Gbps SATA PHY driver"); MODULE_LICENSE("GPL v2");