summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/udc/bdc/bdc_cmd.c
blob: 995f79c79f9683dcd247215548ee2bea21e6f6cd (plain)
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(&param0);
	cpu_to_le32s(&param1);

	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(&param0);
	cpu_to_le32s(&param1);

	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;
}