diff mbox

[20/21] phy: Add support for Qualcomm's USB HSIC phy

Message ID 20160626072838.28082-21-stephen.boyd@linaro.org (mailing list archive)
State New, archived
Headers show

Commit Message

Stephen Boyd June 26, 2016, 7:28 a.m. UTC
The HSIC USB controller on qcom SoCs has an integrated all
digital phy controlled via the ULPI viewport.

Cc: Kishon Vijay Abraham I <kishon@ti.com>
Cc: <devicetree@vger.kernel.org>
Signed-off-by: Stephen Boyd <stephen.boyd@linaro.org>
---
 .../devicetree/bindings/phy/qcom,usb-hsic-phy.txt  |  60 ++++++++
 drivers/phy/Kconfig                                |   7 +
 drivers/phy/Makefile                               |   1 +
 drivers/phy/phy-qcom-usb-hsic.c                    | 161 +++++++++++++++++++++
 4 files changed, 229 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/qcom,usb-hsic-phy.txt
 create mode 100644 drivers/phy/phy-qcom-usb-hsic.c

Comments

Neil Armstrong June 28, 2016, 8:49 a.m. UTC | #1
On 06/26/2016 09:28 AM, Stephen Boyd wrote:
> The HSIC USB controller on qcom SoCs has an integrated all
> digital phy controlled via the ULPI viewport.
> 
> Cc: Kishon Vijay Abraham I <kishon@ti.com>
> Cc: <devicetree@vger.kernel.org>
> Signed-off-by: Stephen Boyd <stephen.boyd@linaro.org>
> ---
>  .../devicetree/bindings/phy/qcom,usb-hsic-phy.txt  |  60 ++++++++
>  drivers/phy/Kconfig                                |   7 +
>  drivers/phy/Makefile                               |   1 +
>  drivers/phy/phy-qcom-usb-hsic.c                    | 161 +++++++++++++++++++++
>  4 files changed, 229 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/phy/qcom,usb-hsic-phy.txt
>  create mode 100644 drivers/phy/phy-qcom-usb-hsic.c
> 
> diff --git a/Documentation/devicetree/bindings/phy/qcom,usb-hsic-phy.txt b/Documentation/devicetree/bindings/phy/qcom,usb-hsic-phy.txt
> new file mode 100644
> index 000000000000..6b1c6aad2962
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/phy/qcom,usb-hsic-phy.txt
> @@ -0,0 +1,60 @@
> +Qualcomm's USB HSIC PHY
> +
> +PROPERTIES
> +
> +- compatible:
> +    Usage: required
> +    Value type: <string>
> +    Definition: Should contain "qcom,usb-hsic-phy"
> +
> +- #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 phy, calibration and
> +                optionally a calibration sleep clock
> +
> +- clock-names:
> +    Usage: required
> +    Value type: <stringlist>
> +    Definition: Should contain "phy, "cal" and optionally "cal_sleep"
> +

[...]

> +
> +static int qcom_usb_hsic_phy_power_on(struct phy *phy)
> +{
> +	struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy);
> +	struct ulpi *ulpi = uphy->ulpi;
> +	struct pinctrl_state *pins_default;
> +	int ret;
> +
> +	ret = clk_prepare_enable(uphy->phy_clk);
> +	if (ret)
> +		return ret;
> +
> +	ret = clk_prepare_enable(uphy->cal_clk);
> +	if (ret)
> +		goto err_cal;
> +
> +	ret = clk_prepare_enable(uphy->cal_sleep_clk);
> +	if (ret)
> +		goto err_sleep;
> +

[...]

> +
> +	return ret;
> +err_ulpi:
> +	clk_disable_unprepare(uphy->cal_sleep_clk);
> +err_sleep:
> +	clk_disable_unprepare(uphy->cal_clk);
> +err_cal:
> +	clk_disable_unprepare(uphy->phy_clk);
> +	return ret;
> +}
> +
> +static int qcom_usb_hsic_phy_power_off(struct phy *phy)
> +{
> +	struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy);
> +
> +	clk_disable_unprepare(uphy->cal_sleep_clk);
> +	clk_disable_unprepare(uphy->cal_clk);
> +	clk_disable_unprepare(uphy->phy_clk);

[...]

> +static int qcom_usb_hsic_phy_probe(struct ulpi *ulpi)
> +{
> +	struct qcom_usb_hsic_phy *uphy;
> +	struct phy_provider *p;
> +	struct clk *clk;
> +
> +	uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);
> +	if (!uphy)
> +		return -ENOMEM;
> +	ulpi_set_drvdata(ulpi, uphy);
> +
> +	uphy->ulpi = ulpi;
> +	uphy->pctl = devm_pinctrl_get(&ulpi->dev);
> +	if (IS_ERR(uphy->pctl))
> +		return PTR_ERR(uphy->pctl);
> +
> +	uphy->phy_clk = clk = devm_clk_get(&ulpi->dev, "phy");
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +
> +	uphy->cal_clk = clk = devm_clk_get(&ulpi->dev, "cal");
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +
> +	uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep");
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);

Hi Stephen,

In the bindings the cal_sleep is marked optional, and I think should be since AFAIK
it's not present on MDM9615 for example.

Also MDM9615 HSIC requires "core", "alt-core", "phy", "cal" and "iface" clocks.
I assume "core" can be attributed to the main chipidea node, but I think "alt-core" and "iface" should be also optionnal.

Finally, it misses an optional reset line AFAIK mandatory on MDM9615.

Neil

> +
> +	uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,
> +				    &qcom_usb_hsic_phy_ops);
> +	if (IS_ERR(uphy->phy))
> +		return PTR_ERR(uphy->phy);
> +	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_hsic_phy_match[] = {
> +	{ .compatible = "qcom,usb-hsic-phy", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, qcom_usb_hsic_phy_match);
> +
> +static struct ulpi_driver qcom_usb_hsic_phy_driver = {
> +	.probe = qcom_usb_hsic_phy_probe,
> +	.driver = {
> +		.name = "qcom_usb_hsic_phy",
> +		.of_match_table = qcom_usb_hsic_phy_match
> +	},
> +};
> +module_ulpi_driver(qcom_usb_hsic_phy_driver);
> +
> +MODULE_DESCRIPTION("Qualcomm USB HSIC phy");
> +MODULE_LICENSE("GPL v2");
>
Stephen Boyd June 28, 2016, 9:58 p.m. UTC | #2
Quoting Neil Armstrong (2016-06-28 01:49:37)
> On 06/26/2016 09:28 AM, Stephen Boyd wrote:
> > +     uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep");
> > +     if (IS_ERR(clk))
> > +             return PTR_ERR(clk);
> 
> Hi Stephen,
> 
> In the bindings the cal_sleep is marked optional, and I think should be since AFAIK
> it's not present on MDM9615 for example.

The cal_sleep clk is just the sleep clk then (should be a board clk in
DT). Sometimes there's a gate in GCC to allow us to turn it off, other
times there isn't. Either way, it's always wired there so I'll update
the binding to say it isn't optional.

> 
> Also MDM9615 HSIC requires "core", "alt-core", "phy", "cal" and "iface" clocks.
> I assume "core" can be attributed to the main chipidea node, but I think "alt-core" and "iface" should be also optionnal.

Looking at the downstream sources I see this:

        "core_clk"     -> usb_hsic_sys_clk
        "iface_clk"    -> usb_hsic_p_clk
        "alt_core_clk" -> usb_hsic_xcvr_clk

        "cal_clk"      -> usb_hsic_hsio_cal_clk
        "phy_clk"      -> usb_hsic_clk

"core_clk" would be the core clk in ci_hdrc_msm. "iface_clk" would be
the iface clk in ci_hdrc_msm. "cal_clk" would be the cal clk in the hsic
phy and "phy_clk" would be the phy clk in the hsic phy.

That leaves alt_core_clk which seems to be a clock that needs to be on
during the reset assert/deassert and possibly for LPM and USB1.1 FS
modes. Sometimes it's referred to as the "housekeeping" clk. Due to the
way resets are done on msm8974 and later SoCs it looks like this clk was
removed. I can make this an optional clk in the ci_hdrc_msm driver, or
we can have two versions of the ci_hdrc_msm compatible string, one for a
device that has the housekeeping clk and one for the device that
doesn't.

> 
> Finally, it misses an optional reset line AFAIK mandatory on MDM9615.
> 

From what I can tell downstream, all those clks point to the same bit 0
of HSIC_RESET register? So there isn't any phy reset, just the chipidea
controller wrapper reset bit, which should go into the wrapper node?
Neil Armstrong June 29, 2016, 9:16 a.m. UTC | #3
On 06/28/2016 11:58 PM, Stephen Boyd wrote:
> Quoting Neil Armstrong (2016-06-28 01:49:37)
>> On 06/26/2016 09:28 AM, Stephen Boyd wrote:
>>> +     uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep");
>>> +     if (IS_ERR(clk))
>>> +             return PTR_ERR(clk);
>>
>> Hi Stephen,
>>
>> In the bindings the cal_sleep is marked optional, and I think should be since AFAIK
>> it's not present on MDM9615 for example.
> 
> The cal_sleep clk is just the sleep clk then (should be a board clk in
> DT). Sometimes there's a gate in GCC to allow us to turn it off, other
> times there isn't. Either way, it's always wired there so I'll update
> the binding to say it isn't optional.

Sorry I don't understand !
What should I do if GCC does not provide a gate here ? And looking at the driver, it could be optional.

> 
>>
>> Also MDM9615 HSIC requires "core", "alt-core", "phy", "cal" and "iface" clocks.
>> I assume "core" can be attributed to the main chipidea node, but I think "alt-core" and "iface" should be also optionnal.
> 
> Looking at the downstream sources I see this:
> 
>         "core_clk"     -> usb_hsic_sys_clk
>         "iface_clk"    -> usb_hsic_p_clk
>         "alt_core_clk" -> usb_hsic_xcvr_clk
> 
>         "cal_clk"      -> usb_hsic_hsio_cal_clk
>         "phy_clk"      -> usb_hsic_clk
> 
> "core_clk" would be the core clk in ci_hdrc_msm. "iface_clk" would be
> the iface clk in ci_hdrc_msm. "cal_clk" would be the cal clk in the hsic
> phy and "phy_clk" would be the phy clk in the hsic phy.
> 
> That leaves alt_core_clk which seems to be a clock that needs to be on
> during the reset assert/deassert and possibly for LPM and USB1.1 FS
> modes. Sometimes it's referred to as the "housekeeping" clk. Due to the
> way resets are done on msm8974 and later SoCs it looks like this clk was
> removed. I can make this an optional clk in the ci_hdrc_msm driver, or
> we can have two versions of the ci_hdrc_msm compatible string, one for a
> device that has the housekeeping clk and one for the device that
> doesn't.

Having it optional would be the best solution I think.

> 
>>
>> Finally, it misses an optional reset line AFAIK mandatory on MDM9615.
>>
> 
> From what I can tell downstream, all those clks point to the same bit 0
> of HSIC_RESET register? So there isn't any phy reset, just the chipidea
> controller wrapper reset bit, which should go into the wrapper node?

Ok, makes sense.
Stephen Boyd June 29, 2016, 6:54 p.m. UTC | #4
Quoting Neil Armstrong (2016-06-29 02:16:51)
> On 06/28/2016 11:58 PM, Stephen Boyd wrote:
> > Quoting Neil Armstrong (2016-06-28 01:49:37)
> >> On 06/26/2016 09:28 AM, Stephen Boyd wrote:
> >>> +     uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep");
> >>> +     if (IS_ERR(clk))
> >>> +             return PTR_ERR(clk);
> >>
> >> Hi Stephen,
> >>
> >> In the bindings the cal_sleep is marked optional, and I think should be since AFAIK
> >> it's not present on MDM9615 for example.
> > 
> > The cal_sleep clk is just the sleep clk then (should be a board clk in
> > DT). Sometimes there's a gate in GCC to allow us to turn it off, other
> > times there isn't. Either way, it's always wired there so I'll update
> > the binding to say it isn't optional.
> 
> Sorry I don't understand !
> What should I do if GCC does not provide a gate here ? And looking at the driver, it could be optional.

You should set the property to point to &sleep_clk which should be under
the "clocks" node at the root of the OF tree. For example, see the
sleep_clk node in arch/arm/boot/dts/qcom-apq8064.dtsi.
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/phy/qcom,usb-hsic-phy.txt b/Documentation/devicetree/bindings/phy/qcom,usb-hsic-phy.txt
new file mode 100644
index 000000000000..6b1c6aad2962
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/qcom,usb-hsic-phy.txt
@@ -0,0 +1,60 @@ 
+Qualcomm's USB HSIC PHY
+
+PROPERTIES
+
+- compatible:
+    Usage: required
+    Value type: <string>
+    Definition: Should contain "qcom,usb-hsic-phy"
+
+- #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 phy, calibration and
+                optionally a calibration sleep clock
+
+- clock-names:
+    Usage: required
+    Value type: <stringlist>
+    Definition: Should contain "phy, "cal" and optionally "cal_sleep"
+
+- pinctrl-names:
+    Usage: required
+    Value type: <stringlist>
+    Definition: Should contain "init" and "default" in that order
+
+- pinctrl-0:
+    Usage: required
+    Value type: <prop-encoded-array>
+    Definition: List of pinctrl settings to apply to keep HSIC pins in a glitch
+                free state
+
+- pinctrl-1:
+    Usage: required
+    Value type: <prop-encoded-array>
+    Definition: List of pinctrl settings to apply to mux out the HSIC pins
+
+EXAMPLE
+
+usb-controller {
+	ulpi {
+		phy {
+			compatible = "qcom,usb-hsic-phy";
+			#phy-cells = <0>;
+			pinctrl-names = "init", "default";
+			pinctrl-0 = <&hsic_sleep>;
+			pinctrl-1 = <&hsic_default>;
+			clocks = <&gcc GCC_USB_HSIC_CLK>,
+				 <&gcc GCC_USB_HSIC_IO_CAL_CLK>,
+				 <&gcc GCC_USB_HSIC_IO_CAL_SLEEP_CLK>;
+			clock-names = "phy", "cal", "cal_sleep";
+			assigned-clocks = <&gcc GCC_USB_HSIC_IO_CAL_CLK>;
+			assigned-clock-rates = <960000>;
+		};
+	};
+};
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index b869b98835f4..a2866949dc97 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -406,6 +406,13 @@  config PHY_QCOM_UFS
 	help
 	  Support for UFS PHY on QCOM chipsets.
 
+config PHY_QCOM_USB_HSIC
+	tristate "Qualcomm USB HSIC ULPI PHY module"
+	depends on USB_ULPI_BUS
+	select GENERIC_PHY
+	help
+	  Support for the USB HSIC ULPI compliant PHY on QCOM chipsets.
+
 config PHY_TUSB1210
 	tristate "TI TUSB1210 ULPI PHY module"
 	depends on USB_ULPI_BUS
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 9c3e73ccabc4..982e84a290ec 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -49,6 +49,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_HSIC) 	+= phy-qcom-usb-hsic.o
 obj-$(CONFIG_PHY_TUSB1210)		+= phy-tusb1210.o
 obj-$(CONFIG_PHY_BRCM_SATA)		+= phy-brcm-sata.o
 obj-$(CONFIG_PHY_PISTACHIO_USB)		+= phy-pistachio-usb.o
diff --git a/drivers/phy/phy-qcom-usb-hsic.c b/drivers/phy/phy-qcom-usb-hsic.c
new file mode 100644
index 000000000000..a81b2f8bfe37
--- /dev/null
+++ b/drivers/phy/phy-qcom-usb-hsic.c
@@ -0,0 +1,161 @@ 
+/**
+ * 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/pinctrl/consumer.h>
+#include <linux/pinctrl/pinctrl-state.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include "ulpi_phy.h"
+
+#define ULPI_HSIC_CFG		0x30
+#define ULPI_HSIC_IO_CAL	0x33
+
+struct qcom_usb_hsic_phy {
+	struct ulpi *ulpi;
+	struct phy *phy;
+	struct pinctrl *pctl;
+	struct clk *phy_clk;
+	struct clk *cal_clk;
+	struct clk *cal_sleep_clk;
+};
+
+static int qcom_usb_hsic_phy_power_on(struct phy *phy)
+{
+	struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy);
+	struct ulpi *ulpi = uphy->ulpi;
+	struct pinctrl_state *pins_default;
+	int ret;
+
+	ret = clk_prepare_enable(uphy->phy_clk);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(uphy->cal_clk);
+	if (ret)
+		goto err_cal;
+
+	ret = clk_prepare_enable(uphy->cal_sleep_clk);
+	if (ret)
+		goto err_sleep;
+
+	/* Set periodic calibration interval to ~2.048sec in HSIC_IO_CAL_REG */
+	ret = ulpi_write(ulpi, ULPI_HSIC_IO_CAL, 0xff);
+	if (ret)
+		goto err_ulpi;
+
+	/* Enable periodic IO calibration in HSIC_CFG register */
+	ret = ulpi_write(ulpi, ULPI_HSIC_CFG, 0xa8);
+	if (ret)
+		goto err_ulpi;
+
+	/* Configure pins for HSIC functionality */
+	pins_default = pinctrl_lookup_state(uphy->pctl, PINCTRL_STATE_DEFAULT);
+	if (IS_ERR(pins_default))
+		return PTR_ERR(pins_default);
+
+	ret = pinctrl_select_state(uphy->pctl, pins_default);
+	if (ret)
+		goto err_ulpi;
+
+	 /* Enable HSIC mode in HSIC_CFG register */
+	ret = ulpi_write(ulpi, ULPI_SET(ULPI_HSIC_CFG), 0x01);
+	if (ret)
+		goto err_ulpi;
+
+	/* Disable auto-resume */
+	ret = ulpi_write(ulpi, ULPI_CLR(ULPI_IFC_CTRL),
+			 ULPI_IFC_CTRL_AUTORESUME);
+	if (ret)
+		goto err_ulpi;
+
+	return ret;
+err_ulpi:
+	clk_disable_unprepare(uphy->cal_sleep_clk);
+err_sleep:
+	clk_disable_unprepare(uphy->cal_clk);
+err_cal:
+	clk_disable_unprepare(uphy->phy_clk);
+	return ret;
+}
+
+static int qcom_usb_hsic_phy_power_off(struct phy *phy)
+{
+	struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy);
+
+	clk_disable_unprepare(uphy->cal_sleep_clk);
+	clk_disable_unprepare(uphy->cal_clk);
+	clk_disable_unprepare(uphy->phy_clk);
+
+	return 0;
+}
+
+static const struct phy_ops qcom_usb_hsic_phy_ops = {
+	.power_on = qcom_usb_hsic_phy_power_on,
+	.power_off = qcom_usb_hsic_phy_power_off,
+	.owner = THIS_MODULE,
+};
+
+static int qcom_usb_hsic_phy_probe(struct ulpi *ulpi)
+{
+	struct qcom_usb_hsic_phy *uphy;
+	struct phy_provider *p;
+	struct clk *clk;
+
+	uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL);
+	if (!uphy)
+		return -ENOMEM;
+	ulpi_set_drvdata(ulpi, uphy);
+
+	uphy->ulpi = ulpi;
+	uphy->pctl = devm_pinctrl_get(&ulpi->dev);
+	if (IS_ERR(uphy->pctl))
+		return PTR_ERR(uphy->pctl);
+
+	uphy->phy_clk = clk = devm_clk_get(&ulpi->dev, "phy");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	uphy->cal_clk = clk = devm_clk_get(&ulpi->dev, "cal");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node,
+				    &qcom_usb_hsic_phy_ops);
+	if (IS_ERR(uphy->phy))
+		return PTR_ERR(uphy->phy);
+	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_hsic_phy_match[] = {
+	{ .compatible = "qcom,usb-hsic-phy", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, qcom_usb_hsic_phy_match);
+
+static struct ulpi_driver qcom_usb_hsic_phy_driver = {
+	.probe = qcom_usb_hsic_phy_probe,
+	.driver = {
+		.name = "qcom_usb_hsic_phy",
+		.of_match_table = qcom_usb_hsic_phy_match
+	},
+};
+module_ulpi_driver(qcom_usb_hsic_phy_driver);
+
+MODULE_DESCRIPTION("Qualcomm USB HSIC phy");
+MODULE_LICENSE("GPL v2");