18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Cobalt/SEAD3 LCD frame buffer driver. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2008 Yoichi Yuasa <yuasa@linux-mips.org> 68c2ecf20Sopenharmony_ci * Copyright (C) 2012 MIPS Technologies, Inc. 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci#include <linux/delay.h> 98c2ecf20Sopenharmony_ci#include <linux/fb.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/io.h> 128c2ecf20Sopenharmony_ci#include <linux/ioport.h> 138c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/sched/signal.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci/* 198c2ecf20Sopenharmony_ci * Cursor position address 208c2ecf20Sopenharmony_ci * \X 0 1 2 ... 14 15 218c2ecf20Sopenharmony_ci * Y+----+----+----+---+----+----+ 228c2ecf20Sopenharmony_ci * 0|0x00|0x01|0x02|...|0x0e|0x0f| 238c2ecf20Sopenharmony_ci * +----+----+----+---+----+----+ 248c2ecf20Sopenharmony_ci * 1|0x40|0x41|0x42|...|0x4e|0x4f| 258c2ecf20Sopenharmony_ci * +----+----+----+---+----+----+ 268c2ecf20Sopenharmony_ci */ 278c2ecf20Sopenharmony_ci#define LCD_DATA_REG_OFFSET 0x10 288c2ecf20Sopenharmony_ci#define LCD_XRES_MAX 16 298c2ecf20Sopenharmony_ci#define LCD_YRES_MAX 2 308c2ecf20Sopenharmony_ci#define LCD_CHARS_MAX 32 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define LCD_CLEAR 0x01 338c2ecf20Sopenharmony_ci#define LCD_CURSOR_MOVE_HOME 0x02 348c2ecf20Sopenharmony_ci#define LCD_RESET 0x06 358c2ecf20Sopenharmony_ci#define LCD_OFF 0x08 368c2ecf20Sopenharmony_ci#define LCD_CURSOR_OFF 0x0c 378c2ecf20Sopenharmony_ci#define LCD_CURSOR_BLINK_OFF 0x0e 388c2ecf20Sopenharmony_ci#define LCD_CURSOR_ON 0x0f 398c2ecf20Sopenharmony_ci#define LCD_ON LCD_CURSOR_ON 408c2ecf20Sopenharmony_ci#define LCD_CURSOR_MOVE_LEFT 0x10 418c2ecf20Sopenharmony_ci#define LCD_CURSOR_MOVE_RIGHT 0x14 428c2ecf20Sopenharmony_ci#define LCD_DISPLAY_LEFT 0x18 438c2ecf20Sopenharmony_ci#define LCD_DISPLAY_RIGHT 0x1c 448c2ecf20Sopenharmony_ci#define LCD_PRERESET 0x3f /* execute 4 times continuously */ 458c2ecf20Sopenharmony_ci#define LCD_BUSY 0x80 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci#define LCD_GRAPHIC_MODE 0x40 488c2ecf20Sopenharmony_ci#define LCD_TEXT_MODE 0x80 498c2ecf20Sopenharmony_ci#define LCD_CUR_POS_MASK 0x7f 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci#define LCD_CUR_POS(x) ((x) & LCD_CUR_POS_MASK) 528c2ecf20Sopenharmony_ci#define LCD_TEXT_POS(x) ((x) | LCD_TEXT_MODE) 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic inline void lcd_write_control(struct fb_info *info, u8 control) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci writel((u32)control << 24, info->screen_base); 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic inline u8 lcd_read_control(struct fb_info *info) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci return readl(info->screen_base) >> 24; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic inline void lcd_write_data(struct fb_info *info, u8 data) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci writel((u32)data << 24, info->screen_base + LCD_DATA_REG_OFFSET); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic inline u8 lcd_read_data(struct fb_info *info) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci return readl(info->screen_base + LCD_DATA_REG_OFFSET) >> 24; 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_cistatic int lcd_busy_wait(struct fb_info *info) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci u8 val = 0; 778c2ecf20Sopenharmony_ci int timeout = 10, retval = 0; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci do { 808c2ecf20Sopenharmony_ci val = lcd_read_control(info); 818c2ecf20Sopenharmony_ci val &= LCD_BUSY; 828c2ecf20Sopenharmony_ci if (val != LCD_BUSY) 838c2ecf20Sopenharmony_ci break; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci if (msleep_interruptible(1)) 868c2ecf20Sopenharmony_ci return -EINTR; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci timeout--; 898c2ecf20Sopenharmony_ci } while (timeout); 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci if (val == LCD_BUSY) 928c2ecf20Sopenharmony_ci retval = -EBUSY; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci return retval; 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic void lcd_clear(struct fb_info *info) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci int i; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci for (i = 0; i < 4; i++) { 1028c2ecf20Sopenharmony_ci udelay(150); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_PRERESET); 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci udelay(150); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_CLEAR); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci udelay(150); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_RESET); 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic const struct fb_fix_screeninfo cobalt_lcdfb_fix = { 1178c2ecf20Sopenharmony_ci .id = "cobalt-lcd", 1188c2ecf20Sopenharmony_ci .type = FB_TYPE_TEXT, 1198c2ecf20Sopenharmony_ci .type_aux = FB_AUX_TEXT_MDA, 1208c2ecf20Sopenharmony_ci .visual = FB_VISUAL_MONO01, 1218c2ecf20Sopenharmony_ci .line_length = LCD_XRES_MAX, 1228c2ecf20Sopenharmony_ci .accel = FB_ACCEL_NONE, 1238c2ecf20Sopenharmony_ci}; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic ssize_t cobalt_lcdfb_read(struct fb_info *info, char __user *buf, 1268c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci char src[LCD_CHARS_MAX]; 1298c2ecf20Sopenharmony_ci unsigned long pos; 1308c2ecf20Sopenharmony_ci int len, retval = 0; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci pos = *ppos; 1338c2ecf20Sopenharmony_ci if (pos >= LCD_CHARS_MAX || count == 0) 1348c2ecf20Sopenharmony_ci return 0; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (count > LCD_CHARS_MAX) 1378c2ecf20Sopenharmony_ci count = LCD_CHARS_MAX; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci if (pos + count > LCD_CHARS_MAX) 1408c2ecf20Sopenharmony_ci count = LCD_CHARS_MAX - pos; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci for (len = 0; len < count; len++) { 1438c2ecf20Sopenharmony_ci retval = lcd_busy_wait(info); 1448c2ecf20Sopenharmony_ci if (retval < 0) 1458c2ecf20Sopenharmony_ci break; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_TEXT_POS(pos)); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci retval = lcd_busy_wait(info); 1508c2ecf20Sopenharmony_ci if (retval < 0) 1518c2ecf20Sopenharmony_ci break; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci src[len] = lcd_read_data(info); 1548c2ecf20Sopenharmony_ci if (pos == 0x0f) 1558c2ecf20Sopenharmony_ci pos = 0x40; 1568c2ecf20Sopenharmony_ci else 1578c2ecf20Sopenharmony_ci pos++; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (retval < 0 && signal_pending(current)) 1618c2ecf20Sopenharmony_ci return -ERESTARTSYS; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci if (copy_to_user(buf, src, len)) 1648c2ecf20Sopenharmony_ci return -EFAULT; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci *ppos += len; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci return len; 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic ssize_t cobalt_lcdfb_write(struct fb_info *info, const char __user *buf, 1728c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci char dst[LCD_CHARS_MAX]; 1758c2ecf20Sopenharmony_ci unsigned long pos; 1768c2ecf20Sopenharmony_ci int len, retval = 0; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci pos = *ppos; 1798c2ecf20Sopenharmony_ci if (pos >= LCD_CHARS_MAX || count == 0) 1808c2ecf20Sopenharmony_ci return 0; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci if (count > LCD_CHARS_MAX) 1838c2ecf20Sopenharmony_ci count = LCD_CHARS_MAX; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci if (pos + count > LCD_CHARS_MAX) 1868c2ecf20Sopenharmony_ci count = LCD_CHARS_MAX - pos; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci if (copy_from_user(dst, buf, count)) 1898c2ecf20Sopenharmony_ci return -EFAULT; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci for (len = 0; len < count; len++) { 1928c2ecf20Sopenharmony_ci retval = lcd_busy_wait(info); 1938c2ecf20Sopenharmony_ci if (retval < 0) 1948c2ecf20Sopenharmony_ci break; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_TEXT_POS(pos)); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci retval = lcd_busy_wait(info); 1998c2ecf20Sopenharmony_ci if (retval < 0) 2008c2ecf20Sopenharmony_ci break; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci lcd_write_data(info, dst[len]); 2038c2ecf20Sopenharmony_ci if (pos == 0x0f) 2048c2ecf20Sopenharmony_ci pos = 0x40; 2058c2ecf20Sopenharmony_ci else 2068c2ecf20Sopenharmony_ci pos++; 2078c2ecf20Sopenharmony_ci } 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci if (retval < 0 && signal_pending(current)) 2108c2ecf20Sopenharmony_ci return -ERESTARTSYS; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci *ppos += len; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci return len; 2158c2ecf20Sopenharmony_ci} 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic int cobalt_lcdfb_blank(int blank_mode, struct fb_info *info) 2188c2ecf20Sopenharmony_ci{ 2198c2ecf20Sopenharmony_ci int retval; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci retval = lcd_busy_wait(info); 2228c2ecf20Sopenharmony_ci if (retval < 0) 2238c2ecf20Sopenharmony_ci return retval; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci switch (blank_mode) { 2268c2ecf20Sopenharmony_ci case FB_BLANK_UNBLANK: 2278c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_ON); 2288c2ecf20Sopenharmony_ci break; 2298c2ecf20Sopenharmony_ci default: 2308c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_OFF); 2318c2ecf20Sopenharmony_ci break; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci return 0; 2358c2ecf20Sopenharmony_ci} 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_cistatic int cobalt_lcdfb_cursor(struct fb_info *info, struct fb_cursor *cursor) 2388c2ecf20Sopenharmony_ci{ 2398c2ecf20Sopenharmony_ci u32 x, y; 2408c2ecf20Sopenharmony_ci int retval; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci switch (cursor->set) { 2438c2ecf20Sopenharmony_ci case FB_CUR_SETPOS: 2448c2ecf20Sopenharmony_ci x = cursor->image.dx; 2458c2ecf20Sopenharmony_ci y = cursor->image.dy; 2468c2ecf20Sopenharmony_ci if (x >= LCD_XRES_MAX || y >= LCD_YRES_MAX) 2478c2ecf20Sopenharmony_ci return -EINVAL; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci retval = lcd_busy_wait(info); 2508c2ecf20Sopenharmony_ci if (retval < 0) 2518c2ecf20Sopenharmony_ci return retval; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci lcd_write_control(info, 2548c2ecf20Sopenharmony_ci LCD_TEXT_POS(info->fix.line_length * y + x)); 2558c2ecf20Sopenharmony_ci break; 2568c2ecf20Sopenharmony_ci default: 2578c2ecf20Sopenharmony_ci return -EINVAL; 2588c2ecf20Sopenharmony_ci } 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci retval = lcd_busy_wait(info); 2618c2ecf20Sopenharmony_ci if (retval < 0) 2628c2ecf20Sopenharmony_ci return retval; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci if (cursor->enable) 2658c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_CURSOR_ON); 2668c2ecf20Sopenharmony_ci else 2678c2ecf20Sopenharmony_ci lcd_write_control(info, LCD_CURSOR_OFF); 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci return 0; 2708c2ecf20Sopenharmony_ci} 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_cistatic const struct fb_ops cobalt_lcd_fbops = { 2738c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2748c2ecf20Sopenharmony_ci .fb_read = cobalt_lcdfb_read, 2758c2ecf20Sopenharmony_ci .fb_write = cobalt_lcdfb_write, 2768c2ecf20Sopenharmony_ci .fb_blank = cobalt_lcdfb_blank, 2778c2ecf20Sopenharmony_ci .fb_cursor = cobalt_lcdfb_cursor, 2788c2ecf20Sopenharmony_ci}; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic int cobalt_lcdfb_probe(struct platform_device *dev) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci struct fb_info *info; 2838c2ecf20Sopenharmony_ci struct resource *res; 2848c2ecf20Sopenharmony_ci int retval; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci info = framebuffer_alloc(0, &dev->dev); 2878c2ecf20Sopenharmony_ci if (!info) 2888c2ecf20Sopenharmony_ci return -ENOMEM; 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci res = platform_get_resource(dev, IORESOURCE_MEM, 0); 2918c2ecf20Sopenharmony_ci if (!res) { 2928c2ecf20Sopenharmony_ci framebuffer_release(info); 2938c2ecf20Sopenharmony_ci return -EBUSY; 2948c2ecf20Sopenharmony_ci } 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci info->screen_size = resource_size(res); 2978c2ecf20Sopenharmony_ci info->screen_base = devm_ioremap(&dev->dev, res->start, 2988c2ecf20Sopenharmony_ci info->screen_size); 2998c2ecf20Sopenharmony_ci if (!info->screen_base) { 3008c2ecf20Sopenharmony_ci framebuffer_release(info); 3018c2ecf20Sopenharmony_ci return -ENOMEM; 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci info->fbops = &cobalt_lcd_fbops; 3058c2ecf20Sopenharmony_ci info->fix = cobalt_lcdfb_fix; 3068c2ecf20Sopenharmony_ci info->fix.smem_start = res->start; 3078c2ecf20Sopenharmony_ci info->fix.smem_len = info->screen_size; 3088c2ecf20Sopenharmony_ci info->pseudo_palette = NULL; 3098c2ecf20Sopenharmony_ci info->par = NULL; 3108c2ecf20Sopenharmony_ci info->flags = FBINFO_DEFAULT; 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci retval = register_framebuffer(info); 3138c2ecf20Sopenharmony_ci if (retval < 0) { 3148c2ecf20Sopenharmony_ci framebuffer_release(info); 3158c2ecf20Sopenharmony_ci return retval; 3168c2ecf20Sopenharmony_ci } 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci platform_set_drvdata(dev, info); 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci lcd_clear(info); 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci fb_info(info, "Cobalt server LCD frame buffer device\n"); 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci return 0; 3258c2ecf20Sopenharmony_ci} 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_cistatic int cobalt_lcdfb_remove(struct platform_device *dev) 3288c2ecf20Sopenharmony_ci{ 3298c2ecf20Sopenharmony_ci struct fb_info *info; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci info = platform_get_drvdata(dev); 3328c2ecf20Sopenharmony_ci if (info) { 3338c2ecf20Sopenharmony_ci unregister_framebuffer(info); 3348c2ecf20Sopenharmony_ci framebuffer_release(info); 3358c2ecf20Sopenharmony_ci } 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci return 0; 3388c2ecf20Sopenharmony_ci} 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_cistatic struct platform_driver cobalt_lcdfb_driver = { 3418c2ecf20Sopenharmony_ci .probe = cobalt_lcdfb_probe, 3428c2ecf20Sopenharmony_ci .remove = cobalt_lcdfb_remove, 3438c2ecf20Sopenharmony_ci .driver = { 3448c2ecf20Sopenharmony_ci .name = "cobalt-lcd", 3458c2ecf20Sopenharmony_ci }, 3468c2ecf20Sopenharmony_ci}; 3478c2ecf20Sopenharmony_cimodule_platform_driver(cobalt_lcdfb_driver); 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3508c2ecf20Sopenharmony_ciMODULE_AUTHOR("Yoichi Yuasa"); 3518c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Cobalt server LCD frame buffer driver"); 352