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 54 static inline void lcd_write_control(struct fb_info *info, u8 control) 55 { 56 writel((u32)control << 24, info->screen_base); 57 } 58 59 static inline u8 lcd_read_control(struct fb_info *info) 60 { 61 return readl(info->screen_base) >> 24; 62 } 63 64 static 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 69 static inline u8 lcd_read_data(struct fb_info *info) 70 { 71 return readl(info->screen_base + LCD_DATA_REG_OFFSET) >> 24; 72 } 73 74 static 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 97 static 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 116 static 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 125 static 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 else 160 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 174 static 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 else 212 pos++; 213 } 214 215 if (retval < 0 && signal_pending(current)) 216 return -ERESTARTSYS; 217 218 *ppos += len; 219 220 return len; 221 } 222 223 static 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 243 static 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 else 273 lcd_write_control(info, LCD_CURSOR_OFF); 274 275 return 0; 276 } 277 278 static 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_cursor = cobalt_lcdfb_cursor, 284 }; 285 286 static int cobalt_lcdfb_probe(struct platform_device *dev) 287 { 288 struct fb_info *info; 289 struct resource *res; 290 int retval; 291 292 info = framebuffer_alloc(0, &dev->dev); 293 if (!info) 294 return -ENOMEM; 295 296 res = platform_get_resource(dev, IORESOURCE_MEM, 0); 297 if (!res) { 298 framebuffer_release(info); 299 return -EBUSY; 300 } 301 302 info->screen_size = resource_size(res); 303 info->screen_base = devm_ioremap(&dev->dev, res->start, 304 info->screen_size); 305 if (!info->screen_base) { 306 framebuffer_release(info); 307 return -ENOMEM; 308 } 309 310 info->fbops = &cobalt_lcd_fbops; 311 info->fix = cobalt_lcdfb_fix; 312 info->fix.smem_start = res->start; 313 info->fix.smem_len = info->screen_size; 314 info->pseudo_palette = NULL; 315 info->par = NULL; 316 info->flags = FBINFO_DEFAULT; 317 318 retval = register_framebuffer(info); 319 if (retval < 0) { 320 framebuffer_release(info); 321 return retval; 322 } 323 324 platform_set_drvdata(dev, info); 325 326 lcd_clear(info); 327 328 fb_info(info, "Cobalt server LCD frame buffer device\n"); 329 330 return 0; 331 } 332 333 static void cobalt_lcdfb_remove(struct platform_device *dev) 334 { 335 struct fb_info *info; 336 337 info = platform_get_drvdata(dev); 338 if (info) { 339 unregister_framebuffer(info); 340 framebuffer_release(info); 341 } 342 } 343 344 static struct platform_driver cobalt_lcdfb_driver = { 345 .probe = cobalt_lcdfb_probe, 346 .remove_new = cobalt_lcdfb_remove, 347 .driver = { 348 .name = "cobalt-lcd", 349 }, 350 }; 351 module_platform_driver(cobalt_lcdfb_driver); 352 353 MODULE_LICENSE("GPL v2"); 354 MODULE_AUTHOR("Yoichi Yuasa"); 355 MODULE_DESCRIPTION("Cobalt server LCD frame buffer driver"); 356