diff options
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/Kconfig | 26 | ||||
-rw-r--r-- | drivers/video/Makefile | 5 | ||||
-rw-r--r-- | drivers/video/console/fbcon.c | 58 | ||||
-rw-r--r-- | drivers/video/console/vgacon.c | 22 | ||||
-rw-r--r-- | drivers/video/display_timing.c | 24 | ||||
-rw-r--r-- | drivers/video/fbmem.c | 11 | ||||
-rw-r--r-- | drivers/video/fbmon.c | 94 | ||||
-rw-r--r-- | drivers/video/fbsysfs.c | 3 | ||||
-rw-r--r-- | drivers/video/hdmi.c | 308 | ||||
-rw-r--r-- | drivers/video/of_display_timing.c | 239 | ||||
-rw-r--r-- | drivers/video/of_videomode.c | 54 | ||||
-rw-r--r-- | drivers/video/via/hw.c | 6 | ||||
-rw-r--r-- | drivers/video/via/hw.h | 2 | ||||
-rw-r--r-- | drivers/video/via/lcd.c | 2 | ||||
-rw-r--r-- | drivers/video/via/share.h | 2 | ||||
-rw-r--r-- | drivers/video/via/via_modesetting.c | 8 | ||||
-rw-r--r-- | drivers/video/via/via_modesetting.h | 6 | ||||
-rw-r--r-- | drivers/video/videomode.c | 39 |
18 files changed, 874 insertions, 35 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 80cbd21b483..4c1546f71d5 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -21,8 +21,6 @@ source "drivers/gpu/vga/Kconfig" source "drivers/gpu/drm/Kconfig" -source "drivers/gpu/stub/Kconfig" - config VGASTATE tristate default n @@ -33,6 +31,30 @@ config VIDEO_OUTPUT_CONTROL This framework adds support for low-level control of the video output switch. +config DISPLAY_TIMING + bool + +config VIDEOMODE + bool + +config OF_DISPLAY_TIMING + bool "Enable device tree display timing support" + depends on OF + select DISPLAY_TIMING + help + helper to parse display timings from the devicetree + +config OF_VIDEOMODE + bool "Enable device tree videomode support" + depends on OF + select VIDEOMODE + select OF_DISPLAY_TIMING + help + helper to get videomodes from the devicetree + +config HDMI + bool + menuconfig FB tristate "Support for frame buffer devices" ---help--- diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 0577f834fdc..9df387334cb 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -5,6 +5,7 @@ # Each configuration option enables a list of files. obj-$(CONFIG_VGASTATE) += vgastate.o +obj-$(CONFIG_HDMI) += hdmi.o obj-y += fb_notify.o obj-$(CONFIG_FB) += fb.o fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ @@ -170,3 +171,7 @@ obj-$(CONFIG_FB_VIRTUAL) += vfb.o #video output switch sysfs driver obj-$(CONFIG_VIDEO_OUTPUT_CONTROL) += output.o +obj-$(CONFIG_DISPLAY_TIMING) += display_timing.o +obj-$(CONFIG_OF_DISPLAY_TIMING) += of_display_timing.o +obj-$(CONFIG_VIDEOMODE) += videomode.o +obj-$(CONFIG_OF_VIDEOMODE) += of_videomode.o diff --git a/drivers/video/console/fbcon.c b/drivers/video/console/fbcon.c index f8a61e210d2..3cd67592782 100644 --- a/drivers/video/console/fbcon.c +++ b/drivers/video/console/fbcon.c @@ -529,6 +529,33 @@ static int search_for_mapped_con(void) return retval; } +static int do_fbcon_takeover(int show_logo) +{ + int err, i; + + if (!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; +} + static int fbcon_takeover(int show_logo) { int err, i; @@ -815,6 +842,8 @@ static void con2fb_init_display(struct vc_data *vc, struct fb_info *info, * * 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) { @@ -832,7 +861,7 @@ static int set_con2fb_map(int unit, int newidx, int user) if (!search_for_mapped_con() || !con_is_bound(&fb_con)) { info_idx = newidx; - return fbcon_takeover(0); + return do_fbcon_takeover(0); } if (oldidx != -1) @@ -840,7 +869,6 @@ static int set_con2fb_map(int unit, int newidx, int user) found = search_fb_in_map(newidx); - console_lock(); con2fb_map[unit] = newidx; if (!err && !found) err = con2fb_acquire_newinfo(vc, info, unit, oldidx); @@ -867,7 +895,6 @@ static int set_con2fb_map(int unit, int newidx, int user) if (!search_fb_in_map(info_idx)) info_idx = newidx; - console_unlock(); return err; } @@ -990,7 +1017,7 @@ static const char *fbcon_startup(void) } /* Setup default font */ - if (!p->fontdata) { + if (!p->fontdata && !vc->vc_font.data) { if (!fontname[0] || !(font = find_font(fontname))) font = get_default_font(info->var.xres, info->var.yres, @@ -1000,6 +1027,8 @@ static const char *fbcon_startup(void) vc->vc_font.height = font->height; vc->vc_font.data = (void *)(p->fontdata = font->data); vc->vc_font.charcount = 256; /* FIXME Need to support more fonts */ + } else { + p->fontdata = vc->vc_font.data; } cols = FBCON_SWAP(ops->rotate, info->var.xres, info->var.yres); @@ -1159,9 +1188,9 @@ static void fbcon_init(struct vc_data *vc, int init) ops->p = &fb_display[fg_console]; } -static void fbcon_free_font(struct display *p) +static void fbcon_free_font(struct display *p, bool freefont) { - if (p->userfont && p->fontdata && (--REFCOUNT(p->fontdata) == 0)) + if (freefont && p->userfont && p->fontdata && (--REFCOUNT(p->fontdata) == 0)) kfree(p->fontdata - FONT_EXTRA_WORDS * sizeof(int)); p->fontdata = NULL; p->userfont = 0; @@ -1173,8 +1202,8 @@ static void fbcon_deinit(struct vc_data *vc) struct fb_info *info; struct fbcon_ops *ops; int idx; + bool free_font = true; - fbcon_free_font(p); idx = con2fb_map[vc->vc_num]; if (idx == -1) @@ -1185,6 +1214,8 @@ static void fbcon_deinit(struct vc_data *vc) if (!info) goto finished; + if (info->flags & FBINFO_MISC_FIRMWARE) + free_font = false; ops = info->fbcon_par; if (!ops) @@ -1196,6 +1227,8 @@ static void fbcon_deinit(struct vc_data *vc) ops->flags &= ~FBCON_FLAGS_INIT; finished: + fbcon_free_font(p, free_font); + if (!con_is_bound(&fb_con)) fbcon_exit(); @@ -2985,7 +3018,7 @@ static int fbcon_unbind(void) { int ret; - ret = unbind_con_driver(&fb_con, first_fb_vc, last_fb_vc, + ret = do_unbind_con_driver(&fb_con, first_fb_vc, last_fb_vc, fbcon_is_default); if (!ret) @@ -3000,6 +3033,7 @@ static inline int fbcon_unbind(void) } #endif /* CONFIG_VT_HW_CONSOLE_BINDING */ +/* called with console_lock held */ static int fbcon_fb_unbind(int idx) { int i, new_idx = -1, ret = 0; @@ -3026,6 +3060,7 @@ static int fbcon_fb_unbind(int idx) return ret; } +/* called with console_lock held */ static int fbcon_fb_unregistered(struct fb_info *info) { int i, idx; @@ -3058,11 +3093,12 @@ static int fbcon_fb_unregistered(struct fb_info *info) primary_device = -1; if (!num_registered_fb) - unregister_con_driver(&fb_con); + do_unregister_con_driver(&fb_con); return 0; } +/* called with console_lock held */ static void fbcon_remap_all(int idx) { int i; @@ -3107,6 +3143,7 @@ static inline void fbcon_select_primary(struct fb_info *info) } #endif /* CONFIG_FRAMEBUFFER_DETECT_PRIMARY */ +/* called with console_lock held */ static int fbcon_fb_registered(struct fb_info *info) { int ret = 0, i, idx; @@ -3123,7 +3160,7 @@ static int fbcon_fb_registered(struct fb_info *info) } if (info_idx != -1) - ret = fbcon_takeover(1); + ret = do_fbcon_takeover(1); } else { for (i = first_fb_vc; i <= last_fb_vc; i++) { if (con2fb_map_boot[i] == idx) @@ -3259,6 +3296,7 @@ static int fbcon_event_notify(struct notifier_block *self, ret = fbcon_fb_unregistered(info); break; case FB_EVENT_SET_CONSOLE_MAP: + /* called with console lock held */ con2fb = event->data; ret = set_con2fb_map(con2fb->console - 1, con2fb->framebuffer, 1); diff --git a/drivers/video/console/vgacon.c b/drivers/video/console/vgacon.c index d449a74d4a3..5855d17d19a 100644 --- a/drivers/video/console/vgacon.c +++ b/drivers/video/console/vgacon.c @@ -1064,7 +1064,7 @@ static int vgacon_do_font_op(struct vgastate *state,char *arg,int set,int ch512) unsigned short video_port_status = vga_video_port_reg + 6; int font_select = 0x00, beg, i; char *charmap; - + bool clear_attribs = false; if (vga_video_type != VIDEO_TYPE_EGAM) { charmap = (char *) VGA_MAP_MEM(colourmap, 0); beg = 0x0e; @@ -1169,12 +1169,6 @@ static int vgacon_do_font_op(struct vgastate *state,char *arg,int set,int ch512) /* if 512 char mode is already enabled don't re-enable it. */ if ((set) && (ch512 != vga_512_chars)) { - /* attribute controller */ - for (i = 0; i < MAX_NR_CONSOLES; i++) { - struct vc_data *c = vc_cons[i].d; - if (c && c->vc_sw == &vga_con) - c->vc_hi_font_mask = ch512 ? 0x0800 : 0; - } vga_512_chars = ch512; /* 256-char: enable intensity bit 512-char: disable intensity bit */ @@ -1185,8 +1179,22 @@ static int vgacon_do_font_op(struct vgastate *state,char *arg,int set,int ch512) it means, but it works, and it appears necessary */ inb_p(video_port_status); vga_wattr(state->vgabase, VGA_AR_ENABLE_DISPLAY, 0); + clear_attribs = true; } raw_spin_unlock_irq(&vga_lock); + + if (clear_attribs) { + for (i = 0; i < MAX_NR_CONSOLES; i++) { + struct vc_data *c = vc_cons[i].d; + if (c && c->vc_sw == &vga_con) { + /* force hi font mask to 0, so we always clear + the bit on either transition */ + c->vc_hi_font_mask = 0x00; + clear_buffer_attributes(c); + c->vc_hi_font_mask = ch512 ? 0x0800 : 0; + } + } + } return 0; } diff --git a/drivers/video/display_timing.c b/drivers/video/display_timing.c new file mode 100644 index 00000000000..5e1822cef57 --- /dev/null +++ b/drivers/video/display_timing.c @@ -0,0 +1,24 @@ +/* + * generic display timing functions + * + * Copyright (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>, Pengutronix + * + * This file is released under the GPLv2 + */ + +#include <linux/export.h> +#include <linux/slab.h> +#include <video/display_timing.h> + +void display_timings_release(struct display_timings *disp) +{ + if (disp->timings) { + unsigned int i; + + for (i = 0; i < disp->num_timings; i++) + kfree(disp->timings[i]); + kfree(disp->timings); + } + kfree(disp); +} +EXPORT_SYMBOL_GPL(display_timings_release); diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c index 3ff0105a496..dc61c12ecf8 100644 --- a/drivers/video/fbmem.c +++ b/drivers/video/fbmem.c @@ -1177,8 +1177,10 @@ static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, event.data = &con2fb; if (!lock_fb_info(info)) return -ENODEV; + console_lock(); event.info = info; ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event); + console_unlock(); unlock_fb_info(info); break; case FBIOBLANK: @@ -1650,7 +1652,9 @@ static int do_register_framebuffer(struct fb_info *fb_info) event.info = fb_info; if (!lock_fb_info(fb_info)) return -ENODEV; + console_lock(); fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); + console_unlock(); unlock_fb_info(fb_info); return 0; } @@ -1666,8 +1670,10 @@ static int do_unregister_framebuffer(struct fb_info *fb_info) if (!lock_fb_info(fb_info)) return -ENODEV; + console_lock(); event.info = fb_info; ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event); + console_unlock(); unlock_fb_info(fb_info); if (ret) @@ -1682,7 +1688,9 @@ static int do_unregister_framebuffer(struct fb_info *fb_info) num_registered_fb--; fb_cleanup_device(fb_info); event.info = fb_info; + console_lock(); fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); + console_unlock(); /* this may free fb info */ put_fb_info(fb_info); @@ -1853,11 +1861,8 @@ int fb_new_modelist(struct fb_info *info) err = 1; if (!list_empty(&info->modelist)) { - if (!lock_fb_info(info)) - return -ENODEV; event.info = info; err = fb_notifier_call_chain(FB_EVENT_NEW_MODELIST, &event); - unlock_fb_info(info); } return err; diff --git a/drivers/video/fbmon.c b/drivers/video/fbmon.c index cef65574db6..94ad0f71383 100644 --- a/drivers/video/fbmon.c +++ b/drivers/video/fbmon.c @@ -31,6 +31,8 @@ #include <linux/pci.h> #include <linux/slab.h> #include <video/edid.h> +#include <video/of_videomode.h> +#include <video/videomode.h> #ifdef CONFIG_PPC_OF #include <asm/prom.h> #include <asm/pci-bridge.h> @@ -1373,6 +1375,98 @@ int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var, struct fb_inf kfree(timings); return err; } + +#if IS_ENABLED(CONFIG_VIDEOMODE) +int fb_videomode_from_videomode(const struct videomode *vm, + struct fb_videomode *fbmode) +{ + unsigned int htotal, vtotal; + + fbmode->xres = vm->hactive; + fbmode->left_margin = vm->hback_porch; + fbmode->right_margin = vm->hfront_porch; + fbmode->hsync_len = vm->hsync_len; + + fbmode->yres = vm->vactive; + fbmode->upper_margin = vm->vback_porch; + fbmode->lower_margin = vm->vfront_porch; + fbmode->vsync_len = vm->vsync_len; + + /* prevent division by zero in KHZ2PICOS macro */ + fbmode->pixclock = vm->pixelclock ? + KHZ2PICOS(vm->pixelclock / 1000) : 0; + + fbmode->sync = 0; + fbmode->vmode = 0; + if (vm->dmt_flags & VESA_DMT_HSYNC_HIGH) + fbmode->sync |= FB_SYNC_HOR_HIGH_ACT; + if (vm->dmt_flags & VESA_DMT_HSYNC_HIGH) + fbmode->sync |= FB_SYNC_VERT_HIGH_ACT; + if (vm->data_flags & DISPLAY_FLAGS_INTERLACED) + fbmode->vmode |= FB_VMODE_INTERLACED; + if (vm->data_flags & DISPLAY_FLAGS_DOUBLESCAN) + fbmode->vmode |= FB_VMODE_DOUBLE; + fbmode->flag = 0; + + htotal = vm->hactive + vm->hfront_porch + vm->hback_porch + + vm->hsync_len; + vtotal = vm->vactive + vm->vfront_porch + vm->vback_porch + + vm->vsync_len; + /* prevent division by zero */ + if (htotal && vtotal) { + fbmode->refresh = vm->pixelclock / (htotal * vtotal); + /* a mode must have htotal and vtotal != 0 or it is invalid */ + } else { + fbmode->refresh = 0; + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(fb_videomode_from_videomode); +#endif + +#if IS_ENABLED(CONFIG_OF_VIDEOMODE) +static inline void dump_fb_videomode(const struct fb_videomode *m) +{ + pr_debug("fb_videomode = %ux%u@%uHz (%ukHz) %u %u %u %u %u %u %u %u %u\n", + m->xres, m->yres, m->refresh, m->pixclock, m->left_margin, + m->right_margin, m->upper_margin, m->lower_margin, + m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); +} + +/** + * of_get_fb_videomode - get a fb_videomode from devicetree + * @np: device_node with the timing specification + * @fb: will be set to the return value + * @index: index into the list of display timings in devicetree + * + * DESCRIPTION: + * This function is expensive and should only be used, if only one mode is to be + * read from DT. To get multiple modes start with of_get_display_timings ond + * work with that instead. + */ +int of_get_fb_videomode(struct device_node *np, struct fb_videomode *fb, + int index) +{ + struct videomode vm; + int ret; + + ret = of_get_videomode(np, &vm, index); + if (ret) + return ret; + + fb_videomode_from_videomode(&vm, fb); + + pr_debug("%s: got %dx%d display mode from %s\n", + of_node_full_name(np), vm.hactive, vm.vactive, np->name); + dump_fb_videomode(fb); + + return 0; +} +EXPORT_SYMBOL_GPL(of_get_fb_videomode); +#endif + #else int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var) { diff --git a/drivers/video/fbsysfs.c b/drivers/video/fbsysfs.c index a55e3669d13..ef476b02fbe 100644 --- a/drivers/video/fbsysfs.c +++ b/drivers/video/fbsysfs.c @@ -177,6 +177,8 @@ static ssize_t store_modes(struct device *device, if (i * sizeof(struct fb_videomode) != count) return -EINVAL; + if (!lock_fb_info(fb_info)) + return -ENODEV; console_lock(); list_splice(&fb_info->modelist, &old_list); fb_videomode_to_modelist((const struct fb_videomode *)buf, i, @@ -188,6 +190,7 @@ static ssize_t store_modes(struct device *device, fb_destroy_modelist(&old_list); console_unlock(); + unlock_fb_info(fb_info); return 0; } diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c new file mode 100644 index 00000000000..ab23c9b7914 --- /dev/null +++ b/drivers/video/hdmi.c @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2012 Avionic Design GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/bitops.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/hdmi.h> +#include <linux/string.h> + +static void hdmi_infoframe_checksum(void *buffer, size_t size) +{ + u8 *ptr = buffer; + u8 csum = 0; + size_t i; + + /* compute checksum */ + for (i = 0; i < size; i++) + csum += ptr[i]; + + ptr[3] = 256 - csum; +} + +/** + * hdmi_avi_infoframe_init() - initialize an HDMI AVI infoframe + * @frame: HDMI AVI infoframe + * + * Returns 0 on success or a negative error code on failure. + */ +int hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame) +{ + memset(frame, 0, sizeof(*frame)); + + frame->type = HDMI_INFOFRAME_TYPE_AVI; + frame->version = 2; + frame->length = 13; + + return 0; +} +EXPORT_SYMBOL(hdmi_avi_infoframe_init); + +/** + * hdmi_avi_infoframe_pack() - write HDMI AVI infoframe to binary buffer + * @frame: HDMI AVI infoframe + * @buffer: destination buffer + * @size: size of buffer + * + * Packs the information contained in the @frame structure into a binary + * representation that can be written into the corresponding controller + * registers. Also computes the checksum as required by section 5.3.5 of + * the HDMI 1.4 specification. + * + * Returns the number of bytes packed into the binary buffer or a negative + * error code on failure. + */ +ssize_t hdmi_avi_infoframe_pack(struct hdmi_avi_infoframe *frame, void *buffer, + size_t size) +{ + u8 *ptr = buffer; + size_t length; + + length = HDMI_INFOFRAME_HEADER_SIZE + frame->length; + + if (size < length) + return -ENOSPC; + + memset(buffer, 0, length); + + ptr[0] = frame->type; + ptr[1] = frame->version; + ptr[2] = frame->length; + ptr[3] = 0; /* checksum */ + + /* start infoframe payload */ + ptr += HDMI_INFOFRAME_HEADER_SIZE; + + ptr[0] = ((frame->colorspace & 0x3) << 5) | (frame->scan_mode & 0x3); + + if (frame->active_info_valid) + ptr[0] |= BIT(4); + + if (frame->horizontal_bar_valid) + ptr[0] |= BIT(3); + + if (frame->vertical_bar_valid) + ptr[0] |= BIT(2); + + ptr[1] = ((frame->colorimetry & 0x3) << 6) | + ((frame->picture_aspect & 0x3) << 4) | + (frame->active_aspect & 0xf); + + ptr[2] = ((frame->extended_colorimetry & 0x7) << 4) | + ((frame->quantization_range & 0x3) << 2) | + (frame->nups & 0x3); + + if (frame->itc) + ptr[2] |= BIT(7); + + ptr[3] = frame->video_code & 0x7f; + + ptr[4] = ((frame->ycc_quantization_range & 0x3) << 6) | + ((frame->content_type & 0x3) << 4) | + (frame->pixel_repeat & 0xf); + + ptr[5] = frame->top_bar & 0xff; + ptr[6] = (frame->top_bar >> 8) & 0xff; + ptr[7] = frame->bottom_bar & 0xff; + ptr[8] = (frame->bottom_bar >> 8) & 0xff; + ptr[9] = frame->left_bar & 0xff; + ptr[10] = (frame->left_bar >> 8) & 0xff; + ptr[11] = frame->right_bar & 0xff; + ptr[12] = (frame->right_bar >> 8) & 0xff; + + hdmi_infoframe_checksum(buffer, length); + + return length; +} +EXPORT_SYMBOL(hdmi_avi_infoframe_pack); + +/** + * hdmi_spd_infoframe_init() - initialize an HDMI SPD infoframe + * @frame: HDMI SPD infoframe + * @vendor: vendor string + * @product: product string + * + * Returns 0 on success or a negative error code on failure. + */ +int hdmi_spd_infoframe_init(struct hdmi_spd_infoframe *frame, + const char *vendor, const char *product) +{ + memset(frame, 0, sizeof(*frame)); + + frame->type = HDMI_INFOFRAME_TYPE_SPD; + frame->version = 1; + frame->length = 25; + + strncpy(frame->vendor, vendor, sizeof(frame->vendor)); + strncpy(frame->product, product, sizeof(frame->product)); + + return 0; +} +EXPORT_SYMBOL(hdmi_spd_infoframe_init); + +/** + * hdmi_spd_infoframe_pack() - write HDMI SPD infoframe to binary buffer + * @frame: HDMI SPD infoframe + * @buffer: destination buffer + * @size: size of buffer + * + * Packs the information contained in the @frame structure into a binary + * representation that can be written into the corresponding controller + * registers. Also computes the checksum as required by section 5.3.5 of + * the HDMI 1.4 specification. + * + * Returns the number of bytes packed into the binary buffer or a negative + * error code on failure. + */ +ssize_t hdmi_spd_infoframe_pack(struct hdmi_spd_infoframe *frame, void *buffer, + size_t size) +{ + u8 *ptr = buffer; + size_t length; + + length = HDMI_INFOFRAME_HEADER_SIZE + frame->length; + + if (size < length) + return -ENOSPC; + + memset(buffer, 0, length); + + ptr[0] = frame->type; + ptr[1] = frame->version; + ptr[2] = frame->length; + ptr[3] = 0; /* checksum */ + + /* start infoframe payload */ + ptr += HDMI_INFOFRAME_HEADER_SIZE; + + memcpy(ptr, frame->vendor, sizeof(frame->vendor)); + memcpy(ptr + 8, frame->product, sizeof(frame->product)); + + ptr[24] = frame->sdi; + + hdmi_infoframe_checksum(buffer, length); + + return length; +} +EXPORT_SYMBOL(hdmi_spd_infoframe_pack); + +/** + * hdmi_audio_infoframe_init() - initialize an HDMI audio infoframe + * @frame: HDMI audio infoframe + * + * Returns 0 on success or a negative error code on failure. + */ +int hdmi_audio_infoframe_init(struct hdmi_audio_infoframe *frame) +{ + memset(frame, 0, sizeof(*frame)); + + frame->type = HDMI_INFOFRAME_TYPE_AUDIO; + frame->version = 1; + frame->length = 10; + + return 0; +} +EXPORT_SYMBOL(hdmi_audio_infoframe_init); + +/** + * hdmi_audio_infoframe_pack() - write HDMI audio infoframe to binary buffer + * @frame: HDMI audio infoframe + * @buffer: destination buffer + * @size: size of buffer + * + * Packs the information contained in the @frame structure into a binary + * representation that can be written into the corresponding controller + * registers. Also computes the checksum as required by section 5.3.5 of + * the HDMI 1.4 specification. + * + * Returns the number of bytes packed into the binary buffer or a negative + * error code on failure. + */ +ssize_t hdmi_audio_infoframe_pack(struct hdmi_audio_infoframe *frame, + void *buffer, size_t size) +{ + unsigned char channels; + u8 *ptr = buffer; + size_t length; + + length = HDMI_INFOFRAME_HEADER_SIZE + frame->length; + + if (size < length) + return -ENOSPC; + + memset(buffer, 0, length); + + if (frame->channels >= 2) + channels = frame->channels - 1; + else + channels = 0; + + ptr[0] = frame->type; + ptr[1] = frame->version; + ptr[2] = frame->length; + ptr[3] = 0; /* checksum */ + + /* start infoframe payload */ + ptr += HDMI_INFOFRAME_HEADER_SIZE; + + ptr[0] = ((frame->coding_type & 0xf) << 4) | (channels & 0x7); + ptr[1] = ((frame->sample_frequency & 0x7) << 2) | + (frame->sample_size & 0x3); + ptr[2] = frame->coding_type_ext & 0x1f; + ptr[3] = frame->channel_allocation; + ptr[4] = (frame->level_shift_value & 0xf) << 3; + + if (frame->downmix_inhibit) + ptr[4] |= BIT(7); + + hdmi_infoframe_checksum(buffer, length); + + return length; +} +EXPORT_SYMBOL(hdmi_audio_infoframe_pack); + +/** + * hdmi_vendor_infoframe_pack() - write a HDMI vendor infoframe to binary + * buffer + * @frame: HDMI vendor infoframe + * @buffer: destination buffer + * @size: size of buffer + * + * Packs the information contained in the @frame structure into a binary + * representation that can be written into the corresponding controller + * registers. Also computes the checksum as required by section 5.3.5 of + * the HDMI 1.4 specification. + * + * Returns the number of bytes packed into the binary buffer or a negative + * error code on failure. + */ +ssize_t hdmi_vendor_infoframe_pack(struct hdmi_vendor_infoframe *frame, + void *buffer, size_t size) +{ + u8 *ptr = buffer; + size_t length; + + length = HDMI_INFOFRAME_HEADER_SIZE + frame->length; + + if (size < length) + return -ENOSPC; + + memset(buffer, 0, length); + + ptr[0] = frame->type; + ptr[1] = frame->version; + ptr[2] = frame->length; + ptr[3] = 0; /* checksum */ + + memcpy(&ptr[HDMI_INFOFRAME_HEADER_SIZE], frame->data, frame->length); + + hdmi_infoframe_checksum(buffer, length); + + return length; +} +EXPORT_SYMBOL(hdmi_vendor_infoframe_pack); diff --git a/drivers/video/of_display_timing.c b/drivers/video/of_display_timing.c new file mode 100644 index 00000000000..13ecd989701 --- /dev/null +++ b/drivers/video/of_display_timing.c @@ -0,0 +1,239 @@ +/* + * OF helpers for parsing display timings + * + * Copyright (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>, Pengutronix + * + * based on of_videomode.c by Sascha Hauer <s.hauer@pengutronix.de> + * + * This file is released under the GPLv2 + */ +#include <linux/export.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> + +/** + * parse_timing_property - parse timing_entry from device_node + * @np: device_node with the property + * @name: name of the property + * @result: will be set to the return value + * + * DESCRIPTION: + * Every display_timing can be specified with either just the typical value or + * a range consisting of min/typ/max. This function helps handling this + **/ +static int parse_timing_property(struct device_node *np, const char *name, + struct timing_entry *result) +{ + struct property *prop; + int length, cells, ret; + + prop = of_find_property(np, name, &length); + if (!prop) { + pr_err("%s: could not find property %s\n", + of_node_full_name(np), name); + return -EINVAL; + } + + cells = length / sizeof(u32); + if (cells == 1) { + ret = of_property_read_u32(np, name, &result->typ); + result->min = result->typ; + result->max = result->typ; + } else if (cells == 3) { + ret = of_property_read_u32_array(np, name, &result->min, cells); + } else { + pr_err("%s: illegal timing specification in %s\n", + of_node_full_name(np), name); + return -EINVAL; + } + + return ret; +} + +/** + * of_get_display_timing - parse display_timing entry from device_node + * @np: device_node with the properties + **/ +static struct display_timing *of_get_display_timing(struct device_node *np) +{ + struct display_timing *dt; + u32 val = 0; + int ret = 0; + + dt = kzalloc(sizeof(*dt), GFP_KERNEL); + if (!dt) { + pr_err("%s: could not allocate display_timing struct\n", + of_node_full_name(np)); + return NULL; + } + + ret |= parse_timing_property(np, "hback-porch", &dt->hback_porch); + ret |= parse_timing_property(np, "hfront-porch", &dt->hfront_porch); + ret |= parse_timing_property(np, "hactive", &dt->hactive); + ret |= parse_timing_property(np, "hsync-len", &dt->hsync_len); + ret |= parse_timing_property(np, "vback-porch", &dt->vback_porch); + ret |= parse_timing_property(np, "vfront-porch", &dt->vfront_porch); + ret |= parse_timing_property(np, "vactive", &dt->vactive); + ret |= parse_timing_property(np, "vsync-len", &dt->vsync_len); + ret |= parse_timing_property(np, "clock-frequency", &dt->pixelclock); + + dt->dmt_flags = 0; + dt->data_flags = 0; + if (!of_property_read_u32(np, "vsync-active", &val)) + dt->dmt_flags |= val ? VESA_DMT_VSYNC_HIGH : + VESA_DMT_VSYNC_LOW; + if (!of_property_read_u32(np, "hsync-active", &val)) + dt->dmt_flags |= val ? VESA_DMT_HSYNC_HIGH : + VESA_DMT_HSYNC_LOW; + if (!of_property_read_u32(np, "de-active", &val)) + dt->data_flags |= val ? DISPLAY_FLAGS_DE_HIGH : + DISPLAY_FLAGS_DE_LOW; + if (!of_property_read_u32(np, "pixelclk-active", &val)) + dt->data_flags |= val ? DISPLAY_FLAGS_PIXDATA_POSEDGE : + DISPLAY_FLAGS_PIXDATA_NEGEDGE; + + if (of_property_read_bool(np, "interlaced")) + dt->data_flags |= DISPLAY_FLAGS_INTERLACED; + if (of_property_read_bool(np, "doublescan")) + dt->data_flags |= DISPLAY_FLAGS_DOUBLESCAN; + + if (ret) { + pr_err("%s: error reading timing properties\n", + of_node_full_name(np)); + kfree(dt); + return NULL; + } + + return dt; +} + +/** + * of_get_display_timings - parse all display_timing entries from a device_node + * @np: device_node with the subnodes + **/ +struct display_timings *of_get_display_timings(struct device_node *np) +{ + struct device_node *timings_np; + struct device_node *entry; + struct device_node *native_mode; + struct display_timings *disp; + + if (!np) { + pr_err("%s: no devicenode given\n", of_node_full_name(np)); + return NULL; + } + + timings_np = of_find_node_by_name(np, "display-timings"); + if (!timings_np) { + pr_err("%s: could not find display-timings node\n", + of_node_full_name(np)); + return NULL; + } + + disp = kzalloc(sizeof(*disp), GFP_KERNEL); + if (!disp) { + pr_err("%s: could not allocate struct disp'\n", + of_node_full_name(np)); + goto dispfail; + } + + entry = of_parse_phandle(timings_np, "native-mode", 0); + /* assume first child as native mode if none provided */ + if (!entry) + entry = of_get_next_child(np, NULL); + /* if there is no child, it is useless to go on */ + if (!entry) { + pr_err("%s: no timing specifications given\n", + of_node_full_name(np)); + goto entryfail; + } + + pr_debug("%s: using %s as default timing\n", + of_node_full_name(np), entry->name); + + native_mode = entry; + + disp->num_timings = of_get_child_count(timings_np); + if (disp->num_timings == 0) { + /* should never happen, as entry was already found above */ + pr_err("%s: no timings specified\n", of_node_full_name(np)); + goto entryfail; + } + + disp->timings = kzalloc(sizeof(struct display_timing *) * + disp->num_timings, GFP_KERNEL); + if (!disp->timings) { + pr_err("%s: could not allocate timings array\n", + of_node_full_name(np)); + goto entryfail; + } + + disp->num_timings = 0; + disp->native_mode = 0; + + for_each_child_of_node(timings_np, entry) { + struct display_timing *dt; + + dt = of_get_display_timing(entry); + if (!dt) { + /* + * to not encourage wrong devicetrees, fail in case of + * an error + */ + pr_err("%s: error in timing %d\n", + of_node_full_name(np), disp->num_timings + 1); + goto timingfail; + } + + if (native_mode == entry) + disp->native_mode = disp->num_timings; + + disp->timings[disp->num_timings] = dt; + disp->num_timings++; + } + of_node_put(timings_np); + /* + * native_mode points to the device_node returned by of_parse_phandle + * therefore call of_node_put on it + */ + of_node_put(native_mode); + + pr_debug("%s: got %d timings. Using timing #%d as default\n", + of_node_full_name(np), disp->num_timings, + disp->native_mode + 1); + + return disp; + +timingfail: + if (native_mode) + of_node_put(native_mode); + display_timings_release(disp); +entryfail: + kfree(disp); +dispfail: + of_node_put(timings_np); + return NULL; +} +EXPORT_SYMBOL_GPL(of_get_display_timings); + +/** + * of_display_timings_exist - check if a display-timings node is provided + * @np: device_node with the timing + **/ +int of_display_timings_exist(struct device_node *np) +{ + struct device_node *timings_np; + + if (!np) + return -EINVAL; + + timings_np = of_parse_phandle(np, "display-timings", 0); + if (!timings_np) + return -EINVAL; + + of_node_put(timings_np); + return 1; +} +EXPORT_SYMBOL_GPL(of_display_timings_exist); diff --git a/drivers/video/of_videomode.c b/drivers/video/of_videomode.c new file mode 100644 index 00000000000..5b8066cd397 --- /dev/null +++ b/drivers/video/of_videomode.c @@ -0,0 +1,54 @@ +/* + * generic videomode helper + * + * Copyright (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>, Pengutronix + * + * This file is released under the GPLv2 + */ +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/of.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/of_videomode.h> +#include <video/videomode.h> + +/** + * of_get_videomode - get the videomode #<index> from devicetree + * @np - devicenode with the display_timings + * @vm - set to return value + * @index - index into list of display_timings + * (Set this to OF_USE_NATIVE_MODE to use whatever mode is + * specified as native mode in the DT.) + * + * DESCRIPTION: + * Get a list of all display timings and put the one + * specified by index into *vm. This function should only be used, if + * only one videomode is to be retrieved. A driver that needs to work + * with multiple/all videomodes should work with + * of_get_display_timings instead. + **/ +int of_get_videomode(struct device_node *np, struct videomode *vm, + int index) +{ + struct display_timings *disp; + int ret; + + disp = of_get_display_timings(np); + if (!disp) { + pr_err("%s: no timings specified\n", of_node_full_name(np)); + return -EINVAL; + } + + if (index == OF_USE_NATIVE_MODE) + index = disp->native_mode; + + ret = videomode_from_timing(disp, vm, index); + if (ret) + return ret; + + display_timings_release(disp); + + return 0; +} +EXPORT_SYMBOL_GPL(of_get_videomode); diff --git a/drivers/video/via/hw.c b/drivers/video/via/hw.c index 80233dae358..22450908306 100644 --- a/drivers/video/via/hw.c +++ b/drivers/video/via/hw.c @@ -1467,10 +1467,10 @@ void viafb_set_vclock(u32 clk, int set_iga) via_write_misc_reg_mask(0x0C, 0x0C); /* select external clock */ } -struct display_timing var_to_timing(const struct fb_var_screeninfo *var, +struct via_display_timing var_to_timing(const struct fb_var_screeninfo *var, u16 cxres, u16 cyres) { - struct display_timing timing; + struct via_display_timing timing; u16 dx = (var->xres - cxres) / 2, dy = (var->yres - cyres) / 2; timing.hor_addr = cxres; @@ -1491,7 +1491,7 @@ struct display_timing var_to_timing(const struct fb_var_screeninfo *var, void viafb_fill_crtc_timing(const struct fb_var_screeninfo *var, u16 cxres, u16 cyres, int iga) { - struct display_timing crt_reg = var_to_timing(var, + struct via_display_timing crt_reg = var_to_timing(var, cxres ? cxres : var->xres, cyres ? cyres : var->yres); if (iga == IGA1) diff --git a/drivers/video/via/hw.h b/drivers/video/via/hw.h index a8205754c73..3be073c58b0 100644 --- a/drivers/video/via/hw.h +++ b/drivers/video/via/hw.h @@ -637,7 +637,7 @@ extern int viafb_LCD_ON; extern int viafb_DVI_ON; extern int viafb_hotplug; -struct display_timing var_to_timing(const struct fb_var_screeninfo *var, +struct via_display_timing var_to_timing(const struct fb_var_screeninfo *var, u16 cxres, u16 cyres); void viafb_fill_crtc_timing(const struct fb_var_screeninfo *var, u16 cxres, u16 cyres, int iga); diff --git a/drivers/video/via/lcd.c b/drivers/video/via/lcd.c index 980ee1b1dcf..5d21ff436ec 100644 --- a/drivers/video/via/lcd.c +++ b/drivers/video/via/lcd.c @@ -549,7 +549,7 @@ void viafb_lcd_set_mode(const struct fb_var_screeninfo *var, u16 cxres, int panel_hres = plvds_setting_info->lcd_panel_hres; int panel_vres = plvds_setting_info->lcd_panel_vres; u32 clock; - struct display_timing timing; + struct via_display_timing timing; struct fb_var_screeninfo panel_var; const struct fb_videomode *mode_crt_table, *panel_crt_table; diff --git a/drivers/video/via/share.h b/drivers/video/via/share.h index 3158dfc90be..65c65c611e0 100644 --- a/drivers/video/via/share.h +++ b/drivers/video/via/share.h @@ -319,7 +319,7 @@ struct crt_mode_table { int refresh_rate; int h_sync_polarity; int v_sync_polarity; - struct display_timing crtc; + struct via_display_timing crtc; }; struct io_reg { diff --git a/drivers/video/via/via_modesetting.c b/drivers/video/via/via_modesetting.c index 0e431aee17b..0b414b09b9b 100644 --- a/drivers/video/via/via_modesetting.c +++ b/drivers/video/via/via_modesetting.c @@ -30,9 +30,9 @@ #include "debug.h" -void via_set_primary_timing(const struct display_timing *timing) +void via_set_primary_timing(const struct via_display_timing *timing) { - struct display_timing raw; + struct via_display_timing raw; raw.hor_total = timing->hor_total / 8 - 5; raw.hor_addr = timing->hor_addr / 8 - 1; @@ -88,9 +88,9 @@ void via_set_primary_timing(const struct display_timing *timing) via_write_reg_mask(VIACR, 0x17, 0x80, 0x80); } -void via_set_secondary_timing(const struct display_timing *timing) +void via_set_secondary_timing(const struct via_display_timing *timing) { - struct display_timing raw; + struct via_display_timing raw; raw.hor_total = timing->hor_total - 1; raw.hor_addr = timing->hor_addr - 1; diff --git a/drivers/video/via/via_modesetting.h b/drivers/video/via/via_modesetting.h index 06e09fe351a..f6a6503da3b 100644 --- a/drivers/video/via/via_modesetting.h +++ b/drivers/video/via/via_modesetting.h @@ -33,7 +33,7 @@ #define VIA_PITCH_MAX 0x3FF8 -struct display_timing { +struct via_display_timing { u16 hor_total; u16 hor_addr; u16 hor_blank_start; @@ -49,8 +49,8 @@ struct display_timing { }; -void via_set_primary_timing(const struct display_timing *timing); -void via_set_secondary_timing(const struct display_timing *timing); +void via_set_primary_timing(const struct via_display_timing *timing); +void via_set_secondary_timing(const struct via_display_timing *timing); void via_set_primary_address(u32 addr); void via_set_secondary_address(u32 addr); void via_set_primary_pitch(u32 pitch); diff --git a/drivers/video/videomode.c b/drivers/video/videomode.c new file mode 100644 index 00000000000..21c47a202af --- /dev/null +++ b/drivers/video/videomode.c @@ -0,0 +1,39 @@ +/* + * generic display timing functions + * + * Copyright (c) 2012 Steffen Trumtrar <s.trumtrar@pengutronix.de>, Pengutronix + * + * This file is released under the GPLv2 + */ + +#include <linux/errno.h> +#include <linux/export.h> +#include <video/display_timing.h> +#include <video/videomode.h> + +int videomode_from_timing(const struct display_timings *disp, + struct videomode *vm, unsigned int index) +{ + struct display_timing *dt; + + dt = display_timings_get(disp, index); + if (!dt) + return -EINVAL; + + vm->pixelclock = display_timing_get_value(&dt->pixelclock, TE_TYP); + vm->hactive = display_timing_get_value(&dt->hactive, TE_TYP); + vm->hfront_porch = display_timing_get_value(&dt->hfront_porch, TE_TYP); + vm->hback_porch = display_timing_get_value(&dt->hback_porch, TE_TYP); + vm->hsync_len = display_timing_get_value(&dt->hsync_len, TE_TYP); + + vm->vactive = display_timing_get_value(&dt->vactive, TE_TYP); + vm->vfront_porch = display_timing_get_value(&dt->vfront_porch, TE_TYP); + vm->vback_porch = display_timing_get_value(&dt->vback_porch, TE_TYP); + vm->vsync_len = display_timing_get_value(&dt->vsync_len, TE_TYP); + + vm->dmt_flags = dt->dmt_flags; + vm->data_flags = dt->data_flags; + + return 0; +} +EXPORT_SYMBOL_GPL(videomode_from_timing); |