blob: 8de31902dce0f0c794520d195d73265190b13e99 [file] [log] [blame]
/*
* 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 <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/iopoll.h>
#include <linux/regulator/consumer.h>
/* 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, &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");