| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Copyright (C) 2021 Marvell | 
 |  * | 
 |  * Authors: | 
 |  *   Konstantin Porotchkin <kostap@marvell.com> | 
 |  * | 
 |  * Marvell CP110 UTMI PHY driver | 
 |  */ | 
 |  | 
 | #include <linux/io.h> | 
 | #include <linux/iopoll.h> | 
 | #include <linux/mfd/syscon.h> | 
 | #include <linux/module.h> | 
 | #include <linux/of.h> | 
 | #include <linux/phy/phy.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/regmap.h> | 
 | #include <linux/usb/of.h> | 
 | #include <linux/usb/otg.h> | 
 |  | 
 | #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 <kostap@marvell.com>"); | 
 | MODULE_DESCRIPTION("Marvell Armada CP110 UTMI PHY driver"); | 
 | MODULE_LICENSE("GPL v2"); |