root/drivers/video/fbdev/clps711x-fb.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. clps711x_fb_setcolreg
  2. clps711x_fb_check_var
  3. clps711x_fb_set_par
  4. clps711x_fb_blank
  5. clps711x_lcd_check_fb
  6. clps711x_lcd_get_power
  7. clps711x_lcd_set_power
  8. clps711x_fb_probe
  9. clps711x_fb_remove

   1 // SPDX-License-Identifier: GPL-2.0-or-later
   2 /*
   3  * Cirrus Logic CLPS711X FB driver
   4  *
   5  * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
   6  * Based on driver by Russell King <rmk@arm.linux.org.uk>
   7  */
   8 
   9 #include <linux/clk.h>
  10 #include <linux/fb.h>
  11 #include <linux/io.h>
  12 #include <linux/lcd.h>
  13 #include <linux/module.h>
  14 #include <linux/of.h>
  15 #include <linux/platform_device.h>
  16 #include <linux/regmap.h>
  17 #include <linux/mfd/syscon.h>
  18 #include <linux/mfd/syscon/clps711x.h>
  19 #include <linux/regulator/consumer.h>
  20 #include <video/of_display_timing.h>
  21 
  22 #define CLPS711X_FB_NAME        "clps711x-fb"
  23 #define CLPS711X_FB_BPP_MAX     (4)
  24 
  25 /* Registers relative to LCDCON */
  26 #define CLPS711X_LCDCON         (0x0000)
  27 # define LCDCON_GSEN            BIT(30)
  28 # define LCDCON_GSMD            BIT(31)
  29 #define CLPS711X_PALLSW         (0x0280)
  30 #define CLPS711X_PALMSW         (0x02c0)
  31 #define CLPS711X_FBADDR         (0x0d40)
  32 
  33 struct clps711x_fb_info {
  34         struct clk              *clk;
  35         void __iomem            *base;
  36         struct regmap           *syscon;
  37         resource_size_t         buffsize;
  38         struct fb_videomode     mode;
  39         struct regulator        *lcd_pwr;
  40         u32                     ac_prescale;
  41         bool                    cmap_invert;
  42 };
  43 
  44 static int clps711x_fb_setcolreg(u_int regno, u_int red, u_int green,
  45                                  u_int blue, u_int transp, struct fb_info *info)
  46 {
  47         struct clps711x_fb_info *cfb = info->par;
  48         u32 level, mask, shift;
  49 
  50         if (regno >= BIT(info->var.bits_per_pixel))
  51                 return -EINVAL;
  52 
  53         shift = 4 * (regno & 7);
  54         mask  = 0xf << shift;
  55         /* gray = 0.30*R + 0.58*G + 0.11*B */
  56         level = (((red * 77 + green * 151 + blue * 28) >> 20) << shift) & mask;
  57         if (cfb->cmap_invert)
  58                 level = 0xf - level;
  59 
  60         regno = (regno < 8) ? CLPS711X_PALLSW : CLPS711X_PALMSW;
  61 
  62         writel((readl(cfb->base + regno) & ~mask) | level, cfb->base + regno);
  63 
  64         return 0;
  65 }
  66 
  67 static int clps711x_fb_check_var(struct fb_var_screeninfo *var,
  68                                  struct fb_info *info)
  69 {
  70         u32 val;
  71 
  72         if (var->bits_per_pixel < 1 ||
  73             var->bits_per_pixel > CLPS711X_FB_BPP_MAX)
  74                 return -EINVAL;
  75 
  76         if (!var->pixclock)
  77                 return -EINVAL;
  78 
  79         val = DIV_ROUND_UP(var->xres, 16) - 1;
  80         if (val < 0x01 || val > 0x3f)
  81                 return -EINVAL;
  82 
  83         val = DIV_ROUND_UP(var->yres * var->xres * var->bits_per_pixel, 128);
  84         val--;
  85         if (val < 0x001 || val > 0x1fff)
  86                 return -EINVAL;
  87 
  88         var->transp.msb_right   = 0;
  89         var->transp.offset      = 0;
  90         var->transp.length      = 0;
  91         var->red.msb_right      = 0;
  92         var->red.offset         = 0;
  93         var->red.length         = var->bits_per_pixel;
  94         var->green              = var->red;
  95         var->blue               = var->red;
  96         var->grayscale          = var->bits_per_pixel > 1;
  97 
  98         return 0;
  99 }
 100 
 101 static int clps711x_fb_set_par(struct fb_info *info)
 102 {
 103         struct clps711x_fb_info *cfb = info->par;
 104         resource_size_t size;
 105         u32 lcdcon, pps;
 106 
 107         size = (info->var.xres * info->var.yres * info->var.bits_per_pixel) / 8;
 108         if (size > cfb->buffsize)
 109                 return -EINVAL;
 110 
 111         switch (info->var.bits_per_pixel) {
 112         case 1:
 113                 info->fix.visual = FB_VISUAL_MONO01;
 114                 break;
 115         case 2:
 116         case 4:
 117                 info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
 118                 break;
 119         default:
 120                 return -EINVAL;
 121         }
 122 
 123         info->fix.line_length = info->var.xres * info->var.bits_per_pixel / 8;
 124         info->fix.smem_len = size;
 125 
 126         lcdcon = (info->var.xres * info->var.yres *
 127                   info->var.bits_per_pixel) / 128 - 1;
 128         lcdcon |= ((info->var.xres / 16) - 1) << 13;
 129         lcdcon |= (cfb->ac_prescale & 0x1f) << 25;
 130 
 131         pps = clk_get_rate(cfb->clk) / (PICOS2KHZ(info->var.pixclock) * 1000);
 132         if (pps)
 133                 pps--;
 134         lcdcon |= (pps & 0x3f) << 19;
 135 
 136         if (info->var.bits_per_pixel == 4)
 137                 lcdcon |= LCDCON_GSMD;
 138         if (info->var.bits_per_pixel >= 2)
 139                 lcdcon |= LCDCON_GSEN;
 140 
 141         /* LCDCON must only be changed while the LCD is disabled */
 142         regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0);
 143         writel(lcdcon, cfb->base + CLPS711X_LCDCON);
 144         regmap_update_bits(cfb->syscon, SYSCON_OFFSET,
 145                            SYSCON1_LCDEN, SYSCON1_LCDEN);
 146 
 147         return 0;
 148 }
 149 
 150 static int clps711x_fb_blank(int blank, struct fb_info *info)
 151 {
 152         /* Return happy */
 153         return 0;
 154 }
 155 
 156 static struct fb_ops clps711x_fb_ops = {
 157         .owner          = THIS_MODULE,
 158         .fb_setcolreg   = clps711x_fb_setcolreg,
 159         .fb_check_var   = clps711x_fb_check_var,
 160         .fb_set_par     = clps711x_fb_set_par,
 161         .fb_blank       = clps711x_fb_blank,
 162         .fb_fillrect    = sys_fillrect,
 163         .fb_copyarea    = sys_copyarea,
 164         .fb_imageblit   = sys_imageblit,
 165 };
 166 
 167 static int clps711x_lcd_check_fb(struct lcd_device *lcddev, struct fb_info *fi)
 168 {
 169         struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);
 170 
 171         return (!fi || fi->par == cfb) ? 1 : 0;
 172 }
 173 
 174 static int clps711x_lcd_get_power(struct lcd_device *lcddev)
 175 {
 176         struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);
 177 
 178         if (!IS_ERR_OR_NULL(cfb->lcd_pwr))
 179                 if (!regulator_is_enabled(cfb->lcd_pwr))
 180                         return FB_BLANK_NORMAL;
 181 
 182         return FB_BLANK_UNBLANK;
 183 }
 184 
 185 static int clps711x_lcd_set_power(struct lcd_device *lcddev, int blank)
 186 {
 187         struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);
 188 
 189         if (!IS_ERR_OR_NULL(cfb->lcd_pwr)) {
 190                 if (blank == FB_BLANK_UNBLANK) {
 191                         if (!regulator_is_enabled(cfb->lcd_pwr))
 192                                 return regulator_enable(cfb->lcd_pwr);
 193                 } else {
 194                         if (regulator_is_enabled(cfb->lcd_pwr))
 195                                 return regulator_disable(cfb->lcd_pwr);
 196                 }
 197         }
 198 
 199         return 0;
 200 }
 201 
 202 static struct lcd_ops clps711x_lcd_ops = {
 203         .check_fb       = clps711x_lcd_check_fb,
 204         .get_power      = clps711x_lcd_get_power,
 205         .set_power      = clps711x_lcd_set_power,
 206 };
 207 
 208 static int clps711x_fb_probe(struct platform_device *pdev)
 209 {
 210         struct device *dev = &pdev->dev;
 211         struct device_node *disp, *np = dev->of_node;
 212         struct clps711x_fb_info *cfb;
 213         struct lcd_device *lcd;
 214         struct fb_info *info;
 215         struct resource *res;
 216         int ret = -ENOENT;
 217         u32 val;
 218 
 219         if (fb_get_options(CLPS711X_FB_NAME, NULL))
 220                 return -ENODEV;
 221 
 222         info = framebuffer_alloc(sizeof(*cfb), dev);
 223         if (!info)
 224                 return -ENOMEM;
 225 
 226         cfb = info->par;
 227         platform_set_drvdata(pdev, info);
 228 
 229         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 230         if (!res)
 231                 goto out_fb_release;
 232         cfb->base = devm_ioremap(dev, res->start, resource_size(res));
 233         if (!cfb->base) {
 234                 ret = -ENOMEM;
 235                 goto out_fb_release;
 236         }
 237 
 238         info->fix.mmio_start = res->start;
 239         info->fix.mmio_len = resource_size(res);
 240 
 241         res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
 242         info->screen_base = devm_ioremap_resource(dev, res);
 243         if (IS_ERR(info->screen_base)) {
 244                 ret = PTR_ERR(info->screen_base);
 245                 goto out_fb_release;
 246         }
 247 
 248         /* Physical address should be aligned to 256 MiB */
 249         if (res->start & 0x0fffffff) {
 250                 ret = -EINVAL;
 251                 goto out_fb_release;
 252         }
 253 
 254         info->apertures = alloc_apertures(1);
 255         if (!info->apertures) {
 256                 ret = -ENOMEM;
 257                 goto out_fb_release;
 258         }
 259 
 260         cfb->buffsize = resource_size(res);
 261         info->fix.smem_start = res->start;
 262         info->apertures->ranges[0].base = info->fix.smem_start;
 263         info->apertures->ranges[0].size = cfb->buffsize;
 264 
 265         cfb->clk = devm_clk_get(dev, NULL);
 266         if (IS_ERR(cfb->clk)) {
 267                 ret = PTR_ERR(cfb->clk);
 268                 goto out_fb_release;
 269         }
 270 
 271         cfb->syscon =
 272                 syscon_regmap_lookup_by_compatible("cirrus,ep7209-syscon1");
 273         if (IS_ERR(cfb->syscon)) {
 274                 ret = PTR_ERR(cfb->syscon);
 275                 goto out_fb_release;
 276         }
 277 
 278         disp = of_parse_phandle(np, "display", 0);
 279         if (!disp) {
 280                 dev_err(&pdev->dev, "No display defined\n");
 281                 ret = -ENODATA;
 282                 goto out_fb_release;
 283         }
 284 
 285         ret = of_get_fb_videomode(disp, &cfb->mode, OF_USE_NATIVE_MODE);
 286         if (ret) {
 287                 of_node_put(disp);
 288                 goto out_fb_release;
 289         }
 290 
 291         of_property_read_u32(disp, "ac-prescale", &cfb->ac_prescale);
 292         cfb->cmap_invert = of_property_read_bool(disp, "cmap-invert");
 293 
 294         ret = of_property_read_u32(disp, "bits-per-pixel",
 295                                    &info->var.bits_per_pixel);
 296         of_node_put(disp);
 297         if (ret)
 298                 goto out_fb_release;
 299 
 300         /* Force disable LCD on any mismatch */
 301         if (info->fix.smem_start != (readb(cfb->base + CLPS711X_FBADDR) << 28))
 302                 regmap_update_bits(cfb->syscon, SYSCON_OFFSET,
 303                                    SYSCON1_LCDEN, 0);
 304 
 305         ret = regmap_read(cfb->syscon, SYSCON_OFFSET, &val);
 306         if (ret)
 307                 goto out_fb_release;
 308 
 309         if (!(val & SYSCON1_LCDEN)) {
 310                 /* Setup start FB address */
 311                 writeb(info->fix.smem_start >> 28, cfb->base + CLPS711X_FBADDR);
 312                 /* Clean FB memory */
 313                 memset_io(info->screen_base, 0, cfb->buffsize);
 314         }
 315 
 316         cfb->lcd_pwr = devm_regulator_get(dev, "lcd");
 317         if (PTR_ERR(cfb->lcd_pwr) == -EPROBE_DEFER) {
 318                 ret = -EPROBE_DEFER;
 319                 goto out_fb_release;
 320         }
 321 
 322         info->fbops = &clps711x_fb_ops;
 323         info->flags = FBINFO_DEFAULT;
 324         info->var.activate = FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW;
 325         info->var.height = -1;
 326         info->var.width = -1;
 327         info->var.vmode = FB_VMODE_NONINTERLACED;
 328         info->fix.type = FB_TYPE_PACKED_PIXELS;
 329         info->fix.accel = FB_ACCEL_NONE;
 330         strlcpy(info->fix.id, CLPS711X_FB_NAME, sizeof(info->fix.id));
 331         fb_videomode_to_var(&info->var, &cfb->mode);
 332 
 333         ret = fb_alloc_cmap(&info->cmap, BIT(CLPS711X_FB_BPP_MAX), 0);
 334         if (ret)
 335                 goto out_fb_release;
 336 
 337         ret = fb_set_var(info, &info->var);
 338         if (ret)
 339                 goto out_fb_dealloc_cmap;
 340 
 341         ret = register_framebuffer(info);
 342         if (ret)
 343                 goto out_fb_dealloc_cmap;
 344 
 345         lcd = devm_lcd_device_register(dev, "clps711x-lcd", dev, cfb,
 346                                        &clps711x_lcd_ops);
 347         if (!IS_ERR(lcd))
 348                 return 0;
 349         
 350         ret = PTR_ERR(lcd);
 351         unregister_framebuffer(info);
 352 
 353 out_fb_dealloc_cmap:
 354         regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0);
 355         fb_dealloc_cmap(&info->cmap);
 356 
 357 out_fb_release:
 358         framebuffer_release(info);
 359 
 360         return ret;
 361 }
 362 
 363 static int clps711x_fb_remove(struct platform_device *pdev)
 364 {
 365         struct fb_info *info = platform_get_drvdata(pdev);
 366         struct clps711x_fb_info *cfb = info->par;
 367 
 368         regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0);
 369 
 370         unregister_framebuffer(info);
 371         fb_dealloc_cmap(&info->cmap);
 372         framebuffer_release(info);
 373 
 374         return 0;
 375 }
 376 
 377 static const struct of_device_id clps711x_fb_dt_ids[] = {
 378         { .compatible = "cirrus,ep7209-fb", },
 379         { }
 380 };
 381 MODULE_DEVICE_TABLE(of, clps711x_fb_dt_ids);
 382 
 383 static struct platform_driver clps711x_fb_driver = {
 384         .driver = {
 385                 .name           = CLPS711X_FB_NAME,
 386                 .of_match_table = clps711x_fb_dt_ids,
 387         },
 388         .probe  = clps711x_fb_probe,
 389         .remove = clps711x_fb_remove,
 390 };
 391 module_platform_driver(clps711x_fb_driver);
 392 
 393 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
 394 MODULE_DESCRIPTION("Cirrus Logic CLPS711X FB driver");
 395 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */