| 1 | |
| 2 | * Xilinx TFT frame buffer driver
|
| 3 | *
|
| 4 | * Author: MontaVista Software, Inc.
|
| 5 | * [email protected]
|
| 6 | *
|
| 7 | * 2002-2007 (c) MontaVista Software, Inc.
|
| 8 | * 2007 (c) Secret Lab Technologies, Ltd.
|
| 9 | * 2009 (c) Xilinx Inc.
|
| 10 | *
|
| 11 | * This file is licensed under the terms of the GNU General Public License
|
| 12 | * version 2. This program is licensed "as is" without any warranty of any
|
| 13 | * kind, whether express or implied.
|
| 14 |
|
| 15 |
|
| 16 | |
| 17 | * This driver was based on au1100fb.c by MontaVista rewritten for 2.6
|
| 18 | * by Embedded Alley Solutions <[email protected]>, which in turn
|
| 19 | * was based on skeletonfb.c, Skeleton for a frame buffer device by
|
| 20 | * Geert Uytterhoeven.
|
| 21 |
|
| 22 |
|
| 23 | #include <linux/device.h>
|
| 24 | #include <linux/module.h>
|
| 25 | #include <linux/kernel.h>
|
| 26 | #include <linux/errno.h>
|
| 27 | #include <linux/platform_device.h>
|
| 28 | #include <linux/string.h>
|
| 29 | #include <linux/mm.h>
|
| 30 | #include <linux/fb.h>
|
| 31 | #include <linux/init.h>
|
| 32 | #include <linux/dma-mapping.h>
|
| 33 | #include <linux/of.h>
|
| 34 | #include <linux/io.h>
|
| 35 | #include <linux/slab.h>
|
| 36 |
|
| 37 | #ifdef CONFIG_PPC_DCR
|
| 38 | #include <asm/dcr.h>
|
| 39 | #endif
|
| 40 |
|
| 41 | #define DRIVER_NAME "xilinxfb"
|
| 42 |
|
| 43 | |
| 44 | * Xilinx calls it "TFT LCD Controller" though it can also be used for
|
| 45 | * the VGA port on the Xilinx ML40x board. This is a hardware display
|
| 46 | * controller for a 640x480 resolution TFT or VGA screen.
|
| 47 | *
|
| 48 | * The interface to the framebuffer is nice and simple. There are two
|
| 49 | * control registers. The first tells the LCD interface where in memory
|
| 50 | * the frame buffer is (only the 11 most significant bits are used, so
|
| 51 | * don't start thinking about scrolling). The second allows the LCD to
|
| 52 | * be turned on or off as well as rotated 180 degrees.
|
| 53 | *
|
| 54 | * In case of direct BUS access the second control register will be at
|
| 55 | * an offset of 4 as compared to the DCR access where the offset is 1
|
| 56 | * i.e. REG_CTRL. So this is taken care in the function
|
| 57 | * xilinx_fb_out32 where it left shifts the offset 2 times in case of
|
| 58 | * direct BUS access.
|
| 59 |
|
| 60 | #define NUM_REGS 2
|
| 61 | #define REG_FB_ADDR 0
|
| 62 | #define REG_CTRL 1
|
| 63 | #define REG_CTRL_ENABLE 0x0001
|
| 64 | #define REG_CTRL_ROTATE 0x0002
|
| 65 |
|
| 66 | |
| 67 | * The hardware only handles a single mode: 640x480 24 bit true
|
| 68 | * color. Each pixel gets a word (32 bits) of memory. Within each word,
|
| 69 | * the 8 most significant bits are ignored, the next 8 bits are the red
|
| 70 | * level, the next 8 bits are the green level and the 8 least
|
| 71 | * significant bits are the blue level. Each row of the LCD uses 1024
|
| 72 | * words, but only the first 640 pixels are displayed with the other 384
|
| 73 | * words being ignored. There are 480 rows.
|
| 74 |
|
| 75 | #define BYTES_PER_PIXEL 4
|
| 76 | #define BITS_PER_PIXEL (BYTES_PER_PIXEL * 8)
|
| 77 |
|
| 78 | #define RED_SHIFT 16
|
| 79 | #define GREEN_SHIFT 8
|
| 80 | #define BLUE_SHIFT 0
|
| 81 |
|
| 82 | #define PALETTE_ENTRIES_NO 16
|
| 83 |
|
| 84 |
|
| 85 | struct xilinxfb_platform_data {
|
| 86 | u32 rotate_screen;
|
| 87 | u32 screen_height_mm;
|
| 88 | u32 screen_width_mm;
|
| 89 | u32 xres, yres;
|
| 90 | u32 xvirt, yvirt;
|
| 91 |
|
| 92 | |
| 93 | * will use provided memory address instead of allocating one from
|
| 94 | * the consistent pool.
|
| 95 |
|
| 96 | u32 fb_phys;
|
| 97 | };
|
| 98 |
|
| 99 | |
| 100 | * Default xilinxfb configuration
|
| 101 |
|
| 102 | static const struct xilinxfb_platform_data xilinx_fb_default_pdata = {
|
| 103 | .xres = 640,
|
| 104 | .yres = 480,
|
| 105 | .xvirt = 1024,
|
| 106 | .yvirt = 480,
|
| 107 | };
|
| 108 |
|
| 109 | |
| 110 | * Here are the default fb_fix_screeninfo and fb_var_screeninfo structures
|
| 111 |
|
| 112 | static const struct fb_fix_screeninfo xilinx_fb_fix = {
|
| 113 | .id = "Xilinx",
|
| 114 | .type = FB_TYPE_PACKED_PIXELS,
|
| 115 | .visual = FB_VISUAL_TRUECOLOR,
|
| 116 | .accel = FB_ACCEL_NONE
|
| 117 | };
|
| 118 |
|
| 119 | static const struct fb_var_screeninfo xilinx_fb_var = {
|
| 120 | .bits_per_pixel = BITS_PER_PIXEL,
|
| 121 |
|
| 122 | .red = { RED_SHIFT, 8, 0 },
|
| 123 | .green = { GREEN_SHIFT, 8, 0 },
|
| 124 | .blue = { BLUE_SHIFT, 8, 0 },
|
| 125 | .transp = { 0, 0, 0 },
|
| 126 |
|
| 127 | .activate = FB_ACTIVATE_NOW
|
| 128 | };
|
| 129 |
|
| 130 | #define BUS_ACCESS_FLAG 0x1
|
| 131 | #define LITTLE_ENDIAN_ACCESS 0x2
|
| 132 |
|
| 133 | struct xilinxfb_drvdata {
|
| 134 | struct fb_info info;
|
| 135 |
|
| 136 | phys_addr_t regs_phys; |
| 137 | * registers
|
| 138 |
|
| 139 | void __iomem *regs; |
| 140 | * registers
|
| 141 |
|
| 142 | #ifdef CONFIG_PPC_DCR
|
| 143 | dcr_host_t dcr_host;
|
| 144 | unsigned int dcr_len;
|
| 145 | #endif
|
| 146 | void *fb_virt;
|
| 147 | dma_addr_t fb_phys;
|
| 148 | int fb_alloced;
|
| 149 |
|
| 150 | u8 flags;
|
| 151 |
|
| 152 | u32 reg_ctrl_default;
|
| 153 |
|
| 154 | u32 pseudo_palette[PALETTE_ENTRIES_NO];
|
| 155 |
|
| 156 | };
|
| 157 |
|
| 158 | #define to_xilinxfb_drvdata(_info) \
|
| 159 | container_of(_info, struct xilinxfb_drvdata, info)
|
| 160 |
|
| 161 | |
| 162 | * The XPS TFT Controller can be accessed through BUS or DCR interface.
|
| 163 | * To perform the read/write on the registers we need to check on
|
| 164 | * which bus its connected and call the appropriate write API.
|
| 165 |
|
| 166 | static void xilinx_fb_out32(struct xilinxfb_drvdata *drvdata, u32 offset,
|
| 167 | u32 val)
|
| 168 | {
|
| 169 | if (drvdata->flags & BUS_ACCESS_FLAG) {
|
| 170 | if (drvdata->flags & LITTLE_ENDIAN_ACCESS)
|
| 171 | iowrite32(val, drvdata->regs + (offset << 2));
|
| 172 | else
|
| 173 | iowrite32be(val, drvdata->regs + (offset << 2));
|
| 174 | }
|
| 175 | #ifdef CONFIG_PPC_DCR
|
| 176 | else
|
| 177 | dcr_write(drvdata->dcr_host, offset, val);
|
| 178 | #endif
|
| 179 | }
|
| 180 |
|
| 181 | static u32 xilinx_fb_in32(struct xilinxfb_drvdata *drvdata, u32 offset)
|
| 182 | {
|
| 183 | if (drvdata->flags & BUS_ACCESS_FLAG) {
|
| 184 | if (drvdata->flags & LITTLE_ENDIAN_ACCESS)
|
| 185 | return ioread32(drvdata->regs + (offset << 2));
|
| 186 | else
|
| 187 | return ioread32be(drvdata->regs + (offset << 2));
|
| 188 | }
|
| 189 | #ifdef CONFIG_PPC_DCR
|
| 190 | else
|
| 191 | return dcr_read(drvdata->dcr_host, offset);
|
| 192 | #endif
|
| 193 | return 0;
|
| 194 | }
|
| 195 |
|
| 196 | static int
|
| 197 | xilinx_fb_setcolreg(unsigned int regno, unsigned int red, unsigned int green,
|
| 198 | unsigned int blue, unsigned int transp, struct fb_info *fbi)
|
| 199 | {
|
| 200 | u32 *palette = fbi->pseudo_palette;
|
| 201 |
|
| 202 | if (regno >= PALETTE_ENTRIES_NO)
|
| 203 | return -EINVAL;
|
| 204 |
|
| 205 | if (fbi->var.grayscale) {
|
| 206 | |
| 207 | * grayscale = 0.30*R + 0.59*G + 0.11*B
|
| 208 |
|
| 209 | blue = (red * 77 + green * 151 + blue * 28 + 127) >> 8;
|
| 210 | green = blue;
|
| 211 | red = green;
|
| 212 | }
|
| 213 |
|
| 214 |
|
| 215 |
|
| 216 |
|
| 217 | red >>= 8;
|
| 218 | green >>= 8;
|
| 219 | blue >>= 8;
|
| 220 | palette[regno] = (red << RED_SHIFT) | (green << GREEN_SHIFT) |
|
| 221 | (blue << BLUE_SHIFT);
|
| 222 |
|
| 223 | return 0;
|
| 224 | }
|
| 225 |
|
| 226 | static int
|
| 227 | xilinx_fb_blank(int blank_mode, struct fb_info *fbi)
|
| 228 | {
|
| 229 | struct xilinxfb_drvdata *drvdata = to_xilinxfb_drvdata(fbi);
|
| 230 |
|
| 231 | switch (blank_mode) {
|
| 232 | case FB_BLANK_UNBLANK:
|
| 233 |
|
| 234 | xilinx_fb_out32(drvdata, REG_CTRL, drvdata->reg_ctrl_default);
|
| 235 | break;
|
| 236 |
|
| 237 | case FB_BLANK_NORMAL:
|
| 238 | case FB_BLANK_VSYNC_SUSPEND:
|
| 239 | case FB_BLANK_HSYNC_SUSPEND:
|
| 240 | case FB_BLANK_POWERDOWN:
|
| 241 |
|
| 242 | xilinx_fb_out32(drvdata, REG_CTRL, 0);
|
| 243 | break;
|
| 244 |
|
| 245 | default:
|
| 246 | break;
|
| 247 | }
|
| 248 | return 0;
|
| 249 | }
|
| 250 |
|
| 251 | static const struct fb_ops xilinxfb_ops = {
|
| 252 | .owner = THIS_MODULE,
|
| 253 | FB_DEFAULT_IOMEM_OPS,
|
| 254 | .fb_setcolreg = xilinx_fb_setcolreg,
|
| 255 | .fb_blank = xilinx_fb_blank,
|
| 256 | };
|
| 257 |
|
| 258 | |
| 259 | * Bus independent setup/teardown
|
| 260 |
|
| 261 |
|
| 262 | static int xilinxfb_assign(struct platform_device *pdev,
|
| 263 | struct xilinxfb_drvdata *drvdata,
|
| 264 | struct xilinxfb_platform_data *pdata)
|
| 265 | {
|
| 266 | int rc;
|
| 267 | struct device *dev = &pdev->dev;
|
| 268 | int fbsize = pdata->xvirt * pdata->yvirt * BYTES_PER_PIXEL;
|
| 269 |
|
| 270 | if (drvdata->flags & BUS_ACCESS_FLAG) {
|
| 271 | struct resource *res;
|
| 272 |
|
| 273 | drvdata->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
| 274 | if (IS_ERR(drvdata->regs))
|
| 275 | return PTR_ERR(drvdata->regs);
|
| 276 |
|
| 277 | drvdata->regs_phys = res->start;
|
| 278 | }
|
| 279 |
|
| 280 |
|
| 281 | if (pdata->fb_phys) {
|
| 282 | drvdata->fb_phys = pdata->fb_phys;
|
| 283 | drvdata->fb_virt = ioremap(pdata->fb_phys, fbsize);
|
| 284 | } else {
|
| 285 | drvdata->fb_alloced = 1;
|
| 286 | drvdata->fb_virt = dma_alloc_coherent(dev, PAGE_ALIGN(fbsize),
|
| 287 | &drvdata->fb_phys,
|
| 288 | GFP_KERNEL);
|
| 289 | }
|
| 290 |
|
| 291 | if (!drvdata->fb_virt) {
|
| 292 | dev_err(dev, "Could not allocate frame buffer memory\n");
|
| 293 | return -ENOMEM;
|
| 294 | }
|
| 295 |
|
| 296 |
|
| 297 | memset_io((void __iomem *)drvdata->fb_virt, 0, fbsize);
|
| 298 |
|
| 299 |
|
| 300 | xilinx_fb_out32(drvdata, REG_FB_ADDR, drvdata->fb_phys);
|
| 301 | rc = xilinx_fb_in32(drvdata, REG_FB_ADDR);
|
| 302 |
|
| 303 | if (rc != drvdata->fb_phys) {
|
| 304 | drvdata->flags |= LITTLE_ENDIAN_ACCESS;
|
| 305 | xilinx_fb_out32(drvdata, REG_FB_ADDR, drvdata->fb_phys);
|
| 306 | }
|
| 307 |
|
| 308 |
|
| 309 | drvdata->reg_ctrl_default = REG_CTRL_ENABLE;
|
| 310 | if (pdata->rotate_screen)
|
| 311 | drvdata->reg_ctrl_default |= REG_CTRL_ROTATE;
|
| 312 | xilinx_fb_out32(drvdata, REG_CTRL, drvdata->reg_ctrl_default);
|
| 313 |
|
| 314 |
|
| 315 | drvdata->info.device = dev;
|
| 316 | drvdata->info.screen_base = (void __iomem *)drvdata->fb_virt;
|
| 317 | drvdata->info.fbops = &xilinxfb_ops;
|
| 318 | drvdata->info.fix = xilinx_fb_fix;
|
| 319 | drvdata->info.fix.smem_start = drvdata->fb_phys;
|
| 320 | drvdata->info.fix.smem_len = fbsize;
|
| 321 | drvdata->info.fix.line_length = pdata->xvirt * BYTES_PER_PIXEL;
|
| 322 |
|
| 323 | drvdata->info.pseudo_palette = drvdata->pseudo_palette;
|
| 324 | drvdata->info.var = xilinx_fb_var;
|
| 325 | drvdata->info.var.height = pdata->screen_height_mm;
|
| 326 | drvdata->info.var.width = pdata->screen_width_mm;
|
| 327 | drvdata->info.var.xres = pdata->xres;
|
| 328 | drvdata->info.var.yres = pdata->yres;
|
| 329 | drvdata->info.var.xres_virtual = pdata->xvirt;
|
| 330 | drvdata->info.var.yres_virtual = pdata->yvirt;
|
| 331 |
|
| 332 |
|
| 333 | rc = fb_alloc_cmap(&drvdata->info.cmap, PALETTE_ENTRIES_NO, 0);
|
| 334 | if (rc) {
|
| 335 | dev_err(dev, "Fail to allocate colormap (%d entries)\n",
|
| 336 | PALETTE_ENTRIES_NO);
|
| 337 | goto err_cmap;
|
| 338 | }
|
| 339 |
|
| 340 |
|
| 341 | rc = register_framebuffer(&drvdata->info);
|
| 342 | if (rc) {
|
| 343 | dev_err(dev, "Could not register frame buffer\n");
|
| 344 | goto err_regfb;
|
| 345 | }
|
| 346 |
|
| 347 | if (drvdata->flags & BUS_ACCESS_FLAG) {
|
| 348 |
|
| 349 | dev_dbg(dev, "regs: phys=%pa, virt=%p\n",
|
| 350 | &drvdata->regs_phys, drvdata->regs);
|
| 351 | }
|
| 352 |
|
| 353 | dev_dbg(dev, "fb: phys=%llx, virt=%p, size=%x\n",
|
| 354 | (unsigned long long)drvdata->fb_phys, drvdata->fb_virt, fbsize);
|
| 355 |
|
| 356 | return 0;
|
| 357 |
|
| 358 | err_regfb:
|
| 359 | fb_dealloc_cmap(&drvdata->info.cmap);
|
| 360 |
|
| 361 | err_cmap:
|
| 362 | if (drvdata->fb_alloced)
|
| 363 | dma_free_coherent(dev, PAGE_ALIGN(fbsize), drvdata->fb_virt,
|
| 364 | drvdata->fb_phys);
|
| 365 | else
|
| 366 | iounmap(drvdata->fb_virt);
|
| 367 |
|
| 368 |
|
| 369 | xilinx_fb_out32(drvdata, REG_CTRL, 0);
|
| 370 |
|
| 371 | return rc;
|
| 372 | }
|
| 373 |
|
| 374 | static void xilinxfb_release(struct device *dev)
|
| 375 | {
|
| 376 | struct xilinxfb_drvdata *drvdata = dev_get_drvdata(dev);
|
| 377 |
|
| 378 | #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)
|
| 379 | xilinx_fb_blank(VESA_POWERDOWN, &drvdata->info);
|
| 380 | #endif
|
| 381 |
|
| 382 | unregister_framebuffer(&drvdata->info);
|
| 383 |
|
| 384 | fb_dealloc_cmap(&drvdata->info.cmap);
|
| 385 |
|
| 386 | if (drvdata->fb_alloced)
|
| 387 | dma_free_coherent(dev, PAGE_ALIGN(drvdata->info.fix.smem_len),
|
| 388 | drvdata->fb_virt, drvdata->fb_phys);
|
| 389 | else
|
| 390 | iounmap(drvdata->fb_virt);
|
| 391 |
|
| 392 |
|
| 393 | xilinx_fb_out32(drvdata, REG_CTRL, 0);
|
| 394 |
|
| 395 | #ifdef CONFIG_PPC_DCR
|
| 396 |
|
| 397 | if (!(drvdata->flags & BUS_ACCESS_FLAG))
|
| 398 | dcr_unmap(drvdata->dcr_host, drvdata->dcr_len);
|
| 399 | #endif
|
| 400 | }
|
| 401 |
|
| 402 | |
| 403 | * OF bus binding
|
| 404 |
|
| 405 |
|
| 406 | static int xilinxfb_of_probe(struct platform_device *pdev)
|
| 407 | {
|
| 408 | const u32 *prop;
|
| 409 | u32 tft_access = 0;
|
| 410 | struct xilinxfb_platform_data pdata;
|
| 411 | int size;
|
| 412 | struct xilinxfb_drvdata *drvdata;
|
| 413 |
|
| 414 |
|
| 415 | pdata = xilinx_fb_default_pdata;
|
| 416 |
|
| 417 |
|
| 418 | drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
|
| 419 | if (!drvdata)
|
| 420 | return -ENOMEM;
|
| 421 |
|
| 422 | |
| 423 | * To check whether the core is connected directly to DCR or BUS
|
| 424 | * interface and initialize the tft_access accordingly.
|
| 425 |
|
| 426 | of_property_read_u32(pdev->dev.of_node, "xlnx,dcr-splb-slave-if",
|
| 427 | &tft_access);
|
| 428 |
|
| 429 | |
| 430 | * Fill the resource structure if its direct BUS interface
|
| 431 | * otherwise fill the dcr_host structure.
|
| 432 |
|
| 433 | if (tft_access)
|
| 434 | drvdata->flags |= BUS_ACCESS_FLAG;
|
| 435 | #ifdef CONFIG_PPC_DCR
|
| 436 | else {
|
| 437 | int start;
|
| 438 |
|
| 439 | start = dcr_resource_start(pdev->dev.of_node, 0);
|
| 440 | drvdata->dcr_len = dcr_resource_len(pdev->dev.of_node, 0);
|
| 441 | drvdata->dcr_host = dcr_map(pdev->dev.of_node, start, drvdata->dcr_len);
|
| 442 | if (!DCR_MAP_OK(drvdata->dcr_host)) {
|
| 443 | dev_err(&pdev->dev, "invalid DCR address\n");
|
| 444 | return -ENODEV;
|
| 445 | }
|
| 446 | }
|
| 447 | #endif
|
| 448 |
|
| 449 | prop = of_get_property(pdev->dev.of_node, "phys-size", &size);
|
| 450 | if ((prop) && (size >= sizeof(u32) * 2)) {
|
| 451 | pdata.screen_width_mm = prop[0];
|
| 452 | pdata.screen_height_mm = prop[1];
|
| 453 | }
|
| 454 |
|
| 455 | prop = of_get_property(pdev->dev.of_node, "resolution", &size);
|
| 456 | if ((prop) && (size >= sizeof(u32) * 2)) {
|
| 457 | pdata.xres = prop[0];
|
| 458 | pdata.yres = prop[1];
|
| 459 | }
|
| 460 |
|
| 461 | prop = of_get_property(pdev->dev.of_node, "virtual-resolution", &size);
|
| 462 | if ((prop) && (size >= sizeof(u32) * 2)) {
|
| 463 | pdata.xvirt = prop[0];
|
| 464 | pdata.yvirt = prop[1];
|
| 465 | }
|
| 466 |
|
| 467 | pdata.rotate_screen = of_property_read_bool(pdev->dev.of_node, "rotate-display");
|
| 468 |
|
| 469 | platform_set_drvdata(pdev, drvdata);
|
| 470 | return xilinxfb_assign(pdev, drvdata, &pdata);
|
| 471 | }
|
| 472 |
|
| 473 | static void xilinxfb_of_remove(struct platform_device *op)
|
| 474 | {
|
| 475 | xilinxfb_release(&op->dev);
|
| 476 | }
|
| 477 |
|
| 478 |
|
| 479 | static const struct of_device_id xilinxfb_of_match[] = {
|
| 480 | { .compatible = "xlnx,xps-tft-1.00.a", },
|
| 481 | { .compatible = "xlnx,xps-tft-2.00.a", },
|
| 482 | { .compatible = "xlnx,xps-tft-2.01.a", },
|
| 483 | { .compatible = "xlnx,plb-tft-cntlr-ref-1.00.a", },
|
| 484 | { .compatible = "xlnx,plb-dvi-cntlr-ref-1.00.c", },
|
| 485 | {},
|
| 486 | };
|
| 487 | MODULE_DEVICE_TABLE(of, xilinxfb_of_match);
|
| 488 |
|
| 489 | static struct platform_driver xilinxfb_of_driver = {
|
| 490 | .probe = xilinxfb_of_probe,
|
| 491 | .remove = xilinxfb_of_remove,
|
| 492 | .driver = {
|
| 493 | .name = DRIVER_NAME,
|
| 494 | .of_match_table = xilinxfb_of_match,
|
| 495 | },
|
| 496 | };
|
| 497 |
|
| 498 | module_platform_driver(xilinxfb_of_driver);
|
| 499 |
|
| 500 | MODULE_AUTHOR("MontaVista Software, Inc. <[email protected]>");
|
| 501 | MODULE_DESCRIPTION("Xilinx TFT frame buffer driver");
|
| 502 | MODULE_LICENSE("GPL");
|