// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2018 Marvell * * Authors: * Igal Liberman * Miquèl Raynal * * Marvell A3700 UTMI PHY driver */ #include #include #include #include #include #include #include #include /* Armada 3700 UTMI PHY registers */ #define USB2_PHY_PLL_CTRL_REG0 0x0 #define PLL_REF_DIV_OFF 0 #define PLL_REF_DIV_MASK GENMASK(6, 0) #define PLL_REF_DIV_5 5 #define PLL_FB_DIV_OFF 16 #define PLL_FB_DIV_MASK GENMASK(24, 16) #define PLL_FB_DIV_96 96 #define PLL_SEL_LPFR_OFF 28 #define PLL_SEL_LPFR_MASK GENMASK(29, 28) #define PLL_READY BIT(31) #define USB2_PHY_CAL_CTRL 0x8 #define PHY_PLLCAL_DONE BIT(31) #define PHY_IMPCAL_DONE BIT(23) #define USB2_RX_CHAN_CTRL1 0x18 #define USB2PHY_SQCAL_DONE BIT(31) #define USB2_PHY_OTG_CTRL 0x34 #define PHY_PU_OTG BIT(4) #define USB2_PHY_CHRGR_DETECT 0x38 #define PHY_CDP_EN BIT(2) #define PHY_DCP_EN BIT(3) #define PHY_PD_EN BIT(4) #define PHY_PU_CHRG_DTC BIT(5) #define PHY_CDP_DM_AUTO BIT(7) #define PHY_ENSWITCH_DP BIT(12) #define PHY_ENSWITCH_DM BIT(13) /* Armada 3700 USB miscellaneous registers */ #define USB2_PHY_CTRL(usb32) (usb32 ? 0x20 : 0x4) #define RB_USB2PHY_PU BIT(0) #define USB2_DP_PULLDN_DEV_MODE BIT(5) #define USB2_DM_PULLDN_DEV_MODE BIT(6) #define RB_USB2PHY_SUSPM(usb32) (usb32 ? BIT(14) : BIT(7)) #define PLL_LOCK_DELAY_US 10000 #define PLL_LOCK_TIMEOUT_US 1000000 /** * struct mvebu_a3700_utmi_caps - PHY capabilities * * @usb32: Flag indicating which PHY is in use (impacts the register map): * - The UTMI PHY wired to the USB3/USB2 controller (otg) * - The UTMI PHY wired to the USB2 controller (host only) * @ops: PHY operations */ struct mvebu_a3700_utmi_caps { int usb32; const struct phy_ops *ops; }; /** * struct mvebu_a3700_utmi - PHY driver data * * @regs: PHY registers * @usb_mis: Regmap with USB miscellaneous registers including PHY ones * @caps: PHY capabilities * @phy: PHY handle */ struct mvebu_a3700_utmi { void __iomem *regs; struct regmap *usb_misc; const struct mvebu_a3700_utmi_caps *caps; struct phy *phy; }; static int mvebu_a3700_utmi_phy_power_on(struct phy *phy) { struct mvebu_a3700_utmi *utmi = phy_get_drvdata(phy); struct device *dev = &phy->dev; int usb32 = utmi->caps->usb32; int ret = 0; u32 reg; /* * Setup PLL. 40MHz clock used to be the default, being 25MHz now. * See "PLL Settings for Typical REFCLK" table. */ reg = readl(utmi->regs + USB2_PHY_PLL_CTRL_REG0); reg &= ~(PLL_REF_DIV_MASK | PLL_FB_DIV_MASK | PLL_SEL_LPFR_MASK); reg |= (PLL_REF_DIV_5 << PLL_REF_DIV_OFF) | (PLL_FB_DIV_96 << PLL_FB_DIV_OFF); writel(reg, utmi->regs + USB2_PHY_PLL_CTRL_REG0); /* Enable PHY pull up and disable USB2 suspend */ regmap_update_bits(utmi->usb_misc, USB2_PHY_CTRL(usb32), RB_USB2PHY_SUSPM(usb32) | RB_USB2PHY_PU, RB_USB2PHY_SUSPM(usb32) | RB_USB2PHY_PU); if (usb32) { /* Power up OTG module */ reg = readl(utmi->regs + USB2_PHY_OTG_CTRL); reg |= PHY_PU_OTG; writel(reg, utmi->regs + USB2_PHY_OTG_CTRL); /* Disable PHY charger detection */ reg = readl(utmi->regs + USB2_PHY_CHRGR_DETECT); reg &= ~(PHY_CDP_EN | PHY_DCP_EN | PHY_PD_EN | PHY_PU_CHRG_DTC | PHY_CDP_DM_AUTO | PHY_ENSWITCH_DP | PHY_ENSWITCH_DM); writel(reg, utmi->regs + USB2_PHY_CHRGR_DETECT); /* Disable PHY DP/DM pull-down (used for device mode) */ regmap_update_bits(utmi->usb_misc, USB2_PHY_CTRL(usb32), USB2_DP_PULLDN_DEV_MODE | USB2_DM_PULLDN_DEV_MODE, 0); } /* Wait for PLL calibration */ ret = readl_poll_timeout(utmi->regs + USB2_PHY_CAL_CTRL, reg, reg & PHY_PLLCAL_DONE, PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); if (ret) { dev_err(dev, "Failed to end USB2 PLL calibration\n"); return ret; } /* Wait for impedance calibration */ ret = readl_poll_timeout(utmi->regs + USB2_PHY_CAL_CTRL, reg, reg & PHY_IMPCAL_DONE, PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); if (ret) { dev_err(dev, "Failed to end USB2 impedance calibration\n"); return ret; } /* Wait for squelch calibration */ ret = readl_poll_timeout(utmi->regs + USB2_RX_CHAN_CTRL1, reg, reg & USB2PHY_SQCAL_DONE, PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); if (ret) { dev_err(dev, "Failed to end USB2 unknown calibration\n"); return ret; } /* Wait for PLL to be locked */ ret = readl_poll_timeout(utmi->regs + USB2_PHY_PLL_CTRL_REG0, reg, reg & PLL_READY, PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); if (ret) dev_err(dev, "Failed to lock USB2 PLL\n"); return ret; } static int mvebu_a3700_utmi_phy_power_off(struct phy *phy) { struct mvebu_a3700_utmi *utmi = phy_get_drvdata(phy); int usb32 = utmi->caps->usb32; u32 reg; /* Disable PHY pull-up and enable USB2 suspend */ reg = readl(utmi->regs + USB2_PHY_CTRL(usb32)); reg &= ~(RB_USB2PHY_PU | RB_USB2PHY_SUSPM(usb32)); writel(reg, utmi->regs + USB2_PHY_CTRL(usb32)); /* Power down OTG module */ if (usb32) { reg = readl(utmi->regs + USB2_PHY_OTG_CTRL); reg &= ~PHY_PU_OTG; writel(reg, utmi->regs + USB2_PHY_OTG_CTRL); } return 0; } static const struct phy_ops mvebu_a3700_utmi_phy_ops = { .power_on = mvebu_a3700_utmi_phy_power_on, .power_off = mvebu_a3700_utmi_phy_power_off, .owner = THIS_MODULE, }; static const struct mvebu_a3700_utmi_caps mvebu_a3700_utmi_otg_phy_caps = { .usb32 = true, .ops = &mvebu_a3700_utmi_phy_ops, }; static const struct mvebu_a3700_utmi_caps mvebu_a3700_utmi_host_phy_caps = { .usb32 = false, .ops = &mvebu_a3700_utmi_phy_ops, }; static const struct of_device_id mvebu_a3700_utmi_of_match[] = { { .compatible = "marvell,a3700-utmi-otg-phy", .data = &mvebu_a3700_utmi_otg_phy_caps, }, { .compatible = "marvell,a3700-utmi-host-phy", .data = &mvebu_a3700_utmi_host_phy_caps, }, {}, }; MODULE_DEVICE_TABLE(of, mvebu_a3700_utmi_of_match); static int mvebu_a3700_utmi_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mvebu_a3700_utmi *utmi; struct phy_provider *provider; utmi = devm_kzalloc(dev, sizeof(*utmi), GFP_KERNEL); if (!utmi) return -ENOMEM; /* Get UTMI memory region */ utmi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(utmi->regs)) return PTR_ERR(utmi->regs); /* Get miscellaneous Host/PHY region */ utmi->usb_misc = syscon_regmap_lookup_by_phandle(dev->of_node, "marvell,usb-misc-reg"); if (IS_ERR(utmi->usb_misc)) { dev_err(dev, "Missing USB misc purpose system controller\n"); return PTR_ERR(utmi->usb_misc); } /* Retrieve PHY capabilities */ utmi->caps = of_device_get_match_data(dev); /* Instantiate the PHY */ utmi->phy = devm_phy_create(dev, NULL, utmi->caps->ops); if (IS_ERR(utmi->phy)) { dev_err(dev, "Failed to create the UTMI PHY\n"); return PTR_ERR(utmi->phy); } phy_set_drvdata(utmi->phy, utmi); /* Ensure the PHY is powered off */ utmi->caps->ops->power_off(utmi->phy); provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); return PTR_ERR_OR_ZERO(provider); } static struct platform_driver mvebu_a3700_utmi_driver = { .probe = mvebu_a3700_utmi_phy_probe, .driver = { .name = "mvebu-a3700-utmi-phy", .of_match_table = mvebu_a3700_utmi_of_match, }, }; module_platform_driver(mvebu_a3700_utmi_driver); MODULE_AUTHOR("Igal Liberman "); MODULE_AUTHOR("Miquel Raynal "); MODULE_DESCRIPTION("Marvell EBU A3700 UTMI PHY driver"); MODULE_LICENSE("GPL v2");