Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 | // SPDX-License-Identifier: GPL-2.0+ /* * bdc_cmd.c - BRCM BDC USB3.0 device controller * * Copyright (C) 2014 Broadcom Corporation * * Author: Ashwini Pahuja */ #include <linux/scatterlist.h> #include <linux/slab.h> #include "bdc.h" #include "bdc_cmd.h" #include "bdc_dbg.h" /* Issues a cmd to cmd processor and waits for cmd completion */ static int bdc_issue_cmd(struct bdc *bdc, u32 cmd_sc, u32 param0, u32 param1, u32 param2) { u32 timeout = BDC_CMD_TIMEOUT; u32 cmd_status; u32 temp; bdc_writel(bdc->regs, BDC_CMDPAR0, param0); bdc_writel(bdc->regs, BDC_CMDPAR1, param1); bdc_writel(bdc->regs, BDC_CMDPAR2, param2); /* Issue the cmd */ /* Make sure the cmd params are written before asking HW to exec cmd */ wmb(); bdc_writel(bdc->regs, BDC_CMDSC, cmd_sc | BDC_CMD_CWS | BDC_CMD_SRD); do { temp = bdc_readl(bdc->regs, BDC_CMDSC); dev_dbg_ratelimited(bdc->dev, "cmdsc=%x", temp); cmd_status = BDC_CMD_CST(temp); if (cmd_status != BDC_CMDS_BUSY) { dev_dbg(bdc->dev, "command completed cmd_sts:%x\n", cmd_status); return cmd_status; } udelay(1); } while (timeout--); dev_err(bdc->dev, "command operation timedout cmd_status=%d\n", cmd_status); return cmd_status; } /* Submits cmd and analyze the return value of bdc_issue_cmd */ static int bdc_submit_cmd(struct bdc *bdc, u32 cmd_sc, u32 param0, u32 param1, u32 param2) { u32 temp, cmd_status; int ret; temp = bdc_readl(bdc->regs, BDC_CMDSC); dev_dbg(bdc->dev, "%s:CMDSC:%08x cmdsc:%08x param0=%08x param1=%08x param2=%08x\n", __func__, temp, cmd_sc, param0, param1, param2); cmd_status = BDC_CMD_CST(temp); if (cmd_status == BDC_CMDS_BUSY) { dev_err(bdc->dev, "command processor busy: %x\n", cmd_status); return -EBUSY; } ret = bdc_issue_cmd(bdc, cmd_sc, param0, param1, param2); switch (ret) { case BDC_CMDS_SUCC: dev_dbg(bdc->dev, "command completed successfully\n"); ret = 0; break; case BDC_CMDS_PARA: dev_err(bdc->dev, "command parameter error\n"); ret = -EINVAL; break; case BDC_CMDS_STAT: dev_err(bdc->dev, "Invalid device/ep state\n"); ret = -EINVAL; break; case BDC_CMDS_FAIL: dev_err(bdc->dev, "Command failed?\n"); ret = -EAGAIN; break; case BDC_CMDS_INTL: dev_err(bdc->dev, "BDC Internal error\n"); ret = -ECONNRESET; break; case BDC_CMDS_BUSY: dev_err(bdc->dev, "command timedout waited for %dusec\n", BDC_CMD_TIMEOUT); ret = -ECONNRESET; break; default: dev_dbg(bdc->dev, "Unknown command completion code:%x\n", ret); } return ret; } /* Deconfigure the endpoint from HW */ int bdc_dconfig_ep(struct bdc *bdc, struct bdc_ep *ep) { u32 cmd_sc; cmd_sc = BDC_SUB_CMD_DRP_EP|BDC_CMD_EPN(ep->ep_num)|BDC_CMD_EPC; dev_dbg(bdc->dev, "%s ep->ep_num =%d cmd_sc=%x\n", __func__, ep->ep_num, cmd_sc); return bdc_submit_cmd(bdc, cmd_sc, 0, 0, 0); } /* Reinitalize the bdlist after config ep command */ static void ep_bd_list_reinit(struct bdc_ep *ep) { struct bdc *bdc = ep->bdc; struct bdc_bd *bd; ep->bd_list.eqp_bdi = 0; ep->bd_list.hwd_bdi = 0; bd = ep->bd_list.bd_table_array[0]->start_bd; dev_dbg(bdc->dev, "%s ep:%p bd:%p\n", __func__, ep, bd); memset(bd, 0, sizeof(struct bdc_bd)); bd->offset[3] |= cpu_to_le32(BD_SBF); } /* Configure an endpoint */ int bdc_config_ep(struct bdc *bdc, struct bdc_ep *ep) { const struct usb_ss_ep_comp_descriptor *comp_desc; const struct usb_endpoint_descriptor *desc; u32 param0, param1, param2, cmd_sc; u32 mps, mbs, mul, si; int ret; desc = ep->desc; comp_desc = ep->comp_desc; cmd_sc = mul = mbs = param2 = 0; param0 = lower_32_bits(ep->bd_list.bd_table_array[0]->dma); param1 = upper_32_bits(ep->bd_list.bd_table_array[0]->dma); cpu_to_le32s(¶m0); cpu_to_le32s(¶m1); dev_dbg(bdc->dev, "%s: param0=%08x param1=%08x", __func__, param0, param1); si = desc->bInterval; si = clamp_val(si, 1, 16) - 1; mps = usb_endpoint_maxp(desc); mps &= 0x7ff; param2 |= mps << MP_SHIFT; param2 |= usb_endpoint_type(desc) << EPT_SHIFT; switch (bdc->gadget.speed) { case USB_SPEED_SUPER: if (usb_endpoint_xfer_int(desc) || usb_endpoint_xfer_isoc(desc)) { param2 |= si; if (usb_endpoint_xfer_isoc(desc) && comp_desc) mul = comp_desc->bmAttributes; } param2 |= mul << EPM_SHIFT; if (comp_desc) mbs = comp_desc->bMaxBurst; param2 |= mbs << MB_SHIFT; break; case USB_SPEED_HIGH: if (usb_endpoint_xfer_isoc(desc) || usb_endpoint_xfer_int(desc)) { param2 |= si; mbs = usb_endpoint_maxp_mult(desc); param2 |= mbs << MB_SHIFT; } break; case USB_SPEED_FULL: case USB_SPEED_LOW: /* the hardware accepts SI in 125usec range */ if (usb_endpoint_xfer_isoc(desc)) si += 3; /* * FS Int endpoints can have si of 1-255ms but the controller * accepts 2^bInterval*125usec, so convert ms to nearest power * of 2 */ if (usb_endpoint_xfer_int(desc)) si = fls(desc->bInterval * 8) - 1; param2 |= si; break; default: dev_err(bdc->dev, "UNKNOWN speed ERR\n"); return -EINVAL; } cmd_sc |= BDC_CMD_EPC|BDC_CMD_EPN(ep->ep_num)|BDC_SUB_CMD_ADD_EP; dev_dbg(bdc->dev, "cmd_sc=%x param2=%08x\n", cmd_sc, param2); ret = bdc_submit_cmd(bdc, cmd_sc, param0, param1, param2); if (ret) { dev_err(bdc->dev, "command failed :%x\n", ret); return ret; } ep_bd_list_reinit(ep); return ret; } /* * Change the HW deq pointer, if this command is successful, HW will start * fetching the next bd from address dma_addr. */ int bdc_ep_bla(struct bdc *bdc, struct bdc_ep *ep, dma_addr_t dma_addr) { u32 param0, param1; u32 cmd_sc = 0; dev_dbg(bdc->dev, "%s: add=%08llx\n", __func__, (unsigned long long)(dma_addr)); param0 = lower_32_bits(dma_addr); param1 = upper_32_bits(dma_addr); cpu_to_le32s(¶m0); cpu_to_le32s(¶m1); cmd_sc |= BDC_CMD_EPN(ep->ep_num)|BDC_CMD_BLA; dev_dbg(bdc->dev, "cmd_sc=%x\n", cmd_sc); return bdc_submit_cmd(bdc, cmd_sc, param0, param1, 0); } /* Set the address sent bu Host in SET_ADD request */ int bdc_address_device(struct bdc *bdc, u32 add) { u32 cmd_sc = 0; u32 param2; dev_dbg(bdc->dev, "%s: add=%d\n", __func__, add); cmd_sc |= BDC_SUB_CMD_ADD|BDC_CMD_DVC; param2 = add & 0x7f; return bdc_submit_cmd(bdc, cmd_sc, 0, 0, param2); } /* Send a Function Wake notification packet using FH command */ int bdc_function_wake_fh(struct bdc *bdc, u8 intf) { u32 param0, param1; u32 cmd_sc = 0; param0 = param1 = 0; dev_dbg(bdc->dev, "%s intf=%d\n", __func__, intf); cmd_sc |= BDC_CMD_FH; param0 |= TRA_PACKET; param0 |= (bdc->dev_addr << 25); param1 |= DEV_NOTF_TYPE; param1 |= (FWK_SUBTYPE<<4); dev_dbg(bdc->dev, "param0=%08x param1=%08x\n", param0, param1); return bdc_submit_cmd(bdc, cmd_sc, param0, param1, 0); } /* Send a Function Wake notification packet using DNC command */ int bdc_function_wake(struct bdc *bdc, u8 intf) { u32 cmd_sc = 0; u32 param2 = 0; dev_dbg(bdc->dev, "%s intf=%d", __func__, intf); param2 |= intf; cmd_sc |= BDC_SUB_CMD_FWK|BDC_CMD_DNC; return bdc_submit_cmd(bdc, cmd_sc, 0, 0, param2); } /* Stall the endpoint */ int bdc_ep_set_stall(struct bdc *bdc, int epnum) { u32 cmd_sc = 0; dev_dbg(bdc->dev, "%s epnum=%d\n", __func__, epnum); /* issue a stall endpoint command */ cmd_sc |= BDC_SUB_CMD_EP_STL | BDC_CMD_EPN(epnum) | BDC_CMD_EPO; return bdc_submit_cmd(bdc, cmd_sc, 0, 0, 0); } /* resets the endpoint, called when host sends CLEAR_FEATURE(HALT) */ int bdc_ep_clear_stall(struct bdc *bdc, int epnum) { struct bdc_ep *ep; u32 cmd_sc = 0; int ret; dev_dbg(bdc->dev, "%s: epnum=%d\n", __func__, epnum); ep = bdc->bdc_ep_array[epnum]; /* * If we are not in stalled then stall Endpoint and issue clear stall, * his will reset the seq number for non EP0. */ if (epnum != 1) { /* if the endpoint it not stallled */ if (!(ep->flags & BDC_EP_STALL)) { ret = bdc_ep_set_stall(bdc, epnum); if (ret) return ret; } } /* Preserve the seq number for ep0 only */ if (epnum != 1) cmd_sc |= BDC_CMD_EPO_RST_SN; /* issue a reset endpoint command */ cmd_sc |= BDC_SUB_CMD_EP_RST | BDC_CMD_EPN(epnum) | BDC_CMD_EPO; ret = bdc_submit_cmd(bdc, cmd_sc, 0, 0, 0); if (ret) { dev_err(bdc->dev, "command failed:%x\n", ret); return ret; } bdc_notify_xfr(bdc, epnum); return ret; } /* Stop the endpoint, called when software wants to dequeue some request */ int bdc_stop_ep(struct bdc *bdc, int epnum) { struct bdc_ep *ep; u32 cmd_sc = 0; int ret; ep = bdc->bdc_ep_array[epnum]; dev_dbg(bdc->dev, "%s: ep:%s ep->flags:%08x\n", __func__, ep->name, ep->flags); /* Endpoint has to be in running state to execute stop ep command */ if (!(ep->flags & BDC_EP_ENABLED)) { dev_err(bdc->dev, "stop endpoint called for disabled ep\n"); return -EINVAL; } if ((ep->flags & BDC_EP_STALL) || (ep->flags & BDC_EP_STOP)) return 0; /* issue a stop endpoint command */ cmd_sc |= BDC_CMD_EP0_XSD | BDC_SUB_CMD_EP_STP | BDC_CMD_EPN(epnum) | BDC_CMD_EPO; ret = bdc_submit_cmd(bdc, cmd_sc, 0, 0, 0); if (ret) { dev_err(bdc->dev, "stop endpoint command didn't complete:%d ep:%s\n", ret, ep->name); return ret; } ep->flags |= BDC_EP_STOP; bdc_dump_epsts(bdc); return ret; } |