diff options
Diffstat (limited to 'drivers/usb/phy/mv_u3d_phy.c')
| -rw-r--r-- | drivers/usb/phy/mv_u3d_phy.c | 345 | 
1 files changed, 345 insertions, 0 deletions
diff --git a/drivers/usb/phy/mv_u3d_phy.c b/drivers/usb/phy/mv_u3d_phy.c new file mode 100644 index 00000000000..9f1c5d3c60e --- /dev/null +++ b/drivers/usb/phy/mv_u3d_phy.c @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2011 Marvell International Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/usb/otg.h> +#include <linux/platform_data/mv_usb.h> + +#include "mv_u3d_phy.h" + +/* + * struct mv_u3d_phy - transceiver driver state + * @phy: transceiver structure + * @dev: The parent device supplied to the probe function + * @clk: usb phy clock + * @base: usb phy register memory base + */ +struct mv_u3d_phy { +	struct usb_phy	phy; +	struct mv_usb_platform_data *plat; +	struct device	*dev; +	struct clk	*clk; +	void __iomem	*base; +}; + +static u32 mv_u3d_phy_read(void __iomem *base, u32 reg) +{ +	void __iomem *addr, *data; + +	addr = base; +	data = base + 0x4; + +	writel_relaxed(reg, addr); +	return readl_relaxed(data); +} + +static void mv_u3d_phy_set(void __iomem *base, u32 reg, u32 value) +{ +	void __iomem *addr, *data; +	u32 tmp; + +	addr = base; +	data = base + 0x4; + +	writel_relaxed(reg, addr); +	tmp = readl_relaxed(data); +	tmp |= value; +	writel_relaxed(tmp, data); +} + +static void mv_u3d_phy_clear(void __iomem *base, u32 reg, u32 value) +{ +	void __iomem *addr, *data; +	u32 tmp; + +	addr = base; +	data = base + 0x4; + +	writel_relaxed(reg, addr); +	tmp = readl_relaxed(data); +	tmp &= ~value; +	writel_relaxed(tmp, data); +} + +static void mv_u3d_phy_write(void __iomem *base, u32 reg, u32 value) +{ +	void __iomem *addr, *data; + +	addr = base; +	data = base + 0x4; + +	writel_relaxed(reg, addr); +	writel_relaxed(value, data); +} + +void mv_u3d_phy_shutdown(struct usb_phy *phy) +{ +	struct mv_u3d_phy *mv_u3d_phy; +	void __iomem *base; +	u32 val; + +	mv_u3d_phy = container_of(phy, struct mv_u3d_phy, phy); +	base = mv_u3d_phy->base; + +	/* Power down Reference Analog current, bit 15 +	 * Power down PLL, bit 14 +	 * Power down Receiver, bit 13 +	 * Power down Transmitter, bit 12 +	 * of USB3_POWER_PLL_CONTROL register +	 */ +	val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); +	val &= ~(USB3_POWER_PLL_CONTROL_PU); +	mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); + +	if (mv_u3d_phy->clk) +		clk_disable(mv_u3d_phy->clk); +} + +static int mv_u3d_phy_init(struct usb_phy *phy) +{ +	struct mv_u3d_phy *mv_u3d_phy; +	void __iomem *base; +	u32 val, count; + +	/* enable usb3 phy */ +	mv_u3d_phy = container_of(phy, struct mv_u3d_phy, phy); + +	if (mv_u3d_phy->clk) +		clk_enable(mv_u3d_phy->clk); + +	base = mv_u3d_phy->base; + +	val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); +	val &= ~(USB3_POWER_PLL_CONTROL_PU_MASK); +	val |= 0xF << USB3_POWER_PLL_CONTROL_PU_SHIFT; +	mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); +	udelay(100); + +	mv_u3d_phy_write(base, USB3_RESET_CONTROL, +			USB3_RESET_CONTROL_RESET_PIPE); +	udelay(100); + +	mv_u3d_phy_write(base, USB3_RESET_CONTROL, +			USB3_RESET_CONTROL_RESET_PIPE +			| USB3_RESET_CONTROL_RESET_PHY); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_POWER_PLL_CONTROL); +	val &= ~(USB3_POWER_PLL_CONTROL_REF_FREF_SEL_MASK +		| USB3_POWER_PLL_CONTROL_PHY_MODE_MASK); +	val |=  (USB3_PLL_25MHZ << USB3_POWER_PLL_CONTROL_REF_FREF_SEL_SHIFT) +		| (0x5 << USB3_POWER_PLL_CONTROL_PHY_MODE_SHIFT); +	mv_u3d_phy_write(base, USB3_POWER_PLL_CONTROL, val); +	udelay(100); + +	mv_u3d_phy_clear(base, USB3_KVCO_CALI_CONTROL, +		USB3_KVCO_CALI_CONTROL_USE_MAX_PLL_RATE_MASK); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_SQUELCH_FFE); +	val &= ~(USB3_SQUELCH_FFE_FFE_CAP_SEL_MASK +		| USB3_SQUELCH_FFE_FFE_RES_SEL_MASK +		| USB3_SQUELCH_FFE_SQ_THRESH_IN_MASK); +	val |= ((0xD << USB3_SQUELCH_FFE_FFE_CAP_SEL_SHIFT) +		| (0x7 << USB3_SQUELCH_FFE_FFE_RES_SEL_SHIFT) +		| (0x8 << USB3_SQUELCH_FFE_SQ_THRESH_IN_SHIFT)); +	mv_u3d_phy_write(base, USB3_SQUELCH_FFE, val); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_GEN1_SET0); +	val &= ~USB3_GEN1_SET0_G1_TX_SLEW_CTRL_EN_MASK; +	val |= 1 << USB3_GEN1_SET0_G1_TX_EMPH_EN_SHIFT; +	mv_u3d_phy_write(base, USB3_GEN1_SET0, val); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_GEN2_SET0); +	val &= ~(USB3_GEN2_SET0_G2_TX_AMP_MASK +		| USB3_GEN2_SET0_G2_TX_EMPH_AMP_MASK +		| USB3_GEN2_SET0_G2_TX_SLEW_CTRL_EN_MASK); +	val |= ((0x14 << USB3_GEN2_SET0_G2_TX_AMP_SHIFT) +		| (1 << USB3_GEN2_SET0_G2_TX_AMP_ADJ_SHIFT) +		| (0xA << USB3_GEN2_SET0_G2_TX_EMPH_AMP_SHIFT) +		| (1 << USB3_GEN2_SET0_G2_TX_EMPH_EN_SHIFT)); +	mv_u3d_phy_write(base, USB3_GEN2_SET0, val); +	udelay(100); + +	mv_u3d_phy_read(base, USB3_TX_EMPPH); +	val &= ~(USB3_TX_EMPPH_AMP_MASK +		| USB3_TX_EMPPH_EN_MASK +		| USB3_TX_EMPPH_AMP_FORCE_MASK +		| USB3_TX_EMPPH_PAR1_MASK +		| USB3_TX_EMPPH_PAR2_MASK); +	val |= ((0xB << USB3_TX_EMPPH_AMP_SHIFT) +		| (1 << USB3_TX_EMPPH_EN_SHIFT) +		| (1 << USB3_TX_EMPPH_AMP_FORCE_SHIFT) +		| (0x1C << USB3_TX_EMPPH_PAR1_SHIFT) +		| (1 << USB3_TX_EMPPH_PAR2_SHIFT)); + +	mv_u3d_phy_write(base, USB3_TX_EMPPH, val); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_GEN2_SET1); +	val &= ~(USB3_GEN2_SET1_G2_RX_SELMUPI_MASK +		| USB3_GEN2_SET1_G2_RX_SELMUPF_MASK +		| USB3_GEN2_SET1_G2_RX_SELMUFI_MASK +		| USB3_GEN2_SET1_G2_RX_SELMUFF_MASK); +	val |= ((1 << USB3_GEN2_SET1_G2_RX_SELMUPI_SHIFT) +		| (1 << USB3_GEN2_SET1_G2_RX_SELMUPF_SHIFT) +		| (1 << USB3_GEN2_SET1_G2_RX_SELMUFI_SHIFT) +		| (1 << USB3_GEN2_SET1_G2_RX_SELMUFF_SHIFT)); +	mv_u3d_phy_write(base, USB3_GEN2_SET1, val); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_DIGITAL_LOOPBACK_EN); +	val &= ~USB3_DIGITAL_LOOPBACK_EN_SEL_BITS_MASK; +	val |= 1 << USB3_DIGITAL_LOOPBACK_EN_SEL_BITS_SHIFT; +	mv_u3d_phy_write(base, USB3_DIGITAL_LOOPBACK_EN, val); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_IMPEDANCE_TX_SSC); +	val &= ~USB3_IMPEDANCE_TX_SSC_SSC_AMP_MASK; +	val |= 0xC << USB3_IMPEDANCE_TX_SSC_SSC_AMP_SHIFT; +	mv_u3d_phy_write(base, USB3_IMPEDANCE_TX_SSC, val); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_IMPEDANCE_CALI_CTRL); +	val &= ~USB3_IMPEDANCE_CALI_CTRL_IMP_CAL_THR_MASK; +	val |= 0x4 << USB3_IMPEDANCE_CALI_CTRL_IMP_CAL_THR_SHIFT; +	mv_u3d_phy_write(base, USB3_IMPEDANCE_CALI_CTRL, val); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_PHY_ISOLATION_MODE); +	val &= ~(USB3_PHY_ISOLATION_MODE_PHY_GEN_RX_MASK +		| USB3_PHY_ISOLATION_MODE_PHY_GEN_TX_MASK +		| USB3_PHY_ISOLATION_MODE_TX_DRV_IDLE_MASK); +	val |= ((1 << USB3_PHY_ISOLATION_MODE_PHY_GEN_RX_SHIFT) +		| (1 << USB3_PHY_ISOLATION_MODE_PHY_GEN_TX_SHIFT)); +	mv_u3d_phy_write(base, USB3_PHY_ISOLATION_MODE, val); +	udelay(100); + +	val = mv_u3d_phy_read(base, USB3_TXDETRX); +	val &= ~(USB3_TXDETRX_VTHSEL_MASK); +	val |= 0x1 << USB3_TXDETRX_VTHSEL_SHIFT; +	mv_u3d_phy_write(base, USB3_TXDETRX, val); +	udelay(100); + +	dev_dbg(mv_u3d_phy->dev, "start calibration\n"); + +calstart: +	/* Perform Manual Calibration */ +	mv_u3d_phy_set(base, USB3_KVCO_CALI_CONTROL, +		1 << USB3_KVCO_CALI_CONTROL_CAL_START_SHIFT); + +	mdelay(1); + +	count = 0; +	while (1) { +		val = mv_u3d_phy_read(base, USB3_KVCO_CALI_CONTROL); +		if (val & (1 << USB3_KVCO_CALI_CONTROL_CAL_DONE_SHIFT)) +			break; +		else if (count > 50) { +			dev_dbg(mv_u3d_phy->dev, "calibration failure, retry...\n"); +			goto calstart; +		} +		count++; +		mdelay(1); +	} + +	/* active PIPE interface */ +	mv_u3d_phy_write(base, USB3_PIPE_SM_CTRL, +		1 << USB3_PIPE_SM_CTRL_PHY_INIT_DONE); + +	return 0; +} + +static int __devinit mv_u3d_phy_probe(struct platform_device *pdev) +{ +	struct mv_u3d_phy *mv_u3d_phy; +	struct mv_usb_platform_data *pdata; +	struct device *dev = &pdev->dev; +	struct resource *res; +	void __iomem	*phy_base; +	int	ret; + +	pdata = pdev->dev.platform_data; +	if (!pdata) { +		dev_err(&pdev->dev, "%s: no platform data defined\n", __func__); +		return -EINVAL; +	} + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		dev_err(dev, "missing mem resource\n"); +		return -ENODEV; +	} + +	phy_base = devm_request_and_ioremap(dev, res); +	if (!phy_base) { +		dev_err(dev, "%s: register mapping failed\n", __func__); +		return -ENXIO; +	} + +	mv_u3d_phy = devm_kzalloc(dev, sizeof(*mv_u3d_phy), GFP_KERNEL); +	if (!mv_u3d_phy) +		return -ENOMEM; + +	mv_u3d_phy->dev			= &pdev->dev; +	mv_u3d_phy->plat		= pdata; +	mv_u3d_phy->base		= phy_base; +	mv_u3d_phy->phy.dev		= mv_u3d_phy->dev; +	mv_u3d_phy->phy.label		= "mv-u3d-phy"; +	mv_u3d_phy->phy.init		= mv_u3d_phy_init; +	mv_u3d_phy->phy.shutdown	= mv_u3d_phy_shutdown; + +	ret = usb_add_phy(&mv_u3d_phy->phy, USB_PHY_TYPE_USB3); +	if (ret) +		goto err; + +	if (!mv_u3d_phy->clk) +		mv_u3d_phy->clk = clk_get(mv_u3d_phy->dev, "u3dphy"); + +	platform_set_drvdata(pdev, mv_u3d_phy); + +	dev_info(&pdev->dev, "Initialized Marvell USB 3.0 PHY\n"); +err: +	return ret; +} + +static int __exit mv_u3d_phy_remove(struct platform_device *pdev) +{ +	struct mv_u3d_phy *mv_u3d_phy = platform_get_drvdata(pdev); + +	usb_remove_phy(&mv_u3d_phy->phy); + +	if (mv_u3d_phy->clk) { +		clk_put(mv_u3d_phy->clk); +		mv_u3d_phy->clk = NULL; +	} + +	return 0; +} + +static struct platform_driver mv_u3d_phy_driver = { +	.probe		= mv_u3d_phy_probe, +	.remove		= __devexit_p(mv_u3d_phy_remove), +	.driver		= { +		.name	= "mv-u3d-phy", +		.owner	= THIS_MODULE, +	}, +}; + +module_platform_driver(mv_u3d_phy_driver); +MODULE_DESCRIPTION("Marvell USB 3.0 PHY controller"); +MODULE_AUTHOR("Yu Xu <yuxu@marvell.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:mv-u3d-phy");  |