| 1 | |
| 2 | |
| 3 | * Copyright (C) 2007 Google, Inc.
|
| 4 | * Copyright (C) 2012 Intel, Inc.
|
| 5 |
|
| 6 |
|
| 7 | #include <linux/module.h>
|
| 8 | #include <linux/kernel.h>
|
| 9 | #include <linux/dma-mapping.h>
|
| 10 | #include <linux/errno.h>
|
| 11 | #include <linux/string.h>
|
| 12 | #include <linux/slab.h>
|
| 13 | #include <linux/delay.h>
|
| 14 | #include <linux/mm.h>
|
| 15 | #include <linux/fb.h>
|
| 16 | #include <linux/init.h>
|
| 17 | #include <linux/interrupt.h>
|
| 18 | #include <linux/ioport.h>
|
| 19 | #include <linux/platform_device.h>
|
| 20 | #include <linux/acpi.h>
|
| 21 |
|
| 22 | enum {
|
| 23 | FB_GET_WIDTH = 0x00,
|
| 24 | FB_GET_HEIGHT = 0x04,
|
| 25 | FB_INT_STATUS = 0x08,
|
| 26 | FB_INT_ENABLE = 0x0c,
|
| 27 | FB_SET_BASE = 0x10,
|
| 28 | FB_SET_ROTATION = 0x14,
|
| 29 | FB_SET_BLANK = 0x18,
|
| 30 | FB_GET_PHYS_WIDTH = 0x1c,
|
| 31 | FB_GET_PHYS_HEIGHT = 0x20,
|
| 32 |
|
| 33 | FB_INT_VSYNC = 1U << 0,
|
| 34 | FB_INT_BASE_UPDATE_DONE = 1U << 1
|
| 35 | };
|
| 36 |
|
| 37 | struct goldfish_fb {
|
| 38 | void __iomem *reg_base;
|
| 39 | int irq;
|
| 40 | spinlock_t lock;
|
| 41 | wait_queue_head_t wait;
|
| 42 | int base_update_count;
|
| 43 | int rotation;
|
| 44 | struct fb_info fb;
|
| 45 | u32 cmap[16];
|
| 46 | };
|
| 47 |
|
| 48 | static irqreturn_t goldfish_fb_interrupt(int irq, void *dev_id)
|
| 49 | {
|
| 50 | unsigned long irq_flags;
|
| 51 | struct goldfish_fb *fb = dev_id;
|
| 52 | u32 status;
|
| 53 |
|
| 54 | spin_lock_irqsave(&fb->lock, irq_flags);
|
| 55 | status = readl(fb->reg_base + FB_INT_STATUS);
|
| 56 | if (status & FB_INT_BASE_UPDATE_DONE) {
|
| 57 | fb->base_update_count++;
|
| 58 | wake_up(&fb->wait);
|
| 59 | }
|
| 60 | spin_unlock_irqrestore(&fb->lock, irq_flags);
|
| 61 | return status ? IRQ_HANDLED : IRQ_NONE;
|
| 62 | }
|
| 63 |
|
| 64 | static inline u32 convert_bitfield(int val, struct fb_bitfield *bf)
|
| 65 | {
|
| 66 | unsigned int mask = (1 << bf->length) - 1;
|
| 67 |
|
| 68 | return (val >> (16 - bf->length) & mask) << bf->offset;
|
| 69 | }
|
| 70 |
|
| 71 | static int
|
| 72 | goldfish_fb_setcolreg(unsigned int regno, unsigned int red, unsigned int green,
|
| 73 | unsigned int blue, unsigned int transp, struct fb_info *info)
|
| 74 | {
|
| 75 | struct goldfish_fb *fb = container_of(info, struct goldfish_fb, fb);
|
| 76 |
|
| 77 | if (regno < 16) {
|
| 78 | fb->cmap[regno] = convert_bitfield(transp, &fb->fb.var.transp) |
|
| 79 | convert_bitfield(blue, &fb->fb.var.blue) |
|
| 80 | convert_bitfield(green, &fb->fb.var.green) |
|
| 81 | convert_bitfield(red, &fb->fb.var.red);
|
| 82 | return 0;
|
| 83 | } else {
|
| 84 | return 1;
|
| 85 | }
|
| 86 | }
|
| 87 |
|
| 88 | static int goldfish_fb_check_var(struct fb_var_screeninfo *var,
|
| 89 | struct fb_info *info)
|
| 90 | {
|
| 91 | if ((var->rotate & 1) != (info->var.rotate & 1)) {
|
| 92 | if ((var->xres != info->var.yres) ||
|
| 93 | (var->yres != info->var.xres) ||
|
| 94 | (var->xres_virtual != info->var.yres) ||
|
| 95 | (var->yres_virtual > info->var.xres * 2) ||
|
| 96 | (var->yres_virtual < info->var.xres)) {
|
| 97 | return -EINVAL;
|
| 98 | }
|
| 99 | } else {
|
| 100 | if ((var->xres != info->var.xres) ||
|
| 101 | (var->yres != info->var.yres) ||
|
| 102 | (var->xres_virtual != info->var.xres) ||
|
| 103 | (var->yres_virtual > info->var.yres * 2) ||
|
| 104 | (var->yres_virtual < info->var.yres)) {
|
| 105 | return -EINVAL;
|
| 106 | }
|
| 107 | }
|
| 108 | if ((var->xoffset != info->var.xoffset) ||
|
| 109 | (var->bits_per_pixel != info->var.bits_per_pixel) ||
|
| 110 | (var->grayscale != info->var.grayscale)) {
|
| 111 | return -EINVAL;
|
| 112 | }
|
| 113 | return 0;
|
| 114 | }
|
| 115 |
|
| 116 | static int goldfish_fb_set_par(struct fb_info *info)
|
| 117 | {
|
| 118 | struct goldfish_fb *fb = container_of(info, struct goldfish_fb, fb);
|
| 119 |
|
| 120 | if (fb->rotation != fb->fb.var.rotate) {
|
| 121 | info->fix.line_length = info->var.xres * 2;
|
| 122 | fb->rotation = fb->fb.var.rotate;
|
| 123 | writel(fb->rotation, fb->reg_base + FB_SET_ROTATION);
|
| 124 | }
|
| 125 | return 0;
|
| 126 | }
|
| 127 |
|
| 128 |
|
| 129 | static int goldfish_fb_pan_display(struct fb_var_screeninfo *var,
|
| 130 | struct fb_info *info)
|
| 131 | {
|
| 132 | unsigned long irq_flags;
|
| 133 | int base_update_count;
|
| 134 | struct goldfish_fb *fb = container_of(info, struct goldfish_fb, fb);
|
| 135 |
|
| 136 | spin_lock_irqsave(&fb->lock, irq_flags);
|
| 137 | base_update_count = fb->base_update_count;
|
| 138 | writel(fb->fb.fix.smem_start + fb->fb.var.xres * 2 * var->yoffset,
|
| 139 | fb->reg_base + FB_SET_BASE);
|
| 140 | spin_unlock_irqrestore(&fb->lock, irq_flags);
|
| 141 | if (!wait_event_timeout(fb->wait,
|
| 142 | fb->base_update_count != base_update_count,
|
| 143 | HZ / 15)) {
|
| 144 | pr_err("%s: timeout waiting for base update\n", __func__);
|
| 145 | return -ETIMEDOUT;
|
| 146 | }
|
| 147 | return 0;
|
| 148 | }
|
| 149 |
|
| 150 | static int goldfish_fb_blank(int blank, struct fb_info *info)
|
| 151 | {
|
| 152 | struct goldfish_fb *fb = container_of(info, struct goldfish_fb, fb);
|
| 153 |
|
| 154 | switch (blank) {
|
| 155 | case FB_BLANK_NORMAL:
|
| 156 | writel(1, fb->reg_base + FB_SET_BLANK);
|
| 157 | break;
|
| 158 | case FB_BLANK_UNBLANK:
|
| 159 | writel(0, fb->reg_base + FB_SET_BLANK);
|
| 160 | break;
|
| 161 | }
|
| 162 | return 0;
|
| 163 | }
|
| 164 |
|
| 165 | static const struct fb_ops goldfish_fb_ops = {
|
| 166 | .owner = THIS_MODULE,
|
| 167 | FB_DEFAULT_IOMEM_OPS,
|
| 168 | .fb_check_var = goldfish_fb_check_var,
|
| 169 | .fb_set_par = goldfish_fb_set_par,
|
| 170 | .fb_setcolreg = goldfish_fb_setcolreg,
|
| 171 | .fb_pan_display = goldfish_fb_pan_display,
|
| 172 | .fb_blank = goldfish_fb_blank,
|
| 173 | };
|
| 174 |
|
| 175 |
|
| 176 | static int goldfish_fb_probe(struct platform_device *pdev)
|
| 177 | {
|
| 178 | int ret;
|
| 179 | struct goldfish_fb *fb;
|
| 180 | size_t framesize;
|
| 181 | u32 width, height;
|
| 182 | dma_addr_t fbpaddr;
|
| 183 |
|
| 184 | fb = kzalloc_obj(*fb);
|
| 185 | if (fb == NULL) {
|
| 186 | ret = -ENOMEM;
|
| 187 | goto err_fb_alloc_failed;
|
| 188 | }
|
| 189 | spin_lock_init(&fb->lock);
|
| 190 | init_waitqueue_head(&fb->wait);
|
| 191 | platform_set_drvdata(pdev, fb);
|
| 192 |
|
| 193 | fb->reg_base = devm_platform_ioremap_resource(pdev, 0);
|
| 194 | if (IS_ERR(fb->reg_base)) {
|
| 195 | ret = PTR_ERR(fb->reg_base);
|
| 196 | goto err_no_io_base;
|
| 197 | }
|
| 198 |
|
| 199 | fb->irq = platform_get_irq(pdev, 0);
|
| 200 | if (fb->irq < 0) {
|
| 201 | ret = fb->irq;
|
| 202 | goto err_no_irq;
|
| 203 | }
|
| 204 |
|
| 205 | width = readl(fb->reg_base + FB_GET_WIDTH);
|
| 206 | height = readl(fb->reg_base + FB_GET_HEIGHT);
|
| 207 |
|
| 208 | fb->fb.fbops = &goldfish_fb_ops;
|
| 209 | fb->fb.pseudo_palette = fb->cmap;
|
| 210 | fb->fb.fix.type = FB_TYPE_PACKED_PIXELS;
|
| 211 | fb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
|
| 212 | fb->fb.fix.line_length = width * 2;
|
| 213 | fb->fb.fix.accel = FB_ACCEL_NONE;
|
| 214 | fb->fb.fix.ypanstep = 1;
|
| 215 |
|
| 216 | fb->fb.var.xres = width;
|
| 217 | fb->fb.var.yres = height;
|
| 218 | fb->fb.var.xres_virtual = width;
|
| 219 | fb->fb.var.yres_virtual = height * 2;
|
| 220 | fb->fb.var.bits_per_pixel = 16;
|
| 221 | fb->fb.var.activate = FB_ACTIVATE_NOW;
|
| 222 | fb->fb.var.height = readl(fb->reg_base + FB_GET_PHYS_HEIGHT);
|
| 223 | fb->fb.var.width = readl(fb->reg_base + FB_GET_PHYS_WIDTH);
|
| 224 | fb->fb.var.pixclock = 0;
|
| 225 |
|
| 226 | fb->fb.var.red.offset = 11;
|
| 227 | fb->fb.var.red.length = 5;
|
| 228 | fb->fb.var.green.offset = 5;
|
| 229 | fb->fb.var.green.length = 6;
|
| 230 | fb->fb.var.blue.offset = 0;
|
| 231 | fb->fb.var.blue.length = 5;
|
| 232 |
|
| 233 | framesize = width * height * 2 * 2;
|
| 234 | fb->fb.screen_base = (char __force __iomem *)dma_alloc_coherent(
|
| 235 | &pdev->dev, framesize,
|
| 236 | &fbpaddr, GFP_KERNEL);
|
| 237 | pr_debug("allocating frame buffer %d * %d, got %p\n",
|
| 238 | width, height, fb->fb.screen_base);
|
| 239 | if (fb->fb.screen_base == NULL) {
|
| 240 | ret = -ENOMEM;
|
| 241 | goto err_alloc_screen_base_failed;
|
| 242 | }
|
| 243 | fb->fb.fix.smem_start = fbpaddr;
|
| 244 | fb->fb.fix.smem_len = framesize;
|
| 245 |
|
| 246 | ret = fb_set_var(&fb->fb, &fb->fb.var);
|
| 247 | if (ret)
|
| 248 | goto err_fb_set_var_failed;
|
| 249 |
|
| 250 | ret = request_irq(fb->irq, goldfish_fb_interrupt, IRQF_SHARED,
|
| 251 | pdev->name, fb);
|
| 252 | if (ret)
|
| 253 | goto err_request_irq_failed;
|
| 254 |
|
| 255 | writel(FB_INT_BASE_UPDATE_DONE, fb->reg_base + FB_INT_ENABLE);
|
| 256 | ret = goldfish_fb_pan_display(&fb->fb.var, &fb->fb);
|
| 257 | if (ret)
|
| 258 | goto err_pan_display_failed;
|
| 259 |
|
| 260 | ret = register_framebuffer(&fb->fb);
|
| 261 | if (ret)
|
| 262 | goto err_register_framebuffer_failed;
|
| 263 | return 0;
|
| 264 |
|
| 265 | err_register_framebuffer_failed:
|
| 266 | err_pan_display_failed:
|
| 267 | free_irq(fb->irq, fb);
|
| 268 | err_request_irq_failed:
|
| 269 | err_fb_set_var_failed:
|
| 270 | dma_free_coherent(&pdev->dev, framesize,
|
| 271 | (void *)fb->fb.screen_base,
|
| 272 | fb->fb.fix.smem_start);
|
| 273 | err_alloc_screen_base_failed:
|
| 274 | err_no_irq:
|
| 275 | err_no_io_base:
|
| 276 | kfree(fb);
|
| 277 | err_fb_alloc_failed:
|
| 278 | return ret;
|
| 279 | }
|
| 280 |
|
| 281 | static void goldfish_fb_remove(struct platform_device *pdev)
|
| 282 | {
|
| 283 | size_t framesize;
|
| 284 | struct goldfish_fb *fb = platform_get_drvdata(pdev);
|
| 285 |
|
| 286 | framesize = fb->fb.var.xres_virtual * fb->fb.var.yres_virtual * 2;
|
| 287 | unregister_framebuffer(&fb->fb);
|
| 288 | free_irq(fb->irq, fb);
|
| 289 |
|
| 290 | dma_free_coherent(&pdev->dev, framesize, (void *)fb->fb.screen_base,
|
| 291 | fb->fb.fix.smem_start);
|
| 292 | kfree(fb);
|
| 293 | }
|
| 294 |
|
| 295 | static const struct of_device_id goldfish_fb_of_match[] = {
|
| 296 | { .compatible = "google,goldfish-fb", },
|
| 297 | {},
|
| 298 | };
|
| 299 | MODULE_DEVICE_TABLE(of, goldfish_fb_of_match);
|
| 300 |
|
| 301 | #ifdef CONFIG_ACPI
|
| 302 | static const struct acpi_device_id goldfish_fb_acpi_match[] = {
|
| 303 | { "GFSH0004", 0 },
|
| 304 | { },
|
| 305 | };
|
| 306 | MODULE_DEVICE_TABLE(acpi, goldfish_fb_acpi_match);
|
| 307 | #endif
|
| 308 |
|
| 309 | static struct platform_driver goldfish_fb_driver = {
|
| 310 | .probe = goldfish_fb_probe,
|
| 311 | .remove = goldfish_fb_remove,
|
| 312 | .driver = {
|
| 313 | .name = "goldfish_fb",
|
| 314 | .of_match_table = goldfish_fb_of_match,
|
| 315 | .acpi_match_table = ACPI_PTR(goldfish_fb_acpi_match),
|
| 316 | }
|
| 317 | };
|
| 318 |
|
| 319 | module_platform_driver(goldfish_fb_driver);
|
| 320 |
|
| 321 | MODULE_DESCRIPTION("Goldfish Virtual Platform Framebuffer driver");
|
| 322 | MODULE_LICENSE("GPL v2");
|