mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1304 lines
36 KiB
1304 lines
36 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
On Screen Display cx23415 Framebuffer driver |
|
|
|
This module presents the cx23415 OSD (onscreen display) framebuffer memory |
|
as a standard Linux /dev/fb style framebuffer device. The framebuffer has |
|
support for 8, 16 & 32 bpp packed pixel formats with alpha channel. In 16bpp |
|
mode, there is a choice of a three color depths (12, 15 or 16 bits), but no |
|
local alpha. The colorspace is selectable between rgb & yuv. |
|
Depending on the TV standard configured in the ivtv module at load time, |
|
the initial resolution is either 640x400 (NTSC) or 640x480 (PAL) at 8bpp. |
|
Video timings are locked to ensure a vertical refresh rate of 50Hz (PAL) |
|
or 59.94 (NTSC) |
|
|
|
Copyright (c) 2003 Matt T. Yourst <[email protected]> |
|
|
|
Derived from drivers/video/vesafb.c |
|
Portions (c) 1998 Gerd Knorr <[email protected]> |
|
|
|
2.6 kernel port: |
|
Copyright (C) 2004 Matthias Badaire |
|
|
|
Copyright (C) 2004 Chris Kennedy <[email protected]> |
|
|
|
Copyright (C) 2006 Ian Armstrong <[email protected]> |
|
|
|
*/ |
|
|
|
#include "ivtv-driver.h" |
|
#include "ivtv-cards.h" |
|
#include "ivtv-i2c.h" |
|
#include "ivtv-udma.h" |
|
#include "ivtv-mailbox.h" |
|
#include "ivtv-firmware.h" |
|
|
|
#include <linux/fb.h> |
|
#include <linux/ivtvfb.h> |
|
|
|
#ifdef CONFIG_X86_64 |
|
#include <asm/memtype.h> |
|
#endif |
|
|
|
/* card parameters */ |
|
static int ivtvfb_card_id = -1; |
|
static int ivtvfb_debug = 0; |
|
static bool ivtvfb_force_pat = IS_ENABLED(CONFIG_VIDEO_FB_IVTV_FORCE_PAT); |
|
static bool osd_laced; |
|
static int osd_depth; |
|
static int osd_upper; |
|
static int osd_left; |
|
static int osd_yres; |
|
static int osd_xres; |
|
|
|
module_param(ivtvfb_card_id, int, 0444); |
|
module_param_named(debug,ivtvfb_debug, int, 0644); |
|
module_param_named(force_pat, ivtvfb_force_pat, bool, 0644); |
|
module_param(osd_laced, bool, 0444); |
|
module_param(osd_depth, int, 0444); |
|
module_param(osd_upper, int, 0444); |
|
module_param(osd_left, int, 0444); |
|
module_param(osd_yres, int, 0444); |
|
module_param(osd_xres, int, 0444); |
|
|
|
MODULE_PARM_DESC(ivtvfb_card_id, |
|
"Only use framebuffer of the specified ivtv card (0-31)\n" |
|
"\t\t\tdefault -1: initialize all available framebuffers"); |
|
|
|
MODULE_PARM_DESC(debug, |
|
"Debug level (bitmask). Default: errors only\n" |
|
"\t\t\t(debug = 3 gives full debugging)"); |
|
|
|
MODULE_PARM_DESC(force_pat, |
|
"Force initialization on x86 PAT-enabled systems (bool).\n"); |
|
|
|
/* Why upper, left, xres, yres, depth, laced ? To match terminology used |
|
by fbset. |
|
Why start at 1 for left & upper coordinate ? Because X doesn't allow 0 */ |
|
|
|
MODULE_PARM_DESC(osd_laced, |
|
"Interlaced mode\n" |
|
"\t\t\t0=off\n" |
|
"\t\t\t1=on\n" |
|
"\t\t\tdefault off"); |
|
|
|
MODULE_PARM_DESC(osd_depth, |
|
"Bits per pixel - 8, 16, 32\n" |
|
"\t\t\tdefault 8"); |
|
|
|
MODULE_PARM_DESC(osd_upper, |
|
"Vertical start position\n" |
|
"\t\t\tdefault 0 (Centered)"); |
|
|
|
MODULE_PARM_DESC(osd_left, |
|
"Horizontal start position\n" |
|
"\t\t\tdefault 0 (Centered)"); |
|
|
|
MODULE_PARM_DESC(osd_yres, |
|
"Display height\n" |
|
"\t\t\tdefault 480 (PAL)\n" |
|
"\t\t\t 400 (NTSC)"); |
|
|
|
MODULE_PARM_DESC(osd_xres, |
|
"Display width\n" |
|
"\t\t\tdefault 640"); |
|
|
|
MODULE_AUTHOR("Kevin Thayer, Chris Kennedy, Hans Verkuil, John Harvey, Ian Armstrong"); |
|
MODULE_LICENSE("GPL"); |
|
|
|
/* --------------------------------------------------------------------- */ |
|
|
|
#define IVTVFB_DBGFLG_WARN (1 << 0) |
|
#define IVTVFB_DBGFLG_INFO (1 << 1) |
|
|
|
#define IVTVFB_DEBUG(x, type, fmt, args...) \ |
|
do { \ |
|
if ((x) & ivtvfb_debug) \ |
|
printk(KERN_INFO "ivtvfb%d " type ": " fmt, itv->instance , ## args); \ |
|
} while (0) |
|
#define IVTVFB_DEBUG_WARN(fmt, args...) IVTVFB_DEBUG(IVTVFB_DBGFLG_WARN, "warning", fmt , ## args) |
|
#define IVTVFB_DEBUG_INFO(fmt, args...) IVTVFB_DEBUG(IVTVFB_DBGFLG_INFO, "info", fmt , ## args) |
|
|
|
/* Standard kernel messages */ |
|
#define IVTVFB_ERR(fmt, args...) printk(KERN_ERR "ivtvfb%d: " fmt, itv->instance , ## args) |
|
#define IVTVFB_WARN(fmt, args...) printk(KERN_WARNING "ivtvfb%d: " fmt, itv->instance , ## args) |
|
#define IVTVFB_INFO(fmt, args...) printk(KERN_INFO "ivtvfb%d: " fmt, itv->instance , ## args) |
|
|
|
/* --------------------------------------------------------------------- */ |
|
|
|
#define IVTV_OSD_MAX_WIDTH 720 |
|
#define IVTV_OSD_MAX_HEIGHT 576 |
|
|
|
#define IVTV_OSD_BPP_8 0x00 |
|
#define IVTV_OSD_BPP_16_444 0x03 |
|
#define IVTV_OSD_BPP_16_555 0x02 |
|
#define IVTV_OSD_BPP_16_565 0x01 |
|
#define IVTV_OSD_BPP_32 0x04 |
|
|
|
struct osd_info { |
|
/* Physical base address */ |
|
unsigned long video_pbase; |
|
/* Relative base address (relative to start of decoder memory) */ |
|
u32 video_rbase; |
|
/* Mapped base address */ |
|
volatile char __iomem *video_vbase; |
|
/* Buffer size */ |
|
u32 video_buffer_size; |
|
|
|
/* video_base rounded down as required by hardware MTRRs */ |
|
unsigned long fb_start_aligned_physaddr; |
|
/* video_base rounded up as required by hardware MTRRs */ |
|
unsigned long fb_end_aligned_physaddr; |
|
int wc_cookie; |
|
|
|
/* Store the buffer offset */ |
|
int set_osd_coords_x; |
|
int set_osd_coords_y; |
|
|
|
/* Current dimensions (NOT VISIBLE SIZE!) */ |
|
int display_width; |
|
int display_height; |
|
int display_byte_stride; |
|
|
|
/* Current bits per pixel */ |
|
int bits_per_pixel; |
|
int bytes_per_pixel; |
|
|
|
/* Frame buffer stuff */ |
|
struct fb_info ivtvfb_info; |
|
struct fb_var_screeninfo ivtvfb_defined; |
|
struct fb_fix_screeninfo ivtvfb_fix; |
|
|
|
/* Used for a warm start */ |
|
struct fb_var_screeninfo fbvar_cur; |
|
int blank_cur; |
|
u32 palette_cur[256]; |
|
u32 pan_cur; |
|
}; |
|
|
|
struct ivtv_osd_coords { |
|
unsigned long offset; |
|
unsigned long max_offset; |
|
int pixel_stride; |
|
int lines; |
|
int x; |
|
int y; |
|
}; |
|
|
|
/* --------------------------------------------------------------------- */ |
|
|
|
/* ivtv API calls for framebuffer related support */ |
|
|
|
static int ivtvfb_get_framebuffer(struct ivtv *itv, u32 *fbbase, |
|
u32 *fblength) |
|
{ |
|
u32 data[CX2341X_MBOX_MAX_DATA]; |
|
int rc; |
|
|
|
ivtv_firmware_check(itv, "ivtvfb_get_framebuffer"); |
|
rc = ivtv_vapi_result(itv, data, CX2341X_OSD_GET_FRAMEBUFFER, 0); |
|
*fbbase = data[0]; |
|
*fblength = data[1]; |
|
return rc; |
|
} |
|
|
|
static int ivtvfb_get_osd_coords(struct ivtv *itv, |
|
struct ivtv_osd_coords *osd) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
u32 data[CX2341X_MBOX_MAX_DATA]; |
|
|
|
ivtv_vapi_result(itv, data, CX2341X_OSD_GET_OSD_COORDS, 0); |
|
|
|
osd->offset = data[0] - oi->video_rbase; |
|
osd->max_offset = oi->display_width * oi->display_height * 4; |
|
osd->pixel_stride = data[1]; |
|
osd->lines = data[2]; |
|
osd->x = data[3]; |
|
osd->y = data[4]; |
|
return 0; |
|
} |
|
|
|
static int ivtvfb_set_osd_coords(struct ivtv *itv, const struct ivtv_osd_coords *osd) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
|
|
oi->display_width = osd->pixel_stride; |
|
oi->display_byte_stride = osd->pixel_stride * oi->bytes_per_pixel; |
|
oi->set_osd_coords_x += osd->x; |
|
oi->set_osd_coords_y = osd->y; |
|
|
|
return ivtv_vapi(itv, CX2341X_OSD_SET_OSD_COORDS, 5, |
|
osd->offset + oi->video_rbase, |
|
osd->pixel_stride, |
|
osd->lines, osd->x, osd->y); |
|
} |
|
|
|
static int ivtvfb_set_display_window(struct ivtv *itv, struct v4l2_rect *ivtv_window) |
|
{ |
|
int osd_height_limit = itv->is_out_50hz ? 576 : 480; |
|
|
|
/* Only fail if resolution too high, otherwise fudge the start coords. */ |
|
if ((ivtv_window->height > osd_height_limit) || (ivtv_window->width > IVTV_OSD_MAX_WIDTH)) |
|
return -EINVAL; |
|
|
|
/* Ensure we don't exceed display limits */ |
|
if (ivtv_window->top + ivtv_window->height > osd_height_limit) { |
|
IVTVFB_DEBUG_WARN("ivtv_ioctl_fb_set_display_window - Invalid height setting (%d, %d)\n", |
|
ivtv_window->top, ivtv_window->height); |
|
ivtv_window->top = osd_height_limit - ivtv_window->height; |
|
} |
|
|
|
if (ivtv_window->left + ivtv_window->width > IVTV_OSD_MAX_WIDTH) { |
|
IVTVFB_DEBUG_WARN("ivtv_ioctl_fb_set_display_window - Invalid width setting (%d, %d)\n", |
|
ivtv_window->left, ivtv_window->width); |
|
ivtv_window->left = IVTV_OSD_MAX_WIDTH - ivtv_window->width; |
|
} |
|
|
|
/* Set the OSD origin */ |
|
write_reg((ivtv_window->top << 16) | ivtv_window->left, 0x02a04); |
|
|
|
/* How much to display */ |
|
write_reg(((ivtv_window->top+ivtv_window->height) << 16) | (ivtv_window->left+ivtv_window->width), 0x02a08); |
|
|
|
/* Pass this info back the yuv handler */ |
|
itv->yuv_info.osd_vis_w = ivtv_window->width; |
|
itv->yuv_info.osd_vis_h = ivtv_window->height; |
|
itv->yuv_info.osd_x_offset = ivtv_window->left; |
|
itv->yuv_info.osd_y_offset = ivtv_window->top; |
|
|
|
return 0; |
|
} |
|
|
|
static int ivtvfb_prep_dec_dma_to_device(struct ivtv *itv, |
|
unsigned long ivtv_dest_addr, void __user *userbuf, |
|
int size_in_bytes) |
|
{ |
|
DEFINE_WAIT(wait); |
|
int got_sig = 0; |
|
|
|
mutex_lock(&itv->udma.lock); |
|
/* Map User DMA */ |
|
if (ivtv_udma_setup(itv, ivtv_dest_addr, userbuf, size_in_bytes) <= 0) { |
|
mutex_unlock(&itv->udma.lock); |
|
IVTVFB_WARN("ivtvfb_prep_dec_dma_to_device, Error with pin_user_pages: %d bytes, %d pages returned\n", |
|
size_in_bytes, itv->udma.page_count); |
|
|
|
/* pin_user_pages must have failed completely */ |
|
return -EIO; |
|
} |
|
|
|
IVTVFB_DEBUG_INFO("ivtvfb_prep_dec_dma_to_device, %d bytes, %d pages\n", |
|
size_in_bytes, itv->udma.page_count); |
|
|
|
ivtv_udma_prepare(itv); |
|
prepare_to_wait(&itv->dma_waitq, &wait, TASK_INTERRUPTIBLE); |
|
/* if no UDMA is pending and no UDMA is in progress, then the DMA |
|
is finished */ |
|
while (test_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags) || |
|
test_bit(IVTV_F_I_UDMA, &itv->i_flags)) { |
|
/* don't interrupt if the DMA is in progress but break off |
|
a still pending DMA. */ |
|
got_sig = signal_pending(current); |
|
if (got_sig && test_and_clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags)) |
|
break; |
|
got_sig = 0; |
|
schedule(); |
|
} |
|
finish_wait(&itv->dma_waitq, &wait); |
|
|
|
/* Unmap Last DMA Xfer */ |
|
ivtv_udma_unmap(itv); |
|
mutex_unlock(&itv->udma.lock); |
|
if (got_sig) { |
|
IVTV_DEBUG_INFO("User stopped OSD\n"); |
|
return -EINTR; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int ivtvfb_prep_frame(struct ivtv *itv, int cmd, void __user *source, |
|
unsigned long dest_offset, int count) |
|
{ |
|
DEFINE_WAIT(wait); |
|
struct osd_info *oi = itv->osd_info; |
|
|
|
/* Nothing to do */ |
|
if (count == 0) { |
|
IVTVFB_DEBUG_WARN("ivtvfb_prep_frame: Nothing to do. count = 0\n"); |
|
return -EINVAL; |
|
} |
|
|
|
/* Check Total FB Size */ |
|
if ((dest_offset + count) > oi->video_buffer_size) { |
|
IVTVFB_WARN("ivtvfb_prep_frame: Overflowing the framebuffer %ld, only %d available\n", |
|
dest_offset + count, oi->video_buffer_size); |
|
return -E2BIG; |
|
} |
|
|
|
/* Not fatal, but will have undesirable results */ |
|
if ((unsigned long)source & 3) |
|
IVTVFB_WARN("ivtvfb_prep_frame: Source address not 32 bit aligned (%p)\n", |
|
source); |
|
|
|
if (dest_offset & 3) |
|
IVTVFB_WARN("ivtvfb_prep_frame: Dest offset not 32 bit aligned (%ld)\n", dest_offset); |
|
|
|
if (count & 3) |
|
IVTVFB_WARN("ivtvfb_prep_frame: Count not a multiple of 4 (%d)\n", count); |
|
|
|
/* Check Source */ |
|
if (!access_ok(source + dest_offset, count)) { |
|
IVTVFB_WARN("Invalid userspace pointer %p\n", source); |
|
|
|
IVTVFB_DEBUG_WARN("access_ok() failed for offset 0x%08lx source %p count %d\n", |
|
dest_offset, source, count); |
|
return -EINVAL; |
|
} |
|
|
|
/* OSD Address to send DMA to */ |
|
dest_offset += IVTV_DECODER_OFFSET + oi->video_rbase; |
|
|
|
/* Fill Buffers */ |
|
return ivtvfb_prep_dec_dma_to_device(itv, dest_offset, source, count); |
|
} |
|
|
|
static ssize_t ivtvfb_write(struct fb_info *info, const char __user *buf, |
|
size_t count, loff_t *ppos) |
|
{ |
|
unsigned long p = *ppos; |
|
void *dst; |
|
int err = 0; |
|
int dma_err; |
|
unsigned long total_size; |
|
struct ivtv *itv = (struct ivtv *) info->par; |
|
unsigned long dma_offset = |
|
IVTV_DECODER_OFFSET + itv->osd_info->video_rbase; |
|
unsigned long dma_size; |
|
u16 lead = 0, tail = 0; |
|
|
|
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 transfer size > threshold and both src/dst |
|
addresses are aligned, use DMA */ |
|
if (count >= 4096 && |
|
((unsigned long)buf & 3) == ((unsigned long)dst & 3)) { |
|
/* Odd address = can't DMA. Align */ |
|
if ((unsigned long)dst & 3) { |
|
lead = 4 - ((unsigned long)dst & 3); |
|
if (copy_from_user(dst, buf, lead)) |
|
return -EFAULT; |
|
buf += lead; |
|
dst += lead; |
|
} |
|
/* DMA resolution is 32 bits */ |
|
if ((count - lead) & 3) |
|
tail = (count - lead) & 3; |
|
/* DMA the data */ |
|
dma_size = count - lead - tail; |
|
dma_err = ivtvfb_prep_dec_dma_to_device(itv, |
|
p + lead + dma_offset, (void __user *)buf, dma_size); |
|
if (dma_err) |
|
return dma_err; |
|
dst += dma_size; |
|
buf += dma_size; |
|
/* Copy any leftover data */ |
|
if (tail && copy_from_user(dst, buf, tail)) |
|
return -EFAULT; |
|
} else if (copy_from_user(dst, buf, count)) { |
|
return -EFAULT; |
|
} |
|
|
|
if (!err) |
|
*ppos += count; |
|
|
|
return (err) ? err : count; |
|
} |
|
|
|
static int ivtvfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) |
|
{ |
|
DEFINE_WAIT(wait); |
|
struct ivtv *itv = (struct ivtv *)info->par; |
|
int rc = 0; |
|
|
|
switch (cmd) { |
|
case FBIOGET_VBLANK: { |
|
struct fb_vblank vblank; |
|
u32 trace; |
|
|
|
memset(&vblank, 0, sizeof(struct fb_vblank)); |
|
|
|
vblank.flags = FB_VBLANK_HAVE_COUNT |FB_VBLANK_HAVE_VCOUNT | |
|
FB_VBLANK_HAVE_VSYNC; |
|
trace = read_reg(IVTV_REG_DEC_LINE_FIELD) >> 16; |
|
if (itv->is_out_50hz && trace > 312) |
|
trace -= 312; |
|
else if (itv->is_out_60hz && trace > 262) |
|
trace -= 262; |
|
if (trace == 1) |
|
vblank.flags |= FB_VBLANK_VSYNCING; |
|
vblank.count = itv->last_vsync_field; |
|
vblank.vcount = trace; |
|
vblank.hcount = 0; |
|
if (copy_to_user((void __user *)arg, &vblank, sizeof(vblank))) |
|
return -EFAULT; |
|
return 0; |
|
} |
|
|
|
case FBIO_WAITFORVSYNC: |
|
prepare_to_wait(&itv->vsync_waitq, &wait, TASK_INTERRUPTIBLE); |
|
if (!schedule_timeout(msecs_to_jiffies(50))) |
|
rc = -ETIMEDOUT; |
|
finish_wait(&itv->vsync_waitq, &wait); |
|
return rc; |
|
|
|
case IVTVFB_IOC_DMA_FRAME: { |
|
struct ivtvfb_dma_frame args; |
|
|
|
IVTVFB_DEBUG_INFO("IVTVFB_IOC_DMA_FRAME\n"); |
|
if (copy_from_user(&args, (void __user *)arg, sizeof(args))) |
|
return -EFAULT; |
|
|
|
return ivtvfb_prep_frame(itv, cmd, args.source, args.dest_offset, args.count); |
|
} |
|
|
|
default: |
|
IVTVFB_DEBUG_INFO("Unknown ioctl %08x\n", cmd); |
|
return -EINVAL; |
|
} |
|
return 0; |
|
} |
|
|
|
/* Framebuffer device handling */ |
|
|
|
static int ivtvfb_set_var(struct ivtv *itv, struct fb_var_screeninfo *var) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
struct ivtv_osd_coords ivtv_osd; |
|
struct v4l2_rect ivtv_window; |
|
int osd_mode = -1; |
|
|
|
IVTVFB_DEBUG_INFO("ivtvfb_set_var\n"); |
|
|
|
/* Select color space */ |
|
if (var->nonstd) /* YUV */ |
|
write_reg(read_reg(0x02a00) | 0x0002000, 0x02a00); |
|
else /* RGB */ |
|
write_reg(read_reg(0x02a00) & ~0x0002000, 0x02a00); |
|
|
|
/* Set the color mode */ |
|
switch (var->bits_per_pixel) { |
|
case 8: |
|
osd_mode = IVTV_OSD_BPP_8; |
|
break; |
|
case 32: |
|
osd_mode = IVTV_OSD_BPP_32; |
|
break; |
|
case 16: |
|
switch (var->green.length) { |
|
case 4: |
|
osd_mode = IVTV_OSD_BPP_16_444; |
|
break; |
|
case 5: |
|
osd_mode = IVTV_OSD_BPP_16_555; |
|
break; |
|
case 6: |
|
osd_mode = IVTV_OSD_BPP_16_565; |
|
break; |
|
default: |
|
IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid bpp\n"); |
|
} |
|
break; |
|
default: |
|
IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid bpp\n"); |
|
} |
|
|
|
/* Set video mode. Although rare, the display can become scrambled even |
|
if we don't change mode. Always 'bounce' to osd_mode via mode 0 */ |
|
if (osd_mode != -1) { |
|
ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, 0); |
|
ivtv_vapi(itv, CX2341X_OSD_SET_PIXEL_FORMAT, 1, osd_mode); |
|
} |
|
|
|
oi->bits_per_pixel = var->bits_per_pixel; |
|
oi->bytes_per_pixel = var->bits_per_pixel / 8; |
|
|
|
/* Set the flicker filter */ |
|
switch (var->vmode & FB_VMODE_MASK) { |
|
case FB_VMODE_NONINTERLACED: /* Filter on */ |
|
ivtv_vapi(itv, CX2341X_OSD_SET_FLICKER_STATE, 1, 1); |
|
break; |
|
case FB_VMODE_INTERLACED: /* Filter off */ |
|
ivtv_vapi(itv, CX2341X_OSD_SET_FLICKER_STATE, 1, 0); |
|
break; |
|
default: |
|
IVTVFB_DEBUG_WARN("ivtvfb_set_var - Invalid video mode\n"); |
|
} |
|
|
|
/* Read the current osd info */ |
|
ivtvfb_get_osd_coords(itv, &ivtv_osd); |
|
|
|
/* Now set the OSD to the size we want */ |
|
ivtv_osd.pixel_stride = var->xres_virtual; |
|
ivtv_osd.lines = var->yres_virtual; |
|
ivtv_osd.x = 0; |
|
ivtv_osd.y = 0; |
|
ivtvfb_set_osd_coords(itv, &ivtv_osd); |
|
|
|
/* Can't seem to find the right API combo for this. |
|
Use another function which does what we need through direct register access. */ |
|
ivtv_window.width = var->xres; |
|
ivtv_window.height = var->yres; |
|
|
|
/* Minimum margin cannot be 0, as X won't allow such a mode */ |
|
if (!var->upper_margin) |
|
var->upper_margin++; |
|
if (!var->left_margin) |
|
var->left_margin++; |
|
ivtv_window.top = var->upper_margin - 1; |
|
ivtv_window.left = var->left_margin - 1; |
|
|
|
ivtvfb_set_display_window(itv, &ivtv_window); |
|
|
|
/* Pass screen size back to yuv handler */ |
|
itv->yuv_info.osd_full_w = ivtv_osd.pixel_stride; |
|
itv->yuv_info.osd_full_h = ivtv_osd.lines; |
|
|
|
/* Force update of yuv registers */ |
|
itv->yuv_info.yuv_forced_update = 1; |
|
|
|
/* Keep a copy of these settings */ |
|
memcpy(&oi->fbvar_cur, var, sizeof(oi->fbvar_cur)); |
|
|
|
IVTVFB_DEBUG_INFO("Display size: %dx%d (virtual %dx%d) @ %dbpp\n", |
|
var->xres, var->yres, |
|
var->xres_virtual, var->yres_virtual, |
|
var->bits_per_pixel); |
|
|
|
IVTVFB_DEBUG_INFO("Display position: %d, %d\n", |
|
var->left_margin, var->upper_margin); |
|
|
|
IVTVFB_DEBUG_INFO("Display filter: %s\n", |
|
(var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED ? "on" : "off"); |
|
IVTVFB_DEBUG_INFO("Color space: %s\n", var->nonstd ? "YUV" : "RGB"); |
|
|
|
return 0; |
|
} |
|
|
|
static int ivtvfb_get_fix(struct ivtv *itv, struct fb_fix_screeninfo *fix) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
|
|
IVTVFB_DEBUG_INFO("ivtvfb_get_fix\n"); |
|
memset(fix, 0, sizeof(struct fb_fix_screeninfo)); |
|
strscpy(fix->id, "cx23415 TV out", sizeof(fix->id)); |
|
fix->smem_start = oi->video_pbase; |
|
fix->smem_len = oi->video_buffer_size; |
|
fix->type = FB_TYPE_PACKED_PIXELS; |
|
fix->visual = (oi->bits_per_pixel == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; |
|
fix->xpanstep = 1; |
|
fix->ypanstep = 1; |
|
fix->ywrapstep = 0; |
|
fix->line_length = oi->display_byte_stride; |
|
fix->accel = FB_ACCEL_NONE; |
|
return 0; |
|
} |
|
|
|
/* Check the requested display mode, returning -EINVAL if we can't |
|
handle it. */ |
|
|
|
static int _ivtvfb_check_var(struct fb_var_screeninfo *var, struct ivtv *itv) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
int osd_height_limit; |
|
u32 pixclock, hlimit, vlimit; |
|
|
|
IVTVFB_DEBUG_INFO("ivtvfb_check_var\n"); |
|
|
|
/* Set base references for mode calcs. */ |
|
if (itv->is_out_50hz) { |
|
pixclock = 84316; |
|
hlimit = 776; |
|
vlimit = 591; |
|
osd_height_limit = 576; |
|
} |
|
else { |
|
pixclock = 83926; |
|
hlimit = 776; |
|
vlimit = 495; |
|
osd_height_limit = 480; |
|
} |
|
|
|
if (var->bits_per_pixel == 8 || var->bits_per_pixel == 32) { |
|
var->transp.offset = 24; |
|
var->transp.length = 8; |
|
var->red.offset = 16; |
|
var->red.length = 8; |
|
var->green.offset = 8; |
|
var->green.length = 8; |
|
var->blue.offset = 0; |
|
var->blue.length = 8; |
|
} |
|
else if (var->bits_per_pixel == 16) { |
|
/* To find out the true mode, check green length */ |
|
switch (var->green.length) { |
|
case 4: |
|
var->red.offset = 8; |
|
var->red.length = 4; |
|
var->green.offset = 4; |
|
var->green.length = 4; |
|
var->blue.offset = 0; |
|
var->blue.length = 4; |
|
var->transp.offset = 12; |
|
var->transp.length = 1; |
|
break; |
|
case 5: |
|
var->red.offset = 10; |
|
var->red.length = 5; |
|
var->green.offset = 5; |
|
var->green.length = 5; |
|
var->blue.offset = 0; |
|
var->blue.length = 5; |
|
var->transp.offset = 15; |
|
var->transp.length = 1; |
|
break; |
|
default: |
|
var->red.offset = 11; |
|
var->red.length = 5; |
|
var->green.offset = 5; |
|
var->green.length = 6; |
|
var->blue.offset = 0; |
|
var->blue.length = 5; |
|
var->transp.offset = 0; |
|
var->transp.length = 0; |
|
break; |
|
} |
|
} |
|
else { |
|
IVTVFB_DEBUG_WARN("Invalid colour mode: %d\n", var->bits_per_pixel); |
|
return -EINVAL; |
|
} |
|
|
|
/* Check the resolution */ |
|
if (var->xres > IVTV_OSD_MAX_WIDTH || var->yres > osd_height_limit) { |
|
IVTVFB_DEBUG_WARN("Invalid resolution: %dx%d\n", |
|
var->xres, var->yres); |
|
return -EINVAL; |
|
} |
|
|
|
/* Max horizontal size is 1023 @ 32bpp, 2046 & 16bpp, 4092 @ 8bpp */ |
|
if (var->xres_virtual > 4095 / (var->bits_per_pixel / 8) || |
|
var->xres_virtual * var->yres_virtual * (var->bits_per_pixel / 8) > oi->video_buffer_size || |
|
var->xres_virtual < var->xres || |
|
var->yres_virtual < var->yres) { |
|
IVTVFB_DEBUG_WARN("Invalid virtual resolution: %dx%d\n", |
|
var->xres_virtual, var->yres_virtual); |
|
return -EINVAL; |
|
} |
|
|
|
/* Some extra checks if in 8 bit mode */ |
|
if (var->bits_per_pixel == 8) { |
|
/* Width must be a multiple of 4 */ |
|
if (var->xres & 3) { |
|
IVTVFB_DEBUG_WARN("Invalid resolution for 8bpp: %d\n", var->xres); |
|
return -EINVAL; |
|
} |
|
if (var->xres_virtual & 3) { |
|
IVTVFB_DEBUG_WARN("Invalid virtual resolution for 8bpp: %d)\n", var->xres_virtual); |
|
return -EINVAL; |
|
} |
|
} |
|
else if (var->bits_per_pixel == 16) { |
|
/* Width must be a multiple of 2 */ |
|
if (var->xres & 1) { |
|
IVTVFB_DEBUG_WARN("Invalid resolution for 16bpp: %d\n", var->xres); |
|
return -EINVAL; |
|
} |
|
if (var->xres_virtual & 1) { |
|
IVTVFB_DEBUG_WARN("Invalid virtual resolution for 16bpp: %d)\n", var->xres_virtual); |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
/* Now check the offsets */ |
|
if (var->xoffset >= var->xres_virtual || var->yoffset >= var->yres_virtual) { |
|
IVTVFB_DEBUG_WARN("Invalid offset: %d (%d) %d (%d)\n", |
|
var->xoffset, var->xres_virtual, var->yoffset, var->yres_virtual); |
|
return -EINVAL; |
|
} |
|
|
|
/* Check pixel format */ |
|
if (var->nonstd > 1) { |
|
IVTVFB_DEBUG_WARN("Invalid nonstd % d\n", var->nonstd); |
|
return -EINVAL; |
|
} |
|
|
|
/* Check video mode */ |
|
if (((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) && |
|
((var->vmode & FB_VMODE_MASK) != FB_VMODE_INTERLACED)) { |
|
IVTVFB_DEBUG_WARN("Invalid video mode: %d\n", var->vmode & FB_VMODE_MASK); |
|
return -EINVAL; |
|
} |
|
|
|
/* Check the left & upper margins |
|
If the margins are too large, just center the screen |
|
(enforcing margins causes too many problems) */ |
|
|
|
if (var->left_margin + var->xres > IVTV_OSD_MAX_WIDTH + 1) |
|
var->left_margin = 1 + ((IVTV_OSD_MAX_WIDTH - var->xres) / 2); |
|
|
|
if (var->upper_margin + var->yres > (itv->is_out_50hz ? 577 : 481)) |
|
var->upper_margin = 1 + (((itv->is_out_50hz ? 576 : 480) - |
|
var->yres) / 2); |
|
|
|
/* Maintain overall 'size' for a constant refresh rate */ |
|
var->right_margin = hlimit - var->left_margin - var->xres; |
|
var->lower_margin = vlimit - var->upper_margin - var->yres; |
|
|
|
/* Fixed sync times */ |
|
var->hsync_len = 24; |
|
var->vsync_len = 2; |
|
|
|
/* Non-interlaced / interlaced mode is used to switch the OSD filter |
|
on or off. Adjust the clock timings to maintain a constant |
|
vertical refresh rate. */ |
|
if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED) |
|
var->pixclock = pixclock / 2; |
|
else |
|
var->pixclock = pixclock; |
|
|
|
itv->osd_rect.width = var->xres; |
|
itv->osd_rect.height = var->yres; |
|
|
|
IVTVFB_DEBUG_INFO("Display size: %dx%d (virtual %dx%d) @ %dbpp\n", |
|
var->xres, var->yres, |
|
var->xres_virtual, var->yres_virtual, |
|
var->bits_per_pixel); |
|
|
|
IVTVFB_DEBUG_INFO("Display position: %d, %d\n", |
|
var->left_margin, var->upper_margin); |
|
|
|
IVTVFB_DEBUG_INFO("Display filter: %s\n", |
|
(var->vmode & FB_VMODE_MASK) == FB_VMODE_NONINTERLACED ? "on" : "off"); |
|
IVTVFB_DEBUG_INFO("Color space: %s\n", var->nonstd ? "YUV" : "RGB"); |
|
return 0; |
|
} |
|
|
|
static int ivtvfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) |
|
{ |
|
struct ivtv *itv = (struct ivtv *) info->par; |
|
IVTVFB_DEBUG_INFO("ivtvfb_check_var\n"); |
|
return _ivtvfb_check_var(var, itv); |
|
} |
|
|
|
static int ivtvfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) |
|
{ |
|
u32 osd_pan_index; |
|
struct ivtv *itv = (struct ivtv *) info->par; |
|
|
|
if (var->yoffset + info->var.yres > info->var.yres_virtual || |
|
var->xoffset + info->var.xres > info->var.xres_virtual) |
|
return -EINVAL; |
|
|
|
osd_pan_index = var->yoffset * info->fix.line_length |
|
+ var->xoffset * info->var.bits_per_pixel / 8; |
|
write_reg(osd_pan_index, 0x02A0C); |
|
|
|
/* Pass this info back the yuv handler */ |
|
itv->yuv_info.osd_x_pan = var->xoffset; |
|
itv->yuv_info.osd_y_pan = var->yoffset; |
|
/* Force update of yuv registers */ |
|
itv->yuv_info.yuv_forced_update = 1; |
|
/* Remember this value */ |
|
itv->osd_info->pan_cur = osd_pan_index; |
|
return 0; |
|
} |
|
|
|
static int ivtvfb_set_par(struct fb_info *info) |
|
{ |
|
int rc = 0; |
|
struct ivtv *itv = (struct ivtv *) info->par; |
|
|
|
IVTVFB_DEBUG_INFO("ivtvfb_set_par\n"); |
|
|
|
rc = ivtvfb_set_var(itv, &info->var); |
|
ivtvfb_pan_display(&info->var, info); |
|
ivtvfb_get_fix(itv, &info->fix); |
|
ivtv_firmware_check(itv, "ivtvfb_set_par"); |
|
return rc; |
|
} |
|
|
|
static int ivtvfb_setcolreg(unsigned regno, unsigned red, unsigned green, |
|
unsigned blue, unsigned transp, |
|
struct fb_info *info) |
|
{ |
|
u32 color, *palette; |
|
struct ivtv *itv = (struct ivtv *)info->par; |
|
|
|
if (regno >= info->cmap.len) |
|
return -EINVAL; |
|
|
|
color = ((transp & 0xFF00) << 16) |((red & 0xFF00) << 8) | (green & 0xFF00) | ((blue & 0xFF00) >> 8); |
|
if (info->var.bits_per_pixel <= 8) { |
|
write_reg(regno, 0x02a30); |
|
write_reg(color, 0x02a34); |
|
itv->osd_info->palette_cur[regno] = color; |
|
return 0; |
|
} |
|
if (regno >= 16) |
|
return -EINVAL; |
|
|
|
palette = info->pseudo_palette; |
|
if (info->var.bits_per_pixel == 16) { |
|
switch (info->var.green.length) { |
|
case 4: |
|
color = ((red & 0xf000) >> 4) | |
|
((green & 0xf000) >> 8) | |
|
((blue & 0xf000) >> 12); |
|
break; |
|
case 5: |
|
color = ((red & 0xf800) >> 1) | |
|
((green & 0xf800) >> 6) | |
|
((blue & 0xf800) >> 11); |
|
break; |
|
case 6: |
|
color = (red & 0xf800 ) | |
|
((green & 0xfc00) >> 5) | |
|
((blue & 0xf800) >> 11); |
|
break; |
|
} |
|
} |
|
palette[regno] = color; |
|
return 0; |
|
} |
|
|
|
/* We don't really support blanking. All this does is enable or |
|
disable the OSD. */ |
|
static int ivtvfb_blank(int blank_mode, struct fb_info *info) |
|
{ |
|
struct ivtv *itv = (struct ivtv *)info->par; |
|
|
|
IVTVFB_DEBUG_INFO("Set blanking mode : %d\n", blank_mode); |
|
switch (blank_mode) { |
|
case FB_BLANK_UNBLANK: |
|
ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 1); |
|
ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 1); |
|
break; |
|
case FB_BLANK_NORMAL: |
|
case FB_BLANK_HSYNC_SUSPEND: |
|
case FB_BLANK_VSYNC_SUSPEND: |
|
ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 0); |
|
ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 1); |
|
break; |
|
case FB_BLANK_POWERDOWN: |
|
ivtv_call_hw(itv, IVTV_HW_SAA7127, video, s_stream, 0); |
|
ivtv_vapi(itv, CX2341X_OSD_SET_STATE, 1, 0); |
|
break; |
|
} |
|
itv->osd_info->blank_cur = blank_mode; |
|
return 0; |
|
} |
|
|
|
static const struct fb_ops ivtvfb_ops = { |
|
.owner = THIS_MODULE, |
|
.fb_write = ivtvfb_write, |
|
.fb_check_var = ivtvfb_check_var, |
|
.fb_set_par = ivtvfb_set_par, |
|
.fb_setcolreg = ivtvfb_setcolreg, |
|
.fb_fillrect = cfb_fillrect, |
|
.fb_copyarea = cfb_copyarea, |
|
.fb_imageblit = cfb_imageblit, |
|
.fb_cursor = NULL, |
|
.fb_ioctl = ivtvfb_ioctl, |
|
.fb_pan_display = ivtvfb_pan_display, |
|
.fb_blank = ivtvfb_blank, |
|
}; |
|
|
|
/* Restore hardware after firmware restart */ |
|
static void ivtvfb_restore(struct ivtv *itv) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
int i; |
|
|
|
ivtvfb_set_var(itv, &oi->fbvar_cur); |
|
ivtvfb_blank(oi->blank_cur, &oi->ivtvfb_info); |
|
for (i = 0; i < 256; i++) { |
|
write_reg(i, 0x02a30); |
|
write_reg(oi->palette_cur[i], 0x02a34); |
|
} |
|
write_reg(oi->pan_cur, 0x02a0c); |
|
} |
|
|
|
/* Initialization */ |
|
|
|
|
|
/* Setup our initial video mode */ |
|
static int ivtvfb_init_vidmode(struct ivtv *itv) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
struct v4l2_rect start_window; |
|
int max_height; |
|
|
|
/* Color mode */ |
|
|
|
if (osd_depth != 8 && osd_depth != 16 && osd_depth != 32) |
|
osd_depth = 8; |
|
oi->bits_per_pixel = osd_depth; |
|
oi->bytes_per_pixel = oi->bits_per_pixel / 8; |
|
|
|
/* Horizontal size & position */ |
|
|
|
if (osd_xres > 720) |
|
osd_xres = 720; |
|
|
|
/* Must be a multiple of 4 for 8bpp & 2 for 16bpp */ |
|
if (osd_depth == 8) |
|
osd_xres &= ~3; |
|
else if (osd_depth == 16) |
|
osd_xres &= ~1; |
|
|
|
start_window.width = osd_xres ? osd_xres : 640; |
|
|
|
/* Check horizontal start (osd_left). */ |
|
if (osd_left && osd_left + start_window.width > 721) { |
|
IVTVFB_ERR("Invalid osd_left - assuming default\n"); |
|
osd_left = 0; |
|
} |
|
|
|
/* Hardware coords start at 0, user coords start at 1. */ |
|
osd_left--; |
|
|
|
start_window.left = osd_left >= 0 ? |
|
osd_left : ((IVTV_OSD_MAX_WIDTH - start_window.width) / 2); |
|
|
|
oi->display_byte_stride = |
|
start_window.width * oi->bytes_per_pixel; |
|
|
|
/* Vertical size & position */ |
|
|
|
max_height = itv->is_out_50hz ? 576 : 480; |
|
|
|
if (osd_yres > max_height) |
|
osd_yres = max_height; |
|
|
|
start_window.height = osd_yres ? |
|
osd_yres : itv->is_out_50hz ? 480 : 400; |
|
|
|
/* Check vertical start (osd_upper). */ |
|
if (osd_upper + start_window.height > max_height + 1) { |
|
IVTVFB_ERR("Invalid osd_upper - assuming default\n"); |
|
osd_upper = 0; |
|
} |
|
|
|
/* Hardware coords start at 0, user coords start at 1. */ |
|
osd_upper--; |
|
|
|
start_window.top = osd_upper >= 0 ? osd_upper : ((max_height - start_window.height) / 2); |
|
|
|
oi->display_width = start_window.width; |
|
oi->display_height = start_window.height; |
|
|
|
/* Generate a valid fb_var_screeninfo */ |
|
|
|
oi->ivtvfb_defined.xres = oi->display_width; |
|
oi->ivtvfb_defined.yres = oi->display_height; |
|
oi->ivtvfb_defined.xres_virtual = oi->display_width; |
|
oi->ivtvfb_defined.yres_virtual = oi->display_height; |
|
oi->ivtvfb_defined.bits_per_pixel = oi->bits_per_pixel; |
|
oi->ivtvfb_defined.vmode = (osd_laced ? FB_VMODE_INTERLACED : FB_VMODE_NONINTERLACED); |
|
oi->ivtvfb_defined.left_margin = start_window.left + 1; |
|
oi->ivtvfb_defined.upper_margin = start_window.top + 1; |
|
oi->ivtvfb_defined.accel_flags = FB_ACCEL_NONE; |
|
oi->ivtvfb_defined.nonstd = 0; |
|
|
|
/* We've filled in the most data, let the usual mode check |
|
routine fill in the rest. */ |
|
_ivtvfb_check_var(&oi->ivtvfb_defined, itv); |
|
|
|
/* Generate valid fb_fix_screeninfo */ |
|
|
|
ivtvfb_get_fix(itv, &oi->ivtvfb_fix); |
|
|
|
/* Generate valid fb_info */ |
|
|
|
oi->ivtvfb_info.node = -1; |
|
oi->ivtvfb_info.flags = FBINFO_FLAG_DEFAULT; |
|
oi->ivtvfb_info.par = itv; |
|
oi->ivtvfb_info.var = oi->ivtvfb_defined; |
|
oi->ivtvfb_info.fix = oi->ivtvfb_fix; |
|
oi->ivtvfb_info.screen_base = (u8 __iomem *)oi->video_vbase; |
|
oi->ivtvfb_info.fbops = &ivtvfb_ops; |
|
|
|
/* Supply some monitor specs. Bogus values will do for now */ |
|
oi->ivtvfb_info.monspecs.hfmin = 8000; |
|
oi->ivtvfb_info.monspecs.hfmax = 70000; |
|
oi->ivtvfb_info.monspecs.vfmin = 10; |
|
oi->ivtvfb_info.monspecs.vfmax = 100; |
|
|
|
/* Allocate color map */ |
|
if (fb_alloc_cmap(&oi->ivtvfb_info.cmap, 256, 1)) { |
|
IVTVFB_ERR("abort, unable to alloc cmap\n"); |
|
return -ENOMEM; |
|
} |
|
|
|
/* Allocate the pseudo palette */ |
|
oi->ivtvfb_info.pseudo_palette = |
|
kmalloc_array(16, sizeof(u32), GFP_KERNEL|__GFP_NOWARN); |
|
|
|
if (!oi->ivtvfb_info.pseudo_palette) { |
|
IVTVFB_ERR("abort, unable to alloc pseudo palette\n"); |
|
return -ENOMEM; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Find OSD buffer base & size. Add to mtrr. Zero osd buffer. */ |
|
|
|
static int ivtvfb_init_io(struct ivtv *itv) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
/* Find the largest power of two that maps the whole buffer */ |
|
int size_shift = 31; |
|
|
|
mutex_lock(&itv->serialize_lock); |
|
if (ivtv_init_on_first_open(itv)) { |
|
mutex_unlock(&itv->serialize_lock); |
|
IVTVFB_ERR("Failed to initialize ivtv\n"); |
|
return -ENXIO; |
|
} |
|
mutex_unlock(&itv->serialize_lock); |
|
|
|
if (ivtvfb_get_framebuffer(itv, &oi->video_rbase, |
|
&oi->video_buffer_size) < 0) { |
|
IVTVFB_ERR("Firmware failed to respond\n"); |
|
return -EIO; |
|
} |
|
|
|
/* The osd buffer size depends on the number of video buffers allocated |
|
on the PVR350 itself. For now we'll hardcode the smallest osd buffer |
|
size to prevent any overlap. */ |
|
oi->video_buffer_size = 1704960; |
|
|
|
oi->video_pbase = itv->base_addr + IVTV_DECODER_OFFSET + oi->video_rbase; |
|
oi->video_vbase = itv->dec_mem + oi->video_rbase; |
|
|
|
if (!oi->video_vbase) { |
|
IVTVFB_ERR("abort, video memory 0x%x @ 0x%lx isn't mapped!\n", |
|
oi->video_buffer_size, oi->video_pbase); |
|
return -EIO; |
|
} |
|
|
|
IVTVFB_INFO("Framebuffer at 0x%lx, mapped to 0x%p, size %dk\n", |
|
oi->video_pbase, oi->video_vbase, |
|
oi->video_buffer_size / 1024); |
|
|
|
while (!(oi->video_buffer_size & (1 << size_shift))) |
|
size_shift--; |
|
size_shift++; |
|
oi->fb_start_aligned_physaddr = oi->video_pbase & ~((1 << size_shift) - 1); |
|
oi->fb_end_aligned_physaddr = oi->video_pbase + oi->video_buffer_size; |
|
oi->fb_end_aligned_physaddr += (1 << size_shift) - 1; |
|
oi->fb_end_aligned_physaddr &= ~((1 << size_shift) - 1); |
|
oi->wc_cookie = arch_phys_wc_add(oi->fb_start_aligned_physaddr, |
|
oi->fb_end_aligned_physaddr - |
|
oi->fb_start_aligned_physaddr); |
|
/* Blank the entire osd. */ |
|
memset_io(oi->video_vbase, 0, oi->video_buffer_size); |
|
|
|
return 0; |
|
} |
|
|
|
/* Release any memory we've grabbed & remove mtrr entry */ |
|
static void ivtvfb_release_buffers (struct ivtv *itv) |
|
{ |
|
struct osd_info *oi = itv->osd_info; |
|
|
|
/* Release cmap */ |
|
if (oi->ivtvfb_info.cmap.len) |
|
fb_dealloc_cmap(&oi->ivtvfb_info.cmap); |
|
|
|
/* Release pseudo palette */ |
|
kfree(oi->ivtvfb_info.pseudo_palette); |
|
arch_phys_wc_del(oi->wc_cookie); |
|
kfree(oi); |
|
itv->osd_info = NULL; |
|
} |
|
|
|
/* Initialize the specified card */ |
|
|
|
static int ivtvfb_init_card(struct ivtv *itv) |
|
{ |
|
int rc; |
|
|
|
#ifdef CONFIG_X86_64 |
|
if (pat_enabled()) { |
|
if (ivtvfb_force_pat) { |
|
pr_info("PAT is enabled. Write-combined framebuffer caching will be disabled.\n"); |
|
pr_info("To enable caching, boot with nopat kernel parameter\n"); |
|
} else { |
|
pr_warn("ivtvfb needs PAT disabled for write-combined framebuffer caching.\n"); |
|
pr_warn("Boot with nopat kernel parameter to use caching, or use the\n"); |
|
pr_warn("force_pat module parameter to run with caching disabled\n"); |
|
return -ENODEV; |
|
} |
|
} |
|
#endif |
|
|
|
if (itv->osd_info) { |
|
IVTVFB_ERR("Card %d already initialised\n", ivtvfb_card_id); |
|
return -EBUSY; |
|
} |
|
|
|
itv->osd_info = kzalloc(sizeof(struct osd_info), |
|
GFP_KERNEL|__GFP_NOWARN); |
|
if (itv->osd_info == NULL) { |
|
IVTVFB_ERR("Failed to allocate memory for osd_info\n"); |
|
return -ENOMEM; |
|
} |
|
|
|
/* Find & setup the OSD buffer */ |
|
rc = ivtvfb_init_io(itv); |
|
if (rc) { |
|
ivtvfb_release_buffers(itv); |
|
return rc; |
|
} |
|
|
|
/* Set the startup video mode information */ |
|
if ((rc = ivtvfb_init_vidmode(itv))) { |
|
ivtvfb_release_buffers(itv); |
|
return rc; |
|
} |
|
|
|
/* Register the framebuffer */ |
|
if (register_framebuffer(&itv->osd_info->ivtvfb_info) < 0) { |
|
ivtvfb_release_buffers(itv); |
|
return -EINVAL; |
|
} |
|
|
|
itv->osd_video_pbase = itv->osd_info->video_pbase; |
|
|
|
/* Set the card to the requested mode */ |
|
ivtvfb_set_par(&itv->osd_info->ivtvfb_info); |
|
|
|
/* Set color 0 to black */ |
|
write_reg(0, 0x02a30); |
|
write_reg(0, 0x02a34); |
|
|
|
/* Enable the osd */ |
|
ivtvfb_blank(FB_BLANK_UNBLANK, &itv->osd_info->ivtvfb_info); |
|
|
|
/* Enable restart */ |
|
itv->ivtvfb_restore = ivtvfb_restore; |
|
|
|
/* Allocate DMA */ |
|
ivtv_udma_alloc(itv); |
|
itv->streams[IVTV_DEC_STREAM_TYPE_YUV].vdev.device_caps |= |
|
V4L2_CAP_VIDEO_OUTPUT_OVERLAY; |
|
itv->streams[IVTV_DEC_STREAM_TYPE_MPG].vdev.device_caps |= |
|
V4L2_CAP_VIDEO_OUTPUT_OVERLAY; |
|
itv->v4l2_cap |= V4L2_CAP_VIDEO_OUTPUT_OVERLAY; |
|
return 0; |
|
|
|
} |
|
|
|
static int __init ivtvfb_callback_init(struct device *dev, void *p) |
|
{ |
|
struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); |
|
struct ivtv *itv = container_of(v4l2_dev, struct ivtv, v4l2_dev); |
|
|
|
if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { |
|
if (ivtvfb_init_card(itv) == 0) { |
|
IVTVFB_INFO("Framebuffer registered on %s\n", |
|
itv->v4l2_dev.name); |
|
(*(int *)p)++; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
static int ivtvfb_callback_cleanup(struct device *dev, void *p) |
|
{ |
|
struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); |
|
struct ivtv *itv = container_of(v4l2_dev, struct ivtv, v4l2_dev); |
|
struct osd_info *oi = itv->osd_info; |
|
|
|
if (itv->v4l2_cap & V4L2_CAP_VIDEO_OUTPUT) { |
|
itv->streams[IVTV_DEC_STREAM_TYPE_YUV].vdev.device_caps &= |
|
~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; |
|
itv->streams[IVTV_DEC_STREAM_TYPE_MPG].vdev.device_caps &= |
|
~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; |
|
itv->v4l2_cap &= ~V4L2_CAP_VIDEO_OUTPUT_OVERLAY; |
|
unregister_framebuffer(&itv->osd_info->ivtvfb_info); |
|
IVTVFB_INFO("Unregister framebuffer %d\n", itv->instance); |
|
itv->ivtvfb_restore = NULL; |
|
ivtvfb_blank(FB_BLANK_VSYNC_SUSPEND, &oi->ivtvfb_info); |
|
ivtvfb_release_buffers(itv); |
|
itv->osd_video_pbase = 0; |
|
} |
|
return 0; |
|
} |
|
|
|
static int __init ivtvfb_init(void) |
|
{ |
|
struct device_driver *drv; |
|
int registered = 0; |
|
int err; |
|
|
|
|
|
if (ivtvfb_card_id < -1 || ivtvfb_card_id >= IVTV_MAX_CARDS) { |
|
pr_err("ivtvfb_card_id parameter is out of range (valid range: -1 - %d)\n", |
|
IVTV_MAX_CARDS - 1); |
|
return -EINVAL; |
|
} |
|
|
|
drv = driver_find("ivtv", &pci_bus_type); |
|
err = driver_for_each_device(drv, NULL, ®istered, ivtvfb_callback_init); |
|
(void)err; /* suppress compiler warning */ |
|
if (!registered) { |
|
pr_err("no cards found\n"); |
|
return -ENODEV; |
|
} |
|
return 0; |
|
} |
|
|
|
static void ivtvfb_cleanup(void) |
|
{ |
|
struct device_driver *drv; |
|
int err; |
|
|
|
pr_info("Unloading framebuffer module\n"); |
|
|
|
drv = driver_find("ivtv", &pci_bus_type); |
|
err = driver_for_each_device(drv, NULL, NULL, ivtvfb_callback_cleanup); |
|
(void)err; /* suppress compiler warning */ |
|
} |
|
|
|
module_init(ivtvfb_init); |
|
module_exit(ivtvfb_cleanup);
|
|
|