// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2021 Marvell * * Authors: * Konstantin Porotchkin * * Marvell CP110 UTMI PHY driver */ #include #include #include #include #include #include #include #include #include #include #define UTMI_PHY_PORTS 2 /* CP110 UTMI register macro definetions */ #define SYSCON_USB_CFG_REG 0x420 #define USB_CFG_DEVICE_EN_MASK BIT(0) #define USB_CFG_DEVICE_MUX_OFFSET 1 #define USB_CFG_DEVICE_MUX_MASK BIT(1) #define USB_CFG_PLL_MASK BIT(25) #define SYSCON_UTMI_CFG_REG(id) (0x440 + (id) * 4) #define UTMI_PHY_CFG_PU_MASK BIT(5) #define UTMI_PLL_CTRL_REG 0x0 #define PLL_REFDIV_OFFSET 0 #define PLL_REFDIV_MASK GENMASK(6, 0) #define PLL_REFDIV_VAL 0x5 #define PLL_FBDIV_OFFSET 16 #define PLL_FBDIV_MASK GENMASK(24, 16) #define PLL_FBDIV_VAL 0x60 #define PLL_SEL_LPFR_MASK GENMASK(29, 28) #define PLL_RDY BIT(31) #define UTMI_CAL_CTRL_REG 0x8 #define IMPCAL_VTH_OFFSET 8 #define IMPCAL_VTH_MASK GENMASK(10, 8) #define IMPCAL_VTH_VAL 0x7 #define IMPCAL_DONE BIT(23) #define PLLCAL_DONE BIT(31) #define UTMI_TX_CH_CTRL_REG 0xC #define DRV_EN_LS_OFFSET 12 #define DRV_EN_LS_MASK GENMASK(15, 12) #define IMP_SEL_LS_OFFSET 16 #define IMP_SEL_LS_MASK GENMASK(19, 16) #define TX_AMP_OFFSET 20 #define TX_AMP_MASK GENMASK(22, 20) #define TX_AMP_VAL 0x4 #define UTMI_RX_CH_CTRL0_REG 0x14 #define SQ_DET_EN BIT(15) #define SQ_ANA_DTC_SEL BIT(28) #define UTMI_RX_CH_CTRL1_REG 0x18 #define SQ_AMP_CAL_OFFSET 0 #define SQ_AMP_CAL_MASK GENMASK(2, 0) #define SQ_AMP_CAL_VAL 1 #define SQ_AMP_CAL_EN BIT(3) #define UTMI_DIG_CTRL1_REG 0x20 #define SWAP_DPDM BIT(15) #define UTMI_CTRL_STATUS0_REG 0x24 #define SUSPENDM BIT(22) #define TEST_SEL BIT(25) #define UTMI_CHGDTC_CTRL_REG 0x38 #define VDAT_OFFSET 8 #define VDAT_MASK GENMASK(9, 8) #define VDAT_VAL 1 #define VSRC_OFFSET 10 #define VSRC_MASK GENMASK(11, 10) #define VSRC_VAL 1 #define PLL_LOCK_DELAY_US 10000 #define PLL_LOCK_TIMEOUT_US 1000000 #define PORT_REGS(p) ((p)->priv->regs + (p)->id * 0x1000) /** * struct mvebu_cp110_utmi - PHY driver data * * @regs: PHY registers * @syscon: Regmap with system controller registers * @dev: device driver handle * @ops: phy ops */ struct mvebu_cp110_utmi { void __iomem *regs; struct regmap *syscon; struct device *dev; const struct phy_ops *ops; }; /** * struct mvebu_cp110_utmi_port - PHY port data * * @priv: PHY driver data * @id: PHY port ID * @dr_mode: PHY connection: USB_DR_MODE_HOST or USB_DR_MODE_PERIPHERAL * @swap_dx: whether to swap d+/d- signals */ struct mvebu_cp110_utmi_port { struct mvebu_cp110_utmi *priv; u32 id; enum usb_dr_mode dr_mode; bool swap_dx; }; static void mvebu_cp110_utmi_port_setup(struct mvebu_cp110_utmi_port *port) { u32 reg; /* * Setup PLL. * The reference clock is the frequency of quartz resonator * connected to pins REFCLK_XIN and REFCLK_XOUT of the SoC. * Register init values are matching the 40MHz default clock. * The crystal used for all platform boards is now 25MHz. * See the functional specification for details. */ reg = readl(PORT_REGS(port) + UTMI_PLL_CTRL_REG); reg &= ~(PLL_REFDIV_MASK | PLL_FBDIV_MASK | PLL_SEL_LPFR_MASK); reg |= (PLL_REFDIV_VAL << PLL_REFDIV_OFFSET) | (PLL_FBDIV_VAL << PLL_FBDIV_OFFSET); writel(reg, PORT_REGS(port) + UTMI_PLL_CTRL_REG); /* Impedance Calibration Threshold Setting */ reg = readl(PORT_REGS(port) + UTMI_CAL_CTRL_REG); reg &= ~IMPCAL_VTH_MASK; reg |= IMPCAL_VTH_VAL << IMPCAL_VTH_OFFSET; writel(reg, PORT_REGS(port) + UTMI_CAL_CTRL_REG); /* Set LS TX driver strength coarse control */ reg = readl(PORT_REGS(port) + UTMI_TX_CH_CTRL_REG); reg &= ~TX_AMP_MASK; reg |= TX_AMP_VAL << TX_AMP_OFFSET; writel(reg, PORT_REGS(port) + UTMI_TX_CH_CTRL_REG); /* Disable SQ and enable analog squelch detect */ reg = readl(PORT_REGS(port) + UTMI_RX_CH_CTRL0_REG); reg &= ~SQ_DET_EN; reg |= SQ_ANA_DTC_SEL; writel(reg, PORT_REGS(port) + UTMI_RX_CH_CTRL0_REG); /* * Set External squelch calibration number and * enable the External squelch calibration */ reg = readl(PORT_REGS(port) + UTMI_RX_CH_CTRL1_REG); reg &= ~SQ_AMP_CAL_MASK; reg |= (SQ_AMP_CAL_VAL << SQ_AMP_CAL_OFFSET) | SQ_AMP_CAL_EN; writel(reg, PORT_REGS(port) + UTMI_RX_CH_CTRL1_REG); /* * Set Control VDAT Reference Voltage - 0.325V and * Control VSRC Reference Voltage - 0.6V */ reg = readl(PORT_REGS(port) + UTMI_CHGDTC_CTRL_REG); reg &= ~(VDAT_MASK | VSRC_MASK); reg |= (VDAT_VAL << VDAT_OFFSET) | (VSRC_VAL << VSRC_OFFSET); writel(reg, PORT_REGS(port) + UTMI_CHGDTC_CTRL_REG); /* Swap D+/D- */ reg = readl(PORT_REGS(port) + UTMI_DIG_CTRL1_REG); reg &= ~(SWAP_DPDM); if (port->swap_dx) reg |= SWAP_DPDM; writel(reg, PORT_REGS(port) + UTMI_DIG_CTRL1_REG); } static int mvebu_cp110_utmi_phy_power_off(struct phy *phy) { struct mvebu_cp110_utmi_port *port = phy_get_drvdata(phy); struct mvebu_cp110_utmi *utmi = port->priv; int i; /* Power down UTMI PHY port */ regmap_clear_bits(utmi->syscon, SYSCON_UTMI_CFG_REG(port->id), UTMI_PHY_CFG_PU_MASK); for (i = 0; i < UTMI_PHY_PORTS; i++) { int test = regmap_test_bits(utmi->syscon, SYSCON_UTMI_CFG_REG(i), UTMI_PHY_CFG_PU_MASK); /* skip PLL shutdown if there are active UTMI PHY ports */ if (test != 0) return 0; } /* PLL Power down if all UTMI PHYs are down */ regmap_clear_bits(utmi->syscon, SYSCON_USB_CFG_REG, USB_CFG_PLL_MASK); return 0; } static int mvebu_cp110_utmi_phy_power_on(struct phy *phy) { struct mvebu_cp110_utmi_port *port = phy_get_drvdata(phy); struct mvebu_cp110_utmi *utmi = port->priv; struct device *dev = &phy->dev; int ret; u32 reg; /* It is necessary to power off UTMI before configuration */ ret = mvebu_cp110_utmi_phy_power_off(phy); if (ret) { dev_err(dev, "UTMI power OFF before power ON failed\n"); return ret; } /* * If UTMI port is connected to USB Device controller, * configure the USB MUX prior to UTMI PHY initialization. * The single USB device controller can be connected * to UTMI0 or to UTMI1 PHY port, but not to both. */ if (port->dr_mode == USB_DR_MODE_PERIPHERAL) { regmap_update_bits(utmi->syscon, SYSCON_USB_CFG_REG, USB_CFG_DEVICE_EN_MASK | USB_CFG_DEVICE_MUX_MASK, USB_CFG_DEVICE_EN_MASK | (port->id << USB_CFG_DEVICE_MUX_OFFSET)); } /* Set Test suspendm mode and enable Test UTMI select */ reg = readl(PORT_REGS(port) + UTMI_CTRL_STATUS0_REG); reg |= SUSPENDM | TEST_SEL; writel(reg, PORT_REGS(port) + UTMI_CTRL_STATUS0_REG); /* Wait for UTMI power down */ mdelay(1); /* PHY port setup first */ mvebu_cp110_utmi_port_setup(port); /* Power UP UTMI PHY */ regmap_set_bits(utmi->syscon, SYSCON_UTMI_CFG_REG(port->id), UTMI_PHY_CFG_PU_MASK); /* Disable Test UTMI select */ reg = readl(PORT_REGS(port) + UTMI_CTRL_STATUS0_REG); reg &= ~TEST_SEL; writel(reg, PORT_REGS(port) + UTMI_CTRL_STATUS0_REG); /* Wait for impedance calibration */ ret = readl_poll_timeout(PORT_REGS(port) + UTMI_CAL_CTRL_REG, reg, reg & IMPCAL_DONE, PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); if (ret) { dev_err(dev, "Failed to end UTMI impedance calibration\n"); return ret; } /* Wait for PLL calibration */ ret = readl_poll_timeout(PORT_REGS(port) + UTMI_CAL_CTRL_REG, reg, reg & PLLCAL_DONE, PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); if (ret) { dev_err(dev, "Failed to end UTMI PLL calibration\n"); return ret; } /* Wait for PLL ready */ ret = readl_poll_timeout(PORT_REGS(port) + UTMI_PLL_CTRL_REG, reg, reg & PLL_RDY, PLL_LOCK_DELAY_US, PLL_LOCK_TIMEOUT_US); if (ret) { dev_err(dev, "PLL is not ready\n"); return ret; } /* PLL Power up */ regmap_set_bits(utmi->syscon, SYSCON_USB_CFG_REG, USB_CFG_PLL_MASK); return 0; } static const struct phy_ops mvebu_cp110_utmi_phy_ops = { .power_on = mvebu_cp110_utmi_phy_power_on, .power_off = mvebu_cp110_utmi_phy_power_off, .owner = THIS_MODULE, }; static const struct of_device_id mvebu_cp110_utmi_of_match[] = { { .compatible = "marvell,cp110-utmi-phy" }, {}, }; MODULE_DEVICE_TABLE(of, mvebu_cp110_utmi_of_match); static int mvebu_cp110_utmi_phy_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mvebu_cp110_utmi *utmi; struct phy_provider *provider; struct device_node *child; u32 usb_devices = 0; u32 swap_dx = 0; utmi = devm_kzalloc(dev, sizeof(*utmi), GFP_KERNEL); if (!utmi) return -ENOMEM; utmi->dev = dev; /* Get system controller region */ utmi->syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "marvell,system-controller"); if (IS_ERR(utmi->syscon)) { dev_err(dev, "Missing UTMI system controller\n"); return PTR_ERR(utmi->syscon); } /* Get UTMI memory region */ utmi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(utmi->regs)) return PTR_ERR(utmi->regs); for_each_available_child_of_node(dev->of_node, child) { struct mvebu_cp110_utmi_port *port; struct phy *phy; int ret; u32 port_id; ret = of_property_read_u32(child, "reg", &port_id); if ((ret < 0) || (port_id >= UTMI_PHY_PORTS)) { dev_err(dev, "invalid 'reg' property on child %pOF\n", child); continue; } port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); if (!port) { of_node_put(child); return -ENOMEM; } port->dr_mode = of_usb_get_dr_mode_by_phy(child, -1); if ((port->dr_mode != USB_DR_MODE_HOST) && (port->dr_mode != USB_DR_MODE_PERIPHERAL)) { dev_err(&pdev->dev, "Missing dual role setting of the port%d, will use HOST mode\n", port_id); port->dr_mode = USB_DR_MODE_HOST; } if (port->dr_mode == USB_DR_MODE_PERIPHERAL) { usb_devices++; if (usb_devices > 1) { dev_err(dev, "Single USB device allowed! Port%d will use HOST mode\n", port_id); port->dr_mode = USB_DR_MODE_HOST; } } of_property_for_each_u32(dev->of_node, "swap-dx-lanes", swap_dx) if (swap_dx == port_id) port->swap_dx = 1; /* Retrieve PHY capabilities */ utmi->ops = &mvebu_cp110_utmi_phy_ops; /* Instantiate the PHY */ phy = devm_phy_create(dev, child, utmi->ops); if (IS_ERR(phy)) { dev_err(dev, "Failed to create the UTMI PHY\n"); of_node_put(child); return PTR_ERR(phy); } port->priv = utmi; port->id = port_id; phy_set_drvdata(phy, port); /* Ensure the PHY is powered off */ mvebu_cp110_utmi_phy_power_off(phy); } dev_set_drvdata(dev, utmi); provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); return PTR_ERR_OR_ZERO(provider); } static struct platform_driver mvebu_cp110_utmi_driver = { .probe = mvebu_cp110_utmi_phy_probe, .driver = { .name = "mvebu-cp110-utmi-phy", .of_match_table = mvebu_cp110_utmi_of_match, }, }; module_platform_driver(mvebu_cp110_utmi_driver); MODULE_AUTHOR("Konstatin Porotchkin "); MODULE_DESCRIPTION("Marvell Armada CP110 UTMI PHY driver"); MODULE_LICENSE("GPL v2");