0001
0002
0003
0004
0005
0006
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
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
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
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
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
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
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
0310 writeb(info->fix.smem_start >> 28, cfb->base + CLPS711X_FBADDR);
0311
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");