linux/drivers/video/fbdev/cobalt_lcdfb.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *  Cobalt/SEAD3 LCD frame buffer driver.
   4 *
   5 *  Copyright (C) 2008  Yoichi Yuasa <yuasa@linux-mips.org>
   6 *  Copyright (C) 2012  MIPS Technologies, Inc.
   7 */
   8#include <linux/delay.h>
   9#include <linux/fb.h>
  10#include <linux/init.h>
  11#include <linux/io.h>
  12#include <linux/ioport.h>
  13#include <linux/uaccess.h>
  14#include <linux/platform_device.h>
  15#include <linux/module.h>
  16#include <linux/sched/signal.h>
  17
  18/*
  19 * Cursor position address
  20 * \X  0    1    2  ...  14   15
  21 * Y+----+----+----+---+----+----+
  22 * 0|0x00|0x01|0x02|...|0x0e|0x0f|
  23 *  +----+----+----+---+----+----+
  24 * 1|0x40|0x41|0x42|...|0x4e|0x4f|
  25 *  +----+----+----+---+----+----+
  26 */
  27#define LCD_DATA_REG_OFFSET     0x10
  28#define LCD_XRES_MAX            16
  29#define LCD_YRES_MAX            2
  30#define LCD_CHARS_MAX           32
  31
  32#define LCD_CLEAR               0x01
  33#define LCD_CURSOR_MOVE_HOME    0x02
  34#define LCD_RESET               0x06
  35#define LCD_OFF                 0x08
  36#define LCD_CURSOR_OFF          0x0c
  37#define LCD_CURSOR_BLINK_OFF    0x0e
  38#define LCD_CURSOR_ON           0x0f
  39#define LCD_ON                  LCD_CURSOR_ON
  40#define LCD_CURSOR_MOVE_LEFT    0x10
  41#define LCD_CURSOR_MOVE_RIGHT   0x14
  42#define LCD_DISPLAY_LEFT        0x18
  43#define LCD_DISPLAY_RIGHT       0x1c
  44#define LCD_PRERESET            0x3f    /* execute 4 times continuously */
  45#define LCD_BUSY                0x80
  46
  47#define LCD_GRAPHIC_MODE        0x40
  48#define LCD_TEXT_MODE           0x80
  49#define LCD_CUR_POS_MASK        0x7f
  50
  51#define LCD_CUR_POS(x)          ((x) & LCD_CUR_POS_MASK)
  52#define LCD_TEXT_POS(x)         ((x) | LCD_TEXT_MODE)
  53
  54static inline void lcd_write_control(struct fb_info *info, u8 control)
  55{
  56        writel((u32)control << 24, info->screen_base);
  57}
  58
  59static inline u8 lcd_read_control(struct fb_info *info)
  60{
  61        return readl(info->screen_base) >> 24;
  62}
  63
  64static inline void lcd_write_data(struct fb_info *info, u8 data)
  65{
  66        writel((u32)data << 24, info->screen_base + LCD_DATA_REG_OFFSET);
  67}
  68
  69static inline u8 lcd_read_data(struct fb_info *info)
  70{
  71        return readl(info->screen_base + LCD_DATA_REG_OFFSET) >> 24;
  72}
  73
  74static int lcd_busy_wait(struct fb_info *info)
  75{
  76        u8 val = 0;
  77        int timeout = 10, retval = 0;
  78
  79        do {
  80                val = lcd_read_control(info);
  81                val &= LCD_BUSY;
  82                if (val != LCD_BUSY)
  83                        break;
  84
  85                if (msleep_interruptible(1))
  86                        return -EINTR;
  87
  88                timeout--;
  89        } while (timeout);
  90
  91        if (val == LCD_BUSY)
  92                retval = -EBUSY;
  93
  94        return retval;
  95}
  96
  97static void lcd_clear(struct fb_info *info)
  98{
  99        int i;
 100
 101        for (i = 0; i < 4; i++) {
 102                udelay(150);
 103
 104                lcd_write_control(info, LCD_PRERESET);
 105        }
 106
 107        udelay(150);
 108
 109        lcd_write_control(info, LCD_CLEAR);
 110
 111        udelay(150);
 112
 113        lcd_write_control(info, LCD_RESET);
 114}
 115
 116static const struct fb_fix_screeninfo cobalt_lcdfb_fix = {
 117        .id             = "cobalt-lcd",
 118        .type           = FB_TYPE_TEXT,
 119        .type_aux       = FB_AUX_TEXT_MDA,
 120        .visual         = FB_VISUAL_MONO01,
 121        .line_length    = LCD_XRES_MAX,
 122        .accel          = FB_ACCEL_NONE,
 123};
 124
 125static ssize_t cobalt_lcdfb_read(struct fb_info *info, char __user *buf,
 126                                 size_t count, loff_t *ppos)
 127{
 128        char src[LCD_CHARS_MAX];
 129        unsigned long pos;
 130        int len, retval = 0;
 131
 132        pos = *ppos;
 133        if (pos >= LCD_CHARS_MAX || count == 0)
 134                return 0;
 135
 136        if (count > LCD_CHARS_MAX)
 137                count = LCD_CHARS_MAX;
 138
 139        if (pos + count > LCD_CHARS_MAX)
 140                count = LCD_CHARS_MAX - pos;
 141
 142        for (len = 0; len < count; len++) {
 143                retval = lcd_busy_wait(info);
 144                if (retval < 0)
 145                        break;
 146
 147                lcd_write_control(info, LCD_TEXT_POS(pos));
 148
 149                retval = lcd_busy_wait(info);
 150                if (retval < 0)
 151                        break;
 152
 153                src[len] = lcd_read_data(info);
 154                if (pos == 0x0f)
 155                        pos = 0x40;
 156                else
 157                        pos++;
 158        }
 159
 160        if (retval < 0 && signal_pending(current))
 161                return -ERESTARTSYS;
 162
 163        if (copy_to_user(buf, src, len))
 164                return -EFAULT;
 165
 166        *ppos += len;
 167
 168        return len;
 169}
 170
 171static ssize_t cobalt_lcdfb_write(struct fb_info *info, const char __user *buf,
 172                                  size_t count, loff_t *ppos)
 173{
 174        char dst[LCD_CHARS_MAX];
 175        unsigned long pos;
 176        int len, retval = 0;
 177
 178        pos = *ppos;
 179        if (pos >= LCD_CHARS_MAX || count == 0)
 180                return 0;
 181
 182        if (count > LCD_CHARS_MAX)
 183                count = LCD_CHARS_MAX;
 184
 185        if (pos + count > LCD_CHARS_MAX)
 186                count = LCD_CHARS_MAX - pos;
 187
 188        if (copy_from_user(dst, buf, count))
 189                return -EFAULT;
 190
 191        for (len = 0; len < count; len++) {
 192                retval = lcd_busy_wait(info);
 193                if (retval < 0)
 194                        break;
 195
 196                lcd_write_control(info, LCD_TEXT_POS(pos));
 197
 198                retval = lcd_busy_wait(info);
 199                if (retval < 0)
 200                        break;
 201
 202                lcd_write_data(info, dst[len]);
 203                if (pos == 0x0f)
 204                        pos = 0x40;
 205                else
 206                        pos++;
 207        }
 208
 209        if (retval < 0 && signal_pending(current))
 210                return -ERESTARTSYS;
 211
 212        *ppos += len;
 213
 214        return len;
 215}
 216
 217static int cobalt_lcdfb_blank(int blank_mode, struct fb_info *info)
 218{
 219        int retval;
 220
 221        retval = lcd_busy_wait(info);
 222        if (retval < 0)
 223                return retval;
 224
 225        switch (blank_mode) {
 226        case FB_BLANK_UNBLANK:
 227                lcd_write_control(info, LCD_ON);
 228                break;
 229        default:
 230                lcd_write_control(info, LCD_OFF);
 231                break;
 232        }
 233
 234        return 0;
 235}
 236
 237static int cobalt_lcdfb_cursor(struct fb_info *info, struct fb_cursor *cursor)
 238{
 239        u32 x, y;
 240        int retval;
 241
 242        switch (cursor->set) {
 243        case FB_CUR_SETPOS:
 244                x = cursor->image.dx;
 245                y = cursor->image.dy;
 246                if (x >= LCD_XRES_MAX || y >= LCD_YRES_MAX)
 247                        return -EINVAL;
 248
 249                retval = lcd_busy_wait(info);
 250                if (retval < 0)
 251                        return retval;
 252
 253                lcd_write_control(info,
 254                                  LCD_TEXT_POS(info->fix.line_length * y + x));
 255                break;
 256        default:
 257                return -EINVAL;
 258        }
 259
 260        retval = lcd_busy_wait(info);
 261        if (retval < 0)
 262                return retval;
 263
 264        if (cursor->enable)
 265                lcd_write_control(info, LCD_CURSOR_ON);
 266        else
 267                lcd_write_control(info, LCD_CURSOR_OFF);
 268
 269        return 0;
 270}
 271
 272static struct fb_ops cobalt_lcd_fbops = {
 273        .owner          = THIS_MODULE,
 274        .fb_read        = cobalt_lcdfb_read,
 275        .fb_write       = cobalt_lcdfb_write,
 276        .fb_blank       = cobalt_lcdfb_blank,
 277        .fb_cursor      = cobalt_lcdfb_cursor,
 278};
 279
 280static int cobalt_lcdfb_probe(struct platform_device *dev)
 281{
 282        struct fb_info *info;
 283        struct resource *res;
 284        int retval;
 285
 286        info = framebuffer_alloc(0, &dev->dev);
 287        if (!info)
 288                return -ENOMEM;
 289
 290        res = platform_get_resource(dev, IORESOURCE_MEM, 0);
 291        if (!res) {
 292                framebuffer_release(info);
 293                return -EBUSY;
 294        }
 295
 296        info->screen_size = resource_size(res);
 297        info->screen_base = devm_ioremap(&dev->dev, res->start,
 298                                         info->screen_size);
 299        if (!info->screen_base) {
 300                framebuffer_release(info);
 301                return -ENOMEM;
 302        }
 303
 304        info->fbops = &cobalt_lcd_fbops;
 305        info->fix = cobalt_lcdfb_fix;
 306        info->fix.smem_start = res->start;
 307        info->fix.smem_len = info->screen_size;
 308        info->pseudo_palette = NULL;
 309        info->par = NULL;
 310        info->flags = FBINFO_DEFAULT;
 311
 312        retval = register_framebuffer(info);
 313        if (retval < 0) {
 314                framebuffer_release(info);
 315                return retval;
 316        }
 317
 318        platform_set_drvdata(dev, info);
 319
 320        lcd_clear(info);
 321
 322        fb_info(info, "Cobalt server LCD frame buffer device\n");
 323
 324        return 0;
 325}
 326
 327static int cobalt_lcdfb_remove(struct platform_device *dev)
 328{
 329        struct fb_info *info;
 330
 331        info = platform_get_drvdata(dev);
 332        if (info) {
 333                unregister_framebuffer(info);
 334                framebuffer_release(info);
 335        }
 336
 337        return 0;
 338}
 339
 340static struct platform_driver cobalt_lcdfb_driver = {
 341        .probe  = cobalt_lcdfb_probe,
 342        .remove = cobalt_lcdfb_remove,
 343        .driver = {
 344                .name   = "cobalt-lcd",
 345        },
 346};
 347module_platform_driver(cobalt_lcdfb_driver);
 348
 349MODULE_LICENSE("GPL v2");
 350MODULE_AUTHOR("Yoichi Yuasa");
 351MODULE_DESCRIPTION("Cobalt server LCD frame buffer driver");
 352