From 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Feb 2023 18:24:12 -0800 Subject: Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next Pull networking updates from Jakub Kicinski: "Core: - Add dedicated kmem_cache for typical/small skb->head, avoid having to access struct page at kfree time, and improve memory use. - Introduce sysctl to set default RPS configuration for new netdevs. - Define Netlink protocol specification format which can be used to describe messages used by each family and auto-generate parsers. Add tools for generating kernel data structures and uAPI headers. - Expose all net/core sysctls inside netns. - Remove 4s sleep in netpoll if carrier is instantly detected on boot. - Add configurable limit of MDB entries per port, and port-vlan. - Continue populating drop reasons throughout the stack. - Retire a handful of legacy Qdiscs and classifiers. Protocols: - Support IPv4 big TCP (TSO frames larger than 64kB). - Add IP_LOCAL_PORT_RANGE socket option, to control local port range on socket by socket basis. - Track and report in procfs number of MPTCP sockets used. - Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path manager. - IPv6: don't check net.ipv6.route.max_size and rely on garbage collection to free memory (similarly to IPv4). - Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986). - ICMP: add per-rate limit counters. - Add support for user scanning requests in ieee802154. - Remove static WEP support. - Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate reporting. - WiFi 7 EHT channel puncturing support (client & AP). BPF: - Add a rbtree data structure following the "next-gen data structure" precedent set by recently added linked list, that is, by using kfunc + kptr instead of adding a new BPF map type. - Expose XDP hints via kfuncs with initial support for RX hash and timestamp metadata. - Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to better support decap on GRE tunnel devices not operating in collect metadata. - Improve x86 JIT's codegen for PROBE_MEM runtime error checks. - Remove the need for trace_printk_lock for bpf_trace_printk and bpf_trace_vprintk helpers. - Extend libbpf's bpf_tracing.h support for tracing arguments of kprobes/uprobes and syscall as a special case. - Significantly reduce the search time for module symbols by livepatch and BPF. - Enable cpumasks to be used as kptrs, which is useful for tracing programs tracking which tasks end up running on which CPUs in different time intervals. - Add support for BPF trampoline on s390x and riscv64. - Add capability to export the XDP features supported by the NIC. - Add __bpf_kfunc tag for marking kernel functions as kfuncs. - Add cgroup.memory=nobpf kernel parameter option to disable BPF memory accounting for container environments. Netfilter: - Remove the CLUSTERIP target. It has been marked as obsolete for years, and we still have WARN splats wrt races of the out-of-band /proc interface installed by this target. - Add 'destroy' commands to nf_tables. They are identical to the existing 'delete' commands, but do not return an error if the referenced object (set, chain, rule...) did not exist. Driver API: - Improve cpumask_local_spread() locality to help NICs set the right IRQ affinity on AMD platforms. - Separate C22 and C45 MDIO bus transactions more clearly. - Introduce new DCB table to control DSCP rewrite on egress. - Support configuration of Physical Layer Collision Avoidance (PLCA) Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of shared medium Ethernet. - Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing preemption of low priority frames by high priority frames. - Add support for controlling MACSec offload using netlink SET. - Rework devlink instance refcounts to allow registration and de-registration under the instance lock. Split the code into multiple files, drop some of the unnecessarily granular locks and factor out common parts of netlink operation handling. - Add TX frame aggregation parameters (for USB drivers). - Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning messages with notifications for debug. - Allow offloading of UDP NEW connections via act_ct. - Add support for per action HW stats in TC. - Support hardware miss to TC action (continue processing in SW from a specific point in the action chain). - Warn if old Wireless Extension user space interface is used with modern cfg80211/mac80211 drivers. Do not support Wireless Extensions for Wi-Fi 7 devices at all. Everyone should switch to using nl80211 interface instead. - Improve the CAN bit timing configuration. Use extack to return error messages directly to user space, update the SJW handling, including the definition of a new default value that will benefit CAN-FD controllers, by increasing their oscillator tolerance. New hardware / drivers: - Ethernet: - nVidia BlueField-3 support (control traffic driver) - Ethernet support for imx93 SoCs - Motorcomm yt8531 gigabit Ethernet PHY - onsemi NCN26000 10BASE-T1S PHY (with support for PLCA) - Microchip LAN8841 PHY (incl. cable diagnostics and PTP) - Amlogic gxl MDIO mux - WiFi: - RealTek RTL8188EU (rtl8xxxu) - Qualcomm Wi-Fi 7 devices (ath12k) - CAN: - Renesas R-Car V4H Drivers: - Bluetooth: - Set Per Platform Antenna Gain (PPAG) for Intel controllers. - Ethernet NICs: - Intel (1G, igc): - support TSN / Qbv / packet scheduling features of i226 model - Intel (100G, ice): - use GNSS subsystem instead of TTY - multi-buffer XDP support - extend support for GPIO pins to E823 devices - nVidia/Mellanox: - update the shared buffer configuration on PFC commands - implement PTP adjphase function for HW offset control - TC support for Geneve and GRE with VF tunnel offload - more efficient crypto key management method - multi-port eswitch support - Netronome/Corigine: - add DCB IEEE support - support IPsec offloading for NFP3800 - Freescale/NXP (enetc): - support XDP_REDIRECT for XDP non-linear buffers - improve reconfig, avoid link flap and waiting for idle - support MAC Merge layer - Other NICs: - sfc/ef100: add basic devlink support for ef100 - ionic: rx_push mode operation (writing descriptors via MMIO) - bnxt: use the auxiliary bus abstraction for RDMA - r8169: disable ASPM and reset bus in case of tx timeout - cpsw: support QSGMII mode for J721e CPSW9G - cpts: support pulse-per-second output - ngbe: add an mdio bus driver - usbnet: optimize usbnet_bh() by avoiding unnecessary queuing - r8152: handle devices with FW with NCM support - amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation - virtio-net: support multi buffer XDP - virtio/vsock: replace virtio_vsock_pkt with sk_buff - tsnep: XDP support - Ethernet high-speed switches: - nVidia/Mellanox (mlxsw): - add support for latency TLV (in FW control messages) - Microchip (sparx5): - separate explicit and implicit traffic forwarding rules, make the implicit rules always active - add support for egress DSCP rewrite - IS0 VCAP support (Ingress Classification) - IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS etc.) - ES2 VCAP support (Egress Access Control) - support for Per-Stream Filtering and Policing (802.1Q, 8.6.5.1) - Ethernet embedded switches: - Marvell (mv88e6xxx): - add MAB (port auth) offload support - enable PTP receive for mv88e6390 - NXP (ocelot): - support MAC Merge layer - support for the the vsc7512 internal copper phys - Microchip: - lan9303: convert to PHYLINK - lan966x: support TC flower filter statistics - lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x - lan937x: support Credit Based Shaper configuration - ksz9477: support Energy Efficient Ethernet - other: - qca8k: convert to regmap read/write API, use bulk operations - rswitch: Improve TX timestamp accuracy - Intel WiFi (iwlwifi): - EHT (Wi-Fi 7) rate reporting - STEP equalizer support: transfer some STEP (connection to radio on platforms with integrated wifi) related parameters from the BIOS to the firmware. - Qualcomm 802.11ax WiFi (ath11k): - IPQ5018 support - Fine Timing Measurement (FTM) responder role support - channel 177 support - MediaTek WiFi (mt76): - per-PHY LED support - mt7996: EHT (Wi-Fi 7) support - Wireless Ethernet Dispatch (WED) reset support - switch to using page pool allocator - RealTek WiFi (rtw89): - support new version of Bluetooth co-existance - Mobile: - rmnet: support TX aggregation" * tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits) page_pool: add a comment explaining the fragment counter usage net: ethtool: fix __ethtool_dev_mm_supported() implementation ethtool: pse-pd: Fix double word in comments xsk: add linux/vmalloc.h to xsk.c sefltests: netdevsim: wait for devlink instance after netns removal selftest: fib_tests: Always cleanup before exit net/mlx5e: Align IPsec ASO result memory to be as required by hardware net/mlx5e: TC, Set CT miss to the specific ct action instance net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG net/mlx5: Refactor tc miss handling to a single function net/mlx5: Kconfig: Make tc offload depend on tc skb extension net/sched: flower: Support hardware miss to tc action net/sched: flower: Move filter handle initialization earlier net/sched: cls_api: Support hardware miss to tc action net/sched: Rename user cookie and act cookie sfc: fix builds without CONFIG_RTC_LIB sfc: clean up some inconsistent indentings net/mlx4_en: Introduce flexible array to silence overflow warning net: lan966x: Fix possible deadlock inside PTP net/ulp: Remove redundant ->clone() test in inet_clone_ulp(). ... --- drivers/video/fbdev/core/Makefile | 28 + drivers/video/fbdev/core/bitblit.c | 406 ++++ drivers/video/fbdev/core/cfbcopyarea.c | 437 ++++ drivers/video/fbdev/core/cfbfillrect.c | 371 ++++ drivers/video/fbdev/core/cfbimgblt.c | 366 ++++ drivers/video/fbdev/core/fb_cmdline.c | 107 + drivers/video/fbdev/core/fb_ddc.c | 127 ++ drivers/video/fbdev/core/fb_defio.c | 342 +++ drivers/video/fbdev/core/fb_draw.h | 187 ++ drivers/video/fbdev/core/fb_notify.c | 54 + drivers/video/fbdev/core/fb_sys_fops.c | 104 + drivers/video/fbdev/core/fbcmap.c | 362 ++++ drivers/video/fbdev/core/fbcon.c | 3443 +++++++++++++++++++++++++++++++ drivers/video/fbdev/core/fbcon.h | 269 +++ drivers/video/fbdev/core/fbcon_ccw.c | 411 ++++ drivers/video/fbdev/core/fbcon_cw.c | 394 ++++ drivers/video/fbdev/core/fbcon_rotate.c | 111 + drivers/video/fbdev/core/fbcon_rotate.h | 96 + drivers/video/fbdev/core/fbcon_ud.c | 438 ++++ drivers/video/fbdev/core/fbcvt.c | 368 ++++ drivers/video/fbdev/core/fbmem.c | 1866 +++++++++++++++++ drivers/video/fbdev/core/fbmon.c | 1520 ++++++++++++++ drivers/video/fbdev/core/fbsysfs.c | 581 ++++++ drivers/video/fbdev/core/modedb.c | 1212 +++++++++++ drivers/video/fbdev/core/softcursor.c | 76 + drivers/video/fbdev/core/svgalib.c | 672 ++++++ drivers/video/fbdev/core/syscopyarea.c | 370 ++++ drivers/video/fbdev/core/sysfillrect.c | 325 +++ drivers/video/fbdev/core/sysimgblt.c | 336 +++ drivers/video/fbdev/core/tileblit.c | 151 ++ 30 files changed, 15530 insertions(+) create mode 100644 drivers/video/fbdev/core/Makefile create mode 100644 drivers/video/fbdev/core/bitblit.c create mode 100644 drivers/video/fbdev/core/cfbcopyarea.c create mode 100644 drivers/video/fbdev/core/cfbfillrect.c create mode 100644 drivers/video/fbdev/core/cfbimgblt.c create mode 100644 drivers/video/fbdev/core/fb_cmdline.c create mode 100644 drivers/video/fbdev/core/fb_ddc.c create mode 100644 drivers/video/fbdev/core/fb_defio.c create mode 100644 drivers/video/fbdev/core/fb_draw.h create mode 100644 drivers/video/fbdev/core/fb_notify.c create mode 100644 drivers/video/fbdev/core/fb_sys_fops.c create mode 100644 drivers/video/fbdev/core/fbcmap.c create mode 100644 drivers/video/fbdev/core/fbcon.c create mode 100644 drivers/video/fbdev/core/fbcon.h create mode 100644 drivers/video/fbdev/core/fbcon_ccw.c create mode 100644 drivers/video/fbdev/core/fbcon_cw.c create mode 100644 drivers/video/fbdev/core/fbcon_rotate.c create mode 100644 drivers/video/fbdev/core/fbcon_rotate.h create mode 100644 drivers/video/fbdev/core/fbcon_ud.c create mode 100644 drivers/video/fbdev/core/fbcvt.c create mode 100644 drivers/video/fbdev/core/fbmem.c create mode 100644 drivers/video/fbdev/core/fbmon.c create mode 100644 drivers/video/fbdev/core/fbsysfs.c create mode 100644 drivers/video/fbdev/core/modedb.c create mode 100644 drivers/video/fbdev/core/softcursor.c create mode 100644 drivers/video/fbdev/core/svgalib.c create mode 100644 drivers/video/fbdev/core/syscopyarea.c create mode 100644 drivers/video/fbdev/core/sysfillrect.c create mode 100644 drivers/video/fbdev/core/sysimgblt.c create mode 100644 drivers/video/fbdev/core/tileblit.c (limited to 'drivers/video/fbdev/core') diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile new file mode 100644 index 000000000..26cbc9654 --- /dev/null +++ b/drivers/video/fbdev/core/Makefile @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_FB_CMDLINE) += fb_cmdline.o +obj-$(CONFIG_FB_NOTIFY) += fb_notify.o +obj-$(CONFIG_FB) += fb.o +fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ + modedb.o fbcvt.o +fb-$(CONFIG_FB_DEFERRED_IO) += fb_defio.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 + +obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o +obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o +obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.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_SYS_FOPS) += fb_sys_fops.o +obj-$(CONFIG_FB_SVGALIB) += svgalib.o +obj-$(CONFIG_FB_DDC) += fb_ddc.o diff --git a/drivers/video/fbdev/core/bitblit.c b/drivers/video/fbdev/core/bitblit.c new file mode 100644 index 000000000..f98e8f298 --- /dev/null +++ b/drivers/video/fbdev/core/bitblit.c @@ -0,0 +1,406 @@ +/* + * linux/drivers/video/console/bitblit.c -- BitBlitting Operation + * + * Originally from the 'accel_*' routines in drivers/video/console/fbcon.c + * + * Copyright (C) 2004 Antonino Daplas + * + * 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 +#include +#include +#include +#include +#include +#include +#include "fbcon.h" + +/* + * Accelerated handlers. + */ +static void update_attr(u8 *dst, 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 bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + struct fb_fillrect region; + + region.color = attr_bgcol_ec(bgshift, vc, info); + 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, ®ion); +} + +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; + u32 idx = vc->vc_font.width >> 3; + u8 *src; + + while (cnt--) { + src = vc->vc_font.data + (scr_readw(s++)& + charmask)*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; + 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 = vc->vc_font.data + (scr_readw(s++)& + charmask)*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 (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; + 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, ®ion); + } + + 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, ®ion); + } +} + +static void bit_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_cursor cursor; + struct fbcon_ops *ops = 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(ops->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1; + char *src; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height)); + + if (ops->cursor_state.image.data != src || + ops->cursor_reset) { + ops->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(ops->cursor_data); + ops->cursor_data = dst; + update_attr(dst, src, attribute, vc); + src = dst; + } + + if (ops->cursor_state.image.fg_color != fg || + ops->cursor_state.image.bg_color != bg || + ops->cursor_reset) { + ops->cursor_state.image.fg_color = fg; + ops->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + + if ((ops->cursor_state.image.dx != (vc->vc_font.width * vc->state.x)) || + (ops->cursor_state.image.dy != (vc->vc_font.height * y)) || + ops->cursor_reset) { + ops->cursor_state.image.dx = vc->vc_font.width * vc->state.x; + ops->cursor_state.image.dy = vc->vc_font.height * y; + cursor.set |= FB_CUR_SETPOS; + } + + if (ops->cursor_state.image.height != vc->vc_font.height || + ops->cursor_state.image.width != vc->vc_font.width || + ops->cursor_reset) { + ops->cursor_state.image.height = vc->vc_font.height; + ops->cursor_state.image.width = vc->vc_font.width; + cursor.set |= FB_CUR_SETSIZE; + } + + if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || + ops->cursor_reset) { + ops->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || + vc->vc_cursor_type != ops->p->cursor_shape || + ops->cursor_state.mask == NULL || + ops->cursor_reset) { + char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + int cur_height, size, i = 0; + u8 msk = 0xff; + + if (!mask) + return; + + kfree(ops->cursor_state.mask); + ops->cursor_state.mask = mask; + + ops->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + + switch (CUR_SIZE(ops->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; + case CUR_UNDERLINE: + cur_height = (vc->vc_font.height < 10) ? 1 : 2; + break; + case CUR_LOWER_THIRD: + cur_height = vc->vc_font.height/3; + break; + case CUR_LOWER_HALF: + cur_height = vc->vc_font.height >> 1; + break; + case CUR_TWO_THIRDS: + cur_height = (vc->vc_font.height << 1)/3; + break; + case CUR_BLOCK: + default: + cur_height = vc->vc_font.height; + break; + } + size = (vc->vc_font.height - cur_height) * w; + while (size--) + mask[i++] = ~msk; + size = cur_height * w; + while (size--) + mask[i++] = msk; + } + + switch (mode) { + case CM_ERASE: + ops->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: + ops->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; + cursor.image.fg_color = ops->cursor_state.image.fg_color; + cursor.image.bg_color = ops->cursor_state.image.bg_color; + cursor.image.dx = ops->cursor_state.image.dx; + cursor.image.dy = ops->cursor_state.image.dy; + cursor.image.height = ops->cursor_state.image.height; + cursor.image.width = ops->cursor_state.image.width; + cursor.hot.x = ops->cursor_state.hot.x; + cursor.hot.y = ops->cursor_state.hot.y; + cursor.mask = ops->cursor_state.mask; + cursor.enable = ops->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); + + ops->cursor_reset = 0; +} + +static int bit_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + int err; + + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_set_bitops(struct fbcon_ops *ops) +{ + ops->bmove = bit_bmove; + ops->clear = bit_clear; + ops->putcs = bit_putcs; + ops->clear_margins = bit_clear_margins; + ops->cursor = bit_cursor; + ops->update_start = bit_update_start; + ops->rotate_font = NULL; + + if (ops->rotate) + fbcon_set_rotate(ops); +} diff --git a/drivers/video/fbdev/core/cfbcopyarea.c b/drivers/video/fbdev/core/cfbcopyarea.c new file mode 100644 index 000000000..6d4bfeece --- /dev/null +++ b/drivers/video/fbdev/core/cfbcopyarea.c @@ -0,0 +1,437 @@ +/* + * Generic function for frame buffer with packed pixels of any depth. + * + * Copyright (C) 1999-2005 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. + * + * NOTES: + * + * This is for cfb packed pixels. Iplan and such are incorporated in the + * drivers that need them. + * + * FIXME + * + * Also need to add code to deal with cards endians that are different than + * the native cpu endians. I also need to deal with MSB position in the word. + * + * The two functions or copying forward and backward could be split up like + * the ones for filling, i.e. in aligned and unaligned versions. This would + * help moving some redundant computations and branches out of the loop, too. + */ + +#include +#include +#include +#include +#include +#include +#include "fb_draw.h" + +#if BITS_PER_LONG == 32 +# define FB_WRITEL fb_writel +# define FB_READL fb_readl +#else +# define FB_WRITEL fb_writeq +# define FB_READL fb_readq +#endif + + /* + * Generic bitwise copy algorithm + */ + +static void +bitcpy(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx, + const unsigned long __iomem *src, unsigned src_idx, int bits, + unsigned n, u32 bswapmask) +{ + unsigned long first, last; + int const shift = dst_idx-src_idx; + +#if 0 + /* + * If you suspect bug in this function, compare it with this simple + * memmove implementation. + */ + memmove((char *)dst + ((dst_idx & (bits - 1))) / 8, + (char *)src + ((src_idx & (bits - 1))) / 8, n / 8); + return; +#endif + + first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); + last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); + + if (!shift) { + // Same alignment for source and dest + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); + } else { + // Multiple destination words + + // Leading bits + if (first != ~0UL) { + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); + dst++; + src++; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 8) { + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + FB_WRITEL(FB_READL(src++), dst++); + n -= 8; + } + while (n--) + FB_WRITEL(FB_READL(src++), dst++); + + // Trailing bits + if (last) + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); + } + } else { + /* Different alignment for source and dest */ + unsigned long d0, d1; + int m; + + int const left = shift & (bits - 1); + int const right = -shift & (bits - 1); + + if (dst_idx+n <= bits) { + // Single destination word + if (last) + first &= last; + d0 = FB_READL(src); + d0 = fb_rev_pixels_in_long(d0, bswapmask); + if (shift > 0) { + // Single source word + d0 <<= left; + } else if (src_idx+n <= bits) { + // Single source word + d0 >>= right; + } else { + // 2 source words + d1 = FB_READL(src + 1); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 >> right | d1 << left; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), first), dst); + } else { + // Multiple destination words + /** We must always remember the last value read, because in case + SRC and DST overlap bitwise (e.g. when moving just one pixel in + 1bpp), we always collect one full long for DST and that might + overlap with the current long from SRC. We store this value in + 'd0'. */ + d0 = FB_READL(src++); + d0 = fb_rev_pixels_in_long(d0, bswapmask); + // Leading bits + if (shift > 0) { + // Single source word + d1 = d0; + d0 <<= left; + n -= bits - dst_idx; + } else { + // 2 source words + d1 = FB_READL(src++); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + + d0 = d0 >> right | d1 << left; + n -= bits - dst_idx; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), first), dst); + d0 = d1; + dst++; + + // Main chunk + m = n % bits; + n /= bits; + while ((n >= 4) && !bswapmask) { + d1 = FB_READL(src++); + FB_WRITEL(d0 >> right | d1 << left, dst++); + d0 = d1; + d1 = FB_READL(src++); + FB_WRITEL(d0 >> right | d1 << left, dst++); + d0 = d1; + d1 = FB_READL(src++); + FB_WRITEL(d0 >> right | d1 << left, dst++); + d0 = d1; + d1 = FB_READL(src++); + FB_WRITEL(d0 >> right | d1 << left, dst++); + d0 = d1; + n -= 4; + } + while (n--) { + d1 = FB_READL(src++); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 >> right | d1 << left; + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(d0, dst++); + d0 = d1; + } + + // Trailing bits + if (m) { + if (m <= bits - right) { + // Single source word + d0 >>= right; + } else { + // 2 source words + d1 = FB_READL(src); + d1 = fb_rev_pixels_in_long(d1, + bswapmask); + d0 = d0 >> right | d1 << left; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), last), dst); + } + } + } +} + + /* + * Generic bitwise copy algorithm, operating backward + */ + +static void +bitcpy_rev(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx, + const unsigned long __iomem *src, unsigned src_idx, int bits, + unsigned n, u32 bswapmask) +{ + unsigned long first, last; + int shift; + +#if 0 + /* + * If you suspect bug in this function, compare it with this simple + * memmove implementation. + */ + memmove((char *)dst + ((dst_idx & (bits - 1))) / 8, + (char *)src + ((src_idx & (bits - 1))) / 8, n / 8); + return; +#endif + + dst += (dst_idx + n - 1) / bits; + src += (src_idx + n - 1) / bits; + dst_idx = (dst_idx + n - 1) % bits; + src_idx = (src_idx + n - 1) % bits; + + shift = dst_idx-src_idx; + + first = ~fb_shifted_pixels_mask_long(p, (dst_idx + 1) % bits, bswapmask); + last = fb_shifted_pixels_mask_long(p, (bits + dst_idx + 1 - n) % bits, bswapmask); + + if (!shift) { + // Same alignment for source and dest + + if ((unsigned long)dst_idx+1 >= n) { + // Single word + if (first) + last &= first; + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); + } else { + // Multiple destination words + + // Leading bits + if (first) { + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); + dst--; + src--; + n -= dst_idx+1; + } + + // Main chunk + n /= bits; + while (n >= 8) { + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + FB_WRITEL(FB_READL(src--), dst--); + n -= 8; + } + while (n--) + FB_WRITEL(FB_READL(src--), dst--); + + // Trailing bits + if (last != -1UL) + FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); + } + } else { + // Different alignment for source and dest + unsigned long d0, d1; + int m; + + int const left = shift & (bits-1); + int const right = -shift & (bits-1); + + if ((unsigned long)dst_idx+1 >= n) { + // Single destination word + if (first) + last &= first; + d0 = FB_READL(src); + if (shift < 0) { + // Single source word + d0 >>= right; + } else if (1+(unsigned long)src_idx >= n) { + // Single source word + d0 <<= left; + } else { + // 2 source words + d1 = FB_READL(src - 1); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 << left | d1 >> right; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), last), dst); + } else { + // Multiple destination words + /** We must always remember the last value read, because in case + SRC and DST overlap bitwise (e.g. when moving just one pixel in + 1bpp), we always collect one full long for DST and that might + overlap with the current long from SRC. We store this value in + 'd0'. */ + + d0 = FB_READL(src--); + d0 = fb_rev_pixels_in_long(d0, bswapmask); + // Leading bits + if (shift < 0) { + // Single source word + d1 = d0; + d0 >>= right; + } else { + // 2 source words + d1 = FB_READL(src--); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 << left | d1 >> right; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + if (!first) + FB_WRITEL(d0, dst); + else + FB_WRITEL(comp(d0, FB_READL(dst), first), dst); + d0 = d1; + dst--; + n -= dst_idx+1; + + // Main chunk + m = n % bits; + n /= bits; + while ((n >= 4) && !bswapmask) { + d1 = FB_READL(src--); + FB_WRITEL(d0 << left | d1 >> right, dst--); + d0 = d1; + d1 = FB_READL(src--); + FB_WRITEL(d0 << left | d1 >> right, dst--); + d0 = d1; + d1 = FB_READL(src--); + FB_WRITEL(d0 << left | d1 >> right, dst--); + d0 = d1; + d1 = FB_READL(src--); + FB_WRITEL(d0 << left | d1 >> right, dst--); + d0 = d1; + n -= 4; + } + while (n--) { + d1 = FB_READL(src--); + d1 = fb_rev_pixels_in_long(d1, bswapmask); + d0 = d0 << left | d1 >> right; + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(d0, dst--); + d0 = d1; + } + + // Trailing bits + if (m) { + if (m <= bits - left) { + // Single source word + d0 <<= left; + } else { + // 2 source words + d1 = FB_READL(src); + d1 = fb_rev_pixels_in_long(d1, + bswapmask); + d0 = d0 << left | d1 >> right; + } + d0 = fb_rev_pixels_in_long(d0, bswapmask); + FB_WRITEL(comp(d0, FB_READL(dst), last), dst); + } + } + } +} + +void cfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ + u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy; + u32 height = area->height, width = area->width; + unsigned long const bits_per_line = p->fix.line_length*8u; + unsigned long __iomem *base = NULL; + int bits = BITS_PER_LONG, bytes = bits >> 3; + unsigned dst_idx = 0, src_idx = 0, rev_copy = 0; + u32 bswapmask = fb_compute_bswapmask(p); + + if (p->state != FBINFO_STATE_RUNNING) + return; + + /* if the beginning of the target area might overlap with the end of + the source area, be have to copy the area reverse. */ + if ((dy == sy && dx > sx) || (dy > sy)) { + dy += height; + sy += height; + rev_copy = 1; + } + + // split the base of the framebuffer into a long-aligned address and the + // index of the first bit + base = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1)); + dst_idx = src_idx = 8*((unsigned long)p->screen_base & (bytes-1)); + // add offset of source and target area + dst_idx += dy*bits_per_line + dx*p->var.bits_per_pixel; + src_idx += sy*bits_per_line + sx*p->var.bits_per_pixel; + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + if (rev_copy) { + while (height--) { + dst_idx -= bits_per_line; + src_idx -= bits_per_line; + bitcpy_rev(p, base + (dst_idx / bits), dst_idx % bits, + base + (src_idx / bits), src_idx % bits, bits, + width*p->var.bits_per_pixel, bswapmask); + } + } else { + while (height--) { + bitcpy(p, base + (dst_idx / bits), dst_idx % bits, + base + (src_idx / bits), src_idx % bits, bits, + width*p->var.bits_per_pixel, bswapmask); + dst_idx += bits_per_line; + src_idx += bits_per_line; + } + } +} + +EXPORT_SYMBOL(cfb_copyarea); + +MODULE_AUTHOR("James Simmons "); +MODULE_DESCRIPTION("Generic software accelerated copyarea"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/core/cfbfillrect.c b/drivers/video/fbdev/core/cfbfillrect.c new file mode 100644 index 000000000..ba9f58b2a --- /dev/null +++ b/drivers/video/fbdev/core/cfbfillrect.c @@ -0,0 +1,371 @@ +/* + * Generic fillrect for frame buffers with packed pixels of any depth. + * + * Copyright (C) 2000 James Simmons (jsimmons@linux-fbdev.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. + * + * NOTES: + * + * Also need to add code to deal with cards endians that are different than + * the native cpu endians. I also need to deal with MSB position in the word. + * + */ +#include +#include +#include +#include +#include "fb_draw.h" + +#if BITS_PER_LONG == 32 +# define FB_WRITEL fb_writel +# define FB_READL fb_readl +#else +# define FB_WRITEL fb_writeq +# define FB_READL fb_readq +#endif + + /* + * Aligned pattern fill using 32/64-bit memory accesses + */ + +static void +bitfill_aligned(struct fb_info *p, unsigned long __iomem *dst, int dst_idx, + unsigned long pat, unsigned n, int bits, u32 bswapmask) +{ + unsigned long first, last; + + if (!n) + return; + + first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); + last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + FB_WRITEL(comp(pat, FB_READL(dst), first), dst); + } else { + // Multiple destination words + + // Leading bits + if (first!= ~0UL) { + FB_WRITEL(comp(pat, FB_READL(dst), first), dst); + dst++; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 8) { + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + FB_WRITEL(pat, dst++); + n -= 8; + } + while (n--) + FB_WRITEL(pat, dst++); + + // Trailing bits + if (last) + FB_WRITEL(comp(pat, FB_READL(dst), last), dst); + } +} + + + /* + * Unaligned generic pattern fill using 32/64-bit memory accesses + * The pattern must have been expanded to a full 32/64-bit value + * Left/right are the appropriate shifts to convert to the pattern to be + * used for the next 32/64-bit word + */ + +static void +bitfill_unaligned(struct fb_info *p, unsigned long __iomem *dst, int dst_idx, + unsigned long pat, int left, int right, unsigned n, int bits) +{ + unsigned long first, last; + + if (!n) + return; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + FB_WRITEL(comp(pat, FB_READL(dst), first), dst); + } else { + // Multiple destination words + // Leading bits + if (first) { + FB_WRITEL(comp(pat, FB_READL(dst), first), dst); + dst++; + pat = pat << left | pat >> right; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 4) { + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + n -= 4; + } + while (n--) { + FB_WRITEL(pat, dst++); + pat = pat << left | pat >> right; + } + + // Trailing bits + if (last) + FB_WRITEL(comp(pat, FB_READL(dst), last), dst); + } +} + + /* + * Aligned pattern invert using 32/64-bit memory accesses + */ +static void +bitfill_aligned_rev(struct fb_info *p, unsigned long __iomem *dst, + int dst_idx, unsigned long pat, unsigned n, int bits, + u32 bswapmask) +{ + unsigned long val = pat, dat; + unsigned long first, last; + + if (!n) + return; + + first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); + last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ val, dat, first), dst); + } else { + // Multiple destination words + // Leading bits + if (first!=0UL) { + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ val, dat, first), dst); + dst++; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 8) { + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + n -= 8; + } + while (n--) { + FB_WRITEL(FB_READL(dst) ^ val, dst); + dst++; + } + // Trailing bits + if (last) { + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ val, dat, last), dst); + } + } +} + + + /* + * Unaligned generic pattern invert using 32/64-bit memory accesses + * The pattern must have been expanded to a full 32/64-bit value + * Left/right are the appropriate shifts to convert to the pattern to be + * used for the next 32/64-bit word + */ + +static void +bitfill_unaligned_rev(struct fb_info *p, unsigned long __iomem *dst, + int dst_idx, unsigned long pat, int left, int right, + unsigned n, int bits) +{ + unsigned long first, last, dat; + + if (!n) + return; + + first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); + last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); + + if (dst_idx+n <= bits) { + // Single word + if (last) + first &= last; + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ pat, dat, first), dst); + } else { + // Multiple destination words + + // Leading bits + if (first != 0UL) { + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ pat, dat, first), dst); + dst++; + pat = pat << left | pat >> right; + n -= bits - dst_idx; + } + + // Main chunk + n /= bits; + while (n >= 4) { + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + n -= 4; + } + while (n--) { + FB_WRITEL(FB_READL(dst) ^ pat, dst); + dst++; + pat = pat << left | pat >> right; + } + + // Trailing bits + if (last) { + dat = FB_READL(dst); + FB_WRITEL(comp(dat ^ pat, dat, last), dst); + } + } +} + +void cfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +{ + unsigned long pat, pat2, fg; + unsigned long width = rect->width, height = rect->height; + int bits = BITS_PER_LONG, bytes = bits >> 3; + u32 bpp = p->var.bits_per_pixel; + unsigned long __iomem *dst; + int dst_idx, left; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR ) + fg = ((u32 *) (p->pseudo_palette))[rect->color]; + else + fg = rect->color; + + pat = pixel_to_pat(bpp, fg); + + dst = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1)); + dst_idx = ((unsigned long)p->screen_base & (bytes - 1))*8; + dst_idx += rect->dy*p->fix.line_length*8+rect->dx*bpp; + /* FIXME For now we support 1-32 bpp only */ + left = bits % bpp; + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + if (!left) { + u32 bswapmask = fb_compute_bswapmask(p); + void (*fill_op32)(struct fb_info *p, + unsigned long __iomem *dst, int dst_idx, + unsigned long pat, unsigned n, int bits, + u32 bswapmask) = NULL; + + switch (rect->rop) { + case ROP_XOR: + fill_op32 = bitfill_aligned_rev; + break; + case ROP_COPY: + fill_op32 = bitfill_aligned; + break; + default: + printk( KERN_ERR "cfb_fillrect(): unknown rop, defaulting to ROP_COPY\n"); + fill_op32 = bitfill_aligned; + break; + } + while (height--) { + dst += dst_idx >> (ffs(bits) - 1); + dst_idx &= (bits - 1); + fill_op32(p, dst, dst_idx, pat, width*bpp, bits, + bswapmask); + dst_idx += p->fix.line_length*8; + } + } else { + int right, r; + void (*fill_op)(struct fb_info *p, unsigned long __iomem *dst, + int dst_idx, unsigned long pat, int left, + int right, unsigned n, int bits) = NULL; +#ifdef __LITTLE_ENDIAN + right = left; + left = bpp - right; +#else + right = bpp - left; +#endif + switch (rect->rop) { + case ROP_XOR: + fill_op = bitfill_unaligned_rev; + break; + case ROP_COPY: + fill_op = bitfill_unaligned; + break; + default: + printk(KERN_ERR "cfb_fillrect(): unknown rop, defaulting to ROP_COPY\n"); + fill_op = bitfill_unaligned; + break; + } + while (height--) { + dst += dst_idx / bits; + dst_idx &= (bits - 1); + r = dst_idx % bpp; + /* rotate pattern to the correct start position */ + pat2 = le_long_to_cpu(rolx(cpu_to_le_long(pat), r, bpp)); + fill_op(p, dst, dst_idx, pat2, left, right, + width*bpp, bits); + dst_idx += p->fix.line_length*8; + } + } +} + +EXPORT_SYMBOL(cfb_fillrect); + +MODULE_AUTHOR("James Simmons "); +MODULE_DESCRIPTION("Generic software accelerated fill rectangle"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/core/cfbimgblt.c b/drivers/video/fbdev/core/cfbimgblt.c new file mode 100644 index 000000000..9ebda4e0d --- /dev/null +++ b/drivers/video/fbdev/core/cfbimgblt.c @@ -0,0 +1,366 @@ +/* + * Generic BitBLT function for frame buffer with packed pixels of any depth. + * + * Copyright (C) June 1999 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. + * + * NOTES: + * + * This function copys a image from system memory to video memory. The + * image can be a bitmap where each 0 represents the background color and + * each 1 represents the foreground color. Great for font handling. It can + * also be a color image. This is determined by image_depth. The color image + * must be laid out exactly in the same format as the framebuffer. Yes I know + * their are cards with hardware that coverts images of various depths to the + * framebuffer depth. But not every card has this. All images must be rounded + * up to the nearest byte. For example a bitmap 12 bits wide must be two + * bytes width. + * + * Tony: + * Incorporate mask tables similar to fbcon-cfb*.c in 2.4 API. This speeds + * up the code significantly. + * + * Code for depths not multiples of BITS_PER_LONG is still kludgy, which is + * still processed a bit at a time. + * + * Also need to add code to deal with cards endians that are different than + * the native cpu endians. I also need to deal with MSB position in the word. + */ +#include +#include +#include +#include +#include "fb_draw.h" + +#define DEBUG + +#ifdef DEBUG +#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt,__func__,## args) +#else +#define DPRINTK(fmt, args...) +#endif + +static const u32 cfb_tab8_be[] = { + 0x00000000,0x000000ff,0x0000ff00,0x0000ffff, + 0x00ff0000,0x00ff00ff,0x00ffff00,0x00ffffff, + 0xff000000,0xff0000ff,0xff00ff00,0xff00ffff, + 0xffff0000,0xffff00ff,0xffffff00,0xffffffff +}; + +static const u32 cfb_tab8_le[] = { + 0x00000000,0xff000000,0x00ff0000,0xffff0000, + 0x0000ff00,0xff00ff00,0x00ffff00,0xffffff00, + 0x000000ff,0xff0000ff,0x00ff00ff,0xffff00ff, + 0x0000ffff,0xff00ffff,0x00ffffff,0xffffffff +}; + +static const u32 cfb_tab16_be[] = { + 0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff +}; + +static const u32 cfb_tab16_le[] = { + 0x00000000, 0xffff0000, 0x0000ffff, 0xffffffff +}; + +static const u32 cfb_tab32[] = { + 0x00000000, 0xffffffff +}; + +#define FB_WRITEL fb_writel +#define FB_READL fb_readl + +static inline void color_imageblit(const struct fb_image *image, + struct fb_info *p, u8 __iomem *dst1, + u32 start_index, + u32 pitch_index) +{ + /* Draw the penguin */ + u32 __iomem *dst, *dst2; + u32 color = 0, val, shift; + int i, n, bpp = p->var.bits_per_pixel; + u32 null_bits = 32 - bpp; + u32 *palette = (u32 *) p->pseudo_palette; + const u8 *src = image->data; + u32 bswapmask = fb_compute_bswapmask(p); + + dst2 = (u32 __iomem *) dst1; + for (i = image->height; i--; ) { + n = image->width; + dst = (u32 __iomem *) dst1; + shift = 0; + val = 0; + + if (start_index) { + u32 start_mask = ~fb_shifted_pixels_mask_u32(p, + start_index, bswapmask); + val = FB_READL(dst) & start_mask; + shift = start_index; + } + while (n--) { + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR ) + color = palette[*src]; + else + color = *src; + color <<= FB_LEFT_POS(p, bpp); + val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask); + if (shift >= null_bits) { + FB_WRITEL(val, dst++); + + val = (shift == null_bits) ? 0 : + FB_SHIFT_LOW(p, color, 32 - shift); + } + shift += bpp; + shift &= (32 - 1); + src++; + } + if (shift) { + u32 end_mask = fb_shifted_pixels_mask_u32(p, shift, + bswapmask); + + FB_WRITEL((FB_READL(dst) & end_mask) | val, dst); + } + dst1 += p->fix.line_length; + if (pitch_index) { + dst2 += p->fix.line_length; + dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1)); + + start_index += pitch_index; + start_index &= 32 - 1; + } + } +} + +static inline void slow_imageblit(const struct fb_image *image, struct fb_info *p, + u8 __iomem *dst1, u32 fgcolor, + u32 bgcolor, + u32 start_index, + u32 pitch_index) +{ + u32 shift, color = 0, bpp = p->var.bits_per_pixel; + u32 __iomem *dst, *dst2; + u32 val, pitch = p->fix.line_length; + u32 null_bits = 32 - bpp; + u32 spitch = (image->width+7)/8; + const u8 *src = image->data, *s; + u32 i, j, l; + u32 bswapmask = fb_compute_bswapmask(p); + + dst2 = (u32 __iomem *) dst1; + fgcolor <<= FB_LEFT_POS(p, bpp); + bgcolor <<= FB_LEFT_POS(p, bpp); + + for (i = image->height; i--; ) { + shift = val = 0; + l = 8; + j = image->width; + dst = (u32 __iomem *) dst1; + s = src; + + /* write leading bits */ + if (start_index) { + u32 start_mask = ~fb_shifted_pixels_mask_u32(p, + start_index, bswapmask); + val = FB_READL(dst) & start_mask; + shift = start_index; + } + + while (j--) { + l--; + color = (*s & (1 << l)) ? fgcolor : bgcolor; + val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask); + + /* Did the bitshift spill bits to the next long? */ + if (shift >= null_bits) { + FB_WRITEL(val, dst++); + val = (shift == null_bits) ? 0 : + FB_SHIFT_LOW(p, color, 32 - shift); + } + shift += bpp; + shift &= (32 - 1); + if (!l) { l = 8; s++; } + } + + /* write trailing bits */ + if (shift) { + u32 end_mask = fb_shifted_pixels_mask_u32(p, shift, + bswapmask); + + FB_WRITEL((FB_READL(dst) & end_mask) | val, dst); + } + + dst1 += pitch; + src += spitch; + if (pitch_index) { + dst2 += pitch; + dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1)); + start_index += pitch_index; + start_index &= 32 - 1; + } + + } +} + +/* + * fast_imageblit - optimized monochrome color expansion + * + * Only if: bits_per_pixel == 8, 16, or 32 + * image->width is divisible by pixel/dword (ppw); + * fix->line_legth is divisible by 4; + * beginning and end of a scanline is dword aligned + */ +static inline void fast_imageblit(const struct fb_image *image, struct fb_info *p, + u8 __iomem *dst1, u32 fgcolor, + u32 bgcolor) +{ + u32 fgx = fgcolor, bgx = bgcolor, bpp = p->var.bits_per_pixel; + u32 ppw = 32/bpp, spitch = (image->width + 7)/8; + u32 bit_mask, eorx, shift; + const char *s = image->data, *src; + u32 __iomem *dst; + const u32 *tab = NULL; + size_t tablen; + u32 colortab[16]; + int i, j, k; + + switch (bpp) { + case 8: + tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le; + tablen = 16; + break; + case 16: + tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le; + tablen = 4; + break; + case 32: + tab = cfb_tab32; + tablen = 2; + break; + default: + return; + } + + for (i = ppw-1; i--; ) { + fgx <<= bpp; + bgx <<= bpp; + fgx |= fgcolor; + bgx |= bgcolor; + } + + bit_mask = (1 << ppw) - 1; + eorx = fgx ^ bgx; + k = image->width/ppw; + + for (i = 0; i < tablen; ++i) + colortab[i] = (tab[i] & eorx) ^ bgx; + + for (i = image->height; i--; ) { + dst = (u32 __iomem *)dst1; + shift = 8; + src = s; + + /* + * Manually unroll the per-line copying loop for better + * performance. This works until we processed the last + * completely filled source byte (inclusive). + */ + switch (ppw) { + case 4: /* 8 bpp */ + for (j = k; j >= 2; j -= 2, ++src) { + FB_WRITEL(colortab[(*src >> 4) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 0) & bit_mask], dst++); + } + break; + case 2: /* 16 bpp */ + for (j = k; j >= 4; j -= 4, ++src) { + FB_WRITEL(colortab[(*src >> 6) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 4) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 2) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 0) & bit_mask], dst++); + } + break; + case 1: /* 32 bpp */ + for (j = k; j >= 8; j -= 8, ++src) { + FB_WRITEL(colortab[(*src >> 7) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 6) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 5) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 4) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 3) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 2) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 1) & bit_mask], dst++); + FB_WRITEL(colortab[(*src >> 0) & bit_mask], dst++); + } + break; + } + + /* + * For image widths that are not a multiple of 8, there + * are trailing pixels left on the current line. Print + * them as well. + */ + for (; j--; ) { + shift -= ppw; + FB_WRITEL(colortab[(*src >> shift) & bit_mask], dst++); + if (!shift) { + shift = 8; + ++src; + } + } + + dst1 += p->fix.line_length; + s += spitch; + } +} + +void cfb_imageblit(struct fb_info *p, const struct fb_image *image) +{ + u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0; + u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel; + u32 width = image->width; + u32 dx = image->dx, dy = image->dy; + u8 __iomem *dst1; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + bitstart = (dy * p->fix.line_length * 8) + (dx * bpp); + start_index = bitstart & (32 - 1); + pitch_index = (p->fix.line_length & (bpl - 1)) * 8; + + bitstart /= 8; + bitstart &= ~(bpl - 1); + dst1 = p->screen_base + bitstart; + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + if (image->depth == 1) { + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR) { + fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color]; + bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color]; + } else { + fgcolor = image->fg_color; + bgcolor = image->bg_color; + } + + if (32 % bpp == 0 && !start_index && !pitch_index && + ((width & (32/bpp-1)) == 0) && + bpp >= 8 && bpp <= 32) + fast_imageblit(image, p, dst1, fgcolor, bgcolor); + else + slow_imageblit(image, p, dst1, fgcolor, bgcolor, + start_index, pitch_index); + } else + color_imageblit(image, p, dst1, start_index, pitch_index); +} + +EXPORT_SYMBOL(cfb_imageblit); + +MODULE_AUTHOR("James Simmons "); +MODULE_DESCRIPTION("Generic software accelerated imaging drawing"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/fbdev/core/fb_cmdline.c b/drivers/video/fbdev/core/fb_cmdline.c new file mode 100644 index 000000000..3b5bd666b --- /dev/null +++ b/drivers/video/fbdev/core/fb_cmdline.c @@ -0,0 +1,107 @@ +/* + * linux/drivers/video/fb_cmdline.c + * + * Copyright (C) 2014 Intel Corp + * Copyright (C) 1994 Martin Schaller + * + * 2001 - Documented with DocBook + * - Brad Douglas + * + * 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: + * Vetter + */ +#include +#include + +static char *video_options[FB_MAX] __read_mostly; +static int ofonly __read_mostly; + +const char *fb_mode_option; +EXPORT_SYMBOL_GPL(fb_mode_option); + +/** + * fb_get_options - get kernel boot parameters + * @name: framebuffer name as it would appear in + * the boot parameter line + * (video=:) + * @option: the option will be stored here + * + * NOTE: Needed to maintain backwards compatibility + */ +int fb_get_options(const char *name, char **option) +{ + char *opt, *options = NULL; + int retval = 0; + int name_len = strlen(name), i; + + if (name_len && ofonly && strncmp(name, "offb", 4)) + retval = 1; + + if (name_len && !retval) { + for (i = 0; i < FB_MAX; i++) { + if (video_options[i] == NULL) + continue; + if (!video_options[i][0]) + continue; + opt = video_options[i]; + if (!strncmp(name, opt, name_len) && + opt[name_len] == ':') + options = opt + name_len + 1; + } + } + /* No match, pass global option */ + if (!options && option && fb_mode_option) + options = kstrdup(fb_mode_option, GFP_KERNEL); + if (options && !strncmp(options, "off", 3)) + retval = 1; + + if (option) + *option = options; + + return retval; +} +EXPORT_SYMBOL(fb_get_options); + +/** + * video_setup - process command line options + * @options: string of options + * + * Process command line options for frame buffer subsystem. + * + * NOTE: This function is a __setup and __init function. + * It only stores the options. Drivers have to call + * fb_get_options() as necessary. + */ +static int __init video_setup(char *options) +{ + if (!options || !*options) + goto out; + + if (!strncmp(options, "ofonly", 6)) { + ofonly = 1; + goto out; + } + + if (strchr(options, ':')) { + /* named */ + int i; + + for (i = 0; i < FB_MAX; i++) { + if (video_options[i] == NULL) { + video_options[i] = options; + break; + } + } + } else { + /* global */ + fb_mode_option = options; + } + +out: + return 1; +} +__setup("video=", video_setup); diff --git a/drivers/video/fbdev/core/fb_ddc.c b/drivers/video/fbdev/core/fb_ddc.c new file mode 100644 index 000000000..8bf5f2f54 --- /dev/null +++ b/drivers/video/fbdev/core/fb_ddc.c @@ -0,0 +1,127 @@ +/* + * drivers/video/fb_ddc.c - DDC/EDID read support. + * + * Copyright (C) 2006 Dennis Munsie + * + * 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 +#include +#include +#include +#include +#include + +#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); + + adapter->class |= I2C_CLASS_DDC; + return edid; +} + +EXPORT_SYMBOL_GPL(fb_ddc_read); + +MODULE_AUTHOR("Dennis Munsie "); +MODULE_DESCRIPTION("DDC/EDID reading support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c new file mode 100644 index 000000000..583cbcf09 --- /dev/null +++ b/drivers/video/fbdev/core/fb_defio.c @@ -0,0 +1,342 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* to support deferred IO */ +#include +#include + +static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs) +{ + void *screen_base = (void __force *) info->screen_base; + struct page *page; + + if (is_vmalloc_addr(screen_base + offs)) + page = vmalloc_to_page(screen_base + offs); + else + page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT); + + return page; +} + +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 list_head *pos = &fbdefio->pagereflist; + unsigned long pgoff = offset >> PAGE_SHIFT; + struct fb_deferred_io_pageref *pageref, *cur; + + if (WARN_ON_ONCE(pgoff >= info->npagerefs)) + return NULL; /* incorrect allocation size */ + + /* 1:1 mapping between pageref and page offset */ + pageref = &info->pagerefs[pgoff]; + + /* + * 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; + + pageref->page = page; + pageref->offset = pgoff << PAGE_SHIFT; + + 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->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) +{ + unsigned long offset; + struct page *page; + struct fb_info *info = vmf->vma->vm_private_data; + + offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= info->fix.smem_len) + return VM_FAULT_SIGBUS; + + page = fb_deferred_io_page(info, offset); + if (!page) + return VM_FAULT_SIGBUS; + + get_page(page); + + if (vmf->vma->vm_file) + page->mapping = vmf->vma->vm_file->f_mapping; + else + printk(KERN_ERR "no mapping available\n"); + + BUG_ON(!page->mapping); + page->index = vmf->pgoff; /* for page_mkclean() */ + + vmf->page = page; + return 0; +} + +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); + /* Kill off the delayed work */ + cancel_delayed_work_sync(&info->deferred_work); + + /* Run it immediately */ + schedule_delayed_work(&info->deferred_work, 0); + 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_info *info, unsigned long offset, + struct page *page) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct fb_deferred_io_pageref *pageref; + vm_fault_t ret; + + /* protect against the workqueue changing the page list */ + mutex_lock(&fbdefio->lock); + + /* first write in this cycle, notify the driver */ + if (fbdefio->first_io && list_empty(&fbdefio->pagereflist)) + fbdefio->first_io(info); + + 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 page_mkclean() 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->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->lock); + return ret; +} + +/* + * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O + * @fb_info: The fbdev info structure + * @vmf: The VM fault + * + * This is a callback we get when userspace first tries to + * write to the page. We schedule a workqueue. That workqueue + * will eventually mkclean the touched pages and execute the + * deferred framebuffer IO. Then if userspace touches a page + * again, we repeat the same scheme. + * + * Returns: + * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise. + */ +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf) +{ + unsigned long offset = vmf->address - vmf->vma->vm_start; + struct page *page = vmf->page; + + file_update_time(vmf->vma->vm_file); + + return fb_deferred_io_track_page(info, offset, page); +} + +/* vm_ops->page_mkwrite handler */ +static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) +{ + struct fb_info *info = vmf->vma->vm_private_data; + + return fb_deferred_io_page_mkwrite(info, vmf); +} + +static const struct vm_operations_struct fb_deferred_io_vm_ops = { + .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_ops = &fb_deferred_io_vm_ops; + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + if (!(info->flags & FBINFO_VIRTFB)) + vma->vm_flags |= VM_IO; + vma->vm_private_data = info; + 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; + + /* here we mkclean the pages, then do all deferred IO */ + mutex_lock(&fbdefio->lock); + list_for_each_entry(pageref, &fbdefio->pagereflist, list) { + struct page *cur = pageref->page; + lock_page(cur); + page_mkclean(cur); + unlock_page(cur); + } + + /* driver's callback with pagereflist */ + fbdefio->deferred_io(info, &fbdefio->pagereflist); + + /* clear the list */ + list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list) + fb_deferred_io_pageref_put(pageref, info); + + mutex_unlock(&fbdefio->lock); +} + +int fb_deferred_io_init(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct fb_deferred_io_pageref *pagerefs; + unsigned long npagerefs, i; + int ret; + + BUG_ON(!fbdefio); + + if (WARN_ON(!info->fix.smem_len)) + return -EINVAL; + + mutex_init(&fbdefio->lock); + INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); + INIT_LIST_HEAD(&fbdefio->pagereflist); + if (fbdefio->delay == 0) /* set a default of 1 s */ + fbdefio->delay = HZ; + + npagerefs = DIV_ROUND_UP(info->fix.smem_len, PAGE_SIZE); + + /* alloc a page ref for each page of the display memory */ + pagerefs = kvcalloc(npagerefs, sizeof(*pagerefs), GFP_KERNEL); + if (!pagerefs) { + ret = -ENOMEM; + goto err; + } + for (i = 0; i < npagerefs; ++i) + INIT_LIST_HEAD(&pagerefs[i].list); + info->npagerefs = npagerefs; + info->pagerefs = pagerefs; + + return 0; + +err: + mutex_destroy(&fbdefio->lock); + return ret; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_init); + +void fb_deferred_io_open(struct fb_info *info, + struct inode *inode, + struct file *file) +{ + file->f_mapping->a_ops = &fb_deferred_io_aops; +} +EXPORT_SYMBOL_GPL(fb_deferred_io_open); + +void fb_deferred_io_release(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct page *page; + int i; + + BUG_ON(!fbdefio); + cancel_delayed_work_sync(&info->deferred_work); + + /* clear out the mapping that we setup */ + for (i = 0 ; i < info->fix.smem_len; i += PAGE_SIZE) { + page = fb_deferred_io_page(info, i); + page->mapping = NULL; + } +} +EXPORT_SYMBOL_GPL(fb_deferred_io_release); + +void fb_deferred_io_cleanup(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + + fb_deferred_io_release(info); + + kvfree(info->pagerefs); + mutex_destroy(&fbdefio->lock); +} +EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup); diff --git a/drivers/video/fbdev/core/fb_draw.h b/drivers/video/fbdev/core/fb_draw.h new file mode 100644 index 000000000..e0d829873 --- /dev/null +++ b/drivers/video/fbdev/core/fb_draw.h @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _FB_DRAW_H +#define _FB_DRAW_H + +#include +#include +#include + + /* + * Compose two values, using a bitmask as decision value + * This is equivalent to (a & mask) | (b & ~mask) + */ + +static inline unsigned long +comp(unsigned long a, unsigned long b, unsigned long mask) +{ + return ((a ^ b) & mask) ^ b; +} + + /* + * Create a pattern with the given pixel's color + */ + +#if BITS_PER_LONG == 64 +static inline unsigned long +pixel_to_pat( u32 bpp, u32 pixel) +{ + switch (bpp) { + case 1: + return 0xfffffffffffffffful*pixel; + case 2: + return 0x5555555555555555ul*pixel; + case 4: + return 0x1111111111111111ul*pixel; + case 8: + return 0x0101010101010101ul*pixel; + case 12: + return 0x1001001001001001ul*pixel; + case 16: + return 0x0001000100010001ul*pixel; + case 24: + return 0x0001000001000001ul*pixel; + case 32: + return 0x0000000100000001ul*pixel; + default: + WARN(1, "pixel_to_pat(): unsupported pixelformat %d\n", bpp); + return 0; + } +} +#else +static inline unsigned long +pixel_to_pat( u32 bpp, u32 pixel) +{ + switch (bpp) { + case 1: + return 0xfffffffful*pixel; + case 2: + return 0x55555555ul*pixel; + case 4: + return 0x11111111ul*pixel; + case 8: + return 0x01010101ul*pixel; + case 12: + return 0x01001001ul*pixel; + case 16: + return 0x00010001ul*pixel; + case 24: + return 0x01000001ul*pixel; + case 32: + return 0x00000001ul*pixel; + default: + WARN(1, "pixel_to_pat(): unsupported pixelformat %d\n", bpp); + return 0; + } +} +#endif + +#ifdef CONFIG_FB_CFB_REV_PIXELS_IN_BYTE +#if BITS_PER_LONG == 64 +#define REV_PIXELS_MASK1 0x5555555555555555ul +#define REV_PIXELS_MASK2 0x3333333333333333ul +#define REV_PIXELS_MASK4 0x0f0f0f0f0f0f0f0ful +#else +#define REV_PIXELS_MASK1 0x55555555ul +#define REV_PIXELS_MASK2 0x33333333ul +#define REV_PIXELS_MASK4 0x0f0f0f0ful +#endif + +static inline unsigned long fb_rev_pixels_in_long(unsigned long val, + u32 bswapmask) +{ + if (bswapmask & 1) + val = comp(val >> 1, val << 1, REV_PIXELS_MASK1); + if (bswapmask & 2) + val = comp(val >> 2, val << 2, REV_PIXELS_MASK2); + if (bswapmask & 3) + val = comp(val >> 4, val << 4, REV_PIXELS_MASK4); + return val; +} + +static inline u32 fb_shifted_pixels_mask_u32(struct fb_info *p, u32 index, + u32 bswapmask) +{ + u32 mask; + + if (!bswapmask) { + mask = FB_SHIFT_HIGH(p, ~(u32)0, index); + } else { + mask = 0xff << FB_LEFT_POS(p, 8); + mask = FB_SHIFT_LOW(p, mask, index & (bswapmask)) & mask; + mask = FB_SHIFT_HIGH(p, mask, index & ~(bswapmask)); +#if defined(__i386__) || defined(__x86_64__) + /* Shift argument is limited to 0 - 31 on x86 based CPU's */ + if(index + bswapmask < 32) +#endif + mask |= FB_SHIFT_HIGH(p, ~(u32)0, + (index + bswapmask) & ~(bswapmask)); + } + return mask; +} + +static inline unsigned long fb_shifted_pixels_mask_long(struct fb_info *p, + u32 index, + u32 bswapmask) +{ + unsigned long mask; + + if (!bswapmask) { + mask = FB_SHIFT_HIGH(p, ~0UL, index); + } else { + mask = 0xff << FB_LEFT_POS(p, 8); + mask = FB_SHIFT_LOW(p, mask, index & (bswapmask)) & mask; + mask = FB_SHIFT_HIGH(p, mask, index & ~(bswapmask)); +#if defined(__i386__) || defined(__x86_64__) + /* Shift argument is limited to 0 - 31 on x86 based CPU's */ + if(index + bswapmask < BITS_PER_LONG) +#endif + mask |= FB_SHIFT_HIGH(p, ~0UL, + (index + bswapmask) & ~(bswapmask)); + } + return mask; +} + + +static inline u32 fb_compute_bswapmask(struct fb_info *info) +{ + u32 bswapmask = 0; + unsigned bpp = info->var.bits_per_pixel; + + if ((bpp < 8) && (info->var.nonstd & FB_NONSTD_REV_PIX_IN_B)) { + /* + * Reversed order of pixel layout in bytes + * works only for 1, 2 and 4 bpp + */ + bswapmask = 7 - bpp + 1; + } + return bswapmask; +} + +#else /* CONFIG_FB_CFB_REV_PIXELS_IN_BYTE */ + +static inline unsigned long fb_rev_pixels_in_long(unsigned long val, + u32 bswapmask) +{ + return val; +} + +#define fb_shifted_pixels_mask_u32(p, i, b) FB_SHIFT_HIGH((p), ~(u32)0, (i)) +#define fb_shifted_pixels_mask_long(p, i, b) FB_SHIFT_HIGH((p), ~0UL, (i)) +#define fb_compute_bswapmask(...) 0 + +#endif /* CONFIG_FB_CFB_REV_PIXELS_IN_BYTE */ + +#define cpu_to_le_long _cpu_to_le_long(BITS_PER_LONG) +#define _cpu_to_le_long(x) __cpu_to_le_long(x) +#define __cpu_to_le_long(x) cpu_to_le##x + +#define le_long_to_cpu _le_long_to_cpu(BITS_PER_LONG) +#define _le_long_to_cpu(x) __le_long_to_cpu(x) +#define __le_long_to_cpu(x) le##x##_to_cpu + +static inline unsigned long rolx(unsigned long word, unsigned int shift, unsigned int x) +{ + return (word << shift) | (word >> (x - shift)); +} + +#endif /* FB_DRAW_H */ diff --git a/drivers/video/fbdev/core/fb_notify.c b/drivers/video/fbdev/core/fb_notify.c new file mode 100644 index 000000000..10e3b9a74 --- /dev/null +++ b/drivers/video/fbdev/core/fb_notify.c @@ -0,0 +1,54 @@ +/* + * linux/drivers/video/fb_notify.c + * + * Copyright (C) 2006 Antonino Daplas + * + * 2001 - Documented with DocBook + * - Brad Douglas + * + * 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 +#include +#include + +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); diff --git a/drivers/video/fbdev/core/fb_sys_fops.c b/drivers/video/fbdev/core/fb_sys_fops.c new file mode 100644 index 000000000..ff275d7f3 --- /dev/null +++ b/drivers/video/fbdev/core/fb_sys_fops.c @@ -0,0 +1,104 @@ +/* + * linux/drivers/video/fb_sys_read.c - Generic file operations where + * framebuffer is in system RAM + * + * Copyright (C) 2007 Antonino Daplas + * + * 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 +#include +#include + +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; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + 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 = (void __force *)(info->screen_base + p); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + if (copy_to_user(buf, src, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + return (err) ? err : count; +} +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; + + if (info->state != FBINFO_STATE_RUNNING) + return -EPERM; + + 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 = (void __force *) (info->screen_base + p); + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + if (copy_from_user(dst, buf, count)) + err = -EFAULT; + + if (!err) + *ppos += count; + + return (err) ? err : count; +} +EXPORT_SYMBOL_GPL(fb_sys_write); + +MODULE_AUTHOR("Antonino Daplas "); +MODULE_DESCRIPTION("Generic file read (fb in system RAM)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/fbdev/core/fbcmap.c b/drivers/video/fbdev/core/fbcmap.c new file mode 100644 index 000000000..ff09e57f3 --- /dev/null +++ b/drivers/video/fbdev/core/fbcmap.c @@ -0,0 +1,362 @@ +/* + * linux/drivers/video/fbcmap.c -- Colormap handling for frame buffer devices + * + * Created 15 Jun 1997 by Geert Uytterhoeven + * + * 2001 - Documented with DocBook + * - Brad Douglas + * + * 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 +#include +#include +#include +#include + +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); diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c new file mode 100644 index 000000000..1b14c21af --- /dev/null +++ b/drivers/video/fbdev/core/fbcon.c @@ -0,0 +1,3443 @@ +/* + * linux/drivers/video/fbcon.c -- Low level frame buffer based console driver + * + * Copyright (C) 1995 Geert Uytterhoeven + * + * + * This file is based on the original Amiga console driver (amicon.c): + * + * Copyright (C) 1993 Hamish Macdonald + * Greg Harp + * Copyright (C) 1994 David Carter [carter@compsci.bristol.ac.uk] + * + * with work by William Rucklidge (wjr@cs.cornell.edu) + * Geert Uytterhoeven + * Jes Sorensen (jds@kom.auc.dk) + * Martin Apel + * + * and on the original Atari console driver (atacon.c): + * + * Copyright (C) 1993 Bjoern Brauel + * Roman Hodek + * + * with work by Guenther Kelleter + * Martin Schaller + * Andreas Schwab + * + * Hardware cursor support added by Emmanuel Marty (core@ggi-project.org) + * Smart redraw scrolling, arbitrary font width support, 512char font support + * and software scrollback added by + * Jakub Jelinek (jj@ultra.linux.cz) + * + * Random hacking by Martin Mares + * + * 2001 - Documented with DocBook + * - Brad Douglas + * + * The low level operations for the various display memory organizations are + * now in separate source files. + * + * Currently the following organizations are supported: + * + * o afb Amiga bitplanes + * o cfb{2,4,8,16,24,32} Packed pixels + * o ilbm Amiga interleaved bitplanes + * o iplan2p[248] Atari interleaved bitplanes + * o mfb Monochrome + * o vga VGA characters/attributes + * + * To do: + * + * - Implement 16 plane mode (iplan2p16) + * + * + * 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 +#include +#include +#include +#include /* MSch: for IRQ probe */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For counting font checksums */ +#include +#include +#include + +#include "fbcon.h" + +/* + * FIXME: Locking + * + * - fbcon state itself is protected by the console_lock, and the code does a + * pretty good job at making sure that lock is held everywhere it's needed. + * + * - fbcon doesn't bother with fb_lock/unlock at all. This is buggy, since it + * means concurrent access to the same fbdev from both fbcon and userspace + * will blow up. To fix this all fbcon calls from fbmem.c need to be moved out + * of fb_lock/unlock protected sections, since otherwise we'll recurse and + * deadlock eventually. Aside: Due to these deadlock issues the fbdev code in + * fbmem.c cannot use locking asserts, and there's lots of callers which get + * the rules wrong, e.g. fbsysfs.c entirely missed fb_lock/unlock calls too. + */ + +enum { + FBCON_LOGO_CANSHOW = -1, /* the logo can be shown */ + FBCON_LOGO_DRAW = -2, /* draw the logo to a console */ + FBCON_LOGO_DONTSHOW = -3 /* do not show the logo */ +}; + +static struct fbcon_display fb_display[MAX_NR_CONSOLES]; + +struct fb_info *fbcon_registered_fb[FB_MAX]; +int fbcon_num_registered_fb; + +#define fbcon_for_each_registered_fb(i) \ + for (i = 0; WARN_CONSOLE_UNLOCKED(), i < FB_MAX; i++) \ + if (!fbcon_registered_fb[i]) {} else + +static signed char con2fb_map[MAX_NR_CONSOLES]; +static signed char con2fb_map_boot[MAX_NR_CONSOLES]; + +static struct fb_info *fbcon_info_from_console(int console) +{ + WARN_CONSOLE_UNLOCKED(); + + return fbcon_registered_fb[con2fb_map[console]]; +} + +static int logo_lines; +/* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO + enums. */ +static int logo_shown = FBCON_LOGO_CANSHOW; +/* console mappings */ +static unsigned int first_fb_vc; +static unsigned int last_fb_vc = MAX_NR_CONSOLES - 1; +static int fbcon_is_default = 1; +static int primary_device = -1; +static int fbcon_has_console_bind; + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY +static int map_override; + +static inline void fbcon_map_override(void) +{ + map_override = 1; +} +#else +static inline void fbcon_map_override(void) +{ +} +#endif /* CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY */ + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER +static bool deferred_takeover = true; +#else +#define deferred_takeover false +#endif + +/* font data */ +static char fontname[40]; + +/* current fb_info */ +static int info_idx = -1; + +/* console rotation */ +static int initial_rotation = -1; +static int fbcon_has_sysfs; +static int margin_color; + +static const struct consw fb_con; + +#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row) + +static int fbcon_cursor_noblink; + +#define divides(a, b) ((!(a) || (b)%(a)) ? 0 : 1) + +/* + * Interface used by the world + */ + +static void fbcon_clear_margins(struct vc_data *vc, int bottom_only); +static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table); + +/* + * Internal routines + */ +static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var, + int unit); +static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p, + int line, int count, int dy); +static void fbcon_modechanged(struct fb_info *info); +static void fbcon_set_all_vcs(struct fb_info *info); + +static struct device *fbcon_device; + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION +static inline void fbcon_set_rotation(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + if (!(info->flags & FBINFO_MISC_TILEBLITTING) && + ops->p->con_rotate < 4) + ops->rotate = ops->p->con_rotate; + else + ops->rotate = 0; +} + +static void fbcon_rotate(struct fb_info *info, u32 rotate) +{ + struct fbcon_ops *ops= info->fbcon_par; + struct fb_info *fb_info; + + if (!ops || ops->currcon == -1) + return; + + fb_info = fbcon_info_from_console(ops->currcon); + + if (info == fb_info) { + struct fbcon_display *p = &fb_display[ops->currcon]; + + if (rotate < 4) + p->con_rotate = rotate; + else + p->con_rotate = 0; + + fbcon_modechanged(info); + } +} + +static void fbcon_rotate_all(struct fb_info *info, u32 rotate) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int i; + + if (!ops || ops->currcon < 0 || rotate > 3) + return; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + vc = vc_cons[i].d; + if (!vc || vc->vc_mode != KD_TEXT || + fbcon_info_from_console(i) != info) + continue; + + p = &fb_display[vc->vc_num]; + p->con_rotate = rotate; + } + + fbcon_set_all_vcs(info); +} +#else +static inline void fbcon_set_rotation(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + ops->rotate = FB_ROTATE_UR; +} + +static void fbcon_rotate(struct fb_info *info, u32 rotate) +{ + return; +} + +static void fbcon_rotate_all(struct fb_info *info, u32 rotate) +{ + return; +} +#endif /* CONFIG_FRAMEBUFFER_CONSOLE_ROTATION */ + +static int fbcon_get_rotate(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + return (ops) ? ops->rotate : 0; +} + +static inline int fbcon_is_inactive(struct vc_data *vc, struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + return (info->state != FBINFO_STATE_RUNNING || + vc->vc_mode != KD_TEXT || ops->graphics); +} + +static int get_color(struct vc_data *vc, struct fb_info *info, + u16 c, int is_fg) +{ + int depth = fb_get_color_depth(&info->var, &info->fix); + int color = 0; + + if (console_blanked) { + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + + c = vc->vc_video_erase_char & charmask; + } + + if (depth != 1) + color = (is_fg) ? attr_fgcol((vc->vc_hi_font_mask) ? 9 : 8, c) + : attr_bgcol((vc->vc_hi_font_mask) ? 13 : 12, c); + + switch (depth) { + case 1: + { + int col = mono_col(info); + /* 0 or 1 */ + int fg = (info->fix.visual != FB_VISUAL_MONO01) ? col : 0; + int bg = (info->fix.visual != FB_VISUAL_MONO01) ? 0 : col; + + if (console_blanked) + fg = bg; + + color = (is_fg) ? fg : bg; + break; + } + case 2: + /* + * Scale down 16-colors to 4 colors. Default 4-color palette + * is grayscale. However, simply dividing the values by 4 + * will not work, as colors 1, 2 and 3 will be scaled-down + * to zero rendering them invisible. So empirically convert + * colors to a sane 4-level grayscale. + */ + switch (color) { + case 0: + color = 0; /* black */ + break; + case 1 ... 6: + color = 2; /* white */ + break; + case 7 ... 8: + color = 1; /* gray */ + break; + default: + color = 3; /* intense white */ + break; + } + break; + case 3: + /* + * Last 8 entries of default 16-color palette is a more intense + * version of the first 8 (i.e., same chrominance, different + * luminance). + */ + color &= 7; + break; + } + + + return color; +} + +static void fb_flashcursor(struct work_struct *work) +{ + struct fbcon_ops *ops = container_of(work, struct fbcon_ops, cursor_work.work); + struct fb_info *info; + struct vc_data *vc = NULL; + int c; + int mode; + int ret; + + /* FIXME: we should sort out the unbind locking instead */ + /* instead we just fail to flash the cursor if we can't get + * the lock instead of blocking fbcon deinit */ + ret = console_trylock(); + if (ret == 0) + return; + + /* protected by console_lock */ + info = ops->info; + + if (ops->currcon != -1) + vc = vc_cons[ops->currcon].d; + + if (!vc || !con_is_visible(vc) || + fbcon_info_from_console(vc->vc_num) != info || + vc->vc_deccm != 1) { + console_unlock(); + return; + } + + c = scr_readw((u16 *) vc->vc_pos); + mode = (!ops->cursor_flash || ops->cursor_state.enable) ? + CM_ERASE : CM_DRAW; + ops->cursor(vc, info, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); + console_unlock(); + + queue_delayed_work(system_power_efficient_wq, &ops->cursor_work, + ops->cur_blink_jiffies); +} + +static void fbcon_add_cursor_work(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + if (!fbcon_cursor_noblink) + queue_delayed_work(system_power_efficient_wq, &ops->cursor_work, + ops->cur_blink_jiffies); +} + +static void fbcon_del_cursor_work(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + cancel_delayed_work_sync(&ops->cursor_work); +} + +#ifndef MODULE +static int __init fb_console_setup(char *this_opt) +{ + char *options; + int i, j; + + if (!this_opt || !*this_opt) + return 1; + + while ((options = strsep(&this_opt, ",")) != NULL) { + if (!strncmp(options, "font:", 5)) { + strscpy(fontname, options + 5, sizeof(fontname)); + continue; + } + + if (!strncmp(options, "scrollback:", 11)) { + pr_warn("Ignoring scrollback size option\n"); + continue; + } + + if (!strncmp(options, "map:", 4)) { + options += 4; + if (*options) { + for (i = 0, j = 0; i < MAX_NR_CONSOLES; i++) { + if (!options[j]) + j = 0; + con2fb_map_boot[i] = + (options[j++]-'0') % FB_MAX; + } + + fbcon_map_override(); + } + continue; + } + + if (!strncmp(options, "vc:", 3)) { + options += 3; + if (*options) + first_fb_vc = simple_strtoul(options, &options, 10) - 1; + if (first_fb_vc >= MAX_NR_CONSOLES) + first_fb_vc = 0; + if (*options++ == '-') + last_fb_vc = simple_strtoul(options, &options, 10) - 1; + if (last_fb_vc < first_fb_vc || last_fb_vc >= MAX_NR_CONSOLES) + last_fb_vc = MAX_NR_CONSOLES - 1; + fbcon_is_default = 0; + continue; + } + + if (!strncmp(options, "rotate:", 7)) { + options += 7; + if (*options) + initial_rotation = simple_strtoul(options, &options, 0); + if (initial_rotation > 3) + initial_rotation = 0; + continue; + } + + if (!strncmp(options, "margin:", 7)) { + options += 7; + if (*options) + margin_color = simple_strtoul(options, &options, 0); + continue; + } +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER + if (!strcmp(options, "nodefer")) { + deferred_takeover = false; + continue; + } +#endif + + if (!strncmp(options, "logo-pos:", 9)) { + options += 9; + if (!strcmp(options, "center")) + fb_center_logo = true; + continue; + } + + if (!strncmp(options, "logo-count:", 11)) { + options += 11; + if (*options) + fb_logo_count = simple_strtol(options, &options, 0); + continue; + } + } + return 1; +} + +__setup("fbcon=", fb_console_setup); +#endif + +static int search_fb_in_map(int idx) +{ + int i, retval = 0; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == idx) + retval = 1; + } + return retval; +} + +static int search_for_mapped_con(void) +{ + int i, retval = 0; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] != -1) + retval = 1; + } + return retval; +} + +static int do_fbcon_takeover(int show_logo) +{ + int err, i; + + if (!fbcon_num_registered_fb) + return -ENODEV; + + if (!show_logo) + logo_shown = FBCON_LOGO_DONTSHOW; + + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map[i] = info_idx; + + err = do_take_over_console(&fb_con, first_fb_vc, last_fb_vc, + fbcon_is_default); + + if (err) { + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map[i] = -1; + info_idx = -1; + } else { + fbcon_has_console_bind = 1; + } + + return err; +} + +#ifdef MODULE +static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info, + int cols, int rows, int new_cols, int new_rows) +{ + logo_shown = FBCON_LOGO_DONTSHOW; +} +#else +static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info, + int cols, int rows, int new_cols, int new_rows) +{ + /* Need to make room for the logo */ + struct fbcon_ops *ops = info->fbcon_par; + int cnt, erase = vc->vc_video_erase_char, step; + unsigned short *save = NULL, *r, *q; + int logo_height; + + if (info->fbops->owner) { + logo_shown = FBCON_LOGO_DONTSHOW; + return; + } + + /* + * remove underline attribute from erase character + * if black and white framebuffer. + */ + if (fb_get_color_depth(&info->var, &info->fix) == 1) + erase &= ~0x400; + logo_height = fb_prepare_logo(info, ops->rotate); + logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height); + q = (unsigned short *) (vc->vc_origin + + vc->vc_size_row * rows); + step = logo_lines * cols; + for (r = q - logo_lines * cols; r < q; r++) + if (scr_readw(r) != vc->vc_video_erase_char) + break; + if (r != q && new_rows >= rows + logo_lines) { + save = kzalloc(array3_size(logo_lines, new_cols, 2), + GFP_KERNEL); + if (save) { + int i = min(cols, new_cols); + scr_memsetw(save, erase, array3_size(logo_lines, new_cols, 2)); + r = q - step; + for (cnt = 0; cnt < logo_lines; cnt++, r += i) + scr_memcpyw(save + cnt * new_cols, r, 2 * i); + r = q; + } + } + if (r == q) { + /* We can scroll screen down */ + r = q - step - cols; + for (cnt = rows - logo_lines; cnt > 0; cnt--) { + scr_memcpyw(r + step, r, vc->vc_size_row); + r -= cols; + } + if (!save) { + int lines; + if (vc->state.y + logo_lines >= rows) + lines = rows - vc->state.y - 1; + else + lines = logo_lines; + vc->state.y += lines; + vc->vc_pos += lines * vc->vc_size_row; + } + } + scr_memsetw((unsigned short *) vc->vc_origin, + erase, + vc->vc_size_row * logo_lines); + + if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) { + fbcon_clear_margins(vc, 0); + update_screen(vc); + } + + if (save) { + q = (unsigned short *) (vc->vc_origin + + vc->vc_size_row * + rows); + scr_memcpyw(q, save, array3_size(logo_lines, new_cols, 2)); + vc->state.y += logo_lines; + vc->vc_pos += logo_lines * vc->vc_size_row; + kfree(save); + } + + if (logo_shown == FBCON_LOGO_DONTSHOW) + return; + + if (logo_lines > vc->vc_bottom) { + logo_shown = FBCON_LOGO_CANSHOW; + printk(KERN_INFO + "fbcon_init: disable boot-logo (boot-logo bigger than screen).\n"); + } else { + logo_shown = FBCON_LOGO_DRAW; + vc->vc_top = logo_lines; + } +} +#endif /* MODULE */ + +#ifdef CONFIG_FB_TILEBLITTING +static void set_blitting_type(struct vc_data *vc, struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + ops->p = &fb_display[vc->vc_num]; + + if ((info->flags & FBINFO_MISC_TILEBLITTING)) + fbcon_set_tileops(vc, info); + else { + fbcon_set_rotation(info); + fbcon_set_bitops(ops); + } +} + +static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount) +{ + int err = 0; + + if (info->flags & FBINFO_MISC_TILEBLITTING && + info->tileops->fb_get_tilemax(info) < charcount) + err = 1; + + return err; +} +#else +static void set_blitting_type(struct vc_data *vc, struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + + info->flags &= ~FBINFO_MISC_TILEBLITTING; + ops->p = &fb_display[vc->vc_num]; + fbcon_set_rotation(info); + fbcon_set_bitops(ops); +} + +static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount) +{ + return 0; +} + +#endif /* CONFIG_MISC_TILEBLITTING */ + +static void fbcon_release(struct fb_info *info) +{ + lock_fb_info(info); + if (info->fbops->fb_release) + info->fbops->fb_release(info, 0); + unlock_fb_info(info); + + module_put(info->fbops->owner); + + if (info->fbcon_par) { + struct fbcon_ops *ops = info->fbcon_par; + + fbcon_del_cursor_work(info); + kfree(ops->cursor_state.mask); + kfree(ops->cursor_data); + kfree(ops->cursor_src); + kfree(ops->fontbuffer); + kfree(info->fbcon_par); + info->fbcon_par = NULL; + } +} + +static int fbcon_open(struct fb_info *info) +{ + struct fbcon_ops *ops; + + if (!try_module_get(info->fbops->owner)) + return -ENODEV; + + lock_fb_info(info); + if (info->fbops->fb_open && + info->fbops->fb_open(info, 0)) { + unlock_fb_info(info); + module_put(info->fbops->owner); + return -ENODEV; + } + unlock_fb_info(info); + + ops = kzalloc(sizeof(struct fbcon_ops), GFP_KERNEL); + if (!ops) { + fbcon_release(info); + return -ENOMEM; + } + + INIT_DELAYED_WORK(&ops->cursor_work, fb_flashcursor); + ops->info = info; + info->fbcon_par = ops; + ops->cur_blink_jiffies = HZ / 5; + + return 0; +} + +static int con2fb_acquire_newinfo(struct vc_data *vc, struct fb_info *info, + int unit) +{ + int err; + + err = fbcon_open(info); + if (err) + return err; + + if (vc) + set_blitting_type(vc, info); + + return err; +} + +static void con2fb_release_oldinfo(struct vc_data *vc, struct fb_info *oldinfo, + struct fb_info *newinfo) +{ + int ret; + + fbcon_release(oldinfo); + + /* + If oldinfo and newinfo are driving the same hardware, + the fb_release() method of oldinfo may attempt to + restore the hardware state. This will leave the + newinfo in an undefined state. Thus, a call to + fb_set_par() may be needed for the newinfo. + */ + if (newinfo && newinfo->fbops->fb_set_par) { + ret = newinfo->fbops->fb_set_par(newinfo); + + if (ret) + printk(KERN_ERR "con2fb_release_oldinfo: " + "detected unhandled fb_set_par error, " + "error code %d\n", ret); + } +} + +static void con2fb_init_display(struct vc_data *vc, struct fb_info *info, + int unit, int show_logo) +{ + struct fbcon_ops *ops = info->fbcon_par; + int ret; + + ops->currcon = fg_console; + + if (info->fbops->fb_set_par && !ops->initialized) { + ret = info->fbops->fb_set_par(info); + + if (ret) + printk(KERN_ERR "con2fb_init_display: detected " + "unhandled fb_set_par error, " + "error code %d\n", ret); + } + + ops->initialized = true; + ops->graphics = 0; + fbcon_set_disp(info, &info->var, unit); + + if (show_logo) { + struct vc_data *fg_vc = vc_cons[fg_console].d; + struct fb_info *fg_info = + fbcon_info_from_console(fg_console); + + fbcon_prepare_logo(fg_vc, fg_info, fg_vc->vc_cols, + fg_vc->vc_rows, fg_vc->vc_cols, + fg_vc->vc_rows); + } + + update_screen(vc_cons[fg_console].d); +} + +/** + * set_con2fb_map - map console to frame buffer device + * @unit: virtual console number to map + * @newidx: frame buffer index to map virtual console to + * @user: user request + * + * Maps a virtual console @unit to a frame buffer device + * @newidx. + * + * This should be called with the console lock held. + */ +static int set_con2fb_map(int unit, int newidx, int user) +{ + struct vc_data *vc = vc_cons[unit].d; + int oldidx = con2fb_map[unit]; + struct fb_info *info = fbcon_registered_fb[newidx]; + struct fb_info *oldinfo = NULL; + int found, err = 0, show_logo; + + WARN_CONSOLE_UNLOCKED(); + + if (oldidx == newidx) + return 0; + + if (!info) + return -EINVAL; + + if (!search_for_mapped_con() || !con_is_bound(&fb_con)) { + info_idx = newidx; + return do_fbcon_takeover(0); + } + + if (oldidx != -1) + oldinfo = fbcon_registered_fb[oldidx]; + + found = search_fb_in_map(newidx); + + if (!err && !found) { + err = con2fb_acquire_newinfo(vc, info, unit); + if (!err) + con2fb_map[unit] = newidx; + } + + /* + * If old fb is not mapped to any of the consoles, + * fbcon should release it. + */ + if (!err && oldinfo && !search_fb_in_map(oldidx)) + con2fb_release_oldinfo(vc, oldinfo, info); + + show_logo = (fg_console == 0 && !user && + logo_shown != FBCON_LOGO_DONTSHOW); + + if (!found) + fbcon_add_cursor_work(info); + con2fb_map_boot[unit] = newidx; + con2fb_init_display(vc, info, unit, show_logo); + + if (!search_fb_in_map(info_idx)) + info_idx = newidx; + + return err; +} + +/* + * Low Level Operations + */ +/* NOTE: fbcon cannot be __init: it may be called from do_take_over_console later */ +static int var_to_display(struct fbcon_display *disp, + struct fb_var_screeninfo *var, + struct fb_info *info) +{ + disp->xres_virtual = var->xres_virtual; + disp->yres_virtual = var->yres_virtual; + disp->bits_per_pixel = var->bits_per_pixel; + disp->grayscale = var->grayscale; + disp->nonstd = var->nonstd; + disp->accel_flags = var->accel_flags; + disp->height = var->height; + disp->width = var->width; + disp->red = var->red; + disp->green = var->green; + disp->blue = var->blue; + disp->transp = var->transp; + disp->rotate = var->rotate; + disp->mode = fb_match_mode(var, &info->modelist); + if (disp->mode == NULL) + /* This should not happen */ + return -EINVAL; + return 0; +} + +static void display_to_var(struct fb_var_screeninfo *var, + struct fbcon_display *disp) +{ + fb_videomode_to_var(var, disp->mode); + var->xres_virtual = disp->xres_virtual; + var->yres_virtual = disp->yres_virtual; + var->bits_per_pixel = disp->bits_per_pixel; + var->grayscale = disp->grayscale; + var->nonstd = disp->nonstd; + var->accel_flags = disp->accel_flags; + var->height = disp->height; + var->width = disp->width; + var->red = disp->red; + var->green = disp->green; + var->blue = disp->blue; + var->transp = disp->transp; + var->rotate = disp->rotate; +} + +static const char *fbcon_startup(void) +{ + const char *display_desc = "frame buffer device"; + struct fbcon_display *p = &fb_display[fg_console]; + struct vc_data *vc = vc_cons[fg_console].d; + const struct font_desc *font = NULL; + struct fb_info *info = NULL; + struct fbcon_ops *ops; + int rows, cols; + + /* + * If num_registered_fb is zero, this is a call for the dummy part. + * The frame buffer devices weren't initialized yet. + */ + if (!fbcon_num_registered_fb || info_idx == -1) + return display_desc; + /* + * Instead of blindly using registered_fb[0], we use info_idx, set by + * fbcon_fb_registered(); + */ + info = fbcon_registered_fb[info_idx]; + if (!info) + return NULL; + + if (fbcon_open(info)) + return NULL; + + ops = info->fbcon_par; + ops->currcon = -1; + ops->graphics = 1; + ops->cur_rotate = -1; + + p->con_rotate = initial_rotation; + if (p->con_rotate == -1) + p->con_rotate = info->fbcon_rotate_hint; + if (p->con_rotate == -1) + p->con_rotate = FB_ROTATE_UR; + + set_blitting_type(vc, info); + + /* Setup default font */ + if (!p->fontdata && !vc->vc_font.data) { + if (!fontname[0] || !(font = find_font(fontname))) + font = get_default_font(info->var.xres, + info->var.yres, + info->pixmap.blit_x, + info->pixmap.blit_y); + vc->vc_font.width = font->width; + vc->vc_font.height = font->height; + vc->vc_font.data = (void *)(p->fontdata = font->data); + vc->vc_font.charcount = font->charcount; + } else { + p->fontdata = vc->vc_font.data; + } + + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); + + pr_debug("mode: %s\n", info->fix.id); + pr_debug("visual: %d\n", info->fix.visual); + pr_debug("res: %dx%d-%d\n", info->var.xres, + info->var.yres, + info->var.bits_per_pixel); + + fbcon_add_cursor_work(info); + return display_desc; +} + +static void fbcon_init(struct vc_data *vc, int init) +{ + struct fb_info *info; + struct fbcon_ops *ops; + struct vc_data **default_mode = vc->vc_display_fg; + struct vc_data *svc = *default_mode; + struct fbcon_display *t, *p = &fb_display[vc->vc_num]; + int logo = 1, new_rows, new_cols, rows, cols; + int ret; + + if (WARN_ON(info_idx == -1)) + return; + + if (con2fb_map[vc->vc_num] == -1) + con2fb_map[vc->vc_num] = info_idx; + + info = fbcon_info_from_console(vc->vc_num); + + if (logo_shown < 0 && console_loglevel <= CONSOLE_LOGLEVEL_QUIET) + logo_shown = FBCON_LOGO_DONTSHOW; + + if (vc != svc || logo_shown == FBCON_LOGO_DONTSHOW || + (info->fix.type == FB_TYPE_TEXT)) + logo = 0; + + if (var_to_display(p, &info->var, info)) + return; + + if (!info->fbcon_par) + con2fb_acquire_newinfo(vc, info, vc->vc_num); + + /* If we are not the first console on this + fb, copy the font from that console */ + t = &fb_display[fg_console]; + if (!p->fontdata) { + if (t->fontdata) { + struct vc_data *fvc = vc_cons[fg_console].d; + + vc->vc_font.data = (void *)(p->fontdata = + fvc->vc_font.data); + vc->vc_font.width = fvc->vc_font.width; + vc->vc_font.height = fvc->vc_font.height; + vc->vc_font.charcount = fvc->vc_font.charcount; + p->userfont = t->userfont; + + if (p->userfont) + REFCOUNT(p->fontdata)++; + } else { + const struct font_desc *font = NULL; + + if (!fontname[0] || !(font = find_font(fontname))) + font = get_default_font(info->var.xres, + info->var.yres, + info->pixmap.blit_x, + info->pixmap.blit_y); + vc->vc_font.width = font->width; + vc->vc_font.height = font->height; + vc->vc_font.data = (void *)(p->fontdata = font->data); + vc->vc_font.charcount = font->charcount; + } + } + + vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1); + vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800; + if (vc->vc_font.charcount == 256) { + vc->vc_hi_font_mask = 0; + } else { + vc->vc_hi_font_mask = 0x100; + if (vc->vc_can_do_color) + vc->vc_complement_mask <<= 1; + } + + if (!*svc->uni_pagedict_loc) + con_set_default_unimap(svc); + if (!*vc->uni_pagedict_loc) + con_copy_unimap(vc, svc); + + ops = info->fbcon_par; + ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); + + p->con_rotate = initial_rotation; + if (p->con_rotate == -1) + p->con_rotate = info->fbcon_rotate_hint; + if (p->con_rotate == -1) + p->con_rotate = FB_ROTATE_UR; + + set_blitting_type(vc, info); + + cols = vc->vc_cols; + rows = vc->vc_rows; + new_cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + new_rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + new_cols /= vc->vc_font.width; + new_rows /= vc->vc_font.height; + + /* + * We must always set the mode. The mode of the previous console + * driver could be in the same resolution but we are using different + * hardware so we have to initialize the hardware. + * + * We need to do it in fbcon_init() to prevent screen corruption. + */ + if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) { + if (info->fbops->fb_set_par && !ops->initialized) { + ret = info->fbops->fb_set_par(info); + + if (ret) + printk(KERN_ERR "fbcon_init: detected " + "unhandled fb_set_par error, " + "error code %d\n", ret); + } + + ops->initialized = true; + } + + ops->graphics = 0; + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION + if ((info->flags & FBINFO_HWACCEL_COPYAREA) && + !(info->flags & FBINFO_HWACCEL_DISABLED)) + p->scrollmode = SCROLL_MOVE; + else /* default to something safe */ + p->scrollmode = SCROLL_REDRAW; +#endif + + /* + * ++guenther: console.c:vc_allocate() relies on initializing + * vc_{cols,rows}, but we must not set those if we are only + * resizing the console. + */ + if (init) { + vc->vc_cols = new_cols; + vc->vc_rows = new_rows; + } else + vc_resize(vc, new_cols, new_rows); + + if (logo) + fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows); + + if (ops->rotate_font && ops->rotate_font(info, vc)) { + ops->rotate = FB_ROTATE_UR; + set_blitting_type(vc, info); + } + + ops->p = &fb_display[fg_console]; +} + +static void fbcon_free_font(struct fbcon_display *p, bool freefont) +{ + if (freefont && p->userfont && p->fontdata && (--REFCOUNT(p->fontdata) == 0)) + kfree(p->fontdata - FONT_EXTRA_WORDS * sizeof(int)); + p->fontdata = NULL; + p->userfont = 0; +} + +static void set_vc_hi_font(struct vc_data *vc, bool set); + +static void fbcon_release_all(void) +{ + struct fb_info *info; + int i, j, mapped; + + fbcon_for_each_registered_fb(i) { + mapped = 0; + info = fbcon_registered_fb[i]; + + for (j = first_fb_vc; j <= last_fb_vc; j++) { + if (con2fb_map[j] == i) { + mapped = 1; + con2fb_map[j] = -1; + } + } + + if (mapped) + fbcon_release(info); + } +} + +static void fbcon_deinit(struct vc_data *vc) +{ + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_info *info; + struct fbcon_ops *ops; + int idx; + bool free_font = true; + + idx = con2fb_map[vc->vc_num]; + + if (idx == -1) + goto finished; + + info = fbcon_registered_fb[idx]; + + if (!info) + goto finished; + + if (info->flags & FBINFO_MISC_FIRMWARE) + free_font = false; + ops = info->fbcon_par; + + if (!ops) + goto finished; + + if (con_is_visible(vc)) + fbcon_del_cursor_work(info); + + ops->initialized = false; +finished: + + fbcon_free_font(p, free_font); + if (free_font) + vc->vc_font.data = NULL; + + if (vc->vc_hi_font_mask && vc->vc_screenbuf) + set_vc_hi_font(vc, false); + + if (!con_is_bound(&fb_con)) + fbcon_release_all(); + + if (vc->vc_num == logo_shown) + logo_shown = FBCON_LOGO_CANSHOW; + + return; +} + +/* ====================================================================== */ + +/* fbcon_XXX routines - interface used by the world + * + * This system is now divided into two levels because of complications + * caused by hardware scrolling. Top level functions: + * + * fbcon_bmove(), fbcon_clear(), fbcon_putc(), fbcon_clear_margins() + * + * handles y values in range [0, scr_height-1] that correspond to real + * screen positions. y_wrap shift means that first line of bitmap may be + * anywhere on this display. These functions convert lineoffsets to + * bitmap offsets and deal with the wrap-around case by splitting blits. + * + * fbcon_bmove_physical_8() -- These functions fast implementations + * fbcon_clear_physical_8() -- of original fbcon_XXX fns. + * fbcon_putc_physical_8() -- (font width != 8) may be added later + * + * WARNING: + * + * At the moment fbcon_putc() cannot blit across vertical wrap boundary + * Implies should only really hardware scroll in rows. Only reason for + * restriction is simplicity & efficiency at the moment. + */ + +static void fbcon_clear(struct vc_data *vc, int sy, int sx, int height, + int width) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + struct fbcon_display *p = &fb_display[vc->vc_num]; + u_int y_break; + + if (fbcon_is_inactive(vc, info)) + return; + + if (!height || !width) + return; + + if (sy < vc->vc_top && vc->vc_top == logo_lines) { + vc->vc_top = 0; + /* + * If the font dimensions are not an integral of the display + * dimensions then the ops->clear below won't end up clearing + * the margins. Call clear_margins here in case the logo + * bitmap stretched into the margin area. + */ + fbcon_clear_margins(vc, 0); + } + + /* Split blits that cross physical y_wrap boundary */ + + y_break = p->vrows - p->yscroll; + if (sy < y_break && sy + height - 1 >= y_break) { + u_int b = y_break - sy; + ops->clear(vc, info, real_y(p, sy), sx, b, width); + ops->clear(vc, info, real_y(p, sy + b), sx, height - b, + width); + } else + ops->clear(vc, info, real_y(p, sy), sx, height, width); +} + +static void fbcon_putcs(struct vc_data *vc, const unsigned short *s, + int count, int ypos, int xpos) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; + + if (!fbcon_is_inactive(vc, info)) + ops->putcs(vc, info, s, count, real_y(p, ypos), xpos, + get_color(vc, info, scr_readw(s), 1), + get_color(vc, info, scr_readw(s), 0)); +} + +static void fbcon_putc(struct vc_data *vc, int c, int ypos, int xpos) +{ + unsigned short chr; + + scr_writew(c, &chr); + fbcon_putcs(vc, &chr, 1, ypos, xpos); +} + +static void fbcon_clear_margins(struct vc_data *vc, int bottom_only) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + if (!fbcon_is_inactive(vc, info)) + ops->clear_margins(vc, info, margin_color, bottom_only); +} + +static void fbcon_cursor(struct vc_data *vc, int mode) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + int c = scr_readw((u16 *) vc->vc_pos); + + ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms); + + if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1) + return; + + if (vc->vc_cursor_type & CUR_SW) + fbcon_del_cursor_work(info); + else + fbcon_add_cursor_work(info); + + ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1; + + if (!ops->cursor) + return; + + ops->cursor(vc, info, mode, get_color(vc, info, c, 1), + get_color(vc, info, c, 0)); +} + +static int scrollback_phys_max = 0; +static int scrollback_max = 0; +static int scrollback_current = 0; + +static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var, + int unit) +{ + struct fbcon_display *p, *t; + struct vc_data **default_mode, *vc; + struct vc_data *svc; + struct fbcon_ops *ops = info->fbcon_par; + int rows, cols; + + p = &fb_display[unit]; + + if (var_to_display(p, var, info)) + return; + + vc = vc_cons[unit].d; + + if (!vc) + return; + + default_mode = vc->vc_display_fg; + svc = *default_mode; + t = &fb_display[svc->vc_num]; + + if (!vc->vc_font.data) { + vc->vc_font.data = (void *)(p->fontdata = t->fontdata); + vc->vc_font.width = (*default_mode)->vc_font.width; + vc->vc_font.height = (*default_mode)->vc_font.height; + vc->vc_font.charcount = (*default_mode)->vc_font.charcount; + p->userfont = t->userfont; + if (p->userfont) + REFCOUNT(p->fontdata)++; + } + + var->activate = FB_ACTIVATE_NOW; + info->var.activate = var->activate; + var->yoffset = info->var.yoffset; + var->xoffset = info->var.xoffset; + fb_set_var(info, var); + ops->var = info->var; + vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1); + vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800; + if (vc->vc_font.charcount == 256) { + vc->vc_hi_font_mask = 0; + } else { + vc->vc_hi_font_mask = 0x100; + if (vc->vc_can_do_color) + vc->vc_complement_mask <<= 1; + } + + if (!*svc->uni_pagedict_loc) + con_set_default_unimap(svc); + if (!*vc->uni_pagedict_loc) + con_copy_unimap(vc, svc); + + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); + + if (con_is_visible(vc)) { + update_screen(vc); + } +} + +static __inline__ void ywrap_up(struct vc_data *vc, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll += count; + if (p->yscroll >= p->vrows) /* Deal with wrap */ + p->yscroll -= p->vrows; + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode |= FB_VMODE_YWRAP; + ops->update_start(info); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) + scrollback_max = scrollback_phys_max; + scrollback_current = 0; +} + +static __inline__ void ywrap_down(struct vc_data *vc, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll -= count; + if (p->yscroll < 0) /* Deal with wrap */ + p->yscroll += p->vrows; + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode |= FB_VMODE_YWRAP; + ops->update_start(info); + scrollback_max -= count; + if (scrollback_max < 0) + scrollback_max = 0; + scrollback_current = 0; +} + +static __inline__ void ypan_up(struct vc_data *vc, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; + + p->yscroll += count; + if (p->yscroll > p->vrows - vc->vc_rows) { + ops->bmove(vc, info, p->vrows - vc->vc_rows, + 0, 0, 0, vc->vc_rows, vc->vc_cols); + p->yscroll -= p->vrows - vc->vc_rows; + } + + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode &= ~FB_VMODE_YWRAP; + ops->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) + scrollback_max = scrollback_phys_max; + scrollback_current = 0; +} + +static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll += count; + + if (p->yscroll > p->vrows - vc->vc_rows) { + p->yscroll -= p->vrows - vc->vc_rows; + fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t); + } + + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode &= ~FB_VMODE_YWRAP; + ops->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max += count; + if (scrollback_max > scrollback_phys_max) + scrollback_max = scrollback_phys_max; + scrollback_current = 0; +} + +static __inline__ void ypan_down(struct vc_data *vc, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fbcon_ops *ops = info->fbcon_par; + + p->yscroll -= count; + if (p->yscroll < 0) { + ops->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows, + 0, vc->vc_rows, vc->vc_cols); + p->yscroll += p->vrows - vc->vc_rows; + } + + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode &= ~FB_VMODE_YWRAP; + ops->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max -= count; + if (scrollback_max < 0) + scrollback_max = 0; + scrollback_current = 0; +} + +static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + + p->yscroll -= count; + + if (p->yscroll < 0) { + p->yscroll += p->vrows - vc->vc_rows; + fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count); + } + + ops->var.xoffset = 0; + ops->var.yoffset = p->yscroll * vc->vc_font.height; + ops->var.vmode &= ~FB_VMODE_YWRAP; + ops->update_start(info); + fbcon_clear_margins(vc, 1); + scrollback_max -= count; + if (scrollback_max < 0) + scrollback_max = 0; + scrollback_current = 0; +} + +static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p, + int line, int count, int dy) +{ + unsigned short *s = (unsigned short *) + (vc->vc_origin + vc->vc_size_row * line); + + while (count--) { + unsigned short *start = s; + unsigned short *le = advance_row(s, 1); + unsigned short c; + int x = 0; + unsigned short attr = 1; + + do { + c = scr_readw(s); + if (attr != (c & 0xff00)) { + attr = c & 0xff00; + if (s > start) { + fbcon_putcs(vc, start, s - start, + dy, x); + x += s - start; + start = s; + } + } + console_conditional_schedule(); + s++; + } while (s < le); + if (s > start) + fbcon_putcs(vc, start, s - start, dy, x); + console_conditional_schedule(); + dy++; + } +} + +static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info, + struct fbcon_display *p, int line, int count, int ycount) +{ + int offset = ycount * vc->vc_cols; + unsigned short *d = (unsigned short *) + (vc->vc_origin + vc->vc_size_row * line); + unsigned short *s = d + offset; + struct fbcon_ops *ops = info->fbcon_par; + + while (count--) { + unsigned short *start = s; + unsigned short *le = advance_row(s, 1); + unsigned short c; + int x = 0; + + do { + c = scr_readw(s); + + if (c == scr_readw(d)) { + if (s > start) { + ops->bmove(vc, info, line + ycount, x, + line, x, 1, s-start); + x += s - start + 1; + start = s + 1; + } else { + x++; + start++; + } + } + + scr_writew(c, d); + console_conditional_schedule(); + s++; + d++; + } while (s < le); + if (s > start) + ops->bmove(vc, info, line + ycount, x, line, x, 1, + s-start); + console_conditional_schedule(); + if (ycount > 0) + line++; + else { + line--; + /* NOTE: We subtract two lines from these pointers */ + s -= vc->vc_size_row; + d -= vc->vc_size_row; + } + } +} + +static void fbcon_redraw(struct vc_data *vc, struct fbcon_display *p, + int line, int count, int offset) +{ + unsigned short *d = (unsigned short *) + (vc->vc_origin + vc->vc_size_row * line); + unsigned short *s = d + offset; + + while (count--) { + unsigned short *start = s; + unsigned short *le = advance_row(s, 1); + unsigned short c; + int x = 0; + unsigned short attr = 1; + + do { + c = scr_readw(s); + if (attr != (c & 0xff00)) { + attr = c & 0xff00; + if (s > start) { + fbcon_putcs(vc, start, s - start, + line, x); + x += s - start; + start = s; + } + } + if (c == scr_readw(d)) { + if (s > start) { + fbcon_putcs(vc, start, s - start, + line, x); + x += s - start + 1; + start = s + 1; + } else { + x++; + start++; + } + } + scr_writew(c, d); + console_conditional_schedule(); + s++; + d++; + } while (s < le); + if (s > start) + fbcon_putcs(vc, start, s - start, line, x); + console_conditional_schedule(); + if (offset > 0) + line++; + else { + line--; + /* NOTE: We subtract two lines from these pointers */ + s -= vc->vc_size_row; + d -= vc->vc_size_row; + } + } +} + +static void fbcon_bmove_rec(struct vc_data *vc, struct fbcon_display *p, int sy, int sx, + int dy, int dx, int height, int width, u_int y_break) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + u_int b; + + if (sy < y_break && sy + height > y_break) { + b = y_break - sy; + if (dy < sy) { /* Avoid trashing self */ + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, + y_break); + fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, + height - b, width, y_break); + } else { + fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, + height - b, width, y_break); + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, + y_break); + } + return; + } + + if (dy < y_break && dy + height > y_break) { + b = y_break - dy; + if (dy < sy) { /* Avoid trashing self */ + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, + y_break); + fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, + height - b, width, y_break); + } else { + fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx, + height - b, width, y_break); + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width, + y_break); + } + return; + } + ops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx, + height, width); +} + +static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx, + int height, int width) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + + if (fbcon_is_inactive(vc, info)) + return; + + if (!width || !height) + return; + + /* Split blits that cross physical y_wrap case. + * Pathological case involves 4 blits, better to use recursive + * code rather than unrolled case + * + * Recursive invocations don't need to erase the cursor over and + * over again, so we use fbcon_bmove_rec() + */ + fbcon_bmove_rec(vc, p, sy, sx, dy, dx, height, width, + p->vrows - p->yscroll); +} + +static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b, + enum con_scroll dir, unsigned int count) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_display *p = &fb_display[vc->vc_num]; + int scroll_partial = info->flags & FBINFO_PARTIAL_PAN_OK; + + if (fbcon_is_inactive(vc, info)) + return true; + + fbcon_cursor(vc, CM_ERASE); + + /* + * ++Geert: Only use ywrap/ypan if the console is in text mode + * ++Andrew: Only use ypan on hardware text mode when scrolling the + * whole screen (prevents flicker). + */ + + switch (dir) { + case SM_UP: + if (count > vc->vc_rows) /* Maximum realistic size */ + count = vc->vc_rows; + switch (fb_scrollmode(p)) { + case SCROLL_MOVE: + fbcon_redraw_blit(vc, info, p, t, b - t - count, + count); + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + scr_memsetw((unsigned short *) (vc->vc_origin + + vc->vc_size_row * + (b - count)), + vc->vc_video_erase_char, + vc->vc_size_row * count); + return true; + + case SCROLL_WRAP_MOVE: + if (b - t - count > 3 * vc->vc_rows >> 2) { + if (t > 0) + fbcon_bmove(vc, 0, 0, count, 0, t, + vc->vc_cols); + ywrap_up(vc, count); + if (vc->vc_rows - b > 0) + fbcon_bmove(vc, b - count, 0, b, 0, + vc->vc_rows - b, + vc->vc_cols); + } else if (info->flags & FBINFO_READS_FAST) + fbcon_bmove(vc, t + count, 0, t, 0, + b - t - count, vc->vc_cols); + else + goto redraw_up; + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + break; + + case SCROLL_PAN_REDRAW: + if ((p->yscroll + count <= + 2 * (p->vrows - vc->vc_rows)) + && ((!scroll_partial && (b - t == vc->vc_rows)) + || (scroll_partial + && (b - t - count > + 3 * vc->vc_rows >> 2)))) { + if (t > 0) + fbcon_redraw_move(vc, p, 0, t, count); + ypan_up_redraw(vc, t, count); + if (vc->vc_rows - b > 0) + fbcon_redraw_move(vc, p, b, + vc->vc_rows - b, b); + } else + fbcon_redraw_move(vc, p, t + count, b - t - count, t); + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + break; + + case SCROLL_PAN_MOVE: + if ((p->yscroll + count <= + 2 * (p->vrows - vc->vc_rows)) + && ((!scroll_partial && (b - t == vc->vc_rows)) + || (scroll_partial + && (b - t - count > + 3 * vc->vc_rows >> 2)))) { + if (t > 0) + fbcon_bmove(vc, 0, 0, count, 0, t, + vc->vc_cols); + ypan_up(vc, count); + if (vc->vc_rows - b > 0) + fbcon_bmove(vc, b - count, 0, b, 0, + vc->vc_rows - b, + vc->vc_cols); + } else if (info->flags & FBINFO_READS_FAST) + fbcon_bmove(vc, t + count, 0, t, 0, + b - t - count, vc->vc_cols); + else + goto redraw_up; + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + break; + + case SCROLL_REDRAW: + redraw_up: + fbcon_redraw(vc, p, t, b - t - count, + count * vc->vc_cols); + fbcon_clear(vc, b - count, 0, count, vc->vc_cols); + scr_memsetw((unsigned short *) (vc->vc_origin + + vc->vc_size_row * + (b - count)), + vc->vc_video_erase_char, + vc->vc_size_row * count); + return true; + } + break; + + case SM_DOWN: + if (count > vc->vc_rows) /* Maximum realistic size */ + count = vc->vc_rows; + switch (fb_scrollmode(p)) { + case SCROLL_MOVE: + fbcon_redraw_blit(vc, info, p, b - 1, b - t - count, + -count); + fbcon_clear(vc, t, 0, count, vc->vc_cols); + scr_memsetw((unsigned short *) (vc->vc_origin + + vc->vc_size_row * + t), + vc->vc_video_erase_char, + vc->vc_size_row * count); + return true; + + case SCROLL_WRAP_MOVE: + if (b - t - count > 3 * vc->vc_rows >> 2) { + if (vc->vc_rows - b > 0) + fbcon_bmove(vc, b, 0, b - count, 0, + vc->vc_rows - b, + vc->vc_cols); + ywrap_down(vc, count); + if (t > 0) + fbcon_bmove(vc, count, 0, 0, 0, t, + vc->vc_cols); + } else if (info->flags & FBINFO_READS_FAST) + fbcon_bmove(vc, t, 0, t + count, 0, + b - t - count, vc->vc_cols); + else + goto redraw_down; + fbcon_clear(vc, t, 0, count, vc->vc_cols); + break; + + case SCROLL_PAN_MOVE: + if ((count - p->yscroll <= p->vrows - vc->vc_rows) + && ((!scroll_partial && (b - t == vc->vc_rows)) + || (scroll_partial + && (b - t - count > + 3 * vc->vc_rows >> 2)))) { + if (vc->vc_rows - b > 0) + fbcon_bmove(vc, b, 0, b - count, 0, + vc->vc_rows - b, + vc->vc_cols); + ypan_down(vc, count); + if (t > 0) + fbcon_bmove(vc, count, 0, 0, 0, t, + vc->vc_cols); + } else if (info->flags & FBINFO_READS_FAST) + fbcon_bmove(vc, t, 0, t + count, 0, + b - t - count, vc->vc_cols); + else + goto redraw_down; + fbcon_clear(vc, t, 0, count, vc->vc_cols); + break; + + case SCROLL_PAN_REDRAW: + if ((count - p->yscroll <= p->vrows - vc->vc_rows) + && ((!scroll_partial && (b - t == vc->vc_rows)) + || (scroll_partial + && (b - t - count > + 3 * vc->vc_rows >> 2)))) { + if (vc->vc_rows - b > 0) + fbcon_redraw_move(vc, p, b, vc->vc_rows - b, + b - count); + ypan_down_redraw(vc, t, count); + if (t > 0) + fbcon_redraw_move(vc, p, count, t, 0); + } else + fbcon_redraw_move(vc, p, t, b - t - count, t + count); + fbcon_clear(vc, t, 0, count, vc->vc_cols); + break; + + case SCROLL_REDRAW: + redraw_down: + fbcon_redraw(vc, p, b - 1, b - t - count, + -count * vc->vc_cols); + fbcon_clear(vc, t, 0, count, vc->vc_cols); + scr_memsetw((unsigned short *) (vc->vc_origin + + vc->vc_size_row * + t), + vc->vc_video_erase_char, + vc->vc_size_row * count); + return true; + } + } + return false; +} + + +static void updatescrollmode_accel(struct fbcon_display *p, + struct fb_info *info, + struct vc_data *vc) +{ +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION + struct fbcon_ops *ops = info->fbcon_par; + int cap = info->flags; + u16 t = 0; + int ypan = FBCON_SWAP(ops->rotate, info->fix.ypanstep, + info->fix.xpanstep); + int ywrap = FBCON_SWAP(ops->rotate, info->fix.ywrapstep, t); + int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual, + info->var.xres_virtual); + int good_pan = (cap & FBINFO_HWACCEL_YPAN) && + divides(ypan, vc->vc_font.height) && vyres > yres; + int good_wrap = (cap & FBINFO_HWACCEL_YWRAP) && + divides(ywrap, vc->vc_font.height) && + divides(vc->vc_font.height, vyres) && + divides(vc->vc_font.height, yres); + int reading_fast = cap & FBINFO_READS_FAST; + int fast_copyarea = (cap & FBINFO_HWACCEL_COPYAREA) && + !(cap & FBINFO_HWACCEL_DISABLED); + int fast_imageblit = (cap & FBINFO_HWACCEL_IMAGEBLIT) && + !(cap & FBINFO_HWACCEL_DISABLED); + + if (good_wrap || good_pan) { + if (reading_fast || fast_copyarea) + p->scrollmode = good_wrap ? + SCROLL_WRAP_MOVE : SCROLL_PAN_MOVE; + else + p->scrollmode = good_wrap ? SCROLL_REDRAW : + SCROLL_PAN_REDRAW; + } else { + if (reading_fast || (fast_copyarea && !fast_imageblit)) + p->scrollmode = SCROLL_MOVE; + else + p->scrollmode = SCROLL_REDRAW; + } +#endif +} + +static void updatescrollmode(struct fbcon_display *p, + struct fb_info *info, + struct vc_data *vc) +{ + struct fbcon_ops *ops = info->fbcon_par; + int fh = vc->vc_font.height; + int yres = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + int vyres = FBCON_SWAP(ops->rotate, info->var.yres_virtual, + info->var.xres_virtual); + + p->vrows = vyres/fh; + if (yres > (fh * (vc->vc_rows + 1))) + p->vrows -= (yres - (fh * vc->vc_rows)) / fh; + if ((yres % fh) && (vyres % fh < yres % fh)) + p->vrows--; + + /* update scrollmode in case hardware acceleration is used */ + updatescrollmode_accel(p, info, vc); +} + +#define PITCH(w) (((w) + 7) >> 3) +#define CALC_FONTSZ(h, p, c) ((h) * (p) * (c)) /* size = height * pitch * charcount */ + +static int fbcon_resize(struct vc_data *vc, unsigned int width, + unsigned int height, unsigned int user) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_var_screeninfo var = info->var; + int x_diff, y_diff, virt_w, virt_h, virt_fw, virt_fh; + + if (p->userfont && FNTSIZE(vc->vc_font.data)) { + int size; + int pitch = PITCH(vc->vc_font.width); + + /* + * If user font, ensure that a possible change to user font + * height or width will not allow a font data out-of-bounds access. + * NOTE: must use original charcount in calculation as font + * charcount can change and cannot be used to determine the + * font data allocated size. + */ + if (pitch <= 0) + return -EINVAL; + size = CALC_FONTSZ(vc->vc_font.height, pitch, vc->vc_font.charcount); + if (size > FNTSIZE(vc->vc_font.data)) + return -EINVAL; + } + + virt_w = FBCON_SWAP(ops->rotate, width, height); + virt_h = FBCON_SWAP(ops->rotate, height, width); + virt_fw = FBCON_SWAP(ops->rotate, vc->vc_font.width, + vc->vc_font.height); + virt_fh = FBCON_SWAP(ops->rotate, vc->vc_font.height, + vc->vc_font.width); + var.xres = virt_w * virt_fw; + var.yres = virt_h * virt_fh; + x_diff = info->var.xres - var.xres; + y_diff = info->var.yres - var.yres; + if (x_diff < 0 || x_diff > virt_fw || + y_diff < 0 || y_diff > virt_fh) { + const struct fb_videomode *mode; + + pr_debug("attempting resize %ix%i\n", var.xres, var.yres); + mode = fb_find_best_mode(&var, &info->modelist); + if (mode == NULL) + return -EINVAL; + display_to_var(&var, p); + fb_videomode_to_var(&var, mode); + + if (virt_w > var.xres/virt_fw || virt_h > var.yres/virt_fh) + return -EINVAL; + + pr_debug("resize now %ix%i\n", var.xres, var.yres); + if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) { + var.activate = FB_ACTIVATE_NOW | + FB_ACTIVATE_FORCE; + fb_set_var(info, &var); + } + var_to_display(p, &info->var, info); + ops->var = info->var; + } + updatescrollmode(p, info, vc); + return 0; +} + +static int fbcon_switch(struct vc_data *vc) +{ + struct fb_info *info, *old_info = NULL; + struct fbcon_ops *ops; + struct fbcon_display *p = &fb_display[vc->vc_num]; + struct fb_var_screeninfo var; + int i, ret, prev_console; + + info = fbcon_info_from_console(vc->vc_num); + ops = info->fbcon_par; + + if (logo_shown >= 0) { + struct vc_data *conp2 = vc_cons[logo_shown].d; + + if (conp2->vc_top == logo_lines + && conp2->vc_bottom == conp2->vc_rows) + conp2->vc_top = 0; + logo_shown = FBCON_LOGO_CANSHOW; + } + + prev_console = ops->currcon; + if (prev_console != -1) + old_info = fbcon_info_from_console(prev_console); + /* + * FIXME: If we have multiple fbdev's loaded, we need to + * update all info->currcon. Perhaps, we can place this + * in a centralized structure, but this might break some + * drivers. + * + * info->currcon = vc->vc_num; + */ + fbcon_for_each_registered_fb(i) { + if (fbcon_registered_fb[i]->fbcon_par) { + struct fbcon_ops *o = fbcon_registered_fb[i]->fbcon_par; + + o->currcon = vc->vc_num; + } + } + memset(&var, 0, sizeof(struct fb_var_screeninfo)); + display_to_var(&var, p); + var.activate = FB_ACTIVATE_NOW; + + /* + * make sure we don't unnecessarily trip the memcmp() + * in fb_set_var() + */ + info->var.activate = var.activate; + var.vmode |= info->var.vmode & ~FB_VMODE_MASK; + fb_set_var(info, &var); + ops->var = info->var; + + if (old_info != NULL && (old_info != info || + info->flags & FBINFO_MISC_ALWAYS_SETPAR)) { + if (info->fbops->fb_set_par) { + ret = info->fbops->fb_set_par(info); + + if (ret) + printk(KERN_ERR "fbcon_switch: detected " + "unhandled fb_set_par error, " + "error code %d\n", ret); + } + + if (old_info != info) + fbcon_del_cursor_work(old_info); + } + + if (fbcon_is_inactive(vc, info) || + ops->blank_state != FB_BLANK_UNBLANK) + fbcon_del_cursor_work(info); + else + fbcon_add_cursor_work(info); + + set_blitting_type(vc, info); + ops->cursor_reset = 1; + + if (ops->rotate_font && ops->rotate_font(info, vc)) { + ops->rotate = FB_ROTATE_UR; + set_blitting_type(vc, info); + } + + vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1); + vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800; + + if (vc->vc_font.charcount > 256) + vc->vc_complement_mask <<= 1; + + updatescrollmode(p, info, vc); + + switch (fb_scrollmode(p)) { + case SCROLL_WRAP_MOVE: + scrollback_phys_max = p->vrows - vc->vc_rows; + break; + case SCROLL_PAN_MOVE: + case SCROLL_PAN_REDRAW: + scrollback_phys_max = p->vrows - 2 * vc->vc_rows; + if (scrollback_phys_max < 0) + scrollback_phys_max = 0; + break; + default: + scrollback_phys_max = 0; + break; + } + + scrollback_max = 0; + scrollback_current = 0; + + if (!fbcon_is_inactive(vc, info)) { + ops->var.xoffset = ops->var.yoffset = p->yscroll = 0; + ops->update_start(info); + } + + fbcon_set_palette(vc, color_table); + fbcon_clear_margins(vc, 0); + + if (logo_shown == FBCON_LOGO_DRAW) { + + logo_shown = fg_console; + fb_show_logo(info, ops->rotate); + update_region(vc, + vc->vc_origin + vc->vc_size_row * vc->vc_top, + vc->vc_size_row * (vc->vc_bottom - + vc->vc_top) / 2); + return 0; + } + return 1; +} + +static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info, + int blank) +{ + if (blank) { + unsigned short charmask = vc->vc_hi_font_mask ? + 0x1ff : 0xff; + unsigned short oldc; + + oldc = vc->vc_video_erase_char; + vc->vc_video_erase_char &= charmask; + fbcon_clear(vc, 0, 0, vc->vc_rows, vc->vc_cols); + vc->vc_video_erase_char = oldc; + } +} + +static int fbcon_blank(struct vc_data *vc, int blank, int mode_switch) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + if (mode_switch) { + struct fb_var_screeninfo var = info->var; + + ops->graphics = 1; + + if (!blank) { + var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE | + FB_ACTIVATE_KD_TEXT; + fb_set_var(info, &var); + ops->graphics = 0; + ops->var = info->var; + } + } + + if (!fbcon_is_inactive(vc, info)) { + if (ops->blank_state != blank) { + ops->blank_state = blank; + fbcon_cursor(vc, blank ? CM_ERASE : CM_DRAW); + ops->cursor_flash = (!blank); + + if (fb_blank(info, blank)) + fbcon_generic_blank(vc, info, blank); + } + + if (!blank) + update_screen(vc); + } + + if (mode_switch || fbcon_is_inactive(vc, info) || + ops->blank_state != FB_BLANK_UNBLANK) + fbcon_del_cursor_work(info); + else + fbcon_add_cursor_work(info); + + return 0; +} + +static int fbcon_debug_enter(struct vc_data *vc) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + ops->save_graphics = ops->graphics; + ops->graphics = 0; + if (info->fbops->fb_debug_enter) + info->fbops->fb_debug_enter(info); + fbcon_set_palette(vc, color_table); + return 0; +} + +static int fbcon_debug_leave(struct vc_data *vc) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + + ops->graphics = ops->save_graphics; + if (info->fbops->fb_debug_leave) + info->fbops->fb_debug_leave(info); + return 0; +} + +static int fbcon_get_font(struct vc_data *vc, struct console_font *font) +{ + u8 *fontdata = vc->vc_font.data; + u8 *data = font->data; + int i, j; + + font->width = vc->vc_font.width; + font->height = vc->vc_font.height; + font->charcount = vc->vc_hi_font_mask ? 512 : 256; + if (!font->data) + return 0; + + if (font->width <= 8) { + j = vc->vc_font.height; + if (font->charcount * j > FNTSIZE(fontdata)) + return -EINVAL; + + for (i = 0; i < font->charcount; i++) { + memcpy(data, fontdata, j); + memset(data + j, 0, 32 - j); + data += 32; + fontdata += j; + } + } else if (font->width <= 16) { + j = vc->vc_font.height * 2; + if (font->charcount * j > FNTSIZE(fontdata)) + return -EINVAL; + + for (i = 0; i < font->charcount; i++) { + memcpy(data, fontdata, j); + memset(data + j, 0, 64 - j); + data += 64; + fontdata += j; + } + } else if (font->width <= 24) { + if (font->charcount * (vc->vc_font.height * sizeof(u32)) > FNTSIZE(fontdata)) + return -EINVAL; + + for (i = 0; i < font->charcount; i++) { + for (j = 0; j < vc->vc_font.height; j++) { + *data++ = fontdata[0]; + *data++ = fontdata[1]; + *data++ = fontdata[2]; + fontdata += sizeof(u32); + } + memset(data, 0, 3 * (32 - j)); + data += 3 * (32 - j); + } + } else { + j = vc->vc_font.height * 4; + if (font->charcount * j > FNTSIZE(fontdata)) + return -EINVAL; + + for (i = 0; i < font->charcount; i++) { + memcpy(data, fontdata, j); + memset(data + j, 0, 128 - j); + data += 128; + fontdata += j; + } + } + return 0; +} + +/* set/clear vc_hi_font_mask and update vc attrs accordingly */ +static void set_vc_hi_font(struct vc_data *vc, bool set) +{ + if (!set) { + vc->vc_hi_font_mask = 0; + if (vc->vc_can_do_color) { + vc->vc_complement_mask >>= 1; + vc->vc_s_complement_mask >>= 1; + } + + /* ++Edmund: reorder the attribute bits */ + if (vc->vc_can_do_color) { + unsigned short *cp = + (unsigned short *) vc->vc_origin; + int count = vc->vc_screenbuf_size / 2; + unsigned short c; + for (; count > 0; count--, cp++) { + c = scr_readw(cp); + scr_writew(((c & 0xfe00) >> 1) | + (c & 0xff), cp); + } + c = vc->vc_video_erase_char; + vc->vc_video_erase_char = + ((c & 0xfe00) >> 1) | (c & 0xff); + vc->vc_attr >>= 1; + } + } else { + vc->vc_hi_font_mask = 0x100; + if (vc->vc_can_do_color) { + vc->vc_complement_mask <<= 1; + vc->vc_s_complement_mask <<= 1; + } + + /* ++Edmund: reorder the attribute bits */ + { + unsigned short *cp = + (unsigned short *) vc->vc_origin; + int count = vc->vc_screenbuf_size / 2; + unsigned short c; + for (; count > 0; count--, cp++) { + unsigned short newc; + c = scr_readw(cp); + if (vc->vc_can_do_color) + newc = + ((c & 0xff00) << 1) | (c & + 0xff); + else + newc = c & ~0x100; + scr_writew(newc, cp); + } + c = vc->vc_video_erase_char; + if (vc->vc_can_do_color) { + vc->vc_video_erase_char = + ((c & 0xff00) << 1) | (c & 0xff); + vc->vc_attr <<= 1; + } else + vc->vc_video_erase_char = c & ~0x100; + } + } +} + +static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount, + const u8 * data, int userfont) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + struct fbcon_ops *ops = info->fbcon_par; + struct fbcon_display *p = &fb_display[vc->vc_num]; + int resize, ret, old_userfont, old_width, old_height, old_charcount; + char *old_data = NULL; + + resize = (w != vc->vc_font.width) || (h != vc->vc_font.height); + if (p->userfont) + old_data = vc->vc_font.data; + vc->vc_font.data = (void *)(p->fontdata = data); + old_userfont = p->userfont; + if ((p->userfont = userfont)) + REFCOUNT(data)++; + + old_width = vc->vc_font.width; + old_height = vc->vc_font.height; + old_charcount = vc->vc_font.charcount; + + vc->vc_font.width = w; + vc->vc_font.height = h; + vc->vc_font.charcount = charcount; + if (vc->vc_hi_font_mask && charcount == 256) + set_vc_hi_font(vc, false); + else if (!vc->vc_hi_font_mask && charcount == 512) + set_vc_hi_font(vc, true); + + if (resize) { + int cols, rows; + + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= w; + rows /= h; + ret = vc_resize(vc, cols, rows); + if (ret) + goto err_out; + } else if (con_is_visible(vc) + && vc->vc_mode == KD_TEXT) { + fbcon_clear_margins(vc, 0); + update_screen(vc); + } + + if (old_data && (--REFCOUNT(old_data) == 0)) + kfree(old_data - FONT_EXTRA_WORDS * sizeof(int)); + return 0; + +err_out: + p->fontdata = old_data; + vc->vc_font.data = (void *)old_data; + + if (userfont) { + p->userfont = old_userfont; + if (--REFCOUNT(data) == 0) + kfree(data - FONT_EXTRA_WORDS * sizeof(int)); + } + + vc->vc_font.width = old_width; + vc->vc_font.height = old_height; + vc->vc_font.charcount = old_charcount; + + return ret; +} + +/* + * User asked to set font; we are guaranteed that + * a) width and height are in range 1..32 + * b) charcount does not exceed 512 + * but lets not assume that, since someone might someday want to use larger + * fonts. And charcount of 512 is small for unicode support. + * + * However, user space gives the font in 32 rows , regardless of + * actual font height. So a new API is needed if support for larger fonts + * is ever implemented. + */ + +static int fbcon_set_font(struct vc_data *vc, struct console_font *font, + unsigned int flags) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + unsigned charcount = font->charcount; + int w = font->width; + int h = font->height; + int size; + int i, csum; + u8 *new_data, *data = font->data; + int pitch = PITCH(font->width); + + /* Is there a reason why fbconsole couldn't handle any charcount >256? + * If not this check should be changed to charcount < 256 */ + if (charcount != 256 && charcount != 512) + return -EINVAL; + + /* font bigger than screen resolution ? */ + if (w > FBCON_SWAP(info->var.rotate, info->var.xres, info->var.yres) || + h > FBCON_SWAP(info->var.rotate, info->var.yres, info->var.xres)) + return -EINVAL; + + if (font->width > 32 || font->height > 32) + return -EINVAL; + + /* Make sure drawing engine can handle the font */ + if (!(info->pixmap.blit_x & BIT(font->width - 1)) || + !(info->pixmap.blit_y & BIT(font->height - 1))) + return -EINVAL; + + /* Make sure driver can handle the font length */ + if (fbcon_invalid_charcount(info, charcount)) + return -EINVAL; + + size = CALC_FONTSZ(h, pitch, charcount); + + new_data = kmalloc(FONT_EXTRA_WORDS * sizeof(int) + size, GFP_USER); + + if (!new_data) + return -ENOMEM; + + memset(new_data, 0, FONT_EXTRA_WORDS * sizeof(int)); + + new_data += FONT_EXTRA_WORDS * sizeof(int); + FNTSIZE(new_data) = size; + REFCOUNT(new_data) = 0; /* usage counter */ + for (i=0; i< charcount; i++) { + memcpy(new_data + i*h*pitch, data + i*32*pitch, h*pitch); + } + + /* Since linux has a nice crc32 function use it for counting font + * checksums. */ + csum = crc32(0, new_data, size); + + FNTSUM(new_data) = csum; + /* Check if the same font is on some other console already */ + for (i = first_fb_vc; i <= last_fb_vc; i++) { + struct vc_data *tmp = vc_cons[i].d; + + if (fb_display[i].userfont && + fb_display[i].fontdata && + FNTSUM(fb_display[i].fontdata) == csum && + FNTSIZE(fb_display[i].fontdata) == size && + tmp->vc_font.width == w && + !memcmp(fb_display[i].fontdata, new_data, size)) { + kfree(new_data - FONT_EXTRA_WORDS * sizeof(int)); + new_data = (u8 *)fb_display[i].fontdata; + break; + } + } + return fbcon_do_set_font(vc, font->width, font->height, charcount, new_data, 1); +} + +static int fbcon_set_def_font(struct vc_data *vc, struct console_font *font, char *name) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + const struct font_desc *f; + + if (!name) + f = get_default_font(info->var.xres, info->var.yres, + info->pixmap.blit_x, info->pixmap.blit_y); + else if (!(f = find_font(name))) + return -ENOENT; + + font->width = f->width; + font->height = f->height; + return fbcon_do_set_font(vc, f->width, f->height, f->charcount, f->data, 0); +} + +static u16 palette_red[16]; +static u16 palette_green[16]; +static u16 palette_blue[16]; + +static struct fb_cmap palette_cmap = { + 0, 16, palette_red, palette_green, palette_blue, NULL +}; + +static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table) +{ + struct fb_info *info = fbcon_info_from_console(vc->vc_num); + int i, j, k, depth; + u8 val; + + if (fbcon_is_inactive(vc, info)) + return; + + if (!con_is_visible(vc)) + return; + + depth = fb_get_color_depth(&info->var, &info->fix); + if (depth > 3) { + for (i = j = 0; i < 16; i++) { + k = table[i]; + val = vc->vc_palette[j++]; + palette_red[k] = (val << 8) | val; + val = vc->vc_palette[j++]; + palette_green[k] = (val << 8) | val; + val = vc->vc_palette[j++]; + palette_blue[k] = (val << 8) | val; + } + palette_cmap.len = 16; + palette_cmap.start = 0; + /* + * If framebuffer is capable of less than 16 colors, + * use default palette of fbcon. + */ + } else + fb_copy_cmap(fb_default_cmap(1 << depth), &palette_cmap); + + fb_set_cmap(&palette_cmap, info); +} + +static u16 *fbcon_screen_pos(const struct vc_data *vc, int offset) +{ + return (u16 *) (vc->vc_origin + offset); +} + +static unsigned long fbcon_getxy(struct vc_data *vc, unsigned long pos, + int *px, int *py) +{ + unsigned long ret; + int x, y; + + if (pos >= vc->vc_origin && pos < vc->vc_scr_end) { + unsigned long offset = (pos - vc->vc_origin) / 2; + + x = offset % vc->vc_cols; + y = offset / vc->vc_cols; + ret = pos + (vc->vc_cols - x) * 2; + } else { + /* Should not happen */ + x = y = 0; + ret = vc->vc_origin; + } + if (px) + *px = x; + if (py) + *py = y; + return ret; +} + +/* As we might be inside of softback, we may work with non-contiguous buffer, + that's why we have to use a separate routine. */ +static void fbcon_invert_region(struct vc_data *vc, u16 * p, int cnt) +{ + while (cnt--) { + u16 a = scr_readw(p); + if (!vc->vc_can_do_color) + a ^= 0x0800; + else if (vc->vc_hi_font_mask == 0x100) + a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) | + (((a) & 0x0e00) << 4); + else + a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) | + (((a) & 0x0700) << 4); + scr_writew(a, p++); + } +} + +void fbcon_suspended(struct fb_info *info) +{ + struct vc_data *vc = NULL; + struct fbcon_ops *ops = info->fbcon_par; + + if (!ops || ops->currcon < 0) + return; + vc = vc_cons[ops->currcon].d; + + /* Clear cursor, restore saved data */ + fbcon_cursor(vc, CM_ERASE); +} + +void fbcon_resumed(struct fb_info *info) +{ + struct vc_data *vc; + struct fbcon_ops *ops = info->fbcon_par; + + if (!ops || ops->currcon < 0) + return; + vc = vc_cons[ops->currcon].d; + + update_screen(vc); +} + +static void fbcon_modechanged(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int rows, cols; + + if (!ops || ops->currcon < 0) + return; + vc = vc_cons[ops->currcon].d; + if (vc->vc_mode != KD_TEXT || + fbcon_info_from_console(ops->currcon) != info) + return; + + p = &fb_display[vc->vc_num]; + set_blitting_type(vc, info); + + if (con_is_visible(vc)) { + var_to_display(p, &info->var, info); + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); + updatescrollmode(p, info, vc); + scrollback_max = 0; + scrollback_current = 0; + + if (!fbcon_is_inactive(vc, info)) { + ops->var.xoffset = ops->var.yoffset = p->yscroll = 0; + ops->update_start(info); + } + + fbcon_set_palette(vc, color_table); + update_screen(vc); + } +} + +static void fbcon_set_all_vcs(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + struct fbcon_display *p; + int i, rows, cols, fg = -1; + + if (!ops || ops->currcon < 0) + return; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + vc = vc_cons[i].d; + if (!vc || vc->vc_mode != KD_TEXT || + fbcon_info_from_console(i) != info) + continue; + + if (con_is_visible(vc)) { + fg = i; + continue; + } + + p = &fb_display[vc->vc_num]; + set_blitting_type(vc, info); + var_to_display(p, &info->var, info); + cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); + rows = FBCON_SWAP(ops->rotate, info->var.yres, info->var.xres); + cols /= vc->vc_font.width; + rows /= vc->vc_font.height; + vc_resize(vc, cols, rows); + } + + if (fg != -1) + fbcon_modechanged(info); +} + + +void fbcon_update_vcs(struct fb_info *info, bool all) +{ + if (all) + fbcon_set_all_vcs(info); + else + fbcon_modechanged(info); +} +EXPORT_SYMBOL(fbcon_update_vcs); + +/* let fbcon check if it supports a new screen resolution */ +int fbcon_modechange_possible(struct fb_info *info, struct fb_var_screeninfo *var) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + unsigned int i; + + WARN_CONSOLE_UNLOCKED(); + + if (!ops) + return 0; + + /* prevent setting a screen size which is smaller than font size */ + for (i = first_fb_vc; i <= last_fb_vc; i++) { + vc = vc_cons[i].d; + if (!vc || vc->vc_mode != KD_TEXT || + fbcon_info_from_console(i) != info) + continue; + + if (vc->vc_font.width > FBCON_SWAP(var->rotate, var->xres, var->yres) || + vc->vc_font.height > FBCON_SWAP(var->rotate, var->yres, var->xres)) + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fbcon_modechange_possible); + +int fbcon_mode_deleted(struct fb_info *info, + struct fb_videomode *mode) +{ + struct fb_info *fb_info; + struct fbcon_display *p; + int i, j, found = 0; + + /* before deletion, ensure that mode is not in use */ + for (i = first_fb_vc; i <= last_fb_vc; i++) { + j = con2fb_map[i]; + if (j == -1) + continue; + fb_info = fbcon_registered_fb[j]; + if (fb_info != info) + continue; + p = &fb_display[i]; + if (!p || !p->mode) + continue; + if (fb_mode_is_equal(p->mode, mode)) { + found = 1; + break; + } + } + return found; +} + +#ifdef CONFIG_VT_HW_CONSOLE_BINDING +static void fbcon_unbind(void) +{ + int ret; + + ret = do_unbind_con_driver(&fb_con, first_fb_vc, last_fb_vc, + fbcon_is_default); + + if (!ret) + fbcon_has_console_bind = 0; +} +#else +static inline void fbcon_unbind(void) {} +#endif /* CONFIG_VT_HW_CONSOLE_BINDING */ + +void fbcon_fb_unbind(struct fb_info *info) +{ + int i, new_idx = -1; + int idx = info->node; + + console_lock(); + + if (!fbcon_has_console_bind) { + console_unlock(); + return; + } + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] != idx && + con2fb_map[i] != -1) { + new_idx = con2fb_map[i]; + break; + } + } + + if (new_idx != -1) { + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == idx) + set_con2fb_map(i, new_idx, 0); + } + } else { + struct fb_info *info = fbcon_registered_fb[idx]; + + /* This is sort of like set_con2fb_map, except it maps + * the consoles to no device and then releases the + * oldinfo to free memory and cancel the cursor blink + * timer. I can imagine this just becoming part of + * set_con2fb_map where new_idx is -1 + */ + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == idx) { + con2fb_map[i] = -1; + if (!search_fb_in_map(idx)) { + con2fb_release_oldinfo(vc_cons[i].d, + info, NULL); + } + } + } + fbcon_unbind(); + } + + console_unlock(); +} + +void fbcon_fb_unregistered(struct fb_info *info) +{ + int i, idx; + + console_lock(); + + fbcon_registered_fb[info->node] = NULL; + fbcon_num_registered_fb--; + + if (deferred_takeover) { + console_unlock(); + return; + } + + idx = info->node; + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == idx) + con2fb_map[i] = -1; + } + + if (idx == info_idx) { + info_idx = -1; + + fbcon_for_each_registered_fb(i) { + info_idx = i; + break; + } + } + + if (info_idx != -1) { + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map[i] == -1) + con2fb_map[i] = info_idx; + } + } + + if (primary_device == idx) + primary_device = -1; + + if (!fbcon_num_registered_fb) + do_unregister_con_driver(&fb_con); + console_unlock(); +} + +void fbcon_remap_all(struct fb_info *info) +{ + int i, idx = info->node; + + console_lock(); + if (deferred_takeover) { + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map_boot[i] = idx; + fbcon_map_override(); + console_unlock(); + return; + } + + for (i = first_fb_vc; i <= last_fb_vc; i++) + set_con2fb_map(i, idx, 0); + + if (con_is_bound(&fb_con)) { + printk(KERN_INFO "fbcon: Remapping primary device, " + "fb%i, to tty %i-%i\n", idx, + first_fb_vc + 1, last_fb_vc + 1); + info_idx = idx; + } + console_unlock(); +} + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY +static void fbcon_select_primary(struct fb_info *info) +{ + if (!map_override && primary_device == -1 && + fb_is_primary_device(info)) { + int i; + + printk(KERN_INFO "fbcon: %s (fb%i) is primary device\n", + info->fix.id, info->node); + primary_device = info->node; + + for (i = first_fb_vc; i <= last_fb_vc; i++) + con2fb_map_boot[i] = primary_device; + + if (con_is_bound(&fb_con)) { + printk(KERN_INFO "fbcon: Remapping primary device, " + "fb%i, to tty %i-%i\n", info->node, + first_fb_vc + 1, last_fb_vc + 1); + info_idx = primary_device; + } + } + +} +#else +static inline void fbcon_select_primary(struct fb_info *info) +{ + return; +} +#endif /* CONFIG_FRAMEBUFFER_DETECT_PRIMARY */ + +static bool lockless_register_fb; +module_param_named_unsafe(lockless_register_fb, lockless_register_fb, bool, 0400); +MODULE_PARM_DESC(lockless_register_fb, + "Lockless framebuffer registration for debugging [default=off]"); + +/* called with console_lock held */ +static int do_fb_registered(struct fb_info *info) +{ + int ret = 0, i, idx; + + WARN_CONSOLE_UNLOCKED(); + + fbcon_registered_fb[info->node] = info; + fbcon_num_registered_fb++; + + idx = info->node; + fbcon_select_primary(info); + + if (deferred_takeover) { + pr_info("fbcon: Deferring console take-over\n"); + return 0; + } + + if (info_idx == -1) { + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map_boot[i] == idx) { + info_idx = idx; + break; + } + } + + if (info_idx != -1) + ret = do_fbcon_takeover(1); + } else { + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (con2fb_map_boot[i] == idx) + set_con2fb_map(i, idx, 0); + } + } + + return ret; +} + +int fbcon_fb_registered(struct fb_info *info) +{ + int ret; + + if (!lockless_register_fb) + console_lock(); + else + atomic_inc(&ignore_console_lock_warning); + + ret = do_fb_registered(info); + + if (!lockless_register_fb) + console_unlock(); + else + atomic_dec(&ignore_console_lock_warning); + + return ret; +} + +void fbcon_fb_blanked(struct fb_info *info, int blank) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct vc_data *vc; + + if (!ops || ops->currcon < 0) + return; + + vc = vc_cons[ops->currcon].d; + if (vc->vc_mode != KD_TEXT || + fbcon_info_from_console(ops->currcon) != info) + return; + + if (con_is_visible(vc)) { + if (blank) + do_blank_screen(0); + else + do_unblank_screen(0); + } + ops->blank_state = blank; +} + +void fbcon_new_modelist(struct fb_info *info) +{ + int i; + struct vc_data *vc; + struct fb_var_screeninfo var; + const struct fb_videomode *mode; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + if (fbcon_info_from_console(i) != info) + continue; + if (!fb_display[i].mode) + continue; + vc = vc_cons[i].d; + display_to_var(&var, &fb_display[i]); + mode = fb_find_nearest_mode(fb_display[i].mode, + &info->modelist); + fb_videomode_to_var(&var, mode); + fbcon_set_disp(info, &var, vc->vc_num); + } +} + +void fbcon_get_requirement(struct fb_info *info, + struct fb_blit_caps *caps) +{ + struct vc_data *vc; + + if (caps->flags) { + int i, charcnt; + + for (i = first_fb_vc; i <= last_fb_vc; i++) { + vc = vc_cons[i].d; + if (vc && vc->vc_mode == KD_TEXT && + info->node == con2fb_map[i]) { + caps->x |= 1 << (vc->vc_font.width - 1); + caps->y |= 1 << (vc->vc_font.height - 1); + charcnt = vc->vc_font.charcount; + if (caps->len < charcnt) + caps->len = charcnt; + } + } + } else { + vc = vc_cons[fg_console].d; + + if (vc && vc->vc_mode == KD_TEXT && + info->node == con2fb_map[fg_console]) { + caps->x = 1 << (vc->vc_font.width - 1); + caps->y = 1 << (vc->vc_font.height - 1); + caps->len = vc->vc_font.charcount; + } + } +} + +int fbcon_set_con2fb_map_ioctl(void __user *argp) +{ + struct fb_con2fbmap con2fb; + int ret; + + if (copy_from_user(&con2fb, argp, sizeof(con2fb))) + return -EFAULT; + if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) + return -EINVAL; + if (con2fb.framebuffer >= FB_MAX) + return -EINVAL; + if (!fbcon_registered_fb[con2fb.framebuffer]) + request_module("fb%d", con2fb.framebuffer); + if (!fbcon_registered_fb[con2fb.framebuffer]) { + return -EINVAL; + } + + console_lock(); + ret = set_con2fb_map(con2fb.console - 1, + con2fb.framebuffer, 1); + console_unlock(); + + return ret; +} + +int fbcon_get_con2fb_map_ioctl(void __user *argp) +{ + struct fb_con2fbmap con2fb; + + if (copy_from_user(&con2fb, argp, sizeof(con2fb))) + return -EFAULT; + if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) + return -EINVAL; + + console_lock(); + con2fb.framebuffer = con2fb_map[con2fb.console - 1]; + console_unlock(); + + return copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0; +} + +/* + * The console `switch' structure for the frame buffer based console + */ + +static const struct consw fb_con = { + .owner = THIS_MODULE, + .con_startup = fbcon_startup, + .con_init = fbcon_init, + .con_deinit = fbcon_deinit, + .con_clear = fbcon_clear, + .con_putc = fbcon_putc, + .con_putcs = fbcon_putcs, + .con_cursor = fbcon_cursor, + .con_scroll = fbcon_scroll, + .con_switch = fbcon_switch, + .con_blank = fbcon_blank, + .con_font_set = fbcon_set_font, + .con_font_get = fbcon_get_font, + .con_font_default = fbcon_set_def_font, + .con_set_palette = fbcon_set_palette, + .con_invert_region = fbcon_invert_region, + .con_screen_pos = fbcon_screen_pos, + .con_getxy = fbcon_getxy, + .con_resize = fbcon_resize, + .con_debug_enter = fbcon_debug_enter, + .con_debug_leave = fbcon_debug_leave, +}; + +static ssize_t store_rotate(struct device *device, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct fb_info *info; + int rotate, idx; + char **last = NULL; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + rotate = simple_strtoul(buf, last, 0); + fbcon_rotate(info, rotate); +err: + console_unlock(); + return count; +} + +static ssize_t store_rotate_all(struct device *device, + struct device_attribute *attr,const char *buf, + size_t count) +{ + struct fb_info *info; + int rotate, idx; + char **last = NULL; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + rotate = simple_strtoul(buf, last, 0); + fbcon_rotate_all(info, rotate); +err: + console_unlock(); + return count; +} + +static ssize_t show_rotate(struct device *device, + struct device_attribute *attr,char *buf) +{ + struct fb_info *info; + int rotate = 0, idx; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + rotate = fbcon_get_rotate(info); +err: + console_unlock(); + return sysfs_emit(buf, "%d\n", rotate); +} + +static ssize_t show_cursor_blink(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info; + struct fbcon_ops *ops; + int idx, blink = -1; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + ops = info->fbcon_par; + + if (!ops) + goto err; + + blink = delayed_work_pending(&ops->cursor_work); +err: + console_unlock(); + return sysfs_emit(buf, "%d\n", blink); +} + +static ssize_t store_cursor_blink(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info; + int blink, idx; + char **last = NULL; + + console_lock(); + idx = con2fb_map[fg_console]; + + if (idx == -1 || fbcon_registered_fb[idx] == NULL) + goto err; + + info = fbcon_registered_fb[idx]; + + if (!info->fbcon_par) + goto err; + + blink = simple_strtoul(buf, last, 0); + + if (blink) { + fbcon_cursor_noblink = 0; + fbcon_add_cursor_work(info); + } else { + fbcon_cursor_noblink = 1; + fbcon_del_cursor_work(info); + } + +err: + console_unlock(); + return count; +} + +static struct device_attribute device_attrs[] = { + __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate), + __ATTR(rotate_all, S_IWUSR, NULL, store_rotate_all), + __ATTR(cursor_blink, S_IRUGO|S_IWUSR, show_cursor_blink, + store_cursor_blink), +}; + +static int fbcon_init_device(void) +{ + int i, error = 0; + + fbcon_has_sysfs = 1; + + for (i = 0; i < ARRAY_SIZE(device_attrs); i++) { + error = device_create_file(fbcon_device, &device_attrs[i]); + + if (error) + break; + } + + if (error) { + while (--i >= 0) + device_remove_file(fbcon_device, &device_attrs[i]); + + fbcon_has_sysfs = 0; + } + + return 0; +} + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER +static void fbcon_register_existing_fbs(struct work_struct *work) +{ + int i; + + console_lock(); + + deferred_takeover = false; + logo_shown = FBCON_LOGO_DONTSHOW; + + fbcon_for_each_registered_fb(i) + do_fb_registered(fbcon_registered_fb[i]); + + console_unlock(); +} + +static struct notifier_block fbcon_output_nb; +static DECLARE_WORK(fbcon_deferred_takeover_work, fbcon_register_existing_fbs); + +static int fbcon_output_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + WARN_CONSOLE_UNLOCKED(); + + pr_info("fbcon: Taking over console\n"); + + dummycon_unregister_output_notifier(&fbcon_output_nb); + + /* We may get called in atomic context */ + schedule_work(&fbcon_deferred_takeover_work); + + return NOTIFY_OK; +} +#endif + +static void fbcon_start(void) +{ + WARN_CONSOLE_UNLOCKED(); + +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER + if (conswitchp != &dummy_con) + deferred_takeover = false; + + if (deferred_takeover) { + fbcon_output_nb.notifier_call = fbcon_output_notifier; + dummycon_register_output_notifier(&fbcon_output_nb); + return; + } +#endif +} + +void __init fb_console_init(void) +{ + int i; + + console_lock(); + fbcon_device = device_create(fb_class, NULL, MKDEV(0, 0), NULL, + "fbcon"); + + if (IS_ERR(fbcon_device)) { + printk(KERN_WARNING "Unable to create device " + "for fbcon; errno = %ld\n", + PTR_ERR(fbcon_device)); + fbcon_device = NULL; + } else + fbcon_init_device(); + + for (i = 0; i < MAX_NR_CONSOLES; i++) + con2fb_map[i] = -1; + + fbcon_start(); + console_unlock(); +} + +#ifdef MODULE + +static void __exit fbcon_deinit_device(void) +{ + int i; + + if (fbcon_has_sysfs) { + for (i = 0; i < ARRAY_SIZE(device_attrs); i++) + device_remove_file(fbcon_device, &device_attrs[i]); + + fbcon_has_sysfs = 0; + } +} + +void __exit fb_console_exit(void) +{ +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER + console_lock(); + if (deferred_takeover) + dummycon_unregister_output_notifier(&fbcon_output_nb); + console_unlock(); + + cancel_work_sync(&fbcon_deferred_takeover_work); +#endif + + console_lock(); + fbcon_deinit_device(); + device_destroy(fb_class, MKDEV(0, 0)); + + do_unregister_con_driver(&fb_con); + console_unlock(); +} +#endif diff --git a/drivers/video/fbdev/core/fbcon.h b/drivers/video/fbdev/core/fbcon.h new file mode 100644 index 000000000..0eaf54a21 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon.h @@ -0,0 +1,269 @@ +/* + * 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 +#include +#include +#include + +#include + + /* + * 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 */ + const u_char *fontdata; + int userfont; /* != 0 if fontdata kmalloc()ed */ +#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION + u_short scrollmode; /* Scroll Method, use fb_scrollmode() */ +#endif + u_short inverse; /* != 0 text black on white as default */ + 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_ops { + 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); + 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, int mode, + int fg, int bg); + int (*update_start)(struct fb_info *info); + int (*rotate_font)(struct fb_info *info, struct vc_data *vc); + 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; + int save_graphics; /* for debug enter/leave */ + bool initialized; + int rotate; + int cur_rotate; + char *cursor_data; + u8 *fontbuffer; + u8 *fontdata; + u8 *cursor_src; + u32 cursor_size; + u32 fd_size; +}; + /* + * 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; +} + +static inline int attr_col_ec(int shift, struct vc_data *vc, + struct fb_info *info, int is_fg) +{ + int is_mono01; + int col; + int fg; + int bg; + + if (!vc) + return 0; + + if (vc->vc_can_do_color) + return is_fg ? attr_fgcol(shift,vc->vc_video_erase_char) + : attr_bgcol(shift,vc->vc_video_erase_char); + + if (!info) + return 0; + + col = mono_col(info); + is_mono01 = info->fix.visual == FB_VISUAL_MONO01; + + if (attr_reverse(vc->vc_video_erase_char)) { + fg = is_mono01 ? col : 0; + bg = is_mono01 ? 0 : col; + } + else { + fg = is_mono01 ? 0 : col; + bg = is_mono01 ? col : 0; + } + + return is_fg ? fg : bg; +} + +#define attr_bgcol_ec(bgshift, vc, info) attr_col_ec(bgshift, vc, info, 0) +#define attr_fgcol_ec(fgshift, vc, info) attr_col_ec(fgshift, vc, info, 1) + + /* + * 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(struct fbcon_ops *ops); +extern int soft_cursor(struct fb_info *info, struct fb_cursor *cursor); + +#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_ROTATION +extern void fbcon_set_rotate(struct fbcon_ops *ops); +#else +#define fbcon_set_rotate(x) do {} while(0) +#endif /* CONFIG_FRAMEBUFFER_CONSOLE_ROTATION */ + +#endif /* _VIDEO_FBCON_H */ diff --git a/drivers/video/fbdev/core/fbcon_ccw.c b/drivers/video/fbdev/core/fbcon_ccw.c new file mode 100644 index 000000000..2789ace79 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_ccw.c @@ -0,0 +1,411 @@ +/* + * linux/drivers/video/console/fbcon_ccw.c -- Software Rotation - 270 degrees + * + * Copyright (C) 2005 Antonino Daplas + * + * 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 +#include +#include +#include +#include +#include +#include +#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 = (vc->vc_font.height + 7) >> 3; + 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_ops *ops = info->fbcon_par; + struct fb_copyarea area; + u32 vyres = GETVYRES(ops->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) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + u32 vyres = GETVYRES(ops->p, info); + + region.color = attr_bgcol_ec(bgshift,vc,info); + 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, ®ion); +} + +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_ops *ops = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = (vc->vc_font.height + 7) >> 3; + u8 *src; + + while (cnt--) { + src = ops->fontbuffer + (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_ops *ops = info->fbcon_par; + u32 width = (vc->vc_font.height + 7)/8; + 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(ops->p, info); + + if (!ops->fontbuffer) + 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, ®ion); + } + + 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, ®ion); + } +} + +static void ccw_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.height + 7) >> 3, c; + int y = real_y(ops->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; + u32 vyres = GETVYRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); + + if (ops->cursor_state.image.data != src || + ops->cursor_reset) { + ops->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(ops->cursor_data); + ops->cursor_data = dst; + ccw_update_attr(dst, src, attribute, vc); + src = dst; + } + + if (ops->cursor_state.image.fg_color != fg || + ops->cursor_state.image.bg_color != bg || + ops->cursor_reset) { + ops->cursor_state.image.fg_color = fg; + ops->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + + if (ops->cursor_state.image.height != vc->vc_font.width || + ops->cursor_state.image.width != vc->vc_font.height || + ops->cursor_reset) { + ops->cursor_state.image.height = vc->vc_font.width; + ops->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 (ops->cursor_state.image.dx != dx || + ops->cursor_state.image.dy != dy || + ops->cursor_reset) { + ops->cursor_state.image.dx = dx; + ops->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + + if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || + ops->cursor_reset) { + ops->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || + vc->vc_cursor_type != ops->p->cursor_shape || + ops->cursor_state.mask == NULL || + ops->cursor_reset) { + char *tmp, *mask = kmalloc_array(w, vc->vc_font.width, + GFP_ATOMIC); + int cur_height, size, i = 0; + int width = (vc->vc_font.width + 7)/8; + + if (!mask) + return; + + tmp = kmalloc_array(width, vc->vc_font.height, GFP_ATOMIC); + + if (!tmp) { + kfree(mask); + return; + } + + kfree(ops->cursor_state.mask); + ops->cursor_state.mask = mask; + + ops->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + + switch (CUR_SIZE(ops->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; + case CUR_UNDERLINE: + cur_height = (vc->vc_font.height < 10) ? 1 : 2; + break; + case CUR_LOWER_THIRD: + cur_height = vc->vc_font.height/3; + break; + case CUR_LOWER_HALF: + cur_height = vc->vc_font.height >> 1; + break; + case CUR_TWO_THIRDS: + cur_height = (vc->vc_font.height << 1)/3; + break; + case CUR_BLOCK: + default: + cur_height = vc->vc_font.height; + break; + } + + size = (vc->vc_font.height - cur_height) * width; + while (size--) + tmp[i++] = 0; + size = cur_height * width; + while (size--) + tmp[i++] = 0xff; + memset(mask, 0, w * vc->vc_font.width); + rotate_ccw(tmp, mask, vc->vc_font.width, vc->vc_font.height); + kfree(tmp); + } + + switch (mode) { + case CM_ERASE: + ops->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: + ops->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; + cursor.image.fg_color = ops->cursor_state.image.fg_color; + cursor.image.bg_color = ops->cursor_state.image.bg_color; + cursor.image.dx = ops->cursor_state.image.dx; + cursor.image.dy = ops->cursor_state.image.dy; + cursor.image.height = ops->cursor_state.image.height; + cursor.image.width = ops->cursor_state.image.width; + cursor.hot.x = ops->cursor_state.hot.x; + cursor.hot.y = ops->cursor_state.hot.y; + cursor.mask = ops->cursor_state.mask; + cursor.enable = ops->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); + + ops->cursor_reset = 0; +} + +static int ccw_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + u32 yoffset; + u32 vyres = GETVYRES(ops->p, info); + int err; + + yoffset = (vyres - info->var.yres) - ops->var.xoffset; + ops->var.xoffset = ops->var.yoffset; + ops->var.yoffset = yoffset; + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_rotate_ccw(struct fbcon_ops *ops) +{ + ops->bmove = ccw_bmove; + ops->clear = ccw_clear; + ops->putcs = ccw_putcs; + ops->clear_margins = ccw_clear_margins; + ops->cursor = ccw_cursor; + ops->update_start = ccw_update_start; +} diff --git a/drivers/video/fbdev/core/fbcon_cw.c b/drivers/video/fbdev/core/fbcon_cw.c new file mode 100644 index 000000000..86a254c1b --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_cw.c @@ -0,0 +1,394 @@ +/* + * linux/drivers/video/console/fbcon_ud.c -- Software Rotation - 90 degrees + * + * Copyright (C) 2005 Antonino Daplas + * + * 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 +#include +#include +#include +#include +#include +#include +#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 = (vc->vc_font.height + 7) >> 3; + 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_ops *ops = info->fbcon_par; + struct fb_copyarea area; + u32 vxres = GETVXRES(ops->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) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + u32 vxres = GETVXRES(ops->p, info); + + region.color = attr_bgcol_ec(bgshift,vc,info); + 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, ®ion); +} + +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_ops *ops = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = (vc->vc_font.height + 7) >> 3; + u8 *src; + + while (cnt--) { + src = ops->fontbuffer + (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_ops *ops = info->fbcon_par; + u32 width = (vc->vc_font.height + 7)/8; + 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(ops->p, info); + + if (!ops->fontbuffer) + 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, ®ion); + } + + 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, ®ion); + } +} + +static void cw_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.height + 7) >> 3, c; + int y = real_y(ops->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; + u32 vxres = GETVXRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width)); + + if (ops->cursor_state.image.data != src || + ops->cursor_reset) { + ops->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(ops->cursor_data); + ops->cursor_data = dst; + cw_update_attr(dst, src, attribute, vc); + src = dst; + } + + if (ops->cursor_state.image.fg_color != fg || + ops->cursor_state.image.bg_color != bg || + ops->cursor_reset) { + ops->cursor_state.image.fg_color = fg; + ops->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + + if (ops->cursor_state.image.height != vc->vc_font.width || + ops->cursor_state.image.width != vc->vc_font.height || + ops->cursor_reset) { + ops->cursor_state.image.height = vc->vc_font.width; + ops->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 (ops->cursor_state.image.dx != dx || + ops->cursor_state.image.dy != dy || + ops->cursor_reset) { + ops->cursor_state.image.dx = dx; + ops->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + + if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || + ops->cursor_reset) { + ops->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || + vc->vc_cursor_type != ops->p->cursor_shape || + ops->cursor_state.mask == NULL || + ops->cursor_reset) { + char *tmp, *mask = kmalloc_array(w, vc->vc_font.width, + GFP_ATOMIC); + int cur_height, size, i = 0; + int width = (vc->vc_font.width + 7)/8; + + if (!mask) + return; + + tmp = kmalloc_array(width, vc->vc_font.height, GFP_ATOMIC); + + if (!tmp) { + kfree(mask); + return; + } + + kfree(ops->cursor_state.mask); + ops->cursor_state.mask = mask; + + ops->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + + switch (CUR_SIZE(ops->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; + case CUR_UNDERLINE: + cur_height = (vc->vc_font.height < 10) ? 1 : 2; + break; + case CUR_LOWER_THIRD: + cur_height = vc->vc_font.height/3; + break; + case CUR_LOWER_HALF: + cur_height = vc->vc_font.height >> 1; + break; + case CUR_TWO_THIRDS: + cur_height = (vc->vc_font.height << 1)/3; + break; + case CUR_BLOCK: + default: + cur_height = vc->vc_font.height; + break; + } + + size = (vc->vc_font.height - cur_height) * width; + while (size--) + tmp[i++] = 0; + size = cur_height * width; + while (size--) + tmp[i++] = 0xff; + memset(mask, 0, w * vc->vc_font.width); + rotate_cw(tmp, mask, vc->vc_font.width, vc->vc_font.height); + kfree(tmp); + } + + switch (mode) { + case CM_ERASE: + ops->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: + ops->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; + cursor.image.fg_color = ops->cursor_state.image.fg_color; + cursor.image.bg_color = ops->cursor_state.image.bg_color; + cursor.image.dx = ops->cursor_state.image.dx; + cursor.image.dy = ops->cursor_state.image.dy; + cursor.image.height = ops->cursor_state.image.height; + cursor.image.width = ops->cursor_state.image.width; + cursor.hot.x = ops->cursor_state.hot.x; + cursor.hot.y = ops->cursor_state.hot.y; + cursor.mask = ops->cursor_state.mask; + cursor.enable = ops->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); + + ops->cursor_reset = 0; +} + +static int cw_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + u32 vxres = GETVXRES(ops->p, info); + u32 xoffset; + int err; + + xoffset = vxres - (info->var.xres + ops->var.yoffset); + ops->var.yoffset = ops->var.xoffset; + ops->var.xoffset = xoffset; + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_rotate_cw(struct fbcon_ops *ops) +{ + ops->bmove = cw_bmove; + ops->clear = cw_clear; + ops->putcs = cw_putcs; + ops->clear_margins = cw_clear_margins; + ops->cursor = cw_cursor; + ops->update_start = cw_update_start; +} diff --git a/drivers/video/fbdev/core/fbcon_rotate.c b/drivers/video/fbdev/core/fbcon_rotate.c new file mode 100644 index 000000000..ec3c88340 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_rotate.c @@ -0,0 +1,111 @@ +/* + * linux/drivers/video/console/fbcon_rotate.c -- Software Rotation + * + * Copyright (C) 2005 Antonino Daplas + * + * 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 +#include +#include +#include +#include +#include +#include +#include "fbcon.h" +#include "fbcon_rotate.h" + +static int fbcon_rotate_font(struct fb_info *info, struct vc_data *vc) +{ + struct fbcon_ops *ops = info->fbcon_par; + int len, err = 0; + int s_cellsize, d_cellsize, i; + const u8 *src; + u8 *dst; + + if (vc->vc_font.data == ops->fontdata && + ops->p->con_rotate == ops->cur_rotate) + goto finished; + + src = ops->fontdata = vc->vc_font.data; + ops->cur_rotate = ops->p->con_rotate; + len = vc->vc_font.charcount; + s_cellsize = ((vc->vc_font.width + 7)/8) * + vc->vc_font.height; + d_cellsize = s_cellsize; + + if (ops->rotate == FB_ROTATE_CW || + ops->rotate == FB_ROTATE_CCW) + d_cellsize = ((vc->vc_font.height + 7)/8) * + vc->vc_font.width; + + if (info->fbops->fb_sync) + info->fbops->fb_sync(info); + + if (ops->fd_size < d_cellsize * len) { + dst = kmalloc_array(len, d_cellsize, GFP_KERNEL); + + if (dst == NULL) { + err = -ENOMEM; + goto finished; + } + + ops->fd_size = d_cellsize * len; + kfree(ops->fontbuffer); + ops->fontbuffer = dst; + } + + dst = ops->fontbuffer; + memset(dst, 0, ops->fd_size); + + switch (ops->rotate) { + case FB_ROTATE_UD: + for (i = len; i--; ) { + rotate_ud(src, dst, vc->vc_font.width, + vc->vc_font.height); + + src += s_cellsize; + dst += d_cellsize; + } + break; + case FB_ROTATE_CW: + for (i = len; i--; ) { + rotate_cw(src, dst, vc->vc_font.width, + vc->vc_font.height); + src += s_cellsize; + dst += d_cellsize; + } + break; + case FB_ROTATE_CCW: + for (i = len; i--; ) { + rotate_ccw(src, dst, vc->vc_font.width, + vc->vc_font.height); + src += s_cellsize; + dst += d_cellsize; + } + break; + } + +finished: + return err; +} + +void fbcon_set_rotate(struct fbcon_ops *ops) +{ + ops->rotate_font = fbcon_rotate_font; + + switch(ops->rotate) { + case FB_ROTATE_CW: + fbcon_rotate_cw(ops); + break; + case FB_ROTATE_UD: + fbcon_rotate_ud(ops); + break; + case FB_ROTATE_CCW: + fbcon_rotate_ccw(ops); + break; + } +} diff --git a/drivers/video/fbdev/core/fbcon_rotate.h b/drivers/video/fbdev/core/fbcon_rotate.h new file mode 100644 index 000000000..01cbe303b --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_rotate.h @@ -0,0 +1,96 @@ +/* + * linux/drivers/video/console/fbcon_rotate.h -- Software Display Rotation + * + * Copyright (C) 2005 Antonino Daplas + * + * 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; }) + + +static inline int pattern_test_bit(u32 x, u32 y, u32 pitch, const char *pat) +{ + u32 tmp = (y * pitch) + x, index = tmp / 8, bit = tmp % 8; + + pat +=index; + return (*pat) & (0x80 >> bit); +} + +static inline void pattern_set_bit(u32 x, u32 y, u32 pitch, char *pat) +{ + u32 tmp = (y * pitch) + x, index = tmp / 8, bit = tmp % 8; + + pat += index; + + (*pat) |= 0x80 >> bit; +} + +static inline void rotate_ud(const char *in, char *out, u32 width, u32 height) +{ + int i, j; + int shift = (8 - (width % 8)) & 7; + + width = (width + 7) & ~7; + + for (i = 0; i < height; i++) { + for (j = 0; j < width - shift; j++) { + if (pattern_test_bit(j, i, width, in)) + pattern_set_bit(width - (1 + j + shift), + height - (1 + i), + width, out); + } + + } +} + +static inline void rotate_cw(const char *in, char *out, u32 width, u32 height) +{ + int i, j, h = height, w = width; + int shift = (8 - (height % 8)) & 7; + + width = (width + 7) & ~7; + height = (height + 7) & ~7; + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + if (pattern_test_bit(j, i, width, in)) + pattern_set_bit(height - 1 - i - shift, j, + height, out); + + } + } +} + +static inline void rotate_ccw(const char *in, char *out, u32 width, u32 height) +{ + int i, j, h = height, w = width; + int shift = (8 - (width % 8)) & 7; + + width = (width + 7) & ~7; + height = (height + 7) & ~7; + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + if (pattern_test_bit(j, i, width, in)) + pattern_set_bit(i, width - 1 - j - shift, + height, out); + } + } +} + +extern void fbcon_rotate_cw(struct fbcon_ops *ops); +extern void fbcon_rotate_ud(struct fbcon_ops *ops); +extern void fbcon_rotate_ccw(struct fbcon_ops *ops); +#endif diff --git a/drivers/video/fbdev/core/fbcon_ud.c b/drivers/video/fbdev/core/fbcon_ud.c new file mode 100644 index 000000000..23bc04576 --- /dev/null +++ b/drivers/video/fbdev/core/fbcon_ud.c @@ -0,0 +1,438 @@ +/* + * linux/drivers/video/console/fbcon_ud.c -- Software Rotation - 180 degrees + * + * Copyright (C) 2005 Antonino Daplas + * + * 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 +#include +#include +#include +#include +#include +#include +#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 = (vc->vc_font.width + 7) >> 3; + 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_ops *ops = info->fbcon_par; + struct fb_copyarea area; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->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) +{ + struct fbcon_ops *ops = info->fbcon_par; + struct fb_fillrect region; + int bgshift = (vc->vc_hi_font_mask) ? 13 : 12; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + + region.color = attr_bgcol_ec(bgshift,vc,info); + 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, ®ion); +} + +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_ops *ops = info->fbcon_par; + u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + u32 idx = vc->vc_font.width >> 3; + u8 *src; + + while (cnt--) { + src = ops->fontbuffer + (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_ops *ops = 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 = ops->fontbuffer + (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_ops *ops = info->fbcon_par; + u32 width = (vc->vc_font.width + 7)/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; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + + if (!ops->fontbuffer) + 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, ®ion); + } + + 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, ®ion); + } +} + +static void ud_cursor(struct vc_data *vc, struct fb_info *info, int mode, + int fg, int bg) +{ + struct fb_cursor cursor; + struct fbcon_ops *ops = info->fbcon_par; + unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff; + int w = (vc->vc_font.width + 7) >> 3, c; + int y = real_y(ops->p, vc->state.y); + int attribute, use_sw = vc->vc_cursor_type & CUR_SW; + int err = 1, dx, dy; + char *src; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + + if (!ops->fontbuffer) + return; + + cursor.set = 0; + + c = scr_readw((u16 *) vc->vc_pos); + attribute = get_attribute(info, c); + src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.height)); + + if (ops->cursor_state.image.data != src || + ops->cursor_reset) { + ops->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(ops->cursor_data); + ops->cursor_data = dst; + ud_update_attr(dst, src, attribute, vc); + src = dst; + } + + if (ops->cursor_state.image.fg_color != fg || + ops->cursor_state.image.bg_color != bg || + ops->cursor_reset) { + ops->cursor_state.image.fg_color = fg; + ops->cursor_state.image.bg_color = bg; + cursor.set |= FB_CUR_SETCMAP; + } + + if (ops->cursor_state.image.height != vc->vc_font.height || + ops->cursor_state.image.width != vc->vc_font.width || + ops->cursor_reset) { + ops->cursor_state.image.height = vc->vc_font.height; + ops->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 (ops->cursor_state.image.dx != dx || + ops->cursor_state.image.dy != dy || + ops->cursor_reset) { + ops->cursor_state.image.dx = dx; + ops->cursor_state.image.dy = dy; + cursor.set |= FB_CUR_SETPOS; + } + + if (ops->cursor_state.hot.x || ops->cursor_state.hot.y || + ops->cursor_reset) { + ops->cursor_state.hot.x = cursor.hot.y = 0; + cursor.set |= FB_CUR_SETHOT; + } + + if (cursor.set & FB_CUR_SETSIZE || + vc->vc_cursor_type != ops->p->cursor_shape || + ops->cursor_state.mask == NULL || + ops->cursor_reset) { + char *mask = kmalloc_array(w, vc->vc_font.height, GFP_ATOMIC); + int cur_height, size, i = 0; + u8 msk = 0xff; + + if (!mask) + return; + + kfree(ops->cursor_state.mask); + ops->cursor_state.mask = mask; + + ops->p->cursor_shape = vc->vc_cursor_type; + cursor.set |= FB_CUR_SETSHAPE; + + switch (CUR_SIZE(ops->p->cursor_shape)) { + case CUR_NONE: + cur_height = 0; + break; + case CUR_UNDERLINE: + cur_height = (vc->vc_font.height < 10) ? 1 : 2; + break; + case CUR_LOWER_THIRD: + cur_height = vc->vc_font.height/3; + break; + case CUR_LOWER_HALF: + cur_height = vc->vc_font.height >> 1; + break; + case CUR_TWO_THIRDS: + cur_height = (vc->vc_font.height << 1)/3; + break; + case CUR_BLOCK: + default: + cur_height = vc->vc_font.height; + break; + } + + size = cur_height * w; + + while (size--) + mask[i++] = msk; + + size = (vc->vc_font.height - cur_height) * w; + + while (size--) + mask[i++] = ~msk; + } + + switch (mode) { + case CM_ERASE: + ops->cursor_state.enable = 0; + break; + case CM_DRAW: + case CM_MOVE: + default: + ops->cursor_state.enable = (use_sw) ? 0 : 1; + break; + } + + cursor.image.data = src; + cursor.image.fg_color = ops->cursor_state.image.fg_color; + cursor.image.bg_color = ops->cursor_state.image.bg_color; + cursor.image.dx = ops->cursor_state.image.dx; + cursor.image.dy = ops->cursor_state.image.dy; + cursor.image.height = ops->cursor_state.image.height; + cursor.image.width = ops->cursor_state.image.width; + cursor.hot.x = ops->cursor_state.hot.x; + cursor.hot.y = ops->cursor_state.hot.y; + cursor.mask = ops->cursor_state.mask; + cursor.enable = ops->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); + + ops->cursor_reset = 0; +} + +static int ud_update_start(struct fb_info *info) +{ + struct fbcon_ops *ops = info->fbcon_par; + int xoffset, yoffset; + u32 vyres = GETVYRES(ops->p, info); + u32 vxres = GETVXRES(ops->p, info); + int err; + + xoffset = vxres - info->var.xres - ops->var.xoffset; + yoffset = vyres - info->var.yres - ops->var.yoffset; + if (yoffset < 0) + yoffset += vyres; + ops->var.xoffset = xoffset; + ops->var.yoffset = yoffset; + err = fb_pan_display(info, &ops->var); + ops->var.xoffset = info->var.xoffset; + ops->var.yoffset = info->var.yoffset; + ops->var.vmode = info->var.vmode; + return err; +} + +void fbcon_rotate_ud(struct fbcon_ops *ops) +{ + ops->bmove = ud_bmove; + ops->clear = ud_clear; + ops->putcs = ud_putcs; + ops->clear_margins = ud_clear_margins; + ops->cursor = ud_cursor; + ops->update_start = ud_update_start; +} diff --git a/drivers/video/fbdev/core/fbcvt.c b/drivers/video/fbdev/core/fbcvt.c new file mode 100644 index 000000000..64843464c --- /dev/null +++ b/drivers/video/fbdev/core/fbcvt.c @@ -0,0 +1,368 @@ +/* + * linux/drivers/video/fbcvt.c - VESA(TM) Coordinated Video Timings + * + * Copyright (C) 2005 Antonino Daplas + * + * 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 +#include + +#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) { + 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; +} diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c new file mode 100644 index 000000000..ab3545a00 --- /dev/null +++ b/drivers/video/fbdev/core/fbmem.c @@ -0,0 +1,1866 @@ +/* + * linux/drivers/video/fbmem.c + * + * Copyright (C) 1994 Martin Schaller + * + * 2001 - Documented with DocBook + * - Brad Douglas + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include