Merge tag 'driver-core-3.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git...
[firefly-linux-kernel-4.4.55.git] / drivers / video / fbdev / simplefb.c
index 76756c80cad6de4cf8f192204cb4dcf57213b334..92cac803dee3c2261655a34a45a6a0c2d170636f 100644 (file)
@@ -26,6 +26,8 @@
 #include <linux/module.h>
 #include <linux/platform_data/simplefb.h>
 #include <linux/platform_device.h>
+#include <linux/clk-provider.h>
+#include <linux/of_platform.h>
 
 static struct fb_fix_screeninfo simplefb_fix = {
        .id             = "simple",
@@ -41,6 +43,8 @@ static struct fb_var_screeninfo simplefb_var = {
        .vmode          = FB_VMODE_NONINTERLACED,
 };
 
+#define PSEUDO_PALETTE_SIZE 16
+
 static int simplefb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
                              u_int transp, struct fb_info *info)
 {
@@ -50,7 +54,7 @@ static int simplefb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
        u32 cb = blue >> (16 - info->var.blue.length);
        u32 value;
 
-       if (regno >= 16)
+       if (regno >= PSEUDO_PALETTE_SIZE)
                return -EINVAL;
 
        value = (cr << info->var.red.offset) |
@@ -163,11 +167,113 @@ static int simplefb_parse_pd(struct platform_device *pdev,
        return 0;
 }
 
+struct simplefb_par {
+       u32 palette[PSEUDO_PALETTE_SIZE];
+#if defined CONFIG_OF && defined CONFIG_COMMON_CLK
+       int clk_count;
+       struct clk **clks;
+#endif
+};
+
+#if defined CONFIG_OF && defined CONFIG_COMMON_CLK
+/*
+ * Clock handling code.
+ *
+ * Here we handle the clocks property of our "simple-framebuffer" dt node.
+ * This is necessary so that we can make sure that any clocks needed by
+ * the display engine that the bootloader set up for us (and for which it
+ * provided a simplefb dt node), stay up, for the life of the simplefb
+ * driver.
+ *
+ * When the driver unloads, we cleanly disable, and then release the clocks.
+ *
+ * We only complain about errors here, no action is taken as the most likely
+ * error can only happen due to a mismatch between the bootloader which set
+ * up simplefb, and the clock definitions in the device tree. Chances are
+ * that there are no adverse effects, and if there are, a clean teardown of
+ * the fb probe will not help us much either. So just complain and carry on,
+ * and hope that the user actually gets a working fb at the end of things.
+ */
+static int simplefb_clocks_init(struct simplefb_par *par,
+                               struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct clk *clock;
+       int i, ret;
+
+       if (dev_get_platdata(&pdev->dev) || !np)
+               return 0;
+
+       par->clk_count = of_clk_get_parent_count(np);
+       if (par->clk_count <= 0)
+               return 0;
+
+       par->clks = kcalloc(par->clk_count, sizeof(struct clk *), GFP_KERNEL);
+       if (!par->clks)
+               return -ENOMEM;
+
+       for (i = 0; i < par->clk_count; i++) {
+               clock = of_clk_get(np, i);
+               if (IS_ERR(clock)) {
+                       if (PTR_ERR(clock) == -EPROBE_DEFER) {
+                               while (--i >= 0) {
+                                       if (par->clks[i])
+                                               clk_put(par->clks[i]);
+                               }
+                               kfree(par->clks);
+                               return -EPROBE_DEFER;
+                       }
+                       dev_err(&pdev->dev, "%s: clock %d not found: %ld\n",
+                               __func__, i, PTR_ERR(clock));
+                       continue;
+               }
+               par->clks[i] = clock;
+       }
+
+       for (i = 0; i < par->clk_count; i++) {
+               if (par->clks[i]) {
+                       ret = clk_prepare_enable(par->clks[i]);
+                       if (ret) {
+                               dev_err(&pdev->dev,
+                                       "%s: failed to enable clock %d: %d\n",
+                                       __func__, i, ret);
+                               clk_put(par->clks[i]);
+                               par->clks[i] = NULL;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static void simplefb_clocks_destroy(struct simplefb_par *par)
+{
+       int i;
+
+       if (!par->clks)
+               return;
+
+       for (i = 0; i < par->clk_count; i++) {
+               if (par->clks[i]) {
+                       clk_disable_unprepare(par->clks[i]);
+                       clk_put(par->clks[i]);
+               }
+       }
+
+       kfree(par->clks);
+}
+#else
+static int simplefb_clocks_init(struct simplefb_par *par,
+       struct platform_device *pdev) { return 0; }
+static void simplefb_clocks_destroy(struct simplefb_par *par) { }
+#endif
+
 static int simplefb_probe(struct platform_device *pdev)
 {
        int ret;
        struct simplefb_params params;
        struct fb_info *info;
+       struct simplefb_par *par;
        struct resource *mem;
 
        if (fb_get_options("simplefb", NULL))
@@ -188,11 +294,13 @@ static int simplefb_probe(struct platform_device *pdev)
                return -EINVAL;
        }
 
-       info = framebuffer_alloc(sizeof(u32) * 16, &pdev->dev);
+       info = framebuffer_alloc(sizeof(struct simplefb_par), &pdev->dev);
        if (!info)
                return -ENOMEM;
        platform_set_drvdata(pdev, info);
 
+       par = info->par;
+
        info->fix = simplefb_fix;
        info->fix.smem_start = mem->start;
        info->fix.smem_len = resource_size(mem);
@@ -211,8 +319,8 @@ static int simplefb_probe(struct platform_device *pdev)
 
        info->apertures = alloc_apertures(1);
        if (!info->apertures) {
-               framebuffer_release(info);
-               return -ENOMEM;
+               ret = -ENOMEM;
+               goto error_fb_release;
        }
        info->apertures->ranges[0].base = info->fix.smem_start;
        info->apertures->ranges[0].size = info->fix.smem_len;
@@ -222,10 +330,14 @@ static int simplefb_probe(struct platform_device *pdev)
        info->screen_base = ioremap_wc(info->fix.smem_start,
                                       info->fix.smem_len);
        if (!info->screen_base) {
-               framebuffer_release(info);
-               return -ENODEV;
+               ret = -ENOMEM;
+               goto error_fb_release;
        }
-       info->pseudo_palette = (void *)(info + 1);
+       info->pseudo_palette = par->palette;
+
+       ret = simplefb_clocks_init(par, pdev);
+       if (ret < 0)
+               goto error_unmap;
 
        dev_info(&pdev->dev, "framebuffer at 0x%lx, 0x%x bytes, mapped to 0x%p\n",
                             info->fix.smem_start, info->fix.smem_len,
@@ -238,21 +350,29 @@ static int simplefb_probe(struct platform_device *pdev)
        ret = register_framebuffer(info);
        if (ret < 0) {
                dev_err(&pdev->dev, "Unable to register simplefb: %d\n", ret);
-               iounmap(info->screen_base);
-               framebuffer_release(info);
-               return ret;
+               goto error_clocks;
        }
 
        dev_info(&pdev->dev, "fb%d: simplefb registered!\n", info->node);
 
        return 0;
+
+error_clocks:
+       simplefb_clocks_destroy(par);
+error_unmap:
+       iounmap(info->screen_base);
+error_fb_release:
+       framebuffer_release(info);
+       return ret;
 }
 
 static int simplefb_remove(struct platform_device *pdev)
 {
        struct fb_info *info = platform_get_drvdata(pdev);
+       struct simplefb_par *par = info->par;
 
        unregister_framebuffer(info);
+       simplefb_clocks_destroy(par);
        framebuffer_release(info);
 
        return 0;
@@ -272,7 +392,27 @@ static struct platform_driver simplefb_driver = {
        .probe = simplefb_probe,
        .remove = simplefb_remove,
 };
-module_platform_driver(simplefb_driver);
+
+static int __init simplefb_init(void)
+{
+       int ret;
+       struct device_node *np;
+
+       ret = platform_driver_register(&simplefb_driver);
+       if (ret)
+               return ret;
+
+       if (IS_ENABLED(CONFIG_OF) && of_chosen) {
+               for_each_child_of_node(of_chosen, np) {
+                       if (of_device_is_compatible(np, "simple-framebuffer"))
+                               of_platform_device_create(np, NULL, NULL);
+               }
+       }
+
+       return 0;
+}
+
+fs_initcall(simplefb_init);
 
 MODULE_AUTHOR("Stephen Warren <swarren@wwwdotorg.org>");
 MODULE_DESCRIPTION("Simple framebuffer driver");