📄 planb.c
字号:
/* planb - v4l-compatible frame grabber driver for the PlanB hardware PlanB is used in the 7x00/8x00 series of PowerMacintosh Computers as video input DMA controller. Copyright (C) 1998 - 2002 Michel Lanners <mailto:mlan@cpu.lu> Based largely on the old bttv driver by Ralph Metzler Additional debugging and coding by Takashi Oe <mailto:toe@unlserve.unl.edu> For more information, see <http://www.cpu.lu/~mlan/planb.html> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.*//* $Id: planb.c,v 2.11 2002/04/03 15:57:57 mlan Exp mlan $ */#include <linux/version.h>#include <linux/init.h>#include <linux/errno.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/major.h>#include <linux/slab.h>#include <linux/types.h>#include <linux/pci.h>#include <linux/delay.h>#include <linux/vmalloc.h>#include <linux/mm.h>#include <linux/sched.h>#include <linux/poll.h>#include <linux/wrapper.h>#include <linux/tqueue.h>#include <linux/videodev.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/prom.h>#include <asm/dbdma.h>#include <asm/pgtable.h>#include <asm/page.h>#include <asm/irq.h>#include <asm/semaphore.h>/* Define these to get general / interrupt debugging */#undef DEBUG#undef IDEBUG//#define DEBUG#ifdef DEBUG#define DBG(x...) printk(KERN_DEBUG ## x)#else#define DBG(x...)#endif#ifdef IDEBUG#define IDBG(x...) printk(KERN_DEBUG ## x)#else#define IDBG(x...)#endif#include "planb.h"#include "saa7196.h"static struct planb planbs;static volatile struct planb_registers *planb_regs;static int def_norm = PLANB_DEF_NORM; /* default norm */static int video_nr = -1;static int vbi_nr = -1;MODULE_PARM(def_norm, "i");MODULE_PARM_DESC(def_norm, "Default startup norm (0=PAL, 1=NTSC, 2=SECAM)");MODULE_PARM(video_nr,"i");MODULE_PARM(vbi_nr,"i");MODULE_DESCRIPTION("planb - v4l driver module for Apple PlanB video in");MODULE_AUTHOR("Michel Lanners & Takashi Oe - see: http://www.cpu.lu/planb.html");MODULE_LICENSE("GPL");/* ------------------ PlanB Exported Functions ------------------ */static long planb_write(struct video_device *, const char *, unsigned long, int);static long planb_read(struct video_device *, char *, unsigned long, int);static int planb_open(struct video_device *, int);static void planb_close(struct video_device *);static int planb_ioctl(struct video_device *, unsigned int, void *);static int planb_mmap(struct video_device *, const char *, unsigned long);static void planb_irq(int, void *, struct pt_regs *);static int planb_vbi_open(struct video_device *, int);static void planb_vbi_close(struct video_device *);static long planb_vbi_read(struct video_device *, char *, unsigned long, int);static unsigned int planb_vbi_poll(struct video_device *, struct file *, poll_table *);static int planb_vbi_ioctl(struct video_device *, unsigned int, void *);static void release_planb(void);static int __init init_planbs(void);static void __exit exit_planbs(void);/* ------------------ PlanB Internal Functions ------------------ */static int planb_prepare_open(struct planb *);static int planb_prepare_vbi(struct planb *);static int planb_prepare_video(struct planb *);static void planb_prepare_close(struct planb *);static void planb_close_vbi(struct planb *);static void planb_close_video(struct planb *);static void saa_write_reg(unsigned char, unsigned char);static unsigned char saa_status(int, struct planb *);static void saa_set(unsigned char, unsigned char, struct planb *);static void saa_init_regs(struct planb *);static int grabbuf_alloc(struct planb *);static int vgrab(struct planb *, struct video_mmap *);static void add_clip(struct planb *, struct video_clip *);static void fill_cmd_buff(struct planb *);static void cmd_buff(struct planb *);static dbdma_cmd_ptr setup_grab_cmd(int, struct planb *);static void overlay_start(struct planb *);static void overlay_stop(struct planb *);static inline void tab_cmd_dbdma(dbdma_cmd_ptr, unsigned short, unsigned int);static inline void tab_cmd_store(dbdma_cmd_ptr, unsigned int, unsigned int);static inline void tab_cmd_gen(dbdma_cmd_ptr, unsigned short, unsigned short, unsigned int, unsigned int);static int init_planb(struct planb *);static int find_planb(void);static void planb_pre_capture(int, struct planb *);static dbdma_cmd_ptr cmd_geo_setup(dbdma_cmd_ptr, int, int, int, int, int, struct planb *);static inline void planb_dbdma_stop(dbdma_regs_ptr);static inline void planb_dbdma_restart(dbdma_regs_ptr);static void saa_geo_setup(int, int, int, int, struct planb *);static inline int overlay_is_active(struct planb *);/*******************************//* Memory management functions *//*******************************//* I know this is not the right way to allocate memory. Whoever knows * the right way to allocate a huge buffer for DMA that can be mapped * to user space, please tell me... or better, fix the code and send * patches. * * Michel Lanners (mlan@cpu.lu) *//* FIXME: As subsequent calls to __get_free_pages don't necessarily return * contiguous pages, we need to do horrible things later on when setting * up DMA, to make sure a single DMA transfer doesn't cross a page boundary. * At least, I hope it's done right later on ;-) ...... * Anyway, there should be a way to get hold of a large buffer of contiguous * pages for DMA.... */static int grabbuf_alloc(struct planb *pb){ int i, npage; npage = MAX_GBUFFERS * ((PLANB_MAX_FBUF / PAGE_SIZE + 1)#ifndef PLANB_GSCANLINE + MAX_LNUM#endif /* PLANB_GSCANLINE */ ); if ((pb->rawbuf = (unsigned char**) kmalloc (npage * sizeof(unsigned long), GFP_KERNEL)) == 0) return -ENOMEM; for (i = 0; i < npage; i++) { pb->rawbuf[i] = (unsigned char *)__get_free_pages(GFP_KERNEL | GFP_DMA, 0); if (!pb->rawbuf[i]) break; mem_map_reserve(virt_to_page(pb->rawbuf[i])); } if (i-- < npage) { DBG("PlanB: init_grab: grab buffer not allocated\n"); for (; i > 0; i--) { mem_map_unreserve(virt_to_page(pb->rawbuf[i])); free_pages((unsigned long)pb->rawbuf[i], 0); } kfree(pb->rawbuf); return -ENOBUFS; } pb->rawbuf_nchunks = npage; return 0;}/*****************************//* Hardware access functions *//*****************************/static void saa_write_reg(unsigned char addr, unsigned char val){ planb_regs->saa_addr = addr; eieio(); planb_regs->saa_regval = val; eieio(); return;}/* return status byte 0 or 1: */static unsigned char saa_status(int byte, struct planb *pb){ saa_regs[pb->win.norm][SAA7196_STDC] = (saa_regs[pb->win.norm][SAA7196_STDC] & ~2) | ((byte & 1) << 1); saa_write_reg (SAA7196_STDC, saa_regs[pb->win.norm][SAA7196_STDC]); /* Let's wait 30msec for this one */ current->state = TASK_INTERRUPTIBLE; schedule_timeout(30 * HZ / 1000); return (unsigned char)in_8 (&planb_regs->saa_status);}static void saa_set(unsigned char addr, unsigned char val, struct planb *pb){ if(saa_regs[pb->win.norm][addr] != val) { saa_regs[pb->win.norm][addr] = val; saa_write_reg (addr, val); } return;}static void saa_init_regs(struct planb *pb){ int i; for (i = 0; i < SAA7196_NUMREGS; i++) saa_write_reg (i, saa_regs[pb->win.norm][i]);}static void saa_geo_setup(int width, int height, int interlace, int fmt, struct planb *pb){ int ht, norm = pb->win.norm; /* bits FS0, FS1 according to format spec */ saa_regs[norm][SAA7196_FMTS] &= ~0x3; saa_regs[norm][SAA7196_FMTS] |= (palette2fmt[fmt].saa_fmt & 0x3); ht = (interlace ? height / 2 : height); saa_regs[norm][SAA7196_OUTPIX] = (unsigned char) (width & 0x00ff); saa_regs[norm][SAA7196_HFILT] = (saa_regs[norm][SAA7196_HFILT] & ~0x3) | (width >> 8 & 0x3); saa_regs[norm][SAA7196_OUTLINE] = (unsigned char) (ht & 0xff); saa_regs[norm][SAA7196_VYP] = (saa_regs[norm][SAA7196_VYP] & ~0x3) | (ht >> 8 & 0x3); /* feed both fields if interlaced, or else feed only even fields */ saa_regs[norm][SAA7196_FMTS] = (interlace) ? (saa_regs[norm][SAA7196_FMTS] & ~0x60) : (saa_regs[norm][SAA7196_FMTS] | 0x60); /* transparent mode; extended format enabled */ saa_regs[norm][SAA7196_DPATH] |= 0x3; /* bits LLV, MCT according to format spec */ saa_regs[norm][SAA7196_DPATH] &= ~0x30; saa_regs[norm][SAA7196_DPATH] |= (palette2fmt[fmt].saa_fmt & 0x30);}/***************************//* DBDMA support functions *//***************************/static inline void planb_dbdma_restart(dbdma_regs_ptr ch){ writel(PLANB_CLR(RUN), &ch->control); writel(PLANB_SET(RUN|WAKE) | PLANB_CLR(PAUSE), &ch->control);}static inline void planb_dbdma_stop(dbdma_regs_ptr ch){ int i = 0; writel(PLANB_CLR(RUN) | PLANB_SET(FLUSH), &ch->control); while((readl(&ch->status) == (ACTIVE | FLUSH)) && (i < 999)) { IDBG("PlanB: waiting for DMA to stop\n"); i++; }}static inline void tab_cmd_dbdma(dbdma_cmd_ptr ch, unsigned short command, unsigned int cmd_dep){ st_le16(&ch->command, command); st_le16(&ch->req_count, 0); st_le32(&ch->phy_addr, 0); st_le32(&ch->cmd_dep, cmd_dep); /* really clears res_count & xfer_status */ st_le32((unsigned int *)&ch->res_count, 0);}static inline void tab_cmd_store(dbdma_cmd_ptr ch, unsigned int phy_addr, unsigned int cmd_dep){ st_le16(&ch->command, STORE_WORD | KEY_SYSTEM); st_le16(&ch->req_count, 4); st_le32(&ch->phy_addr, phy_addr); st_le32(&ch->cmd_dep, cmd_dep); st_le32((unsigned int *)&ch->res_count, 0);}static inline void tab_cmd_gen(dbdma_cmd_ptr ch, unsigned short command, unsigned short req_count, unsigned int phy_addr, unsigned int cmd_dep){ st_le16(&ch->command, command); st_le16(&ch->req_count, req_count); st_le32(&ch->phy_addr, phy_addr); st_le32(&ch->cmd_dep, cmd_dep); st_le32((unsigned int *)&ch->res_count, 0);}static dbdma_cmd_ptr cmd_geo_setup(dbdma_cmd_ptr c1, int width, int height, int interlace, int fmt, int clip, struct planb *pb){ int norm = pb->win.norm; saa_geo_setup(width, height, interlace, fmt, pb); /* if the number of DBDMA commands here (14) changes, lots of * things need to be corrected accordingly... */ tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_addr), SAA7196_FMTS); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_regval), saa_regs[norm][SAA7196_FMTS]); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_addr), SAA7196_DPATH); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_regval), saa_regs[norm][SAA7196_DPATH]); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->even), palette2fmt[fmt].pb_fmt | ((clip)? PLANB_CLIPMASK: 0)); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->odd), palette2fmt[fmt].pb_fmt | ((clip)? PLANB_CLIPMASK: 0)); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_addr), SAA7196_OUTPIX); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_regval), saa_regs[norm][SAA7196_OUTPIX]); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_addr), SAA7196_HFILT); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_regval), saa_regs[norm][SAA7196_HFILT]); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_addr), SAA7196_OUTLINE); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_regval), saa_regs[norm][SAA7196_OUTLINE]); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_addr), SAA7196_VYP); tab_cmd_store(c1++, (unsigned)(&pb->planb_base_bus->saa_regval), saa_regs[norm][SAA7196_VYP]); return c1;}/******************************//* misc. supporting functions *//******************************/static inline void planb_lock(struct planb *pb){ DBG("PlanB: planb_lock\n"); down(&pb->lock);}static inline void planb_unlock(struct planb *pb){ DBG("PlanB: planb_unlock\n"); up(&pb->lock);}/***************//* Driver Core *//***************//* number of entries in the circular DBDMA command buffer * initial stop, odd/even vbi, odd/even video, branch back */#define NUMJUMPS 6static int planb_prepare_open(struct planb *pb){ dbdma_cmd_ptr c; int size, i; size = (NUMJUMPS + 1) * sizeof(struct dbdma_cmd); if((pb->jump_raw = kmalloc (size, GFP_KERNEL|GFP_DMA)) == 0) return -ENOMEM; memset(pb->jump_raw, 0, size); c = pb->jumpbuf = (dbdma_cmd_ptr) DBDMA_ALIGN (pb->jump_raw); /* circular DBDMA command buffer, to hold jumps to transfer commands */ tab_cmd_dbdma(c++, DBDMA_STOP, 0); for (i=1; i<NUMJUMPS-1; i++) tab_cmd_dbdma(c++, DBDMA_NOP, 0); tab_cmd_dbdma(c, DBDMA_NOP|BR_ALWAYS, (unsigned int)pb->jumpbuf); DBG("PlanB: planb_prepare_open, jumpbuffer at 0x%08x, length %d.\n", (unsigned int)pb->jumpbuf, size); return 0;}#define VBIDUMMY 40 /* must be even !! */static int planb_prepare_vbi(struct planb *pb){ int size; /* allocate VBI comand buffer memory (2 fields * VBI_MAXLINES + 40 handling + alignment) */ size = (2*VBI_MAXLINES + VBIDUMMY + 1) * sizeof(struct dbdma_cmd); if ((pb->vbi_raw = kmalloc (size, GFP_KERNEL|GFP_DMA)) == 0) return -ENOMEM; memset (pb->vbi_raw, 0, size); size = (VBI_MAXLINES + VBIDUMMY/2) * sizeof(struct dbdma_cmd); pb->vbi_cbo.start = (dbdma_cmd_ptr) DBDMA_ALIGN (pb->vbi_raw); pb->vbi_cbo.size = pb->vbi_cbe.size = size; pb->vbi_cbe.start = pb->vbi_cbo.start + pb->vbi_cbo.size; pb->vbi_cbo.jumpaddr = pb->jumpbuf + 1; pb->vbi_cbe.jumpaddr = pb->jumpbuf + 3; DBG("PlanB: planb_prepare_vbi, dbdma cmd_buf at 0x%08x, length %d.\n", (unsigned int)pb->vbi_cbo.start, 2*size); return 0;}static int planb_prepare_video(struct planb *pb){ int i, size; /* FIXME: This is stressing kmalloc to its limits... We really should allocate smaller chunks. */ /* allocate memory for two plus alpha command buffers (size: max lines, plus 40 commands handling, plus 1 alignment), plus dummy command buf, plus clipmask buffer, plus frame grabbing status */ size = (pb->tab_size * (2 + MAX_GBUFFERS * TAB_FACTOR) + MAX_GBUFFERS * PLANB_DUMMY + 1) * sizeof(struct dbdma_cmd) + (PLANB_MAXLINES * ((PLANB_MAXPIXELS + 7) & ~7)) / 8 + MAX_GBUFFERS * sizeof(unsigned int); if ((pb->vid_raw = kmalloc (size, GFP_KERNEL|GFP_DMA)) == 0) return -ENOMEM; memset (pb->vid_raw, 0, size); pb->vid_cbo.start = (dbdma_cmd_ptr) DBDMA_ALIGN (pb->vid_raw); pb->vid_cbo.size = pb->vid_cbe.size = pb->tab_size/2; pb->vid_cbe.start = pb->vid_cbo.start + pb->vid_cbo.size; pb->vid_cbo.jumpaddr = pb->jumpbuf + 2; pb->vid_cbe.jumpaddr = pb->jumpbuf + 4; pb->overlay_last1 = pb->vid_cbo.start; pb->vid_cbo.bus = virt_to_bus(pb->vid_cbo.start); pb->vid_cbe.bus = virt_to_bus(pb->vid_cbe.start); pb->clip_cbo.start = pb->vid_cbe.start + pb->vid_cbe.size; pb->clip_cbo.size = pb->clip_cbe.size = pb->tab_size/2; pb->clip_cbe.start = pb->clip_cbo.start + pb->clip_cbo.size; pb->overlay_last2 = pb->clip_cbo.start; pb->clip_cbo.bus = virt_to_bus(pb->clip_cbo.start); pb->clip_cbe.bus = virt_to_bus(pb->clip_cbe.start); pb->gbuf[0].cap_cmd = pb->clip_cbe.start + pb->clip_cbe.size; pb->gbuf[0].pre_cmd = pb->gbuf[0].cap_cmd + pb->tab_size * TAB_FACTOR; for (i = 1; i < MAX_GBUFFERS; i++) { pb->gbuf[i].cap_cmd = pb->gbuf[i-1].pre_cmd + PLANB_DUMMY; pb->gbuf[i].pre_cmd = pb->gbuf[i].cap_cmd + pb->tab_size * TAB_FACTOR; } pb->gbuf[0].status = (volatile unsigned int *) (pb->gbuf[MAX_GBUFFERS-1].pre_cmd + PLANB_DUMMY); for (i = 1; i < MAX_GBUFFERS; i++) pb->gbuf[i].status = pb->gbuf[i-1].status;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -