All of lore.kernel.org
 help / color / mirror / Atom feed
From: l00229106 <lpc.li@hisilicon.com>
To: <kishon@ti.com>, <linux-kernel@vger.kernel.org>,
	<lidongpo@hisilicon.com>, <lpc.li@hisilicon.com>
Subject: [PATCH v2] phy: add phy-hisi-inno-usb2
Date: Sun, 3 Jul 2016 14:50:10 +0800	[thread overview]
Message-ID: <1467528610-129348-1-git-send-email-lpc.li@hisilicon.com> (raw)

Add support for inno usb2 phy integrated on some hisilicon SOCs.

Signed-off-by: Pengcheng Li <lpc.li@hisilicon.com>
---
 .../devicetree/bindings/phy/phy-hisi-inno-usb2.txt |  48 ++++
 drivers/phy/Kconfig                                |  10 +
 drivers/phy/Makefile                               |   1 +
 drivers/phy/phy-hisi-inno-usb2.c                   | 298 +++++++++++++++++++++
 4 files changed, 357 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt
 create mode 100644 drivers/phy/phy-hisi-inno-usb2.c

diff --git a/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt b/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt
new file mode 100644
index 0000000..59eaf73
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/phy-hisi-inno-usb2.txt
@@ -0,0 +1,48 @@
+HiSilicon INNO USB2 PHY
+-----------------------
+Required properties:
+- compatible: Should be "hisilicon,inno_usb2_phy"
+- #phy-cells: Must be 0
+- hisilicon,peripheral-syscon: Phandle of syscon used to control phy.
+- hisilicon,reg-num: Number of phy registers which should be configured
+at phy intialization stage
+- hisilicon,reg-seq: Sequence of triplets of (address, value, delay-us).
+The number of triplets is equal to "hisilicon,reg-num". Each triplet is
+used to write one phy register. The delay-us cell represents the delay
+time in microseconds to be applied after each write.
+- clocks: Phandle and clock specifier pair for reference clock utmi_refclk.
+- resets: List of phandle and reset specifier pairs for each reset signal in
+reset-names.
+- reset-names: Should be "por_rst" and "test_rst". The test_rst only
+exists in some of SOCs, so it is optional.
+
+Phy node can includes up to four subnodes. Each subnode represents one port.
+The required properties of port node are as follows:
+- clocks: Phandle and clock specifier pair for utmi_clock.
+- resets: List of phandle and reset specifier pairs for port reset and utmi reset.
+- reset-names: List of reset signal names. Should be "port_rst" and "utmi_rst"
+
+Refer to phy/phy-bindings.txt for the generic PHY binding properties
+
+Example:
+usb_phy: phy {
+		 compatible = "hisilicon,inno_usb2_phy";
+		 #phy-cells = <0>;
+		 hisilicon,peripheral-syscon = <&peri_ctrl>;
+		 hisilicon,reg-num = <7>;
+		 hisilicon,reg-seq = <0x80 0x800000 20>,
+			 <0x80 0xa0060c 200>,
+			 <0x80 0x80001c 20>,
+			 <0x80 0xa0001c 20>,
+			 <0x80 0x80060f 20>,
+			 <0x80 0xa0060f 20>,
+			 <0x80 0x800a4b 20>;
+		 clocks = <&crg USB2_REF_CLK>;
+		 resets = <&crg 0xb4 2>;
+		 reset-names = "por_rst";
+		 port0 {
+			 clocks = <&crg USB2_UTMI0_CLK>;
+			 resets = <&crg 0xb4 5>, <&crg 0xb4 1>;
+			 reset-names = "port_rst", "utmi_rst";
+		 };
+	 };
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 26566db..8f043ca 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -205,6 +205,16 @@ config PHY_EXYNOS5250_SATA
 	  SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host
 	  port to accept one SATA device.
 
+config PHY_HISI_INNO_USB2
+	tristate "Hisilicon Inno USB2 PHY support"
+	depends on (ARCH_HISI) || COMPILE_TEST
+	select GENERIC_PHY
+	select MFD_SYSCON
+	help
+	  Support for INNO PHY on Hisilicon Socs. This Phy supports
+	  USB 1.5Mb/s, USB 12Mb/s, USB 480Mb/s speeds. It suppots one
+	  USB host port to accept one USB device.
+
 config PHY_HIX5HD2_SATA
 	tristate "HIX5HD2 SATA PHY Driver"
 	depends on ARCH_HIX5HD2 && OF && HAS_IOMEM
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 24596a9..ef6a24b 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_TI_PIPE3)			+= phy-ti-pipe3.o
 obj-$(CONFIG_TWL4030_USB)		+= phy-twl4030-usb.o
 obj-$(CONFIG_PHY_EXYNOS5250_SATA)	+= phy-exynos5250-sata.o
 obj-$(CONFIG_PHY_HIX5HD2_SATA)		+= phy-hix5hd2-sata.o
+obj-$(CONFIG_PHY_HISI_INNO_USB2)	+= phy-hisi-inno-usb2.o
 obj-$(CONFIG_PHY_HI6220_USB)		+= phy-hi6220-usb.o
 obj-$(CONFIG_PHY_MT65XX_USB3)		+= phy-mt65xx-usb3.o
 obj-$(CONFIG_PHY_SUN4I_USB)		+= phy-sun4i-usb.o
diff --git a/drivers/phy/phy-hisi-inno-usb2.c b/drivers/phy/phy-hisi-inno-usb2.c
new file mode 100644
index 0000000..6930bb0
--- /dev/null
+++ b/drivers/phy/phy-hisi-inno-usb2.c
@@ -0,0 +1,298 @@
+ /*
+ * HiSilicon INNO USB2 PHY Driver.
+ *
+ * Copyright (c) 2016 HiSilicon Technologies Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define	MAX_PORTS	4
+
+struct  hisi_inno_phy_port {
+	struct clk *utmi_clk;
+	struct reset_control *port_rst;
+	struct reset_control *utmi_rst;
+};
+
+struct hisi_inno_phy_priv {
+	struct regmap *reg_peri;
+	struct clk *ref_clk;
+	struct reset_control *test_rst;
+	struct reset_control *por_rst;
+	struct reg_sequence *reg_seq;
+	u32	reg_num;
+	struct  hisi_inno_phy_port *ports;
+	u8	port_num;
+};
+
+static int hisi_inno_phy_setup(struct hisi_inno_phy_priv *priv)
+{
+	return regmap_multi_reg_write_bypassed(priv->reg_peri,
+				priv->reg_seq, priv->reg_num);
+}
+
+static int hisi_inno_port_init(struct hisi_inno_phy_port *port)
+{
+	int ret = 0;
+
+	reset_control_deassert(port->port_rst);
+	mdelay(2);
+
+	ret = clk_prepare_enable(port->utmi_clk);
+	if (ret)
+		return ret;
+	udelay(200);
+
+	reset_control_deassert(port->utmi_rst);
+	udelay(200);
+
+	return 0;
+}
+
+static int hisi_inno_phy_init(struct phy *phy)
+{
+	struct hisi_inno_phy_priv *priv = phy_get_drvdata(phy);
+	int ret, port;
+
+	ret = clk_prepare_enable(priv->ref_clk);
+	if (ret)
+		return ret;
+	udelay(100);
+
+	if (priv->test_rst) {
+		reset_control_deassert(priv->test_rst);
+		udelay(100);
+	}
+
+	reset_control_deassert(priv->por_rst);
+	udelay(300);
+
+	/* config phy clk and phy eye diagram */
+	ret = hisi_inno_phy_setup(priv);
+	if (ret)
+		goto err_disable_ref_clk;
+
+	for (port = 0; port < priv->port_num; port++) {
+		ret = hisi_inno_port_init(&priv->ports[port]);
+		if (ret)
+			goto err_disable_clks;
+	}
+
+	return 0;
+
+err_disable_clks:
+	while (--port >= 0)
+		clk_disable_unprepare(priv->ports[port].utmi_clk);
+err_disable_ref_clk:
+	clk_disable_unprepare(priv->ref_clk);
+
+	return ret;
+}
+
+static void hisi_inno_phy_disable(struct phy *phy)
+{
+	struct hisi_inno_phy_priv *priv = phy_get_drvdata(phy);
+	int i;
+
+	for (i = 0; i < priv->port_num; i++)
+		clk_disable_unprepare(priv->ports[i].utmi_clk);
+
+	clk_disable_unprepare(priv->ref_clk);
+}
+
+static int hisi_inno_phy_of_get_ports(struct device *dev,
+					struct  hisi_inno_phy_priv *priv)
+{
+	struct device_node *node = dev->of_node, *child;
+	int port = 0, ret = 0;
+
+	priv->port_num = of_get_child_count(node);
+	if (priv->port_num > MAX_PORTS) {
+		dev_err(dev, "too many ports : %d (max = %d)\n",
+				priv->port_num, MAX_PORTS);
+		return -EINVAL;
+	}
+
+	priv->ports = devm_kcalloc(dev, priv->port_num,
+				sizeof(struct hisi_inno_phy_port), GFP_KERNEL);
+	if (!priv->ports)
+		return -ENOMEM;
+
+	for_each_child_of_node(node, child) {
+		struct hisi_inno_phy_port *phy_port = &priv->ports[port];
+
+		phy_port->utmi_clk = of_clk_get(child, 0);
+		if (IS_ERR(phy_port->utmi_clk)) {
+			ret = PTR_ERR(phy_port->utmi_clk);
+			goto fail;
+		}
+
+		phy_port->port_rst = of_reset_control_get(child, "port_rst");
+		if (IS_ERR(phy_port->port_rst)) {
+			ret = PTR_ERR(phy_port->port_rst);
+			clk_put(phy_port->utmi_clk);
+			goto fail;
+		}
+
+		phy_port->utmi_rst = of_reset_control_get(child, "utmi_rst");
+		if (IS_ERR(phy_port->utmi_rst)) {
+			ret = PTR_ERR(phy_port->utmi_rst);
+			reset_control_put(phy_port->port_rst);
+			clk_put(phy_port->utmi_clk);
+			goto fail;
+		}
+		port++;
+	}
+
+	return ret;
+
+fail:
+	while (--port >= 0) {
+		struct hisi_inno_phy_port *phy_port = &priv->ports[port];
+
+		reset_control_put(phy_port->utmi_rst);
+		reset_control_put(phy_port->port_rst);
+		clk_put(phy_port->utmi_clk);
+	}
+	of_node_put(child);
+
+	return ret;
+}
+
+static struct phy_ops hisi_inno_phy_ops = {
+	.owner = THIS_MODULE,
+};
+
+static int hisi_inno_phy_probe(struct platform_device *pdev)
+{
+	struct phy_provider *phy_provider;
+	struct device *dev = &pdev->dev;
+	struct phy *phy;
+	struct hisi_inno_phy_priv *priv;
+	struct device_node *node = dev->of_node;
+	int ret = 0;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(node, "hisilicon,reg-num", &priv->reg_num);
+	if (ret) {
+		dev_err(dev, "can't read the phy registers number!\n");
+		return ret;
+	}
+
+	priv->reg_seq = devm_kcalloc(dev, priv->reg_num,
+				sizeof(struct reg_sequence), GFP_KERNEL);
+	if (!priv->reg_seq)
+		return -ENOMEM;
+
+	ret = of_property_read_u32_array(node, "hisilicon,reg-seq",
+			(u32 *)priv->reg_seq, 3 * priv->reg_num);
+	if (ret) {
+		dev_err(dev, "can't read the phy offset and value!\n");
+		return ret;
+	}
+
+	priv->reg_peri = syscon_regmap_lookup_by_phandle(node,
+			"hisilicon,peripheral-syscon");
+	if (IS_ERR(priv->reg_peri)) {
+		dev_err(dev, "no hisilicon,peripheral-syscon\n");
+		return PTR_ERR(priv->reg_peri);
+	}
+
+	priv->ref_clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(priv->ref_clk))
+		return PTR_ERR(priv->ref_clk);
+
+	priv->por_rst = devm_reset_control_get(dev, "por_rst");
+	if (IS_ERR(priv->por_rst))
+		return PTR_ERR(priv->por_rst);
+
+	priv->test_rst = devm_reset_control_get(dev, "test_rst");
+	if (IS_ERR(priv->test_rst))
+		priv->test_rst = NULL;
+
+	ret = hisi_inno_phy_of_get_ports(dev, priv);
+	if (ret)
+		return ret;
+
+	phy = devm_phy_create(dev, NULL, &hisi_inno_phy_ops);
+	if (IS_ERR(phy))
+		return PTR_ERR(phy);
+
+	platform_set_drvdata(pdev, phy);
+	phy_set_drvdata(phy, priv);
+	ret = hisi_inno_phy_init(phy);
+	if (ret)
+		return ret;
+
+	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int hisi_inno_phy_suspend(struct device *dev)
+{
+	struct phy *phy = dev_get_drvdata(dev);
+
+	hisi_inno_phy_disable(phy);
+
+	return 0;
+}
+
+static int hisi_inno_phy_resume(struct device *dev)
+{
+	struct phy *phy = dev_get_drvdata(dev);
+	int ret = 0;
+
+	ret = hisi_inno_phy_init(phy);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops hisi_inno_phy_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(hisi_inno_phy_suspend, hisi_inno_phy_resume)
+};
+
+static const struct of_device_id hisi_inno_phy_of_match[] = {
+	{.compatible = "hisilicon,inno_usb2_phy",},
+	{ },
+};
+MODULE_DEVICE_TABLE(of, hisi_inno_phy_of_match);
+
+static struct platform_driver hisi_inno_phy_driver = {
+	.probe	= hisi_inno_phy_probe,
+	.driver = {
+		.name	= "hisi-inno-phy",
+		.of_match_table	= hisi_inno_phy_of_match,
+		.pm    = &hisi_inno_phy_pm_ops,
+	}
+};
+module_platform_driver(hisi_inno_phy_driver);
+
+MODULE_DESCRIPTION("HiSilicon INNO USB2 PHY Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.8.2

             reply	other threads:[~2016-07-03  6:53 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-07-03  6:50 l00229106 [this message]
2016-07-04 10:58 ` [PATCH v2] phy: add phy-hisi-inno-usb2 Kishon Vijay Abraham I
2016-07-07  3:12   ` Lipengcheng
2016-07-07  3:53   ` Lipengcheng

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1467528610-129348-1-git-send-email-lpc.li@hisilicon.com \
    --to=lpc.li@hisilicon.com \
    --cc=kishon@ti.com \
    --cc=lidongpo@hisilicon.com \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.