Root/
Source at commit cc18a5b3a5ffbd720fb2dbd4f5d2c469bbfae417 created 12 years 5 months ago. By Maarten ter Huurne, media: radio: RDA5807: Re-tune on enable. | |
---|---|
1 | /* |
2 | * radio-rda5807.c - Driver for using the RDA5807 FM tuner chip via I2C |
3 | * |
4 | * Copyright (c) 2011 Maarten ter Huurne <maarten@treewalker.org> |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License version 2 as |
8 | * 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, write to the Free Software |
17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
18 | * |
19 | * |
20 | * Many thanks to Jérôme VERES for his command line radio application that |
21 | * demonstrates how the chip can be controlled via I2C. |
22 | * |
23 | * The RDA5807 has three ways of accessing registers: |
24 | * - I2C address 0x10: sequential access, RDA5800 style |
25 | * - I2C address 0x11: random access |
26 | * - I2C address 0x60: sequential access, TEA5767 compatible |
27 | * This driver uses random access and therefore the i2c_board_info should |
28 | * specify address 0x11. |
29 | * Note that while there are many similarities, the register map of the RDA5807 |
30 | * differs from that of the RDA5800 in several essential places. |
31 | */ |
32 | |
33 | |
34 | #include <asm/byteorder.h> |
35 | #include <linux/bitops.h> |
36 | #include <linux/module.h> |
37 | #include <linux/i2c.h> |
38 | #include <linux/kernel.h> |
39 | #include <linux/pm.h> |
40 | #include <linux/slab.h> |
41 | #include <linux/types.h> |
42 | #include <linux/videodev2.h> |
43 | #include <media/v4l2-ctrls.h> |
44 | #include <media/v4l2-dev.h> |
45 | #include <media/v4l2-ioctl.h> |
46 | |
47 | |
48 | enum rda5807_reg { |
49 | RDA5807_REG_CHIPID = 0x00, |
50 | RDA5807_REG_CTRL = 0x02, |
51 | RDA5807_REG_CHAN = 0x03, |
52 | RDA5807_REG_IOCFG = 0x04, |
53 | RDA5807_REG_INTM_THRESH_VOL = 0x05, |
54 | RDA5807_REG_SEEK_RESULT = 0x0A, |
55 | RDA5807_REG_SIGNAL = 0x0B, |
56 | }; |
57 | |
58 | #define RDA5807_MASK_CTRL_DHIZ BIT(15) |
59 | #define RDA5807_MASK_CTRL_DMUTE BIT(14) |
60 | #define RDA5807_MASK_CTRL_MONO BIT(13) |
61 | #define RDA5807_MASK_CTRL_BASS BIT(12) |
62 | #define RDA5807_MASK_CTRL_SEEKUP BIT(9) |
63 | #define RDA5807_MASK_CTRL_SEEK BIT(8) |
64 | #define RDA5807_MASK_CTRL_SKMODE BIT(7) |
65 | #define RDA5807_MASK_CTRL_CLKMODE (7 << 4) |
66 | #define RDA5807_MASK_CTRL_SOFTRESET BIT(1) |
67 | #define RDA5807_MASK_CTRL_ENABLE BIT(0) |
68 | |
69 | #define RDA5807_SHIFT_CHAN_WRCHAN 6 |
70 | #define RDA5807_MASK_CHAN_WRCHAN (0x3FF << RDA5807_SHIFT_CHAN_WRCHAN) |
71 | #define RDA5807_MASK_CHAN_TUNE BIT(4) |
72 | #define RDA5807_SHIFT_CHAN_BAND 2 |
73 | #define RDA5807_MASK_CHAN_BAND (0x3 << RDA5807_SHIFT_CHAN_BAND) |
74 | #define RDA5807_SHIFT_CHAN_SPACE 0 |
75 | #define RDA5807_MASK_CHAN_SPACE (0x3 << RDA5807_SHIFT_CHAN_SPACE) |
76 | |
77 | #define RDA5807_MASK_SEEKRES_COMPLETE BIT(14) |
78 | #define RDA5807_MASK_SEEKRES_FAIL BIT(13) |
79 | #define RDA5807_MASK_SEEKRES_STEREO BIT(10) |
80 | |
81 | #define RDA5807_MASK_DEEMPHASIS BIT(11) |
82 | |
83 | #define RDA5807_SHIFT_VOLUME_DAC 0 |
84 | #define RDA5807_MASK_VOLUME_DAC (0xF << RDA5807_SHIFT_VOLUME_DAC) |
85 | |
86 | #define RDA5807_SHIFT_RSSI 9 |
87 | #define RDA5807_MASK_RSSI (0x7F << RDA5807_SHIFT_RSSI) |
88 | |
89 | #define RDA5807_FREQ_MIN_KHZ 76000 |
90 | #define RDA5807_FREQ_MAX_KHZ 108000 |
91 | |
92 | static int rda5807_i2c_read(struct i2c_client *client, enum rda5807_reg reg) |
93 | { |
94 | __u8 reg_buf = reg; |
95 | __u16 val_buf; |
96 | struct i2c_msg msgs[] = { |
97 | { /* write register number */ |
98 | .addr = client->addr, |
99 | .flags = 0, |
100 | .len = sizeof(reg_buf), |
101 | .buf = ®_buf, |
102 | }, |
103 | { /* read register contents */ |
104 | .addr = client->addr, |
105 | .flags = I2C_M_RD, |
106 | .len = sizeof(val_buf), |
107 | .buf = (__u8 *)&val_buf, |
108 | }, |
109 | }; |
110 | int err; |
111 | |
112 | err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); |
113 | if (err < 0) return err; |
114 | if (err < ARRAY_SIZE(msgs)) return -EIO; |
115 | |
116 | dev_info(&client->dev, "reg[%02X] = %04X\n", reg, be16_to_cpu(val_buf)); |
117 | return be16_to_cpu(val_buf); |
118 | } |
119 | |
120 | static int rda5807_i2c_write(struct i2c_client *client, enum rda5807_reg reg, |
121 | u16 val) |
122 | { |
123 | __u8 buf[] = { reg, val >> 8, val & 0xFF }; |
124 | struct i2c_msg msgs[] = { |
125 | { /* write register number and contents */ |
126 | .addr = client->addr, |
127 | .flags = 0, |
128 | .len = sizeof(buf), |
129 | .buf = buf, |
130 | }, |
131 | }; |
132 | int err; |
133 | |
134 | err = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); |
135 | if (err < 0) return err; |
136 | if (err < ARRAY_SIZE(msgs)) return -EIO; |
137 | |
138 | dev_info(&client->dev, "reg[%02X] := %04X\n", reg, val); |
139 | return 0; |
140 | } |
141 | |
142 | struct rda5807_driver { |
143 | struct v4l2_ctrl_handler ctrl_handler; |
144 | struct video_device video_dev; |
145 | struct i2c_client *i2c_client; |
146 | }; |
147 | |
148 | static const struct v4l2_file_operations rda5807_fops = { |
149 | .owner = THIS_MODULE, |
150 | .unlocked_ioctl = video_ioctl2, |
151 | }; |
152 | |
153 | static int rda5807_update_reg(struct rda5807_driver *radio, |
154 | enum rda5807_reg reg, u16 mask, u16 val) |
155 | { |
156 | int err = 0; |
157 | // TODO: Locking. |
158 | // Or do locking in the caller, in case we ever need to update |
159 | // two registers in one operation? |
160 | err = rda5807_i2c_read(radio->i2c_client, reg); |
161 | if (err >= 0) { |
162 | val |= ((u16)err & ~mask); |
163 | err = rda5807_i2c_write(radio->i2c_client, reg, val); |
164 | } |
165 | return err; |
166 | } |
167 | |
168 | static int rda5807_set_enable(struct rda5807_driver *radio, int enabled) |
169 | { |
170 | u16 val = enabled ? RDA5807_MASK_CTRL_ENABLE : 0; |
171 | int err; |
172 | dev_info(&radio->i2c_client->dev, "set enabled to %d\n", enabled); |
173 | err = rda5807_update_reg(radio, RDA5807_REG_CTRL, |
174 | RDA5807_MASK_CTRL_ENABLE, val); |
175 | if (err < 0) |
176 | return err; |
177 | /* Tuning is lost when the chip is disabled, so re-tune when enabled. */ |
178 | if (enabled) |
179 | return rda5807_update_reg(radio, RDA5807_REG_CHAN, |
180 | RDA5807_MASK_CHAN_TUNE, |
181 | RDA5807_MASK_CHAN_TUNE); |
182 | else |
183 | return 0; |
184 | } |
185 | |
186 | static int rda5807_set_mute(struct rda5807_driver *radio, int muted) |
187 | { |
188 | u16 val = muted ? 0 : RDA5807_MASK_CTRL_DMUTE /* disable mute */; |
189 | dev_info(&radio->i2c_client->dev, "set mute to %d\n", muted); |
190 | return rda5807_update_reg(radio, RDA5807_REG_CTRL, |
191 | RDA5807_MASK_CTRL_DMUTE, val); |
192 | } |
193 | |
194 | static int rda5807_set_volume(struct rda5807_driver *radio, int volume) |
195 | { |
196 | dev_info(&radio->i2c_client->dev, "set volume to %d\n", volume); |
197 | return rda5807_update_reg(radio, RDA5807_REG_INTM_THRESH_VOL, |
198 | RDA5807_MASK_VOLUME_DAC, |
199 | volume << RDA5807_SHIFT_VOLUME_DAC); |
200 | } |
201 | |
202 | static int rda5807_set_preemphasis(struct rda5807_driver *radio, |
203 | enum v4l2_preemphasis preemp) |
204 | { |
205 | dev_info(&radio->i2c_client->dev, "set preemphasis to %d\n", preemp); |
206 | return rda5807_update_reg(radio, RDA5807_REG_IOCFG, |
207 | RDA5807_MASK_DEEMPHASIS, |
208 | preemp == V4L2_PREEMPHASIS_50_uS |
209 | ? RDA5807_MASK_DEEMPHASIS : 0); |
210 | } |
211 | |
212 | static int rda5807_set_frequency(struct rda5807_driver *radio, u32 freq_khz) |
213 | { |
214 | u16 mask = 0; |
215 | u16 val = 0; |
216 | |
217 | dev_info(&radio->i2c_client->dev, "set freq to %u kHz\n", freq_khz); |
218 | |
219 | if (freq_khz < RDA5807_FREQ_MIN_KHZ) |
220 | return -ERANGE; |
221 | if (freq_khz > RDA5807_FREQ_MAX_KHZ) |
222 | return -ERANGE; |
223 | |
224 | /* select widest band */ |
225 | mask |= RDA5807_MASK_CHAN_BAND; |
226 | val |= 2 << RDA5807_SHIFT_CHAN_BAND; |
227 | /* select 50 kHz channel spacing */ |
228 | mask |= RDA5807_MASK_CHAN_SPACE; |
229 | val |= 2 << RDA5807_SHIFT_CHAN_SPACE; |
230 | /* select frequency */ |
231 | mask |= RDA5807_MASK_CHAN_WRCHAN; |
232 | val |= ((freq_khz - RDA5807_FREQ_MIN_KHZ + 25) / 50) |
233 | << RDA5807_SHIFT_CHAN_WRCHAN; |
234 | /* start tune operation */ |
235 | mask |= RDA5807_MASK_CHAN_TUNE; |
236 | val |= RDA5807_MASK_CHAN_TUNE; |
237 | |
238 | return rda5807_update_reg(radio, RDA5807_REG_CHAN, mask, val); |
239 | } |
240 | |
241 | static inline struct rda5807_driver *ctrl_to_radio(struct v4l2_ctrl *ctrl) |
242 | { |
243 | return container_of(ctrl->handler, struct rda5807_driver, ctrl_handler); |
244 | } |
245 | |
246 | static int rda5807_s_ctrl(struct v4l2_ctrl *ctrl) |
247 | { |
248 | struct rda5807_driver *radio = ctrl_to_radio(ctrl); |
249 | |
250 | switch (ctrl->id) { |
251 | case V4L2_CID_AUDIO_MUTE: { |
252 | /* Disable the radio while muted, to save power. |
253 | * TODO: We can't seek while the radio is disabled; |
254 | * is that a problem? |
255 | */ |
256 | int err1 = rda5807_set_enable(radio, !ctrl->val); |
257 | int err2 = rda5807_set_mute(radio, ctrl->val); |
258 | return err1 ? err1 : err2; |
259 | } |
260 | case V4L2_CID_AUDIO_VOLUME: |
261 | return rda5807_set_volume(radio, ctrl->val); |
262 | case V4L2_CID_TUNE_PREEMPHASIS: |
263 | return rda5807_set_preemphasis(radio, ctrl->val); |
264 | default: |
265 | return -EINVAL; |
266 | } |
267 | } |
268 | |
269 | static const struct v4l2_ctrl_ops rda5807_ctrl_ops = { |
270 | .s_ctrl = rda5807_s_ctrl, |
271 | }; |
272 | |
273 | static int rda5807_vidioc_g_audio(struct file *file, void *fh, |
274 | struct v4l2_audio *a) |
275 | { |
276 | if (a->index != 0) |
277 | return -EINVAL; |
278 | |
279 | *a = (struct v4l2_audio) { |
280 | .name = "Radio", |
281 | .capability = V4L2_AUDCAP_STEREO, |
282 | .mode = 0, |
283 | }; |
284 | |
285 | return 0; |
286 | } |
287 | |
288 | static int rda5807_vidioc_g_tuner(struct file *file, void *fh, |
289 | struct v4l2_tuner *a) |
290 | { |
291 | struct rda5807_driver *radio = video_drvdata(file); |
292 | int err; |
293 | u16 seekres, signal; |
294 | __u32 rxsubchans; |
295 | |
296 | if (a->index != 0) |
297 | return -EINVAL; |
298 | |
299 | err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SEEK_RESULT); |
300 | if (err < 0) |
301 | return err; |
302 | seekres = (u16)err; |
303 | if ((seekres & (RDA5807_MASK_SEEKRES_COMPLETE |
304 | | RDA5807_MASK_SEEKRES_FAIL)) |
305 | == RDA5807_MASK_SEEKRES_COMPLETE) |
306 | /* mono/stereo known */ |
307 | rxsubchans = seekres & RDA5807_MASK_SEEKRES_STEREO |
308 | ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; |
309 | else |
310 | /* mono/stereo unknown */ |
311 | rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; |
312 | |
313 | err = rda5807_i2c_read(radio->i2c_client, RDA5807_REG_SIGNAL); |
314 | if (err < 0) |
315 | return err; |
316 | signal = ((u16)err & RDA5807_MASK_RSSI) >> RDA5807_SHIFT_RSSI; |
317 | |
318 | *a = (struct v4l2_tuner) { |
319 | .name = "FM", |
320 | .type = V4L2_TUNER_RADIO, |
321 | .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO, |
322 | /* unit is 1/16 kHz */ |
323 | .rangelow = RDA5807_FREQ_MIN_KHZ * 16, |
324 | .rangehigh = RDA5807_FREQ_MAX_KHZ * 16, |
325 | .rxsubchans = rxsubchans, |
326 | /* TODO: Implement forced mono (RDA5807_MASK_CTRL_MONO). */ |
327 | .audmode = V4L2_TUNER_MODE_STEREO, |
328 | .signal = signal << (16 - 7), |
329 | .afc = 0, /* automatic frequency control */ |
330 | }; |
331 | |
332 | return 0; |
333 | } |
334 | |
335 | static int rda5807_vidioc_s_frequency(struct file *file, void *fh, |
336 | struct v4l2_frequency *a) |
337 | { |
338 | struct rda5807_driver *radio = video_drvdata(file); |
339 | |
340 | if (a->tuner != 0) |
341 | return -EINVAL; |
342 | if (a->type != V4L2_TUNER_RADIO) |
343 | return -EINVAL; |
344 | |
345 | return rda5807_set_frequency(radio, (a->frequency * 625) / 10000); |
346 | } |
347 | |
348 | static const struct v4l2_ioctl_ops rda5807_ioctl_ops = { |
349 | .vidioc_g_audio = rda5807_vidioc_g_audio, |
350 | .vidioc_g_tuner = rda5807_vidioc_g_tuner, |
351 | .vidioc_s_frequency = rda5807_vidioc_s_frequency, |
352 | }; |
353 | |
354 | static int __devinit rda5807_i2c_probe(struct i2c_client *client, |
355 | const struct i2c_device_id *id) |
356 | { |
357 | struct rda5807_driver *radio; |
358 | int chipid; |
359 | int err; |
360 | |
361 | chipid = rda5807_i2c_read(client, RDA5807_REG_CHIPID); |
362 | if (chipid < 0) { |
363 | dev_warn(&client->dev, "Failed to read chip ID (%d)\n", chipid); |
364 | return chipid; |
365 | } |
366 | if ((chipid & 0xFF00) != 0x5800) { |
367 | dev_warn(&client->dev, "Chip ID mismatch: " |
368 | "expected 58xx, got %04X\n", chipid); |
369 | return -ENODEV; |
370 | } |
371 | dev_info(&client->dev, "Found FM radio receiver\n"); |
372 | |
373 | // TODO: Resetting the chip would be good. |
374 | |
375 | radio = kzalloc(sizeof(*radio), GFP_KERNEL); |
376 | if (!radio) { |
377 | dev_warn(&client->dev, "Failed to allocate driver data\n"); |
378 | return -ENOMEM; |
379 | } |
380 | |
381 | radio->i2c_client = client; |
382 | |
383 | /* Initialize controls. */ |
384 | v4l2_ctrl_handler_init(&radio->ctrl_handler, 3); |
385 | v4l2_ctrl_new_std(&radio->ctrl_handler, &rda5807_ctrl_ops, |
386 | V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); |
387 | v4l2_ctrl_new_std(&radio->ctrl_handler, &rda5807_ctrl_ops, |
388 | V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 8); |
389 | v4l2_ctrl_new_std_menu(&radio->ctrl_handler, &rda5807_ctrl_ops, |
390 | V4L2_CID_TUNE_PREEMPHASIS, |
391 | V4L2_PREEMPHASIS_75_uS, |
392 | BIT(V4L2_PREEMPHASIS_DISABLED), |
393 | V4L2_PREEMPHASIS_50_uS); |
394 | err = radio->ctrl_handler.error; |
395 | if (err) { |
396 | dev_warn(&client->dev, "Failed to init controls handler" |
397 | " (%d)\n", err); |
398 | goto err_ctrl_free; |
399 | } |
400 | |
401 | radio->video_dev = (struct video_device) { |
402 | .name = "RDA5807 FM receiver", |
403 | .flags = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG, |
404 | .ctrl_handler = &radio->ctrl_handler, |
405 | .fops = &rda5807_fops, |
406 | .ioctl_ops = &rda5807_ioctl_ops, |
407 | .release = video_device_release_empty, |
408 | //.lock = &radio->lock, |
409 | }; |
410 | i2c_set_clientdata(client, radio); |
411 | video_set_drvdata(&radio->video_dev, radio); |
412 | |
413 | err = video_register_device(&radio->video_dev, VFL_TYPE_RADIO, -1); |
414 | if (err < 0) { |
415 | dev_warn(&client->dev, "Failed to register video device (%d)\n", |
416 | err); |
417 | goto err_ctrl_free; |
418 | } |
419 | |
420 | err = v4l2_ctrl_handler_setup(&radio->ctrl_handler); |
421 | if (err < 0) { |
422 | dev_warn(&client->dev, "Failed to set default control values" |
423 | " (%d)\n", err); |
424 | goto err_video_unreg; |
425 | } |
426 | |
427 | return 0; |
428 | |
429 | err_video_unreg: |
430 | video_unregister_device(&radio->video_dev); |
431 | |
432 | err_ctrl_free: |
433 | v4l2_ctrl_handler_free(&radio->ctrl_handler); |
434 | |
435 | /*err_radio_rel:*/ |
436 | video_device_release_empty(&radio->video_dev); |
437 | kfree(radio); |
438 | |
439 | return err; |
440 | } |
441 | |
442 | static int __devexit rda5807_i2c_remove(struct i2c_client *client) |
443 | { |
444 | struct rda5807_driver *radio = i2c_get_clientdata(client); |
445 | |
446 | video_unregister_device(&radio->video_dev); |
447 | v4l2_ctrl_handler_free(&radio->ctrl_handler); |
448 | video_device_release_empty(&radio->video_dev); |
449 | kfree(radio); |
450 | |
451 | return 0; |
452 | } |
453 | |
454 | #ifdef CONFIG_PM |
455 | |
456 | static int rda5807_suspend(struct device *dev) |
457 | { |
458 | struct i2c_client *client = to_i2c_client(dev); |
459 | struct rda5807_driver *radio = i2c_get_clientdata(client); |
460 | |
461 | return rda5807_set_enable(radio, 0); |
462 | } |
463 | |
464 | static int rda5807_resume(struct device *dev) |
465 | { |
466 | struct i2c_client *client = to_i2c_client(dev); |
467 | struct rda5807_driver *radio = i2c_get_clientdata(client); |
468 | struct v4l2_ctrl *mute_ctrl = v4l2_ctrl_find(&radio->ctrl_handler, |
469 | V4L2_CID_AUDIO_MUTE); |
470 | s32 mute_val = v4l2_ctrl_g_ctrl(mute_ctrl); |
471 | int enabled = !mute_val; |
472 | |
473 | if (enabled) |
474 | return rda5807_set_enable(radio, enabled); |
475 | else |
476 | return 0; |
477 | } |
478 | |
479 | static SIMPLE_DEV_PM_OPS(rda5807_pm_ops, rda5807_suspend, rda5807_resume); |
480 | #define RDA5807_PM_OPS (&rda5807_pm_ops) |
481 | |
482 | #else |
483 | |
484 | #define RDA5807_PM_OPS NULL |
485 | |
486 | #endif |
487 | |
488 | static const struct i2c_device_id rda5807_id[] = { |
489 | { "radio-rda5807", 0 }, |
490 | { } |
491 | }; |
492 | MODULE_DEVICE_TABLE(i2c, rda5807_id); |
493 | |
494 | static struct i2c_driver rda5807_i2c_driver = { |
495 | .probe = rda5807_i2c_probe, |
496 | .remove = __devexit_p(rda5807_i2c_remove), |
497 | .id_table = rda5807_id, |
498 | .driver = { |
499 | .name = "radio-rda5807", |
500 | .owner = THIS_MODULE, |
501 | .pm = RDA5807_PM_OPS, |
502 | }, |
503 | }; |
504 | |
505 | static int __init rda5807_init(void) |
506 | { |
507 | return i2c_add_driver(&rda5807_i2c_driver); |
508 | } |
509 | |
510 | static void __exit rda5807_exit(void) |
511 | { |
512 | i2c_del_driver(&rda5807_i2c_driver); |
513 | } |
514 | |
515 | module_init(rda5807_init); |
516 | module_exit(rda5807_exit); |
517 | |
518 | MODULE_AUTHOR("Maarten ter Huurne <maarten@treewalker.org>"); |
519 | MODULE_DESCRIPTION("RDA5807 FM tuner driver"); |
520 | MODULE_LICENSE("GPL"); |
521 |
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