| /* |
| * 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); |