]>
Commit | Line | Data |
---|---|---|
5fd54ace | 1 | // SPDX-License-Identifier: GPL-2.0+ |
2d57a95f DM |
2 | /* |
3 | * Generic ULPI USB transceiver support | |
4 | * | |
5 | * Copyright (C) 2009 Daniel Mack <daniel@caiaq.de> | |
6 | * | |
7 | * Based on sources from | |
8 | * | |
9 | * Sascha Hauer <s.hauer@pengutronix.de> | |
10 | * Freescale Semiconductors | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or modify | |
13 | * it under the terms of the GNU General Public License as published by | |
14 | * the Free Software Foundation; either version 2 of the License, or | |
15 | * (at your option) any later version. | |
16 | * | |
17 | * This program is distributed in the hope that it will be useful, | |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | * GNU General Public License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU General Public License | |
23 | * along with this program; if not, write to the Free Software | |
24 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
25 | */ | |
26 | ||
27 | #include <linux/kernel.h> | |
5a0e3ad6 | 28 | #include <linux/slab.h> |
f940fcd8 | 29 | #include <linux/export.h> |
2d57a95f DM |
30 | #include <linux/usb.h> |
31 | #include <linux/usb/otg.h> | |
32 | #include <linux/usb/ulpi.h> | |
33 | ||
96b9e832 IG |
34 | |
35 | struct ulpi_info { | |
36 | unsigned int id; | |
37 | char *name; | |
38 | }; | |
39 | ||
2d57a95f | 40 | #define ULPI_ID(vendor, product) (((vendor) << 16) | (product)) |
96b9e832 IG |
41 | #define ULPI_INFO(_id, _name) \ |
42 | { \ | |
43 | .id = (_id), \ | |
44 | .name = (_name), \ | |
45 | } | |
2d57a95f | 46 | |
2d57a95f | 47 | /* ULPI hardcoded IDs, used for probing */ |
96b9e832 IG |
48 | static struct ulpi_info ulpi_ids[] = { |
49 | ULPI_INFO(ULPI_ID(0x04cc, 0x1504), "NXP ISP1504"), | |
1e4cba8b | 50 | ULPI_INFO(ULPI_ID(0x0424, 0x0006), "SMSC USB331x"), |
ead5178b | 51 | ULPI_INFO(ULPI_ID(0x0424, 0x0007), "SMSC USB3320"), |
79cb5b53 | 52 | ULPI_INFO(ULPI_ID(0x0424, 0x0009), "SMSC USB334x"), |
ead5178b | 53 | ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"), |
2d57a95f DM |
54 | }; |
55 | ||
298b083c | 56 | static int ulpi_set_otg_flags(struct usb_phy *phy) |
2d57a95f | 57 | { |
13dd0c97 IG |
58 | unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN | |
59 | ULPI_OTG_CTRL_DM_PULLDOWN; | |
2d57a95f | 60 | |
298b083c | 61 | if (phy->flags & ULPI_OTG_ID_PULLUP) |
fc567f06 | 62 | flags |= ULPI_OTG_CTRL_ID_PULLUP; |
2d57a95f | 63 | |
13dd0c97 IG |
64 | /* |
65 | * ULPI Specification rev.1.1 default | |
66 | * for Dp/DmPulldown is enabled. | |
67 | */ | |
298b083c | 68 | if (phy->flags & ULPI_OTG_DP_PULLDOWN_DIS) |
13dd0c97 | 69 | flags &= ~ULPI_OTG_CTRL_DP_PULLDOWN; |
2d57a95f | 70 | |
298b083c | 71 | if (phy->flags & ULPI_OTG_DM_PULLDOWN_DIS) |
13dd0c97 | 72 | flags &= ~ULPI_OTG_CTRL_DM_PULLDOWN; |
2d57a95f | 73 | |
298b083c | 74 | if (phy->flags & ULPI_OTG_EXTVBUSIND) |
fc567f06 | 75 | flags |= ULPI_OTG_CTRL_EXTVBUSIND; |
2d57a95f | 76 | |
298b083c | 77 | return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL); |
2d57a95f DM |
78 | } |
79 | ||
298b083c | 80 | static int ulpi_set_fc_flags(struct usb_phy *phy) |
13dd0c97 IG |
81 | { |
82 | unsigned int flags = 0; | |
83 | ||
84 | /* | |
85 | * ULPI Specification rev.1.1 default | |
86 | * for XcvrSelect is Full Speed. | |
87 | */ | |
298b083c | 88 | if (phy->flags & ULPI_FC_HS) |
13dd0c97 | 89 | flags |= ULPI_FUNC_CTRL_HIGH_SPEED; |
298b083c | 90 | else if (phy->flags & ULPI_FC_LS) |
13dd0c97 | 91 | flags |= ULPI_FUNC_CTRL_LOW_SPEED; |
298b083c | 92 | else if (phy->flags & ULPI_FC_FS4LS) |
13dd0c97 IG |
93 | flags |= ULPI_FUNC_CTRL_FS4LS; |
94 | else | |
95 | flags |= ULPI_FUNC_CTRL_FULL_SPEED; | |
96 | ||
298b083c | 97 | if (phy->flags & ULPI_FC_TERMSEL) |
13dd0c97 IG |
98 | flags |= ULPI_FUNC_CTRL_TERMSELECT; |
99 | ||
100 | /* | |
101 | * ULPI Specification rev.1.1 default | |
102 | * for OpMode is Normal Operation. | |
103 | */ | |
298b083c | 104 | if (phy->flags & ULPI_FC_OP_NODRV) |
13dd0c97 | 105 | flags |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING; |
298b083c | 106 | else if (phy->flags & ULPI_FC_OP_DIS_NRZI) |
13dd0c97 | 107 | flags |= ULPI_FUNC_CTRL_OPMODE_DISABLE_NRZI; |
298b083c | 108 | else if (phy->flags & ULPI_FC_OP_NSYNC_NEOP) |
13dd0c97 IG |
109 | flags |= ULPI_FUNC_CTRL_OPMODE_NOSYNC_NOEOP; |
110 | else | |
111 | flags |= ULPI_FUNC_CTRL_OPMODE_NORMAL; | |
112 | ||
113 | /* | |
114 | * ULPI Specification rev.1.1 default | |
115 | * for SuspendM is Powered. | |
116 | */ | |
117 | flags |= ULPI_FUNC_CTRL_SUSPENDM; | |
118 | ||
298b083c | 119 | return usb_phy_io_write(phy, flags, ULPI_FUNC_CTRL); |
13dd0c97 IG |
120 | } |
121 | ||
298b083c | 122 | static int ulpi_set_ic_flags(struct usb_phy *phy) |
13dd0c97 IG |
123 | { |
124 | unsigned int flags = 0; | |
125 | ||
298b083c | 126 | if (phy->flags & ULPI_IC_AUTORESUME) |
13dd0c97 IG |
127 | flags |= ULPI_IFC_CTRL_AUTORESUME; |
128 | ||
298b083c | 129 | if (phy->flags & ULPI_IC_EXTVBUS_INDINV) |
13dd0c97 IG |
130 | flags |= ULPI_IFC_CTRL_EXTERNAL_VBUS; |
131 | ||
298b083c | 132 | if (phy->flags & ULPI_IC_IND_PASSTHRU) |
13dd0c97 IG |
133 | flags |= ULPI_IFC_CTRL_PASSTHRU; |
134 | ||
298b083c | 135 | if (phy->flags & ULPI_IC_PROTECT_DIS) |
13dd0c97 IG |
136 | flags |= ULPI_IFC_CTRL_PROTECT_IFC_DISABLE; |
137 | ||
298b083c | 138 | return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL); |
13dd0c97 IG |
139 | } |
140 | ||
298b083c | 141 | static int ulpi_set_flags(struct usb_phy *phy) |
13dd0c97 IG |
142 | { |
143 | int ret; | |
144 | ||
298b083c | 145 | ret = ulpi_set_otg_flags(phy); |
13dd0c97 IG |
146 | if (ret) |
147 | return ret; | |
148 | ||
298b083c | 149 | ret = ulpi_set_ic_flags(phy); |
13dd0c97 IG |
150 | if (ret) |
151 | return ret; | |
152 | ||
298b083c | 153 | return ulpi_set_fc_flags(phy); |
13dd0c97 IG |
154 | } |
155 | ||
298b083c | 156 | static int ulpi_check_integrity(struct usb_phy *phy) |
a9138192 IG |
157 | { |
158 | int ret, i; | |
159 | unsigned int val = 0x55; | |
160 | ||
161 | for (i = 0; i < 2; i++) { | |
298b083c | 162 | ret = usb_phy_io_write(phy, val, ULPI_SCRATCH); |
a9138192 IG |
163 | if (ret < 0) |
164 | return ret; | |
165 | ||
298b083c | 166 | ret = usb_phy_io_read(phy, ULPI_SCRATCH); |
a9138192 IG |
167 | if (ret < 0) |
168 | return ret; | |
169 | ||
170 | if (ret != val) { | |
171 | pr_err("ULPI integrity check: failed!"); | |
172 | return -ENODEV; | |
173 | } | |
174 | val = val << 1; | |
175 | } | |
176 | ||
177 | pr_info("ULPI integrity check: passed.\n"); | |
178 | ||
179 | return 0; | |
180 | } | |
181 | ||
298b083c | 182 | static int ulpi_init(struct usb_phy *phy) |
2d57a95f | 183 | { |
7b4a0367 WS |
184 | int i, vid, pid, ret; |
185 | u32 ulpi_id = 0; | |
2d57a95f | 186 | |
7b4a0367 | 187 | for (i = 0; i < 4; i++) { |
298b083c | 188 | ret = usb_phy_io_read(phy, ULPI_PRODUCT_ID_HIGH - i); |
7b4a0367 WS |
189 | if (ret < 0) |
190 | return ret; | |
191 | ulpi_id = (ulpi_id << 8) | ret; | |
192 | } | |
193 | vid = ulpi_id & 0xffff; | |
194 | pid = ulpi_id >> 16; | |
2d57a95f DM |
195 | |
196 | pr_info("ULPI transceiver vendor/product ID 0x%04x/0x%04x\n", vid, pid); | |
197 | ||
96b9e832 IG |
198 | for (i = 0; i < ARRAY_SIZE(ulpi_ids); i++) { |
199 | if (ulpi_ids[i].id == ULPI_ID(vid, pid)) { | |
200 | pr_info("Found %s ULPI transceiver.\n", | |
201 | ulpi_ids[i].name); | |
a9138192 | 202 | break; |
96b9e832 IG |
203 | } |
204 | } | |
a9138192 | 205 | |
298b083c | 206 | ret = ulpi_check_integrity(phy); |
a9138192 IG |
207 | if (ret) |
208 | return ret; | |
2d57a95f | 209 | |
298b083c | 210 | return ulpi_set_flags(phy); |
2d57a95f DM |
211 | } |
212 | ||
298b083c | 213 | static int ulpi_set_host(struct usb_otg *otg, struct usb_bus *host) |
13dd0c97 | 214 | { |
19c1eac2 | 215 | struct usb_phy *phy = otg->usb_phy; |
298b083c | 216 | unsigned int flags = usb_phy_io_read(phy, ULPI_IFC_CTRL); |
13dd0c97 IG |
217 | |
218 | if (!host) { | |
219 | otg->host = NULL; | |
220 | return 0; | |
221 | } | |
222 | ||
223 | otg->host = host; | |
224 | ||
225 | flags &= ~(ULPI_IFC_CTRL_6_PIN_SERIAL_MODE | | |
226 | ULPI_IFC_CTRL_3_PIN_SERIAL_MODE | | |
227 | ULPI_IFC_CTRL_CARKITMODE); | |
228 | ||
298b083c | 229 | if (phy->flags & ULPI_IC_6PIN_SERIAL) |
13dd0c97 | 230 | flags |= ULPI_IFC_CTRL_6_PIN_SERIAL_MODE; |
298b083c | 231 | else if (phy->flags & ULPI_IC_3PIN_SERIAL) |
13dd0c97 | 232 | flags |= ULPI_IFC_CTRL_3_PIN_SERIAL_MODE; |
298b083c | 233 | else if (phy->flags & ULPI_IC_CARKIT) |
13dd0c97 IG |
234 | flags |= ULPI_IFC_CTRL_CARKITMODE; |
235 | ||
298b083c | 236 | return usb_phy_io_write(phy, flags, ULPI_IFC_CTRL); |
13dd0c97 IG |
237 | } |
238 | ||
298b083c | 239 | static int ulpi_set_vbus(struct usb_otg *otg, bool on) |
2d57a95f | 240 | { |
19c1eac2 | 241 | struct usb_phy *phy = otg->usb_phy; |
298b083c | 242 | unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL); |
2d57a95f | 243 | |
fc567f06 | 244 | flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT); |
2d57a95f DM |
245 | |
246 | if (on) { | |
298b083c | 247 | if (phy->flags & ULPI_OTG_DRVVBUS) |
fc567f06 | 248 | flags |= ULPI_OTG_CTRL_DRVVBUS; |
2d57a95f | 249 | |
298b083c | 250 | if (phy->flags & ULPI_OTG_DRVVBUS_EXT) |
fc567f06 | 251 | flags |= ULPI_OTG_CTRL_DRVVBUS_EXT; |
2d57a95f DM |
252 | } |
253 | ||
298b083c | 254 | return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL); |
2d57a95f DM |
255 | } |
256 | ||
86753811 | 257 | struct usb_phy * |
298b083c | 258 | otg_ulpi_create(struct usb_phy_io_ops *ops, |
2d57a95f DM |
259 | unsigned int flags) |
260 | { | |
298b083c HK |
261 | struct usb_phy *phy; |
262 | struct usb_otg *otg; | |
263 | ||
264 | phy = kzalloc(sizeof(*phy), GFP_KERNEL); | |
265 | if (!phy) | |
266 | return NULL; | |
2d57a95f DM |
267 | |
268 | otg = kzalloc(sizeof(*otg), GFP_KERNEL); | |
298b083c HK |
269 | if (!otg) { |
270 | kfree(phy); | |
2d57a95f | 271 | return NULL; |
298b083c HK |
272 | } |
273 | ||
274 | phy->label = "ULPI"; | |
275 | phy->flags = flags; | |
276 | phy->io_ops = ops; | |
277 | phy->otg = otg; | |
278 | phy->init = ulpi_init; | |
2d57a95f | 279 | |
19c1eac2 | 280 | otg->usb_phy = phy; |
13dd0c97 | 281 | otg->set_host = ulpi_set_host; |
2d57a95f DM |
282 | otg->set_vbus = ulpi_set_vbus; |
283 | ||
298b083c | 284 | return phy; |
2d57a95f DM |
285 | } |
286 | EXPORT_SYMBOL_GPL(otg_ulpi_create); | |
287 |