Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / video / fbdev / arcfb.c
diff --git a/kernel/drivers/video/fbdev/arcfb.c b/kernel/drivers/video/fbdev/arcfb.c
new file mode 100644 (file)
index 0000000..1b0b233
--- /dev/null
@@ -0,0 +1,667 @@
+/*
+ * linux/drivers/video/arcfb.c -- FB driver for Arc monochrome LCD board
+ *
+ * Copyright (C) 2005, Jaya Kumar <jayalk@intworks.biz>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven.
+ *
+ * This driver was written to be used with the Arc LCD board. Arc uses a
+ * set of KS108 chips that control individual 64x64 LCD matrices. The board
+ * can be paneled in a variety of setups such as 2x1=128x64, 4x4=256x256 and
+ * so on. The interface between the board and the host is TTL based GPIO. The
+ * GPIO requirements are 8 writable data lines and 4+n lines for control. On a
+ * GPIO-less system, the board can be tested by connecting the respective sigs
+ * up to a parallel port connector. The driver requires the IO addresses for
+ * data and control GPIO at load time. It is unable to probe for the
+ * existence of the LCD so it must be told at load time whether it should
+ * be enabled or not.
+ *
+ * Todo:
+ * - testing with 4x4
+ * - testing with interrupt hw
+ *
+ * General notes:
+ * - User must set tuhold. It's in microseconds. According to the 108 spec,
+ *   the hold time is supposed to be at least 1 microsecond.
+ * - User must set num_cols=x num_rows=y, eg: x=2 means 128
+ * - User must set arcfb_enable=1 to enable it
+ * - User must set dio_addr=0xIOADDR cio_addr=0xIOADDR
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/arcfb.h>
+#include <linux/platform_device.h>
+
+#include <linux/uaccess.h>
+
+#define floor8(a) (a&(~0x07))
+#define floorXres(a,xres) (a&(~(xres - 1)))
+#define iceil8(a) (((int)((a+7)/8))*8)
+#define ceil64(a) (a|0x3F)
+#define ceilXres(a,xres) (a|(xres - 1))
+
+/* ks108 chipset specific defines and code */
+
+#define KS_SET_DPY_START_LINE  0xC0
+#define KS_SET_PAGE_NUM        0xB8
+#define KS_SET_X               0x40
+#define KS_CEHI                0x01
+#define KS_CELO                0x00
+#define KS_SEL_CMD             0x08
+#define KS_SEL_DATA            0x00
+#define KS_DPY_ON              0x3F
+#define KS_DPY_OFF             0x3E
+#define KS_INTACK              0x40
+#define KS_CLRINT              0x02
+
+struct arcfb_par {
+       unsigned long dio_addr;
+       unsigned long cio_addr;
+       unsigned long c2io_addr;
+       atomic_t ref_count;
+       unsigned char cslut[9];
+       struct fb_info *info;
+       unsigned int irq;
+       spinlock_t lock;
+};
+
+static struct fb_fix_screeninfo arcfb_fix = {
+       .id =           "arcfb",
+       .type =         FB_TYPE_PACKED_PIXELS,
+       .visual =       FB_VISUAL_MONO01,
+       .xpanstep =     0,
+       .ypanstep =     1,
+       .ywrapstep =    0,
+       .accel =        FB_ACCEL_NONE,
+};
+
+static struct fb_var_screeninfo arcfb_var = {
+       .xres           = 128,
+       .yres           = 64,
+       .xres_virtual   = 128,
+       .yres_virtual   = 64,
+       .bits_per_pixel = 1,
+       .nonstd         = 1,
+};
+
+static unsigned long num_cols;
+static unsigned long num_rows;
+static unsigned long dio_addr;
+static unsigned long cio_addr;
+static unsigned long c2io_addr;
+static unsigned long splashval;
+static unsigned long tuhold;
+static unsigned int nosplash;
+static unsigned int arcfb_enable;
+static unsigned int irq;
+
+static DECLARE_WAIT_QUEUE_HEAD(arcfb_waitq);
+
+static void ks108_writeb_ctl(struct arcfb_par *par,
+                               unsigned int chipindex, unsigned char value)
+{
+       unsigned char chipselval = par->cslut[chipindex];
+
+       outb(chipselval|KS_CEHI|KS_SEL_CMD, par->cio_addr);
+       outb(value, par->dio_addr);
+       udelay(tuhold);
+       outb(chipselval|KS_CELO|KS_SEL_CMD, par->cio_addr);
+}
+
+static void ks108_writeb_mainctl(struct arcfb_par *par, unsigned char value)
+{
+
+       outb(value, par->cio_addr);
+       udelay(tuhold);
+}
+
+static unsigned char ks108_readb_ctl2(struct arcfb_par *par)
+{
+       return inb(par->c2io_addr);
+}
+
+static void ks108_writeb_data(struct arcfb_par *par,
+                               unsigned int chipindex, unsigned char value)
+{
+       unsigned char chipselval = par->cslut[chipindex];
+
+       outb(chipselval|KS_CEHI|KS_SEL_DATA, par->cio_addr);
+       outb(value, par->dio_addr);
+       udelay(tuhold);
+       outb(chipselval|KS_CELO|KS_SEL_DATA, par->cio_addr);
+}
+
+static void ks108_set_start_line(struct arcfb_par *par,
+                               unsigned int chipindex, unsigned char y)
+{
+       ks108_writeb_ctl(par, chipindex, KS_SET_DPY_START_LINE|y);
+}
+
+static void ks108_set_yaddr(struct arcfb_par *par,
+                               unsigned int chipindex, unsigned char y)
+{
+       ks108_writeb_ctl(par, chipindex, KS_SET_PAGE_NUM|y);
+}
+
+static void ks108_set_xaddr(struct arcfb_par *par,
+                               unsigned int chipindex, unsigned char x)
+{
+       ks108_writeb_ctl(par, chipindex, KS_SET_X|x);
+}
+
+static void ks108_clear_lcd(struct arcfb_par *par, unsigned int chipindex)
+{
+       int i,j;
+
+       for (i = 0; i <= 8; i++) {
+               ks108_set_yaddr(par, chipindex, i);
+               ks108_set_xaddr(par, chipindex, 0);
+               for (j = 0; j < 64; j++) {
+                       ks108_writeb_data(par, chipindex,
+                               (unsigned char) splashval);
+               }
+       }
+}
+
+/* main arcfb functions */
+
+static int arcfb_open(struct fb_info *info, int user)
+{
+       struct arcfb_par *par = info->par;
+
+       atomic_inc(&par->ref_count);
+       return 0;
+}
+
+static int arcfb_release(struct fb_info *info, int user)
+{
+       struct arcfb_par *par = info->par;
+       int count = atomic_read(&par->ref_count);
+
+       if (!count)
+               return -EINVAL;
+       atomic_dec(&par->ref_count);
+       return 0;
+}
+
+static int arcfb_pan_display(struct fb_var_screeninfo *var,
+                               struct fb_info *info)
+{
+       int i;
+       struct arcfb_par *par = info->par;
+
+       if ((var->vmode & FB_VMODE_YWRAP) && (var->yoffset < 64)
+               && (info->var.yres <= 64)) {
+               for (i = 0; i < num_cols; i++) {
+                       ks108_set_start_line(par, i, var->yoffset);
+               }
+               info->var.yoffset = var->yoffset;
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static irqreturn_t arcfb_interrupt(int vec, void *dev_instance)
+{
+       struct fb_info *info = dev_instance;
+       unsigned char ctl2status;
+       struct arcfb_par *par = info->par;
+
+       ctl2status = ks108_readb_ctl2(par);
+
+       if (!(ctl2status & KS_INTACK)) /* not arc generated interrupt */
+               return IRQ_NONE;
+
+       ks108_writeb_mainctl(par, KS_CLRINT);
+
+       spin_lock(&par->lock);
+        if (waitqueue_active(&arcfb_waitq)) {
+                wake_up(&arcfb_waitq);
+        }
+       spin_unlock(&par->lock);
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * here we handle a specific page on the lcd. the complexity comes from
+ * the fact that the fb is laidout in 8xX vertical columns. we extract
+ * each write of 8 vertical pixels. then we shift out as we move along
+ * X. That's what rightshift does. bitmask selects the desired input bit.
+ */
+static void arcfb_lcd_update_page(struct arcfb_par *par, unsigned int upper,
+               unsigned int left, unsigned int right, unsigned int distance)
+{
+       unsigned char *src;
+       unsigned int xindex, yindex, chipindex, linesize;
+       int i;
+       unsigned char val;
+       unsigned char bitmask, rightshift;
+
+       xindex = left >> 6;
+       yindex = upper >> 6;
+       chipindex = (xindex + (yindex*num_cols));
+
+       ks108_set_yaddr(par, chipindex, upper/8);
+
+       linesize = par->info->var.xres/8;
+       src = (unsigned char __force *) par->info->screen_base + (left/8) +
+               (upper * linesize);
+       ks108_set_xaddr(par, chipindex, left);
+
+       bitmask=1;
+       rightshift=0;
+       while (left <= right) {
+               val = 0;
+               for (i = 0; i < 8; i++) {
+                       if ( i > rightshift) {
+                               val |= (*(src + (i*linesize)) & bitmask)
+                                               << (i - rightshift);
+                       } else {
+                               val |= (*(src + (i*linesize)) & bitmask)
+                                                >> (rightshift - i);
+                       }
+               }
+               ks108_writeb_data(par, chipindex, val);
+               left++;
+               if (bitmask == 0x80) {
+                       bitmask = 1;
+                       src++;
+                       rightshift=0;
+               } else {
+                       bitmask <<= 1;
+                       rightshift++;
+               }
+       }
+}
+
+/*
+ * here we handle the entire vertical page of the update. we write across
+ * lcd chips. update_page uses the upper/left values to decide which
+ * chip to select for the right. upper is needed for setting the page
+ * desired for the write.
+ */
+static void arcfb_lcd_update_vert(struct arcfb_par *par, unsigned int top,
+               unsigned int bottom, unsigned int left, unsigned int right)
+{
+       unsigned int distance, upper, lower;
+
+       distance = (bottom - top) + 1;
+       upper = top;
+       lower = top + 7;
+
+       while (distance > 0) {
+               distance -= 8;
+               arcfb_lcd_update_page(par, upper, left, right, 8);
+               upper = lower + 1;
+               lower = upper + 7;
+       }
+}
+
+/*
+ * here we handle horizontal blocks for the update. update_vert will
+ * handle spaning multiple pages. we break out each horizontal
+ * block in to individual blocks no taller than 64 pixels.
+ */
+static void arcfb_lcd_update_horiz(struct arcfb_par *par, unsigned int left,
+                       unsigned int right, unsigned int top, unsigned int h)
+{
+       unsigned int distance, upper, lower;
+
+       distance = h;
+       upper = floor8(top);
+       lower = min(upper + distance - 1, ceil64(upper));
+
+       while (distance > 0) {
+               distance -= ((lower - upper) + 1 );
+               arcfb_lcd_update_vert(par, upper, lower, left, right);
+               upper = lower + 1;
+               lower = min(upper + distance - 1, ceil64(upper));
+       }
+}
+
+/*
+ * here we start the process of splitting out the fb update into
+ * individual blocks of pixels. we end up splitting into 64x64 blocks
+ * and finally down to 64x8 pages.
+ */
+static void arcfb_lcd_update(struct arcfb_par *par, unsigned int dx,
+                       unsigned int dy, unsigned int w, unsigned int h)
+{
+       unsigned int left, right, distance, y;
+
+       /* align the request first */
+       y = floor8(dy);
+       h += dy - y;
+       h = iceil8(h);
+
+       distance = w;
+       left = dx;
+       right = min(left + w - 1, ceil64(left));
+
+       while (distance > 0) {
+               arcfb_lcd_update_horiz(par, left, right, y, h);
+               distance -= ((right - left) + 1);
+               left = right + 1;
+               right = min(left + distance - 1, ceil64(left));
+       }
+}
+
+static void arcfb_fillrect(struct fb_info *info,
+                          const struct fb_fillrect *rect)
+{
+       struct arcfb_par *par = info->par;
+
+       sys_fillrect(info, rect);
+
+       /* update the physical lcd */
+       arcfb_lcd_update(par, rect->dx, rect->dy, rect->width, rect->height);
+}
+
+static void arcfb_copyarea(struct fb_info *info,
+                          const struct fb_copyarea *area)
+{
+       struct arcfb_par *par = info->par;
+
+       sys_copyarea(info, area);
+
+       /* update the physical lcd */
+       arcfb_lcd_update(par, area->dx, area->dy, area->width, area->height);
+}
+
+static void arcfb_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+       struct arcfb_par *par = info->par;
+
+       sys_imageblit(info, image);
+
+       /* update the physical lcd */
+       arcfb_lcd_update(par, image->dx, image->dy, image->width,
+                               image->height);
+}
+
+static int arcfb_ioctl(struct fb_info *info,
+                         unsigned int cmd, unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       struct arcfb_par *par = info->par;
+       unsigned long flags;
+
+       switch (cmd) {
+               case FBIO_WAITEVENT:
+               {
+                       DEFINE_WAIT(wait);
+                       /* illegal to wait on arc if no irq will occur */
+                       if (!par->irq)
+                               return -EINVAL;
+
+                       /* wait until the Arc has generated an interrupt
+                        * which will wake us up */
+                       spin_lock_irqsave(&par->lock, flags);
+                       prepare_to_wait(&arcfb_waitq, &wait,
+                                       TASK_INTERRUPTIBLE);
+                       spin_unlock_irqrestore(&par->lock, flags);
+                       schedule();
+                       finish_wait(&arcfb_waitq, &wait);
+               }
+               case FBIO_GETCONTROL2:
+               {
+                       unsigned char ctl2;
+
+                       ctl2 = ks108_readb_ctl2(info->par);
+                       if (copy_to_user(argp, &ctl2, sizeof(ctl2)))
+                               return -EFAULT;
+                       return 0;
+               }
+               default:
+                       return -EINVAL;
+       }
+}
+
+/*
+ * this is the access path from userspace. they can seek and write to
+ * the fb. it's inefficient for them to do anything less than 64*8
+ * writes since we update the lcd in each write() anyway.
+ */
+static ssize_t arcfb_write(struct fb_info *info, const char __user *buf,
+                          size_t count, loff_t *ppos)
+{
+       /* modded from epson 1355 */
+
+       unsigned long p;
+       int err=-EINVAL;
+       unsigned int fbmemlength,x,y,w,h, bitppos, startpos, endpos, bitcount;
+       struct arcfb_par *par;
+       unsigned int xres;
+
+       p = *ppos;
+       par = info->par;
+       xres = info->var.xres;
+       fbmemlength = (xres * info->var.yres)/8;
+
+       if (p > fbmemlength)
+               return -ENOSPC;
+
+       err = 0;
+       if ((count + p) > fbmemlength) {
+               count = fbmemlength - p;
+               err = -ENOSPC;
+       }
+
+       if (count) {
+               char *base_addr;
+
+               base_addr = (char __force *)info->screen_base;
+               count -= copy_from_user(base_addr + p, buf, count);
+               *ppos += count;
+               err = -EFAULT;
+       }
+
+
+       bitppos = p*8;
+       startpos = floorXres(bitppos, xres);
+       endpos = ceilXres((bitppos + (count*8)), xres);
+       bitcount = endpos - startpos;
+
+       x = startpos % xres;
+       y = startpos / xres;
+       w = xres;
+       h = bitcount / xres;
+       arcfb_lcd_update(par, x, y, w, h);
+
+       if (count)
+               return count;
+       return err;
+}
+
+static struct fb_ops arcfb_ops = {
+       .owner          = THIS_MODULE,
+       .fb_open        = arcfb_open,
+       .fb_read        = fb_sys_read,
+       .fb_write       = arcfb_write,
+       .fb_release     = arcfb_release,
+       .fb_pan_display = arcfb_pan_display,
+       .fb_fillrect    = arcfb_fillrect,
+       .fb_copyarea    = arcfb_copyarea,
+       .fb_imageblit   = arcfb_imageblit,
+       .fb_ioctl       = arcfb_ioctl,
+};
+
+static int arcfb_probe(struct platform_device *dev)
+{
+       struct fb_info *info;
+       int retval = -ENOMEM;
+       int videomemorysize;
+       unsigned char *videomemory;
+       struct arcfb_par *par;
+       int i;
+
+       videomemorysize = (((64*64)*num_cols)*num_rows)/8;
+
+       /* We need a flat backing store for the Arc's
+          less-flat actual paged framebuffer */
+       videomemory = vzalloc(videomemorysize);
+       if (!videomemory)
+               return retval;
+
+       info = framebuffer_alloc(sizeof(struct arcfb_par), &dev->dev);
+       if (!info)
+               goto err;
+
+       info->screen_base = (char __iomem *)videomemory;
+       info->fbops = &arcfb_ops;
+
+       info->var = arcfb_var;
+       info->fix = arcfb_fix;
+       par = info->par;
+       par->info = info;
+
+       if (!dio_addr || !cio_addr || !c2io_addr) {
+               printk(KERN_WARNING "no IO addresses supplied\n");
+               goto err1;
+       }
+       par->dio_addr = dio_addr;
+       par->cio_addr = cio_addr;
+       par->c2io_addr = c2io_addr;
+       par->cslut[0] = 0x00;
+       par->cslut[1] = 0x06;
+       info->flags = FBINFO_FLAG_DEFAULT;
+       spin_lock_init(&par->lock);
+       retval = register_framebuffer(info);
+       if (retval < 0)
+               goto err1;
+       platform_set_drvdata(dev, info);
+       if (irq) {
+               par->irq = irq;
+               if (request_irq(par->irq, &arcfb_interrupt, IRQF_SHARED,
+                               "arcfb", info)) {
+                       printk(KERN_INFO
+                               "arcfb: Failed req IRQ %d\n", par->irq);
+                       retval = -EBUSY;
+                       goto err1;
+               }
+       }
+       fb_info(info, "Arc frame buffer device, using %dK of video memory\n",
+               videomemorysize >> 10);
+
+       /* this inits the lcd but doesn't clear dirty pixels */
+       for (i = 0; i < num_cols * num_rows; i++) {
+               ks108_writeb_ctl(par, i, KS_DPY_OFF);
+               ks108_set_start_line(par, i, 0);
+               ks108_set_yaddr(par, i, 0);
+               ks108_set_xaddr(par, i, 0);
+               ks108_writeb_ctl(par, i, KS_DPY_ON);
+       }
+
+       /* if we were told to splash the screen, we just clear it */
+       if (!nosplash) {
+               for (i = 0; i < num_cols * num_rows; i++) {
+                       fb_info(info, "splashing lcd %d\n", i);
+                       ks108_set_start_line(par, i, 0);
+                       ks108_clear_lcd(par, i);
+               }
+       }
+
+       return 0;
+err1:
+       framebuffer_release(info);
+err:
+       vfree(videomemory);
+       return retval;
+}
+
+static int arcfb_remove(struct platform_device *dev)
+{
+       struct fb_info *info = platform_get_drvdata(dev);
+
+       if (info) {
+               unregister_framebuffer(info);
+               vfree((void __force *)info->screen_base);
+               framebuffer_release(info);
+       }
+       return 0;
+}
+
+static struct platform_driver arcfb_driver = {
+       .probe  = arcfb_probe,
+       .remove = arcfb_remove,
+       .driver = {
+               .name   = "arcfb",
+       },
+};
+
+static struct platform_device *arcfb_device;
+
+static int __init arcfb_init(void)
+{
+       int ret;
+
+       if (!arcfb_enable)
+               return -ENXIO;
+
+       ret = platform_driver_register(&arcfb_driver);
+       if (!ret) {
+               arcfb_device = platform_device_alloc("arcfb", 0);
+               if (arcfb_device) {
+                       ret = platform_device_add(arcfb_device);
+               } else {
+                       ret = -ENOMEM;
+               }
+               if (ret) {
+                       platform_device_put(arcfb_device);
+                       platform_driver_unregister(&arcfb_driver);
+               }
+       }
+       return ret;
+
+}
+
+static void __exit arcfb_exit(void)
+{
+       platform_device_unregister(arcfb_device);
+       platform_driver_unregister(&arcfb_driver);
+}
+
+module_param(num_cols, ulong, 0);
+MODULE_PARM_DESC(num_cols, "Num horiz panels, eg: 2 = 128 bit wide");
+module_param(num_rows, ulong, 0);
+MODULE_PARM_DESC(num_rows, "Num vert panels, eg: 1 = 64 bit high");
+module_param(nosplash, uint, 0);
+MODULE_PARM_DESC(nosplash, "Disable doing the splash screen");
+module_param(arcfb_enable, uint, 0);
+MODULE_PARM_DESC(arcfb_enable, "Enable communication with Arc board");
+module_param(dio_addr, ulong, 0);
+MODULE_PARM_DESC(dio_addr, "IO address for data, eg: 0x480");
+module_param(cio_addr, ulong, 0);
+MODULE_PARM_DESC(cio_addr, "IO address for control, eg: 0x400");
+module_param(c2io_addr, ulong, 0);
+MODULE_PARM_DESC(c2io_addr, "IO address for secondary control, eg: 0x408");
+module_param(splashval, ulong, 0);
+MODULE_PARM_DESC(splashval, "Splash pattern: 0xFF is black, 0x00 is green");
+module_param(tuhold, ulong, 0);
+MODULE_PARM_DESC(tuhold, "Time to hold between strobing data to Arc board");
+module_param(irq, uint, 0);
+MODULE_PARM_DESC(irq, "IRQ for the Arc board");
+
+module_init(arcfb_init);
+module_exit(arcfb_exit);
+
+MODULE_DESCRIPTION("fbdev driver for Arc monochrome LCD board");
+MODULE_AUTHOR("Jaya Kumar");
+MODULE_LICENSE("GPL");
+