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 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 171 static 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 217 static 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 237 static 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 272 static const 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 280 static 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 327 static void 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 338 static struct platform_driver cobalt_lcdfb_driver = { 339 .probe = cobalt_lcdfb_probe, 340 .remove_new = cobalt_lcdfb_remove, 341 .driver = { 342 .name = "cobalt-lcd", 343 }, 344 }; 345 module_platform_driver(cobalt_lcdfb_driver); 346 347 MODULE_LICENSE("GPL v2"); 348 MODULE_AUTHOR("Yoichi Yuasa"); 349 MODULE_DESCRIPTION("Cobalt server LCD frame buffer driver"); 350