Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,207 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# fbdev core configuration
#
config FB_CORE
select VIDEO
tristate
config FB_NOTIFY
bool
config FB_DEVICE
bool "Provide legacy /dev/fb* device"
depends on FB_CORE
default FB
help
Say Y here if you want the legacy /dev/fb* device file and
interfaces within sysfs and procfs. It is only required if you
have userspace programs that depend on fbdev for graphics output.
This does not affect the framebuffer console. If unsure, say N.
config FB_DDC
tristate
depends on FB
select I2C_ALGOBIT
select I2C
config FB_CFB_FILLRECT
tristate
depends on FB_CORE
help
Include the cfb_fillrect function for generic software rectangle
filling. This is used by drivers that don't provide their own
(accelerated) version.
config FB_CFB_COPYAREA
tristate
depends on FB_CORE
help
Include the cfb_copyarea function for generic software area copying.
This is used by drivers that don't provide their own (accelerated)
version.
config FB_CFB_IMAGEBLIT
tristate
depends on FB_CORE
help
Include the cfb_imageblit function for generic software image
blitting. This is used by drivers that don't provide their own
(accelerated) version.
config FB_CFB_REV_PIXELS_IN_BYTE
bool
depends on FB_CORE
help
Allow I/O memory frame-buffer functions to work on displays with 1, 2
and 4 bits per pixel depths which has opposite order of pixels in
byte order to bytes in long order.
config FB_SYS_FILLRECT
tristate
depends on FB_CORE
help
Include the sys_fillrect function for generic software rectangle
filling. This is used by drivers that don't provide their own
(accelerated) version and the framebuffer is in system RAM.
config FB_SYS_COPYAREA
tristate
depends on FB_CORE
help
Include the sys_copyarea function for generic software area copying.
This is used by drivers that don't provide their own (accelerated)
version and the framebuffer is in system RAM.
config FB_SYS_IMAGEBLIT
tristate
depends on FB_CORE
help
Include the sys_imageblit function for generic software image
blitting. This is used by drivers that don't provide their own
(accelerated) version and the framebuffer is in system RAM.
config FB_SYS_REV_PIXELS_IN_BYTE
bool
depends on FB_CORE
help
Allow virtual memory frame-buffer functions to work on displays with 1, 2
and 4 bits per pixel depths which has opposite order of pixels in
byte order to bytes in long order.
config FB_PROVIDE_GET_FB_UNMAPPED_AREA
bool
depends on FB
help
Allow generic frame-buffer to provide get_fb_unmapped_area
function to provide shareable character device support on nommu.
menuconfig FB_FOREIGN_ENDIAN
bool "Framebuffer foreign endianness support"
depends on FB
help
This menu will let you enable support for the framebuffers with
non-native endianness (e.g. Little-Endian framebuffer on a
Big-Endian machine). Most probably you don't have such hardware,
so it's safe to say "n" here.
choice
prompt "Choice endianness support"
depends on FB_FOREIGN_ENDIAN
config FB_BOTH_ENDIAN
bool "Support for Big- and Little-Endian framebuffers"
config FB_BIG_ENDIAN
bool "Support for Big-Endian framebuffers only"
config FB_LITTLE_ENDIAN
bool "Support for Little-Endian framebuffers only"
endchoice
config FB_SYSMEM_FOPS
tristate
depends on FB_CORE
config FB_DEFERRED_IO
bool
depends on FB_CORE
config FB_DMAMEM_HELPERS
bool
depends on FB_CORE
select FB_SYS_COPYAREA
select FB_SYS_FILLRECT
select FB_SYS_IMAGEBLIT
select FB_SYSMEM_FOPS
config FB_DMAMEM_HELPERS_DEFERRED
bool
depends on FB_CORE
select FB_DEFERRED_IO
select FB_DMAMEM_HELPERS
config FB_IOMEM_FOPS
tristate
depends on FB_CORE
config FB_IOMEM_HELPERS
bool
depends on FB_CORE
select FB_CFB_COPYAREA
select FB_CFB_FILLRECT
select FB_CFB_IMAGEBLIT
select FB_IOMEM_FOPS
config FB_IOMEM_HELPERS_DEFERRED
bool
depends on FB_CORE
select FB_DEFERRED_IO
select FB_IOMEM_HELPERS
config FB_SYSMEM_HELPERS
bool
depends on FB_CORE
select FB_SYS_COPYAREA
select FB_SYS_FILLRECT
select FB_SYS_IMAGEBLIT
select FB_SYSMEM_FOPS
config FB_SYSMEM_HELPERS_DEFERRED
bool
depends on FB_CORE
select FB_DEFERRED_IO
select FB_SYSMEM_HELPERS
config FB_BACKLIGHT
bool
depends on FB
config FB_MODE_HELPERS
bool
depends on FB
help
This enables functions for handling video modes using the
Generalized Timing Formula and the EDID parser. A few drivers rely
on this feature such as the radeonfb, rivafb, and the i810fb. If
your driver does not take advantage of this feature, choosing Y will
just increase the kernel size by about 5K.
config FB_TILEBLITTING
bool "Enable Tile Blitting Support"
depends on FB
help
This enables tile blitting. Tile blitting is a drawing technique
where the screen is divided into rectangular sections (tiles), whereas
the standard blitting divides the screen into pixels. Because the
default drawing element is a tile, drawing functions will be passed
parameters in terms of number of tiles instead of number of pixels.
For example, to draw a single character, instead of using bitmaps,
an index to an array of bitmaps will be used. To clear or move a
rectangular section of a screen, the rectangle will be described in
terms of number of tiles in the x- and y-axis.
This is particularly important to one driver, matroxfb. If
unsure, say N.
@@ -0,0 +1,37 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_FB_NOTIFY) += fb_notify.o
obj-$(CONFIG_FB_CORE) += fb.o
fb-y := fb_info.o \
fbmem.o fbcmap.o \
modedb.o fbcvt.o fb_cmdline.o
ifdef CONFIG_FB
fb-y += fb_backlight.o fbmon.o
endif
fb-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o
fb-$(CONFIG_FB_DEVICE) += fb_chrdev.o \
fb_procfs.o \
fbsysfs.o
ifeq ($(CONFIG_FRAMEBUFFER_CONSOLE),y)
fb-y += fbcon.o bitblit.o softcursor.o
ifeq ($(CONFIG_FB_TILEBLITTING),y)
fb-y += tileblit.o
endif
ifeq ($(CONFIG_FRAMEBUFFER_CONSOLE_ROTATION),y)
fb-y += fbcon_rotate.o fbcon_cw.o fbcon_ud.o \
fbcon_ccw.o
endif
endif
fb-$(CONFIG_LOGO) += fb_logo.o
obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o
obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o
obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.o
obj-$(CONFIG_FB_IOMEM_FOPS) += fb_io_fops.o
obj-$(CONFIG_FB_SYS_FILLRECT) += sysfillrect.o
obj-$(CONFIG_FB_SYS_COPYAREA) += syscopyarea.o
obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o
obj-$(CONFIG_FB_SYSMEM_FOPS) += fb_sys_fops.o
obj-$(CONFIG_FB_SVGALIB) += svgalib.o
obj-$(CONFIG_FB_DDC) += fb_ddc.o
@@ -0,0 +1,399 @@
/*
* linux/drivers/video/console/bitblit.c -- BitBlitting Operation
*
* Originally from the 'accel_*' routines in drivers/video/console/fbcon.c
*
* Copyright (C) 2004 Antonino Daplas <adaplas @pol.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/vt_kern.h>
#include <linux/console.h>
#include <asm/types.h>
#include "fbcon.h"
/*
* Accelerated handlers.
*/
static void update_attr(u8 *dst, const u8 *src, int attribute, struct vc_data *vc)
{
int i, offset = (vc->vc_font.height < 10) ? 1 : 2;
int width = DIV_ROUND_UP(vc->vc_font.width, 8);
unsigned int cellsize = vc->vc_font.height * width;
u8 c;
offset = cellsize - (offset * width);
for (i = 0; i < cellsize; i++) {
c = src[i];
if (attribute & FBCON_ATTRIBUTE_UNDERLINE && i >= offset)
c = 0xff;
if (attribute & FBCON_ATTRIBUTE_BOLD)
c |= c >> 1;
if (attribute & FBCON_ATTRIBUTE_REVERSE)
c = ~c;
dst[i] = c;
}
}
static void bit_bmove(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int dy, int dx, int height, int width)
{
struct fb_copyarea area;
area.sx = sx * vc->vc_font.width;
area.sy = sy * vc->vc_font.height;
area.dx = dx * vc->vc_font.width;
area.dy = dy * vc->vc_font.height;
area.height = height * vc->vc_font.height;
area.width = width * vc->vc_font.width;
info->fbops->fb_copyarea(info, &area);
}
static void bit_clear(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int height, int width, int fg, int bg)
{
struct fb_fillrect region;
region.color = bg;
region.dx = sx * vc->vc_font.width;
region.dy = sy * vc->vc_font.height;
region.width = width * vc->vc_font.width;
region.height = height * vc->vc_font.height;
region.rop = ROP_COPY;
info->fbops->fb_fillrect(info, &region);
}
static inline void bit_putcs_aligned(struct vc_data *vc, struct fb_info *info,
const u16 *s, u32 attr, u32 cnt,
u32 d_pitch, u32 s_pitch, u32 cellsize,
struct fb_image *image, u8 *buf, u8 *dst)
{
u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
unsigned int charcnt = vc->vc_font.charcount;
u32 idx = vc->vc_font.width >> 3;
const u8 *src;
while (cnt--) {
u16 ch = scr_readw(s++) & charmask;
if (ch >= charcnt)
ch = 0;
src = vc->vc_font.data + (unsigned int)ch * cellsize;
if (attr) {
update_attr(buf, src, attr, vc);
src = buf;
}
if (likely(idx == 1))
__fb_pad_aligned_buffer(dst, d_pitch, src, idx,
image->height);
else
fb_pad_aligned_buffer(dst, d_pitch, src, idx,
image->height);
dst += s_pitch;
}
info->fbops->fb_imageblit(info, image);
}
static inline void bit_putcs_unaligned(struct vc_data *vc,
struct fb_info *info, const u16 *s,
u32 attr, u32 cnt, u32 d_pitch,
u32 s_pitch, u32 cellsize,
struct fb_image *image, u8 *buf,
u8 *dst)
{
u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
unsigned int charcnt = vc->vc_font.charcount;
u32 shift_low = 0, mod = vc->vc_font.width % 8;
u32 shift_high = 8;
u32 idx = vc->vc_font.width >> 3;
const u8 *src;
while (cnt--) {
u16 ch = scr_readw(s++) & charmask;
if (ch >= charcnt)
ch = 0;
src = vc->vc_font.data + (unsigned int)ch * cellsize;
if (attr) {
update_attr(buf, src, attr, vc);
src = buf;
}
fb_pad_unaligned_buffer(dst, d_pitch, src, idx,
image->height, shift_high,
shift_low, mod);
shift_low += mod;
dst += (shift_low >= 8) ? s_pitch : s_pitch - 1;
shift_low &= 7;
shift_high = 8 - shift_low;
}
info->fbops->fb_imageblit(info, image);
}
static void bit_putcs(struct vc_data *vc, struct fb_info *info,
const unsigned short *s, int count, int yy, int xx,
int fg, int bg)
{
struct fb_image image;
u32 width = DIV_ROUND_UP(vc->vc_font.width, 8);
u32 cellsize = width * vc->vc_font.height;
u32 maxcnt = info->pixmap.size/cellsize;
u32 scan_align = info->pixmap.scan_align - 1;
u32 buf_align = info->pixmap.buf_align - 1;
u32 mod = vc->vc_font.width % 8, cnt, pitch, size;
u32 attribute = get_attribute(info, scr_readw(s));
u8 *dst, *buf = NULL;
image.fg_color = fg;
image.bg_color = bg;
image.dx = xx * vc->vc_font.width;
image.dy = yy * vc->vc_font.height;
image.height = vc->vc_font.height;
image.depth = 1;
if (image.dy >= info->var.yres)
return;
image.height = min(image.height, info->var.yres - image.dy);
if (attribute) {
buf = kmalloc(cellsize, GFP_ATOMIC);
if (!buf)
return;
}
while (count) {
if (count > maxcnt)
cnt = maxcnt;
else
cnt = count;
image.width = vc->vc_font.width * cnt;
if (image.dx >= info->var.xres)
break;
if (image.dx + image.width > info->var.xres) {
image.width = info->var.xres - image.dx;
cnt = image.width / vc->vc_font.width;
if (cnt == 0)
break;
image.width = cnt * vc->vc_font.width;
}
pitch = DIV_ROUND_UP(image.width, 8) + scan_align;
pitch &= ~scan_align;
size = pitch * image.height + buf_align;
size &= ~buf_align;
dst = fb_get_buffer_offset(info, &info->pixmap, size);
image.data = dst;
if (!mod)
bit_putcs_aligned(vc, info, s, attribute, cnt, pitch,
width, cellsize, &image, buf, dst);
else
bit_putcs_unaligned(vc, info, s, attribute, cnt,
pitch, width, cellsize, &image,
buf, dst);
image.dx += cnt * vc->vc_font.width;
count -= cnt;
s += cnt;
}
/* buf is always NULL except when in monochrome mode, so in this case
it's a gain to check buf against NULL even though kfree() handles
NULL pointers just fine */
if (unlikely(buf))
kfree(buf);
}
static void bit_clear_margins(struct vc_data *vc, struct fb_info *info,
int color, int bottom_only)
{
unsigned int cw = vc->vc_font.width;
unsigned int ch = vc->vc_font.height;
unsigned int rw = info->var.xres - (vc->vc_cols*cw);
unsigned int bh = info->var.yres - (vc->vc_rows*ch);
unsigned int rs = info->var.xres - rw;
unsigned int bs = info->var.yres - bh;
struct fb_fillrect region;
region.color = color;
region.rop = ROP_COPY;
if ((int) rw > 0 && !bottom_only) {
region.dx = info->var.xoffset + rs;
region.dy = 0;
region.width = rw;
region.height = info->var.yres_virtual;
info->fbops->fb_fillrect(info, &region);
}
if ((int) bh > 0) {
region.dx = info->var.xoffset;
region.dy = info->var.yoffset + bs;
region.width = rs;
region.height = bh;
info->fbops->fb_fillrect(info, &region);
}
}
static void bit_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
int fg, int bg)
{
struct fb_cursor cursor;
struct fbcon_par *par = info->fbcon_par;
unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
int w = DIV_ROUND_UP(vc->vc_font.width, 8), c;
int y = real_y(par->p, vc->state.y);
int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
int err = 1;
const u8 *src;
cursor.set = 0;
if (!vc->vc_font.data)
return;
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
c &= charmask;
/* Clamp to font size, same as bit_putcs_aligned() */
if (c >= vc->vc_font.charcount)
c = 0;
src = vc->vc_font.data + (c * (w * vc->vc_font.height));
if (par->cursor_state.image.data != (const char *)src ||
par->cursor_reset) {
par->cursor_state.image.data = src;
cursor.set |= FB_CUR_SETIMAGE;
}
if (attribute) {
u8 *dst;
dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
if (!dst)
return;
kfree(par->cursor_data);
par->cursor_data = dst;
update_attr(dst, src, attribute, vc);
src = dst;
}
if (par->cursor_state.image.fg_color != fg ||
par->cursor_state.image.bg_color != bg ||
par->cursor_reset) {
par->cursor_state.image.fg_color = fg;
par->cursor_state.image.bg_color = bg;
cursor.set |= FB_CUR_SETCMAP;
}
if ((par->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) ||
(par->cursor_state.image.dy != (vc->vc_font.height * y)) ||
par->cursor_reset) {
par->cursor_state.image.dx = vc->vc_font.width * vc->state.x;
par->cursor_state.image.dy = vc->vc_font.height * y;
cursor.set |= FB_CUR_SETPOS;
}
if (par->cursor_state.image.height != vc->vc_font.height ||
par->cursor_state.image.width != vc->vc_font.width ||
par->cursor_reset) {
par->cursor_state.image.height = vc->vc_font.height;
par->cursor_state.image.width = vc->vc_font.width;
cursor.set |= FB_CUR_SETSIZE;
}
if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
par->cursor_reset) {
par->cursor_state.hot.x = cursor.hot.y = 0;
cursor.set |= FB_CUR_SETHOT;
}
if (cursor.set & FB_CUR_SETSIZE ||
vc->vc_cursor_type != par->p->cursor_shape ||
par->cursor_state.mask == NULL ||
par->cursor_reset) {
unsigned char *mask = kmalloc_array(vc->vc_font.height, w, GFP_ATOMIC);
if (!mask)
return;
fbcon_fill_cursor_mask(par, vc, mask);
kfree(par->cursor_state.mask);
par->cursor_state.mask = (const char *)mask;
par->p->cursor_shape = vc->vc_cursor_type;
cursor.set |= FB_CUR_SETSHAPE;
}
par->cursor_state.enable = enable && !use_sw;
cursor.image.data = src;
cursor.image.fg_color = par->cursor_state.image.fg_color;
cursor.image.bg_color = par->cursor_state.image.bg_color;
cursor.image.dx = par->cursor_state.image.dx;
cursor.image.dy = par->cursor_state.image.dy;
cursor.image.height = par->cursor_state.image.height;
cursor.image.width = par->cursor_state.image.width;
cursor.hot.x = par->cursor_state.hot.x;
cursor.hot.y = par->cursor_state.hot.y;
cursor.mask = par->cursor_state.mask;
cursor.enable = par->cursor_state.enable;
cursor.image.depth = 1;
cursor.rop = ROP_XOR;
if (info->fbops->fb_cursor)
err = info->fbops->fb_cursor(info, &cursor);
if (err)
soft_cursor(info, &cursor);
par->cursor_reset = 0;
}
static int bit_update_start(struct fb_info *info)
{
struct fbcon_par *par = info->fbcon_par;
int err;
err = fb_pan_display(info, &par->var);
par->var.xoffset = info->var.xoffset;
par->var.yoffset = info->var.yoffset;
par->var.vmode = info->var.vmode;
return err;
}
static const struct fbcon_bitops bit_fbcon_bitops = {
.bmove = bit_bmove,
.clear = bit_clear,
.putcs = bit_putcs,
.clear_margins = bit_clear_margins,
.cursor = bit_cursor,
.update_start = bit_update_start,
};
void fbcon_set_bitops_ur(struct fbcon_par *par)
{
par->bitops = &bit_fbcon_bitops;
}
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/bitrev.h>
#include <asm/types.h>
#ifdef CONFIG_FB_CFB_REV_PIXELS_IN_BYTE
#define FB_REV_PIXELS_IN_BYTE
#endif
#include "cfbmem.h"
#include "fb_copyarea.h"
void cfb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
{
if (p->state != FBINFO_STATE_RUNNING)
return;
if (p->flags & FBINFO_VIRTFB)
fb_warn_once(p, "%s: framebuffer is not in I/O address space.\n", __func__);
if (p->fbops->fb_sync)
p->fbops->fb_sync(p);
fb_copyarea(p, area);
}
EXPORT_SYMBOL(cfb_copyarea);
MODULE_AUTHOR("Zsolt Kajtar <soci@c64.rulez.org>");
MODULE_DESCRIPTION("I/O memory packed pixel framebuffer area copy");
MODULE_LICENSE("GPL");
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/bitrev.h>
#include <asm/types.h>
#ifdef CONFIG_FB_CFB_REV_PIXELS_IN_BYTE
#define FB_REV_PIXELS_IN_BYTE
#endif
#include "cfbmem.h"
#include "fb_fillrect.h"
void cfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
{
if (p->state != FBINFO_STATE_RUNNING)
return;
if (p->flags & FBINFO_VIRTFB)
fb_warn_once(p, "%s: framebuffer is not in I/O address space.\n", __func__);
if (p->fbops->fb_sync)
p->fbops->fb_sync(p);
fb_fillrect(p, rect);
}
EXPORT_SYMBOL(cfb_fillrect);
MODULE_AUTHOR("Zsolt Kajtar <soci@c64.rulez.org>");
MODULE_DESCRIPTION("I/O memory packed pixel framebuffer area fill");
MODULE_LICENSE("GPL");
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/bitrev.h>
#include <asm/types.h>
#ifdef CONFIG_FB_CFB_REV_PIXELS_IN_BYTE
#define FB_REV_PIXELS_IN_BYTE
#endif
#include "cfbmem.h"
#include "fb_imageblit.h"
void cfb_imageblit(struct fb_info *p, const struct fb_image *image)
{
if (p->state != FBINFO_STATE_RUNNING)
return;
if (p->flags & FBINFO_VIRTFB)
fb_warn_once(p, "%s: framebuffer is not in I/O address space.\n", __func__);
if (p->fbops->fb_sync)
p->fbops->fb_sync(p);
fb_imageblit(p, image);
}
EXPORT_SYMBOL(cfb_imageblit);
MODULE_AUTHOR("Zsolt Kajtar <soci@c64.rulez.org>");
MODULE_DESCRIPTION("I/O memory packed pixel framebuffer image draw");
MODULE_LICENSE("GPL");
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* I/O memory framebuffer access for drawing routines
*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
/* keeps track of a bit address in framebuffer memory */
struct fb_address {
void __iomem *address;
int bits;
};
/* initialize the bit address pointer to the beginning of the frame buffer */
static inline struct fb_address fb_address_init(struct fb_info *p)
{
void __iomem *base = p->screen_base;
struct fb_address ptr;
ptr.address = PTR_ALIGN_DOWN(base, BITS_PER_LONG / BITS_PER_BYTE);
ptr.bits = (base - ptr.address) * BITS_PER_BYTE;
return ptr;
}
/* framebuffer write access */
static inline void fb_write_offset(unsigned long val, int offset, const struct fb_address *dst)
{
#if BITS_PER_LONG == 32
fb_writel(val, dst->address + offset * (BITS_PER_LONG / BITS_PER_BYTE));
#else
fb_writeq(val, dst->address + offset * (BITS_PER_LONG / BITS_PER_BYTE));
#endif
}
/* framebuffer read access */
static inline unsigned long fb_read_offset(int offset, const struct fb_address *src)
{
#if BITS_PER_LONG == 32
return fb_readl(src->address + offset * (BITS_PER_LONG / BITS_PER_BYTE));
#else
return fb_readq(src->address + offset * (BITS_PER_LONG / BITS_PER_BYTE));
#endif
}
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/backlight.h>
#include <linux/export.h>
#include <linux/fb.h>
#include <linux/mutex.h>
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
/*
* This function generates a linear backlight curve
*
* 0: off
* 1-7: min
* 8-127: linear from min to max
*/
void fb_bl_default_curve(struct fb_info *fb_info, u8 off, u8 min, u8 max)
{
unsigned int i, flat, count, range = (max - min);
mutex_lock(&fb_info->bl_curve_mutex);
fb_info->bl_curve[0] = off;
for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat)
fb_info->bl_curve[flat] = min;
count = FB_BACKLIGHT_LEVELS * 15 / 16;
for (i = 0; i < count; ++i)
fb_info->bl_curve[flat + i] = min + (range * (i + 1) / count);
mutex_unlock(&fb_info->bl_curve_mutex);
}
EXPORT_SYMBOL_GPL(fb_bl_default_curve);
struct backlight_device *fb_bl_device(struct fb_info *info)
{
return info->bl_dev;
}
EXPORT_SYMBOL(fb_bl_device);
void fb_bl_notify_blank(struct fb_info *info, int old_blank)
{
bool on = info->blank == FB_BLANK_UNBLANK;
bool prev_on = old_blank == FB_BLANK_UNBLANK;
if (info->bl_dev)
backlight_notify_blank(info->bl_dev, info->device, on, prev_on);
else
backlight_notify_blank_all(info->device, on, prev_on);
}
#endif
@@ -0,0 +1,438 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compat.h>
#include <linux/console.h>
#include <linux/fb.h>
#include <linux/major.h>
#include "fb_internal.h"
#include "fbcon.h"
/*
* We hold a reference to the fb_info in file->private_data,
* but if the current registered fb has changed, we don't
* actually want to use it.
*
* So look up the fb_info using the inode minor number,
* and just verify it against the reference we have.
*/
static struct fb_info *file_fb_info(struct file *file)
{
struct inode *inode = file_inode(file);
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
if (info != file->private_data)
info = NULL;
return info;
}
static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct fb_info *info = file_fb_info(file);
if (!info)
return -ENODEV;
if (fb_WARN_ON_ONCE(info, !info->fbops->fb_read))
return -EINVAL;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
return info->fbops->fb_read(info, buf, count, ppos);
}
static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
struct fb_info *info = file_fb_info(file);
if (!info)
return -ENODEV;
if (fb_WARN_ON_ONCE(info, !info->fbops->fb_write))
return -EINVAL;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
return info->fbops->fb_write(info, buf, count, ppos);
}
static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
const struct fb_ops *fb;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_cmap cmap_from;
struct fb_cmap_user cmap;
void __user *argp = (void __user *)arg;
long ret = 0;
switch (cmd) {
case FBIOGET_VSCREENINFO:
lock_fb_info(info);
var = info->var;
unlock_fb_info(info);
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
break;
case FBIOPUT_VSCREENINFO:
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
/* only for kernel-internal use */
var.activate &= ~FB_ACTIVATE_KD_TEXT;
console_lock();
lock_fb_info(info);
ret = fb_set_var_from_user(info, &var);
unlock_fb_info(info);
console_unlock();
if (!ret && copy_to_user(argp, &var, sizeof(var)))
ret = -EFAULT;
break;
case FBIOGET_FSCREENINFO:
lock_fb_info(info);
memcpy(&fix, &info->fix, sizeof(fix));
if (info->flags & FBINFO_HIDE_SMEM_START)
fix.smem_start = 0;
unlock_fb_info(info);
ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
break;
case FBIOPUTCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
ret = fb_set_user_cmap(&cmap, info);
break;
case FBIOGETCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap)))
return -EFAULT;
lock_fb_info(info);
cmap_from = info->cmap;
unlock_fb_info(info);
ret = fb_cmap_to_user(&cmap_from, &cmap);
break;
case FBIOPAN_DISPLAY:
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
console_lock();
lock_fb_info(info);
ret = fb_pan_display(info, &var);
unlock_fb_info(info);
console_unlock();
if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
break;
case FBIO_CURSOR:
ret = -EINVAL;
break;
case FBIOGET_CON2FBMAP:
ret = fbcon_get_con2fb_map_ioctl(argp);
break;
case FBIOPUT_CON2FBMAP:
ret = fbcon_set_con2fb_map_ioctl(argp);
break;
case FBIOBLANK:
if (arg > FB_BLANK_POWERDOWN)
return -EINVAL;
console_lock();
lock_fb_info(info);
ret = fb_blank_from_user(info, arg);
unlock_fb_info(info);
console_unlock();
break;
default:
lock_fb_info(info);
fb = info->fbops;
if (fb->fb_ioctl)
ret = fb->fb_ioctl(info, cmd, arg);
else
ret = -ENOTTY;
unlock_fb_info(info);
}
return ret;
}
static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct fb_info *info = file_fb_info(file);
if (!info)
return -ENODEV;
return do_fb_ioctl(info, cmd, arg);
}
#ifdef CONFIG_COMPAT
struct fb_fix_screeninfo32 {
char id[16];
compat_caddr_t smem_start;
u32 smem_len;
u32 type;
u32 type_aux;
u32 visual;
u16 xpanstep;
u16 ypanstep;
u16 ywrapstep;
u32 line_length;
compat_caddr_t mmio_start;
u32 mmio_len;
u32 accel;
u16 reserved[3];
};
struct fb_cmap32 {
u32 start;
u32 len;
compat_caddr_t red;
compat_caddr_t green;
compat_caddr_t blue;
compat_caddr_t transp;
};
static int fb_getput_cmap(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
struct fb_cmap32 cmap32;
struct fb_cmap cmap_from;
struct fb_cmap_user cmap;
if (copy_from_user(&cmap32, compat_ptr(arg), sizeof(cmap32)))
return -EFAULT;
cmap = (struct fb_cmap_user) {
.start = cmap32.start,
.len = cmap32.len,
.red = compat_ptr(cmap32.red),
.green = compat_ptr(cmap32.green),
.blue = compat_ptr(cmap32.blue),
.transp = compat_ptr(cmap32.transp),
};
if (cmd == FBIOPUTCMAP)
return fb_set_user_cmap(&cmap, info);
lock_fb_info(info);
cmap_from = info->cmap;
unlock_fb_info(info);
return fb_cmap_to_user(&cmap_from, &cmap);
}
static int do_fscreeninfo_to_user(struct fb_fix_screeninfo *fix,
struct fb_fix_screeninfo32 __user *fix32)
{
__u32 data;
int err;
err = copy_to_user(&fix32->id, &fix->id, sizeof(fix32->id));
data = (__u32) (unsigned long) fix->smem_start;
err |= put_user(data, &fix32->smem_start);
err |= put_user(fix->smem_len, &fix32->smem_len);
err |= put_user(fix->type, &fix32->type);
err |= put_user(fix->type_aux, &fix32->type_aux);
err |= put_user(fix->visual, &fix32->visual);
err |= put_user(fix->xpanstep, &fix32->xpanstep);
err |= put_user(fix->ypanstep, &fix32->ypanstep);
err |= put_user(fix->ywrapstep, &fix32->ywrapstep);
err |= put_user(fix->line_length, &fix32->line_length);
data = (__u32) (unsigned long) fix->mmio_start;
err |= put_user(data, &fix32->mmio_start);
err |= put_user(fix->mmio_len, &fix32->mmio_len);
err |= put_user(fix->accel, &fix32->accel);
err |= copy_to_user(fix32->reserved, fix->reserved,
sizeof(fix->reserved));
if (err)
return -EFAULT;
return 0;
}
static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
struct fb_fix_screeninfo fix;
lock_fb_info(info);
fix = info->fix;
if (info->flags & FBINFO_HIDE_SMEM_START)
fix.smem_start = 0;
unlock_fb_info(info);
return do_fscreeninfo_to_user(&fix, compat_ptr(arg));
}
static long fb_compat_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct fb_info *info = file_fb_info(file);
const struct fb_ops *fb;
long ret = -ENOIOCTLCMD;
if (!info)
return -ENODEV;
fb = info->fbops;
switch (cmd) {
case FBIOGET_VSCREENINFO:
case FBIOPUT_VSCREENINFO:
case FBIOPAN_DISPLAY:
case FBIOGET_CON2FBMAP:
case FBIOPUT_CON2FBMAP:
arg = (unsigned long) compat_ptr(arg);
fallthrough;
case FBIOBLANK:
ret = do_fb_ioctl(info, cmd, arg);
break;
case FBIOGET_FSCREENINFO:
ret = fb_get_fscreeninfo(info, cmd, arg);
break;
case FBIOGETCMAP:
case FBIOPUTCMAP:
ret = fb_getput_cmap(info, cmd, arg);
break;
default:
if (fb->fb_compat_ioctl)
ret = fb->fb_compat_ioctl(info, cmd, arg);
break;
}
return ret;
}
#endif
static int fb_mmap(struct file *file, struct vm_area_struct *vma)
{
struct fb_info *info = file_fb_info(file);
int res;
if (!info)
return -ENODEV;
if (fb_WARN_ON_ONCE(info, !info->fbops->fb_mmap))
return -ENODEV;
mutex_lock(&info->mm_lock);
res = info->fbops->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
}
static int fb_open(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
info = get_fb_info(fbidx);
if (!info) {
request_module("fb%d", fbidx);
info = get_fb_info(fbidx);
if (!info)
return -ENODEV;
}
if (IS_ERR(info))
return PTR_ERR(info);
lock_fb_info(info);
if (!try_module_get(info->fbops->owner)) {
res = -ENODEV;
goto out;
}
file->private_data = info;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info, 1);
if (res)
module_put(info->fbops->owner);
}
#ifdef CONFIG_FB_DEFERRED_IO
if (info->fbdefio)
fb_deferred_io_open(info, inode, file);
#endif
out:
unlock_fb_info(info);
if (res)
put_fb_info(info);
return res;
}
static int fb_release(struct inode *inode, struct file *file)
__acquires(&info->lock)
__releases(&info->lock)
{
struct fb_info * const info = file->private_data;
lock_fb_info(info);
#if IS_ENABLED(CONFIG_FB_DEFERRED_IO)
if (info->fbdefio)
fb_deferred_io_release(info);
#endif
if (info->fbops->fb_release)
info->fbops->fb_release(info, 1);
module_put(info->fbops->owner);
unlock_fb_info(info);
put_fb_info(info);
return 0;
}
#if defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && !defined(CONFIG_MMU)
static unsigned long get_fb_unmapped_area(struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags)
{
struct fb_info * const info = filp->private_data;
unsigned long fb_size = PAGE_ALIGN(info->fix.smem_len);
if (pgoff > fb_size || len > fb_size - pgoff)
return -EINVAL;
return (unsigned long)info->screen_base + pgoff;
}
#endif
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \
(defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \
!defined(CONFIG_MMU))
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};
int fb_register_chrdev(void)
{
int ret;
ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
if (ret) {
pr_err("Unable to get major %d for fb devs\n", FB_MAJOR);
return ret;
}
return ret;
}
void fb_unregister_chrdev(void)
{
unregister_chrdev(FB_MAJOR, "fb");
}
@@ -0,0 +1,61 @@
/*
* linux/drivers/video/fb_cmdline.c
*
* Copyright (C) 2014 Intel Corp
* Copyright (C) 1994 Martin Schaller
*
* 2001 - Documented with DocBook
* - Brad Douglas <brad@neruo.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*
* Authors:
* Daniel Vetter <daniel.vetter@ffwll.ch>
*/
#include <linux/export.h>
#include <linux/fb.h>
#include <linux/string.h>
#include <video/cmdline.h>
/**
* fb_get_options - get kernel boot parameters
* @name: framebuffer name as it would appear in
* the boot parameter line
* (video=<name>:<options>)
* @option: the option will be stored here
*
* The caller owns the string returned in @option and is
* responsible for releasing the memory.
*
* NOTE: Needed to maintain backwards compatibility
*/
int fb_get_options(const char *name, char **option)
{
const char *options = NULL;
bool is_of = false;
bool enabled;
if (name)
is_of = !strncmp(name, "offb", 4);
enabled = __video_get_options(name, &options, is_of);
if (options) {
if (!strncmp(options, "off", 3))
enabled = false;
}
if (option) {
if (options)
*option = kstrdup(options, GFP_KERNEL);
else
*option = NULL;
}
return enabled ? 0 : 1; // 0 on success, 1 otherwise
}
EXPORT_SYMBOL(fb_get_options);
@@ -0,0 +1,405 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Generic bit area copy and twister engine for packed pixel framebuffers
*
* Rewritten by:
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*
* Based on previous work of:
* Copyright (C) 1999-2005 James Simmons <jsimmons@www.infradead.org>
* Anton Vorontsov <avorontsov@ru.mvista.com>
* Pavel Pisa <pisa@cmp.felk.cvut.cz>
* Antonino Daplas <adaplas@hotpop.com>
* Geert Uytterhoeven
* and others
*
* NOTES:
*
* Handles native and foreign byte order on both endians, standard and
* reverse pixel order in a byte (<8 BPP), word length of 32/64 bits,
* bits per pixel from 1 to the word length. Handles line lengths at byte
* granularity while maintaining aligned accesses.
*
* Optimized routines for word aligned copying and byte aligned copying
* on reverse pixel framebuffers.
*/
#include "fb_draw.h"
/* used when no reversing is necessary */
static inline unsigned long fb_no_reverse(unsigned long val, struct fb_reverse reverse)
{
return val;
}
/* modifies the masked area in a word */
static inline void fb_copy_offset_masked(unsigned long mask, int offset,
const struct fb_address *dst,
const struct fb_address *src)
{
fb_modify_offset(fb_read_offset(offset, src), mask, offset, dst);
}
/* copies the whole word */
static inline void fb_copy_offset(int offset, const struct fb_address *dst,
const struct fb_address *src)
{
fb_write_offset(fb_read_offset(offset, src), offset, dst);
}
/* forward aligned copy */
static inline void fb_copy_aligned_fwd(const struct fb_address *dst,
const struct fb_address *src,
int end, struct fb_reverse reverse)
{
unsigned long first, last;
first = fb_pixel_mask(dst->bits, reverse);
last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), reverse);
/* Same alignment for source and dest */
if (end <= BITS_PER_LONG) {
/* Single word */
last = last ? (last & first) : first;
/* Trailing bits */
if (last == ~0UL)
fb_copy_offset(0, dst, src);
else
fb_copy_offset_masked(last, 0, dst, src);
} else {
/* Multiple destination words */
int offset = first != ~0UL;
/* Leading bits */
if (offset)
fb_copy_offset_masked(first, 0, dst, src);
/* Main chunk */
end /= BITS_PER_LONG;
while (offset + 4 <= end) {
fb_copy_offset(offset + 0, dst, src);
fb_copy_offset(offset + 1, dst, src);
fb_copy_offset(offset + 2, dst, src);
fb_copy_offset(offset + 3, dst, src);
offset += 4;
}
while (offset < end)
fb_copy_offset(offset++, dst, src);
/* Trailing bits */
if (last)
fb_copy_offset_masked(last, offset, dst, src);
}
}
/* reverse aligned copy */
static inline void fb_copy_aligned_rev(const struct fb_address *dst,
const struct fb_address *src,
int end, struct fb_reverse reverse)
{
unsigned long first, last;
first = fb_pixel_mask(dst->bits, reverse);
last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), reverse);
if (end <= BITS_PER_LONG) {
/* Single word */
if (last)
first &= last;
if (first == ~0UL)
fb_copy_offset(0, dst, src);
else
fb_copy_offset_masked(first, 0, dst, src);
} else {
/* Multiple destination words */
int offset = first != ~0UL;
/* Trailing bits */
end /= BITS_PER_LONG;
if (last)
fb_copy_offset_masked(last, end, dst, src);
/* Main chunk */
while (end >= offset + 4) {
fb_copy_offset(end - 1, dst, src);
fb_copy_offset(end - 2, dst, src);
fb_copy_offset(end - 3, dst, src);
fb_copy_offset(end - 4, dst, src);
end -= 4;
}
while (end > offset)
fb_copy_offset(--end, dst, src);
/* Leading bits */
if (offset)
fb_copy_offset_masked(first, 0, dst, src);
}
}
static inline void fb_copy_aligned(struct fb_address *dst, struct fb_address *src,
int width, u32 height, unsigned int bits_per_line,
struct fb_reverse reverse, bool rev_copy)
{
if (rev_copy)
while (height--) {
fb_copy_aligned_rev(dst, src, width + dst->bits, reverse);
fb_address_backward(dst, bits_per_line);
fb_address_backward(src, bits_per_line);
}
else
while (height--) {
fb_copy_aligned_fwd(dst, src, width + dst->bits, reverse);
fb_address_forward(dst, bits_per_line);
fb_address_forward(src, bits_per_line);
}
}
static __always_inline void fb_copy_fwd(const struct fb_address *dst,
const struct fb_address *src, int width,
unsigned long (*reorder)(unsigned long val,
struct fb_reverse reverse),
struct fb_reverse reverse)
{
unsigned long first, last;
unsigned long d0, d1;
int end = dst->bits + width;
int shift, left, right;
first = fb_pixel_mask(dst->bits, reverse);
last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), reverse);
shift = dst->bits - src->bits;
right = shift & (BITS_PER_LONG - 1);
left = -shift & (BITS_PER_LONG - 1);
if (end <= BITS_PER_LONG) {
/* Single destination word */
last = last ? (last & first) : first;
if (shift < 0) {
d0 = fb_left(reorder(fb_read_offset(-1, src), reverse), left);
if (src->bits + width > BITS_PER_LONG)
d0 |= fb_right(reorder(fb_read_offset(0, src), reverse), right);
if (last == ~0UL)
fb_write_offset(reorder(d0, reverse), 0, dst);
else
fb_modify_offset(reorder(d0, reverse), last, 0, dst);
} else {
d0 = fb_right(reorder(fb_read_offset(0, src), reverse), right);
fb_modify_offset(reorder(d0, reverse), last, 0, dst);
}
} else {
/* Multiple destination words */
int offset = first != ~0UL;
/* Leading bits */
if (shift < 0)
d0 = reorder(fb_read_offset(-1, src), reverse);
else
d0 = 0;
/* 2 source words */
if (offset) {
d1 = reorder(fb_read_offset(0, src), reverse);
d0 = fb_left(d0, left) | fb_right(d1, right);
fb_modify_offset(reorder(d0, reverse), first, 0, dst);
d0 = d1;
}
/* Main chunk */
end /= BITS_PER_LONG;
if (reorder == fb_no_reverse)
while (offset + 4 <= end) {
d1 = fb_read_offset(offset + 0, src);
d0 = fb_left(d0, left) | fb_right(d1, right);
fb_write_offset(d0, offset + 0, dst);
d0 = d1;
d1 = fb_read_offset(offset + 1, src);
d0 = fb_left(d0, left) | fb_right(d1, right);
fb_write_offset(d0, offset + 1, dst);
d0 = d1;
d1 = fb_read_offset(offset + 2, src);
d0 = fb_left(d0, left) | fb_right(d1, right);
fb_write_offset(d0, offset + 2, dst);
d0 = d1;
d1 = fb_read_offset(offset + 3, src);
d0 = fb_left(d0, left) | fb_right(d1, right);
fb_write_offset(d0, offset + 3, dst);
d0 = d1;
offset += 4;
}
while (offset < end) {
d1 = reorder(fb_read_offset(offset, src), reverse);
d0 = fb_left(d0, left) | fb_right(d1, right);
fb_write_offset(reorder(d0, reverse), offset, dst);
d0 = d1;
offset++;
}
/* Trailing bits */
if (last) {
d0 = fb_left(d0, left);
if (src->bits + width
> offset * BITS_PER_LONG + ((shift < 0) ? BITS_PER_LONG : 0))
d0 |= fb_right(reorder(fb_read_offset(offset, src), reverse),
right);
fb_modify_offset(reorder(d0, reverse), last, offset, dst);
}
}
}
static __always_inline void fb_copy_rev(const struct fb_address *dst,
const struct fb_address *src, int end,
unsigned long (*reorder)(unsigned long val,
struct fb_reverse reverse),
struct fb_reverse reverse)
{
unsigned long first, last;
unsigned long d0, d1;
int shift, left, right;
first = fb_pixel_mask(dst->bits, reverse);
last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), reverse);
shift = dst->bits - src->bits;
right = shift & (BITS_PER_LONG-1);
left = -shift & (BITS_PER_LONG-1);
if (end <= BITS_PER_LONG) {
/* Single destination word */
if (last)
first &= last;
if (shift > 0) {
d0 = fb_right(reorder(fb_read_offset(1, src), reverse), right);
if (src->bits > left)
d0 |= fb_left(reorder(fb_read_offset(0, src), reverse), left);
fb_modify_offset(reorder(d0, reverse), first, 0, dst);
} else {
d0 = fb_left(reorder(fb_read_offset(0, src), reverse), left);
if (src->bits + end - dst->bits > BITS_PER_LONG)
d0 |= fb_right(reorder(fb_read_offset(1, src), reverse), right);
if (first == ~0UL)
fb_write_offset(reorder(d0, reverse), 0, dst);
else
fb_modify_offset(reorder(d0, reverse), first, 0, dst);
}
} else {
/* Multiple destination words */
int offset = first != ~0UL;
end /= BITS_PER_LONG;
/* 2 source words */
if (fb_right(~0UL, right) & last)
d0 = fb_right(reorder(fb_read_offset(end + 1, src), reverse), right);
else
d0 = 0;
/* Trailing bits */
d1 = reorder(fb_read_offset(end, src), reverse);
if (last)
fb_modify_offset(reorder(fb_left(d1, left) | d0, reverse),
last, end, dst);
d0 = d1;
/* Main chunk */
if (reorder == fb_no_reverse)
while (end >= offset + 4) {
d1 = fb_read_offset(end - 1, src);
d0 = fb_left(d1, left) | fb_right(d0, right);
fb_write_offset(d0, end - 1, dst);
d0 = d1;
d1 = fb_read_offset(end - 2, src);
d0 = fb_left(d1, left) | fb_right(d0, right);
fb_write_offset(d0, end - 2, dst);
d0 = d1;
d1 = fb_read_offset(end - 3, src);
d0 = fb_left(d1, left) | fb_right(d0, right);
fb_write_offset(d0, end - 3, dst);
d0 = d1;
d1 = fb_read_offset(end - 4, src);
d0 = fb_left(d1, left) | fb_right(d0, right);
fb_write_offset(d0, end - 4, dst);
d0 = d1;
end -= 4;
}
while (end > offset) {
end--;
d1 = reorder(fb_read_offset(end, src), reverse);
d0 = fb_left(d1, left) | fb_right(d0, right);
fb_write_offset(reorder(d0, reverse), end, dst);
d0 = d1;
}
/* Leading bits */
if (offset) {
d0 = fb_right(d0, right);
if (src->bits > left)
d0 |= fb_left(reorder(fb_read_offset(0, src), reverse), left);
fb_modify_offset(reorder(d0, reverse), first, 0, dst);
}
}
}
static __always_inline void fb_copy(struct fb_address *dst, struct fb_address *src,
int width, u32 height, unsigned int bits_per_line,
unsigned long (*reorder)(unsigned long val,
struct fb_reverse reverse),
struct fb_reverse reverse, bool rev_copy)
{
if (rev_copy)
while (height--) {
int move = src->bits < dst->bits ? -1 : 0;
fb_address_move_long(src, move);
fb_copy_rev(dst, src, width + dst->bits, reorder, reverse);
fb_address_backward(dst, bits_per_line);
fb_address_backward(src, bits_per_line);
fb_address_move_long(src, -move);
}
else
while (height--) {
int move = src->bits > dst->bits ? 1 : 0;
fb_address_move_long(src, move);
fb_copy_fwd(dst, src, width, reorder, reverse);
fb_address_forward(dst, bits_per_line);
fb_address_forward(src, bits_per_line);
fb_address_move_long(src, -move);
}
}
static inline void fb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
{
int bpp = p->var.bits_per_pixel;
u32 dy = area->dy;
u32 sy = area->sy;
u32 height = area->height;
int width = area->width * bpp;
unsigned int bits_per_line = BYTES_TO_BITS(p->fix.line_length);
struct fb_reverse reverse = fb_reverse_init(p);
struct fb_address dst = fb_address_init(p);
struct fb_address src = dst;
bool rev_copy = (dy > sy) || (dy == sy && area->dx > area->sx);
if (rev_copy) {
dy += height - 1;
sy += height - 1;
}
fb_address_forward(&dst, dy*bits_per_line + area->dx*bpp);
fb_address_forward(&src, sy*bits_per_line + area->sx*bpp);
if (src.bits == dst.bits)
fb_copy_aligned(&dst, &src, width, height, bits_per_line, reverse, rev_copy);
else if (!reverse.byte && (!reverse.pixel ||
!((src.bits ^ dst.bits) & (BITS_PER_BYTE-1)))) {
fb_copy(&dst, &src, width, height, bits_per_line,
fb_no_reverse, reverse, rev_copy);
} else
fb_copy(&dst, &src, width, height, bits_per_line,
fb_reverse_long, reverse, rev_copy);
}
@@ -0,0 +1,127 @@
/*
* drivers/video/fb_ddc.c - DDC/EDID read support.
*
* Copyright (C) 2006 Dennis Munsie <dmunsie@cecropia.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/i2c-algo-bit.h>
#include <linux/slab.h>
#include "../edid.h"
#define DDC_ADDR 0x50
static unsigned char *fb_do_probe_ddc_edid(struct i2c_adapter *adapter)
{
unsigned char start = 0x0;
unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL);
struct i2c_msg msgs[] = {
{
.addr = DDC_ADDR,
.flags = 0,
.len = 1,
.buf = &start,
}, {
.addr = DDC_ADDR,
.flags = I2C_M_RD,
.len = EDID_LENGTH,
.buf = buf,
}
};
if (!buf) {
dev_warn(&adapter->dev, "unable to allocate memory for EDID "
"block.\n");
return NULL;
}
if (i2c_transfer(adapter, msgs, 2) == 2)
return buf;
dev_warn(&adapter->dev, "unable to read EDID block.\n");
kfree(buf);
return NULL;
}
unsigned char *fb_ddc_read(struct i2c_adapter *adapter)
{
struct i2c_algo_bit_data *algo_data = adapter->algo_data;
unsigned char *edid = NULL;
int i, j;
algo_data->setscl(algo_data->data, 1);
for (i = 0; i < 3; i++) {
/* For some old monitors we need the
* following process to initialize/stop DDC
*/
algo_data->setsda(algo_data->data, 1);
msleep(13);
algo_data->setscl(algo_data->data, 1);
if (algo_data->getscl) {
for (j = 0; j < 5; j++) {
msleep(10);
if (algo_data->getscl(algo_data->data))
break;
}
if (j == 5)
continue;
} else {
udelay(algo_data->udelay);
}
algo_data->setsda(algo_data->data, 0);
msleep(15);
algo_data->setscl(algo_data->data, 0);
msleep(15);
algo_data->setsda(algo_data->data, 1);
msleep(15);
/* Do the real work */
edid = fb_do_probe_ddc_edid(adapter);
algo_data->setsda(algo_data->data, 0);
algo_data->setscl(algo_data->data, 0);
msleep(15);
algo_data->setscl(algo_data->data, 1);
if (algo_data->getscl) {
for (j = 0; j < 10; j++) {
msleep(10);
if (algo_data->getscl(algo_data->data))
break;
}
} else {
udelay(algo_data->udelay);
}
algo_data->setsda(algo_data->data, 1);
msleep(15);
algo_data->setscl(algo_data->data, 0);
algo_data->setsda(algo_data->data, 0);
if (edid)
break;
}
/* Release the DDC lines when done or the Apple Cinema HD display
* will switch off
*/
algo_data->setsda(algo_data->data, 1);
algo_data->setscl(algo_data->data, 1);
return edid;
}
EXPORT_SYMBOL_GPL(fb_ddc_read);
MODULE_AUTHOR("Dennis Munsie <dmunsie@cecropia.com>");
MODULE_DESCRIPTION("DDC/EDID reading support");
MODULE_LICENSE("GPL");
@@ -0,0 +1,479 @@
/*
* linux/drivers/video/fb_defio.c
*
* Copyright (C) 2006 Jaya Kumar
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/list.h>
/* to support deferred IO */
#include <linux/rmap.h>
#include <linux/pagemap.h>
struct address_space;
/*
* struct fb_deferred_io_state
*/
struct fb_deferred_io_state {
struct kref ref;
int open_count; /* number of opened files; protected by fb_info lock */
struct address_space *mapping; /* page cache object for fb device */
struct mutex lock; /* mutex that protects the pageref list */
/* fields protected by lock */
struct fb_info *info;
struct list_head pagereflist; /* list of pagerefs for touched pages */
unsigned long npagerefs;
struct fb_deferred_io_pageref *pagerefs;
};
static struct fb_deferred_io_state *fb_deferred_io_state_alloc(unsigned long len)
{
struct fb_deferred_io_state *fbdefio_state;
struct fb_deferred_io_pageref *pagerefs;
unsigned long npagerefs;
fbdefio_state = kzalloc_obj(*fbdefio_state);
if (!fbdefio_state)
return NULL;
npagerefs = DIV_ROUND_UP(len, PAGE_SIZE);
/* alloc a page ref for each page of the display memory */
pagerefs = kvzalloc_objs(*pagerefs, npagerefs);
if (!pagerefs)
goto err_kfree;
fbdefio_state->npagerefs = npagerefs;
fbdefio_state->pagerefs = pagerefs;
kref_init(&fbdefio_state->ref);
mutex_init(&fbdefio_state->lock);
INIT_LIST_HEAD(&fbdefio_state->pagereflist);
return fbdefio_state;
err_kfree:
kfree(fbdefio_state);
return NULL;
}
static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
{
WARN_ON(!list_empty(&fbdefio_state->pagereflist));
mutex_destroy(&fbdefio_state->lock);
kvfree(fbdefio_state->pagerefs);
kfree(fbdefio_state);
}
static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
{
kref_get(&fbdefio_state->ref);
}
static void __fb_deferred_io_state_release(struct kref *ref)
{
struct fb_deferred_io_state *fbdefio_state =
container_of(ref, struct fb_deferred_io_state, ref);
fb_deferred_io_state_release(fbdefio_state);
}
static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
{
kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
}
/*
* struct vm_operations_struct
*/
static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
{
struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
WARN_ON_ONCE(!try_module_get(THIS_MODULE));
fb_deferred_io_state_get(fbdefio_state);
}
static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
{
struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
fb_deferred_io_state_put(fbdefio_state);
module_put(THIS_MODULE);
}
static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
const void *screen_buffer = info->screen_buffer;
struct page *page = NULL;
if (fbdefio->get_page)
return fbdefio->get_page(info, offs);
if (is_vmalloc_addr(screen_buffer + offs))
page = vmalloc_to_page(screen_buffer + offs);
else if (info->fix.smem_start)
page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT);
if (page)
get_page(page);
return page;
}
static struct fb_deferred_io_pageref *
fb_deferred_io_pageref_lookup(struct fb_deferred_io_state *fbdefio_state, unsigned long offset,
struct page *page)
{
struct fb_info *info = fbdefio_state->info;
unsigned long pgoff = offset >> PAGE_SHIFT;
struct fb_deferred_io_pageref *pageref;
if (fb_WARN_ON_ONCE(info, pgoff >= fbdefio_state->npagerefs))
return NULL; /* incorrect allocation size */
/* 1:1 mapping between pageref and page offset */
pageref = &fbdefio_state->pagerefs[pgoff];
if (pageref->page)
goto out;
pageref->page = page;
pageref->offset = pgoff << PAGE_SHIFT;
INIT_LIST_HEAD(&pageref->list);
out:
if (fb_WARN_ON_ONCE(info, pageref->page != page))
return NULL; /* inconsistent state */
return pageref;
}
static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info *info,
unsigned long offset,
struct page *page)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
struct list_head *pos = &fbdefio_state->pagereflist;
struct fb_deferred_io_pageref *pageref, *cur;
pageref = fb_deferred_io_pageref_lookup(fbdefio_state, offset, page);
if (!pageref)
return NULL;
/*
* This check is to catch the case where a new process could start
* writing to the same page through a new PTE. This new access
* can cause a call to .page_mkwrite even if the original process'
* PTE is marked writable.
*/
if (!list_empty(&pageref->list))
goto pageref_already_added;
if (unlikely(fbdefio->sort_pagereflist)) {
/*
* We loop through the list of pagerefs before adding in
* order to keep the pagerefs sorted. This has significant
* overhead of O(n^2) with n being the number of written
* pages. If possible, drivers should try to work with
* unsorted page lists instead.
*/
list_for_each_entry(cur, &fbdefio_state->pagereflist, list) {
if (cur->offset > pageref->offset)
break;
}
pos = &cur->list;
}
list_add_tail(&pageref->list, pos);
pageref_already_added:
return pageref;
}
static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
struct fb_info *info)
{
list_del_init(&pageref->list);
}
/* this is to find and return the vmalloc-ed fb pages */
static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
{
struct fb_info *info;
unsigned long offset;
struct page *page;
vm_fault_t ret;
struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
mutex_lock(&fbdefio_state->lock);
info = fbdefio_state->info;
if (!info) {
ret = VM_FAULT_SIGBUS; /* our device is gone */
goto err_mutex_unlock;
}
offset = vmf->pgoff << PAGE_SHIFT;
if (offset >= info->fix.smem_len) {
ret = VM_FAULT_SIGBUS;
goto err_mutex_unlock;
}
page = fb_deferred_io_get_page(info, offset);
if (!page) {
ret = VM_FAULT_SIGBUS;
goto err_mutex_unlock;
}
if (!vmf->vma->vm_file)
fb_err(info, "no mapping available\n");
fb_WARN_ON_ONCE(info, !fbdefio_state->mapping);
mutex_unlock(&fbdefio_state->lock);
vmf->page = page;
return 0;
err_mutex_unlock:
mutex_unlock(&fbdefio_state->lock);
return ret;
}
int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
{
struct fb_info *info = file->private_data;
struct inode *inode = file_inode(file);
int err = file_write_and_wait_range(file, start, end);
if (err)
return err;
/* Skip if deferred io is compiled-in but disabled on this fbdev */
if (!info->fbdefio)
return 0;
inode_lock(inode);
flush_delayed_work(&info->deferred_work);
inode_unlock(inode);
return 0;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
/*
* Adds a page to the dirty list. Call this from struct
* vm_operations_struct.page_mkwrite.
*/
static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
unsigned long offset, struct page *page)
{
struct fb_info *info;
struct fb_deferred_io *fbdefio;
struct fb_deferred_io_pageref *pageref;
vm_fault_t ret;
/* protect against the workqueue changing the page list */
mutex_lock(&fbdefio_state->lock);
info = fbdefio_state->info;
if (!info) {
ret = VM_FAULT_SIGBUS; /* our device is gone */
goto err_mutex_unlock;
}
fbdefio = info->fbdefio;
pageref = fb_deferred_io_pageref_get(info, offset, page);
if (WARN_ON_ONCE(!pageref)) {
ret = VM_FAULT_OOM;
goto err_mutex_unlock;
}
/*
* We want the page to remain locked from ->page_mkwrite until
* the PTE is marked dirty to avoid mapping_wrprotect_range()
* being called before the PTE is updated, which would leave
* the page ignored by defio.
* Do this by locking the page here and informing the caller
* about it with VM_FAULT_LOCKED.
*/
lock_page(pageref->page);
mutex_unlock(&fbdefio_state->lock);
/* come back after delay to process the deferred IO */
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
return VM_FAULT_LOCKED;
err_mutex_unlock:
mutex_unlock(&fbdefio_state->lock);
return ret;
}
static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
struct vm_fault *vmf)
{
unsigned long offset = vmf->pgoff << PAGE_SHIFT;
struct page *page = vmf->page;
file_update_time(vmf->vma->vm_file);
return fb_deferred_io_track_page(fbdefio_state, offset, page);
}
static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
{
struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
}
static const struct vm_operations_struct fb_deferred_io_vm_ops = {
.open = fb_deferred_io_vm_open,
.close = fb_deferred_io_vm_close,
.fault = fb_deferred_io_fault,
.page_mkwrite = fb_deferred_io_mkwrite,
};
static const struct address_space_operations fb_deferred_io_aops = {
.dirty_folio = noop_dirty_folio,
};
int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
if (!try_module_get(THIS_MODULE))
return -EINVAL;
vma->vm_ops = &fb_deferred_io_vm_ops;
vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
if (!(info->flags & FBINFO_VIRTFB))
vm_flags_set(vma, VM_IO);
vma->vm_private_data = info->fbdefio_state;
fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
return 0;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
/* workqueue callback */
static void fb_deferred_io_work(struct work_struct *work)
{
struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
struct fb_deferred_io_pageref *pageref, *next;
struct fb_deferred_io *fbdefio = info->fbdefio;
struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
/* here we wrprotect the page's mappings, then do all deferred IO. */
mutex_lock(&fbdefio_state->lock);
#ifdef CONFIG_MMU
list_for_each_entry(pageref, &fbdefio_state->pagereflist, list) {
struct page *page = pageref->page;
pgoff_t pgoff = pageref->offset >> PAGE_SHIFT;
mapping_wrprotect_range(fbdefio_state->mapping, pgoff,
page_to_pfn(page), 1);
}
#endif
/* driver's callback with pagereflist */
fbdefio->deferred_io(info, &fbdefio_state->pagereflist);
/* clear the list */
list_for_each_entry_safe(pageref, next, &fbdefio_state->pagereflist, list)
fb_deferred_io_pageref_put(pageref, info);
mutex_unlock(&fbdefio_state->lock);
}
int fb_deferred_io_init(struct fb_info *info)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
struct fb_deferred_io_state *fbdefio_state;
BUG_ON(!fbdefio);
if (WARN_ON(!info->fix.smem_len))
return -EINVAL;
fbdefio_state = fb_deferred_io_state_alloc(info->fix.smem_len);
if (!fbdefio_state)
return -ENOMEM;
fbdefio_state->info = info;
INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
if (fbdefio->delay == 0) /* set a default of 1 s */
fbdefio->delay = HZ;
info->fbdefio_state = fbdefio_state;
return 0;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_init);
void fb_deferred_io_open(struct fb_info *info,
struct inode *inode,
struct file *file)
{
struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
fbdefio_state->mapping = file->f_mapping;
file->f_mapping->a_ops = &fb_deferred_io_aops;
fbdefio_state->open_count++;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_open);
static void fb_deferred_io_lastclose(struct fb_info *info)
{
flush_delayed_work(&info->deferred_work);
}
void fb_deferred_io_release(struct fb_info *info)
{
struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
if (!--fbdefio_state->open_count)
fb_deferred_io_lastclose(info);
}
EXPORT_SYMBOL_GPL(fb_deferred_io_release);
void fb_deferred_io_cleanup(struct fb_info *info)
{
struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
fb_deferred_io_lastclose(info);
info->fbdefio_state = NULL;
mutex_lock(&fbdefio_state->lock);
fbdefio_state->info = NULL;
mutex_unlock(&fbdefio_state->lock);
fb_deferred_io_state_put(fbdefio_state);
}
EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
@@ -0,0 +1,163 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Various common functions used by the framebuffer drawing code
*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
#ifndef _FB_DRAW_H
#define _FB_DRAW_H
/* swap bytes in a long, independent of word size */
#define swab_long _swab_long(BITS_PER_LONG)
#define _swab_long(x) __swab_long(x)
#define __swab_long(x) swab##x
/* move the address pointer by the number of words */
static inline void fb_address_move_long(struct fb_address *adr, int offset)
{
adr->address += offset * (BITS_PER_LONG / BITS_PER_BYTE);
}
/* move the address pointer forward with the number of bits */
static inline void fb_address_forward(struct fb_address *adr, unsigned int offset)
{
unsigned int bits = (unsigned int)adr->bits + offset;
adr->bits = bits & (BITS_PER_LONG - 1u);
adr->address += (bits & ~(BITS_PER_LONG - 1u)) / BITS_PER_BYTE;
}
/* move the address pointer backwards with the number of bits */
static inline void fb_address_backward(struct fb_address *adr, unsigned int offset)
{
int bits = adr->bits - (int)offset;
adr->bits = bits & (BITS_PER_LONG - 1);
if (bits < 0)
adr->address -= (adr->bits - bits) / BITS_PER_BYTE;
else
adr->address += (bits - adr->bits) / BITS_PER_BYTE;
}
/* compose pixels based on mask */
static inline unsigned long fb_comp(unsigned long set, unsigned long unset, unsigned long mask)
{
return ((set ^ unset) & mask) ^ unset;
}
/* framebuffer read-modify-write access for replacing bits in the mask */
static inline void fb_modify_offset(unsigned long val, unsigned long mask,
int offset, const struct fb_address *dst)
{
fb_write_offset(fb_comp(val, fb_read_offset(offset, dst), mask), offset, dst);
}
/*
* get current palette, if applicable for visual
*
* The pseudo color table entries (and colors) are right justified and in the
* same byte order as it's expected to be placed into a native ordered
* framebuffer memory. What that means:
*
* Expected bytes in framebuffer memory (in native order):
* RR GG BB RR GG BB RR GG BB ...
*
* Pseudo palette entry on little endian arch:
* RR | GG << 8 | BB << 16
*
* Pseudo palette entry on a big endian arch:
* RR << 16 | GG << 8 | BB
*/
static inline const u32 *fb_palette(struct fb_info *info)
{
return (info->fix.visual == FB_VISUAL_TRUECOLOR ||
info->fix.visual == FB_VISUAL_DIRECTCOLOR) ? info->pseudo_palette : NULL;
}
/* move pixels right on screen when framebuffer is in native order */
static inline unsigned long fb_right(unsigned long value, int index)
{
#ifdef __LITTLE_ENDIAN
return value << index;
#else
return value >> index;
#endif
}
/* move pixels left on screen when framebuffer is in native order */
static inline unsigned long fb_left(unsigned long value, int index)
{
#ifdef __LITTLE_ENDIAN
return value >> index;
#else
return value << index;
#endif
}
/* reversal options */
struct fb_reverse {
bool byte, pixel;
};
/* reverse bits of each byte in a long */
static inline unsigned long fb_reverse_bits_long(unsigned long val)
{
#if defined(CONFIG_HAVE_ARCH_BITREVERSE) && BITS_PER_LONG == 32
return bitrev8x4(val);
#else
val = fb_comp(val >> 1, val << 1, ~0UL / 3);
val = fb_comp(val >> 2, val << 2, ~0UL / 5);
return fb_comp(val >> 4, val << 4, ~0UL / 17);
#endif
}
/* apply byte and bit reversals as necessary */
static inline unsigned long fb_reverse_long(unsigned long val,
struct fb_reverse reverse)
{
if (reverse.pixel)
val = fb_reverse_bits_long(val);
return reverse.byte ? swab_long(val) : val;
}
/* calculate a pixel mask for the given reversal */
static inline unsigned long fb_pixel_mask(int index, struct fb_reverse reverse)
{
#ifdef FB_REV_PIXELS_IN_BYTE
if (reverse.byte)
return reverse.pixel ? fb_left(~0UL, index) : swab_long(fb_right(~0UL, index));
else
return reverse.pixel ? swab_long(fb_left(~0UL, index)) : fb_right(~0UL, index);
#else
return reverse.byte ? swab_long(fb_right(~0UL, index)) : fb_right(~0UL, index);
#endif
}
/*
* initialise reversals based on info
*
* Normally the first byte is the low byte on little endian and in the high
* on big endian. If it's the other way around then that's reverse byte order.
*
* Normally the first pixel is the LSB on little endian and the MSB on big
* endian. If that's not the case that's reverse pixel order.
*/
static inline struct fb_reverse fb_reverse_init(struct fb_info *info)
{
struct fb_reverse reverse;
#ifdef __LITTLE_ENDIAN
reverse.byte = fb_be_math(info) != 0;
#else
reverse.byte = fb_be_math(info) == 0;
#endif
#ifdef FB_REV_PIXELS_IN_BYTE
reverse.pixel = info->var.bits_per_pixel < BITS_PER_BYTE
&& (info->var.nonstd & FB_NONSTD_REV_PIX_IN_B);
#else
reverse.pixel = false;
#endif
return reverse;
}
#endif /* FB_DRAW_H */
@@ -0,0 +1,279 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Generic bit area filler and twister engine for packed pixel framebuffers
*
* Rewritten by:
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*
* Based on earlier work of:
* Copyright (C) 2000 James Simmons (jsimmons@linux-fbdev.org)
* Michal Januszewski <spock@gentoo.org>
* Anton Vorontsov <avorontsov@ru.mvista.com>
* Pavel Pisa <pisa@cmp.felk.cvut.cz>
* Antonino A. Daplas <adaplas@gmail.com>
* Geert Uytterhoeven
* and others
*
* NOTES:
*
* Handles native and foreign byte order on both endians, standard and
* reverse pixel order in a byte (<8 BPP), word length of 32/64 bits,
* bits per pixel from 1 to the word length. Handles line lengths at byte
* granularity while maintaining aligned accesses.
*
* Optimized path for power of two bits per pixel modes.
*/
#include "fb_draw.h"
/* inverts bits at a given offset */
static inline void fb_invert_offset(unsigned long pat, int offset, const struct fb_address *dst)
{
fb_write_offset(fb_read_offset(offset, dst) ^ pat, offset, dst);
}
/* state for pattern generator and whether swapping is necessary */
struct fb_pattern {
unsigned long pixels;
int left, right;
struct fb_reverse reverse;
};
/* used to get the pattern in native order */
static unsigned long fb_pattern_get(struct fb_pattern *pattern)
{
return pattern->pixels;
}
/* used to get the pattern in reverse order */
static unsigned long fb_pattern_get_reverse(struct fb_pattern *pattern)
{
return swab_long(pattern->pixels);
}
/* next static pattern */
static void fb_pattern_static(struct fb_pattern *pattern)
{
/* nothing to do */
}
/* next rotating pattern */
static void fb_pattern_rotate(struct fb_pattern *pattern)
{
pattern->pixels = fb_left(pattern->pixels, pattern->left)
| fb_right(pattern->pixels, pattern->right);
}
#define FB_PAT(i) (((1UL<<(BITS_PER_LONG-1)/(i)*(i))/((1<<(i))-1)<<(i))|1)
/* create the filling pattern from a given color */
static unsigned long pixel_to_pat(int bpp, u32 color)
{
static const unsigned long mulconst[BITS_PER_LONG/4] = {
0, ~0UL, FB_PAT(2), FB_PAT(3),
FB_PAT(4), FB_PAT(5), FB_PAT(6), FB_PAT(7),
#if BITS_PER_LONG == 64
FB_PAT(8), FB_PAT(9), FB_PAT(10), FB_PAT(11),
FB_PAT(12), FB_PAT(13), FB_PAT(14), FB_PAT(15),
#endif
};
unsigned long pattern;
switch (bpp) {
case 0 ... BITS_PER_LONG/4-1:
pattern = mulconst[bpp] * color;
break;
case BITS_PER_LONG/4 ... BITS_PER_LONG/2-1:
pattern = color;
pattern = pattern | pattern << bpp;
pattern = pattern | pattern << bpp*2;
break;
case BITS_PER_LONG/2 ... BITS_PER_LONG-1:
pattern = color;
pattern = pattern | pattern << bpp;
break;
default:
return color;
}
#ifndef __LITTLE_ENDIAN
pattern <<= (BITS_PER_LONG % bpp);
pattern |= pattern >> bpp;
#endif
return pattern;
}
/* overwrite bits according to a pattern in a line */
static __always_inline void bitfill(const struct fb_address *dst,
struct fb_pattern *pattern,
unsigned long (*get)(struct fb_pattern *pattern),
void (*rotate)(struct fb_pattern *pattern),
int end)
{
unsigned long first, last;
end += dst->bits;
first = fb_pixel_mask(dst->bits, pattern->reverse);
last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), pattern->reverse);
if (end <= BITS_PER_LONG) {
last = last ? (last & first) : first;
first = get(pattern);
if (last == ~0UL)
fb_write_offset(first, 0, dst);
else if (last)
fb_modify_offset(first, last, 0, dst);
} else {
int offset = first != ~0UL;
if (offset) {
fb_modify_offset(get(pattern), first, 0, dst);
rotate(pattern);
}
end /= BITS_PER_LONG;
for (; offset + 4 <= end; offset += 4) {
fb_write_offset(get(pattern), offset + 0, dst);
rotate(pattern);
fb_write_offset(get(pattern), offset + 1, dst);
rotate(pattern);
fb_write_offset(get(pattern), offset + 2, dst);
rotate(pattern);
fb_write_offset(get(pattern), offset + 3, dst);
rotate(pattern);
}
while (offset < end) {
fb_write_offset(get(pattern), offset++, dst);
rotate(pattern);
}
if (last)
fb_modify_offset(get(pattern), last, offset, dst);
}
}
/* inverts bits according to a pattern in a line */
static __always_inline void bitinvert(const struct fb_address *dst,
struct fb_pattern *pattern,
unsigned long (*get)(struct fb_pattern *pattern),
void (*rotate)(struct fb_pattern *pattern),
int end)
{
unsigned long first, last;
int offset;
end += dst->bits;
first = fb_pixel_mask(dst->bits, pattern->reverse);
last = ~fb_pixel_mask(end & (BITS_PER_LONG-1), pattern->reverse);
if (end <= BITS_PER_LONG) {
offset = 0;
last = last ? (last & first) : first;
} else {
offset = first != ~0UL;
if (offset) {
first &= get(pattern);
if (first)
fb_invert_offset(first, 0, dst);
rotate(pattern);
}
end /= BITS_PER_LONG;
for (; offset + 4 <= end; offset += 4) {
fb_invert_offset(get(pattern), offset + 0, dst);
rotate(pattern);
fb_invert_offset(get(pattern), offset + 1, dst);
rotate(pattern);
fb_invert_offset(get(pattern), offset + 2, dst);
rotate(pattern);
fb_invert_offset(get(pattern), offset + 3, dst);
rotate(pattern);
}
while (offset < end) {
fb_invert_offset(get(pattern), offset++, dst);
rotate(pattern);
}
}
last &= get(pattern);
if (last)
fb_invert_offset(last, offset, dst);
}
/* pattern doesn't change. 1, 2, 4, 8, 16, 32, 64 bpp */
static inline void fb_fillrect_static(const struct fb_fillrect *rect, int bpp,
struct fb_address *dst, struct fb_pattern *pattern,
unsigned int bits_per_line)
{
u32 height = rect->height;
int width = rect->width * bpp;
if (bpp > 8 && pattern->reverse.byte)
pattern->pixels = swab_long(pattern->pixels);
if (rect->rop == ROP_XOR)
while (height--) {
bitinvert(dst, pattern, fb_pattern_get, fb_pattern_static, width);
fb_address_forward(dst, bits_per_line);
}
else
while (height--) {
bitfill(dst, pattern, fb_pattern_get, fb_pattern_static, width);
fb_address_forward(dst, bits_per_line);
}
}
/* rotate pattern to the correct position */
static inline unsigned long fb_rotate(unsigned long pattern, int shift, int bpp)
{
shift %= bpp;
return fb_right(pattern, shift) | fb_left(pattern, bpp - shift);
}
/* rotating pattern, for example 24 bpp */
static __always_inline void fb_fillrect_rotating(const struct fb_fillrect *rect,
int bpp, struct fb_address *dst,
struct fb_pattern *pattern,
unsigned long (*get)(struct fb_pattern *pattern),
unsigned int bits_per_line)
{
unsigned long pat = pattern->pixels;
u32 height = rect->height;
int width = rect->width * bpp;
if (rect->rop == ROP_XOR)
while (height--) {
pattern->pixels = fb_rotate(pat, dst->bits, bpp);
bitinvert(dst, pattern, get, fb_pattern_rotate, width);
fb_address_forward(dst, bits_per_line);
}
else
while (height--) {
pattern->pixels = fb_rotate(pat, dst->bits, bpp);
bitfill(dst, pattern, get, fb_pattern_rotate, width);
fb_address_forward(dst, bits_per_line);
}
}
static inline void fb_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
{
int bpp = p->var.bits_per_pixel;
unsigned int bits_per_line = BYTES_TO_BITS(p->fix.line_length);
const u32 *palette = fb_palette(p);
struct fb_address dst = fb_address_init(p);
struct fb_pattern pattern;
fb_address_forward(&dst, rect->dy * bits_per_line + rect->dx * bpp);
pattern.pixels = pixel_to_pat(bpp, palette ? palette[rect->color] : rect->color);
pattern.reverse = fb_reverse_init(p);
pattern.left = BITS_PER_LONG % bpp;
if (pattern.left) {
pattern.right = bpp - pattern.left;
if (pattern.reverse.byte)
fb_fillrect_rotating(rect, bpp, &dst, &pattern,
fb_pattern_get_reverse, bits_per_line);
else
fb_fillrect_rotating(rect, bpp, &dst, &pattern,
fb_pattern_get, bits_per_line);
} else
fb_fillrect_static(rect, bpp, &dst, &pattern, bits_per_line);
}
@@ -0,0 +1,495 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Generic bitmap / 8 bpp image bitstreamer for packed pixel framebuffers
*
* Rewritten by:
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*
* Based on previous work of:
* Copyright (C) June 1999 James Simmons
* Anton Vorontsov <avorontsov@ru.mvista.com>
* Pavel Pisa <pisa@cmp.felk.cvut.cz>
* Antonino A. Daplas <adaplas@gmail.com>
* and others
*
* NOTES:
*
* Handles native and foreign byte order on both endians, standard and
* reverse pixel order in a byte (<8 BPP), word length of 32/64 bits,
* bits per pixel from 1 to the word length. Handles line lengths at byte
* granularity while maintaining aligned accesses.
*
* Optimized routines for word aligned 1, 2, 4 pixel per word for high
* bpp modes and 4 pixel at a time operation for low bpp.
*
* The color image is expected to be one byte per pixel, and values should
* not exceed the bitdepth or the pseudo palette (if used).
*/
#include "fb_draw.h"
/* bitmap image iterator, one pixel at a time */
struct fb_bitmap_iter {
const u8 *data;
unsigned long colors[2];
int width, i;
};
static bool fb_bitmap_image(void *iterator, unsigned long *pixels, int *bits)
{
struct fb_bitmap_iter *iter = iterator;
if (iter->i < iter->width) {
int bit = ~iter->i & (BITS_PER_BYTE-1);
int byte = iter->i++ / BITS_PER_BYTE;
*pixels = iter->colors[(iter->data[byte] >> bit) & 1];
return true;
}
iter->data += BITS_TO_BYTES(iter->width);
iter->i = 0;
return false;
}
/* color image iterator, one pixel at a time */
struct fb_color_iter {
const u8 *data;
const u32 *palette;
struct fb_reverse reverse;
int shift;
int width, i;
};
static bool fb_color_image(void *iterator, unsigned long *pixels, int *bits)
{
struct fb_color_iter *iter = iterator;
if (iter->i < iter->width) {
unsigned long color = iter->data[iter->i++];
if (iter->palette)
color = iter->palette[color];
*pixels = color << iter->shift;
if (iter->reverse.pixel)
*pixels = fb_reverse_bits_long(*pixels);
return true;
}
iter->data += iter->width;
iter->i = 0;
return false;
}
/* bitmap image iterator, 4 pixels at a time */
struct fb_bitmap4x_iter {
const u8 *data;
u32 fgxcolor, bgcolor;
int width, i;
const u32 *expand;
int bpp;
bool top;
};
static bool fb_bitmap4x_image(void *iterator, unsigned long *pixels, int *bits)
{
struct fb_bitmap4x_iter *iter = iterator;
u8 data;
if (iter->i >= BITS_PER_BYTE/2) {
iter->i -= BITS_PER_BYTE/2;
iter->top = !iter->top;
if (iter->top)
data = *iter->data++ >> BITS_PER_BYTE/2;
else
data = iter->data[-1] & ((1 << BITS_PER_BYTE/2)-1);
} else if (iter->i != 0) {
*bits = iter->bpp * iter->i;
if (iter->top)
data = iter->data[-1] & ((1 << BITS_PER_BYTE/2)-1);
else
data = *iter->data++ >> BITS_PER_BYTE/2;
#ifndef __LITTLE_ENDIAN
data >>= BITS_PER_BYTE/2 - iter->i;
#endif
iter->i = 0;
} else {
*bits = iter->bpp * BITS_PER_BYTE/2;
iter->i = iter->width;
iter->top = false;
return false;
}
*pixels = (iter->fgxcolor & iter->expand[data]) ^ iter->bgcolor;
#ifndef __LITTLE_ENDIAN
*pixels <<= BITS_PER_LONG - *bits;
#endif
return true;
}
/* draw a line a group of pixels at a time */
static __always_inline void fb_bitblit(bool (*get)(void *iter, unsigned long *pixels,
int *bits),
void *iter, int bits, struct fb_address *dst,
struct fb_reverse reverse)
{
unsigned long pixels, val, mask, old;
int offset = 0;
int shift = dst->bits;
if (shift) {
old = fb_read_offset(0, dst);
val = fb_reverse_long(old, reverse);
val &= ~fb_right(~0UL, shift);
} else {
old = 0;
val = 0;
}
while (get(iter, &pixels, &bits)) {
val |= fb_right(pixels, shift);
shift += bits;
if (shift < BITS_PER_LONG)
continue;
val = fb_reverse_long(val, reverse);
fb_write_offset(val, offset++, dst);
shift &= BITS_PER_LONG - 1;
val = !shift ? 0 : fb_left(pixels, bits - shift);
}
if (shift) {
mask = ~fb_pixel_mask(shift, reverse);
val = fb_reverse_long(val, reverse);
if (offset || !dst->bits)
old = fb_read_offset(offset, dst);
fb_write_offset(fb_comp(val, old, mask), offset, dst);
}
}
/* draw a color image a pixel at a time */
static inline void fb_color_imageblit(const struct fb_image *image, struct fb_address *dst,
unsigned int bits_per_line, const u32 *palette, int bpp,
struct fb_reverse reverse)
{
struct fb_color_iter iter;
u32 height;
iter.data = (const u8 *)image->data;
iter.palette = palette;
iter.reverse = reverse;
#ifdef __LITTLE_ENDIAN
if (reverse.pixel)
iter.shift = BITS_PER_BYTE - bpp;
else
iter.shift = 0;
#else
if (reverse.pixel)
iter.shift = BITS_PER_LONG - BITS_PER_BYTE;
else
iter.shift = BITS_PER_LONG - bpp;
#endif
iter.width = image->width;
iter.i = 0;
height = image->height;
while (height--) {
fb_bitblit(fb_color_image, &iter, bpp, dst, reverse);
fb_address_forward(dst, bits_per_line);
}
}
#ifdef __LITTLE_ENDIAN
#define FB_GEN(a, b) (((a)/8+(((a)&4)<<((b)-2)) \
+(((a)&2)<<((b)*2-1))+(((a)&1u)<<((b)*3)))*((1<<(b))-1))
#define FB_GEN1(a) ((a)/8+((a)&4)/2+((a)&2)*2+((a)&1)*8)
#else
#define FB_GEN(a, b) (((((a)/8)<<((b)*3))+(((a)&4)<<((b)*2-2)) \
+(((a)&2)<<(b-1))+((a)&1u))*((1<<(b))-1))
#define FB_GEN1(a) (a)
#endif
#define FB_GENx(a) { FB_GEN(0, (a)), FB_GEN(1, (a)), FB_GEN(2, (a)), FB_GEN(3, (a)), \
FB_GEN(4, (a)), FB_GEN(5, (a)), FB_GEN(6, (a)), FB_GEN(7, (a)), \
FB_GEN(8, (a)), FB_GEN(9, (a)), FB_GEN(10, (a)), FB_GEN(11, (a)), \
FB_GEN(12, (a)), FB_GEN(13, (a)), FB_GEN(14, (a)), FB_GEN(15, (a)) }
/* draw a 2 color image four pixels at a time (for 1-8 bpp only) */
static inline void fb_bitmap4x_imageblit(const struct fb_image *image, struct fb_address *dst,
unsigned long fgcolor, unsigned long bgcolor, int bpp,
unsigned int bits_per_line, struct fb_reverse reverse)
{
static const u32 mul[BITS_PER_BYTE] = {
0xf, 0x55, 0x249, 0x1111, 0x8421, 0x41041, 0x204081, 0x01010101
};
static const u32 expand[BITS_PER_BYTE][1 << 4] = {
{
FB_GEN1(0), FB_GEN1(1), FB_GEN1(2), FB_GEN1(3),
FB_GEN1(4), FB_GEN1(5), FB_GEN1(6), FB_GEN1(7),
FB_GEN1(8), FB_GEN1(9), FB_GEN1(10), FB_GEN1(11),
FB_GEN1(12), FB_GEN1(13), FB_GEN1(14), FB_GEN1(15)
},
FB_GENx(2), FB_GENx(3), FB_GENx(4),
FB_GENx(5), FB_GENx(6), FB_GENx(7), FB_GENx(8),
};
struct fb_bitmap4x_iter iter;
u32 height;
iter.data = (const u8 *)image->data;
if (reverse.pixel) {
fgcolor = fb_reverse_bits_long(fgcolor << (BITS_PER_BYTE - bpp));
bgcolor = fb_reverse_bits_long(bgcolor << (BITS_PER_BYTE - bpp));
}
iter.fgxcolor = (fgcolor ^ bgcolor) * mul[bpp-1];
iter.bgcolor = bgcolor * mul[bpp-1];
iter.width = image->width;
iter.i = image->width;
iter.expand = expand[bpp-1];
iter.bpp = bpp;
iter.top = false;
height = image->height;
while (height--) {
fb_bitblit(fb_bitmap4x_image, &iter, bpp * BITS_PER_BYTE/2, dst, reverse);
fb_address_forward(dst, bits_per_line);
}
}
/* draw a bitmap image 1 pixel at a time (for >8 bpp) */
static inline void fb_bitmap1x_imageblit(const struct fb_image *image, struct fb_address *dst,
unsigned long fgcolor, unsigned long bgcolor, int bpp,
unsigned int bits_per_line, struct fb_reverse reverse)
{
struct fb_bitmap_iter iter;
u32 height;
iter.colors[0] = bgcolor;
iter.colors[1] = fgcolor;
#ifndef __LITTLE_ENDIAN
iter.colors[0] <<= BITS_PER_LONG - bpp;
iter.colors[1] <<= BITS_PER_LONG - bpp;
#endif
iter.data = (const u8 *)image->data;
iter.width = image->width;
iter.i = 0;
height = image->height;
while (height--) {
fb_bitblit(fb_bitmap_image, &iter, bpp, dst, reverse);
fb_address_forward(dst, bits_per_line);
}
}
/* one pixel per word, 64/32 bpp blitting */
static inline void fb_bitmap_1ppw(const struct fb_image *image, struct fb_address *dst,
unsigned long fgcolor, unsigned long bgcolor,
int words_per_line, struct fb_reverse reverse)
{
unsigned long tab[2];
const u8 *src = (u8 *)image->data;
int width = image->width;
int offset;
u32 height;
if (reverse.byte) {
tab[0] = swab_long(bgcolor);
tab[1] = swab_long(fgcolor);
} else {
tab[0] = bgcolor;
tab[1] = fgcolor;
}
height = image->height;
while (height--) {
for (offset = 0; offset + 8 <= width; offset += 8) {
unsigned int srcbyte = *src++;
fb_write_offset(tab[(srcbyte >> 7) & 1], offset + 0, dst);
fb_write_offset(tab[(srcbyte >> 6) & 1], offset + 1, dst);
fb_write_offset(tab[(srcbyte >> 5) & 1], offset + 2, dst);
fb_write_offset(tab[(srcbyte >> 4) & 1], offset + 3, dst);
fb_write_offset(tab[(srcbyte >> 3) & 1], offset + 4, dst);
fb_write_offset(tab[(srcbyte >> 2) & 1], offset + 5, dst);
fb_write_offset(tab[(srcbyte >> 1) & 1], offset + 6, dst);
fb_write_offset(tab[(srcbyte >> 0) & 1], offset + 7, dst);
}
if (offset < width) {
unsigned int srcbyte = *src++;
while (offset < width) {
fb_write_offset(tab[(srcbyte >> 7) & 1], offset, dst);
srcbyte <<= 1;
offset++;
}
}
fb_address_move_long(dst, words_per_line);
}
}
static inline unsigned long fb_pack(unsigned long left, unsigned long right, int bits)
{
#ifdef __LITTLE_ENDIAN
return left | right << bits;
#else
return right | left << bits;
#endif
}
/* aligned 32/16 bpp blitting */
static inline void fb_bitmap_2ppw(const struct fb_image *image, struct fb_address *dst,
unsigned long fgcolor, unsigned long bgcolor,
int words_per_line, struct fb_reverse reverse)
{
unsigned long tab[4];
const u8 *src = (u8 *)image->data;
int width = image->width / 2;
int offset;
u32 height;
tab[0] = fb_pack(bgcolor, bgcolor, BITS_PER_LONG/2);
tab[1] = fb_pack(bgcolor, fgcolor, BITS_PER_LONG/2);
tab[2] = fb_pack(fgcolor, bgcolor, BITS_PER_LONG/2);
tab[3] = fb_pack(fgcolor, fgcolor, BITS_PER_LONG/2);
if (reverse.byte) {
tab[0] = swab_long(tab[0]);
tab[1] = swab_long(tab[1]);
tab[2] = swab_long(tab[2]);
tab[3] = swab_long(tab[3]);
}
height = image->height;
while (height--) {
for (offset = 0; offset + 4 <= width; offset += 4) {
unsigned int srcbyte = *src++;
fb_write_offset(tab[(srcbyte >> 6) & 3], offset + 0, dst);
fb_write_offset(tab[(srcbyte >> 4) & 3], offset + 1, dst);
fb_write_offset(tab[(srcbyte >> 2) & 3], offset + 2, dst);
fb_write_offset(tab[(srcbyte >> 0) & 3], offset + 3, dst);
}
if (offset < width) {
unsigned int srcbyte = *src++;
while (offset < width) {
fb_write_offset(tab[(srcbyte >> 6) & 3], offset, dst);
srcbyte <<= 2;
offset++;
}
}
fb_address_move_long(dst, words_per_line);
}
}
#define FB_PATP(a, b) (((a)<<((b)*BITS_PER_LONG/4))*((1UL<<BITS_PER_LONG/4)-1UL))
#define FB_PAT4(a) (FB_PATP((a)&1, 0)|FB_PATP(((a)&2)/2, 1)| \
FB_PATP(((a)&4)/4, 2)|FB_PATP(((a)&8)/8, 3))
/* aligned 16/8 bpp blitting */
static inline void fb_bitmap_4ppw(const struct fb_image *image, struct fb_address *dst,
unsigned long fgcolor, unsigned long bgcolor,
int words_per_line, struct fb_reverse reverse)
{
static const unsigned long tab16_be[] = {
0, FB_PAT4(1UL), FB_PAT4(2UL), FB_PAT4(3UL),
FB_PAT4(4UL), FB_PAT4(5UL), FB_PAT4(6UL), FB_PAT4(7UL),
FB_PAT4(8UL), FB_PAT4(9UL), FB_PAT4(10UL), FB_PAT4(11UL),
FB_PAT4(12UL), FB_PAT4(13UL), FB_PAT4(14UL), ~0UL
};
static const unsigned long tab16_le[] = {
0, FB_PAT4(8UL), FB_PAT4(4UL), FB_PAT4(12UL),
FB_PAT4(2UL), FB_PAT4(10UL), FB_PAT4(6UL), FB_PAT4(14UL),
FB_PAT4(1UL), FB_PAT4(9UL), FB_PAT4(5UL), FB_PAT4(13UL),
FB_PAT4(3UL), FB_PAT4(11UL), FB_PAT4(7UL), ~0UL
};
const unsigned long *tab;
const u8 *src = (u8 *)image->data;
int width = image->width / 4;
int offset;
u32 height;
fgcolor = fgcolor | fgcolor << BITS_PER_LONG/4;
bgcolor = bgcolor | bgcolor << BITS_PER_LONG/4;
fgcolor = fgcolor | fgcolor << BITS_PER_LONG/2;
bgcolor = bgcolor | bgcolor << BITS_PER_LONG/2;
fgcolor ^= bgcolor;
if (BITS_PER_LONG/4 > BITS_PER_BYTE && reverse.byte) {
fgcolor = swab_long(fgcolor);
bgcolor = swab_long(bgcolor);
}
#ifdef __LITTLE_ENDIAN
tab = reverse.byte ? tab16_be : tab16_le;
#else
tab = reverse.byte ? tab16_le : tab16_be;
#endif
height = image->height;
while (height--) {
for (offset = 0; offset + 2 <= width; offset += 2, src++) {
fb_write_offset((fgcolor & tab[*src >> 4]) ^ bgcolor, offset + 0, dst);
fb_write_offset((fgcolor & tab[*src & 0xf]) ^ bgcolor, offset + 1, dst);
}
if (offset < width)
fb_write_offset((fgcolor & tab[*src++ >> 4]) ^ bgcolor, offset, dst);
fb_address_move_long(dst, words_per_line);
}
}
static inline void fb_bitmap_imageblit(const struct fb_image *image, struct fb_address *dst,
unsigned int bits_per_line, const u32 *palette, int bpp,
struct fb_reverse reverse)
{
unsigned long fgcolor, bgcolor;
if (palette) {
fgcolor = palette[image->fg_color];
bgcolor = palette[image->bg_color];
} else {
fgcolor = image->fg_color;
bgcolor = image->bg_color;
}
if (!dst->bits && !(bits_per_line & (BITS_PER_LONG-1))) {
if (bpp == BITS_PER_LONG && BITS_PER_LONG == 32) {
fb_bitmap_1ppw(image, dst, fgcolor, bgcolor,
bits_per_line / BITS_PER_LONG, reverse);
return;
}
if (bpp == BITS_PER_LONG/2 && !(image->width & 1)) {
fb_bitmap_2ppw(image, dst, fgcolor, bgcolor,
bits_per_line / BITS_PER_LONG, reverse);
return;
}
if (bpp == BITS_PER_LONG/4 && !(image->width & 3)) {
fb_bitmap_4ppw(image, dst, fgcolor, bgcolor,
bits_per_line / BITS_PER_LONG, reverse);
return;
}
}
if (bpp > 0 && bpp <= BITS_PER_BYTE)
fb_bitmap4x_imageblit(image, dst, fgcolor, bgcolor, bpp,
bits_per_line, reverse);
else if (bpp > BITS_PER_BYTE && bpp <= BITS_PER_LONG)
fb_bitmap1x_imageblit(image, dst, fgcolor, bgcolor, bpp,
bits_per_line, reverse);
}
static inline void fb_imageblit(struct fb_info *p, const struct fb_image *image)
{
int bpp = p->var.bits_per_pixel;
unsigned int bits_per_line = BYTES_TO_BITS(p->fix.line_length);
struct fb_address dst = fb_address_init(p);
struct fb_reverse reverse = fb_reverse_init(p);
const u32 *palette = fb_palette(p);
fb_address_forward(&dst, image->dy * bits_per_line + image->dx * bpp);
if (image->depth == 1)
fb_bitmap_imageblit(image, &dst, bits_per_line, palette, bpp, reverse);
else
fb_color_imageblit(image, &dst, bits_per_line, palette, bpp, reverse);
}
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/export.h>
#include <linux/fb.h>
#include <linux/mutex.h>
#include <linux/slab.h>
/**
* framebuffer_alloc - creates a new frame buffer info structure
*
* @size: size of driver private data, can be zero
* @dev: pointer to the device for this fb, this can be NULL
*
* Creates a new frame buffer info structure. Also reserves @size bytes
* for driver private data (info->par). info->par (if any) will be
* aligned to sizeof(long). The new instances of struct fb_info and
* the driver private data are both cleared to zero.
*
* Returns the new structure, or NULL if an error occurred.
*
*/
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
{
#define BYTES_PER_LONG (BITS_PER_LONG/8)
#define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG))
int fb_info_size = sizeof(struct fb_info);
struct fb_info *info;
char *p;
if (size)
fb_info_size += PADDING;
p = kzalloc(fb_info_size + size, GFP_KERNEL);
if (!p)
return NULL;
info = (struct fb_info *) p;
if (size)
info->par = p + fb_info_size;
info->device = dev;
info->fbcon_rotate_hint = -1;
info->blank = FB_BLANK_UNBLANK;
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
mutex_init(&info->bl_curve_mutex);
#endif
return info;
#undef PADDING
#undef BYTES_PER_LONG
}
EXPORT_SYMBOL(framebuffer_alloc);
/**
* framebuffer_release - marks the structure available for freeing
*
* @info: frame buffer info structure
*
* Drop the reference count of the device embedded in the
* framebuffer info structure.
*
*/
void framebuffer_release(struct fb_info *info)
{
if (!info)
return;
if (WARN_ON(refcount_read(&info->count)))
return;
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
mutex_destroy(&info->bl_curve_mutex);
#endif
kfree(info);
}
EXPORT_SYMBOL(framebuffer_release);
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FB_INTERNAL_H
#define _FB_INTERNAL_H
#include <linux/device.h>
#include <linux/fb.h>
#include <linux/mutex.h>
/* fb_devfs.c */
#if defined(CONFIG_FB_DEVICE)
int fb_register_chrdev(void);
void fb_unregister_chrdev(void);
#else
static inline int fb_register_chrdev(void)
{
return 0;
}
static inline void fb_unregister_chrdev(void)
{ }
#endif
/* fb_logo.c */
#if defined(CONFIG_LOGO)
extern bool fb_center_logo;
extern int fb_logo_count;
int fb_prepare_logo(struct fb_info *fb_info, int rotate);
int fb_show_logo(struct fb_info *fb_info, int rotate);
#else
static inline int fb_prepare_logo(struct fb_info *info, int rotate)
{
return 0;
}
static inline int fb_show_logo(struct fb_info *info, int rotate)
{
return 0;
}
#endif /* CONFIG_LOGO */
/* fbmem.c */
extern struct class *fb_class;
extern struct mutex registration_lock;
extern struct fb_info *registered_fb[FB_MAX];
extern int num_registered_fb;
struct fb_info *get_fb_info(unsigned int idx);
void put_fb_info(struct fb_info *fb_info);
int fb_blank_from_user(struct fb_info *info, int blank);
/* fb_procfs.c */
#if defined(CONFIG_FB_DEVICE)
int fb_init_procfs(void);
void fb_cleanup_procfs(void);
#else
static inline int fb_init_procfs(void)
{
return 0;
}
static inline void fb_cleanup_procfs(void)
{ }
#endif
/* fbsysfs.c */
#if defined(CONFIG_FB_DEVICE)
int fb_device_create(struct fb_info *fb_info);
void fb_device_destroy(struct fb_info *fb_info);
#else
static inline int fb_device_create(struct fb_info *fb_info)
{
/*
* Acquire a reference on the parent device to avoid
* unplug operations behind our back. With the fbdev
* device enabled, this is performed within register_device().
*/
get_device(fb_info->device);
return 0;
}
static inline void fb_device_destroy(struct fb_info *fb_info)
{
/* Undo the get_device() from fb_device_create() */
put_device(fb_info->device);
}
#endif
#endif
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/export.h>
#include <linux/fb.h>
#include <linux/module.h>
#include <linux/uaccess.h>
ssize_t fb_io_read(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
u8 *buffer, *dst;
u8 __iomem *src;
int c, cnt = 0, err = 0;
unsigned long total_size, trailing;
if (info->flags & FBINFO_VIRTFB)
fb_warn_once(info, "Framebuffer is not in I/O address space.");
if (!info->screen_base)
return -ENODEV;
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p >= total_size)
return 0;
if (count >= total_size)
count = total_size;
if (count + p > total_size)
count = total_size - p;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
src = (u8 __iomem *) (info->screen_base + p);
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
dst = buffer;
fb_memcpy_fromio(dst, src, c);
dst += c;
src += c;
trailing = copy_to_user(buf, buffer, c);
if (trailing == c) {
err = -EFAULT;
break;
}
c -= trailing;
*ppos += c;
buf += c;
cnt += c;
count -= c;
/*
* If there was a partial copy, the user buffer is faulty.
* Break out to avoid over-advancing the src pointer and
* reading out of bounds in the next iteration.
*/
if (trailing)
break;
}
kfree(buffer);
return cnt ? cnt : err;
}
EXPORT_SYMBOL(fb_io_read);
ssize_t fb_io_write(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
u8 *buffer, *src;
u8 __iomem *dst;
int c, cnt = 0, err = 0;
unsigned long total_size, trailing;
if (info->flags & FBINFO_VIRTFB)
fb_warn_once(info, "Framebuffer is not in I/O address space.");
if (!info->screen_base)
return -ENODEV;
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p > total_size)
return -EFBIG;
if (count > total_size) {
err = -EFBIG;
count = total_size;
}
if (count + p > total_size) {
if (!err)
err = -ENOSPC;
count = total_size - p;
}
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
dst = (u8 __iomem *) (info->screen_base + p);
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
src = buffer;
trailing = copy_from_user(src, buf, c);
if (trailing == c) {
err = -EFAULT;
break;
}
c -= trailing;
fb_memcpy_toio(dst, src, c);
dst += c;
src += c;
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (cnt) ? cnt : err;
}
EXPORT_SYMBOL(fb_io_write);
int fb_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
unsigned long start = info->fix.smem_start;
u32 len = info->fix.smem_len;
unsigned long mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
if (info->flags & FBINFO_VIRTFB)
fb_warn_once(info, "Framebuffer is not in I/O address space.");
/*
* This can be either the framebuffer mapping, or if pgoff points
* past it, the mmio mapping.
*/
if (vma->vm_pgoff >= mmio_pgoff) {
if (info->var.accel_flags)
return -EINVAL;
vma->vm_pgoff -= mmio_pgoff;
start = info->fix.mmio_start;
len = info->fix.mmio_len;
}
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
vma->vm_page_prot = pgprot_framebuffer(vma->vm_page_prot, vma->vm_start,
vma->vm_end, start);
return vm_iomap_memory(vma, start, len);
}
EXPORT_SYMBOL(fb_io_mmap);
MODULE_DESCRIPTION("Fbdev helpers for framebuffers in I/O memory");
MODULE_LICENSE("GPL");
@@ -0,0 +1,508 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/fb.h>
#include <linux/linux_logo.h>
#include "fb_internal.h"
bool fb_center_logo __read_mostly;
int fb_logo_count __read_mostly = -1;
static inline unsigned int safe_shift(unsigned int d, int n)
{
return n < 0 ? d >> -n : d << n;
}
static void fb_set_logocmap(struct fb_info *info,
const struct linux_logo *logo)
{
struct fb_cmap palette_cmap;
u16 palette_green[16];
u16 palette_blue[16];
u16 palette_red[16];
int i, j, n;
const unsigned char *clut = logo->clut;
palette_cmap.start = 0;
palette_cmap.len = 16;
palette_cmap.red = palette_red;
palette_cmap.green = palette_green;
palette_cmap.blue = palette_blue;
palette_cmap.transp = NULL;
for (i = 0; i < logo->clutsize; i += n) {
n = logo->clutsize - i;
/* palette_cmap provides space for only 16 colors at once */
if (n > 16)
n = 16;
palette_cmap.start = 32 + i;
palette_cmap.len = n;
for (j = 0; j < n; ++j) {
palette_cmap.red[j] = clut[0] << 8 | clut[0];
palette_cmap.green[j] = clut[1] << 8 | clut[1];
palette_cmap.blue[j] = clut[2] << 8 | clut[2];
clut += 3;
}
fb_set_cmap(&palette_cmap, info);
}
}
static void fb_set_logo_truepalette(struct fb_info *info,
const struct linux_logo *logo,
u32 *palette)
{
static const unsigned char mask[] = {
0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
};
unsigned char redmask, greenmask, bluemask;
int redshift, greenshift, blueshift;
int i;
const unsigned char *clut = logo->clut;
/*
* We have to create a temporary palette since console palette is only
* 16 colors long.
*/
/* Bug: Doesn't obey msb_right ... (who needs that?) */
redmask = mask[info->var.red.length < 8 ? info->var.red.length : 8];
greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8];
bluemask = mask[info->var.blue.length < 8 ? info->var.blue.length : 8];
redshift = info->var.red.offset - (8 - info->var.red.length);
greenshift = info->var.green.offset - (8 - info->var.green.length);
blueshift = info->var.blue.offset - (8 - info->var.blue.length);
for (i = 0; i < logo->clutsize; i++) {
palette[i+32] = (safe_shift((clut[0] & redmask), redshift) |
safe_shift((clut[1] & greenmask), greenshift) |
safe_shift((clut[2] & bluemask), blueshift));
clut += 3;
}
}
static void fb_set_logo_directpalette(struct fb_info *info,
const struct linux_logo *logo,
u32 *palette)
{
int redshift, greenshift, blueshift;
int i;
redshift = info->var.red.offset;
greenshift = info->var.green.offset;
blueshift = info->var.blue.offset;
for (i = 32; i < 32 + logo->clutsize; i++)
palette[i] = i << redshift | i << greenshift | i << blueshift;
}
static void fb_set_logo(struct fb_info *info,
const struct linux_logo *logo, u8 *dst,
int depth)
{
int i, j, k;
const u8 *src = logo->data;
u8 xor = (info->fix.visual == FB_VISUAL_MONO01) ? 0xff : 0;
u8 fg = 1, d;
switch (fb_get_color_depth(&info->var, &info->fix)) {
case 1:
fg = 1;
break;
case 2:
fg = 3;
break;
default:
fg = 7;
break;
}
if (info->fix.visual == FB_VISUAL_MONO01 ||
info->fix.visual == FB_VISUAL_MONO10)
fg = ~((u8) (0xfff << info->var.green.length));
switch (depth) {
case 4:
for (i = 0; i < logo->height; i++)
for (j = 0; j < logo->width; src++) {
*dst++ = *src >> 4;
j++;
if (j < logo->width) {
*dst++ = *src & 0x0f;
j++;
}
}
break;
case 1:
for (i = 0; i < logo->height; i++) {
for (j = 0; j < logo->width; src++) {
d = *src ^ xor;
for (k = 7; k >= 0 && j < logo->width; k--) {
*dst++ = ((d >> k) & 1) ? fg : 0;
j++;
}
}
}
break;
}
}
/*
* Three (3) kinds of logo maps exist. linux_logo_clut224 (>16 colors),
* linux_logo_vga16 (16 colors) and linux_logo_mono (2 colors). Depending on
* the visual format and color depth of the framebuffer, the DAC, the
* pseudo_palette, and the logo data will be adjusted accordingly.
*
* Case 1 - linux_logo_clut224:
* Color exceeds the number of console colors (16), thus we set the hardware DAC
* using fb_set_cmap() appropriately. The "needs_cmapreset" flag will be set.
*
* For visuals that require color info from the pseudo_palette, we also construct
* one for temporary use. The "needs_directpalette" or "needs_truepalette" flags
* will be set.
*
* Case 2 - linux_logo_vga16:
* The number of colors just matches the console colors, thus there is no need
* to set the DAC or the pseudo_palette. However, the bitmap is packed, ie,
* each byte contains color information for two pixels (upper and lower nibble).
* To be consistent with fb_imageblit() usage, we therefore separate the two
* nibbles into separate bytes. The "depth" flag will be set to 4.
*
* Case 3 - linux_logo_mono:
* This is similar with Case 2. Each byte contains information for 8 pixels.
* We isolate each bit and expand each into a byte. The "depth" flag will
* be set to 1.
*/
static struct logo_data {
int depth;
int needs_directpalette;
int needs_truepalette;
int needs_cmapreset;
const struct linux_logo *logo;
} fb_logo __read_mostly;
static void fb_rotate_logo_ud(const u8 *in, u8 *out, u32 width, u32 height)
{
u32 size = width * height, i;
out += size - 1;
for (i = size; i--; )
*out-- = *in++;
}
static void fb_rotate_logo_cw(const u8 *in, u8 *out, u32 width, u32 height)
{
int i, j, h = height - 1;
for (i = 0; i < height; i++)
for (j = 0; j < width; j++)
out[height * j + h - i] = *in++;
}
static void fb_rotate_logo_ccw(const u8 *in, u8 *out, u32 width, u32 height)
{
int i, j, w = width - 1;
for (i = 0; i < height; i++)
for (j = 0; j < width; j++)
out[height * (w - j) + i] = *in++;
}
static void fb_rotate_logo(struct fb_info *info, u8 *dst,
struct fb_image *image, int rotate)
{
u32 tmp;
if (rotate == FB_ROTATE_UD) {
fb_rotate_logo_ud(image->data, dst, image->width,
image->height);
image->dx = info->var.xres - image->width - image->dx;
image->dy = info->var.yres - image->height - image->dy;
} else if (rotate == FB_ROTATE_CW) {
fb_rotate_logo_cw(image->data, dst, image->width,
image->height);
swap(image->width, image->height);
tmp = image->dy;
image->dy = image->dx;
image->dx = info->var.xres - image->width - tmp;
} else if (rotate == FB_ROTATE_CCW) {
fb_rotate_logo_ccw(image->data, dst, image->width,
image->height);
swap(image->width, image->height);
tmp = image->dx;
image->dx = image->dy;
image->dy = info->var.yres - image->height - tmp;
}
image->data = dst;
}
static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
int rotate, unsigned int num)
{
unsigned int x;
if (image->width > info->var.xres || image->height > info->var.yres)
return;
if (rotate == FB_ROTATE_UR) {
for (x = 0;
x < num && image->dx + image->width <= info->var.xres;
x++) {
info->fbops->fb_imageblit(info, image);
image->dx += image->width + 8;
}
} else if (rotate == FB_ROTATE_UD) {
u32 dx = image->dx;
for (x = 0; x < num && image->dx <= dx; x++) {
info->fbops->fb_imageblit(info, image);
image->dx -= image->width + 8;
}
} else if (rotate == FB_ROTATE_CW) {
for (x = 0;
x < num && image->dy + image->height <= info->var.yres;
x++) {
info->fbops->fb_imageblit(info, image);
image->dy += image->height + 8;
}
} else if (rotate == FB_ROTATE_CCW) {
u32 dy = image->dy;
for (x = 0; x < num && image->dy <= dy; x++) {
info->fbops->fb_imageblit(info, image);
image->dy -= image->height + 8;
}
}
}
static int fb_show_logo_line(struct fb_info *info, int rotate,
const struct linux_logo *logo, int y,
unsigned int n)
{
u32 *palette = NULL, *saved_pseudo_palette = NULL;
unsigned char *logo_new = NULL, *logo_rotate = NULL;
struct fb_image image;
/* Return if the frame buffer is not mapped or suspended */
if (logo == NULL || info->state != FBINFO_STATE_RUNNING ||
info->fbops->owner)
return 0;
image.depth = 8;
image.data = logo->data;
if (fb_logo.needs_cmapreset)
fb_set_logocmap(info, logo);
if (fb_logo.needs_truepalette ||
fb_logo.needs_directpalette) {
palette = kmalloc(256 * 4, GFP_KERNEL);
if (palette == NULL)
return 0;
if (fb_logo.needs_truepalette)
fb_set_logo_truepalette(info, logo, palette);
else
fb_set_logo_directpalette(info, logo, palette);
saved_pseudo_palette = info->pseudo_palette;
info->pseudo_palette = palette;
}
if (fb_logo.depth <= 4) {
logo_new = kmalloc_array(logo->width, logo->height,
GFP_KERNEL);
if (logo_new == NULL) {
kfree(palette);
if (saved_pseudo_palette)
info->pseudo_palette = saved_pseudo_palette;
return 0;
}
image.data = logo_new;
fb_set_logo(info, logo, logo_new, fb_logo.depth);
}
if (fb_center_logo) {
int xres = info->var.xres;
int yres = info->var.yres;
if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) {
xres = info->var.yres;
yres = info->var.xres;
}
while (n && (n * (logo->width + 8) - 8 > xres))
--n;
image.dx = (xres - (n * (logo->width + 8) - 8)) / 2;
image.dy = y ?: (yres - logo->height) / 2;
} else {
image.dx = 0;
image.dy = y;
}
image.width = logo->width;
image.height = logo->height;
if (rotate) {
logo_rotate = kmalloc_array(logo->width, logo->height,
GFP_KERNEL);
if (logo_rotate)
fb_rotate_logo(info, logo_rotate, &image, rotate);
}
fb_do_show_logo(info, &image, rotate, n);
kfree(palette);
if (saved_pseudo_palette != NULL)
info->pseudo_palette = saved_pseudo_palette;
kfree(logo_new);
kfree(logo_rotate);
return image.dy + logo->height;
}
#ifdef CONFIG_FB_LOGO_EXTRA
#define FB_LOGO_EX_NUM_MAX 10
static struct logo_data_extra {
const struct linux_logo *logo;
unsigned int n;
} fb_logo_ex[FB_LOGO_EX_NUM_MAX];
static unsigned int fb_logo_ex_num;
void fb_append_extra_logo(const struct linux_logo *logo, unsigned int n)
{
if (!n || fb_logo_ex_num == FB_LOGO_EX_NUM_MAX)
return;
fb_logo_ex[fb_logo_ex_num].logo = logo;
fb_logo_ex[fb_logo_ex_num].n = n;
fb_logo_ex_num++;
}
static int fb_prepare_extra_logos(struct fb_info *info, unsigned int height,
unsigned int yres)
{
unsigned int i;
/* FIXME: logo_ex supports only truecolor fb. */
if (info->fix.visual != FB_VISUAL_TRUECOLOR)
fb_logo_ex_num = 0;
for (i = 0; i < fb_logo_ex_num; i++) {
if (fb_logo_ex[i].logo->type != fb_logo.logo->type) {
fb_logo_ex[i].logo = NULL;
continue;
}
height += fb_logo_ex[i].logo->height;
if (height > yres) {
height -= fb_logo_ex[i].logo->height;
fb_logo_ex_num = i;
break;
}
}
return height;
}
static int fb_show_extra_logos(struct fb_info *info, int y, int rotate)
{
unsigned int i;
for (i = 0; i < fb_logo_ex_num; i++)
y = fb_show_logo_line(info, rotate,
fb_logo_ex[i].logo, y, fb_logo_ex[i].n);
return y;
}
#endif /* CONFIG_FB_LOGO_EXTRA */
int fb_prepare_logo(struct fb_info *info, int rotate)
{
int depth = fb_get_color_depth(&info->var, &info->fix);
unsigned int yres;
int height;
memset(&fb_logo, 0, sizeof(struct logo_data));
if (info->flags & FBINFO_MISC_TILEBLITTING ||
info->fbops->owner || !fb_logo_count)
return 0;
if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
depth = info->var.blue.length;
if (info->var.red.length < depth)
depth = info->var.red.length;
if (info->var.green.length < depth)
depth = info->var.green.length;
}
if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) {
/* assume console colormap */
depth = 4;
}
/* Return if no suitable logo was found */
fb_logo.logo = fb_find_logo(depth);
if (!fb_logo.logo)
return 0;
if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD)
yres = info->var.yres;
else
yres = info->var.xres;
if (fb_logo.logo->height > yres) {
fb_logo.logo = NULL;
return 0;
}
/* What depth we asked for might be different from what we get */
if (fb_logo.logo->type == LINUX_LOGO_CLUT224)
fb_logo.depth = 8;
else if (fb_logo.logo->type == LINUX_LOGO_VGA16)
fb_logo.depth = 4;
else
fb_logo.depth = 1;
if (fb_logo.depth > 4 && depth > 4) {
switch (info->fix.visual) {
case FB_VISUAL_TRUECOLOR:
fb_logo.needs_truepalette = 1;
break;
case FB_VISUAL_DIRECTCOLOR:
fb_logo.needs_directpalette = 1;
fb_logo.needs_cmapreset = 1;
break;
case FB_VISUAL_PSEUDOCOLOR:
fb_logo.needs_cmapreset = 1;
break;
}
}
height = fb_logo.logo->height;
if (fb_center_logo)
height += (yres - fb_logo.logo->height) / 2;
#ifdef CONFIG_FB_LOGO_EXTRA
height = fb_prepare_extra_logos(info, height, yres);
#endif
return height;
}
int fb_show_logo(struct fb_info *info, int rotate)
{
unsigned int count;
int y;
if (!fb_logo_count)
return 0;
count = fb_logo_count < 0 ? num_online_cpus() : fb_logo_count;
y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, count);
#ifdef CONFIG_FB_LOGO_EXTRA
y = fb_show_extra_logos(info, y, rotate);
#endif
return y;
}
@@ -0,0 +1,54 @@
/*
* linux/drivers/video/fb_notify.c
*
* Copyright (C) 2006 Antonino Daplas <adaplas@pol.net>
*
* 2001 - Documented with DocBook
* - Brad Douglas <brad@neruo.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#include <linux/fb.h>
#include <linux/notifier.h>
#include <linux/export.h>
static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
/**
* fb_register_client - register a client notifier
* @nb: notifier block to callback on events
*
* Return: 0 on success, negative error code on failure.
*/
int fb_register_client(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&fb_notifier_list, nb);
}
EXPORT_SYMBOL(fb_register_client);
/**
* fb_unregister_client - unregister a client notifier
* @nb: notifier block to callback on events
*
* Return: 0 on success, negative error code on failure.
*/
int fb_unregister_client(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&fb_notifier_list, nb);
}
EXPORT_SYMBOL(fb_unregister_client);
/**
* fb_notifier_call_chain - notify clients of fb_events
* @val: value passed to callback
* @v: pointer passed to callback
*
* Return: The return value of the last notifier function
*/
int fb_notifier_call_chain(unsigned long val, void *v)
{
return blocking_notifier_call_chain(&fb_notifier_list, val, v);
}
EXPORT_SYMBOL_GPL(fb_notifier_call_chain);
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/proc_fs.h>
#include "fb_internal.h"
static struct proc_dir_entry *fb_proc_dir_entry;
static void *fb_seq_start(struct seq_file *m, loff_t *pos)
{
mutex_lock(&registration_lock);
return (*pos < FB_MAX) ? pos : NULL;
}
static void fb_seq_stop(struct seq_file *m, void *v)
{
mutex_unlock(&registration_lock);
}
static void *fb_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
(*pos)++;
return (*pos < FB_MAX) ? pos : NULL;
}
static int fb_seq_show(struct seq_file *m, void *v)
{
int i = *(loff_t *)v;
struct fb_info *fi = registered_fb[i];
if (fi)
seq_printf(m, "%d %s\n", fi->node, fi->fix.id);
return 0;
}
static const struct seq_operations __maybe_unused fb_proc_seq_ops = {
.start = fb_seq_start,
.stop = fb_seq_stop,
.next = fb_seq_next,
.show = fb_seq_show,
};
int fb_init_procfs(void)
{
struct proc_dir_entry *proc;
proc = proc_create_seq("fb", 0, NULL, &fb_proc_seq_ops);
if (!proc)
return -ENOMEM;
fb_proc_dir_entry = proc;
return 0;
}
void fb_cleanup_procfs(void)
{
proc_remove(fb_proc_dir_entry);
}
@@ -0,0 +1,116 @@
/*
* linux/drivers/video/fb_sys_read.c - Generic file operations where
* framebuffer is in system RAM
*
* Copyright (C) 2007 Antonino Daplas <adaplas@pol.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*
*/
#include <linux/export.h>
#include <linux/fb.h>
#include <linux/module.h>
#include <linux/uaccess.h>
ssize_t fb_sys_read(struct fb_info *info, char __user *buf, size_t count,
loff_t *ppos)
{
unsigned long p = *ppos;
void *src;
int err = 0;
unsigned long total_size, c;
ssize_t ret;
if (!(info->flags & FBINFO_VIRTFB))
fb_warn_once(info, "Framebuffer is not in virtual address space.");
if (!info->screen_buffer)
return -ENODEV;
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p >= total_size)
return 0;
if (count >= total_size)
count = total_size;
if (count + p > total_size)
count = total_size - p;
src = info->screen_buffer + p;
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
c = copy_to_user(buf, src, count);
if (c)
err = -EFAULT;
ret = count - c;
*ppos += ret;
return ret ? ret : err;
}
EXPORT_SYMBOL_GPL(fb_sys_read);
ssize_t fb_sys_write(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
void *dst;
int err = 0;
unsigned long total_size, c;
size_t ret;
if (!(info->flags & FBINFO_VIRTFB))
fb_warn_once(info, "Framebuffer is not in virtual address space.");
if (!info->screen_buffer)
return -ENODEV;
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p > total_size)
return -EFBIG;
if (count > total_size) {
err = -EFBIG;
count = total_size;
}
if (count + p > total_size) {
if (!err)
err = -ENOSPC;
count = total_size - p;
}
dst = info->screen_buffer + p;
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
c = copy_from_user(dst, buf, count);
if (c)
err = -EFAULT;
ret = count - c;
*ppos += ret;
return ret ? ret : err;
}
EXPORT_SYMBOL_GPL(fb_sys_write);
MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>");
MODULE_DESCRIPTION("Generic file read (fb in system RAM)");
MODULE_LICENSE("GPL");
@@ -0,0 +1,363 @@
/*
* linux/drivers/video/fbcmap.c -- Colormap handling for frame buffer devices
*
* Created 15 Jun 1997 by Geert Uytterhoeven
*
* 2001 - Documented with DocBook
* - Brad Douglas <brad@neruo.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/export.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
static u16 red2[] __read_mostly = {
0x0000, 0xaaaa
};
static u16 green2[] __read_mostly = {
0x0000, 0xaaaa
};
static u16 blue2[] __read_mostly = {
0x0000, 0xaaaa
};
static u16 red4[] __read_mostly = {
0x0000, 0xaaaa, 0x5555, 0xffff
};
static u16 green4[] __read_mostly = {
0x0000, 0xaaaa, 0x5555, 0xffff
};
static u16 blue4[] __read_mostly = {
0x0000, 0xaaaa, 0x5555, 0xffff
};
static u16 red8[] __read_mostly = {
0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa
};
static u16 green8[] __read_mostly = {
0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x0000, 0x0000, 0x5555, 0xaaaa
};
static u16 blue8[] __read_mostly = {
0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa
};
static u16 red16[] __read_mostly = {
0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa,
0x5555, 0x5555, 0x5555, 0x5555, 0xffff, 0xffff, 0xffff, 0xffff
};
static u16 green16[] __read_mostly = {
0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x0000, 0x0000, 0x5555, 0xaaaa,
0x5555, 0x5555, 0xffff, 0xffff, 0x5555, 0x5555, 0xffff, 0xffff
};
static u16 blue16[] __read_mostly = {
0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa,
0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff
};
static const struct fb_cmap default_2_colors = {
.len=2, .red=red2, .green=green2, .blue=blue2
};
static const struct fb_cmap default_8_colors = {
.len=8, .red=red8, .green=green8, .blue=blue8
};
static const struct fb_cmap default_4_colors = {
.len=4, .red=red4, .green=green4, .blue=blue4
};
static const struct fb_cmap default_16_colors = {
.len=16, .red=red16, .green=green16, .blue=blue16
};
/**
* fb_alloc_cmap_gfp - allocate a colormap
* @cmap: frame buffer colormap structure
* @len: length of @cmap
* @transp: boolean, 1 if there is transparency, 0 otherwise
* @flags: flags for kmalloc memory allocation
*
* Allocates memory for a colormap @cmap. @len is the
* number of entries in the palette.
*
* Returns negative errno on error, or zero on success.
*
*/
int fb_alloc_cmap_gfp(struct fb_cmap *cmap, int len, int transp, gfp_t flags)
{
int size = len * sizeof(u16);
int ret = -ENOMEM;
flags |= __GFP_NOWARN;
if (cmap->len != len) {
fb_dealloc_cmap(cmap);
if (!len)
return 0;
cmap->red = kzalloc(size, flags);
if (!cmap->red)
goto fail;
cmap->green = kzalloc(size, flags);
if (!cmap->green)
goto fail;
cmap->blue = kzalloc(size, flags);
if (!cmap->blue)
goto fail;
if (transp) {
cmap->transp = kzalloc(size, flags);
if (!cmap->transp)
goto fail;
} else {
cmap->transp = NULL;
}
}
cmap->start = 0;
cmap->len = len;
ret = fb_copy_cmap(fb_default_cmap(len), cmap);
if (ret)
goto fail;
return 0;
fail:
fb_dealloc_cmap(cmap);
return ret;
}
int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp)
{
return fb_alloc_cmap_gfp(cmap, len, transp, GFP_ATOMIC);
}
/**
* fb_dealloc_cmap - deallocate a colormap
* @cmap: frame buffer colormap structure
*
* Deallocates a colormap that was previously allocated with
* fb_alloc_cmap().
*
*/
void fb_dealloc_cmap(struct fb_cmap *cmap)
{
kfree(cmap->red);
kfree(cmap->green);
kfree(cmap->blue);
kfree(cmap->transp);
cmap->red = cmap->green = cmap->blue = cmap->transp = NULL;
cmap->len = 0;
}
/**
* fb_copy_cmap - copy a colormap
* @from: frame buffer colormap structure
* @to: frame buffer colormap structure
*
* Copy contents of colormap from @from to @to.
*/
int fb_copy_cmap(const struct fb_cmap *from, struct fb_cmap *to)
{
unsigned int tooff = 0, fromoff = 0;
size_t size;
if (to->start > from->start)
fromoff = to->start - from->start;
else
tooff = from->start - to->start;
if (fromoff >= from->len || tooff >= to->len)
return -EINVAL;
size = min_t(size_t, to->len - tooff, from->len - fromoff);
if (size == 0)
return -EINVAL;
size *= sizeof(u16);
memcpy(to->red+tooff, from->red+fromoff, size);
memcpy(to->green+tooff, from->green+fromoff, size);
memcpy(to->blue+tooff, from->blue+fromoff, size);
if (from->transp && to->transp)
memcpy(to->transp+tooff, from->transp+fromoff, size);
return 0;
}
int fb_cmap_to_user(const struct fb_cmap *from, struct fb_cmap_user *to)
{
unsigned int tooff = 0, fromoff = 0;
size_t size;
if (to->start > from->start)
fromoff = to->start - from->start;
else
tooff = from->start - to->start;
if (fromoff >= from->len || tooff >= to->len)
return -EINVAL;
size = min_t(size_t, to->len - tooff, from->len - fromoff);
if (size == 0)
return -EINVAL;
size *= sizeof(u16);
if (copy_to_user(to->red+tooff, from->red+fromoff, size))
return -EFAULT;
if (copy_to_user(to->green+tooff, from->green+fromoff, size))
return -EFAULT;
if (copy_to_user(to->blue+tooff, from->blue+fromoff, size))
return -EFAULT;
if (from->transp && to->transp)
if (copy_to_user(to->transp+tooff, from->transp+fromoff, size))
return -EFAULT;
return 0;
}
/**
* fb_set_cmap - set the colormap
* @cmap: frame buffer colormap structure
* @info: frame buffer info structure
*
* Sets the colormap @cmap for a screen of device @info.
*
* Returns negative errno on error, or zero on success.
*
*/
int fb_set_cmap(struct fb_cmap *cmap, struct fb_info *info)
{
int i, start, rc = 0;
u16 *red, *green, *blue, *transp;
u_int hred, hgreen, hblue, htransp = 0xffff;
red = cmap->red;
green = cmap->green;
blue = cmap->blue;
transp = cmap->transp;
start = cmap->start;
if (start < 0 || (!info->fbops->fb_setcolreg &&
!info->fbops->fb_setcmap))
return -EINVAL;
if (info->fbops->fb_setcmap) {
rc = info->fbops->fb_setcmap(cmap, info);
} else {
for (i = 0; i < cmap->len; i++) {
hred = *red++;
hgreen = *green++;
hblue = *blue++;
if (transp)
htransp = *transp++;
if (info->fbops->fb_setcolreg(start++,
hred, hgreen, hblue,
htransp, info))
break;
}
}
if (rc == 0)
fb_copy_cmap(cmap, &info->cmap);
return rc;
}
int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info)
{
int rc, size = cmap->len * sizeof(u16);
struct fb_cmap umap;
if (size < 0 || size < cmap->len)
return -E2BIG;
memset(&umap, 0, sizeof(struct fb_cmap));
rc = fb_alloc_cmap_gfp(&umap, cmap->len, cmap->transp != NULL,
GFP_KERNEL);
if (rc)
return rc;
if (copy_from_user(umap.red, cmap->red, size) ||
copy_from_user(umap.green, cmap->green, size) ||
copy_from_user(umap.blue, cmap->blue, size) ||
(cmap->transp && copy_from_user(umap.transp, cmap->transp, size))) {
rc = -EFAULT;
goto out;
}
umap.start = cmap->start;
lock_fb_info(info);
rc = fb_set_cmap(&umap, info);
unlock_fb_info(info);
out:
fb_dealloc_cmap(&umap);
return rc;
}
/**
* fb_default_cmap - get default colormap
* @len: size of palette for a depth
*
* Gets the default colormap for a specific screen depth. @len
* is the size of the palette for a particular screen depth.
*
* Returns pointer to a frame buffer colormap structure.
*
*/
const struct fb_cmap *fb_default_cmap(int len)
{
if (len <= 2)
return &default_2_colors;
if (len <= 4)
return &default_4_colors;
if (len <= 8)
return &default_8_colors;
return &default_16_colors;
}
/**
* fb_invert_cmaps - invert all defaults colormaps
*
* Invert all default colormaps.
*
*/
void fb_invert_cmaps(void)
{
u_int i;
for (i = 0; i < ARRAY_SIZE(red2); i++) {
red2[i] = ~red2[i];
green2[i] = ~green2[i];
blue2[i] = ~blue2[i];
}
for (i = 0; i < ARRAY_SIZE(red4); i++) {
red4[i] = ~red4[i];
green4[i] = ~green4[i];
blue4[i] = ~blue4[i];
}
for (i = 0; i < ARRAY_SIZE(red8); i++) {
red8[i] = ~red8[i];
green8[i] = ~green8[i];
blue8[i] = ~blue8[i];
}
for (i = 0; i < ARRAY_SIZE(red16); i++) {
red16[i] = ~red16[i];
green16[i] = ~green16[i];
blue16[i] = ~blue16[i];
}
}
/*
* Visible symbols for modules
*/
EXPORT_SYMBOL(fb_alloc_cmap);
EXPORT_SYMBOL(fb_dealloc_cmap);
EXPORT_SYMBOL(fb_copy_cmap);
EXPORT_SYMBOL(fb_set_cmap);
EXPORT_SYMBOL(fb_default_cmap);
EXPORT_SYMBOL(fb_invert_cmaps);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,286 @@
/*
* linux/drivers/video/console/fbcon.h -- Low level frame buffer based console driver
*
* Copyright (C) 1997 Geert Uytterhoeven
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#ifndef _VIDEO_FBCON_H
#define _VIDEO_FBCON_H
#include <linux/compiler_types.h>
#include <linux/font.h>
#include <linux/types.h>
#include <linux/vt_buffer.h>
#include <linux/vt_kern.h>
#include <linux/workqueue.h>
#include <asm/io.h>
struct fb_blit_caps;
struct fb_info;
struct fb_var_screeninfo;
struct fb_videomode;
/*
* This is the interface between the low-level console driver and the
* low-level frame buffer device
*/
struct fbcon_display {
/* Filled in by the low-level console driver */
font_data_t *fontdata;
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
u_short scrollmode; /* Scroll Method, use fb_scrollmode() */
#endif
short yscroll; /* Hardware scrolling */
int vrows; /* number of virtual rows */
int cursor_shape;
int con_rotate;
u32 xres_virtual;
u32 yres_virtual;
u32 height;
u32 width;
u32 bits_per_pixel;
u32 grayscale;
u32 nonstd;
u32 accel_flags;
u32 rotate;
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
const struct fb_videomode *mode;
};
struct fbcon_bitops {
void (*bmove)(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int dy, int dx, int height, int width);
void (*clear)(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int height, int width, int fb, int bg);
void (*putcs)(struct vc_data *vc, struct fb_info *info,
const unsigned short *s, int count, int yy, int xx,
int fg, int bg);
void (*clear_margins)(struct vc_data *vc, struct fb_info *info,
int color, int bottom_only);
void (*cursor)(struct vc_data *vc, struct fb_info *info,
bool enable, int fg, int bg);
int (*update_start)(struct fb_info *info);
int (*rotate_font)(struct fb_info *info, struct vc_data *vc);
};
struct fbcon_par {
struct fb_var_screeninfo var; /* copy of the current fb_var_screeninfo */
struct delayed_work cursor_work; /* Cursor timer */
struct fb_cursor cursor_state;
struct fbcon_display *p;
struct fb_info *info;
int currcon; /* Current VC. */
int cur_blink_jiffies;
int cursor_flash;
int cursor_reset;
int blank_state;
int graphics;
bool initialized;
int rotate;
char *cursor_data;
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION
struct {
font_data_t *fontdata; /* source font */
u8 *buf; /* rotated glyphs */
size_t bufsize;
int buf_rotate; /* rotation of buf */
} rotated;
#endif
u8 *cursor_src;
u32 cursor_size;
const struct fbcon_bitops *bitops;
};
/*
* Attribute Decoding
*/
/* Color */
#define attr_fgcol(fgshift,s) \
(((s) >> (fgshift)) & 0x0f)
#define attr_bgcol(bgshift,s) \
(((s) >> (bgshift)) & 0x0f)
/* Monochrome */
#define attr_bold(s) \
((s) & 0x200)
#define attr_reverse(s) \
((s) & 0x800)
#define attr_underline(s) \
((s) & 0x400)
#define attr_blink(s) \
((s) & 0x8000)
static inline int mono_col(const struct fb_info *info)
{
__u32 max_len;
max_len = max(info->var.green.length, info->var.red.length);
max_len = max(info->var.blue.length, max_len);
return (~(0xfff << max_len)) & 0xff;
}
/*
* Scroll Method
*/
/* There are several methods fbcon can use to move text around the screen:
*
* Operation Pan Wrap
*---------------------------------------------
* SCROLL_MOVE copyarea No No
* SCROLL_PAN_MOVE copyarea Yes No
* SCROLL_WRAP_MOVE copyarea No Yes
* SCROLL_REDRAW imageblit No No
* SCROLL_PAN_REDRAW imageblit Yes No
* SCROLL_WRAP_REDRAW imageblit No Yes
*
* (SCROLL_WRAP_REDRAW is not implemented yet)
*
* In general, fbcon will choose the best scrolling
* method based on the rule below:
*
* Pan/Wrap > accel imageblit > accel copyarea >
* soft imageblit > (soft copyarea)
*
* Exception to the rule: Pan + accel copyarea is
* preferred over Pan + accel imageblit.
*
* The above is typical for PCI/AGP cards. Unless
* overridden, fbcon will never use soft copyarea.
*
* If you need to override the above rule, set the
* appropriate flags in fb_info->flags. For example,
* to prefer copyarea over imageblit, set
* FBINFO_READS_FAST.
*
* Other notes:
* + use the hardware engine to move the text
* (hw-accelerated copyarea() and fillrect())
* + use hardware-supported panning on a large virtual screen
* + amifb can not only pan, but also wrap the display by N lines
* (i.e. visible line i = physical line (i+N) % yres).
* + read what's already rendered on the screen and
* write it in a different place (this is cfb_copyarea())
* + re-render the text to the screen
*
* Whether to use wrapping or panning can only be figured out at
* runtime (when we know whether our font height is a multiple
* of the pan/wrap step)
*
*/
#define SCROLL_MOVE 0x001
#define SCROLL_PAN_MOVE 0x002
#define SCROLL_WRAP_MOVE 0x003
#define SCROLL_REDRAW 0x004
#define SCROLL_PAN_REDRAW 0x005
static inline u_short fb_scrollmode(struct fbcon_display *fb)
{
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
return fb->scrollmode;
#else
/* hardcoded to SCROLL_REDRAW if acceleration was disabled. */
return SCROLL_REDRAW;
#endif
}
#ifdef CONFIG_FB_TILEBLITTING
extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info);
#endif
extern void fbcon_set_bitops_ur(struct fbcon_par *par);
extern int soft_cursor(struct fb_info *info, struct fb_cursor *cursor);
void fbcon_fill_cursor_mask(struct fbcon_par *par, struct vc_data *vc, unsigned char *mask);
#define FBCON_ATTRIBUTE_UNDERLINE 1
#define FBCON_ATTRIBUTE_REVERSE 2
#define FBCON_ATTRIBUTE_BOLD 4
static inline int real_y(struct fbcon_display *p, int ypos)
{
int rows = p->vrows;
ypos += p->yscroll;
return ypos < rows ? ypos : ypos - rows;
}
static inline int get_attribute(struct fb_info *info, u16 c)
{
int attribute = 0;
if (fb_get_color_depth(&info->var, &info->fix) == 1) {
if (attr_underline(c))
attribute |= FBCON_ATTRIBUTE_UNDERLINE;
if (attr_reverse(c))
attribute |= FBCON_ATTRIBUTE_REVERSE;
if (attr_bold(c))
attribute |= FBCON_ATTRIBUTE_BOLD;
}
return attribute;
}
#define FBCON_SWAP(i,r,v) ({ \
typeof(r) _r = (r); \
typeof(v) _v = (v); \
(void) (&_r == &_v); \
(i == FB_ROTATE_UR || i == FB_ROTATE_UD) ? _r : _v; })
#ifdef CONFIG_FRAMEBUFFER_CONSOLE
void __init fb_console_init(void);
void __exit fb_console_exit(void);
int fbcon_fb_registered(struct fb_info *info);
void fbcon_fb_unregistered(struct fb_info *info);
void fbcon_fb_unbind(struct fb_info *info);
void fbcon_suspended(struct fb_info *info);
void fbcon_resumed(struct fb_info *info);
int fbcon_mode_deleted(struct fb_info *info,
struct fb_videomode *mode);
void fbcon_delete_modelist(struct list_head *head);
void fbcon_new_modelist(struct fb_info *info);
void fbcon_get_requirement(struct fb_info *info,
struct fb_blit_caps *caps);
void fbcon_fb_blanked(struct fb_info *info, int blank);
int fbcon_modechange_possible(struct fb_info *info,
struct fb_var_screeninfo *var);
void fbcon_update_vcs(struct fb_info *info, bool all);
void fbcon_remap_all(struct fb_info *info);
int fbcon_set_con2fb_map_ioctl(void __user *argp);
int fbcon_get_con2fb_map_ioctl(void __user *argp);
#else
static inline void fb_console_init(void) {}
static inline void fb_console_exit(void) {}
static inline int fbcon_fb_registered(struct fb_info *info) { return 0; }
static inline void fbcon_fb_unregistered(struct fb_info *info) {}
static inline void fbcon_fb_unbind(struct fb_info *info) {}
static inline void fbcon_suspended(struct fb_info *info) {}
static inline void fbcon_resumed(struct fb_info *info) {}
static inline int fbcon_mode_deleted(struct fb_info *info,
struct fb_videomode *mode) { return 0; }
static inline void fbcon_delete_modelist(struct list_head *head) {}
static inline void fbcon_new_modelist(struct fb_info *info) {}
static inline void fbcon_get_requirement(struct fb_info *info,
struct fb_blit_caps *caps) {}
static inline void fbcon_fb_blanked(struct fb_info *info, int blank) {}
static inline int fbcon_modechange_possible(struct fb_info *info,
struct fb_var_screeninfo *var) { return 0; }
static inline void fbcon_update_vcs(struct fb_info *info, bool all) {}
static inline void fbcon_remap_all(struct fb_info *info) {}
static inline int fbcon_set_con2fb_map_ioctl(void __user *argp) { return 0; }
static inline int fbcon_get_con2fb_map_ioctl(void __user *argp) { return 0; }
#endif
#endif /* _VIDEO_FBCON_H */
@@ -0,0 +1,376 @@
/*
* linux/drivers/video/console/fbcon_ccw.c -- Software Rotation - 270 degrees
*
* Copyright (C) 2005 Antonino Daplas <adaplas @pol.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/font.h>
#include <linux/vt_kern.h>
#include <linux/console.h>
#include <asm/types.h>
#include "fbcon.h"
#include "fbcon_rotate.h"
/*
* Rotation 270 degrees
*/
static void ccw_update_attr(u8 *dst, u8 *src, int attribute,
struct vc_data *vc)
{
int i, j, offset = (vc->vc_font.height < 10) ? 1 : 2;
int width = font_glyph_pitch(vc->vc_font.height);
int mod = vc->vc_font.height % 8;
u8 c, msk = ~(0xff << offset), msk1 = 0;
if (mod)
msk <<= (8 - mod);
if (offset > mod)
msk1 |= 0x01;
for (i = 0; i < vc->vc_font.width; i++) {
for (j = 0; j < width; j++) {
c = *src;
if (attribute & FBCON_ATTRIBUTE_UNDERLINE) {
if (j == width - 1)
c |= msk;
if (msk1 && j == width - 2)
c |= msk1;
}
if (attribute & FBCON_ATTRIBUTE_BOLD && i)
*(dst - width) |= c;
if (attribute & FBCON_ATTRIBUTE_REVERSE)
c = ~c;
src++;
*dst++ = c;
}
}
}
static void ccw_bmove(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int dy, int dx, int height, int width)
{
struct fbcon_par *par = info->fbcon_par;
struct fb_copyarea area;
u32 vyres = GETVYRES(par->p, info);
area.sx = sy * vc->vc_font.height;
area.sy = vyres - ((sx + width) * vc->vc_font.width);
area.dx = dy * vc->vc_font.height;
area.dy = vyres - ((dx + width) * vc->vc_font.width);
area.width = height * vc->vc_font.height;
area.height = width * vc->vc_font.width;
info->fbops->fb_copyarea(info, &area);
}
static void ccw_clear(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int height, int width, int fg, int bg)
{
struct fbcon_par *par = info->fbcon_par;
struct fb_fillrect region;
u32 vyres = GETVYRES(par->p, info);
region.color = bg;
region.dx = sy * vc->vc_font.height;
region.dy = vyres - ((sx + width) * vc->vc_font.width);
region.height = width * vc->vc_font.width;
region.width = height * vc->vc_font.height;
region.rop = ROP_COPY;
info->fbops->fb_fillrect(info, &region);
}
static inline void ccw_putcs_aligned(struct vc_data *vc, struct fb_info *info,
const u16 *s, u32 attr, u32 cnt,
u32 d_pitch, u32 s_pitch, u32 cellsize,
struct fb_image *image, u8 *buf, u8 *dst)
{
struct fbcon_par *par = info->fbcon_par;
u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
u32 idx = font_glyph_pitch(vc->vc_font.height);
u8 *src;
while (cnt--) {
src = par->rotated.buf + (scr_readw(s--) & charmask) * cellsize;
if (attr) {
ccw_update_attr(buf, src, attr, vc);
src = buf;
}
if (likely(idx == 1))
__fb_pad_aligned_buffer(dst, d_pitch, src, idx,
vc->vc_font.width);
else
fb_pad_aligned_buffer(dst, d_pitch, src, idx,
vc->vc_font.width);
dst += d_pitch * vc->vc_font.width;
}
info->fbops->fb_imageblit(info, image);
}
static void ccw_putcs(struct vc_data *vc, struct fb_info *info,
const unsigned short *s, int count, int yy, int xx,
int fg, int bg)
{
struct fb_image image;
struct fbcon_par *par = info->fbcon_par;
u32 width = font_glyph_pitch(vc->vc_font.height);
u32 cellsize = width * vc->vc_font.width;
u32 maxcnt = info->pixmap.size/cellsize;
u32 scan_align = info->pixmap.scan_align - 1;
u32 buf_align = info->pixmap.buf_align - 1;
u32 cnt, pitch, size;
u32 attribute = get_attribute(info, scr_readw(s));
u8 *dst, *buf = NULL;
u32 vyres = GETVYRES(par->p, info);
if (!par->rotated.buf)
return;
image.fg_color = fg;
image.bg_color = bg;
image.dx = yy * vc->vc_font.height;
image.dy = vyres - ((xx + count) * vc->vc_font.width);
image.width = vc->vc_font.height;
image.depth = 1;
if (attribute) {
buf = kmalloc(cellsize, GFP_KERNEL);
if (!buf)
return;
}
s += count - 1;
while (count) {
if (count > maxcnt)
cnt = maxcnt;
else
cnt = count;
image.height = vc->vc_font.width * cnt;
pitch = ((image.width + 7) >> 3) + scan_align;
pitch &= ~scan_align;
size = pitch * image.height + buf_align;
size &= ~buf_align;
dst = fb_get_buffer_offset(info, &info->pixmap, size);
image.data = dst;
ccw_putcs_aligned(vc, info, s, attribute, cnt, pitch,
width, cellsize, &image, buf, dst);
image.dy += image.height;
count -= cnt;
s -= cnt;
}
/* buf is always NULL except when in monochrome mode, so in this case
it's a gain to check buf against NULL even though kfree() handles
NULL pointers just fine */
if (unlikely(buf))
kfree(buf);
}
static void ccw_clear_margins(struct vc_data *vc, struct fb_info *info,
int color, int bottom_only)
{
unsigned int cw = vc->vc_font.width;
unsigned int ch = vc->vc_font.height;
unsigned int rw = info->var.yres - (vc->vc_cols*cw);
unsigned int bh = info->var.xres - (vc->vc_rows*ch);
unsigned int bs = vc->vc_rows*ch;
struct fb_fillrect region;
region.color = color;
region.rop = ROP_COPY;
if ((int) rw > 0 && !bottom_only) {
region.dx = 0;
region.dy = info->var.yoffset;
region.height = rw;
region.width = info->var.xres_virtual;
info->fbops->fb_fillrect(info, &region);
}
if ((int) bh > 0) {
region.dx = info->var.xoffset + bs;
region.dy = 0;
region.height = info->var.yres_virtual;
region.width = bh;
info->fbops->fb_fillrect(info, &region);
}
}
static void ccw_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
int fg, int bg)
{
struct fb_cursor cursor;
struct fbcon_par *par = info->fbcon_par;
unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
int w = font_glyph_pitch(vc->vc_font.height);
int c;
int y = real_y(par->p, vc->state.y);
int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
int err = 1, dx, dy;
char *src;
u32 vyres = GETVYRES(par->p, info);
if (!par->rotated.buf)
return;
cursor.set = 0;
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
src = par->rotated.buf + ((c & charmask) * (w * vc->vc_font.width));
if (par->cursor_state.image.data != src ||
par->cursor_reset) {
par->cursor_state.image.data = src;
cursor.set |= FB_CUR_SETIMAGE;
}
if (attribute) {
u8 *dst;
dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC);
if (!dst)
return;
kfree(par->cursor_data);
par->cursor_data = dst;
ccw_update_attr(dst, src, attribute, vc);
src = dst;
}
if (par->cursor_state.image.fg_color != fg ||
par->cursor_state.image.bg_color != bg ||
par->cursor_reset) {
par->cursor_state.image.fg_color = fg;
par->cursor_state.image.bg_color = bg;
cursor.set |= FB_CUR_SETCMAP;
}
if (par->cursor_state.image.height != vc->vc_font.width ||
par->cursor_state.image.width != vc->vc_font.height ||
par->cursor_reset) {
par->cursor_state.image.height = vc->vc_font.width;
par->cursor_state.image.width = vc->vc_font.height;
cursor.set |= FB_CUR_SETSIZE;
}
dx = y * vc->vc_font.height;
dy = vyres - ((vc->state.x + 1) * vc->vc_font.width);
if (par->cursor_state.image.dx != dx ||
par->cursor_state.image.dy != dy ||
par->cursor_reset) {
par->cursor_state.image.dx = dx;
par->cursor_state.image.dy = dy;
cursor.set |= FB_CUR_SETPOS;
}
if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
par->cursor_reset) {
par->cursor_state.hot.x = cursor.hot.y = 0;
cursor.set |= FB_CUR_SETHOT;
}
if (cursor.set & FB_CUR_SETSIZE ||
vc->vc_cursor_type != par->p->cursor_shape ||
par->cursor_state.mask == NULL ||
par->cursor_reset) {
unsigned char *tmp, *mask;
tmp = kmalloc_array(vc->vc_font.height, vc_font_pitch(&vc->vc_font), GFP_ATOMIC);
if (!tmp)
return;
fbcon_fill_cursor_mask(par, vc, tmp);
mask = kmalloc_array(vc->vc_font.width, w, GFP_ATOMIC);
if (!mask) {
kfree(tmp);
return;
}
font_glyph_rotate_270(tmp, vc->vc_font.width, vc->vc_font.height, mask);
kfree(tmp);
kfree(par->cursor_state.mask);
par->cursor_state.mask = (const char *)mask;
par->p->cursor_shape = vc->vc_cursor_type;
cursor.set |= FB_CUR_SETSHAPE;
}
par->cursor_state.enable = enable && !use_sw;
cursor.image.data = src;
cursor.image.fg_color = par->cursor_state.image.fg_color;
cursor.image.bg_color = par->cursor_state.image.bg_color;
cursor.image.dx = par->cursor_state.image.dx;
cursor.image.dy = par->cursor_state.image.dy;
cursor.image.height = par->cursor_state.image.height;
cursor.image.width = par->cursor_state.image.width;
cursor.hot.x = par->cursor_state.hot.x;
cursor.hot.y = par->cursor_state.hot.y;
cursor.mask = par->cursor_state.mask;
cursor.enable = par->cursor_state.enable;
cursor.image.depth = 1;
cursor.rop = ROP_XOR;
if (info->fbops->fb_cursor)
err = info->fbops->fb_cursor(info, &cursor);
if (err)
soft_cursor(info, &cursor);
par->cursor_reset = 0;
}
static int ccw_update_start(struct fb_info *info)
{
struct fbcon_par *par = info->fbcon_par;
u32 yoffset;
u32 vyres = GETVYRES(par->p, info);
int err;
yoffset = (vyres - info->var.yres) - par->var.xoffset;
par->var.xoffset = par->var.yoffset;
par->var.yoffset = yoffset;
err = fb_pan_display(info, &par->var);
par->var.xoffset = info->var.xoffset;
par->var.yoffset = info->var.yoffset;
par->var.vmode = info->var.vmode;
return err;
}
static const struct fbcon_bitops ccw_fbcon_bitops = {
.bmove = ccw_bmove,
.clear = ccw_clear,
.putcs = ccw_putcs,
.clear_margins = ccw_clear_margins,
.cursor = ccw_cursor,
.update_start = ccw_update_start,
.rotate_font = fbcon_rotate_font,
};
void fbcon_set_bitops_ccw(struct fbcon_par *par)
{
par->bitops = &ccw_fbcon_bitops;
}
@@ -0,0 +1,359 @@
/*
* linux/drivers/video/console/fbcon_ud.c -- Software Rotation - 90 degrees
*
* Copyright (C) 2005 Antonino Daplas <adaplas @pol.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/font.h>
#include <linux/vt_kern.h>
#include <linux/console.h>
#include <asm/types.h>
#include "fbcon.h"
#include "fbcon_rotate.h"
/*
* Rotation 90 degrees
*/
static void cw_update_attr(u8 *dst, u8 *src, int attribute,
struct vc_data *vc)
{
int i, j, offset = (vc->vc_font.height < 10) ? 1 : 2;
int width = font_glyph_pitch(vc->vc_font.height);
u8 c, msk = ~(0xff >> offset);
for (i = 0; i < vc->vc_font.width; i++) {
for (j = 0; j < width; j++) {
c = *src;
if (attribute & FBCON_ATTRIBUTE_UNDERLINE && !j)
c |= msk;
if (attribute & FBCON_ATTRIBUTE_BOLD && i)
c |= *(src-width);
if (attribute & FBCON_ATTRIBUTE_REVERSE)
c = ~c;
src++;
*dst++ = c;
}
}
}
static void cw_bmove(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int dy, int dx, int height, int width)
{
struct fbcon_par *par = info->fbcon_par;
struct fb_copyarea area;
u32 vxres = GETVXRES(par->p, info);
area.sx = vxres - ((sy + height) * vc->vc_font.height);
area.sy = sx * vc->vc_font.width;
area.dx = vxres - ((dy + height) * vc->vc_font.height);
area.dy = dx * vc->vc_font.width;
area.width = height * vc->vc_font.height;
area.height = width * vc->vc_font.width;
info->fbops->fb_copyarea(info, &area);
}
static void cw_clear(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int height, int width, int fg, int bg)
{
struct fbcon_par *par = info->fbcon_par;
struct fb_fillrect region;
u32 vxres = GETVXRES(par->p, info);
region.color = bg;
region.dx = vxres - ((sy + height) * vc->vc_font.height);
region.dy = sx * vc->vc_font.width;
region.height = width * vc->vc_font.width;
region.width = height * vc->vc_font.height;
region.rop = ROP_COPY;
info->fbops->fb_fillrect(info, &region);
}
static inline void cw_putcs_aligned(struct vc_data *vc, struct fb_info *info,
const u16 *s, u32 attr, u32 cnt,
u32 d_pitch, u32 s_pitch, u32 cellsize,
struct fb_image *image, u8 *buf, u8 *dst)
{
struct fbcon_par *par = info->fbcon_par;
u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
u32 idx = font_glyph_pitch(vc->vc_font.height);
u8 *src;
while (cnt--) {
src = par->rotated.buf + (scr_readw(s++) & charmask) * cellsize;
if (attr) {
cw_update_attr(buf, src, attr, vc);
src = buf;
}
if (likely(idx == 1))
__fb_pad_aligned_buffer(dst, d_pitch, src, idx,
vc->vc_font.width);
else
fb_pad_aligned_buffer(dst, d_pitch, src, idx,
vc->vc_font.width);
dst += d_pitch * vc->vc_font.width;
}
info->fbops->fb_imageblit(info, image);
}
static void cw_putcs(struct vc_data *vc, struct fb_info *info,
const unsigned short *s, int count, int yy, int xx,
int fg, int bg)
{
struct fb_image image;
struct fbcon_par *par = info->fbcon_par;
u32 width = font_glyph_pitch(vc->vc_font.height);
u32 cellsize = width * vc->vc_font.width;
u32 maxcnt = info->pixmap.size/cellsize;
u32 scan_align = info->pixmap.scan_align - 1;
u32 buf_align = info->pixmap.buf_align - 1;
u32 cnt, pitch, size;
u32 attribute = get_attribute(info, scr_readw(s));
u8 *dst, *buf = NULL;
u32 vxres = GETVXRES(par->p, info);
if (!par->rotated.buf)
return;
image.fg_color = fg;
image.bg_color = bg;
image.dx = vxres - ((yy + 1) * vc->vc_font.height);
image.dy = xx * vc->vc_font.width;
image.width = vc->vc_font.height;
image.depth = 1;
if (attribute) {
buf = kmalloc(cellsize, GFP_KERNEL);
if (!buf)
return;
}
while (count) {
if (count > maxcnt)
cnt = maxcnt;
else
cnt = count;
image.height = vc->vc_font.width * cnt;
pitch = ((image.width + 7) >> 3) + scan_align;
pitch &= ~scan_align;
size = pitch * image.height + buf_align;
size &= ~buf_align;
dst = fb_get_buffer_offset(info, &info->pixmap, size);
image.data = dst;
cw_putcs_aligned(vc, info, s, attribute, cnt, pitch,
width, cellsize, &image, buf, dst);
image.dy += image.height;
count -= cnt;
s += cnt;
}
/* buf is always NULL except when in monochrome mode, so in this case
it's a gain to check buf against NULL even though kfree() handles
NULL pointers just fine */
if (unlikely(buf))
kfree(buf);
}
static void cw_clear_margins(struct vc_data *vc, struct fb_info *info,
int color, int bottom_only)
{
unsigned int cw = vc->vc_font.width;
unsigned int ch = vc->vc_font.height;
unsigned int rw = info->var.yres - (vc->vc_cols*cw);
unsigned int bh = info->var.xres - (vc->vc_rows*ch);
unsigned int rs = info->var.yres - rw;
struct fb_fillrect region;
region.color = color;
region.rop = ROP_COPY;
if ((int) rw > 0 && !bottom_only) {
region.dx = 0;
region.dy = info->var.yoffset + rs;
region.height = rw;
region.width = info->var.xres_virtual;
info->fbops->fb_fillrect(info, &region);
}
if ((int) bh > 0) {
region.dx = info->var.xoffset;
region.dy = info->var.yoffset;
region.height = info->var.yres;
region.width = bh;
info->fbops->fb_fillrect(info, &region);
}
}
static void cw_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
int fg, int bg)
{
struct fb_cursor cursor;
struct fbcon_par *par = info->fbcon_par;
unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
int w = font_glyph_pitch(vc->vc_font.height);
int c;
int y = real_y(par->p, vc->state.y);
int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
int err = 1, dx, dy;
char *src;
u32 vxres = GETVXRES(par->p, info);
if (!par->rotated.buf)
return;
cursor.set = 0;
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
src = par->rotated.buf + ((c & charmask) * (w * vc->vc_font.width));
if (par->cursor_state.image.data != src ||
par->cursor_reset) {
par->cursor_state.image.data = src;
cursor.set |= FB_CUR_SETIMAGE;
}
if (attribute) {
u8 *dst;
dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC);
if (!dst)
return;
kfree(par->cursor_data);
par->cursor_data = dst;
cw_update_attr(dst, src, attribute, vc);
src = dst;
}
if (par->cursor_state.image.fg_color != fg ||
par->cursor_state.image.bg_color != bg ||
par->cursor_reset) {
par->cursor_state.image.fg_color = fg;
par->cursor_state.image.bg_color = bg;
cursor.set |= FB_CUR_SETCMAP;
}
if (par->cursor_state.image.height != vc->vc_font.width ||
par->cursor_state.image.width != vc->vc_font.height ||
par->cursor_reset) {
par->cursor_state.image.height = vc->vc_font.width;
par->cursor_state.image.width = vc->vc_font.height;
cursor.set |= FB_CUR_SETSIZE;
}
dx = vxres - ((y * vc->vc_font.height) + vc->vc_font.height);
dy = vc->state.x * vc->vc_font.width;
if (par->cursor_state.image.dx != dx ||
par->cursor_state.image.dy != dy ||
par->cursor_reset) {
par->cursor_state.image.dx = dx;
par->cursor_state.image.dy = dy;
cursor.set |= FB_CUR_SETPOS;
}
if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
par->cursor_reset) {
par->cursor_state.hot.x = cursor.hot.y = 0;
cursor.set |= FB_CUR_SETHOT;
}
if (cursor.set & FB_CUR_SETSIZE ||
vc->vc_cursor_type != par->p->cursor_shape ||
par->cursor_state.mask == NULL ||
par->cursor_reset) {
unsigned char *tmp, *mask;
tmp = kmalloc_array(vc->vc_font.height, vc_font_pitch(&vc->vc_font), GFP_ATOMIC);
if (!tmp)
return;
fbcon_fill_cursor_mask(par, vc, tmp);
mask = kmalloc_array(vc->vc_font.width, w, GFP_ATOMIC);
if (!mask) {
kfree(tmp);
return;
}
font_glyph_rotate_90(tmp, vc->vc_font.width, vc->vc_font.height, mask);
kfree(tmp);
kfree(par->cursor_state.mask);
par->cursor_state.mask = (const char *)mask;
par->p->cursor_shape = vc->vc_cursor_type;
cursor.set |= FB_CUR_SETSHAPE;
}
par->cursor_state.enable = enable && !use_sw;
cursor.image.data = src;
cursor.image.fg_color = par->cursor_state.image.fg_color;
cursor.image.bg_color = par->cursor_state.image.bg_color;
cursor.image.dx = par->cursor_state.image.dx;
cursor.image.dy = par->cursor_state.image.dy;
cursor.image.height = par->cursor_state.image.height;
cursor.image.width = par->cursor_state.image.width;
cursor.hot.x = par->cursor_state.hot.x;
cursor.hot.y = par->cursor_state.hot.y;
cursor.mask = par->cursor_state.mask;
cursor.enable = par->cursor_state.enable;
cursor.image.depth = 1;
cursor.rop = ROP_XOR;
if (info->fbops->fb_cursor)
err = info->fbops->fb_cursor(info, &cursor);
if (err)
soft_cursor(info, &cursor);
par->cursor_reset = 0;
}
static int cw_update_start(struct fb_info *info)
{
struct fbcon_par *par = info->fbcon_par;
u32 vxres = GETVXRES(par->p, info);
u32 xoffset;
int err;
xoffset = vxres - (info->var.xres + par->var.yoffset);
par->var.yoffset = par->var.xoffset;
par->var.xoffset = xoffset;
err = fb_pan_display(info, &par->var);
par->var.xoffset = info->var.xoffset;
par->var.yoffset = info->var.yoffset;
par->var.vmode = info->var.vmode;
return err;
}
static const struct fbcon_bitops cw_fbcon_bitops = {
.bmove = cw_bmove,
.clear = cw_clear,
.putcs = cw_putcs,
.clear_margins = cw_clear_margins,
.cursor = cw_cursor,
.update_start = cw_update_start,
.rotate_font = fbcon_rotate_font,
};
void fbcon_set_bitops_cw(struct fbcon_par *par)
{
par->bitops = &cw_fbcon_bitops;
}
@@ -0,0 +1,52 @@
/*
* linux/drivers/video/console/fbcon_rotate.c -- Software Rotation
*
* Copyright (C) 2005 Antonino Daplas <adaplas @pol.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/errno.h>
#include <linux/fb.h>
#include <linux/font.h>
#include "fbcon.h"
#include "fbcon_rotate.h"
int fbcon_rotate_font(struct fb_info *info, struct vc_data *vc)
{
struct fbcon_par *par = info->fbcon_par;
unsigned char *buf;
int ret;
if (par->p->fontdata == par->rotated.fontdata && par->rotate == par->rotated.buf_rotate)
return 0;
par->rotated.fontdata = par->p->fontdata;
par->rotated.buf_rotate = par->rotate;
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
buf = font_data_rotate(par->rotated.fontdata, vc->vc_font.width,
vc->vc_font.height, vc->vc_font.charcount,
par->rotated.buf_rotate, par->rotated.buf,
&par->rotated.bufsize);
if (IS_ERR(buf)) {
ret = PTR_ERR(buf);
goto err_kfree;
}
par->rotated.buf = buf;
return 0;
err_kfree:
kfree(par->rotated.buf);
par->rotated.buf = NULL; /* clear here to avoid output */
par->rotated.bufsize = 0;
return ret;
}
@@ -0,0 +1,37 @@
/*
* linux/drivers/video/console/fbcon_rotate.h -- Software Display Rotation
*
* Copyright (C) 2005 Antonino Daplas <adaplas@pol.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#ifndef _FBCON_ROTATE_H
#define _FBCON_ROTATE_H
#define GETVYRES(s,i) ({ \
(fb_scrollmode(s) == SCROLL_REDRAW || fb_scrollmode(s) == SCROLL_MOVE) ? \
(i)->var.yres : (i)->var.yres_virtual; })
#define GETVXRES(s,i) ({ \
(fb_scrollmode(s) == SCROLL_REDRAW || fb_scrollmode(s) == SCROLL_MOVE || !(i)->fix.xpanstep) ? \
(i)->var.xres : (i)->var.xres_virtual; })
int fbcon_rotate_font(struct fb_info *info, struct vc_data *vc);
#if defined(CONFIG_FRAMEBUFFER_CONSOLE_ROTATION)
void fbcon_set_bitops_cw(struct fbcon_par *par);
void fbcon_set_bitops_ud(struct fbcon_par *par);
void fbcon_set_bitops_ccw(struct fbcon_par *par);
#else
static inline void fbcon_set_bitops_cw(struct fbcon_par *par)
{ }
static inline void fbcon_set_bitops_ud(struct fbcon_par *par)
{ }
static inline void fbcon_set_bitops_ccw(struct fbcon_par *par)
{ }
#endif
#endif
@@ -0,0 +1,410 @@
/*
* linux/drivers/video/console/fbcon_ud.c -- Software Rotation - 180 degrees
*
* Copyright (C) 2005 Antonino Daplas <adaplas @pol.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/vt_kern.h>
#include <linux/console.h>
#include <asm/types.h>
#include "fbcon.h"
#include "fbcon_rotate.h"
/*
* Rotation 180 degrees
*/
static void ud_update_attr(u8 *dst, u8 *src, int attribute,
struct vc_data *vc)
{
int i, offset = (vc->vc_font.height < 10) ? 1 : 2;
int width = font_glyph_pitch(vc->vc_font.width);
unsigned int cellsize = vc->vc_font.height * width;
u8 c;
offset = offset * width;
for (i = 0; i < cellsize; i++) {
c = src[i];
if (attribute & FBCON_ATTRIBUTE_UNDERLINE && i < offset)
c = 0xff;
if (attribute & FBCON_ATTRIBUTE_BOLD)
c |= c << 1;
if (attribute & FBCON_ATTRIBUTE_REVERSE)
c = ~c;
dst[i] = c;
}
}
static void ud_bmove(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int dy, int dx, int height, int width)
{
struct fbcon_par *par = info->fbcon_par;
struct fb_copyarea area;
u32 vyres = GETVYRES(par->p, info);
u32 vxres = GETVXRES(par->p, info);
area.sy = vyres - ((sy + height) * vc->vc_font.height);
area.sx = vxres - ((sx + width) * vc->vc_font.width);
area.dy = vyres - ((dy + height) * vc->vc_font.height);
area.dx = vxres - ((dx + width) * vc->vc_font.width);
area.height = height * vc->vc_font.height;
area.width = width * vc->vc_font.width;
info->fbops->fb_copyarea(info, &area);
}
static void ud_clear(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int height, int width, int fg, int bg)
{
struct fbcon_par *par = info->fbcon_par;
struct fb_fillrect region;
u32 vyres = GETVYRES(par->p, info);
u32 vxres = GETVXRES(par->p, info);
region.color = bg;
region.dy = vyres - ((sy + height) * vc->vc_font.height);
region.dx = vxres - ((sx + width) * vc->vc_font.width);
region.width = width * vc->vc_font.width;
region.height = height * vc->vc_font.height;
region.rop = ROP_COPY;
info->fbops->fb_fillrect(info, &region);
}
static inline void ud_putcs_aligned(struct vc_data *vc, struct fb_info *info,
const u16 *s, u32 attr, u32 cnt,
u32 d_pitch, u32 s_pitch, u32 cellsize,
struct fb_image *image, u8 *buf, u8 *dst)
{
struct fbcon_par *par = info->fbcon_par;
u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
u32 idx = vc->vc_font.width >> 3;
u8 *src;
while (cnt--) {
src = par->rotated.buf + (scr_readw(s--) & charmask) * cellsize;
if (attr) {
ud_update_attr(buf, src, attr, vc);
src = buf;
}
if (likely(idx == 1))
__fb_pad_aligned_buffer(dst, d_pitch, src, idx,
image->height);
else
fb_pad_aligned_buffer(dst, d_pitch, src, idx,
image->height);
dst += s_pitch;
}
info->fbops->fb_imageblit(info, image);
}
static inline void ud_putcs_unaligned(struct vc_data *vc,
struct fb_info *info, const u16 *s,
u32 attr, u32 cnt, u32 d_pitch,
u32 s_pitch, u32 cellsize,
struct fb_image *image, u8 *buf,
u8 *dst)
{
struct fbcon_par *par = info->fbcon_par;
u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
u32 shift_low = 0, mod = vc->vc_font.width % 8;
u32 shift_high = 8;
u32 idx = vc->vc_font.width >> 3;
u8 *src;
while (cnt--) {
src = par->rotated.buf + (scr_readw(s--) & charmask) * cellsize;
if (attr) {
ud_update_attr(buf, src, attr, vc);
src = buf;
}
fb_pad_unaligned_buffer(dst, d_pitch, src, idx,
image->height, shift_high,
shift_low, mod);
shift_low += mod;
dst += (shift_low >= 8) ? s_pitch : s_pitch - 1;
shift_low &= 7;
shift_high = 8 - shift_low;
}
info->fbops->fb_imageblit(info, image);
}
static void ud_putcs(struct vc_data *vc, struct fb_info *info,
const unsigned short *s, int count, int yy, int xx,
int fg, int bg)
{
struct fb_image image;
struct fbcon_par *par = info->fbcon_par;
u32 width = font_glyph_pitch(vc->vc_font.width);
u32 cellsize = width * vc->vc_font.height;
u32 maxcnt = info->pixmap.size/cellsize;
u32 scan_align = info->pixmap.scan_align - 1;
u32 buf_align = info->pixmap.buf_align - 1;
u32 mod = vc->vc_font.width % 8, cnt, pitch, size;
u32 attribute = get_attribute(info, scr_readw(s));
u8 *dst, *buf = NULL;
u32 vyres = GETVYRES(par->p, info);
u32 vxres = GETVXRES(par->p, info);
if (!par->rotated.buf)
return;
image.fg_color = fg;
image.bg_color = bg;
image.dy = vyres - ((yy * vc->vc_font.height) + vc->vc_font.height);
image.dx = vxres - ((xx + count) * vc->vc_font.width);
image.height = vc->vc_font.height;
image.depth = 1;
if (attribute) {
buf = kmalloc(cellsize, GFP_KERNEL);
if (!buf)
return;
}
s += count - 1;
while (count) {
if (count > maxcnt)
cnt = maxcnt;
else
cnt = count;
image.width = vc->vc_font.width * cnt;
pitch = ((image.width + 7) >> 3) + scan_align;
pitch &= ~scan_align;
size = pitch * image.height + buf_align;
size &= ~buf_align;
dst = fb_get_buffer_offset(info, &info->pixmap, size);
image.data = dst;
if (!mod)
ud_putcs_aligned(vc, info, s, attribute, cnt, pitch,
width, cellsize, &image, buf, dst);
else
ud_putcs_unaligned(vc, info, s, attribute, cnt, pitch,
width, cellsize, &image,
buf, dst);
image.dx += image.width;
count -= cnt;
s -= cnt;
xx += cnt;
}
/* buf is always NULL except when in monochrome mode, so in this case
it's a gain to check buf against NULL even though kfree() handles
NULL pointers just fine */
if (unlikely(buf))
kfree(buf);
}
static void ud_clear_margins(struct vc_data *vc, struct fb_info *info,
int color, int bottom_only)
{
unsigned int cw = vc->vc_font.width;
unsigned int ch = vc->vc_font.height;
unsigned int rw = info->var.xres - (vc->vc_cols*cw);
unsigned int bh = info->var.yres - (vc->vc_rows*ch);
struct fb_fillrect region;
region.color = color;
region.rop = ROP_COPY;
if ((int) rw > 0 && !bottom_only) {
region.dy = 0;
region.dx = info->var.xoffset;
region.width = rw;
region.height = info->var.yres_virtual;
info->fbops->fb_fillrect(info, &region);
}
if ((int) bh > 0) {
region.dy = info->var.yoffset;
region.dx = info->var.xoffset;
region.height = bh;
region.width = info->var.xres;
info->fbops->fb_fillrect(info, &region);
}
}
static void ud_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
int fg, int bg)
{
struct fb_cursor cursor;
struct fbcon_par *par = info->fbcon_par;
unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
int w = font_glyph_pitch(vc->vc_font.width);
int c;
int y = real_y(par->p, vc->state.y);
int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
int err = 1, dx, dy;
char *src;
u32 vyres = GETVYRES(par->p, info);
u32 vxres = GETVXRES(par->p, info);
if (!par->rotated.buf)
return;
cursor.set = 0;
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
src = par->rotated.buf + ((c & charmask) * (w * vc->vc_font.height));
if (par->cursor_state.image.data != src ||
par->cursor_reset) {
par->cursor_state.image.data = src;
cursor.set |= FB_CUR_SETIMAGE;
}
if (attribute) {
u8 *dst;
dst = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC);
if (!dst)
return;
kfree(par->cursor_data);
par->cursor_data = dst;
ud_update_attr(dst, src, attribute, vc);
src = dst;
}
if (par->cursor_state.image.fg_color != fg ||
par->cursor_state.image.bg_color != bg ||
par->cursor_reset) {
par->cursor_state.image.fg_color = fg;
par->cursor_state.image.bg_color = bg;
cursor.set |= FB_CUR_SETCMAP;
}
if (par->cursor_state.image.height != vc->vc_font.height ||
par->cursor_state.image.width != vc->vc_font.width ||
par->cursor_reset) {
par->cursor_state.image.height = vc->vc_font.height;
par->cursor_state.image.width = vc->vc_font.width;
cursor.set |= FB_CUR_SETSIZE;
}
dy = vyres - ((y * vc->vc_font.height) + vc->vc_font.height);
dx = vxres - ((vc->state.x * vc->vc_font.width) + vc->vc_font.width);
if (par->cursor_state.image.dx != dx ||
par->cursor_state.image.dy != dy ||
par->cursor_reset) {
par->cursor_state.image.dx = dx;
par->cursor_state.image.dy = dy;
cursor.set |= FB_CUR_SETPOS;
}
if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
par->cursor_reset) {
par->cursor_state.hot.x = cursor.hot.y = 0;
cursor.set |= FB_CUR_SETHOT;
}
if (cursor.set & FB_CUR_SETSIZE ||
vc->vc_cursor_type != par->p->cursor_shape ||
par->cursor_state.mask == NULL ||
par->cursor_reset) {
unsigned char *tmp, *mask;
tmp = kmalloc_array(vc->vc_font.height, w, GFP_ATOMIC);
if (!tmp)
return;
fbcon_fill_cursor_mask(par, vc, tmp);
mask = kmalloc_array(vc->vc_font.height, w, GFP_ATOMIC);
if (!mask) {
kfree(tmp);
return;
}
font_glyph_rotate_180(tmp, vc->vc_font.width, vc->vc_font.height, mask);
kfree(tmp);
kfree(par->cursor_state.mask);
par->cursor_state.mask = (const char *)mask;
par->p->cursor_shape = vc->vc_cursor_type;
cursor.set |= FB_CUR_SETSHAPE;
}
par->cursor_state.enable = enable && !use_sw;
cursor.image.data = src;
cursor.image.fg_color = par->cursor_state.image.fg_color;
cursor.image.bg_color = par->cursor_state.image.bg_color;
cursor.image.dx = par->cursor_state.image.dx;
cursor.image.dy = par->cursor_state.image.dy;
cursor.image.height = par->cursor_state.image.height;
cursor.image.width = par->cursor_state.image.width;
cursor.hot.x = par->cursor_state.hot.x;
cursor.hot.y = par->cursor_state.hot.y;
cursor.mask = par->cursor_state.mask;
cursor.enable = par->cursor_state.enable;
cursor.image.depth = 1;
cursor.rop = ROP_XOR;
if (info->fbops->fb_cursor)
err = info->fbops->fb_cursor(info, &cursor);
if (err)
soft_cursor(info, &cursor);
par->cursor_reset = 0;
}
static int ud_update_start(struct fb_info *info)
{
struct fbcon_par *par = info->fbcon_par;
int xoffset, yoffset;
u32 vyres = GETVYRES(par->p, info);
u32 vxres = GETVXRES(par->p, info);
int err;
xoffset = vxres - info->var.xres - par->var.xoffset;
yoffset = vyres - info->var.yres - par->var.yoffset;
if (yoffset < 0)
yoffset += vyres;
par->var.xoffset = xoffset;
par->var.yoffset = yoffset;
err = fb_pan_display(info, &par->var);
par->var.xoffset = info->var.xoffset;
par->var.yoffset = info->var.yoffset;
par->var.vmode = info->var.vmode;
return err;
}
static const struct fbcon_bitops ud_fbcon_bitops = {
.bmove = ud_bmove,
.clear = ud_clear,
.putcs = ud_putcs,
.clear_margins = ud_clear_margins,
.cursor = ud_cursor,
.update_start = ud_update_start,
.rotate_font = fbcon_rotate_font,
};
void fbcon_set_bitops_ud(struct fbcon_par *par)
{
par->bitops = &ud_fbcon_bitops;
}
@@ -0,0 +1,368 @@
/*
* linux/drivers/video/fbcvt.c - VESA(TM) Coordinated Video Timings
*
* Copyright (C) 2005 Antonino Daplas <adaplas@pol.net>
*
* Based from the VESA(TM) Coordinated Video Timing Generator by
* Graham Loveridge April 9, 2003 available at
* http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*
*/
#include <linux/fb.h>
#include <linux/slab.h>
#define FB_CVT_CELLSIZE 8
#define FB_CVT_GTF_C 40
#define FB_CVT_GTF_J 20
#define FB_CVT_GTF_K 128
#define FB_CVT_GTF_M 600
#define FB_CVT_MIN_VSYNC_BP 550
#define FB_CVT_MIN_VPORCH 3
#define FB_CVT_MIN_BPORCH 6
#define FB_CVT_RB_MIN_VBLANK 460
#define FB_CVT_RB_HBLANK 160
#define FB_CVT_RB_V_FPORCH 3
#define FB_CVT_FLAG_REDUCED_BLANK 1
#define FB_CVT_FLAG_MARGINS 2
#define FB_CVT_FLAG_INTERLACED 4
struct fb_cvt_data {
u32 xres;
u32 yres;
u32 refresh;
u32 f_refresh;
u32 pixclock;
u32 hperiod;
u32 hblank;
u32 hfreq;
u32 htotal;
u32 vtotal;
u32 vsync;
u32 hsync;
u32 h_front_porch;
u32 h_back_porch;
u32 v_front_porch;
u32 v_back_porch;
u32 h_margin;
u32 v_margin;
u32 interlace;
u32 aspect_ratio;
u32 active_pixels;
u32 flags;
u32 status;
};
static const unsigned char fb_cvt_vbi_tab[] = {
4, /* 4:3 */
5, /* 16:9 */
6, /* 16:10 */
7, /* 5:4 */
7, /* 15:9 */
8, /* reserved */
9, /* reserved */
10 /* custom */
};
/* returns hperiod * 1000 */
static u32 fb_cvt_hperiod(struct fb_cvt_data *cvt)
{
u32 num = 1000000000/cvt->f_refresh;
u32 den;
if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) {
num -= FB_CVT_RB_MIN_VBLANK * 1000;
den = 2 * (cvt->yres/cvt->interlace + 2 * cvt->v_margin);
} else {
num -= FB_CVT_MIN_VSYNC_BP * 1000;
den = 2 * (cvt->yres/cvt->interlace + cvt->v_margin * 2
+ FB_CVT_MIN_VPORCH + cvt->interlace/2);
}
return 2 * (num/den);
}
/* returns ideal duty cycle * 1000 */
static u32 fb_cvt_ideal_duty_cycle(struct fb_cvt_data *cvt)
{
u32 c_prime = (FB_CVT_GTF_C - FB_CVT_GTF_J) *
(FB_CVT_GTF_K) + 256 * FB_CVT_GTF_J;
u32 m_prime = (FB_CVT_GTF_K * FB_CVT_GTF_M);
u32 h_period_est = cvt->hperiod;
return (1000 * c_prime - ((m_prime * h_period_est)/1000))/256;
}
static u32 fb_cvt_hblank(struct fb_cvt_data *cvt)
{
u32 hblank = 0;
if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
hblank = FB_CVT_RB_HBLANK;
else {
u32 ideal_duty_cycle = fb_cvt_ideal_duty_cycle(cvt);
u32 active_pixels = cvt->active_pixels;
if (ideal_duty_cycle < 20000)
hblank = (active_pixels * 20000)/
(100000 - 20000);
else {
hblank = (active_pixels * ideal_duty_cycle)/
(100000 - ideal_duty_cycle);
}
}
hblank &= ~((2 * FB_CVT_CELLSIZE) - 1);
return hblank;
}
static u32 fb_cvt_hsync(struct fb_cvt_data *cvt)
{
u32 hsync;
if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
hsync = 32;
else
hsync = (FB_CVT_CELLSIZE * cvt->htotal)/100;
hsync &= ~(FB_CVT_CELLSIZE - 1);
return hsync;
}
static u32 fb_cvt_vbi_lines(struct fb_cvt_data *cvt)
{
u32 vbi_lines, min_vbi_lines, act_vbi_lines;
if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) {
vbi_lines = (1000 * FB_CVT_RB_MIN_VBLANK)/cvt->hperiod + 1;
min_vbi_lines = FB_CVT_RB_V_FPORCH + cvt->vsync +
FB_CVT_MIN_BPORCH;
} else {
vbi_lines = (FB_CVT_MIN_VSYNC_BP * 1000)/cvt->hperiod + 1 +
FB_CVT_MIN_VPORCH;
min_vbi_lines = cvt->vsync + FB_CVT_MIN_BPORCH +
FB_CVT_MIN_VPORCH;
}
if (vbi_lines < min_vbi_lines)
act_vbi_lines = min_vbi_lines;
else
act_vbi_lines = vbi_lines;
return act_vbi_lines;
}
static u32 fb_cvt_vtotal(struct fb_cvt_data *cvt)
{
u32 vtotal = cvt->yres/cvt->interlace;
vtotal += 2 * cvt->v_margin + cvt->interlace/2 + fb_cvt_vbi_lines(cvt);
vtotal |= cvt->interlace/2;
return vtotal;
}
static u32 fb_cvt_pixclock(struct fb_cvt_data *cvt)
{
u32 pixclock;
if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
pixclock = (cvt->f_refresh * cvt->vtotal * cvt->htotal)/1000;
else
pixclock = (cvt->htotal * 1000000)/cvt->hperiod;
pixclock /= 250;
pixclock *= 250;
pixclock *= 1000;
return pixclock;
}
static u32 fb_cvt_aspect_ratio(struct fb_cvt_data *cvt)
{
u32 xres = cvt->xres;
u32 yres = cvt->yres;
u32 aspect = -1;
if (xres == (yres * 4)/3 && !((yres * 4) % 3))
aspect = 0;
else if (xres == (yres * 16)/9 && !((yres * 16) % 9))
aspect = 1;
else if (xres == (yres * 16)/10 && !((yres * 16) % 10))
aspect = 2;
else if (xres == (yres * 5)/4 && !((yres * 5) % 4))
aspect = 3;
else if (xres == (yres * 15)/9 && !((yres * 15) % 9))
aspect = 4;
else {
printk(KERN_INFO "fbcvt: Aspect ratio not CVT "
"standard\n");
aspect = 7;
cvt->status = 1;
}
return aspect;
}
static void fb_cvt_print_name(struct fb_cvt_data *cvt)
{
u32 pixcount, pixcount_mod;
int size = 256;
int off = 0;
u8 *buf;
buf = kzalloc(size, GFP_KERNEL);
if (!buf)
return;
pixcount = (cvt->xres * (cvt->yres/cvt->interlace))/1000000;
pixcount_mod = (cvt->xres * (cvt->yres/cvt->interlace)) % 1000000;
pixcount_mod /= 1000;
off += scnprintf(buf + off, size - off, "fbcvt: %dx%d@%d: CVT Name - ",
cvt->xres, cvt->yres, cvt->refresh);
if (cvt->status) {
off += scnprintf(buf + off, size - off,
"Not a CVT standard - %d.%03d Mega Pixel Image\n",
pixcount, pixcount_mod);
} else {
if (pixcount)
off += scnprintf(buf + off, size - off, "%d", pixcount);
off += scnprintf(buf + off, size - off, ".%03dM", pixcount_mod);
if (cvt->aspect_ratio == 0)
off += scnprintf(buf + off, size - off, "3");
else if (cvt->aspect_ratio == 3)
off += scnprintf(buf + off, size - off, "4");
else if (cvt->aspect_ratio == 1 || cvt->aspect_ratio == 4)
off += scnprintf(buf + off, size - off, "9");
else if (cvt->aspect_ratio == 2)
off += scnprintf(buf + off, size - off, "A");
if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
off += scnprintf(buf + off, size - off, "-R");
}
printk(KERN_INFO "%s\n", buf);
kfree(buf);
}
static void fb_cvt_convert_to_mode(struct fb_cvt_data *cvt,
struct fb_videomode *mode)
{
mode->refresh = cvt->f_refresh;
mode->pixclock = KHZ2PICOS(cvt->pixclock/1000);
mode->left_margin = cvt->h_back_porch;
mode->right_margin = cvt->h_front_porch;
mode->hsync_len = cvt->hsync;
mode->upper_margin = cvt->v_back_porch;
mode->lower_margin = cvt->v_front_porch;
mode->vsync_len = cvt->vsync;
mode->sync &= ~(FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT);
if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK)
mode->sync |= FB_SYNC_HOR_HIGH_ACT;
else
mode->sync |= FB_SYNC_VERT_HIGH_ACT;
}
/*
* fb_find_mode_cvt - calculate mode using VESA(TM) CVT
* @mode: pointer to fb_videomode; xres, yres, refresh and vmode must be
* pre-filled with the desired values
* @margins: add margin to calculation (1.8% of xres and yres)
* @rb: compute with reduced blanking (for flatpanels)
*
* RETURNS:
* 0 for success
* @mode is filled with computed values. If interlaced, the refresh field
* will be filled with the field rate (2x the frame rate)
*
* DESCRIPTION:
* Computes video timings using VESA(TM) Coordinated Video Timings
*/
int fb_find_mode_cvt(struct fb_videomode *mode, int margins, int rb)
{
struct fb_cvt_data cvt;
memset(&cvt, 0, sizeof(cvt));
if (margins)
cvt.flags |= FB_CVT_FLAG_MARGINS;
if (rb)
cvt.flags |= FB_CVT_FLAG_REDUCED_BLANK;
if (mode->vmode & FB_VMODE_INTERLACED)
cvt.flags |= FB_CVT_FLAG_INTERLACED;
cvt.xres = mode->xres;
cvt.yres = mode->yres;
cvt.refresh = mode->refresh;
cvt.f_refresh = cvt.refresh;
cvt.interlace = 1;
if (!cvt.xres || !cvt.yres || !cvt.refresh || cvt.f_refresh > INT_MAX) {
printk(KERN_INFO "fbcvt: Invalid input parameters\n");
return 1;
}
if (!(cvt.refresh == 50 || cvt.refresh == 60 || cvt.refresh == 70 ||
cvt.refresh == 85)) {
printk(KERN_INFO "fbcvt: Refresh rate not CVT "
"standard\n");
cvt.status = 1;
}
cvt.xres &= ~(FB_CVT_CELLSIZE - 1);
if (cvt.flags & FB_CVT_FLAG_INTERLACED) {
cvt.interlace = 2;
cvt.f_refresh *= 2;
}
if (cvt.flags & FB_CVT_FLAG_REDUCED_BLANK) {
if (cvt.refresh != 60) {
printk(KERN_INFO "fbcvt: 60Hz refresh rate "
"advised for reduced blanking\n");
cvt.status = 1;
}
}
if (cvt.flags & FB_CVT_FLAG_MARGINS) {
cvt.h_margin = (cvt.xres * 18)/1000;
cvt.h_margin &= ~(FB_CVT_CELLSIZE - 1);
cvt.v_margin = ((cvt.yres/cvt.interlace)* 18)/1000;
}
cvt.aspect_ratio = fb_cvt_aspect_ratio(&cvt);
cvt.active_pixels = cvt.xres + 2 * cvt.h_margin;
cvt.hperiod = fb_cvt_hperiod(&cvt);
cvt.vsync = fb_cvt_vbi_tab[cvt.aspect_ratio];
cvt.vtotal = fb_cvt_vtotal(&cvt);
cvt.hblank = fb_cvt_hblank(&cvt);
cvt.htotal = cvt.active_pixels + cvt.hblank;
cvt.hsync = fb_cvt_hsync(&cvt);
cvt.pixclock = fb_cvt_pixclock(&cvt);
cvt.hfreq = cvt.pixclock/cvt.htotal;
cvt.h_back_porch = cvt.hblank/2 + cvt.h_margin;
cvt.h_front_porch = cvt.hblank - cvt.hsync - cvt.h_back_porch +
2 * cvt.h_margin;
cvt.v_front_porch = 3 + cvt.v_margin;
cvt.v_back_porch = cvt.vtotal - cvt.yres/cvt.interlace -
cvt.v_front_porch - cvt.vsync;
fb_cvt_print_name(&cvt);
fb_cvt_convert_to_mode(&cvt, mode);
return 0;
}
@@ -0,0 +1,802 @@
/*
* linux/drivers/video/fbmem.c
*
* Copyright (C) 1994 Martin Schaller
*
* 2001 - Documented with DocBook
* - Brad Douglas <brad@neruo.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#include <linux/console.h>
#include <linux/export.h>
#include <linux/fb.h>
#include <linux/lcd.h>
#include <linux/leds.h>
#include <video/nomodeset.h>
#include "fb_internal.h"
#include "fbcon.h"
/*
* Frame buffer device initialization and setup routines
*/
#define FBPIXMAPSIZE (1024 * 8)
struct class *fb_class;
DEFINE_MUTEX(registration_lock);
struct fb_info *registered_fb[FB_MAX] __read_mostly;
int num_registered_fb __read_mostly;
#define for_each_registered_fb(i) \
for (i = 0; i < FB_MAX; i++) \
if (!registered_fb[i]) {} else
struct fb_info *get_fb_info(unsigned int idx)
{
struct fb_info *fb_info;
if (idx >= FB_MAX)
return ERR_PTR(-ENODEV);
mutex_lock(&registration_lock);
fb_info = registered_fb[idx];
if (fb_info)
refcount_inc(&fb_info->count);
mutex_unlock(&registration_lock);
return fb_info;
}
void put_fb_info(struct fb_info *fb_info)
{
if (!refcount_dec_and_test(&fb_info->count))
return;
if (fb_info->fbops->fb_destroy)
fb_info->fbops->fb_destroy(fb_info);
}
/*
* Helpers
*/
int fb_get_color_depth(struct fb_var_screeninfo *var,
struct fb_fix_screeninfo *fix)
{
int depth = 0;
if (fix->visual == FB_VISUAL_MONO01 ||
fix->visual == FB_VISUAL_MONO10)
depth = 1;
else {
if (var->green.length == var->blue.length &&
var->green.length == var->red.length &&
var->green.offset == var->blue.offset &&
var->green.offset == var->red.offset)
depth = var->green.length;
else
depth = var->green.length + var->red.length +
var->blue.length;
}
return depth;
}
EXPORT_SYMBOL(fb_get_color_depth);
/*
* Data padding functions.
*/
void fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, const u8 *src, u32 s_pitch, u32 height)
{
__fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, height);
}
EXPORT_SYMBOL(fb_pad_aligned_buffer);
void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, const u8 *src, u32 idx, u32 height,
u32 shift_high, u32 shift_low, u32 mod)
{
u8 mask = (u8) (0xff << shift_high), tmp;
int i, j;
for (i = height; i--; ) {
for (j = 0; j < idx; j++) {
tmp = dst[j];
tmp &= mask;
tmp |= *src >> shift_low;
dst[j] = tmp;
tmp = *src << shift_high;
dst[j+1] = tmp;
src++;
}
tmp = dst[idx];
tmp &= mask;
tmp |= *src >> shift_low;
dst[idx] = tmp;
if (shift_high < mod) {
tmp = *src << shift_high;
dst[idx+1] = tmp;
}
src++;
dst += d_pitch;
}
}
EXPORT_SYMBOL(fb_pad_unaligned_buffer);
/*
* we need to lock this section since fb_cursor
* may use fb_imageblit()
*/
char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size)
{
u32 align = buf->buf_align - 1, offset;
char *addr = buf->addr;
/* If IO mapped, we need to sync before access, no sharing of
* the pixmap is done
*/
if (buf->flags & FB_PIXMAP_IO) {
if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))
info->fbops->fb_sync(info);
return addr;
}
/* See if we fit in the remaining pixmap space */
offset = buf->offset + align;
offset &= ~align;
if (offset + size > buf->size) {
/* We do not fit. In order to be able to re-use the buffer,
* we must ensure no asynchronous DMA'ing or whatever operation
* is in progress, we sync for that.
*/
if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))
info->fbops->fb_sync(info);
offset = 0;
}
buf->offset = offset + size;
addr += offset;
return addr;
}
EXPORT_SYMBOL(fb_get_buffer_offset);
int
fb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var)
{
struct fb_fix_screeninfo *fix = &info->fix;
unsigned int yres = info->var.yres;
int err = 0;
if (var->yoffset > 0) {
if (var->vmode & FB_VMODE_YWRAP) {
if (!fix->ywrapstep || (var->yoffset % fix->ywrapstep))
err = -EINVAL;
else
yres = 0;
} else if (!fix->ypanstep || (var->yoffset % fix->ypanstep))
err = -EINVAL;
}
if (var->xoffset > 0 && (!fix->xpanstep ||
(var->xoffset % fix->xpanstep)))
err = -EINVAL;
if (err || !info->fbops->fb_pan_display ||
var->yoffset > info->var.yres_virtual - yres ||
var->xoffset > info->var.xres_virtual - info->var.xres)
return -EINVAL;
if ((err = info->fbops->fb_pan_display(var, info)))
return err;
info->var.xoffset = var->xoffset;
info->var.yoffset = var->yoffset;
if (var->vmode & FB_VMODE_YWRAP)
info->var.vmode |= FB_VMODE_YWRAP;
else
info->var.vmode &= ~FB_VMODE_YWRAP;
return 0;
}
EXPORT_SYMBOL(fb_pan_display);
static int fb_check_caps(struct fb_info *info, struct fb_var_screeninfo *var,
u32 activate)
{
struct fb_blit_caps caps, fbcaps;
int err = 0;
memset(&caps, 0, sizeof(caps));
memset(&fbcaps, 0, sizeof(fbcaps));
caps.flags = (activate & FB_ACTIVATE_ALL) ? 1 : 0;
fbcon_get_requirement(info, &caps);
info->fbops->fb_get_caps(info, &fbcaps, var);
if (!bitmap_subset(caps.x, fbcaps.x, FB_MAX_BLIT_WIDTH) ||
!bitmap_subset(caps.y, fbcaps.y, FB_MAX_BLIT_HEIGHT) ||
(fbcaps.len < caps.len))
err = -EINVAL;
return err;
}
static void fb_lcd_notify_mode_change(struct fb_info *info,
struct fb_videomode *mode)
{
lcd_notify_mode_change_all(info->device, mode->xres, mode->yres);
}
int
fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
{
int ret = 0;
u32 activate;
struct fb_var_screeninfo old_var;
struct fb_videomode mode;
u32 unused;
if (var->activate & FB_ACTIVATE_INV_MODE) {
struct fb_videomode mode1, mode2;
fb_var_to_videomode(&mode1, var);
fb_var_to_videomode(&mode2, &info->var);
/* make sure we don't delete the videomode of current var */
ret = fb_mode_is_equal(&mode1, &mode2);
if (!ret) {
ret = fbcon_mode_deleted(info, &mode1);
if (!ret) {
if (info->mode && fb_mode_is_equal(info->mode, &mode1))
info->mode = NULL;
fb_delete_videomode(&mode1, &info->modelist);
}
}
return ret ? -EINVAL : 0;
}
if (!(var->activate & FB_ACTIVATE_FORCE) &&
!memcmp(&info->var, var, sizeof(struct fb_var_screeninfo)))
return 0;
activate = var->activate;
/* When using FOURCC mode, make sure the red, green, blue and
* transp fields are set to 0.
*/
if ((info->fix.capabilities & FB_CAP_FOURCC) &&
var->grayscale > 1) {
if (var->red.offset || var->green.offset ||
var->blue.offset || var->transp.offset ||
var->red.length || var->green.length ||
var->blue.length || var->transp.length ||
var->red.msb_right || var->green.msb_right ||
var->blue.msb_right || var->transp.msb_right)
return -EINVAL;
}
if (!info->fbops->fb_check_var) {
*var = info->var;
return 0;
}
/* bitfill_aligned() assumes that it's at least 8x8 */
if (var->xres < 8 || var->yres < 8)
return -EINVAL;
/* Too huge resolution causes multiplication overflow. */
if (check_mul_overflow(var->xres, var->yres, &unused) ||
check_mul_overflow(var->xres_virtual, var->yres_virtual, &unused))
return -EINVAL;
ret = info->fbops->fb_check_var(var, info);
if (ret)
return ret;
/* verify that virtual resolution >= physical resolution */
if (var->xres_virtual < var->xres ||
var->yres_virtual < var->yres) {
pr_warn("WARNING: fbcon: Driver '%s' missed to adjust virtual screen size (%ux%u vs. %ux%u)\n",
info->fix.id,
var->xres_virtual, var->yres_virtual,
var->xres, var->yres);
return -EINVAL;
}
if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW)
return 0;
if (info->fbops->fb_get_caps) {
ret = fb_check_caps(info, var, activate);
if (ret)
return ret;
}
old_var = info->var;
info->var = *var;
if (info->fbops->fb_set_par) {
ret = info->fbops->fb_set_par(info);
if (ret) {
info->var = old_var;
printk(KERN_WARNING "detected "
"fb_set_par error, "
"error code: %d\n", ret);
return ret;
}
}
fb_pan_display(info, &info->var);
fb_set_cmap(&info->cmap, info);
fb_var_to_videomode(&mode, &info->var);
if (info->modelist.prev && info->modelist.next &&
!list_empty(&info->modelist))
ret = fb_add_videomode(&mode, &info->modelist);
if (ret) {
info->var = old_var;
return ret;
}
fb_lcd_notify_mode_change(info, &mode);
return 0;
}
EXPORT_SYMBOL(fb_set_var);
int fb_set_var_from_user(struct fb_info *info, struct fb_var_screeninfo *var)
{
int ret = fbcon_modechange_possible(info, var);
if (!ret)
ret = fb_set_var(info, var);
if (!ret)
fbcon_update_vcs(info, var->activate & FB_ACTIVATE_ALL);
return ret;
}
EXPORT_SYMBOL(fb_set_var_from_user);
static void fb_lcd_notify_blank(struct fb_info *info)
{
int power;
switch (info->blank) {
case FB_BLANK_UNBLANK:
power = LCD_POWER_ON;
break;
/* deprecated; TODO: should become 'off' */
case FB_BLANK_NORMAL:
power = LCD_POWER_REDUCED;
break;
case FB_BLANK_VSYNC_SUSPEND:
power = LCD_POWER_REDUCED_VSYNC_SUSPEND;
break;
/* 'off' */
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_POWERDOWN:
default:
power = LCD_POWER_OFF;
break;
}
lcd_notify_blank_all(info->device, power);
}
static void fb_ledtrig_backlight_notify_blank(struct fb_info *info)
{
if (info->blank == FB_BLANK_UNBLANK)
ledtrig_backlight_blank(false);
else
ledtrig_backlight_blank(true);
}
int fb_blank(struct fb_info *info, int blank)
{
int old_blank = info->blank;
int ret;
if (!info->fbops->fb_blank)
return -EINVAL;
if (blank > FB_BLANK_POWERDOWN)
blank = FB_BLANK_POWERDOWN;
info->blank = blank;
ret = info->fbops->fb_blank(blank, info);
if (ret)
goto err;
fb_bl_notify_blank(info, old_blank);
fb_lcd_notify_blank(info);
fb_ledtrig_backlight_notify_blank(info);
return 0;
err:
info->blank = old_blank;
return ret;
}
EXPORT_SYMBOL(fb_blank);
int fb_blank_from_user(struct fb_info *info, int blank)
{
int ret = fb_blank(info, blank);
/* might again call into fb_blank */
fbcon_fb_blanked(info, blank);
return ret;
}
static int fb_check_foreignness(struct fb_info *fi)
{
const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN;
fi->flags &= ~FBINFO_FOREIGN_ENDIAN;
#ifdef __BIG_ENDIAN
fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH;
#else
fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0;
#endif /* __BIG_ENDIAN */
if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) {
pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to "
"support this framebuffer\n", fi->fix.id);
return -ENOSYS;
} else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(fi)) {
pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to "
"support this framebuffer\n", fi->fix.id);
return -ENOSYS;
}
return 0;
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
int i, err = 0;
struct fb_videomode mode;
if (fb_check_foreignness(fb_info))
return -ENOSYS;
if (num_registered_fb == FB_MAX)
return -ENXIO;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
if (i >= FB_MAX)
return -ENXIO;
if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);
fb_var_to_videomode(&mode, &fb_info->var);
err = fb_add_videomode(&mode, &fb_info->modelist);
if (err < 0)
return err;
fb_info->node = i;
refcount_set(&fb_info->count, 1);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
/*
* With an fb_blank callback present, we assume that the
* display is blank, so that fb_blank() enables it on the
* first modeset.
*/
if (fb_info->fbops->fb_blank)
fb_info->blank = FB_BLANK_POWERDOWN;
fb_device_create(fb_info);
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 32;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0;
if (bitmap_empty(fb_info->pixmap.blit_x, FB_MAX_BLIT_WIDTH))
bitmap_fill(fb_info->pixmap.blit_x, FB_MAX_BLIT_WIDTH);
if (bitmap_empty(fb_info->pixmap.blit_y, FB_MAX_BLIT_HEIGHT))
bitmap_fill(fb_info->pixmap.blit_y, FB_MAX_BLIT_HEIGHT);
if (fb_info->skip_vt_switch)
pm_vt_switch_required(fb_info->device, false);
else
pm_vt_switch_required(fb_info->device, true);
num_registered_fb++;
registered_fb[i] = fb_info;
#ifdef CONFIG_GUMSTIX_AM200EPD
{
struct fb_event event;
event.info = fb_info;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
}
#endif
return fbcon_fb_registered(fb_info);
}
static void unbind_console(struct fb_info *fb_info)
{
int i = fb_info->node;
if (WARN_ON(i < 0 || i >= FB_MAX || registered_fb[i] != fb_info))
return;
fbcon_fb_unbind(fb_info);
}
static void unlink_framebuffer(struct fb_info *fb_info)
{
int i;
i = fb_info->node;
if (WARN_ON(i < 0 || i >= FB_MAX || registered_fb[i] != fb_info))
return;
fb_device_destroy(fb_info);
pm_vt_switch_unregister(fb_info->device);
unbind_console(fb_info);
}
static void do_unregister_framebuffer(struct fb_info *fb_info)
{
unlink_framebuffer(fb_info);
if (fb_info->pixmap.addr &&
(fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) {
kfree(fb_info->pixmap.addr);
fb_info->pixmap.addr = NULL;
}
fbcon_delete_modelist(&fb_info->modelist);
fb_destroy_modelist(&fb_info->modelist);
registered_fb[fb_info->node] = NULL;
num_registered_fb--;
#ifdef CONFIG_GUMSTIX_AM200EPD
{
struct fb_event event;
event.info = fb_info;
fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event);
}
#endif
fbcon_fb_unregistered(fb_info);
/* this may free fb info */
put_fb_info(fb_info);
}
/**
* register_framebuffer - registers a frame buffer device
* @fb_info: frame buffer info structure
*
* Registers a frame buffer device @fb_info.
*
* Returns negative errno on error, or zero for success.
*
*/
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(&registration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(&registration_lock);
return ret;
}
EXPORT_SYMBOL(register_framebuffer);
/**
* unregister_framebuffer - releases a frame buffer device
* @fb_info: frame buffer info structure
*
* Unregisters a frame buffer device @fb_info.
*
* Returns negative errno on error, or zero for success.
*
* This function will also notify the framebuffer console
* to release the driver.
*
* This is meant to be called within a driver's module_exit()
* function. If this is called outside module_exit(), ensure
* that the driver implements fb_open() and fb_release() to
* check that no processes are using the device.
*/
void
unregister_framebuffer(struct fb_info *fb_info)
{
mutex_lock(&registration_lock);
do_unregister_framebuffer(fb_info);
mutex_unlock(&registration_lock);
}
EXPORT_SYMBOL(unregister_framebuffer);
static void devm_unregister_framebuffer(void *data)
{
struct fb_info *info = data;
unregister_framebuffer(info);
}
/**
* devm_register_framebuffer - resource-managed frame buffer device registration
* @dev: device the framebuffer belongs to
* @fb_info: frame buffer info structure
*
* Registers a frame buffer device @fb_info to device @dev.
*
* Returns negative errno on error, or zero for success.
*
*/
int
devm_register_framebuffer(struct device *dev, struct fb_info *fb_info)
{
int ret;
ret = register_framebuffer(fb_info);
if (ret)
return ret;
return devm_add_action_or_reset(dev, devm_unregister_framebuffer, fb_info);
}
EXPORT_SYMBOL(devm_register_framebuffer);
/**
* fb_set_suspend - low level driver signals suspend
* @info: framebuffer affected
* @state: 0 = resuming, !=0 = suspending
*
* This is meant to be used by low level drivers to
* signal suspend/resume to the core & clients.
* It must be called with the console semaphore held
*/
void fb_set_suspend(struct fb_info *info, int state)
{
WARN_CONSOLE_UNLOCKED();
if (state) {
fbcon_suspended(info);
info->state = FBINFO_STATE_SUSPENDED;
} else {
info->state = FBINFO_STATE_RUNNING;
fbcon_resumed(info);
}
}
EXPORT_SYMBOL(fb_set_suspend);
/**
* fb_switch_outputs - framebuffer got the outputs from vga-switcheroo
* @info: framebuffer
*/
void fb_switch_outputs(struct fb_info *info)
{
fbcon_remap_all(info);
}
EXPORT_SYMBOL(fb_switch_outputs);
static int __init fbmem_init(void)
{
int ret;
fb_class = class_create("graphics");
if (IS_ERR(fb_class)) {
ret = PTR_ERR(fb_class);
pr_err("Unable to create fb class; errno = %d\n", ret);
goto err_fb_class;
}
ret = fb_init_procfs();
if (ret)
goto err_class_destroy;
ret = fb_register_chrdev();
if (ret)
goto err_fb_cleanup_procfs;
fb_console_init();
return 0;
err_fb_cleanup_procfs:
fb_cleanup_procfs();
err_class_destroy:
class_destroy(fb_class);
err_fb_class:
fb_class = NULL;
return ret;
}
#ifdef MODULE
static void __exit fbmem_exit(void)
{
fb_console_exit();
fb_unregister_chrdev();
fb_cleanup_procfs();
class_destroy(fb_class);
}
module_init(fbmem_init);
module_exit(fbmem_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Framebuffer base");
#else
subsys_initcall(fbmem_init);
#endif
int fb_new_modelist(struct fb_info *info)
{
struct fb_var_screeninfo var = info->var;
struct list_head *pos, *n;
struct fb_modelist *modelist;
struct fb_videomode *m, mode;
int err;
list_for_each_safe(pos, n, &info->modelist) {
modelist = list_entry(pos, struct fb_modelist, list);
m = &modelist->mode;
fb_videomode_to_var(&var, m);
var.activate = FB_ACTIVATE_TEST;
err = fb_set_var(info, &var);
fb_var_to_videomode(&mode, &var);
if (err || !fb_mode_is_equal(m, &mode)) {
list_del(pos);
kfree(pos);
}
}
if (list_empty(&info->modelist))
return 1;
/*
* The new modelist may not contain the current mode (info->var), and
* fbcon_new_modelist() below only re-points consoles mapped to this
* framebuffer. Add the current mode here so info->var keeps a match
* even when fbcon is unbound.
*/
if (!fb_match_mode(&info->var, &info->modelist)) {
fb_var_to_videomode(&mode, &info->var);
if (fb_add_videomode(&mode, &info->modelist))
return 1;
}
fbcon_new_modelist(info);
return 0;
}
bool fb_modesetting_disabled(const char *drvname)
{
bool fwonly = video_firmware_drivers_only();
if (fwonly)
pr_warn("Driver %s not loading because of nomodeset parameter\n",
drvname);
return fwonly;
}
EXPORT_SYMBOL(fb_modesetting_disabled);
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,519 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* fbsysfs.c - framebuffer device class and attributes
*
* Copyright (c) 2004 James Simmons <jsimmons@infradead.org>
*/
#include <linux/console.h>
#include <linux/fb.h>
#include <linux/major.h>
#include "fb_internal.h"
#include "fbcon.h"
static int activate_locked(struct fb_info *fb_info,
struct fb_var_screeninfo *var)
{
var->activate |= FB_ACTIVATE_FORCE;
return fb_set_var_from_user(fb_info, var);
}
static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var)
{
int err;
console_lock();
lock_fb_info(fb_info);
err = activate_locked(fb_info, var);
unlock_fb_info(fb_info);
console_unlock();
return err;
}
static int mode_string(char *buf, size_t size, unsigned int offset,
const struct fb_videomode *mode)
{
char m = 'U';
char v = 'p';
if (offset >= size)
return 0;
if (mode->flag & FB_MODE_IS_DETAILED)
m = 'D';
if (mode->flag & FB_MODE_IS_VESA)
m = 'V';
if (mode->flag & FB_MODE_IS_STANDARD)
m = 'S';
if (mode->vmode & FB_VMODE_INTERLACED)
v = 'i';
if (mode->vmode & FB_VMODE_DOUBLE)
v = 'd';
return scnprintf(&buf[offset], size - offset, "%c:%dx%d%c-%d\n",
m, mode->xres, mode->yres, v, mode->refresh);
}
static ssize_t store_mode(struct device *device, struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
char mstr[100];
struct fb_var_screeninfo var;
struct fb_modelist *modelist;
struct fb_videomode *mode;
size_t i;
int err;
memset(&var, 0, sizeof(var));
console_lock();
lock_fb_info(fb_info);
list_for_each_entry(modelist, &fb_info->modelist, list) {
mode = &modelist->mode;
i = mode_string(mstr, sizeof(mstr), 0, mode);
if (strncmp(mstr, buf, max(count, i)) == 0) {
var = fb_info->var;
fb_videomode_to_var(&var, mode);
err = activate_locked(fb_info, &var);
if (err) {
unlock_fb_info(fb_info);
console_unlock();
return err;
}
fb_info->mode = mode;
unlock_fb_info(fb_info);
console_unlock();
return count;
}
}
unlock_fb_info(fb_info);
console_unlock();
return -EINVAL;
}
static ssize_t show_mode(struct device *device, struct device_attribute *attr,
char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
struct fb_videomode mode;
bool have_mode = false;
lock_fb_info(fb_info);
if (fb_info->mode) {
mode = *fb_info->mode;
have_mode = true;
}
unlock_fb_info(fb_info);
if (!have_mode)
return 0;
return mode_string(buf, PAGE_SIZE, 0, &mode);
}
static ssize_t store_modes(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
LIST_HEAD(old_list);
int i = count / sizeof(struct fb_videomode);
if (i * sizeof(struct fb_videomode) != count)
return -EINVAL;
console_lock();
lock_fb_info(fb_info);
list_splice(&fb_info->modelist, &old_list);
fb_videomode_to_modelist((const struct fb_videomode *)buf, i,
&fb_info->modelist);
if (fb_new_modelist(fb_info)) {
fb_destroy_modelist(&fb_info->modelist);
list_splice(&old_list, &fb_info->modelist);
} else {
/*
* fb_display[i].mode and fb_info->mode both point into the old
* list. Clear them before it is freed.
*/
fbcon_delete_modelist(&old_list);
fb_info->mode = NULL;
fb_destroy_modelist(&old_list);
}
unlock_fb_info(fb_info);
console_unlock();
return 0;
}
static ssize_t show_modes(struct device *device, struct device_attribute *attr,
char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
unsigned int i;
struct fb_modelist *modelist;
const struct fb_videomode *mode;
i = 0;
lock_fb_info(fb_info);
list_for_each_entry(modelist, &fb_info->modelist, list) {
mode = &modelist->mode;
i += mode_string(buf, PAGE_SIZE, i, mode);
if (i >= PAGE_SIZE - 1)
break;
}
unlock_fb_info(fb_info);
return i;
}
static ssize_t store_bpp(struct device *device, struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
struct fb_var_screeninfo var;
char ** last = NULL;
int err;
var = fb_info->var;
var.bits_per_pixel = simple_strtoul(buf, last, 0);
if ((err = activate(fb_info, &var)))
return err;
return count;
}
static ssize_t show_bpp(struct device *device, struct device_attribute *attr,
char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return sysfs_emit(buf, "%d\n", fb_info->var.bits_per_pixel);
}
static ssize_t store_rotate(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
struct fb_var_screeninfo var;
char **last = NULL;
int err;
var = fb_info->var;
var.rotate = simple_strtoul(buf, last, 0);
if ((err = activate(fb_info, &var)))
return err;
return count;
}
static ssize_t show_rotate(struct device *device,
struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return sysfs_emit(buf, "%d\n", fb_info->var.rotate);
}
static ssize_t store_virtual(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
struct fb_var_screeninfo var;
char *last = NULL;
int err;
var = fb_info->var;
var.xres_virtual = simple_strtoul(buf, &last, 0);
last++;
if (last - buf >= count)
return -EINVAL;
var.yres_virtual = simple_strtoul(last, &last, 0);
if ((err = activate(fb_info, &var)))
return err;
return count;
}
static ssize_t show_virtual(struct device *device,
struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return sysfs_emit(buf, "%d,%d\n", fb_info->var.xres_virtual,
fb_info->var.yres_virtual);
}
static ssize_t show_stride(struct device *device,
struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return sysfs_emit(buf, "%d\n", fb_info->fix.line_length);
}
static ssize_t store_blank(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
char *last = NULL;
int err, arg;
arg = simple_strtoul(buf, &last, 0);
console_lock();
err = fb_blank_from_user(fb_info, arg);
console_unlock();
if (err < 0)
return err;
return count;
}
static ssize_t show_blank(struct device *device, struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return sysfs_emit(buf, "%d\n", fb_info->blank);
}
static ssize_t store_console(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
// struct fb_info *fb_info = dev_get_drvdata(device);
return 0;
}
static ssize_t show_console(struct device *device,
struct device_attribute *attr, char *buf)
{
// struct fb_info *fb_info = dev_get_drvdata(device);
return 0;
}
static ssize_t store_cursor(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
// struct fb_info *fb_info = dev_get_drvdata(device);
return 0;
}
static ssize_t show_cursor(struct device *device,
struct device_attribute *attr, char *buf)
{
// struct fb_info *fb_info = dev_get_drvdata(device);
return 0;
}
static ssize_t store_pan(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
struct fb_var_screeninfo var;
char *last = NULL;
int err;
var = fb_info->var;
var.xoffset = simple_strtoul(buf, &last, 0);
last++;
if (last - buf >= count)
return -EINVAL;
var.yoffset = simple_strtoul(last, &last, 0);
console_lock();
err = fb_pan_display(fb_info, &var);
console_unlock();
if (err < 0)
return err;
return count;
}
static ssize_t show_pan(struct device *device,
struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return sysfs_emit(buf, "%d,%d\n", fb_info->var.xoffset,
fb_info->var.yoffset);
}
static ssize_t show_name(struct device *device,
struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return sysfs_emit(buf, "%s\n", fb_info->fix.id);
}
static ssize_t store_fbstate(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
u32 state;
char *last = NULL;
state = simple_strtoul(buf, &last, 0);
console_lock();
lock_fb_info(fb_info);
fb_set_suspend(fb_info, (int)state);
unlock_fb_info(fb_info);
console_unlock();
return count;
}
static ssize_t show_fbstate(struct device *device,
struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
return sysfs_emit(buf, "%d\n", fb_info->state);
}
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
static ssize_t store_bl_curve(struct device *device,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *fb_info = dev_get_drvdata(device);
u8 tmp_curve[FB_BACKLIGHT_LEVELS];
unsigned int i;
/* Some drivers don't use framebuffer_alloc(), but those also
* don't have backlights.
*/
if (!fb_info || !fb_info->bl_dev)
return -ENODEV;
if (count != (FB_BACKLIGHT_LEVELS / 8 * 24))
return -EINVAL;
for (i = 0; i < (FB_BACKLIGHT_LEVELS / 8); ++i)
if (sscanf(&buf[i * 24],
"%2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx\n",
&tmp_curve[i * 8 + 0],
&tmp_curve[i * 8 + 1],
&tmp_curve[i * 8 + 2],
&tmp_curve[i * 8 + 3],
&tmp_curve[i * 8 + 4],
&tmp_curve[i * 8 + 5],
&tmp_curve[i * 8 + 6],
&tmp_curve[i * 8 + 7]) != 8)
return -EINVAL;
/* If there has been an error in the input data, we won't
* reach this loop.
*/
mutex_lock(&fb_info->bl_curve_mutex);
for (i = 0; i < FB_BACKLIGHT_LEVELS; ++i)
fb_info->bl_curve[i] = tmp_curve[i];
mutex_unlock(&fb_info->bl_curve_mutex);
return count;
}
static ssize_t show_bl_curve(struct device *device,
struct device_attribute *attr, char *buf)
{
struct fb_info *fb_info = dev_get_drvdata(device);
ssize_t len = 0;
unsigned int i;
/* Some drivers don't use framebuffer_alloc(), but those also
* don't have backlights.
*/
if (!fb_info || !fb_info->bl_dev)
return -ENODEV;
mutex_lock(&fb_info->bl_curve_mutex);
for (i = 0; i < FB_BACKLIGHT_LEVELS; i += 8)
len += scnprintf(&buf[len], PAGE_SIZE - len, "%8ph\n",
fb_info->bl_curve + i);
mutex_unlock(&fb_info->bl_curve_mutex);
return len;
}
#endif
/* When cmap is added back in it should be a binary attribute
* not a text one. Consideration should also be given to converting
* fbdev to use configfs instead of sysfs */
static DEVICE_ATTR(bits_per_pixel, 0644, show_bpp, store_bpp);
static DEVICE_ATTR(blank, 0644, show_blank, store_blank);
static DEVICE_ATTR(console, 0644, show_console, store_console);
static DEVICE_ATTR(cursor, 0644, show_cursor, store_cursor);
static DEVICE_ATTR(mode, 0644, show_mode, store_mode);
static DEVICE_ATTR(modes, 0644, show_modes, store_modes);
static DEVICE_ATTR(pan, 0644, show_pan, store_pan);
static DEVICE_ATTR(virtual_size, 0644, show_virtual, store_virtual);
static DEVICE_ATTR(name, 0444, show_name, NULL);
static DEVICE_ATTR(stride, 0444, show_stride, NULL);
static DEVICE_ATTR(rotate, 0644, show_rotate, store_rotate);
static DEVICE_ATTR(state, 0644, show_fbstate, store_fbstate);
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
static DEVICE_ATTR(bl_curve, 0644, show_bl_curve, store_bl_curve);
#endif
static struct attribute *fb_device_attrs[] = {
&dev_attr_bits_per_pixel.attr,
&dev_attr_blank.attr,
&dev_attr_console.attr,
&dev_attr_cursor.attr,
&dev_attr_mode.attr,
&dev_attr_modes.attr,
&dev_attr_pan.attr,
&dev_attr_virtual_size.attr,
&dev_attr_name.attr,
&dev_attr_stride.attr,
&dev_attr_rotate.attr,
&dev_attr_state.attr,
#if IS_ENABLED(CONFIG_FB_BACKLIGHT)
&dev_attr_bl_curve.attr,
#endif
NULL,
};
ATTRIBUTE_GROUPS(fb_device);
int fb_device_create(struct fb_info *fb_info)
{
int node = fb_info->node;
dev_t devt = MKDEV(FB_MAJOR, node);
int ret;
fb_info->dev = device_create_with_groups(fb_class, fb_info->device, devt, fb_info,
fb_device_groups, "fb%d", node);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
ret = PTR_ERR(fb_info->dev);
pr_warn("Unable to create device for framebuffer %d; error %d\n", node, ret);
fb_info->dev = NULL;
}
return 0;
}
void fb_device_destroy(struct fb_info *fb_info)
{
dev_t devt = MKDEV(FB_MAJOR, fb_info->node);
if (!fb_info->dev)
return;
device_destroy(fb_class, devt);
fb_info->dev = NULL;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
/*
* linux/drivers/video/console/softcursor.c
*
* Generic software cursor for frame buffer devices
*
* Created 14 Nov 2002 by James Simmons
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file COPYING in the main directory of this
* archive for more details.
*/
#include <linux/module.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/slab.h>
#include <asm/io.h>
#include "fbcon.h"
int soft_cursor(struct fb_info *info, struct fb_cursor *cursor)
{
struct fbcon_par *par = info->fbcon_par;
unsigned int scan_align = info->pixmap.scan_align - 1;
unsigned int buf_align = info->pixmap.buf_align - 1;
unsigned int i, size, dsize, s_pitch, d_pitch;
struct fb_image *image;
u8 *src, *dst;
if (info->state != FBINFO_STATE_RUNNING)
return 0;
s_pitch = (cursor->image.width + 7) >> 3;
dsize = s_pitch * cursor->image.height;
if (dsize + sizeof(struct fb_image) != par->cursor_size) {
kfree(par->cursor_src);
par->cursor_size = dsize + sizeof(struct fb_image);
par->cursor_src = kmalloc(par->cursor_size, GFP_ATOMIC);
if (!par->cursor_src) {
par->cursor_size = 0;
return -ENOMEM;
}
}
src = par->cursor_src + sizeof(struct fb_image);
image = (struct fb_image *)par->cursor_src;
*image = cursor->image;
d_pitch = (s_pitch + scan_align) & ~scan_align;
size = d_pitch * image->height + buf_align;
size &= ~buf_align;
dst = fb_get_buffer_offset(info, &info->pixmap, size);
if (cursor->enable) {
switch (cursor->rop) {
case ROP_XOR:
for (i = 0; i < dsize; i++)
src[i] = image->data[i] ^ cursor->mask[i];
break;
case ROP_COPY:
default:
for (i = 0; i < dsize; i++)
src[i] = image->data[i] & cursor->mask[i];
break;
}
} else
memcpy(src, image->data, dsize);
fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, image->height);
image->data = dst;
info->fbops->fb_imageblit(info, image);
return 0;
}
@@ -0,0 +1,664 @@
/*
* Common utility functions for VGA-based graphics cards.
*
* Copyright (c) 2006-2007 Ondrej Zajicek <santiago@crfreenet.org>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*
* Some parts are based on David Boucher's viafb (http://davesdomain.org.uk/viafb/)
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/math.h>
#include <linux/svga.h>
#include <asm/types.h>
#include <asm/io.h>
/* Write a CRT register value spread across multiple registers */
void svga_wcrt_multi(void __iomem *regbase, const struct vga_regset *regset, u32 value)
{
u8 regval, bitval, bitnum;
while (regset->regnum != VGA_REGSET_END_VAL) {
regval = vga_rcrt(regbase, regset->regnum);
bitnum = regset->lowbit;
while (bitnum <= regset->highbit) {
bitval = 1 << bitnum;
regval = regval & ~bitval;
if (value & 1)
regval = regval | bitval;
bitnum++;
value = value >> 1;
}
vga_wcrt(regbase, regset->regnum, regval);
regset++;
}
}
/* Write a sequencer register value spread across multiple registers */
void svga_wseq_multi(void __iomem *regbase, const struct vga_regset *regset, u32 value)
{
u8 regval, bitval, bitnum;
while (regset->regnum != VGA_REGSET_END_VAL) {
regval = vga_rseq(regbase, regset->regnum);
bitnum = regset->lowbit;
while (bitnum <= regset->highbit) {
bitval = 1 << bitnum;
regval = regval & ~bitval;
if (value & 1)
regval = regval | bitval;
bitnum++;
value = value >> 1;
}
vga_wseq(regbase, regset->regnum, regval);
regset++;
}
}
static unsigned int svga_regset_size(const struct vga_regset *regset)
{
u8 count = 0;
while (regset->regnum != VGA_REGSET_END_VAL) {
count += regset->highbit - regset->lowbit + 1;
regset++;
}
return 1 << count;
}
/* ------------------------------------------------------------------------- */
/* Set graphics controller registers to sane values */
void svga_set_default_gfx_regs(void __iomem *regbase)
{
/* All standard GFX registers (GR00 - GR08) */
vga_wgfx(regbase, VGA_GFX_SR_VALUE, 0x00);
vga_wgfx(regbase, VGA_GFX_SR_ENABLE, 0x00);
vga_wgfx(regbase, VGA_GFX_COMPARE_VALUE, 0x00);
vga_wgfx(regbase, VGA_GFX_DATA_ROTATE, 0x00);
vga_wgfx(regbase, VGA_GFX_PLANE_READ, 0x00);
vga_wgfx(regbase, VGA_GFX_MODE, 0x00);
/* vga_wgfx(regbase, VGA_GFX_MODE, 0x20); */
/* vga_wgfx(regbase, VGA_GFX_MODE, 0x40); */
vga_wgfx(regbase, VGA_GFX_MISC, 0x05);
/* vga_wgfx(regbase, VGA_GFX_MISC, 0x01); */
vga_wgfx(regbase, VGA_GFX_COMPARE_MASK, 0x0F);
vga_wgfx(regbase, VGA_GFX_BIT_MASK, 0xFF);
}
/* Set attribute controller registers to sane values */
void svga_set_default_atc_regs(void __iomem *regbase)
{
u8 count;
vga_r(regbase, 0x3DA);
vga_w(regbase, VGA_ATT_W, 0x00);
/* All standard ATC registers (AR00 - AR14) */
for (count = 0; count <= 0xF; count++)
svga_wattr(regbase, count, count);
svga_wattr(regbase, VGA_ATC_MODE, 0x01);
/* svga_wattr(regbase, VGA_ATC_MODE, 0x41); */
svga_wattr(regbase, VGA_ATC_OVERSCAN, 0x00);
svga_wattr(regbase, VGA_ATC_PLANE_ENABLE, 0x0F);
svga_wattr(regbase, VGA_ATC_PEL, 0x00);
svga_wattr(regbase, VGA_ATC_COLOR_PAGE, 0x00);
vga_r(regbase, 0x3DA);
vga_w(regbase, VGA_ATT_W, 0x20);
}
/* Set sequencer registers to sane values */
void svga_set_default_seq_regs(void __iomem *regbase)
{
/* Standard sequencer registers (SR01 - SR04), SR00 is not set */
vga_wseq(regbase, VGA_SEQ_CLOCK_MODE, VGA_SR01_CHAR_CLK_8DOTS);
vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, VGA_SR02_ALL_PLANES);
vga_wseq(regbase, VGA_SEQ_CHARACTER_MAP, 0x00);
/* vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM | VGA_SR04_SEQ_MODE | VGA_SR04_CHN_4M); */
vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM | VGA_SR04_SEQ_MODE);
}
/* Set CRTC registers to sane values */
void svga_set_default_crt_regs(void __iomem *regbase)
{
/* Standard CRT registers CR03 CR08 CR09 CR14 CR17 */
svga_wcrt_mask(regbase, 0x03, 0x80, 0x80); /* Enable vertical retrace EVRA */
vga_wcrt(regbase, VGA_CRTC_PRESET_ROW, 0);
svga_wcrt_mask(regbase, VGA_CRTC_MAX_SCAN, 0, 0x1F);
vga_wcrt(regbase, VGA_CRTC_UNDERLINE, 0);
vga_wcrt(regbase, VGA_CRTC_MODE, 0xE3);
}
void svga_set_textmode_vga_regs(void __iomem *regbase)
{
/* svga_wseq_mask(regbase, 0x1, 0x00, 0x01); */ /* Switch 8/9 pixel per char */
vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM);
vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, 0x03);
vga_wcrt(regbase, VGA_CRTC_MAX_SCAN, 0x0f); /* 0x4f */
vga_wcrt(regbase, VGA_CRTC_UNDERLINE, 0x1f);
svga_wcrt_mask(regbase, VGA_CRTC_MODE, 0x23, 0x7f);
vga_wcrt(regbase, VGA_CRTC_CURSOR_START, 0x0d);
vga_wcrt(regbase, VGA_CRTC_CURSOR_END, 0x0e);
vga_wcrt(regbase, VGA_CRTC_CURSOR_HI, 0x00);
vga_wcrt(regbase, VGA_CRTC_CURSOR_LO, 0x00);
vga_wgfx(regbase, VGA_GFX_MODE, 0x10); /* Odd/even memory mode */
vga_wgfx(regbase, VGA_GFX_MISC, 0x0E); /* Misc graphics register - text mode enable */
vga_wgfx(regbase, VGA_GFX_COMPARE_MASK, 0x00);
vga_r(regbase, 0x3DA);
vga_w(regbase, VGA_ATT_W, 0x00);
svga_wattr(regbase, 0x10, 0x0C); /* Attribute Mode Control Register - text mode, blinking and line graphics */
svga_wattr(regbase, 0x13, 0x08); /* Horizontal Pixel Panning Register */
vga_r(regbase, 0x3DA);
vga_w(regbase, VGA_ATT_W, 0x20);
}
#if 0
void svga_dump_var(struct fb_var_screeninfo *var, int node)
{
pr_debug("fb%d: var.vmode : 0x%X\n", node, var->vmode);
pr_debug("fb%d: var.xres : %d\n", node, var->xres);
pr_debug("fb%d: var.yres : %d\n", node, var->yres);
pr_debug("fb%d: var.bits_per_pixel: %d\n", node, var->bits_per_pixel);
pr_debug("fb%d: var.xres_virtual : %d\n", node, var->xres_virtual);
pr_debug("fb%d: var.yres_virtual : %d\n", node, var->yres_virtual);
pr_debug("fb%d: var.left_margin : %d\n", node, var->left_margin);
pr_debug("fb%d: var.right_margin : %d\n", node, var->right_margin);
pr_debug("fb%d: var.upper_margin : %d\n", node, var->upper_margin);
pr_debug("fb%d: var.lower_margin : %d\n", node, var->lower_margin);
pr_debug("fb%d: var.hsync_len : %d\n", node, var->hsync_len);
pr_debug("fb%d: var.vsync_len : %d\n", node, var->vsync_len);
pr_debug("fb%d: var.sync : 0x%X\n", node, var->sync);
pr_debug("fb%d: var.pixclock : %d\n\n", node, var->pixclock);
}
#endif /* 0 */
/* ------------------------------------------------------------------------- */
void svga_settile(struct fb_info *info, struct fb_tilemap *map)
{
const u8 *font = map->data;
u8 __iomem *fb = (u8 __iomem *)info->screen_base;
int i, c;
if ((map->width != 8) || (map->height != 16) ||
(map->depth != 1) || (map->length != 256)) {
fb_err(info, "unsupported font parameters: width %d, height %d, depth %d, length %d\n",
map->width, map->height, map->depth, map->length);
return;
}
fb += 2;
for (c = 0; c < map->length; c++) {
for (i = 0; i < map->height; i++) {
fb_writeb(font[i], fb + i * 4);
// fb[i * 4] = font[i];
}
fb += 128;
font += map->height;
}
}
/* Copy area in text (tileblit) mode */
void svga_tilecopy(struct fb_info *info, struct fb_tilearea *area)
{
int dx, dy;
/* colstride is halved in this function because u16 are used */
int colstride = 1 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK);
int rowstride = colstride * (info->var.xres_virtual / 8);
u16 __iomem *fb = (u16 __iomem *) info->screen_base;
u16 __iomem *src, *dst;
if ((area->sy > area->dy) ||
((area->sy == area->dy) && (area->sx > area->dx))) {
src = fb + area->sx * colstride + area->sy * rowstride;
dst = fb + area->dx * colstride + area->dy * rowstride;
} else {
src = fb + (area->sx + area->width - 1) * colstride
+ (area->sy + area->height - 1) * rowstride;
dst = fb + (area->dx + area->width - 1) * colstride
+ (area->dy + area->height - 1) * rowstride;
colstride = -colstride;
rowstride = -rowstride;
}
for (dy = 0; dy < area->height; dy++) {
u16 __iomem *src2 = src;
u16 __iomem *dst2 = dst;
for (dx = 0; dx < area->width; dx++) {
fb_writew(fb_readw(src2), dst2);
// *dst2 = *src2;
src2 += colstride;
dst2 += colstride;
}
src += rowstride;
dst += rowstride;
}
}
/* Fill area in text (tileblit) mode */
void svga_tilefill(struct fb_info *info, struct fb_tilerect *rect)
{
int dx, dy;
int colstride = 2 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK);
int rowstride = colstride * (info->var.xres_virtual / 8);
int attr = (0x0F & rect->bg) << 4 | (0x0F & rect->fg);
u8 __iomem *fb = (u8 __iomem *)info->screen_base;
fb += rect->sx * colstride + rect->sy * rowstride;
for (dy = 0; dy < rect->height; dy++) {
u8 __iomem *fb2 = fb;
for (dx = 0; dx < rect->width; dx++) {
fb_writeb(rect->index, fb2);
fb_writeb(attr, fb2 + 1);
fb2 += colstride;
}
fb += rowstride;
}
}
/* Write text in text (tileblit) mode */
void svga_tileblit(struct fb_info *info, struct fb_tileblit *blit)
{
int dx, dy, i;
int colstride = 2 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK);
int rowstride = colstride * (info->var.xres_virtual / 8);
int attr = (0x0F & blit->bg) << 4 | (0x0F & blit->fg);
u8 __iomem *fb = (u8 __iomem *)info->screen_base;
fb += blit->sx * colstride + blit->sy * rowstride;
i = 0;
for (dy = 0; dy < blit->height; dy++) {
u8 __iomem *fb2 = fb;
for (dx = 0; dx < blit->width; dx++) {
fb_writeb(blit->indices[i], fb2);
fb_writeb(attr, fb2 + 1);
fb2 += colstride;
i++;
if (i == blit->length)
return;
}
fb += rowstride;
}
}
/* Set cursor in text (tileblit) mode */
void svga_tilecursor(void __iomem *regbase, struct fb_info *info, struct fb_tilecursor *cursor)
{
u8 cs = 0x0d;
u8 ce = 0x0e;
u16 pos = cursor->sx + (info->var.xoffset / 8)
+ (cursor->sy + (info->var.yoffset / 16))
* (info->var.xres_virtual / 8);
if (!cursor->mode)
return;
svga_wcrt_mask(regbase, 0x0A, 0x20, 0x20); /* disable cursor */
if (cursor->shape == FB_TILE_CURSOR_NONE)
return;
switch (cursor->shape) {
case FB_TILE_CURSOR_UNDERLINE:
cs = 0x0d;
break;
case FB_TILE_CURSOR_LOWER_THIRD:
cs = 0x09;
break;
case FB_TILE_CURSOR_LOWER_HALF:
cs = 0x07;
break;
case FB_TILE_CURSOR_TWO_THIRDS:
cs = 0x05;
break;
case FB_TILE_CURSOR_BLOCK:
cs = 0x01;
break;
}
/* set cursor position */
vga_wcrt(regbase, 0x0E, pos >> 8);
vga_wcrt(regbase, 0x0F, pos & 0xFF);
vga_wcrt(regbase, 0x0B, ce); /* set cursor end */
vga_wcrt(regbase, 0x0A, cs); /* set cursor start and enable it */
}
int svga_get_tilemax(struct fb_info *info)
{
return 256;
}
/* Get capabilities of accelerator based on the mode */
void svga_get_caps(struct fb_info *info, struct fb_blit_caps *caps,
struct fb_var_screeninfo *var)
{
if (var->bits_per_pixel == 0) {
/* can only support 256 8x16 bitmap */
bitmap_zero(caps->x, FB_MAX_BLIT_WIDTH);
set_bit(8 - 1, caps->x);
bitmap_zero(caps->y, FB_MAX_BLIT_HEIGHT);
set_bit(16 - 1, caps->y);
caps->len = 256;
} else {
if (var->bits_per_pixel == 4) {
bitmap_zero(caps->x, FB_MAX_BLIT_WIDTH);
set_bit(8 - 1, caps->x);
} else {
bitmap_fill(caps->x, FB_MAX_BLIT_WIDTH);
}
bitmap_fill(caps->y, FB_MAX_BLIT_HEIGHT);
caps->len = ~(u32)0;
}
}
EXPORT_SYMBOL(svga_get_caps);
/* ------------------------------------------------------------------------- */
/*
* Compute PLL settings (M, N, R)
* F_VCO = (F_BASE * M) / N
* F_OUT = F_VCO / (2^R)
*/
int svga_compute_pll(const struct svga_pll *pll, u32 f_wanted, u16 *m, u16 *n, u16 *r, int node)
{
u16 am, an, ar;
u32 f_vco, f_current, delta_current, delta_best;
pr_debug("fb%d: ideal frequency: %d kHz\n", node, (unsigned int)f_wanted);
ar = pll->r_max;
f_vco = f_wanted << ar;
/* overflow check */
if ((f_vco >> ar) != f_wanted)
return -EINVAL;
/* It is usually better to have greater VCO clock
because of better frequency stability.
So first try r_max, then r smaller. */
while ((ar > pll->r_min) && (f_vco > pll->f_vco_max)) {
ar--;
f_vco = f_vco >> 1;
}
/* VCO bounds check */
if ((f_vco < pll->f_vco_min) || (f_vco > pll->f_vco_max))
return -EINVAL;
delta_best = 0xFFFFFFFF;
*m = 0;
*n = 0;
*r = ar;
am = pll->m_min;
an = pll->n_min;
while ((am <= pll->m_max) && (an <= pll->n_max)) {
f_current = (pll->f_base * am) / an;
delta_current = abs_diff(f_current, f_vco);
if (delta_current < delta_best) {
delta_best = delta_current;
*m = am;
*n = an;
}
if (f_current <= f_vco)
am++;
else
an++;
}
f_current = (pll->f_base * *m) / *n;
pr_debug("fb%d: found frequency: %d kHz (VCO %d kHz)\n", node, (int)(f_current >> ar), (int)f_current);
pr_debug("fb%d: m = %d n = %d r = %d\n", node, (unsigned int)*m, (unsigned int)*n, (unsigned int)*r);
return 0;
}
/* ------------------------------------------------------------------------- */
/* Check CRT timing values */
int svga_check_timings(const struct svga_timing_regs *tm, struct fb_var_screeninfo *var, int node)
{
u32 value;
var->xres = (var->xres + 7) & ~7;
var->left_margin = (var->left_margin + 7) & ~7;
var->right_margin = (var->right_margin + 7) & ~7;
var->hsync_len = (var->hsync_len + 7) & ~7;
/* Check horizontal total */
value = var->xres + var->left_margin + var->right_margin + var->hsync_len;
if (((value / 8) - 5) >= svga_regset_size(tm->h_total_regs))
return -EINVAL;
/* Check horizontal display and blank start */
value = var->xres;
if (((value / 8) - 1) >= svga_regset_size(tm->h_display_regs))
return -EINVAL;
if (((value / 8) - 1) >= svga_regset_size(tm->h_blank_start_regs))
return -EINVAL;
/* Check horizontal sync start */
value = var->xres + var->right_margin;
if (((value / 8) - 1) >= svga_regset_size(tm->h_sync_start_regs))
return -EINVAL;
/* Check horizontal blank end (or length) */
value = var->left_margin + var->right_margin + var->hsync_len;
if ((value == 0) || ((value / 8) >= svga_regset_size(tm->h_blank_end_regs)))
return -EINVAL;
/* Check horizontal sync end (or length) */
value = var->hsync_len;
if ((value == 0) || ((value / 8) >= svga_regset_size(tm->h_sync_end_regs)))
return -EINVAL;
/* Check vertical total */
value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len;
if ((value - 1) >= svga_regset_size(tm->v_total_regs))
return -EINVAL;
/* Check vertical display and blank start */
value = var->yres;
if ((value - 1) >= svga_regset_size(tm->v_display_regs))
return -EINVAL;
if ((value - 1) >= svga_regset_size(tm->v_blank_start_regs))
return -EINVAL;
/* Check vertical sync start */
value = var->yres + var->lower_margin;
if ((value - 1) >= svga_regset_size(tm->v_sync_start_regs))
return -EINVAL;
/* Check vertical blank end (or length) */
value = var->upper_margin + var->lower_margin + var->vsync_len;
if ((value == 0) || (value >= svga_regset_size(tm->v_blank_end_regs)))
return -EINVAL;
/* Check vertical sync end (or length) */
value = var->vsync_len;
if ((value == 0) || (value >= svga_regset_size(tm->v_sync_end_regs)))
return -EINVAL;
return 0;
}
/* Set CRT timing registers */
void svga_set_timings(void __iomem *regbase, const struct svga_timing_regs *tm,
struct fb_var_screeninfo *var,
u32 hmul, u32 hdiv, u32 vmul, u32 vdiv, u32 hborder, int node)
{
u8 regval;
u32 value;
value = var->xres + var->left_margin + var->right_margin + var->hsync_len;
value = (value * hmul) / hdiv;
pr_debug("fb%d: horizontal total : %d\n", node, value);
svga_wcrt_multi(regbase, tm->h_total_regs, (value / 8) - 5);
value = var->xres;
value = (value * hmul) / hdiv;
pr_debug("fb%d: horizontal display : %d\n", node, value);
svga_wcrt_multi(regbase, tm->h_display_regs, (value / 8) - 1);
value = var->xres;
value = (value * hmul) / hdiv;
pr_debug("fb%d: horizontal blank start: %d\n", node, value);
svga_wcrt_multi(regbase, tm->h_blank_start_regs, (value / 8) - 1 + hborder);
value = var->xres + var->left_margin + var->right_margin + var->hsync_len;
value = (value * hmul) / hdiv;
pr_debug("fb%d: horizontal blank end : %d\n", node, value);
svga_wcrt_multi(regbase, tm->h_blank_end_regs, (value / 8) - 1 - hborder);
value = var->xres + var->right_margin;
value = (value * hmul) / hdiv;
pr_debug("fb%d: horizontal sync start : %d\n", node, value);
svga_wcrt_multi(regbase, tm->h_sync_start_regs, (value / 8));
value = var->xres + var->right_margin + var->hsync_len;
value = (value * hmul) / hdiv;
pr_debug("fb%d: horizontal sync end : %d\n", node, value);
svga_wcrt_multi(regbase, tm->h_sync_end_regs, (value / 8));
value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len;
value = (value * vmul) / vdiv;
pr_debug("fb%d: vertical total : %d\n", node, value);
svga_wcrt_multi(regbase, tm->v_total_regs, value - 2);
value = var->yres;
value = (value * vmul) / vdiv;
pr_debug("fb%d: vertical display : %d\n", node, value);
svga_wcrt_multi(regbase, tm->v_display_regs, value - 1);
value = var->yres;
value = (value * vmul) / vdiv;
pr_debug("fb%d: vertical blank start : %d\n", node, value);
svga_wcrt_multi(regbase, tm->v_blank_start_regs, value);
value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len;
value = (value * vmul) / vdiv;
pr_debug("fb%d: vertical blank end : %d\n", node, value);
svga_wcrt_multi(regbase, tm->v_blank_end_regs, value - 2);
value = var->yres + var->lower_margin;
value = (value * vmul) / vdiv;
pr_debug("fb%d: vertical sync start : %d\n", node, value);
svga_wcrt_multi(regbase, tm->v_sync_start_regs, value);
value = var->yres + var->lower_margin + var->vsync_len;
value = (value * vmul) / vdiv;
pr_debug("fb%d: vertical sync end : %d\n", node, value);
svga_wcrt_multi(regbase, tm->v_sync_end_regs, value);
/* Set horizontal and vertical sync pulse polarity in misc register */
regval = vga_r(regbase, VGA_MIS_R);
if (var->sync & FB_SYNC_HOR_HIGH_ACT) {
pr_debug("fb%d: positive horizontal sync\n", node);
regval = regval & ~0x80;
} else {
pr_debug("fb%d: negative horizontal sync\n", node);
regval = regval | 0x80;
}
if (var->sync & FB_SYNC_VERT_HIGH_ACT) {
pr_debug("fb%d: positive vertical sync\n", node);
regval = regval & ~0x40;
} else {
pr_debug("fb%d: negative vertical sync\n\n", node);
regval = regval | 0x40;
}
vga_w(regbase, VGA_MIS_W, regval);
}
/* ------------------------------------------------------------------------- */
static inline int match_format(const struct svga_fb_format *frm,
struct fb_var_screeninfo *var)
{
int i = 0;
int stored = -EINVAL;
while (frm->bits_per_pixel != SVGA_FORMAT_END_VAL) {
if ((var->bits_per_pixel == frm->bits_per_pixel) &&
(var->red.length <= frm->red.length) &&
(var->green.length <= frm->green.length) &&
(var->blue.length <= frm->blue.length) &&
(var->transp.length <= frm->transp.length) &&
(var->nonstd == frm->nonstd))
return i;
if (var->bits_per_pixel == frm->bits_per_pixel)
stored = i;
i++;
frm++;
}
return stored;
}
int svga_match_format(const struct svga_fb_format *frm,
struct fb_var_screeninfo *var,
struct fb_fix_screeninfo *fix)
{
int i = match_format(frm, var);
if (i >= 0) {
var->bits_per_pixel = frm[i].bits_per_pixel;
var->red = frm[i].red;
var->green = frm[i].green;
var->blue = frm[i].blue;
var->transp = frm[i].transp;
var->nonstd = frm[i].nonstd;
if (fix != NULL) {
fix->type = frm[i].type;
fix->type_aux = frm[i].type_aux;
fix->visual = frm[i].visual;
fix->xpanstep = frm[i].xpanstep;
}
}
return i;
}
EXPORT_SYMBOL(svga_wcrt_multi);
EXPORT_SYMBOL(svga_wseq_multi);
EXPORT_SYMBOL(svga_set_default_gfx_regs);
EXPORT_SYMBOL(svga_set_default_atc_regs);
EXPORT_SYMBOL(svga_set_default_seq_regs);
EXPORT_SYMBOL(svga_set_default_crt_regs);
EXPORT_SYMBOL(svga_set_textmode_vga_regs);
EXPORT_SYMBOL(svga_settile);
EXPORT_SYMBOL(svga_tilecopy);
EXPORT_SYMBOL(svga_tilefill);
EXPORT_SYMBOL(svga_tileblit);
EXPORT_SYMBOL(svga_tilecursor);
EXPORT_SYMBOL(svga_get_tilemax);
EXPORT_SYMBOL(svga_compute_pll);
EXPORT_SYMBOL(svga_check_timings);
EXPORT_SYMBOL(svga_set_timings);
EXPORT_SYMBOL(svga_match_format);
MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>");
MODULE_DESCRIPTION("Common utility functions for VGA-based graphics cards");
MODULE_LICENSE("GPL");
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/bitrev.h>
#include <asm/types.h>
#ifdef CONFIG_FB_SYS_REV_PIXELS_IN_BYTE
#define FB_REV_PIXELS_IN_BYTE
#endif
#include "sysmem.h"
#include "fb_copyarea.h"
void sys_copyarea(struct fb_info *p, const struct fb_copyarea *area)
{
if (!(p->flags & FBINFO_VIRTFB))
fb_warn_once(p, "%s: framebuffer is not in virtual address space.\n", __func__);
fb_copyarea(p, area);
}
EXPORT_SYMBOL(sys_copyarea);
MODULE_AUTHOR("Zsolt Kajtar <soci@c64.rulez.org>");
MODULE_DESCRIPTION("Virtual memory packed pixel framebuffer area copy");
MODULE_LICENSE("GPL");
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/bitrev.h>
#include <asm/types.h>
#ifdef CONFIG_FB_SYS_REV_PIXELS_IN_BYTE
#define FB_REV_PIXELS_IN_BYTE
#endif
#include "sysmem.h"
#include "fb_fillrect.h"
void sys_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
{
if (!(p->flags & FBINFO_VIRTFB))
fb_warn_once(p, "%s: framebuffer is not in virtual address space.\n", __func__);
fb_fillrect(p, rect);
}
EXPORT_SYMBOL(sys_fillrect);
MODULE_AUTHOR("Zsolt Kajtar <soci@c64.rulez.org>");
MODULE_DESCRIPTION("Virtual memory packed pixel framebuffer area fill");
MODULE_LICENSE("GPL");
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
#include <linux/export.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/bitrev.h>
#include <asm/types.h>
#ifdef CONFIG_FB_SYS_REV_PIXELS_IN_BYTE
#define FB_REV_PIXELS_IN_BYTE
#endif
#include "sysmem.h"
#include "fb_imageblit.h"
void sys_imageblit(struct fb_info *p, const struct fb_image *image)
{
if (!(p->flags & FBINFO_VIRTFB))
fb_warn_once(p, "%s: framebuffer is not in virtual address space.\n", __func__);
fb_imageblit(p, image);
}
EXPORT_SYMBOL(sys_imageblit);
MODULE_AUTHOR("Zsolt Kajtar <soci@c64.rulez.org>");
MODULE_DESCRIPTION("Virtual memory packed pixel framebuffer image draw");
MODULE_LICENSE("GPL");
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Virtual memory framebuffer access for drawing routines
*
* Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
*/
/* keeps track of a bit address in framebuffer memory */
struct fb_address {
void *address;
int bits;
};
/* initialize the bit address pointer to the beginning of the frame buffer */
static inline struct fb_address fb_address_init(struct fb_info *p)
{
void *base = p->screen_buffer;
struct fb_address ptr;
ptr.address = PTR_ALIGN_DOWN(base, BITS_PER_LONG / BITS_PER_BYTE);
ptr.bits = (base - ptr.address) * BITS_PER_BYTE;
return ptr;
}
/* framebuffer write access */
static inline void fb_write_offset(unsigned long val, int offset, const struct fb_address *dst)
{
unsigned long *mem = dst->address;
mem[offset] = val;
}
/* framebuffer read access */
static inline unsigned long fb_read_offset(int offset, const struct fb_address *src)
{
unsigned long *mem = src->address;
return mem[offset];
}
@@ -0,0 +1,188 @@
/*
* linux/drivers/video/console/tileblit.c -- Tile Blitting Operation
*
* Copyright (C) 2004 Antonino Daplas <adaplas @pol.net>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/module.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/vt_kern.h>
#include <linux/console.h>
#include <asm/types.h>
#include "fbcon.h"
static void tile_bmove(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int dy, int dx, int height, int width)
{
struct fb_tilearea area;
area.sx = sx;
area.sy = sy;
area.dx = dx;
area.dy = dy;
area.height = height;
area.width = width;
info->tileops->fb_tilecopy(info, &area);
}
static void tile_clear(struct vc_data *vc, struct fb_info *info, int sy,
int sx, int height, int width, int fg, int bg)
{
struct fb_tilerect rect;
rect.index = vc->vc_video_erase_char &
((vc->vc_hi_font_mask) ? 0x1ff : 0xff);
rect.fg = fg;
rect.bg = bg;
rect.sx = sx;
rect.sy = sy;
rect.width = width;
rect.height = height;
rect.rop = ROP_COPY;
info->tileops->fb_tilefill(info, &rect);
}
static void tile_putcs(struct vc_data *vc, struct fb_info *info,
const unsigned short *s, int count, int yy, int xx,
int fg, int bg)
{
struct fb_tileblit blit;
unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
int size = sizeof(u32) * count, i;
blit.sx = xx;
blit.sy = yy;
blit.width = count;
blit.height = 1;
blit.fg = fg;
blit.bg = bg;
blit.length = count;
blit.indices = (u32 *) fb_get_buffer_offset(info, &info->pixmap, size);
for (i = 0; i < count; i++)
blit.indices[i] = (u32)(scr_readw(s++) & charmask);
info->tileops->fb_tileblit(info, &blit);
}
static void tile_clear_margins(struct vc_data *vc, struct fb_info *info,
int color, int bottom_only)
{
unsigned int cw = vc->vc_font.width;
unsigned int ch = vc->vc_font.height;
unsigned int rw = info->var.xres - (vc->vc_cols*cw);
unsigned int bh = info->var.yres - (vc->vc_rows*ch);
unsigned int rs = info->var.xres - rw;
unsigned int bs = info->var.yres - bh;
unsigned int vwt = info->var.xres_virtual / cw;
unsigned int vht = info->var.yres_virtual / ch;
struct fb_tilerect rect;
rect.index = vc->vc_video_erase_char &
((vc->vc_hi_font_mask) ? 0x1ff : 0xff);
rect.fg = color;
rect.bg = color;
if ((int) rw > 0 && !bottom_only) {
rect.sx = (info->var.xoffset + rs + cw - 1) / cw;
rect.sy = 0;
rect.width = (rw + cw - 1) / cw;
rect.height = vht;
if (rect.width + rect.sx > vwt)
rect.width = vwt - rect.sx;
if (rect.sx < vwt)
info->tileops->fb_tilefill(info, &rect);
}
if ((int) bh > 0) {
rect.sx = info->var.xoffset / cw;
rect.sy = (info->var.yoffset + bs) / ch;
rect.width = rs / cw;
rect.height = (bh + ch - 1) / ch;
if (rect.height + rect.sy > vht)
rect.height = vht - rect.sy;
if (rect.sy < vht)
info->tileops->fb_tilefill(info, &rect);
}
}
static void tile_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
int fg, int bg)
{
struct fb_tilecursor cursor;
int use_sw = vc->vc_cursor_type & CUR_SW;
cursor.sx = vc->state.x;
cursor.sy = vc->state.y;
cursor.mode = enable && !use_sw;
cursor.fg = fg;
cursor.bg = bg;
switch (vc->vc_cursor_type & 0x0f) {
case CUR_NONE:
cursor.shape = FB_TILE_CURSOR_NONE;
break;
case CUR_UNDERLINE:
cursor.shape = FB_TILE_CURSOR_UNDERLINE;
break;
case CUR_LOWER_THIRD:
cursor.shape = FB_TILE_CURSOR_LOWER_THIRD;
break;
case CUR_LOWER_HALF:
cursor.shape = FB_TILE_CURSOR_LOWER_HALF;
break;
case CUR_TWO_THIRDS:
cursor.shape = FB_TILE_CURSOR_TWO_THIRDS;
break;
case CUR_BLOCK:
default:
cursor.shape = FB_TILE_CURSOR_BLOCK;
break;
}
info->tileops->fb_tilecursor(info, &cursor);
}
static int tile_update_start(struct fb_info *info)
{
struct fbcon_par *par = info->fbcon_par;
int err;
err = fb_pan_display(info, &par->var);
par->var.xoffset = info->var.xoffset;
par->var.yoffset = info->var.yoffset;
par->var.vmode = info->var.vmode;
return err;
}
static const struct fbcon_bitops tile_fbcon_bitops = {
.bmove = tile_bmove,
.clear = tile_clear,
.putcs = tile_putcs,
.clear_margins = tile_clear_margins,
.cursor = tile_cursor,
.update_start = tile_update_start,
};
void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info)
{
struct fb_tilemap map;
struct fbcon_par *par = info->fbcon_par;
par->bitops = &tile_fbcon_bitops;
if (par->p) {
map.width = vc->vc_font.width;
map.height = vc->vc_font.height;
map.depth = 1;
map.length = vc->vc_font.charcount;
map.data = par->p->fontdata;
info->tileops->fb_settile(info, &map);
}
}