Root/
Source at commit f82953d88f4d343247624593226af59819a7ee14 created 10 years 11 months ago. By Lars-Peter Clausen, ASoC: omap: Use common DAI DMA data | |
---|---|
1 | /* |
2 | * sound/soc/omap/mcbsp.c |
3 | * |
4 | * Copyright (C) 2004 Nokia Corporation |
5 | * Author: Samuel Ortiz <samuel.ortiz@nokia.com> |
6 | * |
7 | * Contact: Jarkko Nikula <jarkko.nikula@bitmer.com> |
8 | * Peter Ujfalusi <peter.ujfalusi@ti.com> |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2 as |
12 | * published by the Free Software Foundation. |
13 | * |
14 | * Multichannel mode not supported. |
15 | */ |
16 | |
17 | #include <linux/module.h> |
18 | #include <linux/init.h> |
19 | #include <linux/device.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/interrupt.h> |
22 | #include <linux/err.h> |
23 | #include <linux/clk.h> |
24 | #include <linux/delay.h> |
25 | #include <linux/io.h> |
26 | #include <linux/slab.h> |
27 | #include <linux/pm_runtime.h> |
28 | |
29 | #include <linux/platform_data/asoc-ti-mcbsp.h> |
30 | |
31 | #include "mcbsp.h" |
32 | |
33 | static void omap_mcbsp_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val) |
34 | { |
35 | void __iomem *addr = mcbsp->io_base + reg * mcbsp->pdata->reg_step; |
36 | |
37 | if (mcbsp->pdata->reg_size == 2) { |
38 | ((u16 *)mcbsp->reg_cache)[reg] = (u16)val; |
39 | __raw_writew((u16)val, addr); |
40 | } else { |
41 | ((u32 *)mcbsp->reg_cache)[reg] = val; |
42 | __raw_writel(val, addr); |
43 | } |
44 | } |
45 | |
46 | static int omap_mcbsp_read(struct omap_mcbsp *mcbsp, u16 reg, bool from_cache) |
47 | { |
48 | void __iomem *addr = mcbsp->io_base + reg * mcbsp->pdata->reg_step; |
49 | |
50 | if (mcbsp->pdata->reg_size == 2) { |
51 | return !from_cache ? __raw_readw(addr) : |
52 | ((u16 *)mcbsp->reg_cache)[reg]; |
53 | } else { |
54 | return !from_cache ? __raw_readl(addr) : |
55 | ((u32 *)mcbsp->reg_cache)[reg]; |
56 | } |
57 | } |
58 | |
59 | static void omap_mcbsp_st_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val) |
60 | { |
61 | __raw_writel(val, mcbsp->st_data->io_base_st + reg); |
62 | } |
63 | |
64 | static int omap_mcbsp_st_read(struct omap_mcbsp *mcbsp, u16 reg) |
65 | { |
66 | return __raw_readl(mcbsp->st_data->io_base_st + reg); |
67 | } |
68 | |
69 | #define MCBSP_READ(mcbsp, reg) \ |
70 | omap_mcbsp_read(mcbsp, OMAP_MCBSP_REG_##reg, 0) |
71 | #define MCBSP_WRITE(mcbsp, reg, val) \ |
72 | omap_mcbsp_write(mcbsp, OMAP_MCBSP_REG_##reg, val) |
73 | #define MCBSP_READ_CACHE(mcbsp, reg) \ |
74 | omap_mcbsp_read(mcbsp, OMAP_MCBSP_REG_##reg, 1) |
75 | |
76 | #define MCBSP_ST_READ(mcbsp, reg) \ |
77 | omap_mcbsp_st_read(mcbsp, OMAP_ST_REG_##reg) |
78 | #define MCBSP_ST_WRITE(mcbsp, reg, val) \ |
79 | omap_mcbsp_st_write(mcbsp, OMAP_ST_REG_##reg, val) |
80 | |
81 | static void omap_mcbsp_dump_reg(struct omap_mcbsp *mcbsp) |
82 | { |
83 | dev_dbg(mcbsp->dev, "**** McBSP%d regs ****\n", mcbsp->id); |
84 | dev_dbg(mcbsp->dev, "DRR2: 0x%04x\n", |
85 | MCBSP_READ(mcbsp, DRR2)); |
86 | dev_dbg(mcbsp->dev, "DRR1: 0x%04x\n", |
87 | MCBSP_READ(mcbsp, DRR1)); |
88 | dev_dbg(mcbsp->dev, "DXR2: 0x%04x\n", |
89 | MCBSP_READ(mcbsp, DXR2)); |
90 | dev_dbg(mcbsp->dev, "DXR1: 0x%04x\n", |
91 | MCBSP_READ(mcbsp, DXR1)); |
92 | dev_dbg(mcbsp->dev, "SPCR2: 0x%04x\n", |
93 | MCBSP_READ(mcbsp, SPCR2)); |
94 | dev_dbg(mcbsp->dev, "SPCR1: 0x%04x\n", |
95 | MCBSP_READ(mcbsp, SPCR1)); |
96 | dev_dbg(mcbsp->dev, "RCR2: 0x%04x\n", |
97 | MCBSP_READ(mcbsp, RCR2)); |
98 | dev_dbg(mcbsp->dev, "RCR1: 0x%04x\n", |
99 | MCBSP_READ(mcbsp, RCR1)); |
100 | dev_dbg(mcbsp->dev, "XCR2: 0x%04x\n", |
101 | MCBSP_READ(mcbsp, XCR2)); |
102 | dev_dbg(mcbsp->dev, "XCR1: 0x%04x\n", |
103 | MCBSP_READ(mcbsp, XCR1)); |
104 | dev_dbg(mcbsp->dev, "SRGR2: 0x%04x\n", |
105 | MCBSP_READ(mcbsp, SRGR2)); |
106 | dev_dbg(mcbsp->dev, "SRGR1: 0x%04x\n", |
107 | MCBSP_READ(mcbsp, SRGR1)); |
108 | dev_dbg(mcbsp->dev, "PCR0: 0x%04x\n", |
109 | MCBSP_READ(mcbsp, PCR0)); |
110 | dev_dbg(mcbsp->dev, "***********************\n"); |
111 | } |
112 | |
113 | static irqreturn_t omap_mcbsp_irq_handler(int irq, void *dev_id) |
114 | { |
115 | struct omap_mcbsp *mcbsp = dev_id; |
116 | u16 irqst; |
117 | |
118 | irqst = MCBSP_READ(mcbsp, IRQST); |
119 | dev_dbg(mcbsp->dev, "IRQ callback : 0x%x\n", irqst); |
120 | |
121 | if (irqst & RSYNCERREN) |
122 | dev_err(mcbsp->dev, "RX Frame Sync Error!\n"); |
123 | if (irqst & RFSREN) |
124 | dev_dbg(mcbsp->dev, "RX Frame Sync\n"); |
125 | if (irqst & REOFEN) |
126 | dev_dbg(mcbsp->dev, "RX End Of Frame\n"); |
127 | if (irqst & RRDYEN) |
128 | dev_dbg(mcbsp->dev, "RX Buffer Threshold Reached\n"); |
129 | if (irqst & RUNDFLEN) |
130 | dev_err(mcbsp->dev, "RX Buffer Underflow!\n"); |
131 | if (irqst & ROVFLEN) |
132 | dev_err(mcbsp->dev, "RX Buffer Overflow!\n"); |
133 | |
134 | if (irqst & XSYNCERREN) |
135 | dev_err(mcbsp->dev, "TX Frame Sync Error!\n"); |
136 | if (irqst & XFSXEN) |
137 | dev_dbg(mcbsp->dev, "TX Frame Sync\n"); |
138 | if (irqst & XEOFEN) |
139 | dev_dbg(mcbsp->dev, "TX End Of Frame\n"); |
140 | if (irqst & XRDYEN) |
141 | dev_dbg(mcbsp->dev, "TX Buffer threshold Reached\n"); |
142 | if (irqst & XUNDFLEN) |
143 | dev_err(mcbsp->dev, "TX Buffer Underflow!\n"); |
144 | if (irqst & XOVFLEN) |
145 | dev_err(mcbsp->dev, "TX Buffer Overflow!\n"); |
146 | if (irqst & XEMPTYEOFEN) |
147 | dev_dbg(mcbsp->dev, "TX Buffer empty at end of frame\n"); |
148 | |
149 | MCBSP_WRITE(mcbsp, IRQST, irqst); |
150 | |
151 | return IRQ_HANDLED; |
152 | } |
153 | |
154 | static irqreturn_t omap_mcbsp_tx_irq_handler(int irq, void *dev_id) |
155 | { |
156 | struct omap_mcbsp *mcbsp_tx = dev_id; |
157 | u16 irqst_spcr2; |
158 | |
159 | irqst_spcr2 = MCBSP_READ(mcbsp_tx, SPCR2); |
160 | dev_dbg(mcbsp_tx->dev, "TX IRQ callback : 0x%x\n", irqst_spcr2); |
161 | |
162 | if (irqst_spcr2 & XSYNC_ERR) { |
163 | dev_err(mcbsp_tx->dev, "TX Frame Sync Error! : 0x%x\n", |
164 | irqst_spcr2); |
165 | /* Writing zero to XSYNC_ERR clears the IRQ */ |
166 | MCBSP_WRITE(mcbsp_tx, SPCR2, MCBSP_READ_CACHE(mcbsp_tx, SPCR2)); |
167 | } |
168 | |
169 | return IRQ_HANDLED; |
170 | } |
171 | |
172 | static irqreturn_t omap_mcbsp_rx_irq_handler(int irq, void *dev_id) |
173 | { |
174 | struct omap_mcbsp *mcbsp_rx = dev_id; |
175 | u16 irqst_spcr1; |
176 | |
177 | irqst_spcr1 = MCBSP_READ(mcbsp_rx, SPCR1); |
178 | dev_dbg(mcbsp_rx->dev, "RX IRQ callback : 0x%x\n", irqst_spcr1); |
179 | |
180 | if (irqst_spcr1 & RSYNC_ERR) { |
181 | dev_err(mcbsp_rx->dev, "RX Frame Sync Error! : 0x%x\n", |
182 | irqst_spcr1); |
183 | /* Writing zero to RSYNC_ERR clears the IRQ */ |
184 | MCBSP_WRITE(mcbsp_rx, SPCR1, MCBSP_READ_CACHE(mcbsp_rx, SPCR1)); |
185 | } |
186 | |
187 | return IRQ_HANDLED; |
188 | } |
189 | |
190 | /* |
191 | * omap_mcbsp_config simply write a config to the |
192 | * appropriate McBSP. |
193 | * You either call this function or set the McBSP registers |
194 | * by yourself before calling omap_mcbsp_start(). |
195 | */ |
196 | void omap_mcbsp_config(struct omap_mcbsp *mcbsp, |
197 | const struct omap_mcbsp_reg_cfg *config) |
198 | { |
199 | dev_dbg(mcbsp->dev, "Configuring McBSP%d phys_base: 0x%08lx\n", |
200 | mcbsp->id, mcbsp->phys_base); |
201 | |
202 | /* We write the given config */ |
203 | MCBSP_WRITE(mcbsp, SPCR2, config->spcr2); |
204 | MCBSP_WRITE(mcbsp, SPCR1, config->spcr1); |
205 | MCBSP_WRITE(mcbsp, RCR2, config->rcr2); |
206 | MCBSP_WRITE(mcbsp, RCR1, config->rcr1); |
207 | MCBSP_WRITE(mcbsp, XCR2, config->xcr2); |
208 | MCBSP_WRITE(mcbsp, XCR1, config->xcr1); |
209 | MCBSP_WRITE(mcbsp, SRGR2, config->srgr2); |
210 | MCBSP_WRITE(mcbsp, SRGR1, config->srgr1); |
211 | MCBSP_WRITE(mcbsp, MCR2, config->mcr2); |
212 | MCBSP_WRITE(mcbsp, MCR1, config->mcr1); |
213 | MCBSP_WRITE(mcbsp, PCR0, config->pcr0); |
214 | if (mcbsp->pdata->has_ccr) { |
215 | MCBSP_WRITE(mcbsp, XCCR, config->xccr); |
216 | MCBSP_WRITE(mcbsp, RCCR, config->rccr); |
217 | } |
218 | /* Enable wakeup behavior */ |
219 | if (mcbsp->pdata->has_wakeup) |
220 | MCBSP_WRITE(mcbsp, WAKEUPEN, XRDYEN | RRDYEN); |
221 | |
222 | /* Enable TX/RX sync error interrupts by default */ |
223 | if (mcbsp->irq) |
224 | MCBSP_WRITE(mcbsp, IRQEN, RSYNCERREN | XSYNCERREN); |
225 | } |
226 | |
227 | /** |
228 | * omap_mcbsp_dma_reg_params - returns the address of mcbsp data register |
229 | * @id - mcbsp id |
230 | * @stream - indicates the direction of data flow (rx or tx) |
231 | * |
232 | * Returns the address of mcbsp data transmit register or data receive register |
233 | * to be used by DMA for transferring/receiving data based on the value of |
234 | * @stream for the requested mcbsp given by @id |
235 | */ |
236 | static int omap_mcbsp_dma_reg_params(struct omap_mcbsp *mcbsp, |
237 | unsigned int stream) |
238 | { |
239 | int data_reg; |
240 | |
241 | if (mcbsp->pdata->reg_size == 2) { |
242 | if (stream) |
243 | data_reg = OMAP_MCBSP_REG_DRR1; |
244 | else |
245 | data_reg = OMAP_MCBSP_REG_DXR1; |
246 | } else { |
247 | if (stream) |
248 | data_reg = OMAP_MCBSP_REG_DRR; |
249 | else |
250 | data_reg = OMAP_MCBSP_REG_DXR; |
251 | } |
252 | |
253 | return mcbsp->phys_dma_base + data_reg * mcbsp->pdata->reg_step; |
254 | } |
255 | |
256 | static void omap_st_on(struct omap_mcbsp *mcbsp) |
257 | { |
258 | unsigned int w; |
259 | |
260 | if (mcbsp->pdata->enable_st_clock) |
261 | mcbsp->pdata->enable_st_clock(mcbsp->id, 1); |
262 | |
263 | /* Enable McBSP Sidetone */ |
264 | w = MCBSP_READ(mcbsp, SSELCR); |
265 | MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN); |
266 | |
267 | /* Enable Sidetone from Sidetone Core */ |
268 | w = MCBSP_ST_READ(mcbsp, SSELCR); |
269 | MCBSP_ST_WRITE(mcbsp, SSELCR, w | ST_SIDETONEEN); |
270 | } |
271 | |
272 | static void omap_st_off(struct omap_mcbsp *mcbsp) |
273 | { |
274 | unsigned int w; |
275 | |
276 | w = MCBSP_ST_READ(mcbsp, SSELCR); |
277 | MCBSP_ST_WRITE(mcbsp, SSELCR, w & ~(ST_SIDETONEEN)); |
278 | |
279 | w = MCBSP_READ(mcbsp, SSELCR); |
280 | MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN)); |
281 | |
282 | if (mcbsp->pdata->enable_st_clock) |
283 | mcbsp->pdata->enable_st_clock(mcbsp->id, 0); |
284 | } |
285 | |
286 | static void omap_st_fir_write(struct omap_mcbsp *mcbsp, s16 *fir) |
287 | { |
288 | u16 val, i; |
289 | |
290 | val = MCBSP_ST_READ(mcbsp, SSELCR); |
291 | |
292 | if (val & ST_COEFFWREN) |
293 | MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); |
294 | |
295 | MCBSP_ST_WRITE(mcbsp, SSELCR, val | ST_COEFFWREN); |
296 | |
297 | for (i = 0; i < 128; i++) |
298 | MCBSP_ST_WRITE(mcbsp, SFIRCR, fir[i]); |
299 | |
300 | i = 0; |
301 | |
302 | val = MCBSP_ST_READ(mcbsp, SSELCR); |
303 | while (!(val & ST_COEFFWRDONE) && (++i < 1000)) |
304 | val = MCBSP_ST_READ(mcbsp, SSELCR); |
305 | |
306 | MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); |
307 | |
308 | if (i == 1000) |
309 | dev_err(mcbsp->dev, "McBSP FIR load error!\n"); |
310 | } |
311 | |
312 | static void omap_st_chgain(struct omap_mcbsp *mcbsp) |
313 | { |
314 | u16 w; |
315 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
316 | |
317 | w = MCBSP_ST_READ(mcbsp, SSELCR); |
318 | |
319 | MCBSP_ST_WRITE(mcbsp, SGAINCR, ST_CH0GAIN(st_data->ch0gain) | \ |
320 | ST_CH1GAIN(st_data->ch1gain)); |
321 | } |
322 | |
323 | int omap_st_set_chgain(struct omap_mcbsp *mcbsp, int channel, s16 chgain) |
324 | { |
325 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
326 | int ret = 0; |
327 | |
328 | if (!st_data) |
329 | return -ENOENT; |
330 | |
331 | spin_lock_irq(&mcbsp->lock); |
332 | if (channel == 0) |
333 | st_data->ch0gain = chgain; |
334 | else if (channel == 1) |
335 | st_data->ch1gain = chgain; |
336 | else |
337 | ret = -EINVAL; |
338 | |
339 | if (st_data->enabled) |
340 | omap_st_chgain(mcbsp); |
341 | spin_unlock_irq(&mcbsp->lock); |
342 | |
343 | return ret; |
344 | } |
345 | |
346 | int omap_st_get_chgain(struct omap_mcbsp *mcbsp, int channel, s16 *chgain) |
347 | { |
348 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
349 | int ret = 0; |
350 | |
351 | if (!st_data) |
352 | return -ENOENT; |
353 | |
354 | spin_lock_irq(&mcbsp->lock); |
355 | if (channel == 0) |
356 | *chgain = st_data->ch0gain; |
357 | else if (channel == 1) |
358 | *chgain = st_data->ch1gain; |
359 | else |
360 | ret = -EINVAL; |
361 | spin_unlock_irq(&mcbsp->lock); |
362 | |
363 | return ret; |
364 | } |
365 | |
366 | static int omap_st_start(struct omap_mcbsp *mcbsp) |
367 | { |
368 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
369 | |
370 | if (st_data->enabled && !st_data->running) { |
371 | omap_st_fir_write(mcbsp, st_data->taps); |
372 | omap_st_chgain(mcbsp); |
373 | |
374 | if (!mcbsp->free) { |
375 | omap_st_on(mcbsp); |
376 | st_data->running = 1; |
377 | } |
378 | } |
379 | |
380 | return 0; |
381 | } |
382 | |
383 | int omap_st_enable(struct omap_mcbsp *mcbsp) |
384 | { |
385 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
386 | |
387 | if (!st_data) |
388 | return -ENODEV; |
389 | |
390 | spin_lock_irq(&mcbsp->lock); |
391 | st_data->enabled = 1; |
392 | omap_st_start(mcbsp); |
393 | spin_unlock_irq(&mcbsp->lock); |
394 | |
395 | return 0; |
396 | } |
397 | |
398 | static int omap_st_stop(struct omap_mcbsp *mcbsp) |
399 | { |
400 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
401 | |
402 | if (st_data->running) { |
403 | if (!mcbsp->free) { |
404 | omap_st_off(mcbsp); |
405 | st_data->running = 0; |
406 | } |
407 | } |
408 | |
409 | return 0; |
410 | } |
411 | |
412 | int omap_st_disable(struct omap_mcbsp *mcbsp) |
413 | { |
414 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
415 | int ret = 0; |
416 | |
417 | if (!st_data) |
418 | return -ENODEV; |
419 | |
420 | spin_lock_irq(&mcbsp->lock); |
421 | omap_st_stop(mcbsp); |
422 | st_data->enabled = 0; |
423 | spin_unlock_irq(&mcbsp->lock); |
424 | |
425 | return ret; |
426 | } |
427 | |
428 | int omap_st_is_enabled(struct omap_mcbsp *mcbsp) |
429 | { |
430 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
431 | |
432 | if (!st_data) |
433 | return -ENODEV; |
434 | |
435 | return st_data->enabled; |
436 | } |
437 | |
438 | /* |
439 | * omap_mcbsp_set_rx_threshold configures the transmit threshold in words. |
440 | * The threshold parameter is 1 based, and it is converted (threshold - 1) |
441 | * for the THRSH2 register. |
442 | */ |
443 | void omap_mcbsp_set_tx_threshold(struct omap_mcbsp *mcbsp, u16 threshold) |
444 | { |
445 | if (mcbsp->pdata->buffer_size == 0) |
446 | return; |
447 | |
448 | if (threshold && threshold <= mcbsp->max_tx_thres) |
449 | MCBSP_WRITE(mcbsp, THRSH2, threshold - 1); |
450 | } |
451 | |
452 | /* |
453 | * omap_mcbsp_set_rx_threshold configures the receive threshold in words. |
454 | * The threshold parameter is 1 based, and it is converted (threshold - 1) |
455 | * for the THRSH1 register. |
456 | */ |
457 | void omap_mcbsp_set_rx_threshold(struct omap_mcbsp *mcbsp, u16 threshold) |
458 | { |
459 | if (mcbsp->pdata->buffer_size == 0) |
460 | return; |
461 | |
462 | if (threshold && threshold <= mcbsp->max_rx_thres) |
463 | MCBSP_WRITE(mcbsp, THRSH1, threshold - 1); |
464 | } |
465 | |
466 | /* |
467 | * omap_mcbsp_get_tx_delay returns the number of used slots in the McBSP FIFO |
468 | */ |
469 | u16 omap_mcbsp_get_tx_delay(struct omap_mcbsp *mcbsp) |
470 | { |
471 | u16 buffstat; |
472 | |
473 | if (mcbsp->pdata->buffer_size == 0) |
474 | return 0; |
475 | |
476 | /* Returns the number of free locations in the buffer */ |
477 | buffstat = MCBSP_READ(mcbsp, XBUFFSTAT); |
478 | |
479 | /* Number of slots are different in McBSP ports */ |
480 | return mcbsp->pdata->buffer_size - buffstat; |
481 | } |
482 | |
483 | /* |
484 | * omap_mcbsp_get_rx_delay returns the number of free slots in the McBSP FIFO |
485 | * to reach the threshold value (when the DMA will be triggered to read it) |
486 | */ |
487 | u16 omap_mcbsp_get_rx_delay(struct omap_mcbsp *mcbsp) |
488 | { |
489 | u16 buffstat, threshold; |
490 | |
491 | if (mcbsp->pdata->buffer_size == 0) |
492 | return 0; |
493 | |
494 | /* Returns the number of used locations in the buffer */ |
495 | buffstat = MCBSP_READ(mcbsp, RBUFFSTAT); |
496 | /* RX threshold */ |
497 | threshold = MCBSP_READ(mcbsp, THRSH1); |
498 | |
499 | /* Return the number of location till we reach the threshold limit */ |
500 | if (threshold <= buffstat) |
501 | return 0; |
502 | else |
503 | return threshold - buffstat; |
504 | } |
505 | |
506 | int omap_mcbsp_request(struct omap_mcbsp *mcbsp) |
507 | { |
508 | void *reg_cache; |
509 | int err; |
510 | |
511 | reg_cache = kzalloc(mcbsp->reg_cache_size, GFP_KERNEL); |
512 | if (!reg_cache) { |
513 | return -ENOMEM; |
514 | } |
515 | |
516 | spin_lock(&mcbsp->lock); |
517 | if (!mcbsp->free) { |
518 | dev_err(mcbsp->dev, "McBSP%d is currently in use\n", |
519 | mcbsp->id); |
520 | err = -EBUSY; |
521 | goto err_kfree; |
522 | } |
523 | |
524 | mcbsp->free = false; |
525 | mcbsp->reg_cache = reg_cache; |
526 | spin_unlock(&mcbsp->lock); |
527 | |
528 | if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->request) |
529 | mcbsp->pdata->ops->request(mcbsp->id - 1); |
530 | |
531 | /* |
532 | * Make sure that transmitter, receiver and sample-rate generator are |
533 | * not running before activating IRQs. |
534 | */ |
535 | MCBSP_WRITE(mcbsp, SPCR1, 0); |
536 | MCBSP_WRITE(mcbsp, SPCR2, 0); |
537 | |
538 | if (mcbsp->irq) { |
539 | err = request_irq(mcbsp->irq, omap_mcbsp_irq_handler, 0, |
540 | "McBSP", (void *)mcbsp); |
541 | if (err != 0) { |
542 | dev_err(mcbsp->dev, "Unable to request IRQ\n"); |
543 | goto err_clk_disable; |
544 | } |
545 | } else { |
546 | err = request_irq(mcbsp->tx_irq, omap_mcbsp_tx_irq_handler, 0, |
547 | "McBSP TX", (void *)mcbsp); |
548 | if (err != 0) { |
549 | dev_err(mcbsp->dev, "Unable to request TX IRQ\n"); |
550 | goto err_clk_disable; |
551 | } |
552 | |
553 | err = request_irq(mcbsp->rx_irq, omap_mcbsp_rx_irq_handler, 0, |
554 | "McBSP RX", (void *)mcbsp); |
555 | if (err != 0) { |
556 | dev_err(mcbsp->dev, "Unable to request RX IRQ\n"); |
557 | goto err_free_irq; |
558 | } |
559 | } |
560 | |
561 | return 0; |
562 | err_free_irq: |
563 | free_irq(mcbsp->tx_irq, (void *)mcbsp); |
564 | err_clk_disable: |
565 | if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free) |
566 | mcbsp->pdata->ops->free(mcbsp->id - 1); |
567 | |
568 | /* Disable wakeup behavior */ |
569 | if (mcbsp->pdata->has_wakeup) |
570 | MCBSP_WRITE(mcbsp, WAKEUPEN, 0); |
571 | |
572 | spin_lock(&mcbsp->lock); |
573 | mcbsp->free = true; |
574 | mcbsp->reg_cache = NULL; |
575 | err_kfree: |
576 | spin_unlock(&mcbsp->lock); |
577 | kfree(reg_cache); |
578 | |
579 | return err; |
580 | } |
581 | |
582 | void omap_mcbsp_free(struct omap_mcbsp *mcbsp) |
583 | { |
584 | void *reg_cache; |
585 | |
586 | if (mcbsp->pdata && mcbsp->pdata->ops && mcbsp->pdata->ops->free) |
587 | mcbsp->pdata->ops->free(mcbsp->id - 1); |
588 | |
589 | /* Disable wakeup behavior */ |
590 | if (mcbsp->pdata->has_wakeup) |
591 | MCBSP_WRITE(mcbsp, WAKEUPEN, 0); |
592 | |
593 | /* Disable interrupt requests */ |
594 | if (mcbsp->irq) |
595 | MCBSP_WRITE(mcbsp, IRQEN, 0); |
596 | |
597 | if (mcbsp->irq) { |
598 | free_irq(mcbsp->irq, (void *)mcbsp); |
599 | } else { |
600 | free_irq(mcbsp->rx_irq, (void *)mcbsp); |
601 | free_irq(mcbsp->tx_irq, (void *)mcbsp); |
602 | } |
603 | |
604 | reg_cache = mcbsp->reg_cache; |
605 | |
606 | /* |
607 | * Select CLKS source from internal source unconditionally before |
608 | * marking the McBSP port as free. |
609 | * If the external clock source via MCBSP_CLKS pin has been selected the |
610 | * system will refuse to enter idle if the CLKS pin source is not reset |
611 | * back to internal source. |
612 | */ |
613 | if (!mcbsp_omap1()) |
614 | omap2_mcbsp_set_clks_src(mcbsp, MCBSP_CLKS_PRCM_SRC); |
615 | |
616 | spin_lock(&mcbsp->lock); |
617 | if (mcbsp->free) |
618 | dev_err(mcbsp->dev, "McBSP%d was not reserved\n", mcbsp->id); |
619 | else |
620 | mcbsp->free = true; |
621 | mcbsp->reg_cache = NULL; |
622 | spin_unlock(&mcbsp->lock); |
623 | |
624 | if (reg_cache) |
625 | kfree(reg_cache); |
626 | } |
627 | |
628 | /* |
629 | * Here we start the McBSP, by enabling transmitter, receiver or both. |
630 | * If no transmitter or receiver is active prior calling, then sample-rate |
631 | * generator and frame sync are started. |
632 | */ |
633 | void omap_mcbsp_start(struct omap_mcbsp *mcbsp, int tx, int rx) |
634 | { |
635 | int enable_srg = 0; |
636 | u16 w; |
637 | |
638 | if (mcbsp->st_data) |
639 | omap_st_start(mcbsp); |
640 | |
641 | /* Only enable SRG, if McBSP is master */ |
642 | w = MCBSP_READ_CACHE(mcbsp, PCR0); |
643 | if (w & (FSXM | FSRM | CLKXM | CLKRM)) |
644 | enable_srg = !((MCBSP_READ_CACHE(mcbsp, SPCR2) | |
645 | MCBSP_READ_CACHE(mcbsp, SPCR1)) & 1); |
646 | |
647 | if (enable_srg) { |
648 | /* Start the sample generator */ |
649 | w = MCBSP_READ_CACHE(mcbsp, SPCR2); |
650 | MCBSP_WRITE(mcbsp, SPCR2, w | (1 << 6)); |
651 | } |
652 | |
653 | /* Enable transmitter and receiver */ |
654 | tx &= 1; |
655 | w = MCBSP_READ_CACHE(mcbsp, SPCR2); |
656 | MCBSP_WRITE(mcbsp, SPCR2, w | tx); |
657 | |
658 | rx &= 1; |
659 | w = MCBSP_READ_CACHE(mcbsp, SPCR1); |
660 | MCBSP_WRITE(mcbsp, SPCR1, w | rx); |
661 | |
662 | /* |
663 | * Worst case: CLKSRG*2 = 8000khz: (1/8000) * 2 * 2 usec |
664 | * REVISIT: 100us may give enough time for two CLKSRG, however |
665 | * due to some unknown PM related, clock gating etc. reason it |
666 | * is now at 500us. |
667 | */ |
668 | udelay(500); |
669 | |
670 | if (enable_srg) { |
671 | /* Start frame sync */ |
672 | w = MCBSP_READ_CACHE(mcbsp, SPCR2); |
673 | MCBSP_WRITE(mcbsp, SPCR2, w | (1 << 7)); |
674 | } |
675 | |
676 | if (mcbsp->pdata->has_ccr) { |
677 | /* Release the transmitter and receiver */ |
678 | w = MCBSP_READ_CACHE(mcbsp, XCCR); |
679 | w &= ~(tx ? XDISABLE : 0); |
680 | MCBSP_WRITE(mcbsp, XCCR, w); |
681 | w = MCBSP_READ_CACHE(mcbsp, RCCR); |
682 | w &= ~(rx ? RDISABLE : 0); |
683 | MCBSP_WRITE(mcbsp, RCCR, w); |
684 | } |
685 | |
686 | /* Dump McBSP Regs */ |
687 | omap_mcbsp_dump_reg(mcbsp); |
688 | } |
689 | |
690 | void omap_mcbsp_stop(struct omap_mcbsp *mcbsp, int tx, int rx) |
691 | { |
692 | int idle; |
693 | u16 w; |
694 | |
695 | /* Reset transmitter */ |
696 | tx &= 1; |
697 | if (mcbsp->pdata->has_ccr) { |
698 | w = MCBSP_READ_CACHE(mcbsp, XCCR); |
699 | w |= (tx ? XDISABLE : 0); |
700 | MCBSP_WRITE(mcbsp, XCCR, w); |
701 | } |
702 | w = MCBSP_READ_CACHE(mcbsp, SPCR2); |
703 | MCBSP_WRITE(mcbsp, SPCR2, w & ~tx); |
704 | |
705 | /* Reset receiver */ |
706 | rx &= 1; |
707 | if (mcbsp->pdata->has_ccr) { |
708 | w = MCBSP_READ_CACHE(mcbsp, RCCR); |
709 | w |= (rx ? RDISABLE : 0); |
710 | MCBSP_WRITE(mcbsp, RCCR, w); |
711 | } |
712 | w = MCBSP_READ_CACHE(mcbsp, SPCR1); |
713 | MCBSP_WRITE(mcbsp, SPCR1, w & ~rx); |
714 | |
715 | idle = !((MCBSP_READ_CACHE(mcbsp, SPCR2) | |
716 | MCBSP_READ_CACHE(mcbsp, SPCR1)) & 1); |
717 | |
718 | if (idle) { |
719 | /* Reset the sample rate generator */ |
720 | w = MCBSP_READ_CACHE(mcbsp, SPCR2); |
721 | MCBSP_WRITE(mcbsp, SPCR2, w & ~(1 << 6)); |
722 | } |
723 | |
724 | if (mcbsp->st_data) |
725 | omap_st_stop(mcbsp); |
726 | } |
727 | |
728 | int omap2_mcbsp_set_clks_src(struct omap_mcbsp *mcbsp, u8 fck_src_id) |
729 | { |
730 | struct clk *fck_src; |
731 | const char *src; |
732 | int r; |
733 | |
734 | if (fck_src_id == MCBSP_CLKS_PAD_SRC) |
735 | src = "pad_fck"; |
736 | else if (fck_src_id == MCBSP_CLKS_PRCM_SRC) |
737 | src = "prcm_fck"; |
738 | else |
739 | return -EINVAL; |
740 | |
741 | fck_src = clk_get(mcbsp->dev, src); |
742 | if (IS_ERR(fck_src)) { |
743 | dev_err(mcbsp->dev, "CLKS: could not clk_get() %s\n", src); |
744 | return -EINVAL; |
745 | } |
746 | |
747 | pm_runtime_put_sync(mcbsp->dev); |
748 | |
749 | r = clk_set_parent(mcbsp->fclk, fck_src); |
750 | if (r) { |
751 | dev_err(mcbsp->dev, "CLKS: could not clk_set_parent() to %s\n", |
752 | src); |
753 | clk_put(fck_src); |
754 | return r; |
755 | } |
756 | |
757 | pm_runtime_get_sync(mcbsp->dev); |
758 | |
759 | clk_put(fck_src); |
760 | |
761 | return 0; |
762 | |
763 | } |
764 | |
765 | #define max_thres(m) (mcbsp->pdata->buffer_size) |
766 | #define valid_threshold(m, val) ((val) <= max_thres(m)) |
767 | #define THRESHOLD_PROP_BUILDER(prop) \ |
768 | static ssize_t prop##_show(struct device *dev, \ |
769 | struct device_attribute *attr, char *buf) \ |
770 | { \ |
771 | struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \ |
772 | \ |
773 | return sprintf(buf, "%u\n", mcbsp->prop); \ |
774 | } \ |
775 | \ |
776 | static ssize_t prop##_store(struct device *dev, \ |
777 | struct device_attribute *attr, \ |
778 | const char *buf, size_t size) \ |
779 | { \ |
780 | struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); \ |
781 | unsigned long val; \ |
782 | int status; \ |
783 | \ |
784 | status = strict_strtoul(buf, 0, &val); \ |
785 | if (status) \ |
786 | return status; \ |
787 | \ |
788 | if (!valid_threshold(mcbsp, val)) \ |
789 | return -EDOM; \ |
790 | \ |
791 | mcbsp->prop = val; \ |
792 | return size; \ |
793 | } \ |
794 | \ |
795 | static DEVICE_ATTR(prop, 0644, prop##_show, prop##_store); |
796 | |
797 | THRESHOLD_PROP_BUILDER(max_tx_thres); |
798 | THRESHOLD_PROP_BUILDER(max_rx_thres); |
799 | |
800 | static const char *dma_op_modes[] = { |
801 | "element", "threshold", |
802 | }; |
803 | |
804 | static ssize_t dma_op_mode_show(struct device *dev, |
805 | struct device_attribute *attr, char *buf) |
806 | { |
807 | struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); |
808 | int dma_op_mode, i = 0; |
809 | ssize_t len = 0; |
810 | const char * const *s; |
811 | |
812 | dma_op_mode = mcbsp->dma_op_mode; |
813 | |
814 | for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++) { |
815 | if (dma_op_mode == i) |
816 | len += sprintf(buf + len, "[%s] ", *s); |
817 | else |
818 | len += sprintf(buf + len, "%s ", *s); |
819 | } |
820 | len += sprintf(buf + len, "\n"); |
821 | |
822 | return len; |
823 | } |
824 | |
825 | static ssize_t dma_op_mode_store(struct device *dev, |
826 | struct device_attribute *attr, |
827 | const char *buf, size_t size) |
828 | { |
829 | struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); |
830 | const char * const *s; |
831 | int i = 0; |
832 | |
833 | for (s = &dma_op_modes[i]; i < ARRAY_SIZE(dma_op_modes); s++, i++) |
834 | if (sysfs_streq(buf, *s)) |
835 | break; |
836 | |
837 | if (i == ARRAY_SIZE(dma_op_modes)) |
838 | return -EINVAL; |
839 | |
840 | spin_lock_irq(&mcbsp->lock); |
841 | if (!mcbsp->free) { |
842 | size = -EBUSY; |
843 | goto unlock; |
844 | } |
845 | mcbsp->dma_op_mode = i; |
846 | |
847 | unlock: |
848 | spin_unlock_irq(&mcbsp->lock); |
849 | |
850 | return size; |
851 | } |
852 | |
853 | static DEVICE_ATTR(dma_op_mode, 0644, dma_op_mode_show, dma_op_mode_store); |
854 | |
855 | static const struct attribute *additional_attrs[] = { |
856 | &dev_attr_max_tx_thres.attr, |
857 | &dev_attr_max_rx_thres.attr, |
858 | &dev_attr_dma_op_mode.attr, |
859 | NULL, |
860 | }; |
861 | |
862 | static const struct attribute_group additional_attr_group = { |
863 | .attrs = (struct attribute **)additional_attrs, |
864 | }; |
865 | |
866 | static ssize_t st_taps_show(struct device *dev, |
867 | struct device_attribute *attr, char *buf) |
868 | { |
869 | struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); |
870 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
871 | ssize_t status = 0; |
872 | int i; |
873 | |
874 | spin_lock_irq(&mcbsp->lock); |
875 | for (i = 0; i < st_data->nr_taps; i++) |
876 | status += sprintf(&buf[status], (i ? ", %d" : "%d"), |
877 | st_data->taps[i]); |
878 | if (i) |
879 | status += sprintf(&buf[status], "\n"); |
880 | spin_unlock_irq(&mcbsp->lock); |
881 | |
882 | return status; |
883 | } |
884 | |
885 | static ssize_t st_taps_store(struct device *dev, |
886 | struct device_attribute *attr, |
887 | const char *buf, size_t size) |
888 | { |
889 | struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); |
890 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
891 | int val, tmp, status, i = 0; |
892 | |
893 | spin_lock_irq(&mcbsp->lock); |
894 | memset(st_data->taps, 0, sizeof(st_data->taps)); |
895 | st_data->nr_taps = 0; |
896 | |
897 | do { |
898 | status = sscanf(buf, "%d%n", &val, &tmp); |
899 | if (status < 0 || status == 0) { |
900 | size = -EINVAL; |
901 | goto out; |
902 | } |
903 | if (val < -32768 || val > 32767) { |
904 | size = -EINVAL; |
905 | goto out; |
906 | } |
907 | st_data->taps[i++] = val; |
908 | buf += tmp; |
909 | if (*buf != ',') |
910 | break; |
911 | buf++; |
912 | } while (1); |
913 | |
914 | st_data->nr_taps = i; |
915 | |
916 | out: |
917 | spin_unlock_irq(&mcbsp->lock); |
918 | |
919 | return size; |
920 | } |
921 | |
922 | static DEVICE_ATTR(st_taps, 0644, st_taps_show, st_taps_store); |
923 | |
924 | static const struct attribute *sidetone_attrs[] = { |
925 | &dev_attr_st_taps.attr, |
926 | NULL, |
927 | }; |
928 | |
929 | static const struct attribute_group sidetone_attr_group = { |
930 | .attrs = (struct attribute **)sidetone_attrs, |
931 | }; |
932 | |
933 | static int omap_st_add(struct omap_mcbsp *mcbsp, struct resource *res) |
934 | { |
935 | struct omap_mcbsp_st_data *st_data; |
936 | int err; |
937 | |
938 | st_data = devm_kzalloc(mcbsp->dev, sizeof(*mcbsp->st_data), GFP_KERNEL); |
939 | if (!st_data) |
940 | return -ENOMEM; |
941 | |
942 | st_data->io_base_st = devm_ioremap(mcbsp->dev, res->start, |
943 | resource_size(res)); |
944 | if (!st_data->io_base_st) |
945 | return -ENOMEM; |
946 | |
947 | err = sysfs_create_group(&mcbsp->dev->kobj, &sidetone_attr_group); |
948 | if (err) |
949 | return err; |
950 | |
951 | mcbsp->st_data = st_data; |
952 | return 0; |
953 | } |
954 | |
955 | /* |
956 | * McBSP1 and McBSP3 are directly mapped on 1610 and 1510. |
957 | * 730 has only 2 McBSP, and both of them are MPU peripherals. |
958 | */ |
959 | int omap_mcbsp_init(struct platform_device *pdev) |
960 | { |
961 | struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev); |
962 | struct resource *res; |
963 | int ret = 0; |
964 | |
965 | spin_lock_init(&mcbsp->lock); |
966 | mcbsp->free = true; |
967 | |
968 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mpu"); |
969 | if (!res) { |
970 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
971 | if (!res) { |
972 | dev_err(mcbsp->dev, "invalid memory resource\n"); |
973 | return -ENOMEM; |
974 | } |
975 | } |
976 | if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), |
977 | dev_name(&pdev->dev))) { |
978 | dev_err(mcbsp->dev, "memory region already claimed\n"); |
979 | return -ENODEV; |
980 | } |
981 | |
982 | mcbsp->phys_base = res->start; |
983 | mcbsp->reg_cache_size = resource_size(res); |
984 | mcbsp->io_base = devm_ioremap(&pdev->dev, res->start, |
985 | resource_size(res)); |
986 | if (!mcbsp->io_base) |
987 | return -ENOMEM; |
988 | |
989 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); |
990 | if (!res) |
991 | mcbsp->phys_dma_base = mcbsp->phys_base; |
992 | else |
993 | mcbsp->phys_dma_base = res->start; |
994 | |
995 | /* |
996 | * OMAP1, 2 uses two interrupt lines: TX, RX |
997 | * OMAP2430, OMAP3 SoC have combined IRQ line as well. |
998 | * OMAP4 and newer SoC only have the combined IRQ line. |
999 | * Use the combined IRQ if available since it gives better debugging |
1000 | * possibilities. |
1001 | */ |
1002 | mcbsp->irq = platform_get_irq_byname(pdev, "common"); |
1003 | if (mcbsp->irq == -ENXIO) { |
1004 | mcbsp->tx_irq = platform_get_irq_byname(pdev, "tx"); |
1005 | |
1006 | if (mcbsp->tx_irq == -ENXIO) { |
1007 | mcbsp->irq = platform_get_irq(pdev, 0); |
1008 | mcbsp->tx_irq = 0; |
1009 | } else { |
1010 | mcbsp->rx_irq = platform_get_irq_byname(pdev, "rx"); |
1011 | mcbsp->irq = 0; |
1012 | } |
1013 | } |
1014 | |
1015 | res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); |
1016 | if (!res) { |
1017 | dev_err(&pdev->dev, "invalid rx DMA channel\n"); |
1018 | return -ENODEV; |
1019 | } |
1020 | /* RX DMA request number, and port address configuration */ |
1021 | mcbsp->dma_req[1] = res->start; |
1022 | mcbsp->dma_data[1].filter_data = &mcbsp->dma_req[1]; |
1023 | mcbsp->dma_data[1].addr = omap_mcbsp_dma_reg_params(mcbsp, 1); |
1024 | mcbsp->dma_data[1].maxburst = 4; |
1025 | |
1026 | res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); |
1027 | if (!res) { |
1028 | dev_err(&pdev->dev, "invalid tx DMA channel\n"); |
1029 | return -ENODEV; |
1030 | } |
1031 | /* TX DMA request number, and port address configuration */ |
1032 | mcbsp->dma_req[0] = res->start; |
1033 | mcbsp->dma_data[0].filter_data = &mcbsp->dma_req[0]; |
1034 | mcbsp->dma_data[0].addr = omap_mcbsp_dma_reg_params(mcbsp, 0); |
1035 | mcbsp->dma_data[0].maxburst = 4; |
1036 | |
1037 | mcbsp->fclk = clk_get(&pdev->dev, "fck"); |
1038 | if (IS_ERR(mcbsp->fclk)) { |
1039 | ret = PTR_ERR(mcbsp->fclk); |
1040 | dev_err(mcbsp->dev, "unable to get fck: %d\n", ret); |
1041 | return ret; |
1042 | } |
1043 | |
1044 | mcbsp->dma_op_mode = MCBSP_DMA_MODE_ELEMENT; |
1045 | if (mcbsp->pdata->buffer_size) { |
1046 | /* |
1047 | * Initially configure the maximum thresholds to a safe value. |
1048 | * The McBSP FIFO usage with these values should not go under |
1049 | * 16 locations. |
1050 | * If the whole FIFO without safety buffer is used, than there |
1051 | * is a possibility that the DMA will be not able to push the |
1052 | * new data on time, causing channel shifts in runtime. |
1053 | */ |
1054 | mcbsp->max_tx_thres = max_thres(mcbsp) - 0x10; |
1055 | mcbsp->max_rx_thres = max_thres(mcbsp) - 0x10; |
1056 | |
1057 | ret = sysfs_create_group(&mcbsp->dev->kobj, |
1058 | &additional_attr_group); |
1059 | if (ret) { |
1060 | dev_err(mcbsp->dev, |
1061 | "Unable to create additional controls\n"); |
1062 | goto err_thres; |
1063 | } |
1064 | } else { |
1065 | mcbsp->max_tx_thres = -EINVAL; |
1066 | mcbsp->max_rx_thres = -EINVAL; |
1067 | } |
1068 | |
1069 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sidetone"); |
1070 | if (res) { |
1071 | ret = omap_st_add(mcbsp, res); |
1072 | if (ret) { |
1073 | dev_err(mcbsp->dev, |
1074 | "Unable to create sidetone controls\n"); |
1075 | goto err_st; |
1076 | } |
1077 | } |
1078 | |
1079 | return 0; |
1080 | |
1081 | err_st: |
1082 | if (mcbsp->pdata->buffer_size) |
1083 | sysfs_remove_group(&mcbsp->dev->kobj, &additional_attr_group); |
1084 | err_thres: |
1085 | clk_put(mcbsp->fclk); |
1086 | return ret; |
1087 | } |
1088 | |
1089 | void omap_mcbsp_sysfs_remove(struct omap_mcbsp *mcbsp) |
1090 | { |
1091 | if (mcbsp->pdata->buffer_size) |
1092 | sysfs_remove_group(&mcbsp->dev->kobj, &additional_attr_group); |
1093 | |
1094 | if (mcbsp->st_data) |
1095 | sysfs_remove_group(&mcbsp->dev->kobj, &sidetone_attr_group); |
1096 | } |
1097 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9