amba-clcd: Add Device Tree support to amba-clcd driver
authorJon Medhurst <tixy@linaro.org>
Thu, 28 Mar 2013 15:57:56 +0000 (15:57 +0000)
committerJon Medhurst <tixy@linaro.org>
Mon, 1 Jul 2013 10:04:30 +0000 (11:04 +0100)
Add support to parse the display configuration from device tree.

If the board does not provide platform specific functions in the struct
clcd_board contained with the amba device info, then defaults are provided
by the driver.

The device tree configuration can either ask for a DMA setup or provide a
framebuffer address to be remapped into the driver.

Signed-off-by: Ryan Harkin <ryan.harkin@linaro.org>
drivers/video/amba-clcd.c

index 0a2cce7285be99dd8aaa7983a43bbeae7e6b2550..3cacf26f3e5534c87da3e8b9018d8fa76e174330 100644 (file)
 #include <linux/string.h>
 #include <linux/slab.h>
 #include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/memblock.h>
 #include <linux/mm.h>
+#include <linux/of.h>
 #include <linux/fb.h>
 #include <linux/init.h>
 #include <linux/ioport.h>
@@ -391,6 +394,19 @@ static int clcdfb_blank(int blank_mode, struct fb_info *info)
        }
        return 0;
 }
+int clcdfb_mmap_dma(struct clcd_fb *fb, struct vm_area_struct *vma)
+{
+       return dma_mmap_writecombine(&fb->dev->dev, vma,
+                                    fb->fb.screen_base,
+                                    fb->fb.fix.smem_start,
+                                    fb->fb.fix.smem_len);
+}
+
+void clcdfb_remove_dma(struct clcd_fb *fb)
+{
+       dma_free_writecombine(&fb->dev->dev, fb->fb.fix.smem_len,
+                             fb->fb.screen_base, fb->fb.fix.smem_start);
+}
 
 static int clcdfb_mmap(struct fb_info *info,
                       struct vm_area_struct *vma)
@@ -542,12 +558,246 @@ static int clcdfb_register(struct clcd_fb *fb)
        return ret;
 }
 
+struct string_lookup {
+       const char *string;
+       const u32       val;
+};
+
+static struct string_lookup vmode_lookups[] = {
+       { "FB_VMODE_NONINTERLACED", FB_VMODE_NONINTERLACED},
+       { "FB_VMODE_INTERLACED",    FB_VMODE_INTERLACED},
+       { "FB_VMODE_DOUBLE",        FB_VMODE_DOUBLE},
+       { "FB_VMODE_ODD_FLD_FIRST", FB_VMODE_ODD_FLD_FIRST},
+       { NULL, 0 },
+};
+
+static struct string_lookup tim2_lookups[] = {
+       { "TIM2_CLKSEL", TIM2_CLKSEL},
+       { "TIM2_IVS",    TIM2_IVS},
+       { "TIM2_IHS",    TIM2_IHS},
+       { "TIM2_IPC",    TIM2_IPC},
+       { "TIM2_IOE",    TIM2_IOE},
+       { "TIM2_BCD",    TIM2_BCD},
+       { NULL, 0},
+};
+static struct string_lookup cntl_lookups[] = {
+       {"CNTL_LCDEN",        CNTL_LCDEN},
+       {"CNTL_LCDBPP1",      CNTL_LCDBPP1},
+       {"CNTL_LCDBPP2",      CNTL_LCDBPP2},
+       {"CNTL_LCDBPP4",      CNTL_LCDBPP4},
+       {"CNTL_LCDBPP8",      CNTL_LCDBPP8},
+       {"CNTL_LCDBPP16",     CNTL_LCDBPP16},
+       {"CNTL_LCDBPP16_565", CNTL_LCDBPP16_565},
+       {"CNTL_LCDBPP16_444", CNTL_LCDBPP16_444},
+       {"CNTL_LCDBPP24",     CNTL_LCDBPP24},
+       {"CNTL_LCDBW",        CNTL_LCDBW},
+       {"CNTL_LCDTFT",       CNTL_LCDTFT},
+       {"CNTL_LCDMONO8",     CNTL_LCDMONO8},
+       {"CNTL_LCDDUAL",      CNTL_LCDDUAL},
+       {"CNTL_BGR",          CNTL_BGR},
+       {"CNTL_BEBO",         CNTL_BEBO},
+       {"CNTL_BEPO",         CNTL_BEPO},
+       {"CNTL_LCDPWR",       CNTL_LCDPWR},
+       {"CNTL_LCDVCOMP(1)",  CNTL_LCDVCOMP(1)},
+       {"CNTL_LCDVCOMP(2)",  CNTL_LCDVCOMP(2)},
+       {"CNTL_LCDVCOMP(3)",  CNTL_LCDVCOMP(3)},
+       {"CNTL_LCDVCOMP(4)",  CNTL_LCDVCOMP(4)},
+       {"CNTL_LCDVCOMP(5)",  CNTL_LCDVCOMP(5)},
+       {"CNTL_LCDVCOMP(6)",  CNTL_LCDVCOMP(6)},
+       {"CNTL_LCDVCOMP(7)",  CNTL_LCDVCOMP(7)},
+       {"CNTL_LDMAFIFOTIME", CNTL_LDMAFIFOTIME},
+       {"CNTL_WATERMARK",    CNTL_WATERMARK},
+       { NULL, 0},
+};
+static struct string_lookup caps_lookups[] = {
+       {"CLCD_CAP_RGB444",  CLCD_CAP_RGB444},
+       {"CLCD_CAP_RGB5551", CLCD_CAP_RGB5551},
+       {"CLCD_CAP_RGB565",  CLCD_CAP_RGB565},
+       {"CLCD_CAP_RGB888",  CLCD_CAP_RGB888},
+       {"CLCD_CAP_BGR444",  CLCD_CAP_BGR444},
+       {"CLCD_CAP_BGR5551", CLCD_CAP_BGR5551},
+       {"CLCD_CAP_BGR565",  CLCD_CAP_BGR565},
+       {"CLCD_CAP_BGR888",  CLCD_CAP_BGR888},
+       {"CLCD_CAP_444",     CLCD_CAP_444},
+       {"CLCD_CAP_5551",    CLCD_CAP_5551},
+       {"CLCD_CAP_565",     CLCD_CAP_565},
+       {"CLCD_CAP_888",     CLCD_CAP_888},
+       {"CLCD_CAP_RGB",     CLCD_CAP_RGB},
+       {"CLCD_CAP_BGR",     CLCD_CAP_BGR},
+       {"CLCD_CAP_ALL",     CLCD_CAP_ALL},
+       { NULL, 0},
+};
+
+u32 parse_setting(struct string_lookup *lookup, const char *name)
+{
+       int i = 0;
+       while (lookup[i].string != NULL) {
+               if (strcmp(lookup[i].string, name) == 0)
+                       return lookup[i].val;
+               ++i;
+       }
+       return -EINVAL;
+}
+
+u32 get_string_lookup(struct device_node *node, const char *name,
+                     struct string_lookup *lookup)
+{
+       const char *string;
+       int count, i, ret = 0;
+
+       count = of_property_count_strings(node, name);
+       if (count >= 0)
+               for (i = 0; i < count; i++)
+                       if (of_property_read_string_index(node, name, i,
+                                       &string) == 0)
+                               ret |= parse_setting(lookup, string);
+       return ret;
+}
+
+int get_val(struct device_node *node, const char *string)
+{
+       u32 ret = 0;
+
+       if (of_property_read_u32(node, string, &ret))
+               ret = -1;
+       return ret;
+}
+
+struct clcd_panel *getPanel(struct device_node *node)
+{
+       static struct clcd_panel panel;
+
+       panel.mode.refresh      = get_val(node, "refresh");
+       panel.mode.xres         = get_val(node, "xres");
+       panel.mode.yres         = get_val(node, "yres");
+       panel.mode.pixclock     = get_val(node, "pixclock");
+       panel.mode.left_margin  = get_val(node, "left_margin");
+       panel.mode.right_margin = get_val(node, "right_margin");
+       panel.mode.upper_margin = get_val(node, "upper_margin");
+       panel.mode.lower_margin = get_val(node, "lower_margin");
+       panel.mode.hsync_len    = get_val(node, "hsync_len");
+       panel.mode.vsync_len    = get_val(node, "vsync_len");
+       panel.mode.sync         = get_val(node, "sync");
+       panel.bpp               = get_val(node, "bpp");
+       panel.width             = (signed short) get_val(node, "width");
+       panel.height            = (signed short) get_val(node, "height");
+
+       panel.mode.vmode = get_string_lookup(node, "vmode", vmode_lookups);
+       panel.tim2       = get_string_lookup(node, "tim2",  tim2_lookups);
+       panel.cntl       = get_string_lookup(node, "cntl",  cntl_lookups);
+       panel.caps       = get_string_lookup(node, "caps",  caps_lookups);
+
+       return &panel;
+}
+
+struct clcd_panel *clcdfb_get_panel(const char *name)
+{
+       struct device_node *node = NULL;
+       const char *mode;
+       struct clcd_panel *panel = NULL;
+
+       do {
+               node = of_find_compatible_node(node, NULL, "panel");
+               if (node)
+                       if (of_property_read_string(node, "mode", &mode) == 0)
+                               if (strcmp(mode, name) == 0) {
+                                       panel = getPanel(node);
+                                       panel->mode.name = name;
+                               }
+       } while (node != NULL);
+
+       return panel;
+}
+
+#ifdef CONFIG_OF
+static int clcdfb_dt_init(struct clcd_fb *fb)
+{
+       int err = 0;
+       struct device_node *node;
+       const char *mode;
+       dma_addr_t dma;
+       u32 use_dma;
+       const __be32 *prop;
+       int len, na, ns;
+       phys_addr_t fb_base, fb_size;
+
+       node = fb->dev->dev.of_node;
+       if (!node)
+               return -ENODEV;
+
+       na = of_n_addr_cells(node);
+       ns = of_n_size_cells(node);
+
+       if (WARN_ON(of_property_read_string(node, "mode", &mode)))
+               return -ENODEV;
+
+       fb->panel = clcdfb_get_panel(mode);
+       if (!fb->panel)
+               return -EINVAL;
+       fb->fb.fix.smem_len = fb->panel->mode.xres * fb->panel->mode.yres * 2;
+
+       if (of_property_read_u32(node, "use_dma", &use_dma))
+               use_dma = 0;
+       if (use_dma) {
+               fb->fb.screen_base = dma_alloc_writecombine(&fb->dev->dev,
+                       fb->fb.fix.smem_len, &dma, GFP_KERNEL);
+               if (!fb->fb.screen_base) {
+                       pr_err("CLCD: unable to map framebuffer\n");
+                       err = -ENOMEM;
+               } else
+                       fb->fb.fix.smem_start   = dma;
+       } else {
+               prop = of_get_property(node, "framebuffer", &len);
+               if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop)))
+                       return -EINVAL;
+               fb_base = of_read_number(prop, na);
+               fb_size = of_read_number(prop + na, ns);
+
+               fb->fb.fix.smem_start = fb_base;
+               fb->fb.screen_base = ioremap_wc(fb->fb.fix.smem_start, fb_size);
+       }
+       return err;
+}
+#endif /* CONFIG_OF */
+
 static int clcdfb_probe(struct amba_device *dev, const struct amba_id *id)
 {
        struct clcd_board *board = dev->dev.platform_data;
        struct clcd_fb *fb;
        int ret;
 
+#ifdef CONFIG_OF
+       if (dev->dev.of_node) {
+               const __be32 *prop;
+               int len, na, ns;
+               phys_addr_t reg_base;
+
+               na = of_n_addr_cells(dev->dev.of_node);
+               ns = of_n_size_cells(dev->dev.of_node);
+
+               prop = of_get_property(dev->dev.of_node, "reg", &len);
+               if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop)))
+                       return -EINVAL;
+               reg_base = of_read_number(prop, na);
+
+               if (dev->res.start != reg_base)
+                       return -EINVAL;
+
+               if (!board) {
+                       board = kzalloc(sizeof(struct clcd_board), GFP_KERNEL);
+                       if (!board)
+                               return -EINVAL;
+                       board->name    = "Device Tree CLCD PL111";
+                       board->caps    = CLCD_CAP_5551 | CLCD_CAP_565;
+                       board->check   = clcdfb_check;
+                       board->decode  = clcdfb_decode;
+                       board->setup   = clcdfb_dt_init;
+                       board->mmap    = clcdfb_mmap_dma;
+                       board->remove  = clcdfb_remove_dma;
+               }
+       }
+#endif /* CONFIG_OF */
+
        if (!board)
                return -EINVAL;