~ubuntu-branches/ubuntu/precise/linux-lts-saucy/precise-proposed

« back to all changes in this revision

Viewing changes to drivers/usb/host/whci/pzl.c

  • Committer: Package Import Robot
  • Author(s): Tim Gardner
  • Date: 2013-10-09 13:31:18 UTC
  • Revision ID: package-import@ubuntu.com-20131009133118-l5q5o2hmtz96hefq
Tags: upstream-3.11.0
ImportĀ upstreamĀ versionĀ 3.11.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Wireless Host Controller (WHC) periodic schedule management.
 
3
 *
 
4
 * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
 
5
 *
 
6
 * This program is free software; you can redistribute it and/or
 
7
 * modify it under the terms of the GNU General Public License version
 
8
 * 2 as published by the Free Software Foundation.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 * GNU General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License
 
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
17
 */
 
18
#include <linux/kernel.h>
 
19
#include <linux/gfp.h>
 
20
#include <linux/dma-mapping.h>
 
21
#include <linux/uwb/umc.h>
 
22
#include <linux/usb.h>
 
23
 
 
24
#include "../../wusbcore/wusbhc.h"
 
25
 
 
26
#include "whcd.h"
 
27
 
 
28
static void update_pzl_pointers(struct whc *whc, int period, u64 addr)
 
29
{
 
30
        switch (period) {
 
31
        case 0:
 
32
                whc_qset_set_link_ptr(&whc->pz_list[0], addr);
 
33
                whc_qset_set_link_ptr(&whc->pz_list[2], addr);
 
34
                whc_qset_set_link_ptr(&whc->pz_list[4], addr);
 
35
                whc_qset_set_link_ptr(&whc->pz_list[6], addr);
 
36
                whc_qset_set_link_ptr(&whc->pz_list[8], addr);
 
37
                whc_qset_set_link_ptr(&whc->pz_list[10], addr);
 
38
                whc_qset_set_link_ptr(&whc->pz_list[12], addr);
 
39
                whc_qset_set_link_ptr(&whc->pz_list[14], addr);
 
40
                break;
 
41
        case 1:
 
42
                whc_qset_set_link_ptr(&whc->pz_list[1], addr);
 
43
                whc_qset_set_link_ptr(&whc->pz_list[5], addr);
 
44
                whc_qset_set_link_ptr(&whc->pz_list[9], addr);
 
45
                whc_qset_set_link_ptr(&whc->pz_list[13], addr);
 
46
                break;
 
47
        case 2:
 
48
                whc_qset_set_link_ptr(&whc->pz_list[3], addr);
 
49
                whc_qset_set_link_ptr(&whc->pz_list[11], addr);
 
50
                break;
 
51
        case 3:
 
52
                whc_qset_set_link_ptr(&whc->pz_list[7], addr);
 
53
                break;
 
54
        case 4:
 
55
                whc_qset_set_link_ptr(&whc->pz_list[15], addr);
 
56
                break;
 
57
        }
 
58
}
 
59
 
 
60
/*
 
61
 * Return the 'period' to use for this qset.  The minimum interval for
 
62
 * the endpoint is used so whatever urbs are submitted the device is
 
63
 * polled often enough.
 
64
 */
 
65
static int qset_get_period(struct whc *whc, struct whc_qset *qset)
 
66
{
 
67
        uint8_t bInterval = qset->ep->desc.bInterval;
 
68
 
 
69
        if (bInterval < 6)
 
70
                bInterval = 6;
 
71
        if (bInterval > 10)
 
72
                bInterval = 10;
 
73
        return bInterval - 6;
 
74
}
 
75
 
 
76
static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset)
 
77
{
 
78
        int period;
 
79
 
 
80
        period = qset_get_period(whc, qset);
 
81
 
 
82
        qset_clear(whc, qset);
 
83
        list_move(&qset->list_node, &whc->periodic_list[period]);
 
84
        qset->in_sw_list = true;
 
85
}
 
86
 
 
87
static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset)
 
88
{
 
89
        list_move(&qset->list_node, &whc->periodic_removed_list);
 
90
        qset->in_hw_list = false;
 
91
        qset->in_sw_list = false;
 
92
}
 
93
 
 
94
/**
 
95
 * pzl_process_qset - process any recently inactivated or halted qTDs
 
96
 * in a qset.
 
97
 *
 
98
 * After inactive qTDs are removed, new qTDs can be added if the
 
99
 * urb queue still contains URBs.
 
100
 *
 
101
 * Returns the schedule updates required.
 
102
 */
 
103
static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset)
 
104
{
 
105
        enum whc_update update = 0;
 
106
        uint32_t status = 0;
 
107
 
 
108
        while (qset->ntds) {
 
109
                struct whc_qtd *td;
 
110
                int t;
 
111
 
 
112
                t = qset->td_start;
 
113
                td = &qset->qtd[qset->td_start];
 
114
                status = le32_to_cpu(td->status);
 
115
 
 
116
                /*
 
117
                 * Nothing to do with a still active qTD.
 
118
                 */
 
119
                if (status & QTD_STS_ACTIVE)
 
120
                        break;
 
121
 
 
122
                if (status & QTD_STS_HALTED) {
 
123
                        /* Ug, an error. */
 
124
                        process_halted_qtd(whc, qset, td);
 
125
                        /* A halted qTD always triggers an update
 
126
                           because the qset was either removed or
 
127
                           reactivated. */
 
128
                        update |= WHC_UPDATE_UPDATED;
 
129
                        goto done;
 
130
                }
 
131
 
 
132
                /* Mmm, a completed qTD. */
 
133
                process_inactive_qtd(whc, qset, td);
 
134
        }
 
135
 
 
136
        if (!qset->remove)
 
137
                update |= qset_add_qtds(whc, qset);
 
138
 
 
139
done:
 
140
        /*
 
141
         * If there are no qTDs in this qset, remove it from the PZL.
 
142
         */
 
143
        if (qset->remove && qset->ntds == 0) {
 
144
                pzl_qset_remove(whc, qset);
 
145
                update |= WHC_UPDATE_REMOVED;
 
146
        }
 
147
 
 
148
        return update;
 
149
}
 
150
 
 
151
/**
 
152
 * pzl_start - start the periodic schedule
 
153
 * @whc: the WHCI host controller
 
154
 *
 
155
 * The PZL must be valid (e.g., all entries in the list should have
 
156
 * the T bit set).
 
157
 */
 
158
void pzl_start(struct whc *whc)
 
159
{
 
160
        le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
 
161
 
 
162
        whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN);
 
163
        whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
 
164
                      WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED,
 
165
                      1000, "start PZL");
 
166
}
 
167
 
 
168
/**
 
169
 * pzl_stop - stop the periodic schedule
 
170
 * @whc: the WHCI host controller
 
171
 */
 
172
void pzl_stop(struct whc *whc)
 
173
{
 
174
        whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0);
 
175
        whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
 
176
                      WUSBSTS_PERIODIC_SCHED, 0,
 
177
                      1000, "stop PZL");
 
178
}
 
179
 
 
180
/**
 
181
 * pzl_update - request a PZL update and wait for the hardware to be synced
 
182
 * @whc: the WHCI HC
 
183
 * @wusbcmd: WUSBCMD value to start the update.
 
184
 *
 
185
 * If the WUSB HC is inactive (i.e., the PZL is stopped) then the
 
186
 * update must be skipped as the hardware may not respond to update
 
187
 * requests.
 
188
 */
 
189
void pzl_update(struct whc *whc, uint32_t wusbcmd)
 
190
{
 
191
        struct wusbhc *wusbhc = &whc->wusbhc;
 
192
        long t;
 
193
 
 
194
        mutex_lock(&wusbhc->mutex);
 
195
        if (wusbhc->active) {
 
196
                whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
 
197
                t = wait_event_timeout(
 
198
                        whc->periodic_list_wq,
 
199
                        (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0,
 
200
                        msecs_to_jiffies(1000));
 
201
                if (t == 0)
 
202
                        whc_hw_error(whc, "PZL update timeout");
 
203
        }
 
204
        mutex_unlock(&wusbhc->mutex);
 
205
}
 
206
 
 
207
static void update_pzl_hw_view(struct whc *whc)
 
208
{
 
209
        struct whc_qset *qset, *t;
 
210
        int period;
 
211
        u64 tmp_qh = 0;
 
212
 
 
213
        for (period = 0; period < 5; period++) {
 
214
                list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
 
215
                        whc_qset_set_link_ptr(&qset->qh.link, tmp_qh);
 
216
                        tmp_qh = qset->qset_dma;
 
217
                        qset->in_hw_list = true;
 
218
                }
 
219
                update_pzl_pointers(whc, period, tmp_qh);
 
220
        }
 
221
}
 
222
 
 
223
/**
 
224
 * scan_periodic_work - scan the PZL for qsets to process.
 
225
 *
 
226
 * Process each qset in the PZL in turn and then signal the WHC that
 
227
 * the PZL has been updated.
 
228
 *
 
229
 * Then start, stop or update the periodic schedule as required.
 
230
 */
 
231
void scan_periodic_work(struct work_struct *work)
 
232
{
 
233
        struct whc *whc = container_of(work, struct whc, periodic_work);
 
234
        struct whc_qset *qset, *t;
 
235
        enum whc_update update = 0;
 
236
        int period;
 
237
 
 
238
        spin_lock_irq(&whc->lock);
 
239
 
 
240
        for (period = 4; period >= 0; period--) {
 
241
                list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
 
242
                        if (!qset->in_hw_list)
 
243
                                update |= WHC_UPDATE_ADDED;
 
244
                        update |= pzl_process_qset(whc, qset);
 
245
                }
 
246
        }
 
247
 
 
248
        if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED))
 
249
                update_pzl_hw_view(whc);
 
250
 
 
251
        spin_unlock_irq(&whc->lock);
 
252
 
 
253
        if (update) {
 
254
                uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB;
 
255
                if (update & WHC_UPDATE_REMOVED)
 
256
                        wusbcmd |= WUSBCMD_PERIODIC_QSET_RM;
 
257
                pzl_update(whc, wusbcmd);
 
258
        }
 
259
 
 
260
        /*
 
261
         * Now that the PZL is updated, complete the removal of any
 
262
         * removed qsets.
 
263
         *
 
264
         * If the qset was to be reset, do so and reinsert it into the
 
265
         * PZL if it has pending transfers.
 
266
         */
 
267
        spin_lock_irq(&whc->lock);
 
268
 
 
269
        list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) {
 
270
                qset_remove_complete(whc, qset);
 
271
                if (qset->reset) {
 
272
                        qset_reset(whc, qset);
 
273
                        if (!list_empty(&qset->stds)) {
 
274
                                qset_insert_in_sw_list(whc, qset);
 
275
                                queue_work(whc->workqueue, &whc->periodic_work);
 
276
                        }
 
277
                }
 
278
        }
 
279
 
 
280
        spin_unlock_irq(&whc->lock);
 
281
}
 
282
 
 
283
/**
 
284
 * pzl_urb_enqueue - queue an URB onto the periodic list (PZL)
 
285
 * @whc: the WHCI host controller
 
286
 * @urb: the URB to enqueue
 
287
 * @mem_flags: flags for any memory allocations
 
288
 *
 
289
 * The qset for the endpoint is obtained and the urb queued on to it.
 
290
 *
 
291
 * Work is scheduled to update the hardware's view of the PZL.
 
292
 */
 
293
int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
 
294
{
 
295
        struct whc_qset *qset;
 
296
        int err;
 
297
        unsigned long flags;
 
298
 
 
299
        spin_lock_irqsave(&whc->lock, flags);
 
300
 
 
301
        err = usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
 
302
        if (err < 0) {
 
303
                spin_unlock_irqrestore(&whc->lock, flags);
 
304
                return err;
 
305
        }
 
306
 
 
307
        qset = get_qset(whc, urb, GFP_ATOMIC);
 
308
        if (qset == NULL)
 
309
                err = -ENOMEM;
 
310
        else
 
311
                err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
 
312
        if (!err) {
 
313
                if (!qset->in_sw_list && !qset->remove)
 
314
                        qset_insert_in_sw_list(whc, qset);
 
315
        } else
 
316
                usb_hcd_unlink_urb_from_ep(&whc->wusbhc.usb_hcd, urb);
 
317
 
 
318
        spin_unlock_irqrestore(&whc->lock, flags);
 
319
 
 
320
        if (!err)
 
321
                queue_work(whc->workqueue, &whc->periodic_work);
 
322
 
 
323
        return err;
 
324
}
 
325
 
 
326
/**
 
327
 * pzl_urb_dequeue - remove an URB (qset) from the periodic list
 
328
 * @whc: the WHCI host controller
 
329
 * @urb: the URB to dequeue
 
330
 * @status: the current status of the URB
 
331
 *
 
332
 * URBs that do yet have qTDs can simply be removed from the software
 
333
 * queue, otherwise the qset must be removed so the qTDs can be safely
 
334
 * removed.
 
335
 */
 
336
int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
 
337
{
 
338
        struct whc_urb *wurb = urb->hcpriv;
 
339
        struct whc_qset *qset = wurb->qset;
 
340
        struct whc_std *std, *t;
 
341
        bool has_qtd = false;
 
342
        int ret;
 
343
        unsigned long flags;
 
344
 
 
345
        spin_lock_irqsave(&whc->lock, flags);
 
346
 
 
347
        ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
 
348
        if (ret < 0)
 
349
                goto out;
 
350
 
 
351
        list_for_each_entry_safe(std, t, &qset->stds, list_node) {
 
352
                if (std->urb == urb) {
 
353
                        if (std->qtd)
 
354
                                has_qtd = true;
 
355
                        qset_free_std(whc, std);
 
356
                } else
 
357
                        std->qtd = NULL; /* so this std is re-added when the qset is */
 
358
        }
 
359
 
 
360
        if (has_qtd) {
 
361
                pzl_qset_remove(whc, qset);
 
362
                update_pzl_hw_view(whc);
 
363
                wurb->status = status;
 
364
                wurb->is_async = false;
 
365
                queue_work(whc->workqueue, &wurb->dequeue_work);
 
366
        } else
 
367
                qset_remove_urb(whc, qset, urb, status);
 
368
out:
 
369
        spin_unlock_irqrestore(&whc->lock, flags);
 
370
 
 
371
        return ret;
 
372
}
 
373
 
 
374
/**
 
375
 * pzl_qset_delete - delete a qset from the PZL
 
376
 */
 
377
void pzl_qset_delete(struct whc *whc, struct whc_qset *qset)
 
378
{
 
379
        qset->remove = 1;
 
380
        queue_work(whc->workqueue, &whc->periodic_work);
 
381
        qset_delete(whc, qset);
 
382
}
 
383
 
 
384
/**
 
385
 * pzl_init - initialize the periodic zone list
 
386
 * @whc: the WHCI host controller
 
387
 */
 
388
int pzl_init(struct whc *whc)
 
389
{
 
390
        int i;
 
391
 
 
392
        whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16,
 
393
                                          &whc->pz_list_dma, GFP_KERNEL);
 
394
        if (whc->pz_list == NULL)
 
395
                return -ENOMEM;
 
396
 
 
397
        /* Set T bit on all elements in PZL. */
 
398
        for (i = 0; i < 16; i++)
 
399
                whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
 
400
 
 
401
        le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
 
402
 
 
403
        return 0;
 
404
}
 
405
 
 
406
/**
 
407
 * pzl_clean_up - free PZL resources
 
408
 * @whc: the WHCI host controller
 
409
 *
 
410
 * The PZL is stopped and empty.
 
411
 */
 
412
void pzl_clean_up(struct whc *whc)
 
413
{
 
414
        if (whc->pz_list)
 
415
                dma_free_coherent(&whc->umc->dev,  sizeof(u64) * 16, whc->pz_list,
 
416
                                  whc->pz_list_dma);
 
417
}