Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Cirrus Logic CLPS711X FB driver
0004  *
0005  * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
0006  * Based on driver by Russell King <rmk@arm.linux.org.uk>
0007  */
0008 
0009 #include <linux/clk.h>
0010 #include <linux/fb.h>
0011 #include <linux/io.h>
0012 #include <linux/lcd.h>
0013 #include <linux/module.h>
0014 #include <linux/of.h>
0015 #include <linux/platform_device.h>
0016 #include <linux/regmap.h>
0017 #include <linux/mfd/syscon.h>
0018 #include <linux/mfd/syscon/clps711x.h>
0019 #include <linux/regulator/consumer.h>
0020 #include <video/of_display_timing.h>
0021 
0022 #define CLPS711X_FB_NAME    "clps711x-fb"
0023 #define CLPS711X_FB_BPP_MAX (4)
0024 
0025 /* Registers relative to LCDCON */
0026 #define CLPS711X_LCDCON     (0x0000)
0027 # define LCDCON_GSEN        BIT(30)
0028 # define LCDCON_GSMD        BIT(31)
0029 #define CLPS711X_PALLSW     (0x0280)
0030 #define CLPS711X_PALMSW     (0x02c0)
0031 #define CLPS711X_FBADDR     (0x0d40)
0032 
0033 struct clps711x_fb_info {
0034     struct clk      *clk;
0035     void __iomem        *base;
0036     struct regmap       *syscon;
0037     resource_size_t     buffsize;
0038     struct fb_videomode mode;
0039     struct regulator    *lcd_pwr;
0040     u32         ac_prescale;
0041     bool            cmap_invert;
0042 };
0043 
0044 static int clps711x_fb_setcolreg(u_int regno, u_int red, u_int green,
0045                  u_int blue, u_int transp, struct fb_info *info)
0046 {
0047     struct clps711x_fb_info *cfb = info->par;
0048     u32 level, mask, shift;
0049 
0050     if (regno >= BIT(info->var.bits_per_pixel))
0051         return -EINVAL;
0052 
0053     shift = 4 * (regno & 7);
0054     mask  = 0xf << shift;
0055     /* gray = 0.30*R + 0.58*G + 0.11*B */
0056     level = (((red * 77 + green * 151 + blue * 28) >> 20) << shift) & mask;
0057     if (cfb->cmap_invert)
0058         level = 0xf - level;
0059 
0060     regno = (regno < 8) ? CLPS711X_PALLSW : CLPS711X_PALMSW;
0061 
0062     writel((readl(cfb->base + regno) & ~mask) | level, cfb->base + regno);
0063 
0064     return 0;
0065 }
0066 
0067 static int clps711x_fb_check_var(struct fb_var_screeninfo *var,
0068                  struct fb_info *info)
0069 {
0070     u32 val;
0071 
0072     if (var->bits_per_pixel < 1 ||
0073         var->bits_per_pixel > CLPS711X_FB_BPP_MAX)
0074         return -EINVAL;
0075 
0076     if (!var->pixclock)
0077         return -EINVAL;
0078 
0079     val = DIV_ROUND_UP(var->xres, 16) - 1;
0080     if (val < 0x01 || val > 0x3f)
0081         return -EINVAL;
0082 
0083     val = DIV_ROUND_UP(var->yres * var->xres * var->bits_per_pixel, 128);
0084     val--;
0085     if (val < 0x001 || val > 0x1fff)
0086         return -EINVAL;
0087 
0088     var->transp.msb_right   = 0;
0089     var->transp.offset  = 0;
0090     var->transp.length  = 0;
0091     var->red.msb_right  = 0;
0092     var->red.offset     = 0;
0093     var->red.length     = var->bits_per_pixel;
0094     var->green      = var->red;
0095     var->blue       = var->red;
0096     var->grayscale      = var->bits_per_pixel > 1;
0097 
0098     return 0;
0099 }
0100 
0101 static int clps711x_fb_set_par(struct fb_info *info)
0102 {
0103     struct clps711x_fb_info *cfb = info->par;
0104     resource_size_t size;
0105     u32 lcdcon, pps;
0106 
0107     size = (info->var.xres * info->var.yres * info->var.bits_per_pixel) / 8;
0108     if (size > cfb->buffsize)
0109         return -EINVAL;
0110 
0111     switch (info->var.bits_per_pixel) {
0112     case 1:
0113         info->fix.visual = FB_VISUAL_MONO01;
0114         break;
0115     case 2:
0116     case 4:
0117         info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
0118         break;
0119     default:
0120         return -EINVAL;
0121     }
0122 
0123     info->fix.line_length = info->var.xres * info->var.bits_per_pixel / 8;
0124     info->fix.smem_len = size;
0125 
0126     lcdcon = (info->var.xres * info->var.yres *
0127           info->var.bits_per_pixel) / 128 - 1;
0128     lcdcon |= ((info->var.xres / 16) - 1) << 13;
0129     lcdcon |= (cfb->ac_prescale & 0x1f) << 25;
0130 
0131     pps = clk_get_rate(cfb->clk) / (PICOS2KHZ(info->var.pixclock) * 1000);
0132     if (pps)
0133         pps--;
0134     lcdcon |= (pps & 0x3f) << 19;
0135 
0136     if (info->var.bits_per_pixel == 4)
0137         lcdcon |= LCDCON_GSMD;
0138     if (info->var.bits_per_pixel >= 2)
0139         lcdcon |= LCDCON_GSEN;
0140 
0141     /* LCDCON must only be changed while the LCD is disabled */
0142     regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0);
0143     writel(lcdcon, cfb->base + CLPS711X_LCDCON);
0144     regmap_update_bits(cfb->syscon, SYSCON_OFFSET,
0145                SYSCON1_LCDEN, SYSCON1_LCDEN);
0146 
0147     return 0;
0148 }
0149 
0150 static int clps711x_fb_blank(int blank, struct fb_info *info)
0151 {
0152     /* Return happy */
0153     return 0;
0154 }
0155 
0156 static const struct fb_ops clps711x_fb_ops = {
0157     .owner      = THIS_MODULE,
0158     .fb_setcolreg   = clps711x_fb_setcolreg,
0159     .fb_check_var   = clps711x_fb_check_var,
0160     .fb_set_par = clps711x_fb_set_par,
0161     .fb_blank   = clps711x_fb_blank,
0162     .fb_fillrect    = sys_fillrect,
0163     .fb_copyarea    = sys_copyarea,
0164     .fb_imageblit   = sys_imageblit,
0165 };
0166 
0167 static int clps711x_lcd_check_fb(struct lcd_device *lcddev, struct fb_info *fi)
0168 {
0169     struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);
0170 
0171     return (!fi || fi->par == cfb) ? 1 : 0;
0172 }
0173 
0174 static int clps711x_lcd_get_power(struct lcd_device *lcddev)
0175 {
0176     struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);
0177 
0178     if (!IS_ERR_OR_NULL(cfb->lcd_pwr))
0179         if (!regulator_is_enabled(cfb->lcd_pwr))
0180             return FB_BLANK_NORMAL;
0181 
0182     return FB_BLANK_UNBLANK;
0183 }
0184 
0185 static int clps711x_lcd_set_power(struct lcd_device *lcddev, int blank)
0186 {
0187     struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);
0188 
0189     if (!IS_ERR_OR_NULL(cfb->lcd_pwr)) {
0190         if (blank == FB_BLANK_UNBLANK) {
0191             if (!regulator_is_enabled(cfb->lcd_pwr))
0192                 return regulator_enable(cfb->lcd_pwr);
0193         } else {
0194             if (regulator_is_enabled(cfb->lcd_pwr))
0195                 return regulator_disable(cfb->lcd_pwr);
0196         }
0197     }
0198 
0199     return 0;
0200 }
0201 
0202 static struct lcd_ops clps711x_lcd_ops = {
0203     .check_fb   = clps711x_lcd_check_fb,
0204     .get_power  = clps711x_lcd_get_power,
0205     .set_power  = clps711x_lcd_set_power,
0206 };
0207 
0208 static int clps711x_fb_probe(struct platform_device *pdev)
0209 {
0210     struct device *dev = &pdev->dev;
0211     struct device_node *disp, *np = dev->of_node;
0212     struct clps711x_fb_info *cfb;
0213     struct lcd_device *lcd;
0214     struct fb_info *info;
0215     struct resource *res;
0216     int ret = -ENOENT;
0217     u32 val;
0218 
0219     if (fb_get_options(CLPS711X_FB_NAME, NULL))
0220         return -ENODEV;
0221 
0222     info = framebuffer_alloc(sizeof(*cfb), dev);
0223     if (!info)
0224         return -ENOMEM;
0225 
0226     cfb = info->par;
0227     platform_set_drvdata(pdev, info);
0228 
0229     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
0230     if (!res)
0231         goto out_fb_release;
0232     cfb->base = devm_ioremap(dev, res->start, resource_size(res));
0233     if (!cfb->base) {
0234         ret = -ENOMEM;
0235         goto out_fb_release;
0236     }
0237 
0238     info->fix.mmio_start = res->start;
0239     info->fix.mmio_len = resource_size(res);
0240 
0241     res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
0242     info->screen_base = devm_ioremap_resource(dev, res);
0243     if (IS_ERR(info->screen_base)) {
0244         ret = PTR_ERR(info->screen_base);
0245         goto out_fb_release;
0246     }
0247 
0248     /* Physical address should be aligned to 256 MiB */
0249     if (res->start & 0x0fffffff) {
0250         ret = -EINVAL;
0251         goto out_fb_release;
0252     }
0253 
0254     info->apertures = alloc_apertures(1);
0255     if (!info->apertures) {
0256         ret = -ENOMEM;
0257         goto out_fb_release;
0258     }
0259 
0260     cfb->buffsize = resource_size(res);
0261     info->fix.smem_start = res->start;
0262     info->apertures->ranges[0].base = info->fix.smem_start;
0263     info->apertures->ranges[0].size = cfb->buffsize;
0264 
0265     cfb->clk = devm_clk_get(dev, NULL);
0266     if (IS_ERR(cfb->clk)) {
0267         ret = PTR_ERR(cfb->clk);
0268         goto out_fb_release;
0269     }
0270 
0271     cfb->syscon = syscon_regmap_lookup_by_phandle(np, "syscon");
0272     if (IS_ERR(cfb->syscon)) {
0273         ret = PTR_ERR(cfb->syscon);
0274         goto out_fb_release;
0275     }
0276 
0277     disp = of_parse_phandle(np, "display", 0);
0278     if (!disp) {
0279         dev_err(&pdev->dev, "No display defined\n");
0280         ret = -ENODATA;
0281         goto out_fb_release;
0282     }
0283 
0284     ret = of_get_fb_videomode(disp, &cfb->mode, OF_USE_NATIVE_MODE);
0285     if (ret) {
0286         of_node_put(disp);
0287         goto out_fb_release;
0288     }
0289 
0290     of_property_read_u32(disp, "ac-prescale", &cfb->ac_prescale);
0291     cfb->cmap_invert = of_property_read_bool(disp, "cmap-invert");
0292 
0293     ret = of_property_read_u32(disp, "bits-per-pixel",
0294                    &info->var.bits_per_pixel);
0295     of_node_put(disp);
0296     if (ret)
0297         goto out_fb_release;
0298 
0299     /* Force disable LCD on any mismatch */
0300     if (info->fix.smem_start != (readb(cfb->base + CLPS711X_FBADDR) << 28))
0301         regmap_update_bits(cfb->syscon, SYSCON_OFFSET,
0302                    SYSCON1_LCDEN, 0);
0303 
0304     ret = regmap_read(cfb->syscon, SYSCON_OFFSET, &val);
0305     if (ret)
0306         goto out_fb_release;
0307 
0308     if (!(val & SYSCON1_LCDEN)) {
0309         /* Setup start FB address */
0310         writeb(info->fix.smem_start >> 28, cfb->base + CLPS711X_FBADDR);
0311         /* Clean FB memory */
0312         memset_io(info->screen_base, 0, cfb->buffsize);
0313     }
0314 
0315     cfb->lcd_pwr = devm_regulator_get(dev, "lcd");
0316     if (PTR_ERR(cfb->lcd_pwr) == -EPROBE_DEFER) {
0317         ret = -EPROBE_DEFER;
0318         goto out_fb_release;
0319     }
0320 
0321     info->fbops = &clps711x_fb_ops;
0322     info->flags = FBINFO_DEFAULT;
0323     info->var.activate = FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW;
0324     info->var.height = -1;
0325     info->var.width = -1;
0326     info->var.vmode = FB_VMODE_NONINTERLACED;
0327     info->fix.type = FB_TYPE_PACKED_PIXELS;
0328     info->fix.accel = FB_ACCEL_NONE;
0329     strscpy(info->fix.id, CLPS711X_FB_NAME, sizeof(info->fix.id));
0330     fb_videomode_to_var(&info->var, &cfb->mode);
0331 
0332     ret = fb_alloc_cmap(&info->cmap, BIT(CLPS711X_FB_BPP_MAX), 0);
0333     if (ret)
0334         goto out_fb_release;
0335 
0336     ret = fb_set_var(info, &info->var);
0337     if (ret)
0338         goto out_fb_dealloc_cmap;
0339 
0340     ret = register_framebuffer(info);
0341     if (ret)
0342         goto out_fb_dealloc_cmap;
0343 
0344     lcd = devm_lcd_device_register(dev, "clps711x-lcd", dev, cfb,
0345                        &clps711x_lcd_ops);
0346     if (!IS_ERR(lcd))
0347         return 0;
0348     
0349     ret = PTR_ERR(lcd);
0350     unregister_framebuffer(info);
0351 
0352 out_fb_dealloc_cmap:
0353     regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0);
0354     fb_dealloc_cmap(&info->cmap);
0355 
0356 out_fb_release:
0357     framebuffer_release(info);
0358 
0359     return ret;
0360 }
0361 
0362 static int clps711x_fb_remove(struct platform_device *pdev)
0363 {
0364     struct fb_info *info = platform_get_drvdata(pdev);
0365     struct clps711x_fb_info *cfb = info->par;
0366 
0367     regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0);
0368 
0369     unregister_framebuffer(info);
0370     fb_dealloc_cmap(&info->cmap);
0371     framebuffer_release(info);
0372 
0373     return 0;
0374 }
0375 
0376 static const struct of_device_id clps711x_fb_dt_ids[] = {
0377     { .compatible = "cirrus,ep7209-fb", },
0378     { }
0379 };
0380 MODULE_DEVICE_TABLE(of, clps711x_fb_dt_ids);
0381 
0382 static struct platform_driver clps711x_fb_driver = {
0383     .driver = {
0384         .name       = CLPS711X_FB_NAME,
0385         .of_match_table = clps711x_fb_dt_ids,
0386     },
0387     .probe  = clps711x_fb_probe,
0388     .remove = clps711x_fb_remove,
0389 };
0390 module_platform_driver(clps711x_fb_driver);
0391 
0392 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
0393 MODULE_DESCRIPTION("Cirrus Logic CLPS711X FB driver");
0394 MODULE_LICENSE("GPL");