1/*-
2 * Copyright (c) 1999 Michael Smith <msmith@freebsd.org>
3 * Copyright (c) 1999 Kazutaka YOKOTA <yokota@freebsd.org>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 * $FreeBSD$
28 */
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/kernel.h>
33#include <sys/module.h>
34#include <sys/linker.h>
35#include <sys/fbio.h>
36
37#include <dev/fb/fbreg.h>
38#include <dev/fb/splashreg.h>
39#ifndef PC98
40#include <dev/fb/vgareg.h>
41
42#include <isa/isareg.h>
43#endif
44
45#define FADE_TIMEOUT	15	/* sec */
46#define FADE_LEVELS	10
47
48static int splash_mode = -1;
49static int splash_on = FALSE;
50
51static int bmp_start(video_adapter_t *adp);
52static int bmp_end(video_adapter_t *adp);
53static int bmp_splash(video_adapter_t *adp, int on);
54static int bmp_Init(char *data, int swidth, int sheight, int sdepth);
55static int bmp_Draw(video_adapter_t *adp);
56
57static splash_decoder_t bmp_decoder = {
58    "splash_bmp", bmp_start, bmp_end, bmp_splash, SPLASH_IMAGE,
59};
60
61SPLASH_DECODER(splash_bmp, bmp_decoder);
62
63static int
64bmp_start(video_adapter_t *adp)
65{
66    /* currently only 256-color modes are supported XXX */
67    static int		modes[] = {
68#ifdef PC98
69    			/*
70			 * As 640x400 doesn't generally look great,
71			 * it's least preferred here.
72			 */
73			M_PC98_PEGC640x400,
74			M_PC98_PEGC640x480,
75			M_PC98_EGC640x400,
76#else
77			M_VESA_CG640x480,
78			M_VESA_CG800x600,
79			M_VESA_CG1024x768,
80			M_CG640x480,
81    			/*
82			 * As 320x200 doesn't generally look great,
83			 * it's least preferred here.
84			 */
85			M_VGA_CG320,
86#endif
87			-1,
88    };
89    video_info_t 	info;
90    int			i;
91
92    if ((bmp_decoder.data == NULL) || (bmp_decoder.data_size <= 0)) {
93	printf("splash_bmp: No bitmap file found\n");
94	return ENODEV;
95    }
96    for (i = 0; modes[i] >= 0; ++i) {
97	if ((vidd_get_info(adp, modes[i], &info) == 0) &&
98	    (bmp_Init((u_char *)bmp_decoder.data, info.vi_width,
99		      info.vi_height, info.vi_depth) == 0))
100	    break;
101    }
102    splash_mode = modes[i];
103    if (splash_mode < 0)
104	printf("splash_bmp: No appropriate video mode found\n");
105    if (bootverbose)
106	printf("bmp_start(): splash_mode:%d\n", splash_mode);
107    return ((splash_mode < 0) ? ENODEV : 0);
108}
109
110static int
111bmp_end(video_adapter_t *adp)
112{
113    /* nothing to do */
114    return 0;
115}
116
117static int
118bmp_splash(video_adapter_t *adp, int on)
119{
120    static u_char	pal[256*3];
121    static long		time_stamp;
122    u_char		tpal[256*3];
123    static int		fading = TRUE, brightness = FADE_LEVELS;
124    struct timeval	tv;
125    int			i;
126
127    if (on) {
128	if (!splash_on) {
129	    /* set up the video mode and draw something */
130	    if (vidd_set_mode(adp, splash_mode))
131		return 1;
132	    if (bmp_Draw(adp))
133		return 1;
134	    vidd_save_palette(adp, pal);
135	    time_stamp = 0;
136	    splash_on = TRUE;
137	}
138	/*
139	 * This is a kludge to fade the image away.  This section of the
140	 * code takes effect only after the system is completely up.
141	 * FADE_TIMEOUT should be configurable.
142	 */
143	if (!cold) {
144	    getmicrotime(&tv);
145	    if (time_stamp == 0)
146		time_stamp = tv.tv_sec;
147	    if (tv.tv_sec > time_stamp + FADE_TIMEOUT) {
148		if (fading)
149		    if (brightness == 0) {
150			fading = FALSE;
151			brightness++;
152		    }
153		    else brightness--;
154		else
155		    if (brightness == FADE_LEVELS) {
156			fading = TRUE;
157			brightness--;
158		    }
159		    else brightness++;
160		for (i = 0; i < sizeof(pal); ++i) {
161		    tpal[i] = pal[i] * brightness / FADE_LEVELS;
162		}
163		vidd_load_palette(adp, tpal);
164		time_stamp = tv.tv_sec;
165	    }
166	}
167	return 0;
168    } else {
169	/* the video mode will be restored by the caller */
170	splash_on = FALSE;
171	return 0;
172    }
173}
174
175/*
176** Code to handle Microsoft DIB (".BMP") format images.
177**
178** Blame me (msmith@freebsd.org) if this is broken, not Soren.
179*/
180
181typedef struct tagBITMAPFILEHEADER {    /* bmfh */
182    u_short	bfType;
183    int		bfSize;
184    u_short	bfReserved1;
185    u_short	bfReserved2;
186    int		bfOffBits;
187} __packed BITMAPFILEHEADER;
188
189typedef struct tagBITMAPINFOHEADER {    /* bmih */
190    int		biSize;
191    int		biWidth;
192    int		biHeight;
193    short	biPlanes;
194    short	biBitCount;
195    int		biCompression;
196    int		biSizeImage;
197    int		biXPelsPerMeter;
198    int		biYPelsPerMeter;
199    int		biClrUsed;
200    int		biClrImportant;
201} __packed BITMAPINFOHEADER;
202
203typedef struct tagRGBQUAD {     /* rgbq */
204    u_char	rgbBlue;
205    u_char	rgbGreen;
206    u_char	rgbRed;
207    u_char	rgbReserved;
208} __packed RGBQUAD;
209
210typedef struct tagBITMAPINFO {  /* bmi */
211    BITMAPINFOHEADER	bmiHeader;
212    RGBQUAD		bmiColors[256];
213} __packed BITMAPINFO;
214
215typedef struct tagBITMAPF
216{
217    BITMAPFILEHEADER	bmfh;
218    BITMAPINFO		bmfi;
219} __packed BITMAPF;
220
221#define BI_RGB		0
222#define BI_RLE8		1
223#define BI_RLE4		2
224
225/*
226** all we actually care about the image
227*/
228typedef struct
229{
230    int		width,height;		/* image dimensions */
231    int		swidth,sheight;		/* screen dimensions for the current mode */
232    u_char	depth;			/* image depth (1, 4, 8, 24 bits) */
233    u_char	sdepth;			/* screen depth (1, 4, 8 bpp) */
234    int		ncols;			/* number of colours */
235    u_char	palette[256][3];	/* raw palette data */
236    u_char	format;			/* one of the BI_* constants above */
237    u_char	*data;			/* pointer to the raw data */
238    u_char	*index;			/* running pointer to the data while drawing */
239    u_char	*vidmem;		/* video memory allocated for drawing */
240    video_adapter_t *adp;
241    int		bank;
242#ifdef PC98
243    u_char	prev_val;
244#endif
245} BMP_INFO;
246
247static BMP_INFO bmp_info;
248
249/*
250** bmp_SetPix
251**
252** Given (info), set the pixel at (x),(y) to (val)
253**
254*/
255static void
256bmp_SetPix(BMP_INFO *info, int x, int y, u_char val)
257{
258    int		sofs, bofs;
259    int		newbank;
260
261    /*
262     * range check to avoid explosions
263     */
264    if ((x < 0) || (x >= info->swidth) || (y < 0) || (y >= info->sheight))
265	return;
266
267    /*
268     * calculate offset into video memory;
269     * because 0,0 is bottom-left for DIB, we have to convert.
270     */
271    sofs = ((info->height - (y+1) + (info->sheight - info->height) / 2)
272		* info->adp->va_line_width);
273    x += (info->swidth - info->width) / 2;
274
275    switch(info->sdepth) {
276#ifdef PC98
277    case 4:
278	sofs += (x >> 3);
279	bofs = x & 0x7;				/* offset within byte */
280
281	outb(0x7c, 0x80 | 0x40);	/* GRCG on & RMW mode */
282	if (val != info->prev_val) {
283	    outb(0x7e, (val & 1) ? 0xff : 0);	/* tile B */
284	    outb(0x7e, (val & 2) ? 0xff : 0);	/* tile R */
285	    outb(0x7e, (val & 4) ? 0xff : 0);	/* tile G */
286	    outb(0x7e, (val & 8) ? 0xff : 0);	/* tile I */
287
288	    info->prev_val = val;
289	}
290
291	*(info->vidmem+sofs) = (0x80 >> bofs);	/* write new bit */
292	outb(0x7c, 0);		/* GRCG off */
293	break;
294#else
295    case 4:
296    case 1:
297	/* EGA/VGA planar modes */
298	sofs += (x >> 3);
299	newbank = sofs/info->adp->va_window_size;
300	if (info->bank != newbank) {
301	    vidd_set_win_org(info->adp, newbank*info->adp->va_window_size);
302	    info->bank = newbank;
303	}
304	sofs %= info->adp->va_window_size;
305	bofs = x & 0x7;				/* offset within byte */
306	outw(GDCIDX, (0x8000 >> bofs) | 0x08);	/* bit mask */
307	outw(GDCIDX, (val << 8) | 0x00);	/* set/reset */
308	*(info->vidmem + sofs) ^= 0xff;		/* read-modify-write */
309	break;
310#endif
311
312    case 8:
313	sofs += x;
314	newbank = sofs/info->adp->va_window_size;
315	if (info->bank != newbank) {
316	    vidd_set_win_org(info->adp, newbank*info->adp->va_window_size);
317	    info->bank = newbank;
318	}
319	sofs %= info->adp->va_window_size;
320	*(info->vidmem+sofs) = val;
321	break;
322    }
323}
324
325/*
326** bmp_DecodeRLE4
327**
328** Given (data) pointing to a line of RLE4-format data and (line) being the starting
329** line onscreen, decode the line.
330*/
331static void
332bmp_DecodeRLE4(BMP_INFO *info, int line)
333{
334    int		count;		/* run count */
335    u_char	val;
336    int		x,y;		/* screen position */
337
338    x = 0;			/* starting position */
339    y = line;
340
341    /* loop reading data */
342    for (;;) {
343	/*
344	 * encoded mode starts with a run length, and then a byte with
345	 * two colour indexes to alternate between for the run
346	 */
347	if (*info->index) {
348	    for (count = 0; count < *info->index; count++, x++) {
349		if (count & 1) {		/* odd count, low nybble */
350		    bmp_SetPix(info, x, y, *(info->index+1) & 0x0f);
351		} else {			/* even count, high nybble */
352		    bmp_SetPix(info, x, y, (*(info->index+1) >>4) & 0x0f);
353		}
354	    }
355	    info->index += 2;
356        /*
357	 * A leading zero is an escape; it may signal the end of the
358	 * bitmap, a cursor move, or some absolute data.
359	 */
360	} else {	/* zero tag may be absolute mode or an escape */
361	    switch (*(info->index+1)) {
362	    case 0:				/* end of line */
363		info->index += 2;
364		return;
365	    case 1:				/* end of bitmap */
366		info->index = NULL;
367		return;
368	    case 2:				/* move */
369		x += *(info->index + 2);	/* new coords */
370		y += *(info->index + 3);
371		info->index += 4;
372		break;
373	    default:				/* literal bitmap data */
374		for (count = 0; count < *(info->index + 1); count++, x++) {
375		    val = *(info->index + 2 + (count / 2));	/* byte with nybbles */
376		    if (count & 1) {
377			val &= 0xf;		/* get low nybble */
378		    } else {
379			val = (val >> 4);	/* get high nybble */
380		    }
381		    bmp_SetPix(info, x, y, val);
382		}
383		/* warning, this depends on integer truncation, do not hand-optimise! */
384		info->index += 2 + ((count + 3) / 4) * 2;
385		break;
386	    }
387	}
388    }
389}
390
391/*
392** bmp_DecodeRLE8
393** Given (data) pointing to a line of RLE8-format data and (line) being the starting
394** line onscreen, decode the line.
395*/
396static void
397bmp_DecodeRLE8(BMP_INFO *info, int line)
398{
399    int		count;		/* run count */
400    int		x,y;		/* screen position */
401
402    x = 0;			/* starting position */
403    y = line;
404
405    /* loop reading data */
406    for(;;) {
407	/*
408	 * encoded mode starts with a run length, and then a byte with
409	 * two colour indexes to alternate between for the run
410	 */
411	if (*info->index) {
412	    for (count = 0; count < *info->index; count++, x++)
413		bmp_SetPix(info, x, y, *(info->index+1));
414	    info->index += 2;
415        /*
416	 * A leading zero is an escape; it may signal the end of the
417	 * bitmap, a cursor move, or some absolute data.
418	 */
419	} else {	/* zero tag may be absolute mode or an escape */
420	    switch(*(info->index+1)) {
421	    case 0:				/* end of line */
422		info->index += 2;
423		return;
424	    case 1:				/* end of bitmap */
425		info->index = NULL;
426		return;
427	    case 2:				/* move */
428		x += *(info->index + 2);	/* new coords */
429		y += *(info->index + 3);
430		info->index += 4;
431		break;
432	    default:				/* literal bitmap data */
433		for (count = 0; count < *(info->index + 1); count++, x++)
434		    bmp_SetPix(info, x, y, *(info->index + 2 + count));
435		/* must be an even count */
436		info->index += 2 + count + (count & 1);
437		break;
438	    }
439	}
440    }
441}
442
443/*
444** bmp_DecodeLine
445**
446** Given (info) pointing to an image being decoded, (line) being the line currently
447** being displayed, decode a line of data.
448*/
449static void
450bmp_DecodeLine(BMP_INFO *info, int line)
451{
452    int		x;
453    u_char	val, mask, *p;
454
455    switch(info->format) {
456    case BI_RGB:
457    	switch(info->depth) {
458	case 8:
459	    for (x = 0; x < info->width; x++, info->index++)
460		bmp_SetPix(info, x, line, *info->index);
461	    info->index += 3 - (--x % 4);
462	    break;
463	case 4:
464	    p = info->index;
465	    for (x = 0; x < info->width; x++) {
466		if (x & 1) {
467		    val = *p & 0xf;	/* get low nybble */
468		    p++;
469		} else {
470		    val = *p >> 4;	/* get high nybble */
471		}
472		bmp_SetPix(info, x, line, val);
473	    }
474	    /* warning, this depends on integer truncation, do not hand-optimise! */
475	    info->index += ((x + 7) / 8) * 4;
476	    break;
477	case 1:
478	    p = info->index;
479	    mask = 0x80;
480	    for (x = 0; x < info->width; x++) {
481		val = (*p & mask) ? 1 : 0;
482		mask >>= 1;
483		if (mask == 0) {
484		    mask = 0x80;
485		    p++;
486		}
487		bmp_SetPix(info, x, line, val);
488	    }
489	    /* warning, this depends on integer truncation, do not hand-optimise! */
490	    info->index += ((x + 31) / 32) * 4;
491	    break;
492	}
493	break;
494    case BI_RLE4:
495	bmp_DecodeRLE4(info, line);
496	break;
497    case BI_RLE8:
498	bmp_DecodeRLE8(info, line);
499	break;
500    }
501}
502
503/*
504** bmp_Init
505**
506** Given a pointer (data) to the image of a BMP file, fill in bmp_info with what
507** can be learnt from it.  Return nonzero if the file isn't usable.
508**
509** Take screen dimensions (swidth), (sheight) and (sdepth) and make sure we
510** can work with these.
511*/
512static int
513bmp_Init(char *data, int swidth, int sheight, int sdepth)
514{
515    BITMAPF	*bmf = (BITMAPF *)data;
516    int		pind;
517
518    bmp_info.data = NULL;	/* assume setup failed */
519
520    /* check file ID */
521    if (bmf->bmfh.bfType != 0x4d42) {
522	printf("splash_bmp: not a BMP file\n");
523	return(1);		/* XXX check word ordering for big-endian ports? */
524    }
525
526    /* do we understand this bitmap format? */
527    if (bmf->bmfi.bmiHeader.biSize > sizeof(bmf->bmfi.bmiHeader)) {
528	printf("splash_bmp: unsupported BMP format (size=%d)\n",
529		bmf->bmfi.bmiHeader.biSize);
530	return(1);
531    }
532
533    /* save what we know about the screen */
534    bmp_info.swidth = swidth;
535    bmp_info.sheight = sheight;
536    bmp_info.sdepth = sdepth;
537
538    /* where's the data? */
539    bmp_info.data = (u_char *)data + bmf->bmfh.bfOffBits;
540
541    /* image parameters */
542    bmp_info.width = bmf->bmfi.bmiHeader.biWidth;
543    bmp_info.height = bmf->bmfi.bmiHeader.biHeight;
544    bmp_info.depth = bmf->bmfi.bmiHeader.biBitCount;
545    bmp_info.format = bmf->bmfi.bmiHeader.biCompression;
546
547    switch(bmp_info.format) {	/* check compression format */
548    case BI_RGB:
549    case BI_RLE4:
550    case BI_RLE8:
551	break;
552    default:
553	printf("splash_bmp: unsupported compression format\n");
554	return(1);		/* unsupported compression format */
555    }
556
557    /* palette details */
558    bmp_info.ncols = (bmf->bmfi.bmiHeader.biClrUsed);
559    bzero(bmp_info.palette,sizeof(bmp_info.palette));
560    if (bmp_info.ncols == 0) {	/* uses all of them */
561	bmp_info.ncols = 1 << bmf->bmfi.bmiHeader.biBitCount;
562    }
563    if ((bmp_info.height > bmp_info.sheight) ||
564	(bmp_info.width > bmp_info.swidth) ||
565	(bmp_info.ncols > (1 << sdepth))) {
566	if (bootverbose)
567	    printf("splash_bmp: beyond screen capacity (%dx%d, %d colors)\n",
568		   bmp_info.width, bmp_info.height, bmp_info.ncols);
569	return(1);
570    }
571
572    /* read palette */
573    for (pind = 0; pind < bmp_info.ncols; pind++) {
574	bmp_info.palette[pind][0] = bmf->bmfi.bmiColors[pind].rgbRed;
575	bmp_info.palette[pind][1] = bmf->bmfi.bmiColors[pind].rgbGreen;
576	bmp_info.palette[pind][2] = bmf->bmfi.bmiColors[pind].rgbBlue;
577    }
578    return(0);
579}
580
581/*
582** bmp_Draw
583**
584** Render the image.  Return nonzero if that's not possible.
585**
586*/
587static int
588bmp_Draw(video_adapter_t *adp)
589{
590    int		line;
591#if 0
592#ifndef PC98
593    int		i;
594#endif
595#endif
596
597    if (bmp_info.data == NULL) {	/* init failed, do nothing */
598	return(1);
599    }
600
601    /* clear the screen */
602    bmp_info.vidmem = (u_char *)adp->va_window;
603    bmp_info.adp = adp;
604    vidd_clear(adp);
605    vidd_set_win_org(adp, 0);
606    bmp_info.bank = 0;
607
608    /* initialise the info structure for drawing */
609    bmp_info.index = bmp_info.data;
610#ifdef PC98
611    bmp_info.prev_val = 255;
612#endif
613
614    /* set the palette for our image */
615    vidd_load_palette(adp, (u_char *)&bmp_info.palette);
616
617#if 0
618#ifndef PC98
619    /* XXX: this is ugly, but necessary for EGA/VGA 1bpp/4bpp modes */
620    if ((adp->va_type == KD_EGA) || (adp->va_type == KD_VGA)) {
621	inb(adp->va_crtc_addr + 6);		/* reset flip-flop */
622	outb(ATC, 0x14);
623	outb(ATC, 0);
624	for (i = 0; i < 16; ++i) {
625	    outb(ATC, i);
626	    outb(ATC, i);
627	}
628	inb(adp->va_crtc_addr + 6);		/* reset flip-flop */
629	outb(ATC, 0x20);			/* enable palette */
630
631	outw(GDCIDX, 0x0f01);			/* set/reset enable */
632
633	if (bmp_info.sdepth == 1)
634	    outw(TSIDX, 0x0102);		/* unmask plane #0 */
635    }
636#endif
637#endif
638
639    for (line = 0; (line < bmp_info.height) && bmp_info.index; line++) {
640	bmp_DecodeLine(&bmp_info, line);
641    }
642    return(0);
643}
644