blob: fdc76a4b67b3624c2db038b1f72f422c996473b3 [file] [log] [blame]
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/phy/phy.h>
#include <linux/sched.h>
#include <linux/reset.h>
#include <dt-bindings/phy/phy.h>
#include "phy-snowbush-regs.h"
struct snowbush_phy {
struct phy *phy;
void __iomem *base;
void __iomem *dwc1_serdes_cfg_base;
struct reset_control *reset;
struct reset_control *pcie_rst;
struct reset_control *sata_rst;
bool pcie_tx_pol_inv;
bool sata_tx_pol_inv;
u32 sata_gen;
u32 ctrlreg;
u8 type;
};
struct snowbush_dev {
struct device *dev;
struct regmap *regmap;
struct mutex sbphy_mutex;
struct snowbush_phy **phys;
int nphys;
};
#define SD_COMMON_LANE 0xA00
#define SD_PHY_STS_REG_OFST 0x00
#define SD_PHY_CTRL1_REG_OFST 0x04
#define SD_PHY_CTRL2_REG_OFST 0x08
#define SD_PHY_CTRL3_REG_OFST 0x0C
#define MAX_LANE_OK_WAIT_JIFFIES (200 * HZ) / 1000 /* 200ms */
#define MAX_CMU_OK_WAIT_JIFFIES (2000 * HZ) / 1000 /* 2 Seconds */
/**
* This function Wait for the 'Lane OK' to be signaled by the
* Snowbush Serdes PHY.
* @param sbphy_num SerDes PHY intefrace number.
*/
static int wait_lane_ok(struct snowbush_phy *sbphy_phy,
struct snowbush_dev *sbphy_dev)
{
u32 rd_data = 0, masked_data = 0;
u32 lane_ok_dtctd_mask = 0x00001000;
unsigned long deadline = jiffies + MAX_LANE_OK_WAIT_JIFFIES;
/* Keep looping until you see the lane_ok_o of Serdes */
do
{
rd_data = readl(sbphy_phy->dwc1_serdes_cfg_base + SD_PHY_STS_REG_OFST);
/* Mask lane_ok Status */
masked_data = rd_data & lane_ok_dtctd_mask;
if(masked_data == lane_ok_dtctd_mask) {
/* Lane OK Detected on Serdes Port */
dev_info(&sbphy_phy->phy->dev, "Serdes: Lane OK passed\n");
return 1;
}
cond_resched();
} while (!time_after_eq(jiffies, deadline));
dev_warn(&sbphy_phy->phy->dev, "Serdes: Lane OK failed\n");
return 0;
}
/**
* This function wait for the 'CMU OK' to be signaled by the
* Snowbush Serdes PHY.
* @param sbphy_num SerDes PHY intefrace number.
*/
static int wait_cmu_ok(struct snowbush_phy *sbphy_phy,
struct snowbush_dev *sbphy_dev)
{
u32 rd_data = 0, masked_data = 0;
u32 cmu_ok_dtctd_mask = 0x00004000;
volatile void __iomem *CMU_Offset;
unsigned long deadline = jiffies + MAX_CMU_OK_WAIT_JIFFIES;
CMU_Offset = sbphy_phy->dwc1_serdes_cfg_base + SD_PHY_STS_REG_OFST;
/* Keep looping until you see the cmu_ok_o of Serdes */
do
{
rd_data = readl(CMU_Offset);
/* Mask cmu_ok Status */
masked_data = rd_data & cmu_ok_dtctd_mask;
if(masked_data == cmu_ok_dtctd_mask) {
/* CMU OK Detected on Serdes Port */
dev_info(&sbphy_phy->phy->dev, "Serdes: CMU OK Passed\n");
return 1;
}
cond_resched();
} while (!time_after_eq(jiffies, deadline));
dev_warn(&sbphy_phy->phy->dev, "Serdes: CMU OK failed\n");
return 0;
}
/**
* This function wait for the specified configured Snowbush PHY
* (Serdes) to issue it's CMU-OK, and it's Lane to become Ready
* after releasing the CMU & Lane resets.
* @param sbphy_num SerDes PHY intefrace number.
*/
static int wait_sb_cmu_lane_rdy(struct snowbush_phy *sbphy_phy,
struct snowbush_dev *sbphy_dev)
{
volatile void __iomem *sd_ctl2_reg_offset;
u32 cmu_rst_mask = 0x00010000;
u32 lane_rst_mask = 0x00000040;
u32 tmp = 0;
sd_ctl2_reg_offset =
sbphy_phy->dwc1_serdes_cfg_base + SD_PHY_CTRL2_REG_OFST;
/* Releasing the CMU Reset */
tmp = readl(sd_ctl2_reg_offset);
tmp = tmp & (~cmu_rst_mask);
tmp = tmp | cmu_rst_mask;
writel(tmp, sd_ctl2_reg_offset );
/* Waiting for CMU OK */
if( !wait_cmu_ok(sbphy_phy, sbphy_dev) )
return -1;
if (sbphy_phy->type == PHY_TYPE_PCIE)
writel(0xC3, sbphy_phy->base + (SD_COMMON_LANE << 2));
else
writel(0x03, sbphy_phy->base + (SD_COMMON_LANE << 2));
/* Releasing the Lane Reset */
tmp = readl(sd_ctl2_reg_offset);
tmp = tmp & (~lane_rst_mask);
tmp = tmp | lane_rst_mask;
writel(tmp, sd_ctl2_reg_offset);
/* Waiting for the Lane Ready */
if (sbphy_phy->type != PHY_TYPE_PCIE) {
if( !wait_lane_ok(sbphy_phy, sbphy_dev) )
return -1;
}
return 0;
}
/**
* This function initialize the Snowbush PHY (Serdes) for operation
* with the one of the PCIE,SATA or SGMII IP blocks, and then waiting
* until it issue it's CMU-OK, and it's Lane to become Ready after
* releasing the CMU & Lane Resets.
* @param phy_num SerDes PHY intefrace number.
* @param *regs Register file (Array of registers and coresponding
* values to be programmed).
* @param size Number of registers to be programmed.
*/
static int serdes_phy_init(struct snowbush_phy *sbphy_phy,
struct snowbush_dev *sbphy_dev)
{
int i, size, ret;
struct serdes_regs_s *regs;
static int misc = 0;
ret = reset_control_deassert(sbphy_phy->reset);
if (ret)
return ret;
if (sbphy_phy->type == PHY_TYPE_PCIE && sbphy_phy->pcie_rst) {
ret = reset_control_deassert(sbphy_phy->pcie_rst);
if (ret)
return ret;
}
if (sbphy_phy->type == PHY_TYPE_SATA && sbphy_phy->sata_rst) {
ret = reset_control_deassert(sbphy_phy->sata_rst);
if (ret)
return ret;
}
if (!misc) {
/* Move SATA controller to DDRC2 port */
writel(readl((void*) 0xF097006C) | 0x2, (void*) 0xF097006C); // TODO:COMCERTO_GPIO_FABRIC_CTRL_REG
writel(readl((void*) 0xF09B0058) & ~4, (void*) 0xF09B0058); // AXI_RESET_2 TODO: SATA_AXI
writel(readl((void*) 0xF09B0048) | 3, (void*) 0xF09B0048); // AXI_CLK_CNTRL_2
writel(readl((void*) 0xF09B0048) | 4, (void*) 0xF09B0048); // AXI_CLK_CNTRL_2: SATA
writel(readl((void*) 0xF09B0168) & ~1, (void*) 0xF09B0168); // TODO: SATA_PMU
writel(readl((void*) 0xF09B0178) & ~1, (void*) 0xF09B0178); // TODO: SATA_OOB
misc = 1;
}
if (sbphy_phy->type == PHY_TYPE_PCIE) {
/* SW select for ck_soc_div_i SOC clock */
writel(0xFF3C, sbphy_phy->dwc1_serdes_cfg_base + SD_PHY_CTRL3_REG_OFST);
writel(readl(sbphy_phy->dwc1_serdes_cfg_base + SD_PHY_CTRL2_REG_OFST) & ~0x3,
sbphy_phy->dwc1_serdes_cfg_base + SD_PHY_CTRL2_REG_OFST);
}
if (sbphy_phy->type == PHY_TYPE_SATA) {
regs = sata_phy_reg_file_24; /* TODO: Support for 48MHz crystal */
size = ARRAY_SIZE(sata_phy_reg_file_24);
} else if (sbphy_phy->type == PHY_TYPE_PCIE) {
regs = pcie_phy_reg_file_24;
size = ARRAY_SIZE(pcie_phy_reg_file_24);
if (1) { // (system_rev == 1) { /* TODO */
// C2K RevA1 devices use a different serdes clk divider
regs[0x61].val = 0x6;
}
} else {
BUG();
}
/* Initilize serdes phy registers */
for(i = 0; i < size; i++ )
writel(regs[i].val, sbphy_phy->base + regs[i].ofst);
/* Wait for the initialization of Serdes-1 Port/Lane to become Ready */
return wait_sb_cmu_lane_rdy(sbphy_phy, sbphy_dev);
}
static int snowbush_init(struct phy *phy)
{
struct snowbush_phy *sbphy_phy = phy_get_drvdata(phy);
struct snowbush_dev *sbphy_dev = dev_get_drvdata(phy->dev.parent);
int ret = 0;
mutex_lock(&sbphy_dev->sbphy_mutex);
serdes_phy_init(sbphy_phy, sbphy_dev);
mutex_unlock(&sbphy_dev->sbphy_mutex);
return ret;
}
static struct phy *snowbush_xlate(struct device *dev,
struct of_phandle_args *args)
{
struct snowbush_dev *sbphy_dev = dev_get_drvdata(dev);
struct snowbush_phy *sbphy_phy = NULL;
struct device_node *phynode = args->np;
int index;
if (!of_device_is_available(phynode)) {
dev_warn(dev, "Requested PHY is disabled\n");
return ERR_PTR(-ENODEV);
}
if (args->args_count != 1) {
dev_err(dev, "Invalid number of cells in 'phy' property\n");
return ERR_PTR(-EINVAL);
}
for (index = 0; index < sbphy_dev->nphys; index++)
if (phynode == sbphy_dev->phys[index]->phy->dev.of_node) {
sbphy_phy = sbphy_dev->phys[index];
break;
}
if (!sbphy_phy) {
dev_err(dev, "Failed to find appropriate phy\n");
return ERR_PTR(-EINVAL);
}
sbphy_phy->type = args->args[0];
if (!(sbphy_phy->type == PHY_TYPE_SATA ||
sbphy_phy->type == PHY_TYPE_PCIE)) {
dev_err(dev, "Unsupported device type: %d\n", sbphy_phy->type);
return ERR_PTR(-EINVAL);
}
return sbphy_phy->phy;
}
static struct phy_ops snowbush_ops = {
.init = snowbush_init,
.owner = THIS_MODULE,
};
static int snowbush_of_probe(struct device_node *phynode,
struct snowbush_phy *sbphy_phy)
{
sbphy_phy->base = of_iomap(phynode, 0);
if (!sbphy_phy->base) {
dev_err(&sbphy_phy->phy->dev, "Failed to map %s\n", phynode->full_name);
return -EINVAL;
}
sbphy_phy->dwc1_serdes_cfg_base = of_iomap(phynode, 1);
if (!sbphy_phy->dwc1_serdes_cfg_base) {
dev_err(&sbphy_phy->phy->dev, "Failed to map %s dwc1_serdes_cfg_base\n", phynode->full_name);
return -EINVAL;
}
sbphy_phy->reset = devm_reset_control_get(&sbphy_phy->phy->dev, "serdes");
if (IS_ERR(sbphy_phy->reset)) {
dev_err(&sbphy_phy->phy->dev, "failed to get reset control\n");
return PTR_ERR(sbphy_phy->reset);
}
sbphy_phy->pcie_rst = devm_reset_control_get(&sbphy_phy->phy->dev, "serdes_pcie");
if (IS_ERR(sbphy_phy->pcie_rst)) {
sbphy_phy->pcie_rst = 0;
}
sbphy_phy->sata_rst = devm_reset_control_get(&sbphy_phy->phy->dev, "serdes_sata");
if (IS_ERR(sbphy_phy->sata_rst)) {
sbphy_phy->sata_rst = 0;
}
/* TODO */
return 0;
}
static int snowbush_probe(struct platform_device *pdev)
{
struct device_node *child, *np = pdev->dev.of_node;
struct snowbush_dev *sbphy_dev;
struct phy_provider *provider;
struct phy *phy;
int ret, port = 0;
sbphy_dev = devm_kzalloc(&pdev->dev, sizeof(*sbphy_dev), GFP_KERNEL);
if (!sbphy_dev)
return -ENOMEM;
sbphy_dev->nphys = of_get_child_count(np);
sbphy_dev->phys = devm_kcalloc(&pdev->dev, sbphy_dev->nphys,
sizeof(*sbphy_dev->phys), GFP_KERNEL);
if (!sbphy_dev->phys)
return -ENOMEM;
sbphy_dev->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, sbphy_dev);
mutex_init(&sbphy_dev->sbphy_mutex);
for_each_child_of_node(np, child) {
struct snowbush_phy *sbphy_phy;
sbphy_phy = devm_kzalloc(&pdev->dev, sizeof(*sbphy_phy),
GFP_KERNEL);
if (!sbphy_phy)
return -ENOMEM;
sbphy_dev->phys[port] = sbphy_phy;
phy = devm_phy_create(&pdev->dev, child, &snowbush_ops);
if (IS_ERR(phy)) {
dev_err(&pdev->dev, "failed to create PHY\n");
return PTR_ERR(phy);
}
sbphy_dev->phys[port]->phy = phy;
ret = snowbush_of_probe(child, sbphy_phy);
if (ret)
return ret;
phy_set_drvdata(phy, sbphy_dev->phys[port]);
port++;
}
provider = devm_of_phy_provider_register(&pdev->dev, snowbush_xlate);
return PTR_ERR_OR_ZERO(provider);
}
static const struct of_device_id snowbush_of_match[] = {
{ .compatible = "fsl,ls1024a-snowbush-phy", },
{ },
};
MODULE_DEVICE_TABLE(of, snowbush_of_match);
static struct platform_driver snowbush_driver = {
.probe = snowbush_probe,
.driver = {
.name = "snowbush-phy",
.of_match_table = snowbush_of_match,
}
};
module_platform_driver(snowbush_driver);
MODULE_AUTHOR("Daniel Mentz <danielmentz@google.com>");
MODULE_DESCRIPTION("Snowbush PHY driver");
MODULE_LICENSE("GPL v2");