[PATCH v4 22/22] phy: Add support for Qualcomm's USB HS phy

Peter Chen hzpeterchen at gmail.com
Tue Sep 13 00:03:58 PDT 2016


On Wed, Sep 07, 2016 at 02:35:19PM -0700, Stephen Boyd wrote:
> The high-speed phy on qcom SoCs is controlled via the ULPI
> viewport.
> 

Hi Stephen, I am a little puzzled how this driver co-work with chipidea
driver. According to nxp IC guys, the ULPI PHY's clock needs to be enabled
before access portsc.pts (calling hw_phymode_configure), otherwise,
the system will hang. But I find you call hw_phymode_configure before
phy->power_on, doesn't your design have this requirement?
       
Besides, you read ulpi id before phy->power_on, how can read work before
phy power on?

Peter

> Cc: Kishon Vijay Abraham I <kishon at ti.com>
> Cc: <devicetree at vger.kernel.org>
> Signed-off-by: Stephen Boyd <stephen.boyd at linaro.org>
> ---
>  .../devicetree/bindings/phy/qcom,usb-hs-phy.txt    |  83 ++++++
>  drivers/phy/Kconfig                                |   8 +
>  drivers/phy/Makefile                               |   1 +
>  drivers/phy/phy-qcom-usb-hs.c                      | 289 +++++++++++++++++++++
>  4 files changed, 381 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt
>  create mode 100644 drivers/phy/phy-qcom-usb-hs.c
> 
> diff --git a/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt b/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt
> new file mode 100644
> index 000000000000..d7eacd63d06b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/qcom,usb-hs-phy.txt
> @@ -0,0 +1,83 @@
> +Qualcomm's USB HS PHY
> +
> +PROPERTIES
> +
> +- compatible:
> +    Usage: required
> +    Value type: <string>
> +    Definition: Should contain "qcom,usb-hs-phy" and more specifically one of the
> +                following:
> +
> +                        "qcom,usb-hs-phy-apq8064"
> +                        "qcom,usb-hs-phy-msm8916"
> +                        "qcom,usb-hs-phy-msm8974"
> +
> +- #phy-cells:
> +    Usage: required
> +    Value type: <u32>
> +    Definition: Should contain 0
> +
> +- clocks:
> +    Usage: required
> +    Value type: <prop-encoded-array>
> +    Definition: Should contain clock specifier for the reference and sleep
> +                clocks
> +
> +- clock-names:
> +    Usage: required
> +    Value type: <stringlist>
> +    Definition: Should contain "ref" and "sleep" for the reference and sleep
> +                clocks respectively
> +
> +- resets:
> +    Usage: required
> +    Value type: <prop-encoded-array>
> +    Definition: Should contain the phy and POR resets
> +
> +- reset-names:
> +    Usage: required
> +    Value type: <stringlist>
> +    Definition: Should contain "phy" and "por" for the phy and POR resets
> +                respectively
> +
> +- v3p3-supply:
> +    Usage: required
> +    Value type: <phandle>
> +    Definition: Should contain a reference to the 3.3V supply
> +
> +- v1p8-supply:
> +    Usage: required
> +    Value type: <phandle>
> +    Definition: Should contain a reference to the 1.8V supply
> +
> +- extcon:
> +    Usage: optional
> +    Value type: <prop-encoded-array>
> +    Definition: Should contain the vbus and ID extcons in the first and second
> +                cells respectively
> +
> +- qcom,init-seq:
> +    Usage: optional
> +    Value type: <u8 array>
> +    Definition: Should contain a sequence of ULPI register and address pairs to
> +                program into the ULPI_EXT_VENDOR_SPECIFIC area. This is related
> +                to Device Mode Eye Diagram test.
> +
> +EXAMPLE
> +
> +otg: usb-controller {
> +	ulpi {
> +		phy {
> +			compatible = "qcom,usb-hs-phy-msm8974", "qcom,usb-hs-phy";
> +			#phy-cells = <0>;
> +			clocks = <&xo_board>, <&gcc GCC_USB2A_PHY_SLEEP_CLK>;
> +			clock-names = "ref", "sleep";
> +			resets = <&gcc GCC_USB2A_PHY_BCR>, <&otg 0>;
> +			reset-names = "phy", "por";
> +			v3p3-supply = <&pm8941_l24>;
> +			v1p8-supply = <&pm8941_l6>;
> +			extcon = <&smbb>, <&usb_id>;
> +			qcom,init-seq = /bits/ 8 <0x81 0x63>;
> +		};
> +	};
> +};
> diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
> index 830c443eeabf..ee0ec021a98c 100644
> --- a/drivers/phy/Kconfig
> +++ b/drivers/phy/Kconfig
> @@ -417,6 +417,14 @@ config PHY_QCOM_UFS
>  	help
>  	  Support for UFS PHY on QCOM chipsets.
>  
> +config PHY_QCOM_USB_HS
> +	tristate "Qualcomm USB HS PHY module"
> +	depends on USB_ULPI_BUS
> +	select GENERIC_PHY
> +	help
> +	  Support for the USB high-speed ULPI compliant phy on Qualcomm
> +	  chipsets.
> +
>  config PHY_QCOM_USB_HSIC
>  	tristate "Qualcomm USB HSIC ULPI PHY module"
>  	depends on USB_ULPI_BUS
> diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
> index 5422f543d17d..31c84faa07fa 100644
> --- a/drivers/phy/Makefile
> +++ b/drivers/phy/Makefile
> @@ -50,6 +50,7 @@ obj-$(CONFIG_PHY_STIH41X_USB)		+= phy-stih41x-usb.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-20nm.o
>  obj-$(CONFIG_PHY_QCOM_UFS) 	+= phy-qcom-ufs-qmp-14nm.o
> +obj-$(CONFIG_PHY_QCOM_USB_HS) 		+= phy-qcom-usb-hs.o
>  obj-$(CONFIG_PHY_QCOM_USB_HSIC) 	+= phy-qcom-usb-hsic.o
>  obj-$(CONFIG_PHY_TUSB1210)		+= phy-tusb1210.o
>  obj-$(CONFIG_PHY_BRCM_SATA)		+= phy-brcm-sata.o
> diff --git a/drivers/phy/phy-qcom-usb-hs.c b/drivers/phy/phy-qcom-usb-hs.c
> new file mode 100644
> index 000000000000..73fb4b49a8e1
> --- /dev/null
> +++ b/drivers/phy/phy-qcom-usb-hs.c
> @@ -0,0 +1,289 @@
> +/**
> + * Copyright (C) 2016 Linaro Ltd
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/module.h>
> +#include <linux/ulpi/driver.h>
> +#include <linux/ulpi/regs.h>
> +#include <linux/clk.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/of_device.h>
> +#include <linux/reset.h>
> +#include <linux/extcon.h>
> +#include <linux/notifier.h>
> +#include <linux/usb/of.h>
> +
> +#include "ulpi_phy.h"
> +
> +#define ULPI_PWR_CLK_MNG_REG		0x88
> +# define ULPI_PWR_OTG_COMP_DISABLE	BIT(0)
> +
> +#define ULPI_MISC_A			0x96
> +# define ULPI_MISC_A_VBUSVLDEXTSEL	BIT(1)
> +# define ULPI_MISC_A_VBUSVLDEXT		BIT(0)
> +
> +
> +struct ulpi_seq {
> +	u8 addr;
> +	u8 val;
> +};
> +
> +struct qcom_usb_hs_phy {
> +	struct ulpi *ulpi;
> +	struct phy *phy;
> +	struct clk *ref_clk;
> +	struct clk *sleep_clk;
> +	struct regulator *v1p8;
> +	struct regulator *v3p3;
> +	struct reset_control *reset;
> +	struct ulpi_seq *init_seq;
> +	struct notifier_block vbus_notify;
> +	struct extcon_dev *vbus_edev;
> +	struct extcon_dev *id_edev;
> +	enum usb_dr_mode dr_mode;
> +};
> +
> +static int
> +qcom_usb_hs_phy_vbus_notifier(struct notifier_block *nb, unsigned long event,
> +			      void *ptr)
> +{
> +	struct qcom_usb_hs_phy *uphy;
> +	int is_host;
> +	u8 addr;
> +
> +	uphy = container_of(nb, struct qcom_usb_hs_phy, vbus_notify);
> +	is_host = extcon_get_cable_state_(uphy->id_edev, EXTCON_USB_HOST);
> +	if (is_host < 0)
> +		is_host = 0; /* No id event means always a peripheral */
> +
> +	if (event && !is_host)
> +		addr = ULPI_SET(ULPI_MISC_A);
> +	else
> +		addr = ULPI_CLR(ULPI_MISC_A);
> +
> +	return ulpi_write(uphy->ulpi, addr,
> +			  ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT);
> +}
> +
> +static int qcom_usb_hs_phy_power_on(struct phy *phy)
> +{
> +	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
> +	struct ulpi *ulpi = uphy->ulpi;
> +	const struct ulpi_seq *seq;
> +	int ret, state;
> +
> +	ret = clk_prepare_enable(uphy->ref_clk);
> +	if (ret)
> +		return ret;
> +
> +	ret = clk_prepare_enable(uphy->sleep_clk);
> +	if (ret)
> +		goto err_sleep;
> +
> +	ret = regulator_set_voltage(uphy->v1p8, 1800000, 1800000);
> +	if (ret)
> +		goto err_1p8;
> +
> +	ret = regulator_set_load(uphy->v1p8, 50000);
> +	if (ret < 0)
> +		goto err_1p8;
> +
> +	ret = regulator_enable(uphy->v1p8);
> +	if (ret)
> +		goto err_1p8;
> +
> +	ret = regulator_set_voltage_triplet(uphy->v3p3, 3050000, 3300000,
> +					    3300000);
> +	if (ret)
> +		goto err_3p3;
> +
> +	ret = regulator_set_load(uphy->v3p3, 50000);
> +	if (ret < 0)
> +		goto err_3p3;
> +
> +	ret = regulator_enable(uphy->v3p3);
> +	if (ret)
> +		goto err_3p3;
> +
> +	for (seq = uphy->init_seq; seq->addr; seq++) {
> +		ret = ulpi_write(ulpi, seq->addr, seq->val);
> +		if (ret)
> +			goto err_ulpi;
> +	}
> +
> +	if (uphy->reset) {
> +		ret = reset_control_reset(uphy->reset);
> +		if (ret)
> +			goto err_ulpi;
> +	}
> +
> +	if (uphy->vbus_edev) {
> +		ulpi_write(ulpi, ULPI_SET(ULPI_PWR_CLK_MNG_REG),
> +			   ULPI_PWR_OTG_COMP_DISABLE);
> +		state = extcon_get_cable_state_(uphy->vbus_edev, EXTCON_USB);
> +		/* setup initial state */
> +		qcom_usb_hs_phy_vbus_notifier(&uphy->vbus_notify, state,
> +					      uphy->vbus_edev);
> +		ret = extcon_register_notifier(uphy->vbus_edev, EXTCON_USB,
> +						&uphy->vbus_notify);
> +		if (ret)
> +			return ret;
> +	} else {
> +		u8 val;
> +
> +		switch (uphy->dr_mode) {
> +		case USB_DR_MODE_OTG:
> +			val = ULPI_INT_IDGRD;
> +		case USB_DR_MODE_PERIPHERAL:
> +			val |= ULPI_INT_SESS_VALID;
> +			break;
> +		default:
> +			val = 0;
> +		}
> +
> +		ret = ulpi_write(ulpi, ULPI_USB_INT_EN_RISE, val);
> +		if (ret)
> +			goto err_ulpi;
> +		ret = ulpi_write(ulpi, ULPI_USB_INT_EN_FALL, val);
> +		if (ret)
> +			goto err_ulpi;
> +	}
> +
> +	return 0;
> +err_ulpi:
> +	regulator_disable(uphy->v3p3);
> +err_3p3:
> +	regulator_disable(uphy->v1p8);
> +err_1p8:
> +	clk_disable_unprepare(uphy->sleep_clk);
> +err_sleep:
> +	clk_disable_unprepare(uphy->ref_clk);
> +	return ret;
> +}
> +
> +static int qcom_usb_hs_phy_power_off(struct phy *phy)
> +{
> +	int ret;
> +	struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy);
> +
> +	if (uphy->vbus_edev) {
> +		ret = extcon_unregister_notifier(uphy->vbus_edev, EXTCON_USB,
> +						 &uphy->vbus_notify);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	regulator_disable(uphy->v3p3);
> +	regulator_disable(uphy->v1p8);
> +	clk_disable_unprepare(uphy->sleep_clk);
> +	clk_disable_unprepare(uphy->ref_clk);
> +
> +	return 0;
> +}
> +
> +static const struct phy_ops qcom_usb_hs_phy_ops = {
> +	.power_on = qcom_usb_hs_phy_power_on,
> +	.power_off = qcom_usb_hs_phy_power_off,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int qcom_usb_hs_phy_probe(struct ulpi *ulpi)
> +{
> +	struct qcom_usb_hs_phy *uphy;
> +	struct phy_provider *p;
> +	struct clk *clk;
> +	struct regulator *reg;
> +	struct reset_control *reset;
> +	int size;
> +	int ret;
> +
> +	uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);
> +	if (!uphy)
> +		return -ENOMEM;
> +	ulpi_set_drvdata(ulpi, uphy);
> +	uphy->ulpi = ulpi;
> +	uphy->dr_mode = of_usb_get_dr_mode_by_phy(ulpi->dev.of_node, -1);
> +
> +	size = of_property_count_u8_elems(ulpi->dev.of_node, "qcom,init-seq");
> +	if (size < 0)
> +		size = 0;
> +	uphy->init_seq = devm_kmalloc_array(&ulpi->dev, (size / 2) + 1,
> +					   sizeof(*uphy->init_seq), GFP_KERNEL);
> +	if (!uphy->init_seq)
> +		return -ENOMEM;
> +	ret = of_property_read_u8_array(ulpi->dev.of_node, "qcom,init-seq",
> +					(u8 *)uphy->init_seq, size);
> +	if (ret && size)
> +		return ret;
> +	/* NUL terminate */
> +	uphy->init_seq[size / 2].addr = uphy->init_seq[size / 2].val = 0;
> +
> +	uphy->ref_clk = clk = devm_clk_get(&ulpi->dev, "ref");
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +
> +	uphy->sleep_clk = clk = devm_clk_get(&ulpi->dev, "sleep");
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +
> +	uphy->v1p8 = reg = devm_regulator_get(&ulpi->dev, "v1p8");
> +	if (IS_ERR(reg))
> +		return PTR_ERR(reg);
> +
> +	uphy->v3p3 = reg = devm_regulator_get(&ulpi->dev, "v3p3");
> +	if (IS_ERR(reg))
> +		return PTR_ERR(reg);
> +
> +	uphy->reset = reset = devm_reset_control_get(&ulpi->dev, "por");
> +	if (IS_ERR(reset)) {
> +		if (PTR_ERR(reset) == -EPROBE_DEFER)
> +			return PTR_ERR(reset);
> +		uphy->reset = NULL;
> +	}
> +
> +	uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,
> +				    &qcom_usb_hs_phy_ops);
> +	if (IS_ERR(uphy->phy))
> +		return PTR_ERR(uphy->phy);
> +
> +	uphy->vbus_edev = extcon_get_edev_by_phandle(&ulpi->dev, 0);
> +	if (IS_ERR(uphy->vbus_edev)) {
> +		if (PTR_ERR(uphy->vbus_edev) != -ENODEV)
> +			return PTR_ERR(uphy->vbus_edev);
> +		uphy->vbus_edev = NULL;
> +	}
> +
> +	uphy->id_edev = extcon_get_edev_by_phandle(&ulpi->dev, 1);
> +	if (IS_ERR(uphy->id_edev)) {
> +		if (PTR_ERR(uphy->id_edev) != -ENODEV)
> +			return PTR_ERR(uphy->id_edev);
> +		uphy->id_edev = NULL;
> +	}
> +
> +	uphy->vbus_notify.notifier_call = qcom_usb_hs_phy_vbus_notifier;
> +	phy_set_drvdata(uphy->phy, uphy);
> +
> +	p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate);
> +	return PTR_ERR_OR_ZERO(p);
> +}
> +
> +static const struct of_device_id qcom_usb_hs_phy_match[] = {
> +	{ .compatible = "qcom,usb-hs-phy", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, qcom_usb_hs_phy_match);
> +
> +static struct ulpi_driver qcom_usb_hs_phy_driver = {
> +	.probe = qcom_usb_hs_phy_probe,
> +	.driver = {
> +		.name = "qcom_usb_hs_phy",
> +		.of_match_table = qcom_usb_hs_phy_match,
> +	},
> +};
> +module_ulpi_driver(qcom_usb_hs_phy_driver);
> +
> +MODULE_DESCRIPTION("Qualcomm USB HS phy");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.9.0.rc2.8.ga28705d
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-usb" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 

Best Regards,
Peter Chen



More information about the linux-arm-kernel mailing list