コード例 #1
0
ファイル: vloopback.c プロジェクト: rfajardo/devmech
static int vloopback_release(struct file *f)
#endif
{
    struct video_device *loopdev = video_devdata(f);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
    priv_ptr ptr = (priv_ptr)dev_get_drvdata(&loopdev->dev);
#else
    priv_ptr ptr = (priv_ptr)loopdev->vd_private_data;
#endif    
    int nr = ptr->pipenr;

    if (debug > LOG_NODEBUG)
        info("Video loopback %d", nr);

    if (ptr->in) {
        down(&loops[nr]->lock);
        if (loops[nr]->buffer && !loops[nr]->ropen) {
            rvfree(loops[nr]->buffer, loops[nr]->buflength * num_buffers);
            loops[nr]->buffer = NULL;
        }

        up(&loops[nr]->lock);
        loops[nr]->frameswrite++;
        
        if (waitqueue_active(&loops[nr]->wait))
            wake_up(&loops[nr]->wait);

        loops[nr]->width = 0;
        loops[nr]->height = 0;
        loops[nr]->palette = 0;
        loops[nr]->wopen = 0;
        pipesused--;
    } else {
        down(&loops[nr]->lock);

        if (loops[nr]->buffer && !loops[nr]->wopen) {
            rvfree(loops[nr]->buffer, loops[nr]->buflength * num_buffers);
            loops[nr]->buffer = NULL;
        }

        up(&loops[nr]->lock);
        loops[nr]->ropen = 0;

        if (loops[nr]->zerocopy && loops[nr]->buffer) {
            loops[nr]->ioctlnr = 0;
            loops[nr]->ioctllength = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
            kill_proc(loops[nr]->pid, SIGIO, 1);
#else
            kill_pid(loops[nr]->pid, SIGIO, 1);
#endif            
        }
    }

    return 0;
}
コード例 #2
0
ファイル: vloopback.c プロジェクト: ravalnet/xarxa-omnia
static int vloopback_release(struct inode *inod, struct file *f)
#endif
{
	struct video_device *loopdev=video_devdata(f);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
	priv_ptr ptr=(priv_ptr)video_get_drvdata(loopdev);
#else
	priv_ptr ptr=(priv_ptr)loopdev->priv;
#endif	
	int nr=ptr->pipenr;
	
	if (ptr->in) {
		down(&loops[nr]->lock);
		if (loops[nr]->buffer && !loops[nr]->ropen) {
			rvfree(loops[nr]->buffer,
			    loops[nr]->buflength*N_BUFFS);
			loops[nr]->buffer=NULL;
		}
		up(&loops[nr]->lock);
		if (waitqueue_active(&loops[nr]->wait)) {
			loops[nr]->frameswrite++;
			wake_up(&loops[nr]->wait);
		}

		loops[nr]->width=0;
		loops[nr]->height=0;
		loops[nr]->palette=0;
		loops[nr]->wopen=0;
		pipesused--;
	} else {
		down(&loops[nr]->lock);
		if (loops[nr]->buffer && !loops[nr]->wopen) {
			rvfree(loops[nr]->buffer,
			    loops[nr]->buflength*N_BUFFS);
			loops[nr]->buffer=NULL;
		}
		up(&loops[nr]->lock);
		if (waitqueue_active(&loops[nr]->wait)) {
			loops[nr]->pendingread = 1;
			wake_up(&loops[nr]->wait);
		}
		loops[nr]->ropen=0;
		if (loops[nr]->zerocopy && loops[nr]->buffer) {
			loops[nr]->ioctlnr=0;
			loops[nr]->ioctllength=0;
			kill_proc(loops[nr]->pid, SIGIO, 1);
		}
	}

	return 0;
}
コード例 #3
0
ファイル: vloopback.c プロジェクト: rfajardo/devmech
static void __exit cleanup_vloopback_module(void)
{
    int i;

    info("Unregistering video4linux loopback devices");

    for (i = 0; i < nr_o_pipes; i++) {
        if (loops[i]) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)            
            priv_ptr ptr_in = (priv_ptr)dev_get_drvdata(&loops[i]->vloopin->dev);
            kfree(ptr_in);
            dev_set_drvdata(&loops[i]->vloopin->dev, NULL);
#else
            kfree(loops[i]->vloopin->vd_private_data);
#endif            
            video_unregister_device(loops[i]->vloopin);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)            
            priv_ptr ptr_out = (priv_ptr)dev_get_drvdata(&loops[i]->vloopout->dev);
            kfree(ptr_out);
            dev_set_drvdata(&loops[i]->vloopout->dev, NULL);
#else
            kfree(loops[i]->vloopout->vd_private_data);
#endif            
            video_unregister_device(loops[i]->vloopout);
            
            if (loops[i]->buffer) 
                rvfree(loops[i]->buffer, loops[i]->buflength * num_buffers);

            kfree(loops[i]->ioctldata);
            kfree(loops[i]->ioctlretdata);
            kfree(loops[i]);
        }
    }        
}
コード例 #4
0
ファイル: shm.c プロジェクト: Enextuse/RTAI
static inline void *_rt_shm_alloc(unsigned long name, int size, int suprt)
{
	void *adr;

//	suprt = USE_GFP_ATOMIC; // to force some testing
	if (!(adr = rt_get_adr_cnt(name)) && size > 0 && suprt >= 0 && RT_SHM_OP_PERM())
	{
		size = ((size - 1) & PAGE_MASK) + PAGE_SIZE;
		if ((adr = suprt ? rkmalloc(&size, SUPRT[suprt]) : rvmalloc(size)))
		{
			if (!rt_register(name, adr, suprt ? -size : size, 0))
			{
				if (suprt)
				{
					rkfree(adr, size);
				}
				else
				{
					rvfree(adr, size);
				}
				return 0;
			}
			memset(ALIGN2PAGE(adr), 0, size);
		}
	}
	return ALIGN2PAGE(adr);
}
コード例 #5
0
ファイル: dhahelper.c プロジェクト: Caught/openpliPC
static int dhahelper_free_pa(dhahelper_mem_t *arg)
{
	dhahelper_mem_t mem;
	if (copy_from_user(&mem, arg, sizeof(dhahelper_mem_t)))
	{
		if (dhahelper_verbosity > 0)
		    printk(KERN_ERR "dhahelper: failed copy from userspace\n");
		return -EFAULT;
	}
	rvfree(mem.addr,mem.length);
	return 0;
}
コード例 #6
0
ファイル: qtft_fb.c プロジェクト: dujiepeng/module
static int qtft_fb_remove(struct platform_device *dev)
{
	struct fb_info *info = platform_get_drvdata(dev);

	func_in();
	if (info)
	{
		lcd_exit();
		unregister_framebuffer(info);
		fb_dealloc_cmap(&info->cmap);
		framebuffer_release(info);
		rvfree(videomemory, videomemorysize);
	}
	func_out();
	return 0;
}
コード例 #7
0
static inline int _rt_shm_free(unsigned long name, int size)
{
	void *adr;

	if (size && (adr = rt_get_adr(name))) {
		if (RT_SHM_OP_PERM()) {
			if (!rt_drg_on_name_cnt(name) && name != GLOBAL_HEAP_ID) {
				if (size < 0) {
					rkfree(adr, -size);
				} else {
					rvfree(adr, size);
				}
			}
		}
		return abs(size);
	}
	return 0;
}
コード例 #8
0
ファイル: vloopback.c プロジェクト: ravalnet/xarxa-omnia
static void __exit cleanup_vloopback_module(void)
{
	int i;

	info("Unregistering video4linux loopback devices");
	for (i=0; i<nr_o_pipes; i++) if (loops[i]) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
		kfree(((priv_ptr)video_get_drvdata(loops[i]->vloopin)));
		video_unregister_device(loops[i]->vloopin);
		kfree(((priv_ptr)video_get_drvdata(loops[i]->vloopout)));
#else
		kfree(loops[i]->vloopin->priv);
		video_unregister_device(loops[i]->vloopin);
		kfree(loops[i]->vloopout->priv);
#endif	
		video_unregister_device(loops[i]->vloopout);
		if (loops[i]->buffer) rvfree(loops[i]->buffer, loops[i]->buflength*N_BUFFS);
		kfree(loops[i]->ioctldata);
		kfree(loops[i]->ioctlretdata);
		kfree(loops[i]);
	}
}
コード例 #9
0
ファイル: op_dname.c プロジェクト: OPSF/uClinux
void oprof_free_hashmap(void)
{
	kfree(op_dname_stack);
	rvfree(hash_map, PAGE_ALIGN(OP_HASH_MAP_SIZE));
}
コード例 #10
0
ファイル: vloopback.c プロジェクト: ravalnet/xarxa-omnia
static int vloopback_mmap(struct file *f, struct vm_area_struct *vma)
{
	struct video_device *loopdev=video_devdata(f);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
	priv_ptr ptr=(priv_ptr)video_get_drvdata(loopdev);
#else
	priv_ptr ptr=(priv_ptr)loopdev->priv;
#endif	
	int nr=ptr->pipenr;
	unsigned long start = (unsigned long)vma->vm_start;
	long size = vma->vm_end - vma->vm_start;
	unsigned long page, pos;

	down(&loops[nr]->lock);
	if (ptr->in) {
		loops[nr]->zerocopy=1;
		if (loops[nr]->ropen) {
			info("Can't change size while opened for read");
			up(&loops[nr]->lock);
			return -EINVAL;
		}
		if (!size) {
			up(&loops[nr]->lock);
			return -EINVAL;
		}
		if (loops[nr]->buffer)
			rvfree(loops[nr]->buffer, loops[nr]->buflength*N_BUFFS);
		loops[nr]->buflength=size;
		loops[nr]->buffer=rvmalloc(loops[nr]->buflength*N_BUFFS);
	}
        if (loops[nr]->buffer == NULL) {
		up(&loops[nr]->lock);
                return -EINVAL;
	}

        if (size > (((N_BUFFS * loops[nr]->buflength) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))) {
		up(&loops[nr]->lock);
                return -EINVAL;
	}

        pos = (unsigned long)loops[nr]->buffer;
        while (size > 0) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,9)
                page = kvirt_to_pa(pos);
                if (remap_page_range(vma,start, page, PAGE_SIZE, PAGE_SHARED)) {
#else
		page = vmalloc_to_pfn((void *)pos);
		if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) {
#endif
			up(&loops[nr]->lock);
                        return -EAGAIN;
		}
                start += PAGE_SIZE;
                pos += PAGE_SIZE;
		size -= PAGE_SIZE;
        }
	up(&loops[nr]->lock);

	return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
static long vloopback_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
#else
static int vloopback_ioctl(struct inode *inod, struct file *f, unsigned int cmd, unsigned long arg)
#endif
{
	struct video_device *loopdev=video_devdata(f);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
	priv_ptr ptr=(priv_ptr)video_get_drvdata(loopdev);
#else
	priv_ptr ptr=(priv_ptr)loopdev->priv;
#endif	
	int nr=ptr->pipenr;
	int i;

	if (loops[nr]->zerocopy) {
		if (!ptr->in) {
			loops[nr]->ioctlnr=cmd;
			loops[nr]->ioctllength=_IOC_SIZE(cmd);
			/* info("DEBUG: vl_ioctl: !loop->in"); */
			/* info("DEBUG: vl_ioctl: cmd %lu", cmd); */
			/* info("DEBUG: vl_ioctl: len %lu", loops[nr]->ioctllength); */
			if(copy_from_user(loops[nr]->ioctldata, (void*)arg, _IOC_SIZE(cmd)))
				return -EFAULT;
			kill_proc(loops[nr]->pid, SIGIO, 1);
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
			wait_event_interruptible(loops[nr]->wait, loops[nr]->ioctlnr==-1);
#else
			interruptible_sleep_on(&loops[nr]->wait);
#endif			
			
			if (loops[nr]->invalid_ioctl) {
				//info ("DEBUG: There was an invalid ioctl");
				loops[nr]->invalid_ioctl = 0;
				return -ENOTTY;
			}
			if (cmd & IOC_IN && !(cmd & IOC_OUT)) {
				//info("DEBUG: vl_ioctl: cmd & IOC_IN 1"); 
				if (memcmp(loops[nr]->ioctlretdata, loops[nr]->ioctldata, _IOC_SIZE(cmd))) {
					return -EINVAL;
				}
			 	//info("DEBUG: vl_ioctl: cmd & IOC_IN 2"); 
				return 0;
			} else {
				if (copy_to_user((void*)arg, loops[nr]->ioctlretdata, _IOC_SIZE(cmd)))
					return -EFAULT;
				//info("DEBUG: vl_ioctl: !(cmd & IOC_IN) 1");
				return 0;
			}
		} else {
			if ( (loops[nr]->ioctlnr!=cmd) && (cmd != (VIDIOCSINVALID))) {
				/* wrong ioctl */
				info("DEBUG: vo_ioctl: Wrong IOCTL");
				return 0;
			}
			if (cmd == VIDIOCSINVALID) {
				loops[nr]->invalid_ioctl = 1;
			} else {
				if (copy_from_user(loops[nr]->ioctlretdata, (void*)arg, loops[nr]->ioctllength))
					return -EFAULT;
			}
			loops[nr]->ioctlnr=-1;
			if (waitqueue_active(&loops[nr]->wait))
				wake_up(&loops[nr]->wait);
			return 0;
		}
	}
	switch(cmd)
	{
		/* Get capabilities */
		case VIDIOCGCAP:
		{
			struct video_capability b;
			if (ptr->in) {
				sprintf(b.name, "Video loopback %d input",
				    ptr->pipenr);
				b.type = 0;
			} else {
				sprintf(b.name, "Video loopback %d output",
				    ptr->pipenr);
				b.type = VID_TYPE_CAPTURE;
			}
			b.channels=1;
			b.audios=0;
			b.maxwidth=loops[nr]->width;
			b.maxheight=loops[nr]->height;
			b.minwidth=20;
			b.minheight=20;
			if(copy_to_user((void*)arg, &b, sizeof(b)))
				return -EFAULT;
			return 0;
		}
		/* Get channel info (sources) */
		case VIDIOCGCHAN:
		{
			struct video_channel v;
			if(copy_from_user(&v, (void*)arg, sizeof(v)))
				return -EFAULT;
			if(v.channel!=0) {
				info("VIDIOCGCHAN: Invalid Channel, was %d", v.channel);
				v.channel=0;
				//return -EINVAL;
			}
			v.flags=0;
			v.tuners=0;
			v.norm=0;
			v.type = VIDEO_TYPE_CAMERA;
			/*strcpy(v.name, "Loopback"); -- tibit */
			strcpy(v.name, "Composite1");
			if(copy_to_user((void*)arg, &v, sizeof(v)))
				return -EFAULT;
			return 0;
		}
		/* Set channel 	*/
		case VIDIOCSCHAN:
		{
			int v;
			if(copy_from_user(&v, (void*)arg, sizeof(v)))
				return -EFAULT;
			if(v!=0) {
				info("VIDIOCSCHAN: Invalid Channel, was %d", v);
				return -EINVAL;
			}
			return 0;
		}
		/* Get tuner abilities */
		case VIDIOCGTUNER:
		{
			struct video_tuner v;
			if(copy_from_user(&v, (void*)arg, sizeof(v))!=0)
				return -EFAULT;
			if(v.tuner) {
				info("VIDIOCGTUNER: Invalid Tuner, was %d", v.tuner);
				return -EINVAL;
			}
			strcpy(v.name, "Format");
			v.rangelow=0;
			v.rangehigh=0;
			v.flags=0;
			v.mode=VIDEO_MODE_AUTO;
			if(copy_to_user((void*)arg,&v, sizeof(v))!=0)
				return -EFAULT;
			return 0;
		}
		/* Get picture properties */
		case VIDIOCGPICT:
		{
			struct video_picture p;
			p.colour=0x8000;
			p.hue=0x8000;
			p.brightness=0x8000;
			p.contrast=0x8000;
			p.whiteness=0x8000;
			p.depth=0x8000;
			p.palette=loops[nr]->palette;
			if(copy_to_user((void*)arg, &p, sizeof(p)))
				return -EFAULT;
			return 0;

		}
		/* Set picture properties */
		case VIDIOCSPICT:
		{
			struct video_picture p;
			if(copy_from_user(&p, (void*)arg, sizeof(p)))
				return -EFAULT;
			if (!ptr->in) {
				if (p.palette!=loops[nr]->palette)
					return -EINVAL;
			} else
				loops[nr]->palette=p.palette;
			return 0;
		}
		/* Get the video overlay window */
		case VIDIOCGWIN:
		{
			struct video_window vw;
			vw.x=0;
			vw.y=0;
			vw.width=loops[nr]->width;
			vw.height=loops[nr]->height;
			vw.chromakey=0;
			vw.flags=0;
			vw.clipcount=0;
			if(copy_to_user((void*)arg, &vw, sizeof(vw)))
				return -EFAULT;
			return 0;
		}
		/* Set the video overlay window - passes clip list for hardware smarts , chromakey etc */
		case VIDIOCSWIN:
		{
			struct video_window vw;
			
			if(copy_from_user(&vw, (void*)arg, sizeof(vw)))
				return -EFAULT;
			if(vw.flags)
				return -EINVAL;
			if(vw.clipcount)
				return -EINVAL;
			if (loops[nr]->height==vw.height &&
			    loops[nr]->width==vw.width)
				return 0;
			if(!ptr->in) {
				return -EINVAL;
			} else {
				loops[nr]->height=vw.height;
				loops[nr]->width=vw.width;
				/* Make sure nobody is using the buffer while we
				   fool around with it.
				   We are also not allowing changes while
				   somebody using mmap has the output open.
				 */
				down(&loops[nr]->lock);
				if (loops[nr]->ropen) {
					info("Can't change size while opened for read");
					up(&loops[nr]->lock);
					return -EINVAL;
				}
				if (loops[nr]->buffer)
					rvfree(loops[nr]->buffer, loops[nr]->buflength*N_BUFFS);
				loops[nr]->buflength=vw.width*vw.height*4;
				loops[nr]->buffer=rvmalloc(loops[nr]->buflength*N_BUFFS);
				up(&loops[nr]->lock);
			}
			return 0;
		}
		/* Memory map buffer info */
		case VIDIOCGMBUF:
		{
			struct video_mbuf vm;
			
			vm.size=loops[nr]->buflength*N_BUFFS;
			vm.frames=N_BUFFS;
			for (i=0; i<vm.frames; i++)
				vm.offsets[i]=i*loops[nr]->buflength;
			if(copy_to_user((void*)arg, &vm, sizeof(vm)))
				return -EFAULT;
			return 0;
		}
		/* Grab frames */
		case VIDIOCMCAPTURE:
		{
			struct video_mmap vm;

			if (ptr->in)
				return -EINVAL;
			if (!loops[nr]->buffer)
				return -EINVAL;
			if (copy_from_user(&vm, (void*)arg, sizeof(vm)))
				return -EFAULT;
			if (vm.format!=loops[nr]->palette)
				return -EINVAL;
			if (vm.frame > N_BUFFS)
				return -EINVAL;
			return 0;
		}
		/* Sync with mmap grabbing */
		case VIDIOCSYNC:
		{
			int frame;
			unsigned long fw;

			if (copy_from_user((void *)&frame, (void*)arg, sizeof(int)))
				return -EFAULT;
			if (ptr->in)
				return -EINVAL;
			if (!loops[nr]->buffer)
				return -EINVAL;
			/* Ok, everything should be alright since the program
			   should have called VIDIOMCAPTURE and we are ready to
			   do the 'capturing' */
			if (frame > 1)
				return -EINVAL;
			loops[nr]->frame=frame;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
			fw = loops[nr]->frameswrite;
			wait_event_interruptible(loops[nr]->wait, fw!=loops[nr]->frameswrite);
#else
			interruptible_sleep_on(&loops[nr]->wait);
#endif			
			if (!loops[nr]->buffer)		/* possibly released during sleep */
				return -EINVAL;
			loops[nr]->framesread++;
			return 0;
		}
		/* Get attached units */
		case VIDIOCGUNIT:
		{
			struct video_unit vu;
			
			if (ptr->in)
				vu.video=loops[nr]->vloopout->minor;
			else
				vu.video=loops[nr]->vloopin->minor;
			vu.vbi=VIDEO_NO_UNIT;
			vu.radio=VIDEO_NO_UNIT;
			vu.audio=VIDEO_NO_UNIT;
			vu.teletext=VIDEO_NO_UNIT;
			if (copy_to_user((void*)arg, &vu, sizeof(vu)))
				return -EFAULT;
			return 0;
		}
		/* Get frame buffer */
		case VIDIOCGFBUF:
		{
			struct video_buffer vb;

			memset(&vb, 0, sizeof(vb));
			vb.base=NULL;

			if(copy_to_user((void *)arg, (void *)&vb, sizeof(vb)))
				return -EFAULT;

			return 0;
		}
		/* Start, end capture */
		case VIDIOCCAPTURE:
		{
			int start;
			if (copy_from_user(&start, (void*)arg, sizeof(int)))
				return -EFAULT;
			/*
			if (start) info ("Capture started");
			else info ("Capture stopped");
			*/

			return 0;
		}

		case VIDIOCGFREQ:
		case VIDIOCSFREQ:
		case VIDIOCGAUDIO:
		case VIDIOCSAUDIO:
			return -EINVAL;
		case VIDIOCKEY:
			return 0;
		default:
			return -ENOTTY;
			//return -ENOIOCTLCMD;
	}
	return 0;
}

static unsigned int vloopback_poll(struct file *f, struct poll_table_struct *wait)
{
	struct video_device *loopdev=video_devdata(f);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
	priv_ptr ptr=(priv_ptr)video_get_drvdata(loopdev);
#else
	priv_ptr ptr=(priv_ptr)loopdev->priv;
#endif	
	int nr=ptr->pipenr;

	if (loopdev==NULL)
		return -EFAULT;
	if (!ptr->in)
		return 0;

	if (loops[nr]->ioctlnr!=-1) {
		if (loops[nr]->zerocopy) {
			return (POLLIN | POLLPRI | POLLOUT | POLLRDNORM);
		} else {
			return (POLLOUT);
		}
	}
	return 0;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
static struct v4l2_file_operations fileops_template=
#else
static struct file_operations fileops_template=
#endif
{
	owner:		THIS_MODULE,
	open:		vloopback_open,
	release:	vloopback_release,
	read:		vloopback_read,
	write:		vloopback_write,
	poll:		vloopback_poll,
	ioctl:		vloopback_ioctl,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,15) && defined(CONFIG_COMPAT)
	compat_ioctl:	v4l_compat_ioctl32,
#endif
	mmap:		vloopback_mmap,
};
コード例 #11
0
ファイル: qtft_fb.c プロジェクト: dujiepeng/module
static int qtft_fb_probe(struct platform_device * dev)
{
	struct fb_info *info;
	int retval = -ENOMEM;

	func_in();

	// 分配显存
	if (!(videomemory = rvmalloc(videomemorysize)))
		return retval;
	memset(videomemory, 0, videomemorysize);

	// 动态分配 fb_info
	info = framebuffer_alloc((sizeof(u32)*16), &dev->dev);
	if (!info)
		goto err0;

	// 虚拟地址
	info->screen_base = (char __iomem *)videomemory;
	info->screen_size = videomemorysize;
	
	// 文件操作符 qtft_fb_ops 在 ops.c 中定义
	info->fbops = &qtft_fb_ops;

	info->var = qtft_fb_var_default;

	qtft_fb_fix_default.smem_start = (unsigned long) videomemory;
	qtft_fb_fix_default.smem_len = videomemorysize;
	info->fix = qtft_fb_fix_default;
	
	// 16色伪调色板指针指向了 info->par 的私有空间
	info->pseudo_palette = info->par;
	info->par = NULL;
	info->flags = FBINFO_DEFAULT;

	// 为16色伪调色板分配内存
	retval = fb_alloc_cmap(&info->cmap, 16, 0);
	if (retval < 0)
		goto err1;

	retval = register_framebuffer(info);
	if (retval < 0)
		goto err2;

	// 初始化 LCD 模块
	retval = lcd_init();
	if (retval < 0)
		goto err3;

	retval = lcd_hard_reset();
	if (retval < 0)
		goto err3;

	retval = lcd_normal_config();
	if (retval < 0)
		goto err3;

	lcd_address_set(0,0,(info->var).xres,(info->var).yres);
	
	// 将 info 指针存入平台设备私有数据
	platform_set_drvdata(dev, info);

	printk(KERN_INFO "SPI QVGA TFT LCD driver: fb%d, %ldK video memory\n", info->node, videomemorysize >> 10);
	goto out;

err3:
	unregister_framebuffer(info);
err2:
	fb_dealloc_cmap(&info->cmap);
err1:
	framebuffer_release(info);
err0:
	rvfree(videomemory, videomemorysize);
out:
	func_out();
	return retval;
}