diff options
Diffstat (limited to 'drivers/phy/broadcom')
-rw-r--r-- | drivers/phy/broadcom/Kconfig | 55 | ||||
-rw-r--r-- | drivers/phy/broadcom/Makefile | 6 | ||||
-rw-r--r-- | drivers/phy/broadcom/phy-bcm-cygnus-pcie.c | 221 | ||||
-rw-r--r-- | drivers/phy/broadcom/phy-bcm-kona-usb2.c | 155 | ||||
-rw-r--r-- | drivers/phy/broadcom/phy-bcm-ns-usb2.c | 137 | ||||
-rw-r--r-- | drivers/phy/broadcom/phy-bcm-ns-usb3.c | 303 | ||||
-rw-r--r-- | drivers/phy/broadcom/phy-bcm-ns2-pcie.c | 101 | ||||
-rw-r--r-- | drivers/phy/broadcom/phy-brcm-sata.c | 493 |
8 files changed, 1471 insertions, 0 deletions
diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig new file mode 100644 index 000000000000..d2d99023ec50 --- /dev/null +++ b/drivers/phy/broadcom/Kconfig @@ -0,0 +1,55 @@ +# +# Phy drivers for Broadcom platforms +# +config PHY_CYGNUS_PCIE + tristate "Broadcom Cygnus PCIe PHY driver" + depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST) + select GENERIC_PHY + default ARCH_BCM_CYGNUS + help + Enable this to support the Broadcom Cygnus PCIe PHY. + If unsure, say N. + +config BCM_KONA_USB2_PHY + tristate "Broadcom Kona USB2 PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support the Broadcom Kona USB 2.0 PHY. + +config PHY_BCM_NS_USB2 + tristate "Broadcom Northstar USB 2.0 PHY Driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support Broadcom USB 2.0 PHY connected to the USB + controller on Northstar family. + +config PHY_BCM_NS_USB3 + tristate "Broadcom Northstar USB 3.0 PHY Driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support Broadcom USB 3.0 PHY connected to the USB + controller on Northstar family. + +config PHY_NS2_PCIE + tristate "Broadcom Northstar2 PCIe PHY driver" + depends on OF && MDIO_BUS_MUX_BCM_IPROC + select GENERIC_PHY + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom Northstar2 PCIe PHY. + If unsure, say N. + +config PHY_BRCM_SATA + tristate "Broadcom SATA PHY driver" + depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || COMPILE_TEST + depends on OF + select GENERIC_PHY + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom SATA PHY. + If unsure, say N. diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile new file mode 100644 index 000000000000..357a7d16529f --- /dev/null +++ b/drivers/phy/broadcom/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o +obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o +obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o +obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o +obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o +obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c new file mode 100644 index 000000000000..0f4ac5d63cff --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2015 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#define PCIE_CFG_OFFSET 0x00 +#define PCIE1_PHY_IDDQ_SHIFT 10 +#define PCIE0_PHY_IDDQ_SHIFT 2 + +enum cygnus_pcie_phy_id { + CYGNUS_PHY_PCIE0 = 0, + CYGNUS_PHY_PCIE1, + MAX_NUM_PHYS, +}; + +struct cygnus_pcie_phy_core; + +/** + * struct cygnus_pcie_phy - Cygnus PCIe PHY device + * @core: pointer to the Cygnus PCIe PHY core control + * @id: internal ID to identify the Cygnus PCIe PHY + * @phy: pointer to the kernel PHY device + */ +struct cygnus_pcie_phy { + struct cygnus_pcie_phy_core *core; + enum cygnus_pcie_phy_id id; + struct phy *phy; +}; + +/** + * struct cygnus_pcie_phy_core - Cygnus PCIe PHY core control + * @dev: pointer to device + * @base: base register + * @lock: mutex to protect access to individual PHYs + * @phys: pointer to Cygnus PHY device + */ +struct cygnus_pcie_phy_core { + struct device *dev; + void __iomem *base; + struct mutex lock; + struct cygnus_pcie_phy phys[MAX_NUM_PHYS]; +}; + +static int cygnus_pcie_power_config(struct cygnus_pcie_phy *phy, bool enable) +{ + struct cygnus_pcie_phy_core *core = phy->core; + unsigned shift; + u32 val; + + mutex_lock(&core->lock); + + switch (phy->id) { + case CYGNUS_PHY_PCIE0: + shift = PCIE0_PHY_IDDQ_SHIFT; + break; + + case CYGNUS_PHY_PCIE1: + shift = PCIE1_PHY_IDDQ_SHIFT; + break; + + default: + mutex_unlock(&core->lock); + dev_err(core->dev, "PCIe PHY %d invalid\n", phy->id); + return -EINVAL; + } + + if (enable) { + val = readl(core->base + PCIE_CFG_OFFSET); + val &= ~BIT(shift); + writel(val, core->base + PCIE_CFG_OFFSET); + /* + * Wait 50 ms for the PCIe Serdes to stabilize after the analog + * front end is brought up + */ + msleep(50); + } else { + val = readl(core->base + PCIE_CFG_OFFSET); + val |= BIT(shift); + writel(val, core->base + PCIE_CFG_OFFSET); + } + + mutex_unlock(&core->lock); + dev_dbg(core->dev, "PCIe PHY %d %s\n", phy->id, + enable ? "enabled" : "disabled"); + return 0; +} + +static int cygnus_pcie_phy_power_on(struct phy *p) +{ + struct cygnus_pcie_phy *phy = phy_get_drvdata(p); + + return cygnus_pcie_power_config(phy, true); +} + +static int cygnus_pcie_phy_power_off(struct phy *p) +{ + struct cygnus_pcie_phy *phy = phy_get_drvdata(p); + + return cygnus_pcie_power_config(phy, false); +} + +static const struct phy_ops cygnus_pcie_phy_ops = { + .power_on = cygnus_pcie_phy_power_on, + .power_off = cygnus_pcie_phy_power_off, + .owner = THIS_MODULE, +}; + +static int cygnus_pcie_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node, *child; + struct cygnus_pcie_phy_core *core; + struct phy_provider *provider; + struct resource *res; + unsigned cnt = 0; + int ret; + + if (of_get_child_count(node) == 0) { + dev_err(dev, "PHY no child node\n"); + return -ENODEV; + } + + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + core->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + core->base = devm_ioremap_resource(dev, res); + if (IS_ERR(core->base)) + return PTR_ERR(core->base); + + mutex_init(&core->lock); + + for_each_available_child_of_node(node, child) { + unsigned int id; + struct cygnus_pcie_phy *p; + + if (of_property_read_u32(child, "reg", &id)) { + dev_err(dev, "missing reg property for %s\n", + child->name); + ret = -EINVAL; + goto put_child; + } + + if (id >= MAX_NUM_PHYS) { + dev_err(dev, "invalid PHY id: %u\n", id); + ret = -EINVAL; + goto put_child; + } + + if (core->phys[id].phy) { + dev_err(dev, "duplicated PHY id: %u\n", id); + ret = -EINVAL; + goto put_child; + } + + p = &core->phys[id]; + p->phy = devm_phy_create(dev, child, &cygnus_pcie_phy_ops); + if (IS_ERR(p->phy)) { + dev_err(dev, "failed to create PHY\n"); + ret = PTR_ERR(p->phy); + goto put_child; + } + + p->core = core; + p->id = id; + phy_set_drvdata(p->phy, p); + cnt++; + } + + dev_set_drvdata(dev, core); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "failed to register PHY provider\n"); + return PTR_ERR(provider); + } + + dev_dbg(dev, "registered %u PCIe PHY(s)\n", cnt); + + return 0; +put_child: + of_node_put(child); + return ret; +} + +static const struct of_device_id cygnus_pcie_phy_match_table[] = { + { .compatible = "brcm,cygnus-pcie-phy" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cygnus_pcie_phy_match_table); + +static struct platform_driver cygnus_pcie_phy_driver = { + .driver = { + .name = "cygnus-pcie-phy", + .of_match_table = cygnus_pcie_phy_match_table, + }, + .probe = cygnus_pcie_phy_probe, +}; +module_platform_driver(cygnus_pcie_phy_driver); + +MODULE_AUTHOR("Ray Jui <rjui@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom Cygnus PCIe PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/broadcom/phy-bcm-kona-usb2.c b/drivers/phy/broadcom/phy-bcm-kona-usb2.c new file mode 100644 index 000000000000..7b67fe49e30b --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-kona-usb2.c @@ -0,0 +1,155 @@ +/* + * phy-bcm-kona-usb2.c - Broadcom Kona USB2 Phy Driver + * + * Copyright (C) 2013 Linaro Limited + * Matt Porter <mporter@linaro.org> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#define OTGCTL (0) +#define OTGCTL_OTGSTAT2 BIT(31) +#define OTGCTL_OTGSTAT1 BIT(30) +#define OTGCTL_PRST_N_SW BIT(11) +#define OTGCTL_HRESET_N BIT(10) +#define OTGCTL_UTMI_LINE_STATE1 BIT(9) +#define OTGCTL_UTMI_LINE_STATE0 BIT(8) + +#define P1CTL (8) +#define P1CTL_SOFT_RESET BIT(1) +#define P1CTL_NON_DRIVING BIT(0) + +struct bcm_kona_usb { + void __iomem *regs; +}; + +static void bcm_kona_usb_phy_power(struct bcm_kona_usb *phy, int on) +{ + u32 val; + + val = readl(phy->regs + OTGCTL); + if (on) { + /* Configure and power PHY */ + val &= ~(OTGCTL_OTGSTAT2 | OTGCTL_OTGSTAT1 | + OTGCTL_UTMI_LINE_STATE1 | OTGCTL_UTMI_LINE_STATE0); + val |= OTGCTL_PRST_N_SW | OTGCTL_HRESET_N; + } else { + val &= ~(OTGCTL_PRST_N_SW | OTGCTL_HRESET_N); + } + writel(val, phy->regs + OTGCTL); +} + +static int bcm_kona_usb_phy_init(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + u32 val; + + /* Soft reset PHY */ + val = readl(phy->regs + P1CTL); + val &= ~P1CTL_NON_DRIVING; + val |= P1CTL_SOFT_RESET; + writel(val, phy->regs + P1CTL); + writel(val & ~P1CTL_SOFT_RESET, phy->regs + P1CTL); + /* Reset needs to be asserted for 2ms */ + mdelay(2); + writel(val | P1CTL_SOFT_RESET, phy->regs + P1CTL); + + return 0; +} + +static int bcm_kona_usb_phy_power_on(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + + bcm_kona_usb_phy_power(phy, 1); + + return 0; +} + +static int bcm_kona_usb_phy_power_off(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + + bcm_kona_usb_phy_power(phy, 0); + + return 0; +} + +static const struct phy_ops ops = { + .init = bcm_kona_usb_phy_init, + .power_on = bcm_kona_usb_phy_power_on, + .power_off = bcm_kona_usb_phy_power_off, + .owner = THIS_MODULE, +}; + +static int bcm_kona_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm_kona_usb *phy; + struct resource *res; + struct phy *gphy; + struct phy_provider *phy_provider; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(phy->regs)) + return PTR_ERR(phy->regs); + + platform_set_drvdata(pdev, phy); + + gphy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(gphy)) + return PTR_ERR(gphy); + + /* The Kona PHY supports an 8-bit wide UTMI interface */ + phy_set_bus_width(gphy, 8); + + phy_set_drvdata(gphy, phy); + + phy_provider = devm_of_phy_provider_register(dev, + of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id bcm_kona_usb2_dt_ids[] = { + { .compatible = "brcm,kona-usb2-phy" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, bcm_kona_usb2_dt_ids); + +static struct platform_driver bcm_kona_usb2_driver = { + .probe = bcm_kona_usb2_probe, + .driver = { + .name = "bcm-kona-usb2", + .of_match_table = bcm_kona_usb2_dt_ids, + }, +}; + +module_platform_driver(bcm_kona_usb2_driver); + +MODULE_ALIAS("platform:bcm-kona-usb2"); +MODULE_AUTHOR("Matt Porter <mporter@linaro.org>"); +MODULE_DESCRIPTION("BCM Kona USB 2.0 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb2.c b/drivers/phy/broadcom/phy-bcm-ns-usb2.c new file mode 100644 index 000000000000..58dff80e9386 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-ns-usb2.c @@ -0,0 +1,137 @@ +/* + * Broadcom Northstar USB 2.0 PHY Driver + * + * Copyright (C) 2016 Rafał Miłecki <zajec5@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/bcma/bcma.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct bcm_ns_usb2 { + struct device *dev; + struct clk *ref_clk; + struct phy *phy; + void __iomem *dmu; +}; + +static int bcm_ns_usb2_phy_init(struct phy *phy) +{ + struct bcm_ns_usb2 *usb2 = phy_get_drvdata(phy); + struct device *dev = usb2->dev; + void __iomem *dmu = usb2->dmu; + u32 ref_clk_rate, usb2ctl, usb_pll_ndiv, usb_pll_pdiv; + int err = 0; + + err = clk_prepare_enable(usb2->ref_clk); + if (err < 0) { + dev_err(dev, "Failed to prepare ref clock: %d\n", err); + goto err_out; + } + + ref_clk_rate = clk_get_rate(usb2->ref_clk); + if (!ref_clk_rate) { + dev_err(dev, "Failed to get ref clock rate\n"); + err = -EINVAL; + goto err_clk_off; + } + + usb2ctl = readl(dmu + BCMA_DMU_CRU_USB2_CONTROL); + + if (usb2ctl & BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK) { + usb_pll_pdiv = usb2ctl; + usb_pll_pdiv &= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK; + usb_pll_pdiv >>= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_SHIFT; + } else { + usb_pll_pdiv = 1 << 3; + } + + /* Calculate ndiv based on a solid 1920 MHz that is for USB2 PHY */ + usb_pll_ndiv = (1920000000 * usb_pll_pdiv) / ref_clk_rate; + + /* Unlock DMU PLL settings with some magic value */ + writel(0x0000ea68, dmu + BCMA_DMU_CRU_CLKSET_KEY); + + /* Write USB 2.0 PLL control setting */ + usb2ctl &= ~BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_MASK; + usb2ctl |= usb_pll_ndiv << BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_SHIFT; + writel(usb2ctl, dmu + BCMA_DMU_CRU_USB2_CONTROL); + + /* Lock DMU PLL settings */ + writel(0x00000000, dmu + BCMA_DMU_CRU_CLKSET_KEY); + +err_clk_off: + clk_disable_unprepare(usb2->ref_clk); +err_out: + return err; +} + +static const struct phy_ops ops = { + .init = bcm_ns_usb2_phy_init, + .owner = THIS_MODULE, +}; + +static int bcm_ns_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm_ns_usb2 *usb2; + struct resource *res; + struct phy_provider *phy_provider; + + usb2 = devm_kzalloc(&pdev->dev, sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return -ENOMEM; + usb2->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmu"); + usb2->dmu = devm_ioremap_resource(dev, res); + if (IS_ERR(usb2->dmu)) { + dev_err(dev, "Failed to map DMU regs\n"); + return PTR_ERR(usb2->dmu); + } + + usb2->ref_clk = devm_clk_get(dev, "phy-ref-clk"); + if (IS_ERR(usb2->ref_clk)) { + dev_err(dev, "Clock not defined\n"); + return PTR_ERR(usb2->ref_clk); + } + + usb2->phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(usb2->phy)) + return PTR_ERR(usb2->phy); + + phy_set_drvdata(usb2->phy, usb2); + platform_set_drvdata(pdev, usb2); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id bcm_ns_usb2_id_table[] = { + { .compatible = "brcm,ns-usb2-phy", }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm_ns_usb2_id_table); + +static struct platform_driver bcm_ns_usb2_driver = { + .probe = bcm_ns_usb2_probe, + .driver = { + .name = "bcm_ns_usb2", + .of_match_table = bcm_ns_usb2_id_table, + }, +}; +module_platform_driver(bcm_ns_usb2_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c new file mode 100644 index 000000000000..22b5e7047fa6 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c @@ -0,0 +1,303 @@ +/* + * Broadcom Northstar USB 3.0 PHY Driver + * + * Copyright (C) 2016 Rafał Miłecki <rafal@milecki.pl> + * Copyright (C) 2016 Broadcom + * + * All magic values used for initialization (and related comments) were obtained + * from Broadcom's SDK: + * Copyright (c) Broadcom Corp, 2012 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/bcma/bcma.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/slab.h> + +#define BCM_NS_USB3_MII_MNG_TIMEOUT_US 1000 /* usecs */ + +#define BCM_NS_USB3_PHY_BASE_ADDR_REG 0x1f +#define BCM_NS_USB3_PHY_PLL30_BLOCK 0x8000 +#define BCM_NS_USB3_PHY_TX_PMD_BLOCK 0x8040 +#define BCM_NS_USB3_PHY_PIPE_BLOCK 0x8060 + +/* Registers of PLL30 block */ +#define BCM_NS_USB3_PLL_CONTROL 0x01 +#define BCM_NS_USB3_PLLA_CONTROL0 0x0a +#define BCM_NS_USB3_PLLA_CONTROL1 0x0b + +/* Registers of TX PMD block */ +#define BCM_NS_USB3_TX_PMD_CONTROL1 0x01 + +/* Registers of PIPE block */ +#define BCM_NS_USB3_LFPS_CMP 0x02 +#define BCM_NS_USB3_LFPS_DEGLITCH 0x03 + +enum bcm_ns_family { + BCM_NS_UNKNOWN, + BCM_NS_AX, + BCM_NS_BX, +}; + +struct bcm_ns_usb3 { + struct device *dev; + enum bcm_ns_family family; + void __iomem *dmp; + void __iomem *ccb_mii; + struct phy *phy; +}; + +static const struct of_device_id bcm_ns_usb3_id_table[] = { + { + .compatible = "brcm,ns-ax-usb3-phy", + .data = (int *)BCM_NS_AX, + }, + { + .compatible = "brcm,ns-bx-usb3-phy", + .data = (int *)BCM_NS_BX, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table); + +static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, + u32 mask, u32 value, unsigned long timeout) +{ + unsigned long deadline = jiffies + timeout; + u32 val; + + do { + val = readl(addr); + if ((val & mask) == value) + return 0; + cpu_relax(); + udelay(10); + } while (!time_after_eq(jiffies, deadline)); + + dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); + + return -EBUSY; +} + +static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) +{ + return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, + 0x0100, 0x0000, + usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); +} + +static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, + u16 value) +{ + u32 tmp = 0; + int err; + + err = bcm_ns_usb3_mii_mng_wait_idle(usb3); + if (err < 0) { + dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); + return err; + } + + /* TODO: Use a proper MDIO bus layer */ + tmp |= 0x58020000; /* Magic value for MDIO PHY write */ + tmp |= reg << 18; + tmp |= value; + writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); + + return 0; +} + +static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) +{ + int err; + + /* Enable MDIO. Setting MDCDIV as 26 */ + writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); + + /* Wait for MDIO? */ + udelay(2); + + /* USB3 PLL Block */ + err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PLL30_BLOCK); + if (err < 0) + return err; + + /* Assert Ana_Pllseq start */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000); + + /* Assert CML Divider ratio to 26 */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); + + /* Asserting PLL Reset */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000); + + /* Deaaserting PLL Reset */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + /* Deasserting USB3 system reset */ + writel(0, usb3->dmp + BCMA_RESET_CTL); + + /* PLL frequency monitor enable */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000); + + /* PIPE Block */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PIPE_BLOCK); + + /* CMPMAX & CMPMINTH setting */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d); + + /* DEGLITCH MIN & MAX setting */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302); + + /* TXPMD block */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_TX_PMD_BLOCK); + + /* Enabling SSC */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + return 0; +} + +static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) +{ + int err; + + /* Enable MDIO. Setting MDCDIV as 26 */ + writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); + + /* Wait for MDIO? */ + udelay(2); + + /* PLL30 block */ + err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PLL30_BLOCK); + if (err < 0) + return err; + + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); + + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0); + + bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c); + + /* Enable SSC */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_TX_PMD_BLOCK); + + bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3); + + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + /* Deasserting USB3 system reset */ + writel(0, usb3->dmp + BCMA_RESET_CTL); + + return 0; +} + +static int bcm_ns_usb3_phy_init(struct phy *phy) +{ + struct bcm_ns_usb3 *usb3 = phy_get_drvdata(phy); + int err; + + /* Perform USB3 system soft reset */ + writel(BCMA_RESET_CTL_RESET, usb3->dmp + BCMA_RESET_CTL); + + switch (usb3->family) { + case BCM_NS_AX: + err = bcm_ns_usb3_phy_init_ns_ax(usb3); + break; + case BCM_NS_BX: + err = bcm_ns_usb3_phy_init_ns_bx(usb3); + break; + default: + WARN_ON(1); + err = -ENOTSUPP; + } + + return err; +} + +static const struct phy_ops ops = { + .init = bcm_ns_usb3_phy_init, + .owner = THIS_MODULE, +}; + +static int bcm_ns_usb3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct bcm_ns_usb3 *usb3; + struct resource *res; + struct phy_provider *phy_provider; + + usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + usb3->dev = dev; + + of_id = of_match_device(bcm_ns_usb3_id_table, dev); + if (!of_id) + return -EINVAL; + usb3->family = (enum bcm_ns_family)of_id->data; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmp"); + usb3->dmp = devm_ioremap_resource(dev, res); + if (IS_ERR(usb3->dmp)) { + dev_err(dev, "Failed to map DMP regs\n"); + return PTR_ERR(usb3->dmp); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ccb-mii"); + usb3->ccb_mii = devm_ioremap_resource(dev, res); + if (IS_ERR(usb3->ccb_mii)) { + dev_err(dev, "Failed to map ChipCommon B MII regs\n"); + return PTR_ERR(usb3->ccb_mii); + } + + usb3->phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(usb3->phy)) { + dev_err(dev, "Failed to create PHY\n"); + return PTR_ERR(usb3->phy); + } + + phy_set_drvdata(usb3->phy, usb3); + platform_set_drvdata(pdev, usb3); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (!IS_ERR(phy_provider)) + dev_info(dev, "Registered Broadcom Northstar USB 3.0 PHY driver\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver bcm_ns_usb3_driver = { + .probe = bcm_ns_usb3_probe, + .driver = { + .name = "bcm_ns_usb3", + .of_match_table = bcm_ns_usb3_id_table, + }, +}; +module_platform_driver(bcm_ns_usb3_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/broadcom/phy-bcm-ns2-pcie.c b/drivers/phy/broadcom/phy-bcm-ns2-pcie.c new file mode 100644 index 000000000000..4c7d11d2b378 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-ns2-pcie.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 Broadcom + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_mdio.h> +#include <linux/mdio.h> +#include <linux/phy.h> +#include <linux/phy/phy.h> + +#define BLK_ADDR_REG_OFFSET 0x1f +#define PLL_AFE1_100MHZ_BLK 0x2100 +#define PLL_CLK_AMP_OFFSET 0x03 +#define PLL_CLK_AMP_2P05V 0x2b18 + +static int ns2_pci_phy_init(struct phy *p) +{ + struct mdio_device *mdiodev = phy_get_drvdata(p); + int rc; + + /* select the AFE 100MHz block page */ + rc = mdiobus_write(mdiodev->bus, mdiodev->addr, + BLK_ADDR_REG_OFFSET, PLL_AFE1_100MHZ_BLK); + if (rc) + goto err; + + /* set the 100 MHz reference clock amplitude to 2.05 v */ + rc = mdiobus_write(mdiodev->bus, mdiodev->addr, + PLL_CLK_AMP_OFFSET, PLL_CLK_AMP_2P05V); + if (rc) + goto err; + + return 0; + +err: + dev_err(&mdiodev->dev, "Error %d writing to phy\n", rc); + return rc; +} + +static const struct phy_ops ns2_pci_phy_ops = { + .init = ns2_pci_phy_init, + .owner = THIS_MODULE, +}; + +static int ns2_pci_phy_probe(struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + struct phy_provider *provider; + struct phy *phy; + + phy = devm_phy_create(dev, dev->of_node, &ns2_pci_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create Phy\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, mdiodev); + + provider = devm_of_phy_provider_register(&phy->dev, + of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "failed to register Phy provider\n"); + return PTR_ERR(provider); + } + + dev_info(dev, "%s PHY registered\n", dev_name(dev)); + + return 0; +} + +static const struct of_device_id ns2_pci_phy_of_match[] = { + { .compatible = "brcm,ns2-pcie-phy", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ns2_pci_phy_of_match); + +static struct mdio_driver ns2_pci_phy_driver = { + .mdiodrv = { + .driver = { + .name = "phy-bcm-ns2-pci", + .of_match_table = ns2_pci_phy_of_match, + }, + }, + .probe = ns2_pci_phy_probe, +}; +mdio_module_driver(ns2_pci_phy_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom Northstar2 PCI Phy driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:phy-bcm-ns2-pci"); diff --git a/drivers/phy/broadcom/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c new file mode 100644 index 000000000000..ccbc3d994998 --- /dev/null +++ b/drivers/phy/broadcom/phy-brcm-sata.c @@ -0,0 +1,493 @@ +/* + * Broadcom SATA3 AHCI Controller PHY Driver + * + * Copyright (C) 2016 Broadcom + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#define SATA_PCB_BANK_OFFSET 0x23c +#define SATA_PCB_REG_OFFSET(ofs) ((ofs) * 4) + +#define MAX_PORTS 2 + +/* Register offset between PHYs in PCB space */ +#define SATA_PCB_REG_28NM_SPACE_SIZE 0x1000 + +/* The older SATA PHY registers duplicated per port registers within the map, + * rather than having a separate map per port. + */ +#define SATA_PCB_REG_40NM_SPACE_SIZE 0x10 + +/* Register offset between PHYs in PHY control space */ +#define SATA_PHY_CTRL_REG_28NM_SPACE_SIZE 0x8 + +enum brcm_sata_phy_version { + BRCM_SATA_PHY_STB_28NM, + BRCM_SATA_PHY_STB_40NM, + BRCM_SATA_PHY_IPROC_NS2, + BRCM_SATA_PHY_IPROC_NSP, +}; + +struct brcm_sata_port { + int portnum; + struct phy *phy; + struct brcm_sata_phy *phy_priv; + bool ssc_en; +}; + +struct brcm_sata_phy { + struct device *dev; + void __iomem *phy_base; + void __iomem *ctrl_base; + enum brcm_sata_phy_version version; + + struct brcm_sata_port phys[MAX_PORTS]; +}; + +enum sata_phy_regs { + BLOCK0_REG_BANK = 0x000, + BLOCK0_XGXSSTATUS = 0x81, + BLOCK0_XGXSSTATUS_PLL_LOCK = BIT(12), + BLOCK0_SPARE = 0x8d, + BLOCK0_SPARE_OOB_CLK_SEL_MASK = 0x3, + BLOCK0_SPARE_OOB_CLK_SEL_REFBY2 = 0x1, + + PLL_REG_BANK_0 = 0x050, + PLL_REG_BANK_0_PLLCONTROL_0 = 0x81, + PLLCONTROL_0_FREQ_DET_RESTART = BIT(13), + PLLCONTROL_0_FREQ_MONITOR = BIT(12), + PLLCONTROL_0_SEQ_START = BIT(15), + PLL_CAP_CONTROL = 0x85, + PLL_ACTRL2 = 0x8b, + PLL_ACTRL2_SELDIV_MASK = 0x1f, + PLL_ACTRL2_SELDIV_SHIFT = 9, + + PLL1_REG_BANK = 0x060, + PLL1_ACTRL2 = 0x82, + PLL1_ACTRL3 = 0x83, + PLL1_ACTRL4 = 0x84, + + OOB_REG_BANK = 0x150, + OOB1_REG_BANK = 0x160, + OOB_CTRL1 = 0x80, + OOB_CTRL1_BURST_MAX_MASK = 0xf, + OOB_CTRL1_BURST_MAX_SHIFT = 12, + OOB_CTRL1_BURST_MIN_MASK = 0xf, + OOB_CTRL1_BURST_MIN_SHIFT = 8, + OOB_CTRL1_WAKE_IDLE_MAX_MASK = 0xf, + OOB_CTRL1_WAKE_IDLE_MAX_SHIFT = 4, + OOB_CTRL1_WAKE_IDLE_MIN_MASK = 0xf, + OOB_CTRL1_WAKE_IDLE_MIN_SHIFT = 0, + OOB_CTRL2 = 0x81, + OOB_CTRL2_SEL_ENA_SHIFT = 15, + OOB_CTRL2_SEL_ENA_RC_SHIFT = 14, + OOB_CTRL2_RESET_IDLE_MAX_MASK = 0x3f, + OOB_CTRL2_RESET_IDLE_MAX_SHIFT = 8, + OOB_CTRL2_BURST_CNT_MASK = 0x3, + OOB_CTRL2_BURST_CNT_SHIFT = 6, + OOB_CTRL2_RESET_IDLE_MIN_MASK = 0x3f, + OOB_CTRL2_RESET_IDLE_MIN_SHIFT = 0, + + TXPMD_REG_BANK = 0x1a0, + TXPMD_CONTROL1 = 0x81, + TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0), + TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1), + TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82, + TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83, + TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff, + TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84, + TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff, +}; + +enum sata_phy_ctrl_regs { + PHY_CTRL_1 = 0x0, + PHY_CTRL_1_RESET = BIT(0), +}; + +static inline void __iomem *brcm_sata_pcb_base(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + u32 size = 0; + + switch (priv->version) { + case BRCM_SATA_PHY_STB_28NM: + case BRCM_SATA_PHY_IPROC_NS2: + size = SATA_PCB_REG_28NM_SPACE_SIZE; + break; + case BRCM_SATA_PHY_STB_40NM: + size = SATA_PCB_REG_40NM_SPACE_SIZE; + break; + default: + dev_err(priv->dev, "invalid phy version\n"); + break; + } + + return priv->phy_base + (port->portnum * size); +} + +static inline void __iomem *brcm_sata_ctrl_base(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + u32 size = 0; + + switch (priv->version) { + case BRCM_SATA_PHY_IPROC_NS2: + size = SATA_PHY_CTRL_REG_28NM_SPACE_SIZE; + break; + default: + dev_err(priv->dev, "invalid phy version\n"); + break; + } + + return priv->ctrl_base + (port->portnum * size); +} + +static void brcm_sata_phy_wr(void __iomem *pcb_base, u32 bank, + u32 ofs, u32 msk, u32 value) +{ + u32 tmp; + + writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); + tmp = readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); + tmp = (tmp & msk) | value; + writel(tmp, pcb_base + SATA_PCB_REG_OFFSET(ofs)); +} + +static u32 brcm_sata_phy_rd(void __iomem *pcb_base, u32 bank, u32 ofs) +{ + writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); + return readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); +} + +/* These defaults were characterized by H/W group */ +#define STB_FMIN_VAL_DEFAULT 0x3df +#define STB_FMAX_VAL_DEFAULT 0x3df +#define STB_FMAX_VAL_SSC 0x83 + +static int brcm_stb_sata_init(struct brcm_sata_port *port) +{ + void __iomem *base = brcm_sata_pcb_base(port); + struct brcm_sata_phy *priv = port->phy_priv; + u32 tmp; + + /* override the TX spread spectrum setting */ + tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC; + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp); + + /* set fixed min freq */ + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2, + ~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK, + STB_FMIN_VAL_DEFAULT); + + /* set fixed max freq depending on SSC config */ + if (port->ssc_en) { + dev_info(priv->dev, "enabling SSC on port%d\n", port->portnum); + tmp = STB_FMAX_VAL_SSC; + } else { + tmp = STB_FMAX_VAL_DEFAULT; + } + + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3, + ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp); + + return 0; +} + +/* NS2 SATA PLL1 defaults were characterized by H/W group */ +#define NS2_PLL1_ACTRL2_MAGIC 0x1df8 +#define NS2_PLL1_ACTRL3_MAGIC 0x2b00 +#define NS2_PLL1_ACTRL4_MAGIC 0x8824 + +static int brcm_ns2_sata_init(struct brcm_sata_port *port) +{ + int try; + unsigned int val; + void __iomem *base = brcm_sata_pcb_base(port); + void __iomem *ctrl_base = brcm_sata_ctrl_base(port); + struct device *dev = port->phy_priv->dev; + + /* Configure OOB control */ + val = 0x0; + val |= (0xc << OOB_CTRL1_BURST_MAX_SHIFT); + val |= (0x4 << OOB_CTRL1_BURST_MIN_SHIFT); + val |= (0x9 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT); + val |= (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val); + val = 0x0; + val |= (0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT); + val |= (0x2 << OOB_CTRL2_BURST_CNT_SHIFT); + val |= (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val); + + /* Configure PHY PLL register bank 1 */ + val = NS2_PLL1_ACTRL2_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val); + val = NS2_PLL1_ACTRL3_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val); + val = NS2_PLL1_ACTRL4_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val); + + /* Configure PHY BLOCK0 register bank */ + /* Set oob_clk_sel to refclk/2 */ + brcm_sata_phy_wr(base, BLOCK0_REG_BANK, BLOCK0_SPARE, + ~BLOCK0_SPARE_OOB_CLK_SEL_MASK, + BLOCK0_SPARE_OOB_CLK_SEL_REFBY2); + + /* Strobe PHY reset using PHY control register */ + writel(PHY_CTRL_1_RESET, ctrl_base + PHY_CTRL_1); + mdelay(1); + writel(0x0, ctrl_base + PHY_CTRL_1); + mdelay(1); + + /* Wait for PHY PLL lock by polling pll_lock bit */ + try = 50; + while (try) { + val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, + BLOCK0_XGXSSTATUS); + if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) + break; + msleep(20); + try--; + } + if (!try) { + /* PLL did not lock; give up */ + dev_err(dev, "port%d PLL did not lock\n", port->portnum); + return -ETIMEDOUT; + } + + dev_dbg(dev, "port%d initialized\n", port->portnum); + + return 0; +} + +static int brcm_nsp_sata_init(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + struct device *dev = port->phy_priv->dev; + void __iomem *base = priv->phy_base; + unsigned int oob_bank; + unsigned int val, try; + + /* Configure OOB control */ + if (port->portnum == 0) + oob_bank = OOB_REG_BANK; + else if (port->portnum == 1) + oob_bank = OOB1_REG_BANK; + else + return -EINVAL; + + val = 0x0; + val |= (0x0f << OOB_CTRL1_BURST_MAX_SHIFT); + val |= (0x06 << OOB_CTRL1_BURST_MIN_SHIFT); + val |= (0x0f << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT); + val |= (0x06 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, oob_bank, OOB_CTRL1, 0x0, val); + + val = 0x0; + val |= (0x2e << OOB_CTRL2_RESET_IDLE_MAX_SHIFT); + val |= (0x02 << OOB_CTRL2_BURST_CNT_SHIFT); + val |= (0x16 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, oob_bank, OOB_CTRL2, 0x0, val); + + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_ACTRL2, + ~(PLL_ACTRL2_SELDIV_MASK << PLL_ACTRL2_SELDIV_SHIFT), + 0x0c << PLL_ACTRL2_SELDIV_SHIFT); + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_CAP_CONTROL, + 0xff0, 0x4f0); + + val = PLLCONTROL_0_FREQ_DET_RESTART | PLLCONTROL_0_FREQ_MONITOR; + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, + ~val, val); + val = PLLCONTROL_0_SEQ_START; + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, + ~val, 0); + mdelay(10); + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, + ~val, val); + + /* Wait for pll_seq_done bit */ + try = 50; + while (try--) { + val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, + BLOCK0_XGXSSTATUS); + if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) + break; + msleep(20); + } + if (!try) { + /* PLL did not lock; give up */ + dev_err(dev, "port%d PLL did not lock\n", port->portnum); + return -ETIMEDOUT; + } + + dev_dbg(dev, "port%d initialized\n", port->portnum); + + return 0; +} + +static int brcm_sata_phy_init(struct phy *phy) +{ + int rc; + struct brcm_sata_port *port = phy_get_drvdata(phy); + + switch (port->phy_priv->version) { + case BRCM_SATA_PHY_STB_28NM: + case BRCM_SATA_PHY_STB_40NM: + rc = brcm_stb_sata_init(port); + break; + case BRCM_SATA_PHY_IPROC_NS2: + rc = brcm_ns2_sata_init(port); + break; + case BRCM_SATA_PHY_IPROC_NSP: + rc = brcm_nsp_sata_init(port); + break; + default: + rc = -ENODEV; + } + + return rc; +} + +static const struct phy_ops phy_ops = { + .init = brcm_sata_phy_init, + .owner = THIS_MODULE, +}; + +static const struct of_device_id brcm_sata_phy_of_match[] = { + { .compatible = "brcm,bcm7445-sata-phy", + .data = (void *)BRCM_SATA_PHY_STB_28NM }, + { .compatible = "brcm,bcm7425-sata-phy", + .data = (void *)BRCM_SATA_PHY_STB_40NM }, + { .compatible = "brcm,iproc-ns2-sata-phy", + .data = (void *)BRCM_SATA_PHY_IPROC_NS2 }, + { .compatible = "brcm,iproc-nsp-sata-phy", + .data = (void *)BRCM_SATA_PHY_IPROC_NSP }, + {}, +}; +MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); + +static int brcm_sata_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dn = dev->of_node, *child; + const struct of_device_id *of_id; + struct brcm_sata_phy *priv; + struct resource *res; + struct phy_provider *provider; + int ret, count = 0; + + if (of_get_child_count(dn) == 0) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(dev, priv); + priv->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); + priv->phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->phy_base)) + return PTR_ERR(priv->phy_base); + + of_id = of_match_node(brcm_sata_phy_of_match, dn); + if (of_id) + priv->version = (enum brcm_sata_phy_version)of_id->data; + else + priv->version = BRCM_SATA_PHY_STB_28NM; + + if (priv->version == BRCM_SATA_PHY_IPROC_NS2) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phy-ctrl"); + priv->ctrl_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->ctrl_base)) + return PTR_ERR(priv->ctrl_base); + } + + for_each_available_child_of_node(dn, child) { + unsigned int id; + struct brcm_sata_port *port; + + if (of_property_read_u32(child, "reg", &id)) { + dev_err(dev, "missing reg property in node %s\n", + child->name); + ret = -EINVAL; + goto put_child; + } + + if (id >= MAX_PORTS) { + dev_err(dev, "invalid reg: %u\n", id); + ret = -EINVAL; + goto put_child; + } + if (priv->phys[id].phy) { + dev_err(dev, "already registered port %u\n", id); + ret = -EINVAL; + goto put_child; + } + + port = &priv->phys[id]; + port->portnum = id; + port->phy_priv = priv; + port->phy = devm_phy_create(dev, child, &phy_ops); + port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); + if (IS_ERR(port->phy)) { + dev_err(dev, "failed to create PHY\n"); + ret = PTR_ERR(port->phy); + goto put_child; + } + + phy_set_drvdata(port->phy, port); + count++; + } + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "could not register PHY provider\n"); + return PTR_ERR(provider); + } + + dev_info(dev, "registered %d port(s)\n", count); + + return 0; +put_child: + of_node_put(child); + return ret; +} + +static struct platform_driver brcm_sata_phy_driver = { + .probe = brcm_sata_phy_probe, + .driver = { + .of_match_table = brcm_sata_phy_of_match, + .name = "brcm-sata-phy", + } +}; +module_platform_driver(brcm_sata_phy_driver); + +MODULE_DESCRIPTION("Broadcom SATA PHY driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marc Carino"); +MODULE_AUTHOR("Brian Norris"); +MODULE_ALIAS("platform:phy-brcm-sata"); |