diff options
Diffstat (limited to 'drivers/phy/phy-armada375-usb2.c')
-rw-r--r-- | drivers/phy/phy-armada375-usb2.c | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/drivers/phy/phy-armada375-usb2.c b/drivers/phy/phy-armada375-usb2.c new file mode 100644 index 000000000000..ac7d99d01cb3 --- /dev/null +++ b/drivers/phy/phy-armada375-usb2.c | |||
@@ -0,0 +1,158 @@ | |||
1 | /* | ||
2 | * USB cluster support for Armada 375 platform. | ||
3 | * | ||
4 | * Copyright (C) 2014 Marvell | ||
5 | * | ||
6 | * Gregory CLEMENT <gregory.clement@free-electrons.com> | ||
7 | * | ||
8 | * This file is licensed under the terms of the GNU General Public | ||
9 | * License version 2 or later. This program is licensed "as is" | ||
10 | * without any warranty of any kind, whether express or implied. | ||
11 | * | ||
12 | * Armada 375 comes with an USB2 host and device controller and an | ||
13 | * USB3 controller. The USB cluster control register allows to manage | ||
14 | * common features of both USB controllers. | ||
15 | */ | ||
16 | |||
17 | #include <dt-bindings/phy/phy.h> | ||
18 | #include <linux/init.h> | ||
19 | #include <linux/io.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/of_address.h> | ||
23 | #include <linux/phy/phy.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | |||
26 | #define USB2_PHY_CONFIG_DISABLE BIT(0) | ||
27 | |||
28 | struct armada375_cluster_phy { | ||
29 | struct phy *phy; | ||
30 | void __iomem *reg; | ||
31 | bool use_usb3; | ||
32 | int phy_provided; | ||
33 | }; | ||
34 | |||
35 | static int armada375_usb_phy_init(struct phy *phy) | ||
36 | { | ||
37 | struct armada375_cluster_phy *cluster_phy; | ||
38 | u32 reg; | ||
39 | |||
40 | cluster_phy = dev_get_drvdata(phy->dev.parent); | ||
41 | if (!cluster_phy) | ||
42 | return -ENODEV; | ||
43 | |||
44 | reg = readl(cluster_phy->reg); | ||
45 | if (cluster_phy->use_usb3) | ||
46 | reg |= USB2_PHY_CONFIG_DISABLE; | ||
47 | else | ||
48 | reg &= ~USB2_PHY_CONFIG_DISABLE; | ||
49 | writel(reg, cluster_phy->reg); | ||
50 | |||
51 | return 0; | ||
52 | } | ||
53 | |||
54 | static struct phy_ops armada375_usb_phy_ops = { | ||
55 | .init = armada375_usb_phy_init, | ||
56 | .owner = THIS_MODULE, | ||
57 | }; | ||
58 | |||
59 | /* | ||
60 | * Only one controller can use this PHY. We shouldn't have the case | ||
61 | * when two controllers want to use this PHY. But if this case occurs | ||
62 | * then we provide a phy to the first one and return an error for the | ||
63 | * next one. This error has also to be an error returned by | ||
64 | * devm_phy_optional_get() so different from ENODEV for USB2. In the | ||
65 | * USB3 case it still optional and we use ENODEV. | ||
66 | */ | ||
67 | static struct phy *armada375_usb_phy_xlate(struct device *dev, | ||
68 | struct of_phandle_args *args) | ||
69 | { | ||
70 | struct armada375_cluster_phy *cluster_phy = dev_get_drvdata(dev); | ||
71 | |||
72 | if (!cluster_phy) | ||
73 | return ERR_PTR(-ENODEV); | ||
74 | |||
75 | /* | ||
76 | * Either the phy had never been requested and then the first | ||
77 | * usb claiming it can get it, or it had already been | ||
78 | * requested in this case, we only allow to use it with the | ||
79 | * same configuration. | ||
80 | */ | ||
81 | if (WARN_ON((cluster_phy->phy_provided != PHY_NONE) && | ||
82 | (cluster_phy->phy_provided != args->args[0]))) { | ||
83 | dev_err(dev, "This PHY has already been provided!\n"); | ||
84 | dev_err(dev, "Check your device tree, only one controller can use it\n."); | ||
85 | if (args->args[0] == PHY_TYPE_USB2) | ||
86 | return ERR_PTR(-EBUSY); | ||
87 | else | ||
88 | return ERR_PTR(-ENODEV); | ||
89 | } | ||
90 | |||
91 | if (args->args[0] == PHY_TYPE_USB2) | ||
92 | cluster_phy->use_usb3 = false; | ||
93 | else if (args->args[0] == PHY_TYPE_USB3) | ||
94 | cluster_phy->use_usb3 = true; | ||
95 | else { | ||
96 | dev_err(dev, "Invalid PHY mode\n"); | ||
97 | return ERR_PTR(-ENODEV); | ||
98 | } | ||
99 | |||
100 | /* Store which phy mode is used for next test */ | ||
101 | cluster_phy->phy_provided = args->args[0]; | ||
102 | |||
103 | return cluster_phy->phy; | ||
104 | } | ||
105 | |||
106 | static int armada375_usb_phy_probe(struct platform_device *pdev) | ||
107 | { | ||
108 | struct device *dev = &pdev->dev; | ||
109 | struct phy *phy; | ||
110 | struct phy_provider *phy_provider; | ||
111 | void __iomem *usb_cluster_base; | ||
112 | struct resource *res; | ||
113 | struct armada375_cluster_phy *cluster_phy; | ||
114 | |||
115 | cluster_phy = devm_kzalloc(dev, sizeof(*cluster_phy), GFP_KERNEL); | ||
116 | if (!cluster_phy) | ||
117 | return -ENOMEM; | ||
118 | |||
119 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
120 | usb_cluster_base = devm_ioremap_resource(&pdev->dev, res); | ||
121 | if (!usb_cluster_base) | ||
122 | return -ENOMEM; | ||
123 | |||
124 | phy = devm_phy_create(dev, NULL, &armada375_usb_phy_ops); | ||
125 | if (IS_ERR(phy)) { | ||
126 | dev_err(dev, "failed to create PHY\n"); | ||
127 | return PTR_ERR(phy); | ||
128 | } | ||
129 | |||
130 | cluster_phy->phy = phy; | ||
131 | cluster_phy->reg = usb_cluster_base; | ||
132 | |||
133 | dev_set_drvdata(dev, cluster_phy); | ||
134 | |||
135 | phy_provider = devm_of_phy_provider_register(&pdev->dev, | ||
136 | armada375_usb_phy_xlate); | ||
137 | return PTR_ERR_OR_ZERO(phy_provider); | ||
138 | } | ||
139 | |||
140 | static const struct of_device_id of_usb_cluster_table[] = { | ||
141 | { .compatible = "marvell,armada-375-usb-cluster", }, | ||
142 | { /* end of list */ }, | ||
143 | }; | ||
144 | MODULE_DEVICE_TABLE(of, of_usb_cluster_table); | ||
145 | |||
146 | static struct platform_driver armada375_usb_phy_driver = { | ||
147 | .probe = armada375_usb_phy_probe, | ||
148 | .driver = { | ||
149 | .of_match_table = of_usb_cluster_table, | ||
150 | .name = "armada-375-usb-cluster", | ||
151 | .owner = THIS_MODULE, | ||
152 | } | ||
153 | }; | ||
154 | module_platform_driver(armada375_usb_phy_driver); | ||
155 | |||
156 | MODULE_DESCRIPTION("Armada 375 USB cluster driver"); | ||
157 | MODULE_AUTHOR("Gregory CLEMENT <gregory.clement@free-electrons.com>"); | ||
158 | MODULE_LICENSE("GPL"); | ||