381 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Cirrus Logic CLPS711X FB driver4 *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 33struct 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 44static 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 67static 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 101static 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 150static int clps711x_fb_blank(int blank, struct fb_info *info)151{152 /* Return happy */153 return 0;154}155 156static const struct fb_ops clps711x_fb_ops = {157 .owner = THIS_MODULE,158 FB_DEFAULT_IOMEM_OPS,159 .fb_setcolreg = clps711x_fb_setcolreg,160 .fb_check_var = clps711x_fb_check_var,161 .fb_set_par = clps711x_fb_set_par,162 .fb_blank = clps711x_fb_blank,163};164 165static int clps711x_lcd_check_fb(struct lcd_device *lcddev, struct fb_info *fi)166{167 struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);168 169 return (!fi || fi->par == cfb) ? 1 : 0;170}171 172static int clps711x_lcd_get_power(struct lcd_device *lcddev)173{174 struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);175 176 if (!IS_ERR_OR_NULL(cfb->lcd_pwr))177 if (!regulator_is_enabled(cfb->lcd_pwr))178 return FB_BLANK_NORMAL;179 180 return FB_BLANK_UNBLANK;181}182 183static int clps711x_lcd_set_power(struct lcd_device *lcddev, int blank)184{185 struct clps711x_fb_info *cfb = dev_get_drvdata(&lcddev->dev);186 187 if (!IS_ERR_OR_NULL(cfb->lcd_pwr)) {188 if (blank == FB_BLANK_UNBLANK) {189 if (!regulator_is_enabled(cfb->lcd_pwr))190 return regulator_enable(cfb->lcd_pwr);191 } else {192 if (regulator_is_enabled(cfb->lcd_pwr))193 return regulator_disable(cfb->lcd_pwr);194 }195 }196 197 return 0;198}199 200static const struct lcd_ops clps711x_lcd_ops = {201 .check_fb = clps711x_lcd_check_fb,202 .get_power = clps711x_lcd_get_power,203 .set_power = clps711x_lcd_set_power,204};205 206static int clps711x_fb_probe(struct platform_device *pdev)207{208 struct device *dev = &pdev->dev;209 struct device_node *disp, *np = dev->of_node;210 struct clps711x_fb_info *cfb;211 struct lcd_device *lcd;212 struct fb_info *info;213 struct resource *res;214 int ret = -ENOENT;215 u32 val;216 217 if (fb_get_options(CLPS711X_FB_NAME, NULL))218 return -ENODEV;219 220 info = framebuffer_alloc(sizeof(*cfb), dev);221 if (!info)222 return -ENOMEM;223 224 cfb = info->par;225 platform_set_drvdata(pdev, info);226 227 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);228 if (!res)229 goto out_fb_release;230 cfb->base = devm_ioremap(dev, res->start, resource_size(res));231 if (!cfb->base) {232 ret = -ENOMEM;233 goto out_fb_release;234 }235 236 info->fix.mmio_start = res->start;237 info->fix.mmio_len = resource_size(res);238 239 info->screen_base = devm_platform_get_and_ioremap_resource(pdev, 1, &res);240 if (IS_ERR(info->screen_base)) {241 ret = PTR_ERR(info->screen_base);242 goto out_fb_release;243 }244 245 /* Physical address should be aligned to 256 MiB */246 if (res->start & 0x0fffffff) {247 ret = -EINVAL;248 goto out_fb_release;249 }250 251 cfb->buffsize = resource_size(res);252 info->fix.smem_start = res->start;253 254 cfb->clk = devm_clk_get(dev, NULL);255 if (IS_ERR(cfb->clk)) {256 ret = PTR_ERR(cfb->clk);257 goto out_fb_release;258 }259 260 cfb->syscon = syscon_regmap_lookup_by_phandle(np, "syscon");261 if (IS_ERR(cfb->syscon)) {262 ret = PTR_ERR(cfb->syscon);263 goto out_fb_release;264 }265 266 disp = of_parse_phandle(np, "display", 0);267 if (!disp) {268 dev_err(&pdev->dev, "No display defined\n");269 ret = -ENODATA;270 goto out_fb_release;271 }272 273 ret = of_get_fb_videomode(disp, &cfb->mode, OF_USE_NATIVE_MODE);274 if (ret) {275 of_node_put(disp);276 goto out_fb_release;277 }278 279 of_property_read_u32(disp, "ac-prescale", &cfb->ac_prescale);280 cfb->cmap_invert = of_property_read_bool(disp, "cmap-invert");281 282 ret = of_property_read_u32(disp, "bits-per-pixel",283 &info->var.bits_per_pixel);284 of_node_put(disp);285 if (ret)286 goto out_fb_release;287 288 /* Force disable LCD on any mismatch */289 if (info->fix.smem_start != (readb(cfb->base + CLPS711X_FBADDR) << 28))290 regmap_update_bits(cfb->syscon, SYSCON_OFFSET,291 SYSCON1_LCDEN, 0);292 293 ret = regmap_read(cfb->syscon, SYSCON_OFFSET, &val);294 if (ret)295 goto out_fb_release;296 297 if (!(val & SYSCON1_LCDEN)) {298 /* Setup start FB address */299 writeb(info->fix.smem_start >> 28, cfb->base + CLPS711X_FBADDR);300 /* Clean FB memory */301 memset_io(info->screen_base, 0, cfb->buffsize);302 }303 304 cfb->lcd_pwr = devm_regulator_get(dev, "lcd");305 if (PTR_ERR(cfb->lcd_pwr) == -EPROBE_DEFER) {306 ret = -EPROBE_DEFER;307 goto out_fb_release;308 }309 310 info->fbops = &clps711x_fb_ops;311 info->var.activate = FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW;312 info->var.height = -1;313 info->var.width = -1;314 info->var.vmode = FB_VMODE_NONINTERLACED;315 info->fix.type = FB_TYPE_PACKED_PIXELS;316 info->fix.accel = FB_ACCEL_NONE;317 strscpy(info->fix.id, CLPS711X_FB_NAME, sizeof(info->fix.id));318 fb_videomode_to_var(&info->var, &cfb->mode);319 320 ret = fb_alloc_cmap(&info->cmap, BIT(CLPS711X_FB_BPP_MAX), 0);321 if (ret)322 goto out_fb_release;323 324 ret = fb_set_var(info, &info->var);325 if (ret)326 goto out_fb_dealloc_cmap;327 328 ret = register_framebuffer(info);329 if (ret)330 goto out_fb_dealloc_cmap;331 332 lcd = devm_lcd_device_register(dev, "clps711x-lcd", dev, cfb,333 &clps711x_lcd_ops);334 if (!IS_ERR(lcd))335 return 0;336 337 ret = PTR_ERR(lcd);338 unregister_framebuffer(info);339 340out_fb_dealloc_cmap:341 regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0);342 fb_dealloc_cmap(&info->cmap);343 344out_fb_release:345 framebuffer_release(info);346 347 return ret;348}349 350static void clps711x_fb_remove(struct platform_device *pdev)351{352 struct fb_info *info = platform_get_drvdata(pdev);353 struct clps711x_fb_info *cfb = info->par;354 355 regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0);356 357 unregister_framebuffer(info);358 fb_dealloc_cmap(&info->cmap);359 framebuffer_release(info);360}361 362static const struct of_device_id clps711x_fb_dt_ids[] = {363 { .compatible = "cirrus,ep7209-fb", },364 { }365};366MODULE_DEVICE_TABLE(of, clps711x_fb_dt_ids);367 368static struct platform_driver clps711x_fb_driver = {369 .driver = {370 .name = CLPS711X_FB_NAME,371 .of_match_table = clps711x_fb_dt_ids,372 },373 .probe = clps711x_fb_probe,374 .remove = clps711x_fb_remove,375};376module_platform_driver(clps711x_fb_driver);377 378MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");379MODULE_DESCRIPTION("Cirrus Logic CLPS711X FB driver");380MODULE_LICENSE("GPL");381