1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2018 John Crispin <john@phrozen.org> |
4 | * |
5 | * Based on code from |
6 | * Allwinner Technology Co., Ltd. <www.allwinnertech.com> |
7 | * |
8 | */ |
9 | |
10 | #include <linux/delay.h> |
11 | #include <linux/err.h> |
12 | #include <linux/io.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/of.h> |
17 | #include <linux/phy/phy.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/reset.h> |
20 | |
21 | struct ipq4019_usb_phy { |
22 | struct device *dev; |
23 | struct phy *phy; |
24 | void __iomem *base; |
25 | struct reset_control *por_rst; |
26 | struct reset_control *srif_rst; |
27 | }; |
28 | |
29 | static int ipq4019_ss_phy_power_off(struct phy *_phy) |
30 | { |
31 | struct ipq4019_usb_phy *phy = phy_get_drvdata(phy: _phy); |
32 | |
33 | reset_control_assert(rstc: phy->por_rst); |
34 | msleep(msecs: 10); |
35 | |
36 | return 0; |
37 | } |
38 | |
39 | static int ipq4019_ss_phy_power_on(struct phy *_phy) |
40 | { |
41 | struct ipq4019_usb_phy *phy = phy_get_drvdata(phy: _phy); |
42 | |
43 | ipq4019_ss_phy_power_off(_phy); |
44 | |
45 | reset_control_deassert(rstc: phy->por_rst); |
46 | |
47 | return 0; |
48 | } |
49 | |
50 | static const struct phy_ops ipq4019_usb_ss_phy_ops = { |
51 | .power_on = ipq4019_ss_phy_power_on, |
52 | .power_off = ipq4019_ss_phy_power_off, |
53 | }; |
54 | |
55 | static int ipq4019_hs_phy_power_off(struct phy *_phy) |
56 | { |
57 | struct ipq4019_usb_phy *phy = phy_get_drvdata(phy: _phy); |
58 | |
59 | reset_control_assert(rstc: phy->por_rst); |
60 | msleep(msecs: 10); |
61 | |
62 | reset_control_assert(rstc: phy->srif_rst); |
63 | msleep(msecs: 10); |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | static int ipq4019_hs_phy_power_on(struct phy *_phy) |
69 | { |
70 | struct ipq4019_usb_phy *phy = phy_get_drvdata(phy: _phy); |
71 | |
72 | ipq4019_hs_phy_power_off(_phy); |
73 | |
74 | reset_control_deassert(rstc: phy->srif_rst); |
75 | msleep(msecs: 10); |
76 | |
77 | reset_control_deassert(rstc: phy->por_rst); |
78 | |
79 | return 0; |
80 | } |
81 | |
82 | static const struct phy_ops ipq4019_usb_hs_phy_ops = { |
83 | .power_on = ipq4019_hs_phy_power_on, |
84 | .power_off = ipq4019_hs_phy_power_off, |
85 | }; |
86 | |
87 | static const struct of_device_id ipq4019_usb_phy_of_match[] = { |
88 | { .compatible = "qcom,usb-hs-ipq4019-phy" , .data = &ipq4019_usb_hs_phy_ops}, |
89 | { .compatible = "qcom,usb-ss-ipq4019-phy" , .data = &ipq4019_usb_ss_phy_ops}, |
90 | { }, |
91 | }; |
92 | MODULE_DEVICE_TABLE(of, ipq4019_usb_phy_of_match); |
93 | |
94 | static int ipq4019_usb_phy_probe(struct platform_device *pdev) |
95 | { |
96 | struct device *dev = &pdev->dev; |
97 | struct phy_provider *phy_provider; |
98 | struct ipq4019_usb_phy *phy; |
99 | |
100 | phy = devm_kzalloc(dev, size: sizeof(*phy), GFP_KERNEL); |
101 | if (!phy) |
102 | return -ENOMEM; |
103 | |
104 | phy->dev = &pdev->dev; |
105 | phy->base = devm_platform_ioremap_resource(pdev, index: 0); |
106 | if (IS_ERR(ptr: phy->base)) { |
107 | dev_err(dev, "failed to remap register memory\n" ); |
108 | return PTR_ERR(ptr: phy->base); |
109 | } |
110 | |
111 | phy->por_rst = devm_reset_control_get(dev: phy->dev, id: "por_rst" ); |
112 | if (IS_ERR(ptr: phy->por_rst)) { |
113 | if (PTR_ERR(ptr: phy->por_rst) != -EPROBE_DEFER) |
114 | dev_err(dev, "POR reset is missing\n" ); |
115 | return PTR_ERR(ptr: phy->por_rst); |
116 | } |
117 | |
118 | phy->srif_rst = devm_reset_control_get_optional(dev: phy->dev, id: "srif_rst" ); |
119 | if (IS_ERR(ptr: phy->srif_rst)) |
120 | return PTR_ERR(ptr: phy->srif_rst); |
121 | |
122 | phy->phy = devm_phy_create(dev, NULL, ops: of_device_get_match_data(dev)); |
123 | if (IS_ERR(ptr: phy->phy)) { |
124 | dev_err(dev, "failed to create PHY\n" ); |
125 | return PTR_ERR(ptr: phy->phy); |
126 | } |
127 | phy_set_drvdata(phy: phy->phy, data: phy); |
128 | |
129 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
130 | |
131 | return PTR_ERR_OR_ZERO(ptr: phy_provider); |
132 | } |
133 | |
134 | static struct platform_driver ipq4019_usb_phy_driver = { |
135 | .probe = ipq4019_usb_phy_probe, |
136 | .driver = { |
137 | .of_match_table = ipq4019_usb_phy_of_match, |
138 | .name = "ipq4019-usb-phy" , |
139 | } |
140 | }; |
141 | module_platform_driver(ipq4019_usb_phy_driver); |
142 | |
143 | MODULE_DESCRIPTION("QCOM/IPQ4019 USB phy driver" ); |
144 | MODULE_AUTHOR("John Crispin <john@phrozen.org>" ); |
145 | MODULE_LICENSE("GPL v2" ); |
146 | |