357 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*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 address20 * \X 0 1 2 ... 14 1521 * Y+----+----+----+---+----+----+22 * 0|0x00|0x01|0x02|...|0x0e|0x0f|23 * +----+----+----+---+----+----+24 * 1|0x40|0x41|0x42|...|0x4e|0x4f|25 * +----+----+----+---+----+----+26 */27#define LCD_DATA_REG_OFFSET 0x1028#define LCD_XRES_MAX 1629#define LCD_YRES_MAX 230#define LCD_CHARS_MAX 3231 32#define LCD_CLEAR 0x0133#define LCD_CURSOR_MOVE_HOME 0x0234#define LCD_RESET 0x0635#define LCD_OFF 0x0836#define LCD_CURSOR_OFF 0x0c37#define LCD_CURSOR_BLINK_OFF 0x0e38#define LCD_CURSOR_ON 0x0f39#define LCD_ON LCD_CURSOR_ON40#define LCD_CURSOR_MOVE_LEFT 0x1041#define LCD_CURSOR_MOVE_RIGHT 0x1442#define LCD_DISPLAY_LEFT 0x1843#define LCD_DISPLAY_RIGHT 0x1c44#define LCD_PRERESET 0x3f /* execute 4 times continuously */45#define LCD_BUSY 0x8046 47#define LCD_GRAPHIC_MODE 0x4048#define LCD_TEXT_MODE 0x8049#define LCD_CUR_POS_MASK 0x7f50 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 if (!info->screen_base)133 return -ENODEV;134 135 pos = *ppos;136 if (pos >= LCD_CHARS_MAX || count == 0)137 return 0;138 139 if (count > LCD_CHARS_MAX)140 count = LCD_CHARS_MAX;141 142 if (pos + count > LCD_CHARS_MAX)143 count = LCD_CHARS_MAX - pos;144 145 for (len = 0; len < count; len++) {146 retval = lcd_busy_wait(info);147 if (retval < 0)148 break;149 150 lcd_write_control(info, LCD_TEXT_POS(pos));151 152 retval = lcd_busy_wait(info);153 if (retval < 0)154 break;155 156 src[len] = lcd_read_data(info);157 if (pos == 0x0f)158 pos = 0x40;159 else160 pos++;161 }162 163 if (retval < 0 && signal_pending(current))164 return -ERESTARTSYS;165 166 if (copy_to_user(buf, src, len))167 return -EFAULT;168 169 *ppos += len;170 171 return len;172}173 174static ssize_t cobalt_lcdfb_write(struct fb_info *info, const char __user *buf,175 size_t count, loff_t *ppos)176{177 char dst[LCD_CHARS_MAX];178 unsigned long pos;179 int len, retval = 0;180 181 if (!info->screen_base)182 return -ENODEV;183 184 pos = *ppos;185 if (pos >= LCD_CHARS_MAX || count == 0)186 return 0;187 188 if (count > LCD_CHARS_MAX)189 count = LCD_CHARS_MAX;190 191 if (pos + count > LCD_CHARS_MAX)192 count = LCD_CHARS_MAX - pos;193 194 if (copy_from_user(dst, buf, count))195 return -EFAULT;196 197 for (len = 0; len < count; len++) {198 retval = lcd_busy_wait(info);199 if (retval < 0)200 break;201 202 lcd_write_control(info, LCD_TEXT_POS(pos));203 204 retval = lcd_busy_wait(info);205 if (retval < 0)206 break;207 208 lcd_write_data(info, dst[len]);209 if (pos == 0x0f)210 pos = 0x40;211 else212 pos++;213 }214 215 if (retval < 0 && signal_pending(current))216 return -ERESTARTSYS;217 218 *ppos += len;219 220 return len;221}222 223static int cobalt_lcdfb_blank(int blank_mode, struct fb_info *info)224{225 int retval;226 227 retval = lcd_busy_wait(info);228 if (retval < 0)229 return retval;230 231 switch (blank_mode) {232 case FB_BLANK_UNBLANK:233 lcd_write_control(info, LCD_ON);234 break;235 default:236 lcd_write_control(info, LCD_OFF);237 break;238 }239 240 return 0;241}242 243static int cobalt_lcdfb_cursor(struct fb_info *info, struct fb_cursor *cursor)244{245 u32 x, y;246 int retval;247 248 switch (cursor->set) {249 case FB_CUR_SETPOS:250 x = cursor->image.dx;251 y = cursor->image.dy;252 if (x >= LCD_XRES_MAX || y >= LCD_YRES_MAX)253 return -EINVAL;254 255 retval = lcd_busy_wait(info);256 if (retval < 0)257 return retval;258 259 lcd_write_control(info,260 LCD_TEXT_POS(info->fix.line_length * y + x));261 break;262 default:263 return -EINVAL;264 }265 266 retval = lcd_busy_wait(info);267 if (retval < 0)268 return retval;269 270 if (cursor->enable)271 lcd_write_control(info, LCD_CURSOR_ON);272 else273 lcd_write_control(info, LCD_CURSOR_OFF);274 275 return 0;276}277 278static const struct fb_ops cobalt_lcd_fbops = {279 .owner = THIS_MODULE,280 .fb_read = cobalt_lcdfb_read,281 .fb_write = cobalt_lcdfb_write,282 .fb_blank = cobalt_lcdfb_blank,283 __FB_DEFAULT_IOMEM_OPS_DRAW,284 .fb_cursor = cobalt_lcdfb_cursor,285 __FB_DEFAULT_IOMEM_OPS_MMAP,286};287 288static int cobalt_lcdfb_probe(struct platform_device *dev)289{290 struct fb_info *info;291 struct resource *res;292 int retval;293 294 info = framebuffer_alloc(0, &dev->dev);295 if (!info)296 return -ENOMEM;297 298 res = platform_get_resource(dev, IORESOURCE_MEM, 0);299 if (!res) {300 framebuffer_release(info);301 return -EBUSY;302 }303 304 info->screen_size = resource_size(res);305 info->screen_base = devm_ioremap(&dev->dev, res->start,306 info->screen_size);307 if (!info->screen_base) {308 framebuffer_release(info);309 return -ENOMEM;310 }311 312 info->fbops = &cobalt_lcd_fbops;313 info->fix = cobalt_lcdfb_fix;314 info->fix.smem_start = res->start;315 info->fix.smem_len = info->screen_size;316 info->pseudo_palette = NULL;317 info->par = NULL;318 319 retval = register_framebuffer(info);320 if (retval < 0) {321 framebuffer_release(info);322 return retval;323 }324 325 platform_set_drvdata(dev, info);326 327 lcd_clear(info);328 329 fb_info(info, "Cobalt server LCD frame buffer device\n");330 331 return 0;332}333 334static void cobalt_lcdfb_remove(struct platform_device *dev)335{336 struct fb_info *info;337 338 info = platform_get_drvdata(dev);339 if (info) {340 unregister_framebuffer(info);341 framebuffer_release(info);342 }343}344 345static struct platform_driver cobalt_lcdfb_driver = {346 .probe = cobalt_lcdfb_probe,347 .remove = cobalt_lcdfb_remove,348 .driver = {349 .name = "cobalt-lcd",350 },351};352module_platform_driver(cobalt_lcdfb_driver);353 354MODULE_LICENSE("GPL v2");355MODULE_AUTHOR("Yoichi Yuasa");356MODULE_DESCRIPTION("Cobalt server LCD frame buffer driver");357