RE: [RFC 7/8] TILER-DMM: Main TILER driver implementation.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> -----Original Message-----
> From: Sin, David
> Sent: Saturday, July 24, 2010 4:52 AM
> To: linux-arm-kernel@xxxxxxxxxxxxxxxxxxxxxx; linux-omap@xxxxxxxxxxxxxxx;
> Tony Lindgren; Russell King
> Cc: Kanigeri, Hari; Ohad Ben-Cohen; Hiremath, Vaibhav; Shilimkar, Santosh;
> Molnar, Lajos; Sin, David
> Subject: [RFC 7/8] TILER-DMM: Main TILER driver implementation.
>
> From: Lajos Molnar <molnar@xxxxxx>
>
> This patch contains the TILER driver and implementation of the TILER
> block manipulation and mapping functions.
>
> It also contains the makefile and config file for the TILER driver.
>
> Signed-off-by: Lajos Molnar <molnar@xxxxxx>
> Signed-off-by: David Sin <davidsin@xxxxxx>
> ---
>  drivers/media/video/tiler/Kconfig       |   65 +++++
>  drivers/media/video/tiler/Makefile      |    7 +
>  drivers/media/video/tiler/tiler-iface.c |  106 ++++++++
>  drivers/media/video/tiler/tiler-main.c  |  426
> +++++++++++++++++++++++++++++++
>  4 files changed, 604 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/media/video/tiler/Kconfig
>  create mode 100644 drivers/media/video/tiler/Makefile
>  create mode 100644 drivers/media/video/tiler/tiler-iface.c
>  create mode 100644 drivers/media/video/tiler/tiler-main.c
>
> diff --git a/drivers/media/video/tiler/Kconfig
> b/drivers/media/video/tiler/Kconfig
> new file mode 100644
> index 0000000..2c61471
> --- /dev/null
> +++ b/drivers/media/video/tiler/Kconfig
> @@ -0,0 +1,65 @@
> +config HAVE_TI_TILER
> +        bool
> +        default y
> +        depends on ARCH_OMAP4
> +
> +menuconfig TI_TILER
> +        tristate "TI TILER support"
> +        default y
> +        depends on HAVE_TI_TILER
> +        help
> +           TILER and TILER-DMM driver for TI chips.  The TI TILER device
> +           enables video rotation on certain TI chips such as OMAP4 or
> +           Netra.  Video rotation will be limited without TILER support.
Sorry but I don't understand the need of above two entries. Should one entry not sufficient ?
> +
> +config TILER_GRANULARITY
> +        int "Allocation granularity (2^n)"
> +        range 1 4096
> +        default 128
> +        depends on TI_TILER
> +        help
> +           This option sets the default TILER allocation granularity.  It
> can
> +           be overriden by the tiler.grain boot argument.
> +
> +           The allocation granularity is the smallest TILER block size
> (in
> +           bytes) managed distinctly by the TILER driver.  TILER blocks
> of any
> +           size are managed in chunks of at least this size.
> +
> +           Must be a 2^n in the range of 1 to 4096; however, the TILER
> driver
> +           may use a larger supported granularity.
> +
> +           Supported values are: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512,
> 1024,
> +           2048, 4096.
> +
> +config TILER_ALIGNMENT
> +        int "Allocation alignment (2^n)"
> +        range 1 4096
> +        default 4096
> +        depends on TI_TILER
> +        help
> +           This option sets the default TILER allocation alignment.  It
> can
> +           be overriden by the tiler.align boot argument.
Do you want this entry since you already support boot-argument ?
> +
> +           Must be a 2^n in the range of 1 to 4096; however, it is
> naturally
> +           aligned to the TILER granularity.
> +
> +           Supported values are: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512,
> 1024,
> +           2048, 4096.
> +
> +config TILER_CACHE_LIMIT
> +        int "Memory limit to cache free pages in MBytes"
> +        range 0 128
> +        default 40
> +        depends on TI_TILER
> +        help
> +           This option sets the minimum memory that TILER retains even if
> +           there is less TILER allocated memory is use.  The unused
> memory is
> +           instead stored in a cache to speed up allocation and freeing
> of
> +           physical pages.
> +
> +           This option can be overriden by the tiler.cache boot argument.
> +
> +           While initially TILER will use less memory than this limit
> (0), it
> +           will not release any memory used until it reaches this limit.
> +           Thereafter, TILER will release any unused memory immediately
> as
> +           long as there it is above this threshold.
> diff --git a/drivers/media/video/tiler/Makefile
> b/drivers/media/video/tiler/Makefile
> new file mode 100644
> index 0000000..4a6495e
> --- /dev/null
> +++ b/drivers/media/video/tiler/Makefile
> @@ -0,0 +1,7 @@
> +obj-$(CONFIG_TI_TILER) += tcm/
> +
> +obj-$(CONFIG_TI_TILER) += tiler.o
> +tiler-objs = tiler-geom.o tiler-main.o tiler-iface.o tmm-pat.o
> +
> +obj-$(CONFIG_TI_TILER) += tiler_dmm.o
> +tiler_dmm-objs = dmm.o
> diff --git a/drivers/media/video/tiler/tiler-iface.c
> b/drivers/media/video/tiler/tiler-iface.c
> new file mode 100644
> index 0000000..0b10fae
> --- /dev/null
> +++ b/drivers/media/video/tiler/tiler-iface.c
> @@ -0,0 +1,106 @@
> +/*
> + * tiler-iface.c
> + *
> + * TILER driver interace functions for TI TILER hardware block.
> + *
> + * Authors: Lajos Molnar <molnar@xxxxxx>
> + *          David Sin <davidsin@xxxxxx>
> + *
> + * Copyright (C) 2009-2010 Texas Instruments, Inc.
> + *
> + * This package is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
> + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
> + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>              /* kmalloc */
> +#include <linux/mm.h>
> +#include <linux/mm_types.h>
> +#include <asm/mach/map.h>    /* for ioremap_page */
> +
> +#include "_tiler.h"
> +
> +/*
> + *  Memory-Map Kernel APIs
> + *
> ==========================================================================
> + */
> +
> +s32 tiler_mmap_blk(struct tiler_block_t *blk, u32 offs, u32 size,
> +                             struct vm_area_struct *vma, u32 voffs)
> +{
> +     u32 v, p, len;
> +
> +     /* don't allow mremap */
> +     vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED;
Should we add " VM_LOCKED" as well considering the page swapping  ?
> +
> +     /* mapping must fit into vma */
> +     BUG_ON(vma->vm_start > vma->vm_start + voffs ||
> +             vma->vm_start + voffs > vma->vm_start + voffs + size ||
> +             vma->vm_start + voffs + size > vma->vm_end);
> +
> +     /* mapping must fit into block */
> +     BUG_ON(offs > offs + size || offs + size > tiler_size(blk));
> +
> +     v = tiler_vstride(blk);
> +     p = tiler_pstride(blk);
> +
> +     /* remap block portion */
> +     len = v - (offs % v);   /* initial area to map */
> +     while (size) {
> +             /* restrict to size still needs mapping */
> +             if (len > size)
> +                     len = size;
> +
> +             vma->vm_pgoff = (blk->phys + offs) >> PAGE_SHIFT;
> +             if (remap_pfn_range(vma, vma->vm_start + voffs, vma->vm_pgoff,
> +                                 len, vma->vm_page_prot))
> +                     return -EAGAIN;
> +             voffs += len;
> +             offs += len + p - v;
> +             size -= len;
> +             len = v;        /* subsequent area to map */
> +     }
> +     return 0;
> +}
> +EXPORT_SYMBOL(tiler_mmap_blk);
> +
> +s32 tiler_ioremap_blk(struct tiler_block_t *blk, u32 offs, u32 size,
> +                             u32 addr, u32 mtype)
> +{
> +     u32 v, p;
> +     u32 len;                /* area to map */
> +     const struct mem_type *type = get_mem_type(mtype);
> +
> +     /* mapping must fit into address space */
> +     BUG_ON(addr > addr + size);
> +
> +     /* mapping must fit into block */
> +     BUG_ON(offs > offs + size || offs + size > tiler_size(blk));
> +
> +     v = tiler_vstride(blk);
> +     p = tiler_pstride(blk);
> +
> +     /* move offset and address to end */
> +     offs += blk->phys + size;
> +     addr += size;
> +
> +     len = v - (offs % v);   /* initial area to map */
> +     while (size) {
> +             while (len && size) {
> +                     if (ioremap_page(addr - size, offs - size, type))
Same comment about doing iormap on RAM.
> +                             return -EAGAIN;
> +                     len  -= PAGE_SIZE;
> +                     size -= PAGE_SIZE;
> +             }
> +
> +             offs += p - v;
> +             len = v;        /* subsequent area to map */
> +     }
> +     return 0;
> +}
> +EXPORT_SYMBOL(tiler_ioremap_blk);
> diff --git a/drivers/media/video/tiler/tiler-main.c
> b/drivers/media/video/tiler/tiler-main.c
> new file mode 100644
> index 0000000..cbd84d1
> --- /dev/null
> +++ b/drivers/media/video/tiler/tiler-main.c
> @@ -0,0 +1,426 @@
> +/*
> + * tiler-main.c
> + *
> + * TILER driver main support functions for TI TILER hardware block.
> + *
> + * Authors: Lajos Molnar <molnar@xxxxxx>
> + *          David Sin <davidsin@xxxxxx>
> + *
> + * Copyright (C) 2009-2010 Texas Instruments, Inc.
> + *
> + * This package is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
> + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
> + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>   /* platform_device() */
> +#include <linux/errno.h>
> +#include <linux/mutex.h>
> +#include <linux/dma-mapping.h>               /* dma_alloc_coherent */
> +#include <linux/pagemap.h>           /* page_cache_release() */
> +#include <linux/slab.h>
> +
> +#include <mach/dmm.h>
> +#include "tmm.h"
> +#include "_tiler.h"
> +#include "tcm/tcm-sita.h"            /* TCM algorithm */
> +
> +static uint default_align = CONFIG_TILER_ALIGNMENT;
> +static uint granularity = CONFIG_TILER_GRANULARITY;
> +
> +module_param_named(align, default_align, uint, 0444);
> +MODULE_PARM_DESC(align, "Default block ssptr alignment");
> +module_param_named(grain, granularity, uint, 0444);
> +MODULE_PARM_DESC(grain, "Granularity (bytes)");
> +
> +struct platform_driver tiler_driver_ldm = {
> +     .driver = {
> +             .owner = THIS_MODULE,
> +             .name = "tiler",
> +     },
> +     .probe = NULL,
> +     .shutdown = NULL,
> +     .remove = NULL,
> +};
> +
> +static struct tiler_ops tiler;               /* shared methods and variables
> */
> +
> +static struct list_head blocks;              /* all tiler blocks */
> +
> +static struct mutex mtx;
> +static struct tcm *tcm[TILER_FORMATS];
> +static struct tmm *tmm[TILER_FORMATS];
> +static u32 *dmac_va;
> +static dma_addr_t dmac_pa;
> +
> +/* info for a block */
> +struct mem_info {
> +     struct list_head global;        /* global blocks */
> +     struct tiler_block_t blk;       /* block info */
> +     struct tcm_area area;
> +     u32 *mem;                       /* list of alloced phys addresses */
> +};
> +
> +/*
> + *  TMM connectors
> + *
> ==========================================================================
> + */
> +/* wrapper around tmm_map */
> +static s32 refill_pat(struct tmm *tmm, struct tcm_area *area, u32 *ptr)
> +{
> +     s32 res = 0;
> +     struct pat_area p_area = {0};
> +
> +     p_area.x0 = area->p0.x;
> +     p_area.y0 = area->p0.y;
> +     p_area.x1 = area->p1.x;
> +     p_area.y1 = area->p1.y;
> +
> +     memcpy(dmac_va, ptr, sizeof(*ptr) * tcm_sizeof(*area));
> +
> +     if (tmm_map(tmm, p_area, dmac_pa))
> +             res = -EFAULT;
> +
> +     return res;
> +}
> +
> +/* wrapper around tmm_clear */
> +static void clear_pat(struct tmm *tmm, struct tcm_area *area)
> +{
> +     struct pat_area p_area = {0};
> +
> +     p_area.x0 = area->p0.x;
> +     p_area.y0 = area->p0.y;
> +     p_area.x1 = area->p1.x;
> +     p_area.y1 = area->p1.y;
> +
> +     tmm_clear(tmm, p_area);
> +}
> +
> +/*
> + *  Area handling methods
> + *
> ==========================================================================
> + */
> +
> +/* verify input params and calculate tiler container params for a block
> */
> +static s32 __analize_area(enum tiler_fmt fmt, u32 width, u32 height,
> +                       u16 *x_area, u16 *y_area, u16 *align, u16 *offs)
> +{
> +     /* input: width, height is in pixels, *align, *offs in bytes */
> +     /* output: x_area, y_area, *align in slots */
> +
> +     /* slot width, height, and row size */
> +     u32 slot_row, min_align;
> +     const struct tiler_geom *g;
> +
> +     /* width and height must be positive, format must be 2D */
> +     if (!width || !height || fmt == TILFMT_PAGE)
> +             return -EINVAL;
> +
> +     /* align must be 2 power */
> +     if (*align & (*align - 1))
> +             return -EINVAL;
> +
> +     /* format must be valid */
> +     g = tiler.geom(fmt);
> +     if (!g)
> +             return -EINVAL;
> +
> +     /* get the # of bytes per row in 1 slot */
> +     slot_row = g->slot_w * g->bpp;
> +
> +     /* minimum alignment is at least 1 slot.  Use default if needed */
> +     min_align = max(slot_row, granularity);
> +     *align = ALIGN(*align ? : default_align, min_align);
> +
> +     /* offset must be multiple of bpp */
> +     if (*offs & (g->bpp - 1) || *offs >= *align)
> +             return -EINVAL;
> +
> +     /* round down the offset to the nearest slot size, and increase
> width
> +        to allow space for having the correct offset */
> +     width += (*offs & (min_align - 1)) / g->bpp;
> +
> +     /* expand width to block size */
> +     width = ALIGN(width, min_align / g->bpp);
> +
> +     /* adjust to slots */
> +     *x_area = DIV_ROUND_UP(width, g->slot_w);
> +     *y_area = DIV_ROUND_UP(height, g->slot_h);
> +     *align /= slot_row;
> +
> +     if (*x_area > tiler.width || *y_area > tiler.height)
> +             return -ENOMEM;
> +     return 0;
> +}
> +
> +/* allocate a mem_info structure and reserves a 2d container area */
> +static struct mem_info *get_2d_area(u16 w, u16 h, u16 align, struct tcm
> *tcm)
> +{
> +     struct mem_info *mi = NULL;
> +
> +     /* reserve a block struct */
> +     mi = kmalloc(sizeof(*mi), GFP_KERNEL);
> +     if (!mi)
> +             return mi;
> +     memset(mi, 0, sizeof(*mi));
> +
> +     /* reserve an area */
> +     if (tcm_reserve_2d(tcm, w, h, align, &mi->area)) {
> +             kfree(mi);
> +             return NULL;
> +     }
> +
> +     return mi;
> +}
> +
> +/*
> + *  Block operations
> + *
> ==========================================================================
> + */
> +
> +/* free a block */
> +static s32 free_block(struct mem_info *mi)
> +{
> +     /* release memory */
> +     if (mi->mem)
> +             tmm_free(tmm[tiler_fmt(mi->blk.phys)], mi->mem);
> +     clear_pat(tmm[tiler_fmt(mi->blk.phys)], &mi->area);
> +
> +     /* unreserve area */
> +     tcm_free(&mi->area);
> +
> +     /* have mutex */
> +
> +     /* safe deletion as list may not have been assigned */
> +     if (mi->global.next)
> +             list_del(&mi->global);
> +
> +     kfree(mi);
> +     return 0;
> +}
> +
> +/* create an empty block with just an area and add it to the global list
> */
> +static struct mem_info *get_area(enum tiler_fmt fmt, u32 width, u32
> height,
> +                                                     u16 align, u16 offs)
> +{
> +     u16 x, y;
> +     struct mem_info *mi = NULL;
> +     const struct tiler_geom *g = tiler.geom(fmt);
> +
> +     /* calculate dimensions and alignment in slots */
> +     if (__analize_area(fmt, width, height, &x, &y, &align, &offs))
> +             return NULL;
> +
> +     mi = get_2d_area(x, y, align, tcm[fmt]);
> +     if (!mi)
> +             return NULL;
> +
> +     /* have mutex */
> +     list_add(&mi->global, &blocks);
> +
> +     mi->blk.phys = tiler.addr(fmt,
> +             mi->area.p0.x * g->slot_w, mi->area.p0.y * g->slot_h)
> +             + offs;
> +     return mi;
> +}
> +
> +/* allocate a new tiler block */
> +static s32 alloc_block(enum tiler_fmt fmt, u32 width, u32 height,
> +             u32 align, u32 offs, struct mem_info **info)
> +{
> +     struct mem_info *mi = NULL;
> +
> +     *info = NULL;
> +
> +     /* only support up to page alignment */
> +     if (align > PAGE_SIZE || offs >= (align ? : default_align))
> +             return -EINVAL;
> +
> +     mutex_lock(&mtx);
> +
> +     /* reserve area in tiler container */
> +     mi = get_area(fmt, width, height, align, offs);
> +     if (!mi)
> +             goto nomem;
> +
> +     mi->blk.width = width;
> +     mi->blk.height = height;
> +
> +     /* allocate and map if mapping is supported */
> +     if (tmm_can_map(tmm[fmt])) {
> +             mi->mem = tmm_get(tmm[fmt], tcm_sizeof(mi->area));
> +             if (!mi->mem)
> +                     goto cleanup;
> +
> +             /* Ensure the data reaches to main memory before PAT refill */
> +             wmb();
> +
> +             /* program PAT */
> +             if (refill_pat(tmm[fmt], &mi->area, mi->mem))
> +                     goto cleanup;
> +     }
> +     *info = mi;
> +     mutex_unlock(&mtx);
> +     return 0;
> +
> +cleanup:
> +     free_block(mi);
> +nomem:
> +     mutex_unlock(&mtx);
> +     return -ENOMEM;
> +}
> +
> +/*
> + *  Driver code
> + *
> ==========================================================================
> + */
> +
> +/* driver initialization */
> +static s32 __init tiler_init(void)
> +{
> +     s32 r = -1;
> +     struct tcm *sita = NULL;
> +     struct tmm *tmm_pat = NULL;
> +
> +     tiler_geom_init(&tiler);
> +
> +     /* check module parameters for correctness */
> +     if (default_align > PAGE_SIZE ||
> +         default_align & (default_align - 1) ||
> +         granularity < 1 || granularity > PAGE_SIZE ||
> +         granularity & (granularity - 1))
> +             return -EINVAL;
> +
> +     /*
> +      * Array of physical pages for PAT programming, which must be a 16-
> byte
> +      * aligned physical address.
> +      */
> +     dmac_va = dma_alloc_coherent(NULL, tiler.width * tiler.height *
> +                                     sizeof(*dmac_va), &dmac_pa, GFP_ATOMIC);
> +     if (!dmac_va)
> +             return -ENOMEM;
> +
> +     /* Allocate tiler container manager (we share 1 on OMAP4) */
> +     sita = sita_init(tiler.width, tiler.height, NULL);
> +
> +     tcm[TILFMT_8BIT]  = sita;
> +     tcm[TILFMT_16BIT] = sita;
> +     tcm[TILFMT_32BIT] = sita;
> +
> +     /* Allocate tiler memory manager (must have 1 unique TMM per TCM )
> */
> +     tmm_pat = tmm_pat_init(0);
> +     tmm[TILFMT_8BIT]  = tmm_pat;
> +     tmm[TILFMT_16BIT] = tmm_pat;
> +     tmm[TILFMT_32BIT] = tmm_pat;
> +
> +     if (!sita || !tmm_pat) {
> +             r = -ENOMEM;
> +             goto error;
> +     }
> +
> +     r = platform_driver_register(&tiler_driver_ldm);
> +
> +     mutex_init(&mtx);
> +     INIT_LIST_HEAD(&blocks);
> +
> +error:
> +     if (r) {
> +             tcm_deinit(sita);
> +             tmm_deinit(tmm_pat);
> +             dma_free_coherent(NULL, tiler.width * tiler.height *
> +                                     sizeof(*dmac_va), dmac_va, dmac_pa);
> +     }
> +
> +     return r;
> +}
> +
> +/* driver cleanup */
> +static void __exit tiler_exit(void)
> +{
> +     int i, j;
> +     struct mem_info *mi, *mi_;
> +
> +     mutex_lock(&mtx);
> +
> +     /* free all blocks */
> +     list_for_each_entry_safe(mi, mi_, &blocks, global)
> +             free_block(mi);
> +
> +     /* all lists should have cleared */
> +     BUG_ON(!list_empty(&blocks));
> +
> +     mutex_unlock(&mtx);
> +
> +     dma_free_coherent(NULL, tiler.width * tiler.height *
> sizeof(*dmac_va),
> +                                                     dmac_va, dmac_pa);
> +
> +     /* close containers only once */
> +     for (i = TILFMT_MIN; i <= TILFMT_MAX; i++) {
> +             /* remove identical containers (tmm is unique per tcm) */
> +             for (j = i + 1; j <= TILFMT_MAX; j++)
> +                     if (tcm[i] == tcm[j]) {
> +                             tcm[j] = NULL;
> +                             tmm[j] = NULL;
> +                     }
> +
> +             tcm_deinit(tcm[i]);
> +             tmm_deinit(tmm[i]);
> +     }
> +
> +     mutex_destroy(&mtx);
> +     platform_driver_unregister(&tiler_driver_ldm);
> +}
> +
> +/*
> + *  Block Kernel APIs
> + *
> ==========================================================================
> + */
> +
> +s32 tiler_alloc(struct tiler_block_t *blk, enum tiler_fmt fmt,
> +             u32 align, u32 offs)
> +{
> +     struct mem_info *mi;
> +     s32 res;
> +
> +     /* blk must be valid, and blk->phys must be 0 */
> +     BUG_ON(!blk || blk->phys);
> +
> +     res = alloc_block(fmt, blk->width, blk->height, align, offs, &mi);
> +     if (mi)
> +             blk->phys = mi->blk.phys;
> +     return res;
> +}
> +EXPORT_SYMBOL(tiler_alloc);
> +
> +void tiler_free(struct tiler_block_t *blk)
> +{
> +     struct mem_info *mi;
> +
> +     mutex_lock(&mtx);
> +
> +     /* find block */
> +     list_for_each_entry(mi, &blocks, global) {
> +             if (mi->blk.phys == blk->phys) {
> +                     free_block(mi);
> +                     break;
> +             }
> +     }
> +
> +     blk->phys = 0;
> +
> +     mutex_unlock(&mtx);
> +}
> +EXPORT_SYMBOL(tiler_free);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Lajos Molnar <molnar@xxxxxx>");
> +MODULE_AUTHOR("David Sin <davidsin@xxxxxx>");
> +module_init(tiler_init);
> +module_exit(tiler_exit);
> --
> 1.6.3.3

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux