Re: [PATCH v4 22/22] phy: Add support for Qualcomm's USB HS phy
From: Kishon Vijay Abraham I
Date: Wed Sep 14 2016 - 01:30:40 EST
On Saturday 10 September 2016 05:48 PM, Kishon Vijay Abraham I wrote:
>
> 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.
>>
>> Cc: Kishon Vijay Abraham I <kishon@xxxxxx>
>> Cc: <devicetree@xxxxxxxxxxxxxxx>
>> Signed-off-by: Stephen Boyd <stephen.boyd@xxxxxxxxxx>
>
> merged this and the previous patch to linux-phy.
since there are pending discussions, I'll drop this patch for now.
Thanks
Kishon
>
> Thanks
> Kishon
>
>> ---
>> .../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
>>