Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756693AbbEVBIY (ORCPT ); Thu, 21 May 2015 21:08:24 -0400 Received: from mail-gw1-out.broadcom.com ([216.31.210.62]:43906 "EHLO mail-gw1-out.broadcom.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756200AbbEVBHJ (ORCPT ); Thu, 21 May 2015 21:07:09 -0400 X-IronPort-AV: E=Sophos;i="5.13,472,1427785200"; d="scan'208";a="65687878" From: Ray Jui To: Kishon Vijay Abraham I , Arnd Bergmann CC: , "JD (Jiandong) Zheng" , Arun Parameswaran , , Ray Jui Subject: [PATCH v2 2/5] phy: iproc-mdio: Initial iProc MDC/MDIO support Date: Thu, 21 May 2015 18:07:06 -0700 Message-ID: <1432256829-17931-3-git-send-email-rjui@broadcom.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1432256829-17931-1-git-send-email-rjui@broadcom.com> References: <1432256829-17931-1-git-send-email-rjui@broadcom.com> MIME-Version: 1.0 Content-Type: text/plain Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 9865 Lines: 349 This adds the support for the iProc MDC/MDIO bus interface. Various iProc SoCs can use the MDC/MDIO bus interface to communicate with various Serdes/PHYs including Ethernet, PCIe, USB, etc. Signed-off-by: Ray Jui Reviewed-by: Arun Parameswaran Reviewed-by: JD (Jiandong) Zheng Reviewed-by: Scott Branden --- drivers/bus/Kconfig | 12 ++ drivers/bus/Makefile | 1 + drivers/bus/iproc-mdio-bus.c | 255 ++++++++++++++++++++++++++++++++++++++++ include/linux/iproc_mdio_bus.h | 22 ++++ 4 files changed, 290 insertions(+) create mode 100644 drivers/bus/iproc-mdio-bus.c create mode 100644 include/linux/iproc_mdio_bus.h diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index a1d4af6..99b38cb 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -55,6 +55,18 @@ config IMX_WEIM The WEIM(Wireless External Interface Module) works like a bus. You can attach many different devices on it, such as NOR, onenand. +config IPROC_MDIO + bool "Broadcom iProc MDC/MDIO driver" + depends on ARCH_BCM_IPROC + default ARCH_BCM_IPROC + help + Enable this to support the iProc generic MDC/MDIO bus interface found + in various iProc based SoCs. The generic MDC/MDIO bus interface can + be used to communicate with various types of Serdes/PHYs including + Ethernet, PCIe, USB, and etc. + + If unsure, say N. + config MIPS_CDMM bool "MIPS Common Device Memory Map (CDMM) Driver" depends on CPU_MIPSR2 diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 790e7b9..6dff76a 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_ARM_CCN) += arm-ccn.o obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o obj-$(CONFIG_IMX_WEIM) += imx-weim.o +obj-$(CONFIG_IPROC_MDIO) += iproc-mdio-bus.o obj-$(CONFIG_MIPS_CDMM) += mips_cdmm.o obj-$(CONFIG_MVEBU_MBUS) += mvebu-mbus.o diff --git a/drivers/bus/iproc-mdio-bus.c b/drivers/bus/iproc-mdio-bus.c new file mode 100644 index 0000000..ae91722 --- /dev/null +++ b/drivers/bus/iproc-mdio-bus.c @@ -0,0 +1,255 @@ +/* + * 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 MDIO_TIMEOUT_MSEC 10 + +#define MDC_CTRL_OFFSET 0x000 + +#define MDC_CTRL_DIV_SHIFT 0 +#define MDC_CTRL_DIV_WIDTH 7 +#define MDC_MAX_DIV (BIT(MDC_CTRL_DIV_WIDTH) - 1) +#define MDC_CTRL_PRE_SHIFT 7 +#define MDC_CTRL_BUSY_SHIFT 8 +#define MDC_CTRL_EXT_SHIFT 9 +#define MDC_CTRL_BTP_SHIFT 10 + +#define MDC_DATA_OFFSET 0x004 + +#define MDC_DATA_SHIFT 0 +#define MDC_DATA_MASK 0xffff + +#define MDC_DATA_TA_SHIFT 16 +#define MDC_DATA_TA_VAL 2 + +#define MDC_DATA_RA_SHIFT 18 +#define MDC_DATA_RA_WIDTH 5 +#define MDC_MAX_RA (BIT(MDC_DATA_RA_WIDTH) - 1) + +#define MDC_DATA_PA_SHIFT 23 +#define MDC_DATA_PA_WIDTH 5 +#define MDC_MAX_PA (BIT(MDC_DATA_PA_WIDTH) - 1) + +#define MDC_DATA_OP_SHIFT 28 +#define MDC_DATA_OP_WRITE 1 +#define MDC_DATA_OP_READ 2 + +#define MDC_DATA_SB_SHIFT 30 + +/** + * struct iproc_mdc - iProc MDC/MDIO bus interface + * @dev: pointer to device + * @base: MDC controller register base pointer + * @lock: mutex to protect access to the MDC device + * @is_ready: flag to indicate the driver has been initialized and is ready for + * use + */ +struct iproc_mdc { + struct device *dev; + void __iomem *base; + struct mutex lock; + bool is_ready; +}; + +static struct iproc_mdc iproc_mdc; + +static inline int iproc_mdio_check_params(unsigned clk_div, unsigned pa, + unsigned ra) +{ + if (clk_div > MDC_MAX_DIV || pa > MDC_MAX_PA || ra > MDC_MAX_RA) + return -EINVAL; + + return 0; +} + +static inline int iproc_mdio_wait_for_idle(void __iomem *base) +{ + u32 val; + unsigned long timeout = jiffies + msecs_to_jiffies(MDIO_TIMEOUT_MSEC); + + while (!time_after(jiffies, timeout)) { + val = readl(base + MDC_CTRL_OFFSET); + if ((val & BIT(MDC_CTRL_BUSY_SHIFT)) == 0) + return 0; + + cpu_relax(); + } + + return -ETIMEDOUT; +} + +static inline void iproc_mdio_config_clk(void __iomem *base, unsigned clk_div) +{ + u32 val; + + val = (clk_div << MDC_CTRL_DIV_SHIFT) | BIT(MDC_CTRL_PRE_SHIFT); + writel(val, base + MDC_CTRL_OFFSET); +} + +static int iproc_mdio_read_write(void __iomem *base, bool dir_is_write, + unsigned clk_div, unsigned pa, unsigned ra, + u16 *data) +{ + int ret; + u32 val; + + ret = iproc_mdio_check_params(clk_div, pa, ra); + if (ret) + return ret; + + ret = iproc_mdio_wait_for_idle(base); + if (ret) + return ret; + + iproc_mdio_config_clk(base, clk_div); + + ret = iproc_mdio_wait_for_idle(base); + if (ret) + return ret; + + val = (MDC_DATA_TA_VAL << MDC_DATA_TA_SHIFT) | + (ra << MDC_DATA_RA_SHIFT) | + (pa << MDC_DATA_PA_SHIFT) | + BIT(MDC_DATA_SB_SHIFT); + + if (dir_is_write) { + val |= (MDC_DATA_OP_WRITE << MDC_DATA_OP_SHIFT); + val |= ((u32)(*data) & MDC_DATA_MASK); + } else { + val |= (MDC_DATA_OP_READ << MDC_DATA_OP_SHIFT); + } + + writel(val, base + MDC_DATA_OFFSET); + + ret = iproc_mdio_wait_for_idle(base); + if (ret) + return ret; + + if (!dir_is_write) { + val = readl(base + MDC_DATA_OFFSET) & MDC_DATA_MASK; + *data = (u16)val; + } + + return 0; +} + + +/** + * iproc_mdio_read() - read from a PHY register through the MDC interface + * + * @clk_div: MDC clock divisor + * @phy_addr: MDC PHY address + * @reg_addr: PHY register address + * @data: pointer to the memory where data will be stored + */ +int iproc_mdio_read(unsigned clk_div, unsigned phy_addr, unsigned reg_addr, + u16 *data) +{ + int ret; + struct iproc_mdc *mdc = &iproc_mdc; + + if (!mdc->is_ready) + return -ENODEV; + + mutex_lock(&mdc->lock); + ret = iproc_mdio_read_write(mdc->base, false, clk_div, phy_addr, + reg_addr, data); + if (ret) + dev_err(mdc->dev, "mdio read failed\n"); + mutex_unlock(&mdc->lock); + + return ret; +} +EXPORT_SYMBOL(iproc_mdio_read); + +/** + * iproc_mdio_write() - write to a PHY register through the MDC interface + * + * @clk_div: MDC clock divisor + * @phy_addr: MDC PHY address + * @reg_addr: PHY register address + * @data: data value to be written to the PHY register + */ +int iproc_mdio_write(unsigned clk_div, unsigned phy_addr, unsigned reg_addr, + u16 data) +{ + int ret; + struct iproc_mdc *mdc = &iproc_mdc; + + if (!mdc->is_ready) + return -ENODEV; + + mutex_lock(&mdc->lock); + ret = iproc_mdio_read_write(mdc->base, true, clk_div, phy_addr, + reg_addr, &data); + if (ret) + dev_err(mdc->dev, "mdio write failed\n"); + mutex_unlock(&mdc->lock); + + return ret; +} +EXPORT_SYMBOL(iproc_mdio_write); + +static int iproc_mdio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct iproc_mdc *mdc = &iproc_mdc; + struct resource *res; + + mdc->dev = dev; + mutex_init(&mdc->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mdc->base = devm_ioremap_resource(dev, res); + if (IS_ERR(mdc->base)) + return PTR_ERR(mdc->base); + + mdc->is_ready = true; + + if (np) + of_platform_populate(np, NULL, NULL, dev); + + return 0; +} + +static const struct of_device_id iproc_mdio_match_table[] = { + { .compatible = "brcm,iproc-mdio" }, + { } +}; + +static struct platform_driver iproc_mdio_driver = { + .driver = { + .name = "iproc-mdio", + .of_match_table = iproc_mdio_match_table, + .suppress_bind_attrs = true, + }, + .probe = iproc_mdio_probe, +}; + +static int __init iproc_mdio_init(void) +{ + return platform_driver_register(&iproc_mdio_driver); +} +arch_initcall_sync(iproc_mdio_init); + +MODULE_AUTHOR("Ray Jui "); +MODULE_DESCRIPTION("Broadcom iPROC MDC/MDIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/iproc_mdio_bus.h b/include/linux/iproc_mdio_bus.h new file mode 100644 index 0000000..4d1235a --- /dev/null +++ b/include/linux/iproc_mdio_bus.h @@ -0,0 +1,22 @@ +/* + * 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. + */ + +#ifndef __IPROC_MDIO_BUS_H +#define __IPROC_MDIO_BUS_H + +int iproc_mdio_read(unsigned clk_div, unsigned phy_addr, unsigned reg_addr, + u16 *data); +int iproc_mdio_write(unsigned clk_div, unsigned phy_addr, unsigned reg_addr, + u16 data); + +#endif /* __IPROC_MDIO_BUS_H */ -- 1.7.9.5 -- 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/