Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  *  Cobalt/SEAD3 LCD frame buffer driver.
0004  *
0005  *  Copyright (C) 2008  Yoichi Yuasa <yuasa@linux-mips.org>
0006  *  Copyright (C) 2012  MIPS Technologies, Inc.
0007  */
0008 #include <linux/delay.h>
0009 #include <linux/fb.h>
0010 #include <linux/init.h>
0011 #include <linux/io.h>
0012 #include <linux/ioport.h>
0013 #include <linux/uaccess.h>
0014 #include <linux/platform_device.h>
0015 #include <linux/module.h>
0016 #include <linux/sched/signal.h>
0017 
0018 /*
0019  * Cursor position address
0020  * \X  0    1    2  ...  14   15
0021  * Y+----+----+----+---+----+----+
0022  * 0|0x00|0x01|0x02|...|0x0e|0x0f|
0023  *  +----+----+----+---+----+----+
0024  * 1|0x40|0x41|0x42|...|0x4e|0x4f|
0025  *  +----+----+----+---+----+----+
0026  */
0027 #define LCD_DATA_REG_OFFSET 0x10
0028 #define LCD_XRES_MAX        16
0029 #define LCD_YRES_MAX        2
0030 #define LCD_CHARS_MAX       32
0031 
0032 #define LCD_CLEAR       0x01
0033 #define LCD_CURSOR_MOVE_HOME    0x02
0034 #define LCD_RESET       0x06
0035 #define LCD_OFF         0x08
0036 #define LCD_CURSOR_OFF      0x0c
0037 #define LCD_CURSOR_BLINK_OFF    0x0e
0038 #define LCD_CURSOR_ON       0x0f
0039 #define LCD_ON          LCD_CURSOR_ON
0040 #define LCD_CURSOR_MOVE_LEFT    0x10
0041 #define LCD_CURSOR_MOVE_RIGHT   0x14
0042 #define LCD_DISPLAY_LEFT    0x18
0043 #define LCD_DISPLAY_RIGHT   0x1c
0044 #define LCD_PRERESET        0x3f    /* execute 4 times continuously */
0045 #define LCD_BUSY        0x80
0046 
0047 #define LCD_GRAPHIC_MODE    0x40
0048 #define LCD_TEXT_MODE       0x80
0049 #define LCD_CUR_POS_MASK    0x7f
0050 
0051 #define LCD_CUR_POS(x)      ((x) & LCD_CUR_POS_MASK)
0052 #define LCD_TEXT_POS(x)     ((x) | LCD_TEXT_MODE)
0053 
0054 static inline void lcd_write_control(struct fb_info *info, u8 control)
0055 {
0056     writel((u32)control << 24, info->screen_base);
0057 }
0058 
0059 static inline u8 lcd_read_control(struct fb_info *info)
0060 {
0061     return readl(info->screen_base) >> 24;
0062 }
0063 
0064 static inline void lcd_write_data(struct fb_info *info, u8 data)
0065 {
0066     writel((u32)data << 24, info->screen_base + LCD_DATA_REG_OFFSET);
0067 }
0068 
0069 static inline u8 lcd_read_data(struct fb_info *info)
0070 {
0071     return readl(info->screen_base + LCD_DATA_REG_OFFSET) >> 24;
0072 }
0073 
0074 static int lcd_busy_wait(struct fb_info *info)
0075 {
0076     u8 val = 0;
0077     int timeout = 10, retval = 0;
0078 
0079     do {
0080         val = lcd_read_control(info);
0081         val &= LCD_BUSY;
0082         if (val != LCD_BUSY)
0083             break;
0084 
0085         if (msleep_interruptible(1))
0086             return -EINTR;
0087 
0088         timeout--;
0089     } while (timeout);
0090 
0091     if (val == LCD_BUSY)
0092         retval = -EBUSY;
0093 
0094     return retval;
0095 }
0096 
0097 static void lcd_clear(struct fb_info *info)
0098 {
0099     int i;
0100 
0101     for (i = 0; i < 4; i++) {
0102         udelay(150);
0103 
0104         lcd_write_control(info, LCD_PRERESET);
0105     }
0106 
0107     udelay(150);
0108 
0109     lcd_write_control(info, LCD_CLEAR);
0110 
0111     udelay(150);
0112 
0113     lcd_write_control(info, LCD_RESET);
0114 }
0115 
0116 static const struct fb_fix_screeninfo cobalt_lcdfb_fix = {
0117     .id     = "cobalt-lcd",
0118     .type       = FB_TYPE_TEXT,
0119     .type_aux   = FB_AUX_TEXT_MDA,
0120     .visual     = FB_VISUAL_MONO01,
0121     .line_length    = LCD_XRES_MAX,
0122     .accel      = FB_ACCEL_NONE,
0123 };
0124 
0125 static ssize_t cobalt_lcdfb_read(struct fb_info *info, char __user *buf,
0126                  size_t count, loff_t *ppos)
0127 {
0128     char src[LCD_CHARS_MAX];
0129     unsigned long pos;
0130     int len, retval = 0;
0131 
0132     pos = *ppos;
0133     if (pos >= LCD_CHARS_MAX || count == 0)
0134         return 0;
0135 
0136     if (count > LCD_CHARS_MAX)
0137         count = LCD_CHARS_MAX;
0138 
0139     if (pos + count > LCD_CHARS_MAX)
0140         count = LCD_CHARS_MAX - pos;
0141 
0142     for (len = 0; len < count; len++) {
0143         retval = lcd_busy_wait(info);
0144         if (retval < 0)
0145             break;
0146 
0147         lcd_write_control(info, LCD_TEXT_POS(pos));
0148 
0149         retval = lcd_busy_wait(info);
0150         if (retval < 0)
0151             break;
0152 
0153         src[len] = lcd_read_data(info);
0154         if (pos == 0x0f)
0155             pos = 0x40;
0156         else
0157             pos++;
0158     }
0159 
0160     if (retval < 0 && signal_pending(current))
0161         return -ERESTARTSYS;
0162 
0163     if (copy_to_user(buf, src, len))
0164         return -EFAULT;
0165 
0166     *ppos += len;
0167 
0168     return len;
0169 }
0170 
0171 static ssize_t cobalt_lcdfb_write(struct fb_info *info, const char __user *buf,
0172                   size_t count, loff_t *ppos)
0173 {
0174     char dst[LCD_CHARS_MAX];
0175     unsigned long pos;
0176     int len, retval = 0;
0177 
0178     pos = *ppos;
0179     if (pos >= LCD_CHARS_MAX || count == 0)
0180         return 0;
0181 
0182     if (count > LCD_CHARS_MAX)
0183         count = LCD_CHARS_MAX;
0184 
0185     if (pos + count > LCD_CHARS_MAX)
0186         count = LCD_CHARS_MAX - pos;
0187 
0188     if (copy_from_user(dst, buf, count))
0189         return -EFAULT;
0190 
0191     for (len = 0; len < count; len++) {
0192         retval = lcd_busy_wait(info);
0193         if (retval < 0)
0194             break;
0195 
0196         lcd_write_control(info, LCD_TEXT_POS(pos));
0197 
0198         retval = lcd_busy_wait(info);
0199         if (retval < 0)
0200             break;
0201 
0202         lcd_write_data(info, dst[len]);
0203         if (pos == 0x0f)
0204             pos = 0x40;
0205         else
0206             pos++;
0207     }
0208 
0209     if (retval < 0 && signal_pending(current))
0210         return -ERESTARTSYS;
0211 
0212     *ppos += len;
0213 
0214     return len;
0215 }
0216 
0217 static int cobalt_lcdfb_blank(int blank_mode, struct fb_info *info)
0218 {
0219     int retval;
0220 
0221     retval = lcd_busy_wait(info);
0222     if (retval < 0)
0223         return retval;
0224 
0225     switch (blank_mode) {
0226     case FB_BLANK_UNBLANK:
0227         lcd_write_control(info, LCD_ON);
0228         break;
0229     default:
0230         lcd_write_control(info, LCD_OFF);
0231         break;
0232     }
0233 
0234     return 0;
0235 }
0236 
0237 static int cobalt_lcdfb_cursor(struct fb_info *info, struct fb_cursor *cursor)
0238 {
0239     u32 x, y;
0240     int retval;
0241 
0242     switch (cursor->set) {
0243     case FB_CUR_SETPOS:
0244         x = cursor->image.dx;
0245         y = cursor->image.dy;
0246         if (x >= LCD_XRES_MAX || y >= LCD_YRES_MAX)
0247             return -EINVAL;
0248 
0249         retval = lcd_busy_wait(info);
0250         if (retval < 0)
0251             return retval;
0252 
0253         lcd_write_control(info,
0254                   LCD_TEXT_POS(info->fix.line_length * y + x));
0255         break;
0256     default:
0257         return -EINVAL;
0258     }
0259 
0260     retval = lcd_busy_wait(info);
0261     if (retval < 0)
0262         return retval;
0263 
0264     if (cursor->enable)
0265         lcd_write_control(info, LCD_CURSOR_ON);
0266     else
0267         lcd_write_control(info, LCD_CURSOR_OFF);
0268 
0269     return 0;
0270 }
0271 
0272 static const struct fb_ops cobalt_lcd_fbops = {
0273     .owner      = THIS_MODULE,
0274     .fb_read    = cobalt_lcdfb_read,
0275     .fb_write   = cobalt_lcdfb_write,
0276     .fb_blank   = cobalt_lcdfb_blank,
0277     .fb_cursor  = cobalt_lcdfb_cursor,
0278 };
0279 
0280 static int cobalt_lcdfb_probe(struct platform_device *dev)
0281 {
0282     struct fb_info *info;
0283     struct resource *res;
0284     int retval;
0285 
0286     info = framebuffer_alloc(0, &dev->dev);
0287     if (!info)
0288         return -ENOMEM;
0289 
0290     res = platform_get_resource(dev, IORESOURCE_MEM, 0);
0291     if (!res) {
0292         framebuffer_release(info);
0293         return -EBUSY;
0294     }
0295 
0296     info->screen_size = resource_size(res);
0297     info->screen_base = devm_ioremap(&dev->dev, res->start,
0298                      info->screen_size);
0299     if (!info->screen_base) {
0300         framebuffer_release(info);
0301         return -ENOMEM;
0302     }
0303 
0304     info->fbops = &cobalt_lcd_fbops;
0305     info->fix = cobalt_lcdfb_fix;
0306     info->fix.smem_start = res->start;
0307     info->fix.smem_len = info->screen_size;
0308     info->pseudo_palette = NULL;
0309     info->par = NULL;
0310     info->flags = FBINFO_DEFAULT;
0311 
0312     retval = register_framebuffer(info);
0313     if (retval < 0) {
0314         framebuffer_release(info);
0315         return retval;
0316     }
0317 
0318     platform_set_drvdata(dev, info);
0319 
0320     lcd_clear(info);
0321 
0322     fb_info(info, "Cobalt server LCD frame buffer device\n");
0323 
0324     return 0;
0325 }
0326 
0327 static int cobalt_lcdfb_remove(struct platform_device *dev)
0328 {
0329     struct fb_info *info;
0330 
0331     info = platform_get_drvdata(dev);
0332     if (info) {
0333         unregister_framebuffer(info);
0334         framebuffer_release(info);
0335     }
0336 
0337     return 0;
0338 }
0339 
0340 static struct platform_driver cobalt_lcdfb_driver = {
0341     .probe  = cobalt_lcdfb_probe,
0342     .remove = cobalt_lcdfb_remove,
0343     .driver = {
0344         .name   = "cobalt-lcd",
0345     },
0346 };
0347 module_platform_driver(cobalt_lcdfb_driver);
0348 
0349 MODULE_LICENSE("GPL v2");
0350 MODULE_AUTHOR("Yoichi Yuasa");
0351 MODULE_DESCRIPTION("Cobalt server LCD frame buffer driver");