blob: 7d68c52ae009891224787a17ab420ab19b478f15 [file] [log] [blame]
/*
* sata_brcmstb_phy.c - Broadcom SATA3 AHCI Controller PHY Driver
*
* Copyright (C) 2009 - 2013 Broadcom Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define pr_fmt(fmt) "brcm-sata3-phy: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/libata.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/ahci_platform.h>
#include <linux/compiler.h>
#include <linux/brcmstb/brcmstb.h>
#include <scsi/scsi_host.h>
#include "sata_brcmstb.h"
#include "ahci.h"
static void sata_mdio_wr_28nm(void __iomem *addr, u32 port, u32 bank, u32 ofs,
u32 msk, u32 value)
{
u32 tmp;
void __iomem *base = addr + (port * SATA_MDIO_REG_SPACE_SIZE);
writel(bank, base + SATA_MDIO_BANK_OFFSET);
tmp = readl(base + SATA_MDIO_REG_OFFSET(ofs));
tmp = (tmp & msk) | value;
writel(tmp, base + SATA_MDIO_REG_OFFSET(ofs));
}
static void sata_mdio_wr_legacy(void __iomem *addr, u32 port, u32 bank, u32 ofs,
u32 msk, u32 value)
{
u32 tmp;
u32 bank_port = bank + (port * SATA_MDIO_REG_LEGACY_BANK_OFS);
writel(bank_port, addr + SATA_MDIO_BANK_OFFSET);
tmp = readl(addr + SATA_MDIO_REG_OFFSET(ofs));
tmp = (tmp & msk) | value;
writel(tmp, addr + SATA_MDIO_REG_OFFSET(ofs));
}
/* These defaults were characterized by H/W group */
#define FMIN_VAL_DEFAULT 0x3df
#define FMAX_VAL_DEFAULT 0x3df
#define FMAX_VAL_SSC 0x83
static void cfg_ssc_28nm(void __iomem *base, int port, int ssc_en)
{
u32 tmp;
/* override the TX spread spectrum setting */
tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC;
sata_mdio_wr_28nm(base, port, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp,
tmp);
/* set fixed min freq */
sata_mdio_wr_28nm(base, port, TXPMD_REG_BANK,
TXPMD_TX_FREQ_CTRL_CONTROL2,
~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK,
FMIN_VAL_DEFAULT);
/* set fixed max freq depending on SSC config */
if (ssc_en) {
pr_info("Enabling SSC on port %d\n", port);
tmp = FMAX_VAL_SSC;
} else
tmp = FMAX_VAL_DEFAULT;
sata_mdio_wr_28nm(base, port, TXPMD_REG_BANK,
TXPMD_TX_FREQ_CTRL_CONTROL3,
~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp);
}
static void cfg_ssc_legacy(void __iomem *base, int port, int ssc_en)
{
u32 tmp;
/* override the TX spread spectrum setting */
tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC;
sata_mdio_wr_legacy(base, port, TXPMD_REG_BANK_LEGACY, TXPMD_CONTROL1,
~tmp, tmp);
/* set fixed min freq */
sata_mdio_wr_legacy(base, port, TXPMD_REG_BANK_LEGACY,
TXPMD_TX_FREQ_CTRL_CONTROL2,
~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK,
FMIN_VAL_DEFAULT);
/* set fixed max freq depending on SSC config */
if (ssc_en) {
pr_info("Enabling SSC on port %d\n", port);
tmp = FMAX_VAL_SSC;
} else
tmp = FMAX_VAL_DEFAULT;
sata_mdio_wr_legacy(base, port, TXPMD_REG_BANK_LEGACY,
TXPMD_TX_FREQ_CTRL_CONTROL3,
~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp);
}
static struct sata_phy_cfg_ops cfg_op_tbl[SATA_PHY_MDIO_END] = {
[SATA_PHY_MDIO_LEGACY] = {
.cfg_ssc = cfg_ssc_legacy,
},
[SATA_PHY_MDIO_28NM] = {
.cfg_ssc = cfg_ssc_28nm,
},
};
static struct sata_phy_cfg_ops *cfg_op;
int brcm_sata3_phy_spd_get(const struct sata_brcm_pdata *pdata, int port)
{
int val = (pdata->phy_force_spd[port / SPD_SETTING_PER_U32]
>> SPD_SETTING_SHIFT(port));
return val & SPD_SETTING_MASK;
}
EXPORT_SYMBOL(brcm_sata3_phy_spd_get);
void brcm_sata3_phy_spd_set(struct sata_brcm_pdata *pdata, int port, int val)
{
int tmp = pdata->phy_force_spd[port / SPD_SETTING_PER_U32];
pr_debug("Forcing port %d to gen %d speed\n", port, val);
tmp &= ~(SPD_SETTING_MASK << SPD_SETTING_SHIFT(port));
tmp |= (val & SPD_SETTING_MASK) << SPD_SETTING_SHIFT(port);
pdata->phy_force_spd[port / SPD_SETTING_WIDTH] = tmp;
}
EXPORT_SYMBOL(brcm_sata3_phy_spd_set);
static void _brcm_sata3_phy_cfg(const struct sata_brcm_pdata *pdata, int port,
int enable)
{
/* yfzhang@broadcom.com has stated that the core will only have (2)
* ports. Further, the RDB currently lacks documentation for these
* registers. So just keep a map of which port corresponds to these
* magic registers.
*/
const u32 port_to_phy_ctrl_ofs[MAX_PHY_CTRL_PORTS] = {
SATA_TOP_CTRL_PHY_CTRL_OFS + (0 * SATA_TOP_CTRL_PHY_CTRL_LEN),
SATA_TOP_CTRL_PHY_CTRL_OFS + (1 * SATA_TOP_CTRL_PHY_CTRL_LEN),
};
void __iomem *top_ctrl;
top_ctrl = ioremap(pdata->top_ctrl_base_addr, SATA_TOP_CTRL_REG_LENGTH);
if (!top_ctrl) {
pr_err("failed to ioremap SATA top ctrl regs\n");
return;
}
if (port < MAX_PHY_CTRL_PORTS) {
void __iomem *p;
u32 reg;
if (enable) {
/* clear PHY_DEFAULT_POWER_STATE */
p = top_ctrl + port_to_phy_ctrl_ofs[port] +
SATA_TOP_CTRL_PHY_CTRL_1;
reg = readl(p);
reg &= ~SATA_TOP_CTRL_1_PHY_DEFAULT_POWER_STATE;
writel(reg, p);
/* reset the PHY digital logic */
p = top_ctrl + port_to_phy_ctrl_ofs[port] +
SATA_TOP_CTRL_PHY_CTRL_2;
reg = readl(p);
reg &= ~(SATA_TOP_CTRL_2_SW_RST_MDIOREG |
SATA_TOP_CTRL_2_SW_RST_OOB |
SATA_TOP_CTRL_2_SW_RST_RX);
reg |= SATA_TOP_CTRL_2_SW_RST_TX;
writel(reg, p);
reg = readl(p);
reg |= SATA_TOP_CTRL_2_PHY_GLOBAL_RESET;
writel(reg, p);
reg = readl(p);
reg &= ~SATA_TOP_CTRL_2_PHY_GLOBAL_RESET;
writel(reg, p);
reg = readl(p);
} else {
/* power-off the PHY digital logic */
p = top_ctrl + port_to_phy_ctrl_ofs[port] +
SATA_TOP_CTRL_PHY_CTRL_2;
reg = readl(p);
reg |= (SATA_TOP_CTRL_2_SW_RST_MDIOREG |
SATA_TOP_CTRL_2_SW_RST_OOB |
SATA_TOP_CTRL_2_SW_RST_RX |
SATA_TOP_CTRL_2_SW_RST_TX |
SATA_TOP_CTRL_2_PHY_GLOBAL_RESET);
writel(reg, p);
/* set PHY_DEFAULT_POWER_STATE */
p = top_ctrl + port_to_phy_ctrl_ofs[port] +
SATA_TOP_CTRL_PHY_CTRL_1;
reg = readl(p);
reg |= SATA_TOP_CTRL_1_PHY_DEFAULT_POWER_STATE;
writel(reg, p);
}
}
iounmap(top_ctrl);
}
void brcm_sata3_phy_cfg(const struct sata_brcm_pdata *pdata, int port,
int enable)
{
const u32 phy_base = pdata->phy_base_addr;
const int ssc_enable = pdata->phy_enable_ssc_mask & (1 << port);
void __iomem *base;
base = ioremap(phy_base, SATA_MDIO_REG_LENGTH);
if (!base) {
pr_err("%s: Failed to ioremap PHY registers!\n", __func__);
goto err;
}
if (pdata->phy_generation == 0x2800)
cfg_op = &cfg_op_tbl[SATA_PHY_MDIO_28NM];
else
cfg_op = &cfg_op_tbl[SATA_PHY_MDIO_LEGACY];
if (enable) {
_brcm_sata3_phy_cfg(pdata, port, 1);
if (cfg_op->cfg_ssc)
cfg_op->cfg_ssc(base, port, ssc_enable);
} else
_brcm_sata3_phy_cfg(pdata, port, 0);
iounmap(base);
err:
return;
}
EXPORT_SYMBOL(brcm_sata3_phy_cfg);