diff options
Diffstat (limited to 'drivers/phy')
-rw-r--r-- | drivers/phy/broadcom/Kconfig | 10 | ||||
-rw-r--r-- | drivers/phy/broadcom/Makefile | 2 | ||||
-rw-r--r-- | drivers/phy/broadcom/phy-bcm-sr-pcie.c | 305 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-berlin-sata.c | 5 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-berlin-usb.c | 5 | ||||
-rw-r--r-- | drivers/phy/marvell/phy-mvebu-cp110-comphy.c | 5 | ||||
-rw-r--r-- | drivers/phy/mediatek/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/mediatek/phy-mtk-tphy.c | 85 | ||||
-rw-r--r-- | drivers/phy/qualcomm/phy-qcom-usb-hs.c | 1 | ||||
-rw-r--r-- | drivers/phy/renesas/Kconfig | 7 | ||||
-rw-r--r-- | drivers/phy/renesas/Makefile | 1 | ||||
-rw-r--r-- | drivers/phy/renesas/phy-rcar-gen3-pcie.c | 151 |
12 files changed, 557 insertions, 21 deletions
diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig index 97d27b0d5cc7..8786a9674471 100644 --- a/drivers/phy/broadcom/Kconfig +++ b/drivers/phy/broadcom/Kconfig @@ -80,3 +80,13 @@ config PHY_BRCM_USB This driver is required by the USB XHCI, EHCI and OHCI drivers. If unsure, say N. + +config PHY_BCM_SR_PCIE + tristate "Broadcom Stingray PCIe PHY driver" + depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST) + select GENERIC_PHY + select MFD_SYSCON + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom Stingray PCIe PHY + If unsure, say N. diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile index 13e000c1a43a..0f60184e6662 100644 --- a/drivers/phy/broadcom/Makefile +++ b/drivers/phy/broadcom/Makefile @@ -9,3 +9,5 @@ obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o obj-$(CONFIG_PHY_BRCM_USB) += phy-brcm-usb-dvr.o phy-brcm-usb-dvr-objs := phy-brcm-usb.o phy-brcm-usb-init.o + +obj-$(CONFIG_PHY_BCM_SR_PCIE) += phy-bcm-sr-pcie.o diff --git a/drivers/phy/broadcom/phy-bcm-sr-pcie.c b/drivers/phy/broadcom/phy-bcm-sr-pcie.c new file mode 100644 index 000000000000..c10e95f86de5 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-sr-pcie.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2018 Broadcom + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +/* we have up to 8 PAXB based RC. The 9th one is always PAXC */ +#define SR_NR_PCIE_PHYS 9 +#define SR_PAXC_PHY_IDX (SR_NR_PCIE_PHYS - 1) + +#define PCIE_PIPEMUX_CFG_OFFSET 0x10c +#define PCIE_PIPEMUX_SELECT_STRAP 0xf + +#define CDRU_STRAP_DATA_LSW_OFFSET 0x5c +#define PCIE_PIPEMUX_SHIFT 19 +#define PCIE_PIPEMUX_MASK 0xf + +#define MHB_MEM_PW_PAXC_OFFSET 0x1c0 +#define MHB_PWR_ARR_POWERON 0x8 +#define MHB_PWR_ARR_POWEROK 0x4 +#define MHB_PWR_POWERON 0x2 +#define MHB_PWR_POWEROK 0x1 +#define MHB_PWR_STATUS_MASK (MHB_PWR_ARR_POWERON | \ + MHB_PWR_ARR_POWEROK | \ + MHB_PWR_POWERON | \ + MHB_PWR_POWEROK) + +struct sr_pcie_phy_core; + +/** + * struct sr_pcie_phy - Stingray PCIe PHY + * + * @core: pointer to the Stingray PCIe PHY core control + * @index: PHY index + * @phy: pointer to the kernel PHY device + */ +struct sr_pcie_phy { + struct sr_pcie_phy_core *core; + unsigned int index; + struct phy *phy; +}; + +/** + * struct sr_pcie_phy_core - Stingray PCIe PHY core control + * + * @dev: pointer to device + * @base: base register of PCIe SS + * @cdru: regmap to the CDRU device + * @mhb: regmap to the MHB device + * @pipemux: pipemuex strap + * @phys: array of PCIe PHYs + */ +struct sr_pcie_phy_core { + struct device *dev; + void __iomem *base; + struct regmap *cdru; + struct regmap *mhb; + u32 pipemux; + struct sr_pcie_phy phys[SR_NR_PCIE_PHYS]; +}; + +/* + * PCIe PIPEMUX lookup table + * + * Each array index represents a PIPEMUX strap setting + * The array element represents a bitmap where a set bit means the PCIe + * core and associated serdes has been enabled as RC and is available for use + */ +static const u8 pipemux_table[] = { + /* PIPEMUX = 0, EP 1x16 */ + 0x00, + /* PIPEMUX = 1, EP 2x8 */ + 0x00, + /* PIPEMUX = 2, EP 4x4 */ + 0x00, + /* PIPEMUX = 3, RC 2x8, cores 0, 7 */ + 0x81, + /* PIPEMUX = 4, RC 4x4, cores 0, 1, 6, 7 */ + 0xc3, + /* PIPEMUX = 5, RC 8x2, all 8 cores */ + 0xff, + /* PIPEMUX = 6, RC 3x4 + 2x2, cores 0, 2, 3, 6, 7 */ + 0xcd, + /* PIPEMUX = 7, RC 1x4 + 6x2, cores 0, 2, 3, 4, 5, 6, 7 */ + 0xfd, + /* PIPEMUX = 8, EP 1x8 + RC 4x2, cores 4, 5, 6, 7 */ + 0xf0, + /* PIPEMUX = 9, EP 1x8 + RC 2x4, cores 6, 7 */ + 0xc0, + /* PIPEMUX = 10, EP 2x4 + RC 2x4, cores 1, 6 */ + 0x42, + /* PIPEMUX = 11, EP 2x4 + RC 4x2, cores 2, 3, 4, 5 */ + 0x3c, + /* PIPEMUX = 12, EP 1x4 + RC 6x2, cores 2, 3, 4, 5, 6, 7 */ + 0xfc, + /* PIPEMUX = 13, RC 2x4 + RC 1x4 + 2x2, cores 2, 3, 6 */ + 0x4c, +}; + +/* + * Return true if the strap setting is valid + */ +static bool pipemux_strap_is_valid(u32 pipemux) +{ + return !!(pipemux < ARRAY_SIZE(pipemux_table)); +} + +/* + * Read the PCIe PIPEMUX from strap + */ +static u32 pipemux_strap_read(struct sr_pcie_phy_core *core) +{ + u32 pipemux; + + /* + * Read PIPEMUX configuration register to determine the pipemux setting + * + * In the case when the value indicates using HW strap, fall back to + * use HW strap + */ + pipemux = readl(core->base + PCIE_PIPEMUX_CFG_OFFSET); + pipemux &= PCIE_PIPEMUX_MASK; + if (pipemux == PCIE_PIPEMUX_SELECT_STRAP) { + regmap_read(core->cdru, CDRU_STRAP_DATA_LSW_OFFSET, &pipemux); + pipemux >>= PCIE_PIPEMUX_SHIFT; + pipemux &= PCIE_PIPEMUX_MASK; + } + + return pipemux; +} + +/* + * Given a PIPEMUX strap and PCIe core index, this function returns true if the + * PCIe core needs to be enabled + */ +static bool pcie_core_is_for_rc(struct sr_pcie_phy *phy) +{ + struct sr_pcie_phy_core *core = phy->core; + unsigned int core_idx = phy->index; + + return !!((pipemux_table[core->pipemux] >> core_idx) & 0x1); +} + +static int sr_pcie_phy_init(struct phy *p) +{ + struct sr_pcie_phy *phy = phy_get_drvdata(p); + + /* + * Check whether this PHY is for root complex or not. If yes, return + * zero so the host driver can proceed to enumeration. If not, return + * an error and that will force the host driver to bail out + */ + if (pcie_core_is_for_rc(phy)) + return 0; + + return -ENODEV; +} + +static int sr_paxc_phy_init(struct phy *p) +{ + struct sr_pcie_phy *phy = phy_get_drvdata(p); + struct sr_pcie_phy_core *core = phy->core; + unsigned int core_idx = phy->index; + u32 val; + + if (core_idx != SR_PAXC_PHY_IDX) + return -EINVAL; + + regmap_read(core->mhb, MHB_MEM_PW_PAXC_OFFSET, &val); + if ((val & MHB_PWR_STATUS_MASK) != MHB_PWR_STATUS_MASK) { + dev_err(core->dev, "PAXC is not powered up\n"); + return -ENODEV; + } + + return 0; +} + +static const struct phy_ops sr_pcie_phy_ops = { + .init = sr_pcie_phy_init, + .owner = THIS_MODULE, +}; + +static const struct phy_ops sr_paxc_phy_ops = { + .init = sr_paxc_phy_init, + .owner = THIS_MODULE, +}; + +static struct phy *sr_pcie_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct sr_pcie_phy_core *core; + int phy_idx; + + core = dev_get_drvdata(dev); + if (!core) + return ERR_PTR(-EINVAL); + + phy_idx = args->args[0]; + + if (WARN_ON(phy_idx >= SR_NR_PCIE_PHYS)) + return ERR_PTR(-ENODEV); + + return core->phys[phy_idx].phy; +} + +static int sr_pcie_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct sr_pcie_phy_core *core; + struct resource *res; + struct phy_provider *provider; + unsigned int phy_idx = 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->base = devm_ioremap_resource(core->dev, res); + if (IS_ERR(core->base)) + return PTR_ERR(core->base); + + core->cdru = syscon_regmap_lookup_by_phandle(node, "brcm,sr-cdru"); + if (IS_ERR(core->cdru)) { + dev_err(core->dev, "unable to find CDRU device\n"); + return PTR_ERR(core->cdru); + } + + core->mhb = syscon_regmap_lookup_by_phandle(node, "brcm,sr-mhb"); + if (IS_ERR(core->mhb)) { + dev_err(core->dev, "unable to find MHB device\n"); + return PTR_ERR(core->mhb); + } + + /* read the PCIe PIPEMUX strap setting */ + core->pipemux = pipemux_strap_read(core); + if (!pipemux_strap_is_valid(core->pipemux)) { + dev_err(core->dev, "invalid PCIe PIPEMUX strap %u\n", + core->pipemux); + return -EIO; + } + + for (phy_idx = 0; phy_idx < SR_NR_PCIE_PHYS; phy_idx++) { + struct sr_pcie_phy *p = &core->phys[phy_idx]; + const struct phy_ops *ops; + + if (phy_idx == SR_PAXC_PHY_IDX) + ops = &sr_paxc_phy_ops; + else + ops = &sr_pcie_phy_ops; + + p->phy = devm_phy_create(dev, NULL, ops); + if (IS_ERR(p->phy)) { + dev_err(dev, "failed to create PCIe PHY\n"); + return PTR_ERR(p->phy); + } + + p->core = core; + p->index = phy_idx; + phy_set_drvdata(p->phy, p); + } + + dev_set_drvdata(dev, core); + + provider = devm_of_phy_provider_register(dev, sr_pcie_phy_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "failed to register PHY provider\n"); + return PTR_ERR(provider); + } + + dev_info(dev, "Stingray PCIe PHY driver initialized\n"); + + return 0; +} + +static const struct of_device_id sr_pcie_phy_match_table[] = { + { .compatible = "brcm,sr-pcie-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, sr_pcie_phy_match_table); + +static struct platform_driver sr_pcie_phy_driver = { + .driver = { + .name = "sr-pcie-phy", + .of_match_table = sr_pcie_phy_match_table, + }, + .probe = sr_pcie_phy_probe, +}; +module_platform_driver(sr_pcie_phy_driver); + +MODULE_AUTHOR("Ray Jui <ray.jui@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom Stingray PCIe PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-berlin-sata.c b/drivers/phy/marvell/phy-berlin-sata.c index 2c7a57f2d595..c1bb6725e48f 100644 --- a/drivers/phy/marvell/phy-berlin-sata.c +++ b/drivers/phy/marvell/phy-berlin-sata.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Marvell Berlin SATA PHY driver * * Copyright (C) 2014 Marvell Technology Group Ltd. * * Antoine Ténart <antoine.tenart@free-electrons.com> - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include <linux/clk.h> diff --git a/drivers/phy/marvell/phy-berlin-usb.c b/drivers/phy/marvell/phy-berlin-usb.c index 8f2b5cae360f..a43df63007c5 100644 --- a/drivers/phy/marvell/phy-berlin-usb.c +++ b/drivers/phy/marvell/phy-berlin-usb.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2014 Marvell Technology Group Ltd. * * Antoine Tenart <antoine.tenart@free-electrons.com> * Jisheng Zhang <jszhang@marvell.com> - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include <linux/io.h> diff --git a/drivers/phy/marvell/phy-mvebu-cp110-comphy.c b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c index 4ef429250d7b..86a5f7b9448b 100644 --- a/drivers/phy/marvell/phy-mvebu-cp110-comphy.c +++ b/drivers/phy/marvell/phy-mvebu-cp110-comphy.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2017 Marvell * * Antoine Tenart <antoine.tenart@free-electrons.com> - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. */ #include <linux/io.h> diff --git a/drivers/phy/mediatek/Makefile b/drivers/phy/mediatek/Makefile index e5074b607d3d..ee49edc97ee9 100644 --- a/drivers/phy/mediatek/Makefile +++ b/drivers/phy/mediatek/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 # # Makefile for the phy drivers. # diff --git a/drivers/phy/mediatek/phy-mtk-tphy.c b/drivers/phy/mediatek/phy-mtk-tphy.c index 38c281b5abbb..3eb8e1bd7b78 100644 --- a/drivers/phy/mediatek/phy-mtk-tphy.c +++ b/drivers/phy/mediatek/phy-mtk-tphy.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2015 MediaTek Inc. * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> * - * 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 <dt-bindings/phy/phy.h> @@ -50,6 +42,12 @@ #define PA0_RG_U2PLL_FORCE_ON BIT(15) #define PA0_RG_USB20_INTR_EN BIT(5) +#define U3P_USBPHYACR1 0x004 +#define PA1_RG_VRT_SEL GENMASK(14, 12) +#define PA1_RG_VRT_SEL_VAL(x) ((0x7 & (x)) << 12) +#define PA1_RG_TERM_SEL GENMASK(10, 8) +#define PA1_RG_TERM_SEL_VAL(x) ((0x7 & (x)) << 8) + #define U3P_USBPHYACR2 0x008 #define PA2_RG_SIF_U2PLL_FORCE_EN BIT(18) @@ -103,6 +101,9 @@ #define P2C_RG_AVALID BIT(2) #define P2C_RG_IDDIG BIT(1) +#define U3P_U2PHYBC12C 0x080 +#define P2C_RG_CHGDT_EN BIT(0) + #define U3P_U3_CHIP_GPIO_CTLD 0x0c #define P3C_REG_IP_SW_RST BIT(31) #define P3C_MCU_BUS_CK_GATE_EN BIT(30) @@ -296,6 +297,10 @@ struct mtk_phy_instance { struct clk *ref_clk; /* reference clock of anolog phy */ u32 index; u8 type; + int eye_src; + int eye_vrt; + int eye_term; + bool bc12_en; }; struct mtk_tphy { @@ -320,6 +325,10 @@ static void hs_slew_rate_calibrate(struct mtk_tphy *tphy, int fm_out; u32 tmp; + /* use force value */ + if (instance->eye_src) + return; + /* enable USB ring oscillator */ tmp = readl(com + U3P_USBPHYACR5); tmp |= PA5_RG_U2_HSTX_SRCAL_EN; @@ -826,6 +835,61 @@ static void phy_v2_banks_init(struct mtk_tphy *tphy, } } +static void phy_parse_property(struct mtk_tphy *tphy, + struct mtk_phy_instance *instance) +{ + struct device *dev = &instance->phy->dev; + + if (instance->type != PHY_TYPE_USB2) + return; + + instance->bc12_en = device_property_read_bool(dev, "mediatek,bc12"); + device_property_read_u32(dev, "mediatek,eye-src", + &instance->eye_src); + device_property_read_u32(dev, "mediatek,eye-vrt", + &instance->eye_vrt); + device_property_read_u32(dev, "mediatek,eye-term", + &instance->eye_term); + dev_dbg(dev, "bc12:%d, src:%d, vrt:%d, term:%d\n", + instance->bc12_en, instance->eye_src, + instance->eye_vrt, instance->eye_term); +} + +static void u2_phy_props_set(struct mtk_tphy *tphy, + struct mtk_phy_instance *instance) +{ + struct u2phy_banks *u2_banks = &instance->u2_banks; + void __iomem *com = u2_banks->com; + u32 tmp; + + if (instance->bc12_en) { + tmp = readl(com + U3P_U2PHYBC12C); + tmp |= P2C_RG_CHGDT_EN; /* BC1.2 path Enable */ + writel(tmp, com + U3P_U2PHYBC12C); + } + + if (instance->eye_src) { + tmp = readl(com + U3P_USBPHYACR5); + tmp &= ~PA5_RG_U2_HSTX_SRCTRL; + tmp |= PA5_RG_U2_HSTX_SRCTRL_VAL(instance->eye_src); + writel(tmp, com + U3P_USBPHYACR5); + } + + if (instance->eye_vrt) { + tmp = readl(com + U3P_USBPHYACR1); + tmp &= ~PA1_RG_VRT_SEL; + tmp |= PA1_RG_VRT_SEL_VAL(instance->eye_vrt); + writel(tmp, com + U3P_USBPHYACR1); + } + + if (instance->eye_term) { + tmp = readl(com + U3P_USBPHYACR1); + tmp &= ~PA1_RG_TERM_SEL; + tmp |= PA1_RG_TERM_SEL_VAL(instance->eye_term); + writel(tmp, com + U3P_USBPHYACR1); + } +} + static int mtk_phy_init(struct phy *phy) { struct mtk_phy_instance *instance = phy_get_drvdata(phy); @@ -847,6 +911,7 @@ static int mtk_phy_init(struct phy *phy) switch (instance->type) { case PHY_TYPE_USB2: u2_phy_instance_init(tphy, instance); + u2_phy_props_set(tphy, instance); break; case PHY_TYPE_USB3: u3_phy_instance_init(tphy, instance); @@ -959,6 +1024,8 @@ static struct phy *mtk_phy_xlate(struct device *dev, return ERR_PTR(-EINVAL); } + phy_parse_property(tphy, instance); + return instance->phy; } diff --git a/drivers/phy/qualcomm/phy-qcom-usb-hs.c b/drivers/phy/qualcomm/phy-qcom-usb-hs.c index 2d0c70b5589f..abbbe75070da 100644 --- a/drivers/phy/qualcomm/phy-qcom-usb-hs.c +++ b/drivers/phy/qualcomm/phy-qcom-usb-hs.c @@ -55,6 +55,7 @@ static int qcom_usb_hs_phy_set_mode(struct phy *phy, enum phy_mode mode) case PHY_MODE_USB_OTG: case PHY_MODE_USB_HOST: val |= ULPI_INT_IDGRD; + /* fall through */ case PHY_MODE_USB_DEVICE: val |= ULPI_INT_SESS_VALID; default: diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig index c845facacb06..4bd390c79d21 100644 --- a/drivers/phy/renesas/Kconfig +++ b/drivers/phy/renesas/Kconfig @@ -8,6 +8,13 @@ config PHY_RCAR_GEN2 help Support for USB PHY found on Renesas R-Car generation 2 SoCs. +config PHY_RCAR_GEN3_PCIE + tristate "Renesas R-Car generation 3 PCIe PHY driver" + depends on ARCH_RENESAS + select GENERIC_PHY + help + Support for the PCIe PHY found on Renesas R-Car generation 3 SoCs. + config PHY_RCAR_GEN3_USB2 tristate "Renesas R-Car generation 3 USB 2.0 PHY driver" depends on ARCH_RENESAS diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile index 8b6025916a93..4b76fc439ed6 100644 --- a/drivers/phy/renesas/Makefile +++ b/drivers/phy/renesas/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o +obj-$(CONFIG_PHY_RCAR_GEN3_PCIE) += phy-rcar-gen3-pcie.o obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o obj-$(CONFIG_PHY_RCAR_GEN3_USB3) += phy-rcar-gen3-usb3.o diff --git a/drivers/phy/renesas/phy-rcar-gen3-pcie.c b/drivers/phy/renesas/phy-rcar-gen3-pcie.c new file mode 100644 index 000000000000..c4e4aa216936 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen3-pcie.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Car Gen3 PCIe PHY driver + * + * Copyright (C) 2018 Cogent Embedded, Inc. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define PHY_CTRL 0x4000 /* R8A77980 only */ + +/* PHY control register (PHY_CTRL) */ +#define PHY_CTRL_PHY_PWDN BIT(2) + +struct rcar_gen3_phy { + struct phy *phy; + spinlock_t lock; + void __iomem *base; +}; + +static void rcar_gen3_phy_pcie_modify_reg(struct phy *p, unsigned int reg, + u32 clear, u32 set) +{ + struct rcar_gen3_phy *phy = phy_get_drvdata(p); + void __iomem *base = phy->base; + unsigned long flags; + u32 value; + + spin_lock_irqsave(&phy->lock, flags); + + value = readl(base + reg); + value &= ~clear; + value |= set; + writel(value, base + reg); + + spin_unlock_irqrestore(&phy->lock, flags); +} + +static int r8a77980_phy_pcie_power_on(struct phy *p) +{ + /* Power on the PCIe PHY */ + rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, PHY_CTRL_PHY_PWDN, 0); + + return 0; +} + +static int r8a77980_phy_pcie_power_off(struct phy *p) +{ + /* Power off the PCIe PHY */ + rcar_gen3_phy_pcie_modify_reg(p, PHY_CTRL, 0, PHY_CTRL_PHY_PWDN); + + return 0; +} + +static const struct phy_ops r8a77980_phy_pcie_ops = { + .power_on = r8a77980_phy_pcie_power_on, + .power_off = r8a77980_phy_pcie_power_off, + .owner = THIS_MODULE, +}; + +static const struct of_device_id rcar_gen3_phy_pcie_match_table[] = { + { .compatible = "renesas,r8a77980-pcie-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_pcie_match_table); + +static int rcar_gen3_phy_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *provider; + struct rcar_gen3_phy *phy; + struct resource *res; + void __iomem *base; + int error; + + if (!dev->of_node) { + dev_err(dev, + "This driver must only be instantiated from the device tree\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + spin_lock_init(&phy->lock); + + phy->base = base; + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime PM for this device. + */ + pm_runtime_enable(dev); + + phy->phy = devm_phy_create(dev, NULL, &r8a77980_phy_pcie_ops); + if (IS_ERR(phy->phy)) { + dev_err(dev, "Failed to create PCIe PHY\n"); + error = PTR_ERR(phy->phy); + goto error; + } + phy_set_drvdata(phy->phy, phy); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "Failed to register PHY provider\n"); + error = PTR_ERR(provider); + goto error; + } + + return 0; + +error: + pm_runtime_disable(dev); + + return error; +} + +static int rcar_gen3_phy_pcie_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +}; + +static struct platform_driver rcar_gen3_phy_driver = { + .driver = { + .name = "phy_rcar_gen3_pcie", + .of_match_table = rcar_gen3_phy_pcie_match_table, + }, + .probe = rcar_gen3_phy_pcie_probe, + .remove = rcar_gen3_phy_pcie_remove, +}; + +module_platform_driver(rcar_gen3_phy_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 PCIe PHY"); +MODULE_AUTHOR("Sergei Shtylyov <sergei.shtylyov@cogentembedded.com>"); |