|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (C) 2011 Marvell International Ltd. All rights reserved. | 
|  | * Copyright (C) 2018,2019 Lubomir Rintel <lkundrak@v3.sk> | 
|  | */ | 
|  |  | 
|  | #include <linux/delay.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/phy/phy.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/soc/mmp/cputype.h> | 
|  |  | 
|  | #define USB2_PLL_REG0		0x4 | 
|  | #define USB2_PLL_REG1		0x8 | 
|  | #define USB2_TX_REG0		0x10 | 
|  | #define USB2_TX_REG1		0x14 | 
|  | #define USB2_TX_REG2		0x18 | 
|  | #define USB2_RX_REG0		0x20 | 
|  | #define USB2_RX_REG1		0x24 | 
|  | #define USB2_RX_REG2		0x28 | 
|  | #define USB2_ANA_REG0		0x30 | 
|  | #define USB2_ANA_REG1		0x34 | 
|  | #define USB2_ANA_REG2		0x38 | 
|  | #define USB2_DIG_REG0		0x3C | 
|  | #define USB2_DIG_REG1		0x40 | 
|  | #define USB2_DIG_REG2		0x44 | 
|  | #define USB2_DIG_REG3		0x48 | 
|  | #define USB2_TEST_REG0		0x4C | 
|  | #define USB2_TEST_REG1		0x50 | 
|  | #define USB2_TEST_REG2		0x54 | 
|  | #define USB2_CHARGER_REG0	0x58 | 
|  | #define USB2_OTG_REG0		0x5C | 
|  | #define USB2_PHY_MON0		0x60 | 
|  | #define USB2_RESETVE_REG0	0x64 | 
|  | #define USB2_ICID_REG0		0x78 | 
|  | #define USB2_ICID_REG1		0x7C | 
|  |  | 
|  | /* USB2_PLL_REG0 */ | 
|  |  | 
|  | /* This is for Ax stepping */ | 
|  | #define USB2_PLL_FBDIV_SHIFT_MMP3		0 | 
|  | #define USB2_PLL_FBDIV_MASK_MMP3		(0xFF << 0) | 
|  |  | 
|  | #define USB2_PLL_REFDIV_SHIFT_MMP3		8 | 
|  | #define USB2_PLL_REFDIV_MASK_MMP3		(0xF << 8) | 
|  |  | 
|  | #define USB2_PLL_VDD12_SHIFT_MMP3		12 | 
|  | #define USB2_PLL_VDD18_SHIFT_MMP3		14 | 
|  |  | 
|  | /* This is for B0 stepping */ | 
|  | #define USB2_PLL_FBDIV_SHIFT_MMP3_B0		0 | 
|  | #define USB2_PLL_REFDIV_SHIFT_MMP3_B0		9 | 
|  | #define USB2_PLL_VDD18_SHIFT_MMP3_B0		14 | 
|  | #define USB2_PLL_FBDIV_MASK_MMP3_B0		0x01FF | 
|  | #define USB2_PLL_REFDIV_MASK_MMP3_B0		0x3E00 | 
|  |  | 
|  | #define USB2_PLL_CAL12_SHIFT_MMP3		0 | 
|  | #define USB2_PLL_CALI12_MASK_MMP3		(0x3 << 0) | 
|  |  | 
|  | #define USB2_PLL_VCOCAL_START_SHIFT_MMP3	2 | 
|  |  | 
|  | #define USB2_PLL_KVCO_SHIFT_MMP3		4 | 
|  | #define USB2_PLL_KVCO_MASK_MMP3			(0x7<<4) | 
|  |  | 
|  | #define USB2_PLL_ICP_SHIFT_MMP3			8 | 
|  | #define USB2_PLL_ICP_MASK_MMP3			(0x7<<8) | 
|  |  | 
|  | #define USB2_PLL_LOCK_BYPASS_SHIFT_MMP3		12 | 
|  |  | 
|  | #define USB2_PLL_PU_PLL_SHIFT_MMP3		13 | 
|  | #define USB2_PLL_PU_PLL_MASK			(0x1 << 13) | 
|  |  | 
|  | #define USB2_PLL_READY_MASK_MMP3		(0x1 << 15) | 
|  |  | 
|  | /* USB2_TX_REG0 */ | 
|  | #define USB2_TX_IMPCAL_VTH_SHIFT_MMP3		8 | 
|  | #define USB2_TX_IMPCAL_VTH_MASK_MMP3		(0x7 << 8) | 
|  |  | 
|  | #define USB2_TX_RCAL_START_SHIFT_MMP3		13 | 
|  |  | 
|  | /* USB2_TX_REG1 */ | 
|  | #define USB2_TX_CK60_PHSEL_SHIFT_MMP3		0 | 
|  | #define USB2_TX_CK60_PHSEL_MASK_MMP3		(0xf << 0) | 
|  |  | 
|  | #define USB2_TX_AMP_SHIFT_MMP3			4 | 
|  | #define USB2_TX_AMP_MASK_MMP3			(0x7 << 4) | 
|  |  | 
|  | #define USB2_TX_VDD12_SHIFT_MMP3		8 | 
|  | #define USB2_TX_VDD12_MASK_MMP3			(0x3 << 8) | 
|  |  | 
|  | /* USB2_TX_REG2 */ | 
|  | #define USB2_TX_DRV_SLEWRATE_SHIFT		10 | 
|  |  | 
|  | /* USB2_RX_REG0 */ | 
|  | #define USB2_RX_SQ_THRESH_SHIFT_MMP3		4 | 
|  | #define USB2_RX_SQ_THRESH_MASK_MMP3		(0xf << 4) | 
|  |  | 
|  | #define USB2_RX_SQ_LENGTH_SHIFT_MMP3		10 | 
|  | #define USB2_RX_SQ_LENGTH_MASK_MMP3		(0x3 << 10) | 
|  |  | 
|  | /* USB2_ANA_REG1*/ | 
|  | #define USB2_ANA_PU_ANA_SHIFT_MMP3		14 | 
|  |  | 
|  | /* USB2_OTG_REG0 */ | 
|  | #define USB2_OTG_PU_OTG_SHIFT_MMP3		3 | 
|  |  | 
|  | struct mmp3_usb_phy { | 
|  | struct phy *phy; | 
|  | void __iomem *base; | 
|  | }; | 
|  |  | 
|  | static unsigned int u2o_get(void __iomem *base, unsigned int offset) | 
|  | { | 
|  | return readl_relaxed(base + offset); | 
|  | } | 
|  |  | 
|  | static void u2o_set(void __iomem *base, unsigned int offset, | 
|  | unsigned int value) | 
|  | { | 
|  | u32 reg; | 
|  |  | 
|  | reg = readl_relaxed(base + offset); | 
|  | reg |= value; | 
|  | writel_relaxed(reg, base + offset); | 
|  | readl_relaxed(base + offset); | 
|  | } | 
|  |  | 
|  | static void u2o_clear(void __iomem *base, unsigned int offset, | 
|  | unsigned int value) | 
|  | { | 
|  | u32 reg; | 
|  |  | 
|  | reg = readl_relaxed(base + offset); | 
|  | reg &= ~value; | 
|  | writel_relaxed(reg, base + offset); | 
|  | readl_relaxed(base + offset); | 
|  | } | 
|  |  | 
|  | static int mmp3_usb_phy_init(struct phy *phy) | 
|  | { | 
|  | struct mmp3_usb_phy *mmp3_usb_phy = phy_get_drvdata(phy); | 
|  | void __iomem *base = mmp3_usb_phy->base; | 
|  |  | 
|  | if (cpu_is_mmp3_a0()) { | 
|  | u2o_clear(base, USB2_PLL_REG0, (USB2_PLL_FBDIV_MASK_MMP3 | 
|  | | USB2_PLL_REFDIV_MASK_MMP3)); | 
|  | u2o_set(base, USB2_PLL_REG0, | 
|  | 0xd << USB2_PLL_REFDIV_SHIFT_MMP3 | 
|  | | 0xf0 << USB2_PLL_FBDIV_SHIFT_MMP3); | 
|  | } else if (cpu_is_mmp3_b0()) { | 
|  | u2o_clear(base, USB2_PLL_REG0, USB2_PLL_REFDIV_MASK_MMP3_B0 | 
|  | | USB2_PLL_FBDIV_MASK_MMP3_B0); | 
|  | u2o_set(base, USB2_PLL_REG0, | 
|  | 0xd << USB2_PLL_REFDIV_SHIFT_MMP3_B0 | 
|  | | 0xf0 << USB2_PLL_FBDIV_SHIFT_MMP3_B0); | 
|  | } else { | 
|  | dev_err(&phy->dev, "unsupported silicon revision\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | u2o_clear(base, USB2_PLL_REG1, USB2_PLL_PU_PLL_MASK | 
|  | | USB2_PLL_ICP_MASK_MMP3 | 
|  | | USB2_PLL_KVCO_MASK_MMP3 | 
|  | | USB2_PLL_CALI12_MASK_MMP3); | 
|  | u2o_set(base, USB2_PLL_REG1, 1 << USB2_PLL_PU_PLL_SHIFT_MMP3 | 
|  | | 1 << USB2_PLL_LOCK_BYPASS_SHIFT_MMP3 | 
|  | | 3 << USB2_PLL_ICP_SHIFT_MMP3 | 
|  | | 3 << USB2_PLL_KVCO_SHIFT_MMP3 | 
|  | | 3 << USB2_PLL_CAL12_SHIFT_MMP3); | 
|  |  | 
|  | u2o_clear(base, USB2_TX_REG0, USB2_TX_IMPCAL_VTH_MASK_MMP3); | 
|  | u2o_set(base, USB2_TX_REG0, 2 << USB2_TX_IMPCAL_VTH_SHIFT_MMP3); | 
|  |  | 
|  | u2o_clear(base, USB2_TX_REG1, USB2_TX_VDD12_MASK_MMP3 | 
|  | | USB2_TX_AMP_MASK_MMP3 | 
|  | | USB2_TX_CK60_PHSEL_MASK_MMP3); | 
|  | u2o_set(base, USB2_TX_REG1, 3 << USB2_TX_VDD12_SHIFT_MMP3 | 
|  | | 4 << USB2_TX_AMP_SHIFT_MMP3 | 
|  | | 4 << USB2_TX_CK60_PHSEL_SHIFT_MMP3); | 
|  |  | 
|  | u2o_clear(base, USB2_TX_REG2, 3 << USB2_TX_DRV_SLEWRATE_SHIFT); | 
|  | u2o_set(base, USB2_TX_REG2, 2 << USB2_TX_DRV_SLEWRATE_SHIFT); | 
|  |  | 
|  | u2o_clear(base, USB2_RX_REG0, USB2_RX_SQ_THRESH_MASK_MMP3); | 
|  | u2o_set(base, USB2_RX_REG0, 0xa << USB2_RX_SQ_THRESH_SHIFT_MMP3); | 
|  |  | 
|  | u2o_set(base, USB2_ANA_REG1, 0x1 << USB2_ANA_PU_ANA_SHIFT_MMP3); | 
|  |  | 
|  | u2o_set(base, USB2_OTG_REG0, 0x1 << USB2_OTG_PU_OTG_SHIFT_MMP3); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int mmp3_usb_phy_calibrate(struct phy *phy) | 
|  | { | 
|  | struct mmp3_usb_phy *mmp3_usb_phy = phy_get_drvdata(phy); | 
|  | void __iomem *base = mmp3_usb_phy->base; | 
|  | int loops; | 
|  |  | 
|  | /* | 
|  | * PLL VCO and TX Impedance Calibration Timing: | 
|  | * | 
|  | *                _____________________________________ | 
|  | * PU  __________| | 
|  | *                        _____________________________ | 
|  | * VCOCAL START _________| | 
|  | *                                 ___ | 
|  | * REG_RCAL_START ________________|   |________|_______ | 
|  | *               | 200us | 400us  | 40| 400us  | USB PHY READY | 
|  | */ | 
|  |  | 
|  | udelay(200); | 
|  | u2o_set(base, USB2_PLL_REG1, 1 << USB2_PLL_VCOCAL_START_SHIFT_MMP3); | 
|  | udelay(400); | 
|  | u2o_set(base, USB2_TX_REG0, 1 << USB2_TX_RCAL_START_SHIFT_MMP3); | 
|  | udelay(40); | 
|  | u2o_clear(base, USB2_TX_REG0, 1 << USB2_TX_RCAL_START_SHIFT_MMP3); | 
|  | udelay(400); | 
|  |  | 
|  | loops = 0; | 
|  | while ((u2o_get(base, USB2_PLL_REG1) & USB2_PLL_READY_MASK_MMP3) == 0) { | 
|  | mdelay(1); | 
|  | loops++; | 
|  | if (loops > 100) { | 
|  | dev_err(&phy->dev, "PLL_READY not set after 100mS.\n"); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct phy_ops mmp3_usb_phy_ops = { | 
|  | .init		= mmp3_usb_phy_init, | 
|  | .calibrate	= mmp3_usb_phy_calibrate, | 
|  | .owner		= THIS_MODULE, | 
|  | }; | 
|  |  | 
|  | static const struct of_device_id mmp3_usb_phy_of_match[] = { | 
|  | { .compatible = "marvell,mmp3-usb-phy", }, | 
|  | { }, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, mmp3_usb_phy_of_match); | 
|  |  | 
|  | static int mmp3_usb_phy_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct resource *resource; | 
|  | struct mmp3_usb_phy *mmp3_usb_phy; | 
|  | struct phy_provider *provider; | 
|  |  | 
|  | mmp3_usb_phy = devm_kzalloc(dev, sizeof(*mmp3_usb_phy), GFP_KERNEL); | 
|  | if (!mmp3_usb_phy) | 
|  | return -ENOMEM; | 
|  |  | 
|  | resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | mmp3_usb_phy->base = devm_ioremap_resource(dev, resource); | 
|  | if (IS_ERR(mmp3_usb_phy->base)) { | 
|  | dev_err(dev, "failed to remap PHY regs\n"); | 
|  | return PTR_ERR(mmp3_usb_phy->base); | 
|  | } | 
|  |  | 
|  | mmp3_usb_phy->phy = devm_phy_create(dev, NULL, &mmp3_usb_phy_ops); | 
|  | if (IS_ERR(mmp3_usb_phy->phy)) { | 
|  | dev_err(dev, "failed to create PHY\n"); | 
|  | return PTR_ERR(mmp3_usb_phy->phy); | 
|  | } | 
|  |  | 
|  | phy_set_drvdata(mmp3_usb_phy->phy, mmp3_usb_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"); | 
|  | return PTR_ERR(provider); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver mmp3_usb_phy_driver = { | 
|  | .probe		= mmp3_usb_phy_probe, | 
|  | .driver		= { | 
|  | .name	= "mmp3-usb-phy", | 
|  | .of_match_table = mmp3_usb_phy_of_match, | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(mmp3_usb_phy_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); | 
|  | MODULE_DESCRIPTION("Marvell MMP3 USB PHY Driver"); | 
|  | MODULE_LICENSE("GPL v2"); |