*/
 
 #include <linux/device.h>
+#include <linux/err.h>
 #include <linux/genalloc.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
 /*
  * sh_mobile_meram_icb - MERAM ICB information
  * @regs: Registers cache
+ * @index: ICB index
  * @offset: MERAM block offset
- * @size: MERAM block size in bytes
+ * @size: MERAM block size in KiB
  * @cache_unit: Bytes to cache per ICB
  * @pixelformat: Video pixel format of the data stored in the ICB
  * @current_reg: Which of Start Address Register A (0) or B (1) is in use
  */
 struct sh_mobile_meram_icb {
        unsigned long regs[ICB_REGS_SIZE];
+       unsigned int index;
        unsigned long offset;
        unsigned int size;
 
 
 #define MERAM_ICB_NUM                  32
 
+struct sh_mobile_meram_fb_plane {
+       struct sh_mobile_meram_icb *marker;
+       struct sh_mobile_meram_icb *cache;
+};
+
+struct sh_mobile_meram_fb_cache {
+       unsigned int nplanes;
+       struct sh_mobile_meram_fb_plane planes[2];
+};
+
 /*
  * sh_mobile_meram_priv - MERAM device
  * @base: Registers base address
  * Allocation
  */
 
-/* Check if there's no overlaps in MERAM allocation. */
-static int meram_check_overlap(struct sh_mobile_meram_priv *priv,
-                              const struct sh_mobile_meram_icb_cfg *new)
+/* Allocate ICBs and MERAM for a plane. */
+static int __meram_alloc(struct sh_mobile_meram_priv *priv,
+                        struct sh_mobile_meram_fb_plane *plane,
+                        size_t size)
 {
-       /* valid ICB? */
-       if (new->marker_icb & ~0x1f || new->cache_icb & ~0x1f)
-               return 1;
-
-       if (test_bit(new->marker_icb, &priv->used_icb) ||
-           test_bit(new->cache_icb,  &priv->used_icb))
-               return  1;
+       unsigned long mem;
+       unsigned long idx;
 
-       return 0;
-}
+       idx = find_first_zero_bit(&priv->used_icb, 28);
+       if (idx == 28)
+               return -ENOMEM;
+       plane->cache = &priv->icbs[idx];
 
-/* Allocate memory for the ICBs and mark them as used. */
-static int meram_alloc(struct sh_mobile_meram_priv *priv,
-                      const struct sh_mobile_meram_icb_cfg *new,
-                      int pixelformat)
-{
-       struct sh_mobile_meram_icb *marker = &priv->icbs[new->marker_icb];
-       unsigned long mem;
+       idx = find_next_zero_bit(&priv->used_icb, 32, 28);
+       if (idx == 32)
+               return -ENOMEM;
+       plane->marker = &priv->icbs[idx];
 
-       mem = gen_pool_alloc(priv->pool, new->meram_size * 1024);
+       mem = gen_pool_alloc(priv->pool, size * 1024);
        if (mem == 0)
                return -ENOMEM;
 
-       __set_bit(new->marker_icb, &priv->used_icb);
-       __set_bit(new->cache_icb, &priv->used_icb);
+       __set_bit(plane->marker->index, &priv->used_icb);
+       __set_bit(plane->cache->index, &priv->used_icb);
 
-       marker->offset = mem - priv->meram;
-       marker->size = new->meram_size * 1024;
-       marker->current_reg = 1;
-       marker->pixelformat = pixelformat;
+       plane->marker->offset = mem - priv->meram;
+       plane->marker->size = size;
 
        return 0;
 }
 
-/* Unmark the specified ICB as used. */
-static void meram_free(struct sh_mobile_meram_priv *priv,
-                      const struct sh_mobile_meram_icb_cfg *icb)
+/* Free ICBs and MERAM for a plane. */
+static void __meram_free(struct sh_mobile_meram_priv *priv,
+                        struct sh_mobile_meram_fb_plane *plane)
 {
-       struct sh_mobile_meram_icb *marker = &priv->icbs[icb->marker_icb];
+       gen_pool_free(priv->pool, priv->meram + plane->marker->offset,
+                     plane->marker->size * 1024);
 
-       gen_pool_free(priv->pool, priv->meram + marker->offset, marker->size);
-
-       __clear_bit(icb->marker_icb, &priv->used_icb);
-       __clear_bit(icb->cache_icb, &priv->used_icb);
+       __clear_bit(plane->marker->index, &priv->used_icb);
+       __clear_bit(plane->cache->index, &priv->used_icb);
 }
 
 /* Is this a YCbCr(NV12, NV16 or NV24) colorspace? */
        return 0;
 }
 
+/* Allocate memory for the ICBs and mark them as used. */
+static struct sh_mobile_meram_fb_cache *
+meram_alloc(struct sh_mobile_meram_priv *priv,
+           const struct sh_mobile_meram_cfg *cfg,
+           int pixelformat)
+{
+       struct sh_mobile_meram_fb_cache *cache;
+       unsigned int nplanes = is_nvcolor(pixelformat) ? 2 : 1;
+       int ret;
+
+       if (cfg->icb[0].meram_size == 0)
+               return ERR_PTR(-EINVAL);
+
+       if (nplanes == 2 && cfg->icb[1].meram_size == 0)
+               return ERR_PTR(-EINVAL);
+
+       cache = kzalloc(sizeof(*cache), GFP_KERNEL);
+       if (cache == NULL)
+               return ERR_PTR(-ENOMEM);
+
+       cache->nplanes = nplanes;
+
+       ret = __meram_alloc(priv, &cache->planes[0], cfg->icb[0].meram_size);
+       if (ret < 0)
+               goto error;
+
+       cache->planes[0].marker->current_reg = 1;
+       cache->planes[0].marker->pixelformat = pixelformat;
+
+       if (cache->nplanes == 1)
+               return cache;
+
+       ret = __meram_alloc(priv, &cache->planes[1], cfg->icb[1].meram_size);
+       if (ret < 0) {
+               __meram_free(priv, &cache->planes[0]);
+               goto error;
+       }
+
+       return cache;
+
+error:
+       kfree(cache);
+       return ERR_PTR(-ENOMEM);
+}
+
+/* Unmark the specified ICB as used. */
+static void meram_free(struct sh_mobile_meram_priv *priv,
+                      struct sh_mobile_meram_fb_cache *cache)
+{
+       __meram_free(priv, &cache->planes[0]);
+       if (cache->nplanes == 2)
+               __meram_free(priv, &cache->planes[1]);
+
+       kfree(cache);
+}
+
 /* Set the next address to fetch. */
 static void meram_set_next_addr(struct sh_mobile_meram_priv *priv,
-                               const struct sh_mobile_meram_cfg *cfg,
+                               struct sh_mobile_meram_fb_cache *cache,
                                unsigned long base_addr_y,
                                unsigned long base_addr_c)
 {
-       struct sh_mobile_meram_icb *icb = &priv->icbs[cfg->icb[0].marker_icb];
+       struct sh_mobile_meram_icb *icb = cache->planes[0].marker;
        unsigned long target;
 
        icb->current_reg ^= 1;
        target = icb->current_reg ? MExxSARB : MExxSARA;
 
        /* set the next address to fetch */
-       meram_write_icb(priv->base, cfg->icb[0].cache_icb, target,
+       meram_write_icb(priv->base, cache->planes[0].cache->index, target,
                        base_addr_y);
-       meram_write_icb(priv->base, cfg->icb[0].marker_icb, target,
-                       base_addr_y +
-                       priv->icbs[cfg->icb[0].marker_icb].cache_unit);
-
-       if (is_nvcolor(icb->pixelformat)) {
-               meram_write_icb(priv->base, cfg->icb[1].cache_icb,  target,
-                               base_addr_c);
-               meram_write_icb(priv->base, cfg->icb[1].marker_icb, target,
-                               base_addr_c +
-                               priv->icbs[cfg->icb[1].marker_icb].cache_unit);
+       meram_write_icb(priv->base, cache->planes[0].marker->index, target,
+                       base_addr_y + cache->planes[0].marker->cache_unit);
+
+       if (cache->nplanes == 2) {
+               meram_write_icb(priv->base, cache->planes[1].cache->index,
+                               target, base_addr_c);
+               meram_write_icb(priv->base, cache->planes[1].marker->index,
+                               target, base_addr_c +
+                               cache->planes[1].marker->cache_unit);
        }
 }
 
 /* Get the next ICB address. */
 static void
 meram_get_next_icb_addr(struct sh_mobile_meram_info *pdata,
-                       const struct sh_mobile_meram_cfg *cfg,
+                       struct sh_mobile_meram_fb_cache *cache,
                        unsigned long *icb_addr_y, unsigned long *icb_addr_c)
 {
-       struct sh_mobile_meram_priv *priv = pdata->priv;
-       struct sh_mobile_meram_icb *icb = &priv->icbs[cfg->icb[0].marker_icb];
+       struct sh_mobile_meram_icb *icb = cache->planes[0].marker;
        unsigned long icb_offset;
 
        if (pdata->addr_mode == SH_MOBILE_MERAM_MODE0)
        else
                icb_offset = 0xc0000000 | (icb->current_reg << 23);
 
-       *icb_addr_y = icb_offset | (cfg->icb[0].marker_icb << 24);
-       if (is_nvcolor(icb->pixelformat))
-               *icb_addr_c = icb_offset | (cfg->icb[1].marker_icb << 24);
+       *icb_addr_y = icb_offset | (cache->planes[0].marker->index << 24);
+       if (cache->nplanes == 2)
+               *icb_addr_c = icb_offset
+                           | (cache->planes[1].marker->index << 24);
 }
 
 #define MERAM_CALC_BYTECOUNT(x, y) \
 
 /* Initialize MERAM. */
 static int meram_init(struct sh_mobile_meram_priv *priv,
-                     const struct sh_mobile_meram_icb_cfg *icb,
+                     struct sh_mobile_meram_fb_plane *plane,
                      unsigned int xres, unsigned int yres,
                      unsigned int *out_pitch)
 {
-       struct sh_mobile_meram_icb *marker = &priv->icbs[icb->marker_icb];
+       struct sh_mobile_meram_icb *marker = plane->marker;
        unsigned long total_byte_count = MERAM_CALC_BYTECOUNT(xres, yres);
        unsigned long bnm;
        unsigned int lcdc_pitch;
                lcdc_pitch = xpitch = MERAM_LINE_WIDTH;
                line_cnt = total_byte_count >> 11;
                *out_pitch = xres;
-               save_lines = (icb->meram_size / 16 / MERAM_SEC_LINE);
+               save_lines = plane->marker->size / 16 / MERAM_SEC_LINE;
                save_lines *= MERAM_SEC_LINE;
        } else {
                xpitch = xres;
                line_cnt = yres;
                *out_pitch = lcdc_pitch;
-               save_lines = icb->meram_size / (lcdc_pitch >> 10) / 2;
+               save_lines = plane->marker->size / (lcdc_pitch >> 10) / 2;
                save_lines &= 0xff;
        }
        bnm = (save_lines - 1) << 16;
        /* TODO: we better to check if we have enough MERAM buffer size */
 
        /* set up ICB */
-       meram_write_icb(priv->base, icb->cache_icb,  MExxBSIZE,
+       meram_write_icb(priv->base, plane->cache->index,  MExxBSIZE,
                        MERAM_MExxBSIZE_VAL(0x0, line_cnt - 1, xpitch - 1));
-       meram_write_icb(priv->base, icb->marker_icb, MExxBSIZE,
+       meram_write_icb(priv->base, plane->marker->index, MExxBSIZE,
                        MERAM_MExxBSIZE_VAL(0xf, line_cnt - 1, xpitch - 1));
 
-       meram_write_icb(priv->base, icb->cache_icb,  MExxMNCF, bnm);
-       meram_write_icb(priv->base, icb->marker_icb, MExxMNCF, bnm);
+       meram_write_icb(priv->base, plane->cache->index,  MExxMNCF, bnm);
+       meram_write_icb(priv->base, plane->marker->index, MExxMNCF, bnm);
 
-       meram_write_icb(priv->base, icb->cache_icb,  MExxSBSIZE, xpitch);
-       meram_write_icb(priv->base, icb->marker_icb, MExxSBSIZE, xpitch);
+       meram_write_icb(priv->base, plane->cache->index,  MExxSBSIZE, xpitch);
+       meram_write_icb(priv->base, plane->marker->index, MExxSBSIZE, xpitch);
 
        /* save a cache unit size */
-       priv->icbs[icb->cache_icb].cache_unit = xres * save_lines;
-       priv->icbs[icb->marker_icb].cache_unit = xres * save_lines;
+       plane->cache->cache_unit = xres * save_lines;
+       plane->marker->cache_unit = xres * save_lines;
 
        /*
         * Set MERAM for framebuffer
         * we also chain the cache_icb and the marker_icb.
         * we also split the allocated MERAM buffer between two ICBs.
         */
-       meram_write_icb(priv->base, icb->cache_icb, MExxCTL,
-                       MERAM_MExxCTL_VAL(icb->marker_icb, marker->offset) |
-                       MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM |
+       meram_write_icb(priv->base, plane->cache->index, MExxCTL,
+                       MERAM_MExxCTL_VAL(plane->marker->index, marker->offset)
+                       | MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM |
                        MExxCTL_MD_FB);
-       meram_write_icb(priv->base, icb->marker_icb, MExxCTL,
-                       MERAM_MExxCTL_VAL(icb->cache_icb, marker->offset +
-                                         icb->meram_size / 2) |
+       meram_write_icb(priv->base, plane->marker->index, MExxCTL,
+                       MERAM_MExxCTL_VAL(plane->cache->index, marker->offset +
+                                         plane->marker->size / 2) |
                        MExxCTL_WD1 | MExxCTL_WD0 | MExxCTL_WS | MExxCTL_CM |
                        MExxCTL_MD_FB);
 
 }
 
 static void meram_deinit(struct sh_mobile_meram_priv *priv,
-                        const struct sh_mobile_meram_icb_cfg *icb)
+                        struct sh_mobile_meram_fb_plane *plane)
 {
        /* disable ICB */
-       meram_write_icb(priv->base, icb->cache_icb,  MExxCTL,
+       meram_write_icb(priv->base, plane->cache->index,  MExxCTL,
                        MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF);
-       meram_write_icb(priv->base, icb->marker_icb, MExxCTL,
+       meram_write_icb(priv->base, plane->marker->index, MExxCTL,
                        MExxCTL_WBF | MExxCTL_WF | MExxCTL_RF);
 
-       priv->icbs[icb->cache_icb].cache_unit = 0;
-       priv->icbs[icb->marker_icb].cache_unit = 0;
+       plane->cache->cache_unit = 0;
+       plane->marker->cache_unit = 0;
 }
 
 /* -----------------------------------------------------------------------------
  * Registration/unregistration
  */
 
-static int sh_mobile_meram_register(struct sh_mobile_meram_info *pdata,
-                                   const struct sh_mobile_meram_cfg *cfg,
-                                   unsigned int xres, unsigned int yres,
-                                   unsigned int pixelformat,
-                                   unsigned long base_addr_y,
-                                   unsigned long base_addr_c,
-                                   unsigned long *icb_addr_y,
-                                   unsigned long *icb_addr_c,
-                                   unsigned int *pitch)
+static void *sh_mobile_meram_register(struct sh_mobile_meram_info *pdata,
+                                     const struct sh_mobile_meram_cfg *cfg,
+                                     unsigned int xres, unsigned int yres,
+                                     unsigned int pixelformat,
+                                     unsigned long base_addr_y,
+                                     unsigned long base_addr_c,
+                                     unsigned long *icb_addr_y,
+                                     unsigned long *icb_addr_c,
+                                     unsigned int *pitch)
 {
-       struct platform_device *pdev;
+       struct sh_mobile_meram_fb_cache *cache;
        struct sh_mobile_meram_priv *priv;
+       struct platform_device *pdev;
        unsigned int out_pitch;
-       unsigned int n;
-       int error = 0;
 
        if (!pdata || !pdata->priv || !pdata->pdev || !cfg)
-               return -EINVAL;
+               return ERR_PTR(-EINVAL);
 
        if (pixelformat != SH_MOBILE_MERAM_PF_NV &&
            pixelformat != SH_MOBILE_MERAM_PF_NV24 &&
            pixelformat != SH_MOBILE_MERAM_PF_RGB)
-               return -EINVAL;
+               return ERR_PTR(-EINVAL);
 
        priv = pdata->priv;
        pdev = pdata->pdev;
        /* we can't handle wider than 8192px */
        if (xres > 8192) {
                dev_err(&pdev->dev, "width exceeding the limit (> 8192).");
-               return -EINVAL;
-       }
-
-       /* do we have at least one ICB config? */
-       if (cfg->icb[0].marker_icb < 0 || cfg->icb[0].cache_icb < 0) {
-               dev_err(&pdev->dev, "at least one ICB is required.");
-               return -EINVAL;
+               return ERR_PTR(-EINVAL);
        }
 
        mutex_lock(&priv->lock);
 
-       /* make sure that there's no overlaps */
-       if (meram_check_overlap(priv, &cfg->icb[0])) {
-               dev_err(&pdev->dev, "conflicting config detected.");
-               error = -EINVAL;
-               goto err;
-       }
-       n = 1;
-
-       /* do the same if we have the second ICB set */
-       if (cfg->icb[1].marker_icb >= 0 && cfg->icb[1].cache_icb >= 0) {
-               if (meram_check_overlap(priv, &cfg->icb[1])) {
-                       dev_err(&pdev->dev, "conflicting config detected.");
-                       error = -EINVAL;
-                       goto err;
-               }
-               n = 2;
-       }
-
-       if (is_nvcolor(pixelformat) && n != 2) {
-               dev_err(&pdev->dev, "requires two ICB sets for planar Y/C.");
-               error =  -EINVAL;
-               goto err;
-       }
-
        /* We now register the ICBs and allocate the MERAM regions. */
-       error = meram_alloc(priv, &cfg->icb[0], pixelformat);
-       if (error < 0)
+       cache = meram_alloc(priv, cfg, pixelformat);
+       if (IS_ERR(cache)) {
+               dev_err(&pdev->dev, "MERAM allocation failed (%ld).",
+                       PTR_ERR(cache));
                goto err;
-
-       if (is_nvcolor(pixelformat)) {
-               error = meram_alloc(priv, &cfg->icb[1], pixelformat);
-               if (error < 0) {
-                       meram_free(priv, &cfg->icb[0]);
-                       goto err;
-               }
        }
 
        /* initialize MERAM */
-       meram_init(priv, &cfg->icb[0], xres, yres, &out_pitch);
+       meram_init(priv, &cache->planes[0], xres, yres, &out_pitch);
        *pitch = out_pitch;
        if (pixelformat == SH_MOBILE_MERAM_PF_NV)
-               meram_init(priv, &cfg->icb[1], xres, (yres + 1) / 2,
+               meram_init(priv, &cache->planes[1], xres, (yres + 1) / 2,
                        &out_pitch);
        else if (pixelformat == SH_MOBILE_MERAM_PF_NV24)
-               meram_init(priv, &cfg->icb[1], 2 * xres, (yres + 1) / 2,
+               meram_init(priv, &cache->planes[1], 2 * xres, (yres + 1) / 2,
                        &out_pitch);
 
-       meram_set_next_addr(priv, cfg, base_addr_y, base_addr_c);
-       meram_get_next_icb_addr(pdata, cfg, icb_addr_y, icb_addr_c);
+       meram_set_next_addr(priv, cache, base_addr_y, base_addr_c);
+       meram_get_next_icb_addr(pdata, cache, icb_addr_y, icb_addr_c);
 
        dev_dbg(&pdev->dev, "registered - can access via y=%08lx, c=%08lx",
                *icb_addr_y, *icb_addr_c);
 
 err:
        mutex_unlock(&priv->lock);
-       return error;
+       return cache;
 }
 
-static int sh_mobile_meram_unregister(struct sh_mobile_meram_info *pdata,
-                                     const struct sh_mobile_meram_cfg *cfg)
+static int
+sh_mobile_meram_unregister(struct sh_mobile_meram_info *pdata, void *data)
 {
+       struct sh_mobile_meram_fb_cache *cache = data;
        struct sh_mobile_meram_priv *priv;
-       struct sh_mobile_meram_icb *icb;
 
-       if (!pdata || !pdata->priv || !cfg)
+       if (!pdata || !pdata->priv || !data)
                return -EINVAL;
 
        priv = pdata->priv;
-       icb = &priv->icbs[cfg->icb[0].marker_icb];
 
        mutex_lock(&priv->lock);
 
-       /* deinit & unmark */
-       if (is_nvcolor(icb->pixelformat)) {
-               meram_deinit(priv, &cfg->icb[1]);
-               meram_free(priv, &cfg->icb[1]);
-       }
-       meram_deinit(priv, &cfg->icb[0]);
-       meram_free(priv, &cfg->icb[0]);
+       /* deinit & free */
+       meram_deinit(priv, &cache->planes[0]);
+       if (cache->nplanes == 2)
+               meram_deinit(priv, &cache->planes[1]);
+
+       meram_free(priv, cache);
 
        mutex_unlock(&priv->lock);
 
        return 0;
 }
 
-static int sh_mobile_meram_update(struct sh_mobile_meram_info *pdata,
-                                 const struct sh_mobile_meram_cfg *cfg,
-                                 unsigned long base_addr_y,
-                                 unsigned long base_addr_c,
-                                 unsigned long *icb_addr_y,
-                                 unsigned long *icb_addr_c)
+static int
+sh_mobile_meram_update(struct sh_mobile_meram_info *pdata, void *data,
+                      unsigned long base_addr_y, unsigned long base_addr_c,
+                      unsigned long *icb_addr_y, unsigned long *icb_addr_c)
 {
+       struct sh_mobile_meram_fb_cache *cache = data;
        struct sh_mobile_meram_priv *priv;
 
-       if (!pdata || !pdata->priv || !cfg)
+       if (!pdata || !pdata->priv || !data)
                return -EINVAL;
 
        priv = pdata->priv;
 
        mutex_lock(&priv->lock);
 
-       meram_set_next_addr(priv, cfg, base_addr_y, base_addr_c);
-       meram_get_next_icb_addr(pdata, cfg, icb_addr_y, icb_addr_c);
+       meram_set_next_addr(priv, cache, base_addr_y, base_addr_c);
+       meram_get_next_icb_addr(pdata, cache, icb_addr_y, icb_addr_c);
 
        mutex_unlock(&priv->lock);
 
        struct sh_mobile_meram_info *pdata = pdev->dev.platform_data;
        struct resource *regs;
        struct resource *meram;
+       unsigned int i;
        int error;
 
        if (!pdata) {
                return -ENOMEM;
        }
 
-       /* initialize private data */
+       /* Initialize private data. */
        mutex_init(&priv->lock);
+       priv->used_icb = pdata->reserved_icbs;
+
+       for (i = 0; i < MERAM_ICB_NUM; ++i)
+               priv->icbs[i].index = i;
+
        pdata->ops = &sh_mobile_meram_ops;
        pdata->priv = priv;
        pdata->pdev = pdev;