Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755145AbbEUNTW (ORCPT ); Thu, 21 May 2015 09:19:22 -0400 Received: from devils.ext.ti.com ([198.47.26.153]:43015 "EHLO devils.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754189AbbEUNTT (ORCPT ); Thu, 21 May 2015 09:19:19 -0400 Message-ID: <555DDB52.5030301@ti.com> Date: Thu, 21 May 2015 18:49:14 +0530 From: Kishon Vijay Abraham I User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.0 MIME-Version: 1.0 To: Ray Jui CC: , "JD (Jiandong) Zheng" , Arun Parameswaran , Subject: Re: [PATCH 4/5] phy: cygnus: pcie: Add Cygnus PCIe PHY support References: <1432085014-20758-1-git-send-email-rjui@broadcom.com> <1432085014-20758-5-git-send-email-rjui@broadcom.com> In-Reply-To: <1432085014-20758-5-git-send-email-rjui@broadcom.com> Content-Type: text/plain; charset="windows-1252"; format=flowed Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 10891 Lines: 392 On Wednesday 20 May 2015 06:53 AM, Ray Jui wrote: > This patch adds the PCIe PHY support for the Broadcom PCIe RC interface > > Signed-off-by: Ray Jui > Reviewed-by: Arun Parameswaran > Reviewed-by: JD (Jiandong) Zheng > Reviewed-by: Scott Branden > --- > drivers/phy/Kconfig | 15 ++ > drivers/phy/Makefile | 1 + > drivers/phy/phy-cygnus-pcie.c | 340 +++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 356 insertions(+) > create mode 100644 drivers/phy/phy-cygnus-pcie.c > > diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig > index 2664285..8250169 100644 > --- a/drivers/phy/Kconfig > +++ b/drivers/phy/Kconfig > @@ -35,6 +35,21 @@ config ARMADA375_USBCLUSTER_PHY > depends on OF > select GENERIC_PHY > > +config PHY_CYGNUS_PCIE > + bool "Broadcom Cygnus PCIe PHY driver" > + depends on ARCH_BCM_CYGNUS > + select GENERIC_PHY > + select PHY_IPROC_MDIO > + default ARCH_BCM_CYGNUS > + help > + Enable this to support the Broadcom Cygnus PCIe PHY. > + > + The host communicates with the PHY through the iProc MDC/MDIO > + interface. > + > + If unsure, say N. > + > + trailing blank line > config PHY_DM816X_USB > tristate "TI dm816x USB PHY driver" > depends on ARCH_OMAP2PLUS > diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile > index d989dd7b..6545950 100644 > --- a/drivers/phy/Makefile > +++ b/drivers/phy/Makefile > @@ -5,6 +5,7 @@ > obj-$(CONFIG_GENERIC_PHY) += phy-core.o > obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o > obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o > +obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-cygnus-pcie.o > obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.o > obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o > obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o > diff --git a/drivers/phy/phy-cygnus-pcie.c b/drivers/phy/phy-cygnus-pcie.c > new file mode 100644 > index 0000000..5817cd4 > --- /dev/null > +++ b/drivers/phy/phy-cygnus-pcie.c > @@ -0,0 +1,340 @@ > +/* > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > + > +#define MAX_PHY_ADDR 0x1f > + > +#define PCIE_MDCDIV_VAL 0x3e > + > +#define PCIE_BLK_ADDR_OFFSET 0x1f > +#define PCIE_BLK_ADDR_MASK 0xff00 > +#define PCIE_REG_ADDR_MASK 0x1f > + > +#define PCIE_SW_CTRL0_OFFSET 0x1000 > +#define PCIE_SW_PWRDOWN_SHIFT 0 > + > +#define PCIE_AFE1_100MHZ_C3_OFFSET 0x2103 > +#define PCIE_AFE1_100MHZ_C3_VAL 0x2b1c > + > +#define CRMU_PCIE_CFG_OFFSET 0x00 > +#define CRMU_PCIE1_PHY_IDDQ_SHIFT 10 > +#define CRMU_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 > + * @addr: PHY address used by MDC to communicate with the PHY > + * @phy: pointer to the kernel PHY device > + * > + */ > +struct cygnus_pcie_phy { > + struct cygnus_pcie_phy_core *core; > + enum cygnus_pcie_phy_id id; > + unsigned addr; > + struct phy *phy; > +}; > + > +/** > + * struct cygnus_pcie_phy_core - Cygnus PCIe PHY core control > + * @dev: pointer to device > + * @crmu: CRMU register base > + * @lock: mutex to protect access to individual PHYs > + * @phys: pointer to Cygnus PHY device > + * > + */ > +struct cygnus_pcie_phy_core { > + struct device *dev; > + void __iomem *crmu; > + struct mutex lock; > + struct cygnus_pcie_phy phys[MAX_NUM_PHYS]; > +}; > + > +static int cygnus_pcie_phy_reg_read(unsigned phy_addr, unsigned reg_addr, > + u16 *val) > +{ > + int ret; > + u16 addr = (u16)(reg_addr & PCIE_BLK_ADDR_MASK); > + > + ret = iproc_mdio_write(PCIE_MDCDIV_VAL, phy_addr, PCIE_BLK_ADDR_OFFSET, > + addr); > + if (ret) > + return ret; > + > + ret = iproc_mdio_read(PCIE_MDCDIV_VAL, phy_addr, > + reg_addr & PCIE_REG_ADDR_MASK, val); > + return ret; > +} > + > +static int cygnus_pcie_phy_reg_write(unsigned phy_addr, unsigned reg_addr, > + u16 val) > +{ > + int ret; > + u16 addr = (u16)(reg_addr & PCIE_BLK_ADDR_MASK); > + > + ret = iproc_mdio_write(PCIE_MDCDIV_VAL, phy_addr, PCIE_BLK_ADDR_OFFSET, > + addr); > + if (ret) > + return ret; > + > + ret = iproc_mdio_write(PCIE_MDCDIV_VAL, phy_addr, > + reg_addr & PCIE_REG_ADDR_MASK, val); > + return ret; > +} > + > +static void cygnus_pcie_afe_enable_disable(void __iomem *reg, > + enum cygnus_pcie_phy_id id, > + bool enable) > +{ > + unsigned shift; > + u32 val; > + > + if (id == CYGNUS_PHY_PCIE0) > + shift = CRMU_PCIE0_PHY_IDDQ_SHIFT; > + else > + shift = CRMU_PCIE1_PHY_IDDQ_SHIFT; > + > + if (enable) { > + val = readl(reg + CRMU_PCIE_CFG_OFFSET); > + val &= ~BIT(shift); > + writel(val, reg + CRMU_PCIE_CFG_OFFSET); > + } else { > + val = readl(reg + CRMU_PCIE_CFG_OFFSET); > + val |= BIT(shift); > + writel(val, reg + CRMU_PCIE_CFG_OFFSET); > + } > + > + /* wait for AFE to come up or shut down */ > + msleep(100); > +} > + > +static int cygnus_pcie_power_config(struct cygnus_pcie_phy *phy, bool on) > +{ > + struct cygnus_pcie_phy_core *core = phy->core; > + u16 val; > + int ret; > + > + if (phy->id != CYGNUS_PHY_PCIE0 && phy->id != CYGNUS_PHY_PCIE1) > + return -EINVAL; > + > + if (on) { > + /* enable AFE through the CRMU interface */ > + cygnus_pcie_afe_enable_disable(core->crmu, phy->id, true); > + > + /* to get the reference clock configured */ > + ret = cygnus_pcie_phy_reg_write(phy->addr, > + PCIE_AFE1_100MHZ_C3_OFFSET, > + PCIE_AFE1_100MHZ_C3_VAL); > + if (ret) > + goto err_disable_afe; > + cygnus_pcie_phy_reg_read(phy->addr, > + PCIE_AFE1_100MHZ_C3_OFFSET, &val); > + if (ret) > + goto err_disable_afe; > + > + msleep(20); > + > + /* now toggle AFE */ > + cygnus_pcie_afe_enable_disable(core->crmu, phy->id, false); > + cygnus_pcie_afe_enable_disable(core->crmu, phy->id, true); > + > + dev_info(core->dev, > + "pcie phy on, addr:0x%02x off:0x%04x val:0x%04x\n", > + phy->addr, PCIE_AFE1_100MHZ_C3_OFFSET, val); > + } else { > + /* disable AFE through the CRMU interface */ > + cygnus_pcie_afe_enable_disable(core->crmu, phy->id, false); > + dev_info(core->dev, "pcie phy off\n"); > + } > + > + return 0; > + > +err_disable_afe: > + cygnus_pcie_afe_enable_disable(core->crmu, phy->id, false); > + return ret; > +} > + > +static int cygnus_pcie_phy_init(struct phy *p) > +{ > + return 0; > +} > + > +static int cygnus_pcie_phy_exit(struct phy *p) > +{ > + return 0; > +} empty callbacks are not required. > + > +static int cygnus_pcie_phy_power_on(struct phy *p) > +{ > + struct cygnus_pcie_phy *phy = phy_get_drvdata(p); > + struct cygnus_pcie_phy_core *core = phy->core; > + int ret = 0; > + > + mutex_lock(&core->lock); > + > + switch (phy->id) { > + case CYGNUS_PHY_PCIE0: > + case CYGNUS_PHY_PCIE1: > + ret = cygnus_pcie_power_config(phy, true); > + if (ret) > + dev_err(core->dev, "unable to power on PCIe PHY\n"); > + break; > + > + default: > + dev_err(core->dev, "PHY id not supported\n"); > + mutex_unlock(&core->lock); > + return -EINVAL; > + } > + > + mutex_unlock(&core->lock); > + > + return ret; > +} > + > +static int cygnus_pcie_phy_power_off(struct phy *p) > +{ > + struct cygnus_pcie_phy *phy = phy_get_drvdata(p); > + struct cygnus_pcie_phy_core *core = phy->core; > + int ret = 0; > + > + mutex_lock(&core->lock); > + > + switch (phy->id) { > + case CYGNUS_PHY_PCIE0: > + case CYGNUS_PHY_PCIE1: > + ret = cygnus_pcie_power_config(phy, false); > + if (ret) > + dev_err(core->dev, "unable to power off PCIe PHY\n"); > + break; > + > + default: > + dev_err(core->dev, "PHY id not supported\n"); > + mutex_unlock(&core->lock); > + break; > + } > + > + mutex_unlock(&core->lock); > + > + return ret; > +} > + > +static struct phy_ops cygnus_pcie_phy_ops = { > + .init = cygnus_pcie_phy_init, > + .exit = cygnus_pcie_phy_exit, > + .power_on = cygnus_pcie_phy_power_on, > + .power_off = cygnus_pcie_phy_power_off, > +}; > + > +static struct phy *cygnus_pcie_phy_xlate(struct device *dev, > + struct of_phandle_args *args) > +{ > + struct cygnus_pcie_phy_core *core; > + int id, phy_addr; > + > + core = dev_get_drvdata(dev); > + if (!core) > + return ERR_PTR(-EINVAL); > + > + id = args->args[0]; > + phy_addr = args->args[1]; > + > + if (WARN_ON(id >= MAX_NUM_PHYS)) > + return ERR_PTR(-ENODEV); > + > + if (WARN_ON(phy_addr > MAX_PHY_ADDR)) > + return ERR_PTR(-ENODEV); > + > + core->phys[id].addr = phy_addr; > + > + return core->phys[id].phy; > +} > + > +static int cygnus_pcie_phy_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct cygnus_pcie_phy_core *core; > + struct phy_provider *provider; > + struct resource *res; > + int i = 0; > + > + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); > + if (!core) > + return -ENOMEM; > + > + core->dev = dev; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + core->crmu = devm_ioremap_resource(dev, res); > + if (IS_ERR(core->crmu)) > + return PTR_ERR(core->crmu); > + > + mutex_init(&core->lock); > + > + for (i = 0; i < MAX_NUM_PHYS; i++) { > + struct cygnus_pcie_phy *p = &core->phys[i]; > + > + p->phy = devm_phy_create(dev, NULL, &cygnus_pcie_phy_ops); > + if (IS_ERR(p->phy)) { > + dev_err(dev, "failed to create PHY\n"); > + return PTR_ERR(p->phy); > + } > + > + p->core = core; > + p->id = i; > + phy_set_drvdata(p->phy, p); > + } > + > + dev_set_drvdata(dev, core); > + > + provider = devm_of_phy_provider_register(dev, cygnus_pcie_phy_xlate); > + if (IS_ERR(provider)) { > + dev_err(dev, "failed to register PHY provider\n"); > + return PTR_ERR(provider); > + } PTR_ERR_OR_ZERO(phy_provider)? Thanks Kishon -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/