18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * linux/drivers/video/arcfb.c -- FB driver for Arc monochrome LCD board
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2005, Jaya Kumar <jayalk@intworks.biz>
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
78c2ecf20Sopenharmony_ci * License. See the file COPYING in the main directory of this archive for
88c2ecf20Sopenharmony_ci * more details.
98c2ecf20Sopenharmony_ci *
108c2ecf20Sopenharmony_ci * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven.
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * This driver was written to be used with the Arc LCD board. Arc uses a
138c2ecf20Sopenharmony_ci * set of KS108 chips that control individual 64x64 LCD matrices. The board
148c2ecf20Sopenharmony_ci * can be paneled in a variety of setups such as 2x1=128x64, 4x4=256x256 and
158c2ecf20Sopenharmony_ci * so on. The interface between the board and the host is TTL based GPIO. The
168c2ecf20Sopenharmony_ci * GPIO requirements are 8 writable data lines and 4+n lines for control. On a
178c2ecf20Sopenharmony_ci * GPIO-less system, the board can be tested by connecting the respective sigs
188c2ecf20Sopenharmony_ci * up to a parallel port connector. The driver requires the IO addresses for
198c2ecf20Sopenharmony_ci * data and control GPIO at load time. It is unable to probe for the
208c2ecf20Sopenharmony_ci * existence of the LCD so it must be told at load time whether it should
218c2ecf20Sopenharmony_ci * be enabled or not.
228c2ecf20Sopenharmony_ci *
238c2ecf20Sopenharmony_ci * Todo:
248c2ecf20Sopenharmony_ci * - testing with 4x4
258c2ecf20Sopenharmony_ci * - testing with interrupt hw
268c2ecf20Sopenharmony_ci *
278c2ecf20Sopenharmony_ci * General notes:
288c2ecf20Sopenharmony_ci * - User must set tuhold. It's in microseconds. According to the 108 spec,
298c2ecf20Sopenharmony_ci *   the hold time is supposed to be at least 1 microsecond.
308c2ecf20Sopenharmony_ci * - User must set num_cols=x num_rows=y, eg: x=2 means 128
318c2ecf20Sopenharmony_ci * - User must set arcfb_enable=1 to enable it
328c2ecf20Sopenharmony_ci * - User must set dio_addr=0xIOADDR cio_addr=0xIOADDR
338c2ecf20Sopenharmony_ci *
348c2ecf20Sopenharmony_ci */
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#include <linux/module.h>
378c2ecf20Sopenharmony_ci#include <linux/kernel.h>
388c2ecf20Sopenharmony_ci#include <linux/errno.h>
398c2ecf20Sopenharmony_ci#include <linux/string.h>
408c2ecf20Sopenharmony_ci#include <linux/mm.h>
418c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
428c2ecf20Sopenharmony_ci#include <linux/delay.h>
438c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
448c2ecf20Sopenharmony_ci#include <linux/fb.h>
458c2ecf20Sopenharmony_ci#include <linux/init.h>
468c2ecf20Sopenharmony_ci#include <linux/arcfb.h>
478c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci#define floor8(a) (a&(~0x07))
528c2ecf20Sopenharmony_ci#define floorXres(a,xres) (a&(~(xres - 1)))
538c2ecf20Sopenharmony_ci#define iceil8(a) (((int)((a+7)/8))*8)
548c2ecf20Sopenharmony_ci#define ceil64(a) (a|0x3F)
558c2ecf20Sopenharmony_ci#define ceilXres(a,xres) (a|(xres - 1))
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/* ks108 chipset specific defines and code */
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci#define KS_SET_DPY_START_LINE 	0xC0
608c2ecf20Sopenharmony_ci#define KS_SET_PAGE_NUM 	0xB8
618c2ecf20Sopenharmony_ci#define KS_SET_X 		0x40
628c2ecf20Sopenharmony_ci#define KS_CEHI 		0x01
638c2ecf20Sopenharmony_ci#define KS_CELO 		0x00
648c2ecf20Sopenharmony_ci#define KS_SEL_CMD 		0x08
658c2ecf20Sopenharmony_ci#define KS_SEL_DATA 		0x00
668c2ecf20Sopenharmony_ci#define KS_DPY_ON 		0x3F
678c2ecf20Sopenharmony_ci#define KS_DPY_OFF 		0x3E
688c2ecf20Sopenharmony_ci#define KS_INTACK 		0x40
698c2ecf20Sopenharmony_ci#define KS_CLRINT		0x02
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_cistruct arcfb_par {
728c2ecf20Sopenharmony_ci	unsigned long dio_addr;
738c2ecf20Sopenharmony_ci	unsigned long cio_addr;
748c2ecf20Sopenharmony_ci	unsigned long c2io_addr;
758c2ecf20Sopenharmony_ci	atomic_t ref_count;
768c2ecf20Sopenharmony_ci	unsigned char cslut[9];
778c2ecf20Sopenharmony_ci	struct fb_info *info;
788c2ecf20Sopenharmony_ci	unsigned int irq;
798c2ecf20Sopenharmony_ci	spinlock_t lock;
808c2ecf20Sopenharmony_ci};
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic const struct fb_fix_screeninfo arcfb_fix = {
838c2ecf20Sopenharmony_ci	.id =		"arcfb",
848c2ecf20Sopenharmony_ci	.type =		FB_TYPE_PACKED_PIXELS,
858c2ecf20Sopenharmony_ci	.visual =	FB_VISUAL_MONO01,
868c2ecf20Sopenharmony_ci	.xpanstep =	0,
878c2ecf20Sopenharmony_ci	.ypanstep =	1,
888c2ecf20Sopenharmony_ci	.ywrapstep =	0,
898c2ecf20Sopenharmony_ci	.accel =	FB_ACCEL_NONE,
908c2ecf20Sopenharmony_ci};
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic const struct fb_var_screeninfo arcfb_var = {
938c2ecf20Sopenharmony_ci	.xres		= 128,
948c2ecf20Sopenharmony_ci	.yres		= 64,
958c2ecf20Sopenharmony_ci	.xres_virtual	= 128,
968c2ecf20Sopenharmony_ci	.yres_virtual	= 64,
978c2ecf20Sopenharmony_ci	.bits_per_pixel	= 1,
988c2ecf20Sopenharmony_ci	.nonstd		= 1,
998c2ecf20Sopenharmony_ci};
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic unsigned long num_cols;
1028c2ecf20Sopenharmony_cistatic unsigned long num_rows;
1038c2ecf20Sopenharmony_cistatic unsigned long dio_addr;
1048c2ecf20Sopenharmony_cistatic unsigned long cio_addr;
1058c2ecf20Sopenharmony_cistatic unsigned long c2io_addr;
1068c2ecf20Sopenharmony_cistatic unsigned long splashval;
1078c2ecf20Sopenharmony_cistatic unsigned long tuhold;
1088c2ecf20Sopenharmony_cistatic unsigned int nosplash;
1098c2ecf20Sopenharmony_cistatic unsigned int arcfb_enable;
1108c2ecf20Sopenharmony_cistatic unsigned int irq;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(arcfb_waitq);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistatic void ks108_writeb_ctl(struct arcfb_par *par,
1158c2ecf20Sopenharmony_ci				unsigned int chipindex, unsigned char value)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	unsigned char chipselval = par->cslut[chipindex];
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	outb(chipselval|KS_CEHI|KS_SEL_CMD, par->cio_addr);
1208c2ecf20Sopenharmony_ci	outb(value, par->dio_addr);
1218c2ecf20Sopenharmony_ci	udelay(tuhold);
1228c2ecf20Sopenharmony_ci	outb(chipselval|KS_CELO|KS_SEL_CMD, par->cio_addr);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic void ks108_writeb_mainctl(struct arcfb_par *par, unsigned char value)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	outb(value, par->cio_addr);
1298c2ecf20Sopenharmony_ci	udelay(tuhold);
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic unsigned char ks108_readb_ctl2(struct arcfb_par *par)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	return inb(par->c2io_addr);
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic void ks108_writeb_data(struct arcfb_par *par,
1388c2ecf20Sopenharmony_ci				unsigned int chipindex, unsigned char value)
1398c2ecf20Sopenharmony_ci{
1408c2ecf20Sopenharmony_ci	unsigned char chipselval = par->cslut[chipindex];
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	outb(chipselval|KS_CEHI|KS_SEL_DATA, par->cio_addr);
1438c2ecf20Sopenharmony_ci	outb(value, par->dio_addr);
1448c2ecf20Sopenharmony_ci	udelay(tuhold);
1458c2ecf20Sopenharmony_ci	outb(chipselval|KS_CELO|KS_SEL_DATA, par->cio_addr);
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic void ks108_set_start_line(struct arcfb_par *par,
1498c2ecf20Sopenharmony_ci				unsigned int chipindex, unsigned char y)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	ks108_writeb_ctl(par, chipindex, KS_SET_DPY_START_LINE|y);
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic void ks108_set_yaddr(struct arcfb_par *par,
1558c2ecf20Sopenharmony_ci				unsigned int chipindex, unsigned char y)
1568c2ecf20Sopenharmony_ci{
1578c2ecf20Sopenharmony_ci	ks108_writeb_ctl(par, chipindex, KS_SET_PAGE_NUM|y);
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic void ks108_set_xaddr(struct arcfb_par *par,
1618c2ecf20Sopenharmony_ci				unsigned int chipindex, unsigned char x)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	ks108_writeb_ctl(par, chipindex, KS_SET_X|x);
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic void ks108_clear_lcd(struct arcfb_par *par, unsigned int chipindex)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	int i,j;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	for (i = 0; i <= 8; i++) {
1718c2ecf20Sopenharmony_ci		ks108_set_yaddr(par, chipindex, i);
1728c2ecf20Sopenharmony_ci		ks108_set_xaddr(par, chipindex, 0);
1738c2ecf20Sopenharmony_ci		for (j = 0; j < 64; j++) {
1748c2ecf20Sopenharmony_ci			ks108_writeb_data(par, chipindex,
1758c2ecf20Sopenharmony_ci				(unsigned char) splashval);
1768c2ecf20Sopenharmony_ci		}
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci/* main arcfb functions */
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic int arcfb_open(struct fb_info *info, int user)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct arcfb_par *par = info->par;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	atomic_inc(&par->ref_count);
1878c2ecf20Sopenharmony_ci	return 0;
1888c2ecf20Sopenharmony_ci}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_cistatic int arcfb_release(struct fb_info *info, int user)
1918c2ecf20Sopenharmony_ci{
1928c2ecf20Sopenharmony_ci	struct arcfb_par *par = info->par;
1938c2ecf20Sopenharmony_ci	int count = atomic_read(&par->ref_count);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	if (!count)
1968c2ecf20Sopenharmony_ci		return -EINVAL;
1978c2ecf20Sopenharmony_ci	atomic_dec(&par->ref_count);
1988c2ecf20Sopenharmony_ci	return 0;
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_cistatic int arcfb_pan_display(struct fb_var_screeninfo *var,
2028c2ecf20Sopenharmony_ci				struct fb_info *info)
2038c2ecf20Sopenharmony_ci{
2048c2ecf20Sopenharmony_ci	int i;
2058c2ecf20Sopenharmony_ci	struct arcfb_par *par = info->par;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	if ((var->vmode & FB_VMODE_YWRAP) && (var->yoffset < 64)
2088c2ecf20Sopenharmony_ci		&& (info->var.yres <= 64)) {
2098c2ecf20Sopenharmony_ci		for (i = 0; i < num_cols; i++) {
2108c2ecf20Sopenharmony_ci			ks108_set_start_line(par, i, var->yoffset);
2118c2ecf20Sopenharmony_ci		}
2128c2ecf20Sopenharmony_ci		info->var.yoffset = var->yoffset;
2138c2ecf20Sopenharmony_ci		return 0;
2148c2ecf20Sopenharmony_ci	}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	return -EINVAL;
2178c2ecf20Sopenharmony_ci}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_cistatic irqreturn_t arcfb_interrupt(int vec, void *dev_instance)
2208c2ecf20Sopenharmony_ci{
2218c2ecf20Sopenharmony_ci	struct fb_info *info = dev_instance;
2228c2ecf20Sopenharmony_ci	unsigned char ctl2status;
2238c2ecf20Sopenharmony_ci	struct arcfb_par *par = info->par;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	ctl2status = ks108_readb_ctl2(par);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	if (!(ctl2status & KS_INTACK)) /* not arc generated interrupt */
2288c2ecf20Sopenharmony_ci		return IRQ_NONE;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	ks108_writeb_mainctl(par, KS_CLRINT);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	spin_lock(&par->lock);
2338c2ecf20Sopenharmony_ci        if (waitqueue_active(&arcfb_waitq)) {
2348c2ecf20Sopenharmony_ci                wake_up(&arcfb_waitq);
2358c2ecf20Sopenharmony_ci        }
2368c2ecf20Sopenharmony_ci	spin_unlock(&par->lock);
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2398c2ecf20Sopenharmony_ci}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci/*
2428c2ecf20Sopenharmony_ci * here we handle a specific page on the lcd. the complexity comes from
2438c2ecf20Sopenharmony_ci * the fact that the fb is laidout in 8xX vertical columns. we extract
2448c2ecf20Sopenharmony_ci * each write of 8 vertical pixels. then we shift out as we move along
2458c2ecf20Sopenharmony_ci * X. That's what rightshift does. bitmask selects the desired input bit.
2468c2ecf20Sopenharmony_ci */
2478c2ecf20Sopenharmony_cistatic void arcfb_lcd_update_page(struct arcfb_par *par, unsigned int upper,
2488c2ecf20Sopenharmony_ci		unsigned int left, unsigned int right, unsigned int distance)
2498c2ecf20Sopenharmony_ci{
2508c2ecf20Sopenharmony_ci	unsigned char *src;
2518c2ecf20Sopenharmony_ci	unsigned int xindex, yindex, chipindex, linesize;
2528c2ecf20Sopenharmony_ci	int i;
2538c2ecf20Sopenharmony_ci	unsigned char val;
2548c2ecf20Sopenharmony_ci	unsigned char bitmask, rightshift;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	xindex = left >> 6;
2578c2ecf20Sopenharmony_ci	yindex = upper >> 6;
2588c2ecf20Sopenharmony_ci	chipindex = (xindex + (yindex*num_cols));
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	ks108_set_yaddr(par, chipindex, upper/8);
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	linesize = par->info->var.xres/8;
2638c2ecf20Sopenharmony_ci	src = (unsigned char __force *) par->info->screen_base + (left/8) +
2648c2ecf20Sopenharmony_ci		(upper * linesize);
2658c2ecf20Sopenharmony_ci	ks108_set_xaddr(par, chipindex, left);
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	bitmask=1;
2688c2ecf20Sopenharmony_ci	rightshift=0;
2698c2ecf20Sopenharmony_ci	while (left <= right) {
2708c2ecf20Sopenharmony_ci		val = 0;
2718c2ecf20Sopenharmony_ci		for (i = 0; i < 8; i++) {
2728c2ecf20Sopenharmony_ci			if ( i > rightshift) {
2738c2ecf20Sopenharmony_ci				val |= (*(src + (i*linesize)) & bitmask)
2748c2ecf20Sopenharmony_ci						<< (i - rightshift);
2758c2ecf20Sopenharmony_ci			} else {
2768c2ecf20Sopenharmony_ci				val |= (*(src + (i*linesize)) & bitmask)
2778c2ecf20Sopenharmony_ci						 >> (rightshift - i);
2788c2ecf20Sopenharmony_ci			}
2798c2ecf20Sopenharmony_ci		}
2808c2ecf20Sopenharmony_ci		ks108_writeb_data(par, chipindex, val);
2818c2ecf20Sopenharmony_ci		left++;
2828c2ecf20Sopenharmony_ci		if (bitmask == 0x80) {
2838c2ecf20Sopenharmony_ci			bitmask = 1;
2848c2ecf20Sopenharmony_ci			src++;
2858c2ecf20Sopenharmony_ci			rightshift=0;
2868c2ecf20Sopenharmony_ci		} else {
2878c2ecf20Sopenharmony_ci			bitmask <<= 1;
2888c2ecf20Sopenharmony_ci			rightshift++;
2898c2ecf20Sopenharmony_ci		}
2908c2ecf20Sopenharmony_ci	}
2918c2ecf20Sopenharmony_ci}
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci/*
2948c2ecf20Sopenharmony_ci * here we handle the entire vertical page of the update. we write across
2958c2ecf20Sopenharmony_ci * lcd chips. update_page uses the upper/left values to decide which
2968c2ecf20Sopenharmony_ci * chip to select for the right. upper is needed for setting the page
2978c2ecf20Sopenharmony_ci * desired for the write.
2988c2ecf20Sopenharmony_ci */
2998c2ecf20Sopenharmony_cistatic void arcfb_lcd_update_vert(struct arcfb_par *par, unsigned int top,
3008c2ecf20Sopenharmony_ci		unsigned int bottom, unsigned int left, unsigned int right)
3018c2ecf20Sopenharmony_ci{
3028c2ecf20Sopenharmony_ci	unsigned int distance, upper, lower;
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	distance = (bottom - top) + 1;
3058c2ecf20Sopenharmony_ci	upper = top;
3068c2ecf20Sopenharmony_ci	lower = top + 7;
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	while (distance > 0) {
3098c2ecf20Sopenharmony_ci		distance -= 8;
3108c2ecf20Sopenharmony_ci		arcfb_lcd_update_page(par, upper, left, right, 8);
3118c2ecf20Sopenharmony_ci		upper = lower + 1;
3128c2ecf20Sopenharmony_ci		lower = upper + 7;
3138c2ecf20Sopenharmony_ci	}
3148c2ecf20Sopenharmony_ci}
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci/*
3178c2ecf20Sopenharmony_ci * here we handle horizontal blocks for the update. update_vert will
3188c2ecf20Sopenharmony_ci * handle spaning multiple pages. we break out each horizontal
3198c2ecf20Sopenharmony_ci * block in to individual blocks no taller than 64 pixels.
3208c2ecf20Sopenharmony_ci */
3218c2ecf20Sopenharmony_cistatic void arcfb_lcd_update_horiz(struct arcfb_par *par, unsigned int left,
3228c2ecf20Sopenharmony_ci			unsigned int right, unsigned int top, unsigned int h)
3238c2ecf20Sopenharmony_ci{
3248c2ecf20Sopenharmony_ci	unsigned int distance, upper, lower;
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	distance = h;
3278c2ecf20Sopenharmony_ci	upper = floor8(top);
3288c2ecf20Sopenharmony_ci	lower = min(upper + distance - 1, ceil64(upper));
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	while (distance > 0) {
3318c2ecf20Sopenharmony_ci		distance -= ((lower - upper) + 1 );
3328c2ecf20Sopenharmony_ci		arcfb_lcd_update_vert(par, upper, lower, left, right);
3338c2ecf20Sopenharmony_ci		upper = lower + 1;
3348c2ecf20Sopenharmony_ci		lower = min(upper + distance - 1, ceil64(upper));
3358c2ecf20Sopenharmony_ci	}
3368c2ecf20Sopenharmony_ci}
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci/*
3398c2ecf20Sopenharmony_ci * here we start the process of splitting out the fb update into
3408c2ecf20Sopenharmony_ci * individual blocks of pixels. we end up splitting into 64x64 blocks
3418c2ecf20Sopenharmony_ci * and finally down to 64x8 pages.
3428c2ecf20Sopenharmony_ci */
3438c2ecf20Sopenharmony_cistatic void arcfb_lcd_update(struct arcfb_par *par, unsigned int dx,
3448c2ecf20Sopenharmony_ci			unsigned int dy, unsigned int w, unsigned int h)
3458c2ecf20Sopenharmony_ci{
3468c2ecf20Sopenharmony_ci	unsigned int left, right, distance, y;
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci	/* align the request first */
3498c2ecf20Sopenharmony_ci	y = floor8(dy);
3508c2ecf20Sopenharmony_ci	h += dy - y;
3518c2ecf20Sopenharmony_ci	h = iceil8(h);
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	distance = w;
3548c2ecf20Sopenharmony_ci	left = dx;
3558c2ecf20Sopenharmony_ci	right = min(left + w - 1, ceil64(left));
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	while (distance > 0) {
3588c2ecf20Sopenharmony_ci		arcfb_lcd_update_horiz(par, left, right, y, h);
3598c2ecf20Sopenharmony_ci		distance -= ((right - left) + 1);
3608c2ecf20Sopenharmony_ci		left = right + 1;
3618c2ecf20Sopenharmony_ci		right = min(left + distance - 1, ceil64(left));
3628c2ecf20Sopenharmony_ci	}
3638c2ecf20Sopenharmony_ci}
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_cistatic void arcfb_fillrect(struct fb_info *info,
3668c2ecf20Sopenharmony_ci			   const struct fb_fillrect *rect)
3678c2ecf20Sopenharmony_ci{
3688c2ecf20Sopenharmony_ci	struct arcfb_par *par = info->par;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	sys_fillrect(info, rect);
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	/* update the physical lcd */
3738c2ecf20Sopenharmony_ci	arcfb_lcd_update(par, rect->dx, rect->dy, rect->width, rect->height);
3748c2ecf20Sopenharmony_ci}
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_cistatic void arcfb_copyarea(struct fb_info *info,
3778c2ecf20Sopenharmony_ci			   const struct fb_copyarea *area)
3788c2ecf20Sopenharmony_ci{
3798c2ecf20Sopenharmony_ci	struct arcfb_par *par = info->par;
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	sys_copyarea(info, area);
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	/* update the physical lcd */
3848c2ecf20Sopenharmony_ci	arcfb_lcd_update(par, area->dx, area->dy, area->width, area->height);
3858c2ecf20Sopenharmony_ci}
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_cistatic void arcfb_imageblit(struct fb_info *info, const struct fb_image *image)
3888c2ecf20Sopenharmony_ci{
3898c2ecf20Sopenharmony_ci	struct arcfb_par *par = info->par;
3908c2ecf20Sopenharmony_ci
3918c2ecf20Sopenharmony_ci	sys_imageblit(info, image);
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	/* update the physical lcd */
3948c2ecf20Sopenharmony_ci	arcfb_lcd_update(par, image->dx, image->dy, image->width,
3958c2ecf20Sopenharmony_ci				image->height);
3968c2ecf20Sopenharmony_ci}
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_cistatic int arcfb_ioctl(struct fb_info *info,
3998c2ecf20Sopenharmony_ci			  unsigned int cmd, unsigned long arg)
4008c2ecf20Sopenharmony_ci{
4018c2ecf20Sopenharmony_ci	void __user *argp = (void __user *)arg;
4028c2ecf20Sopenharmony_ci	struct arcfb_par *par = info->par;
4038c2ecf20Sopenharmony_ci	unsigned long flags;
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	switch (cmd) {
4068c2ecf20Sopenharmony_ci		case FBIO_WAITEVENT:
4078c2ecf20Sopenharmony_ci		{
4088c2ecf20Sopenharmony_ci			DEFINE_WAIT(wait);
4098c2ecf20Sopenharmony_ci			/* illegal to wait on arc if no irq will occur */
4108c2ecf20Sopenharmony_ci			if (!par->irq)
4118c2ecf20Sopenharmony_ci				return -EINVAL;
4128c2ecf20Sopenharmony_ci
4138c2ecf20Sopenharmony_ci			/* wait until the Arc has generated an interrupt
4148c2ecf20Sopenharmony_ci			 * which will wake us up */
4158c2ecf20Sopenharmony_ci			spin_lock_irqsave(&par->lock, flags);
4168c2ecf20Sopenharmony_ci			prepare_to_wait(&arcfb_waitq, &wait,
4178c2ecf20Sopenharmony_ci					TASK_INTERRUPTIBLE);
4188c2ecf20Sopenharmony_ci			spin_unlock_irqrestore(&par->lock, flags);
4198c2ecf20Sopenharmony_ci			schedule();
4208c2ecf20Sopenharmony_ci			finish_wait(&arcfb_waitq, &wait);
4218c2ecf20Sopenharmony_ci		}
4228c2ecf20Sopenharmony_ci		fallthrough;
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci		case FBIO_GETCONTROL2:
4258c2ecf20Sopenharmony_ci		{
4268c2ecf20Sopenharmony_ci			unsigned char ctl2;
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci			ctl2 = ks108_readb_ctl2(info->par);
4298c2ecf20Sopenharmony_ci			if (copy_to_user(argp, &ctl2, sizeof(ctl2)))
4308c2ecf20Sopenharmony_ci				return -EFAULT;
4318c2ecf20Sopenharmony_ci			return 0;
4328c2ecf20Sopenharmony_ci		}
4338c2ecf20Sopenharmony_ci		default:
4348c2ecf20Sopenharmony_ci			return -EINVAL;
4358c2ecf20Sopenharmony_ci	}
4368c2ecf20Sopenharmony_ci}
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci/*
4398c2ecf20Sopenharmony_ci * this is the access path from userspace. they can seek and write to
4408c2ecf20Sopenharmony_ci * the fb. it's inefficient for them to do anything less than 64*8
4418c2ecf20Sopenharmony_ci * writes since we update the lcd in each write() anyway.
4428c2ecf20Sopenharmony_ci */
4438c2ecf20Sopenharmony_cistatic ssize_t arcfb_write(struct fb_info *info, const char __user *buf,
4448c2ecf20Sopenharmony_ci			   size_t count, loff_t *ppos)
4458c2ecf20Sopenharmony_ci{
4468c2ecf20Sopenharmony_ci	/* modded from epson 1355 */
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	unsigned long p;
4498c2ecf20Sopenharmony_ci	int err=-EINVAL;
4508c2ecf20Sopenharmony_ci	unsigned int fbmemlength,x,y,w,h, bitppos, startpos, endpos, bitcount;
4518c2ecf20Sopenharmony_ci	struct arcfb_par *par;
4528c2ecf20Sopenharmony_ci	unsigned int xres;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	p = *ppos;
4558c2ecf20Sopenharmony_ci	par = info->par;
4568c2ecf20Sopenharmony_ci	xres = info->var.xres;
4578c2ecf20Sopenharmony_ci	fbmemlength = (xres * info->var.yres)/8;
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci	if (p > fbmemlength)
4608c2ecf20Sopenharmony_ci		return -ENOSPC;
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_ci	err = 0;
4638c2ecf20Sopenharmony_ci	if ((count + p) > fbmemlength) {
4648c2ecf20Sopenharmony_ci		count = fbmemlength - p;
4658c2ecf20Sopenharmony_ci		err = -ENOSPC;
4668c2ecf20Sopenharmony_ci	}
4678c2ecf20Sopenharmony_ci
4688c2ecf20Sopenharmony_ci	if (count) {
4698c2ecf20Sopenharmony_ci		char *base_addr;
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci		base_addr = (char __force *)info->screen_base;
4728c2ecf20Sopenharmony_ci		count -= copy_from_user(base_addr + p, buf, count);
4738c2ecf20Sopenharmony_ci		*ppos += count;
4748c2ecf20Sopenharmony_ci		err = -EFAULT;
4758c2ecf20Sopenharmony_ci	}
4768c2ecf20Sopenharmony_ci
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci	bitppos = p*8;
4798c2ecf20Sopenharmony_ci	startpos = floorXres(bitppos, xres);
4808c2ecf20Sopenharmony_ci	endpos = ceilXres((bitppos + (count*8)), xres);
4818c2ecf20Sopenharmony_ci	bitcount = endpos - startpos;
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	x = startpos % xres;
4848c2ecf20Sopenharmony_ci	y = startpos / xres;
4858c2ecf20Sopenharmony_ci	w = xres;
4868c2ecf20Sopenharmony_ci	h = bitcount / xres;
4878c2ecf20Sopenharmony_ci	arcfb_lcd_update(par, x, y, w, h);
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	if (count)
4908c2ecf20Sopenharmony_ci		return count;
4918c2ecf20Sopenharmony_ci	return err;
4928c2ecf20Sopenharmony_ci}
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_cistatic const struct fb_ops arcfb_ops = {
4958c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
4968c2ecf20Sopenharmony_ci	.fb_open	= arcfb_open,
4978c2ecf20Sopenharmony_ci	.fb_read        = fb_sys_read,
4988c2ecf20Sopenharmony_ci	.fb_write	= arcfb_write,
4998c2ecf20Sopenharmony_ci	.fb_release	= arcfb_release,
5008c2ecf20Sopenharmony_ci	.fb_pan_display	= arcfb_pan_display,
5018c2ecf20Sopenharmony_ci	.fb_fillrect	= arcfb_fillrect,
5028c2ecf20Sopenharmony_ci	.fb_copyarea	= arcfb_copyarea,
5038c2ecf20Sopenharmony_ci	.fb_imageblit	= arcfb_imageblit,
5048c2ecf20Sopenharmony_ci	.fb_ioctl 	= arcfb_ioctl,
5058c2ecf20Sopenharmony_ci};
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_cistatic int arcfb_probe(struct platform_device *dev)
5088c2ecf20Sopenharmony_ci{
5098c2ecf20Sopenharmony_ci	struct fb_info *info;
5108c2ecf20Sopenharmony_ci	int retval = -ENOMEM;
5118c2ecf20Sopenharmony_ci	int videomemorysize;
5128c2ecf20Sopenharmony_ci	unsigned char *videomemory;
5138c2ecf20Sopenharmony_ci	struct arcfb_par *par;
5148c2ecf20Sopenharmony_ci	int i;
5158c2ecf20Sopenharmony_ci
5168c2ecf20Sopenharmony_ci	videomemorysize = (((64*64)*num_cols)*num_rows)/8;
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci	/* We need a flat backing store for the Arc's
5198c2ecf20Sopenharmony_ci	   less-flat actual paged framebuffer */
5208c2ecf20Sopenharmony_ci	videomemory = vzalloc(videomemorysize);
5218c2ecf20Sopenharmony_ci	if (!videomemory)
5228c2ecf20Sopenharmony_ci		return retval;
5238c2ecf20Sopenharmony_ci
5248c2ecf20Sopenharmony_ci	info = framebuffer_alloc(sizeof(struct arcfb_par), &dev->dev);
5258c2ecf20Sopenharmony_ci	if (!info)
5268c2ecf20Sopenharmony_ci		goto err_fb_alloc;
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ci	info->screen_base = (char __iomem *)videomemory;
5298c2ecf20Sopenharmony_ci	info->fbops = &arcfb_ops;
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci	info->var = arcfb_var;
5328c2ecf20Sopenharmony_ci	info->fix = arcfb_fix;
5338c2ecf20Sopenharmony_ci	par = info->par;
5348c2ecf20Sopenharmony_ci	par->info = info;
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ci	if (!dio_addr || !cio_addr || !c2io_addr) {
5378c2ecf20Sopenharmony_ci		printk(KERN_WARNING "no IO addresses supplied\n");
5388c2ecf20Sopenharmony_ci		goto err_addr;
5398c2ecf20Sopenharmony_ci	}
5408c2ecf20Sopenharmony_ci	par->dio_addr = dio_addr;
5418c2ecf20Sopenharmony_ci	par->cio_addr = cio_addr;
5428c2ecf20Sopenharmony_ci	par->c2io_addr = c2io_addr;
5438c2ecf20Sopenharmony_ci	par->cslut[0] = 0x00;
5448c2ecf20Sopenharmony_ci	par->cslut[1] = 0x06;
5458c2ecf20Sopenharmony_ci	info->flags = FBINFO_FLAG_DEFAULT;
5468c2ecf20Sopenharmony_ci	spin_lock_init(&par->lock);
5478c2ecf20Sopenharmony_ci	if (irq) {
5488c2ecf20Sopenharmony_ci		par->irq = irq;
5498c2ecf20Sopenharmony_ci		if (request_irq(par->irq, &arcfb_interrupt, IRQF_SHARED,
5508c2ecf20Sopenharmony_ci				"arcfb", info)) {
5518c2ecf20Sopenharmony_ci			printk(KERN_INFO
5528c2ecf20Sopenharmony_ci				"arcfb: Failed req IRQ %d\n", par->irq);
5538c2ecf20Sopenharmony_ci			retval = -EBUSY;
5548c2ecf20Sopenharmony_ci			goto err_addr;
5558c2ecf20Sopenharmony_ci		}
5568c2ecf20Sopenharmony_ci	}
5578c2ecf20Sopenharmony_ci	retval = register_framebuffer(info);
5588c2ecf20Sopenharmony_ci	if (retval < 0)
5598c2ecf20Sopenharmony_ci		goto err_register_fb;
5608c2ecf20Sopenharmony_ci	platform_set_drvdata(dev, info);
5618c2ecf20Sopenharmony_ci	fb_info(info, "Arc frame buffer device, using %dK of video memory\n",
5628c2ecf20Sopenharmony_ci		videomemorysize >> 10);
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci	/* this inits the lcd but doesn't clear dirty pixels */
5658c2ecf20Sopenharmony_ci	for (i = 0; i < num_cols * num_rows; i++) {
5668c2ecf20Sopenharmony_ci		ks108_writeb_ctl(par, i, KS_DPY_OFF);
5678c2ecf20Sopenharmony_ci		ks108_set_start_line(par, i, 0);
5688c2ecf20Sopenharmony_ci		ks108_set_yaddr(par, i, 0);
5698c2ecf20Sopenharmony_ci		ks108_set_xaddr(par, i, 0);
5708c2ecf20Sopenharmony_ci		ks108_writeb_ctl(par, i, KS_DPY_ON);
5718c2ecf20Sopenharmony_ci	}
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_ci	/* if we were told to splash the screen, we just clear it */
5748c2ecf20Sopenharmony_ci	if (!nosplash) {
5758c2ecf20Sopenharmony_ci		for (i = 0; i < num_cols * num_rows; i++) {
5768c2ecf20Sopenharmony_ci			fb_info(info, "splashing lcd %d\n", i);
5778c2ecf20Sopenharmony_ci			ks108_set_start_line(par, i, 0);
5788c2ecf20Sopenharmony_ci			ks108_clear_lcd(par, i);
5798c2ecf20Sopenharmony_ci		}
5808c2ecf20Sopenharmony_ci	}
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_ci	return 0;
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_cierr_register_fb:
5858c2ecf20Sopenharmony_ci	free_irq(par->irq, info);
5868c2ecf20Sopenharmony_cierr_addr:
5878c2ecf20Sopenharmony_ci	framebuffer_release(info);
5888c2ecf20Sopenharmony_cierr_fb_alloc:
5898c2ecf20Sopenharmony_ci	vfree(videomemory);
5908c2ecf20Sopenharmony_ci	return retval;
5918c2ecf20Sopenharmony_ci}
5928c2ecf20Sopenharmony_ci
5938c2ecf20Sopenharmony_cistatic int arcfb_remove(struct platform_device *dev)
5948c2ecf20Sopenharmony_ci{
5958c2ecf20Sopenharmony_ci	struct fb_info *info = platform_get_drvdata(dev);
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci	if (info) {
5988c2ecf20Sopenharmony_ci		unregister_framebuffer(info);
5998c2ecf20Sopenharmony_ci		if (irq)
6008c2ecf20Sopenharmony_ci			free_irq(((struct arcfb_par *)(info->par))->irq, info);
6018c2ecf20Sopenharmony_ci		vfree((void __force *)info->screen_base);
6028c2ecf20Sopenharmony_ci		framebuffer_release(info);
6038c2ecf20Sopenharmony_ci	}
6048c2ecf20Sopenharmony_ci	return 0;
6058c2ecf20Sopenharmony_ci}
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_cistatic struct platform_driver arcfb_driver = {
6088c2ecf20Sopenharmony_ci	.probe	= arcfb_probe,
6098c2ecf20Sopenharmony_ci	.remove = arcfb_remove,
6108c2ecf20Sopenharmony_ci	.driver	= {
6118c2ecf20Sopenharmony_ci		.name	= "arcfb",
6128c2ecf20Sopenharmony_ci	},
6138c2ecf20Sopenharmony_ci};
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_cistatic struct platform_device *arcfb_device;
6168c2ecf20Sopenharmony_ci
6178c2ecf20Sopenharmony_cistatic int __init arcfb_init(void)
6188c2ecf20Sopenharmony_ci{
6198c2ecf20Sopenharmony_ci	int ret;
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_ci	if (!arcfb_enable)
6228c2ecf20Sopenharmony_ci		return -ENXIO;
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_ci	ret = platform_driver_register(&arcfb_driver);
6258c2ecf20Sopenharmony_ci	if (!ret) {
6268c2ecf20Sopenharmony_ci		arcfb_device = platform_device_alloc("arcfb", 0);
6278c2ecf20Sopenharmony_ci		if (arcfb_device) {
6288c2ecf20Sopenharmony_ci			ret = platform_device_add(arcfb_device);
6298c2ecf20Sopenharmony_ci		} else {
6308c2ecf20Sopenharmony_ci			ret = -ENOMEM;
6318c2ecf20Sopenharmony_ci		}
6328c2ecf20Sopenharmony_ci		if (ret) {
6338c2ecf20Sopenharmony_ci			platform_device_put(arcfb_device);
6348c2ecf20Sopenharmony_ci			platform_driver_unregister(&arcfb_driver);
6358c2ecf20Sopenharmony_ci		}
6368c2ecf20Sopenharmony_ci	}
6378c2ecf20Sopenharmony_ci	return ret;
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci}
6408c2ecf20Sopenharmony_ci
6418c2ecf20Sopenharmony_cistatic void __exit arcfb_exit(void)
6428c2ecf20Sopenharmony_ci{
6438c2ecf20Sopenharmony_ci	platform_device_unregister(arcfb_device);
6448c2ecf20Sopenharmony_ci	platform_driver_unregister(&arcfb_driver);
6458c2ecf20Sopenharmony_ci}
6468c2ecf20Sopenharmony_ci
6478c2ecf20Sopenharmony_cimodule_param(num_cols, ulong, 0);
6488c2ecf20Sopenharmony_ciMODULE_PARM_DESC(num_cols, "Num horiz panels, eg: 2 = 128 bit wide");
6498c2ecf20Sopenharmony_cimodule_param(num_rows, ulong, 0);
6508c2ecf20Sopenharmony_ciMODULE_PARM_DESC(num_rows, "Num vert panels, eg: 1 = 64 bit high");
6518c2ecf20Sopenharmony_cimodule_param(nosplash, uint, 0);
6528c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nosplash, "Disable doing the splash screen");
6538c2ecf20Sopenharmony_cimodule_param(arcfb_enable, uint, 0);
6548c2ecf20Sopenharmony_ciMODULE_PARM_DESC(arcfb_enable, "Enable communication with Arc board");
6558c2ecf20Sopenharmony_cimodule_param_hw(dio_addr, ulong, ioport, 0);
6568c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dio_addr, "IO address for data, eg: 0x480");
6578c2ecf20Sopenharmony_cimodule_param_hw(cio_addr, ulong, ioport, 0);
6588c2ecf20Sopenharmony_ciMODULE_PARM_DESC(cio_addr, "IO address for control, eg: 0x400");
6598c2ecf20Sopenharmony_cimodule_param_hw(c2io_addr, ulong, ioport, 0);
6608c2ecf20Sopenharmony_ciMODULE_PARM_DESC(c2io_addr, "IO address for secondary control, eg: 0x408");
6618c2ecf20Sopenharmony_cimodule_param(splashval, ulong, 0);
6628c2ecf20Sopenharmony_ciMODULE_PARM_DESC(splashval, "Splash pattern: 0xFF is black, 0x00 is green");
6638c2ecf20Sopenharmony_cimodule_param(tuhold, ulong, 0);
6648c2ecf20Sopenharmony_ciMODULE_PARM_DESC(tuhold, "Time to hold between strobing data to Arc board");
6658c2ecf20Sopenharmony_cimodule_param_hw(irq, uint, irq, 0);
6668c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ for the Arc board");
6678c2ecf20Sopenharmony_ci
6688c2ecf20Sopenharmony_cimodule_init(arcfb_init);
6698c2ecf20Sopenharmony_cimodule_exit(arcfb_exit);
6708c2ecf20Sopenharmony_ci
6718c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("fbdev driver for Arc monochrome LCD board");
6728c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jaya Kumar");
6738c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
6748c2ecf20Sopenharmony_ci
675