/* linux/drivers/media/video/samsung/g2d/s3c_fimg2d2x.c
 *
 * Driver file for Samsung 2D Accelerator(FIMG-2D)
 *
 * Jonghun Han, Copyright (c) 2009 Samsung Electronics
 *     http://www.samsungsemi.com/
 *
 * add some notes by tommy.hong@2009.05.09
 *
 * 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.
*/

由于时间不多,部分用了C++的注释风格,请原谅。
#include <linux/module.h> module_init,module_exit
#include <linux/kernel.h>
#include <linux/errno.h>ENOENT and etc
#include <linux/clk.h>//clk_get
#include <linux/poll.h>//poll_table and etc
#include <linux/fs.h>//more imporant file structure ,such as file_operation operation and etc
#include <linux/irq.h>//IRQ_DISABLED and etc
#include <linux/mm.h>//vm_area_struct
#include <linux/interrupt.h>//IRQ and etc
#include <linux/platform_device.h>//platform_device
#include <linux/miscdevice.h>//misc_register
#include <asm/io.h>//ioremap ,__raw_readl and __raw_writl and etc
#include <mach/map.h>//FIMG2D Physical  address definition
#include <plat/regs-g2d.h>
#include "s3c_fimg2d2x.h"

static struct clk *s3c_g2d_clock;//for s3c_g2d_clk use by clk_get
static struct clk *h_clk;//for s3c6410 HCLK,because fimg2d clk generate from devider of HCLK
static int s3c_g2d_irq_num = NO_IRQ;//fimg2d irq ,initialize
static struct resource *s3c_g2d_mem;//resource for memory region
static void __iomem *s3c_g2d_base;//for ioremap to set FIMG2D register
static wait_queue_head_t waitq_g2d;//wait_queue_head_t use for kernel process schedule
static struct mutex *h_rot_mutex;//Kernel lock

static u16 s3c_g2d_poll_flag = 0;//use for Poll during processing,device driver need this

/*
 * Here is S3C6410 FIMG2D command FIFO check or monitor ,must do it by user;
 * If FIFO Entries reach FIFO FULL ,and full interrupt occurs.
 * FIFO full number is 32.
 * */
void s3c_g2d_check_fifo(int empty_fifo)
{
    u32 val;

    val = __raw_readl(s3c_g2d_base + S3C_G2D_FIFO_STAT_REG);
    while( S3C_G2D_FIFO_USED(val) > (FIFO_NUM - empty_fifo));
}

/*
 * FIMG2D Register Initialize,when user use FIMG2D to accerate,must do this.
 * user open FIMG2D device node and ioctl,copy_from_user by params(such BPP,src_add,src_h,src_w,
 * dest_h,dest_w,and rotation mode ,and etc) let FIMG2D driver initialize,FIMG2D initalize FIMG2D whole
 * register,take S3C6410 data sheet as refer
 *
 * @Todo:s3c_g2d_params ===>copy_from_user transfer GUI application funtion param to Driver
 *
 * */
static int s3c_g2d_init_regs(s3c_g2d_params *params)
{
    u32 bpp_mode;
    u32 tmp_reg;
    s3c_g2d_check_fifo(25);


    switch(params->bpp) {
    case ARGB8:
        bpp_mode = S3C_G2D_COLOR_MODE_REG_C0_15BPP;
        break;
       
    case RGB16:
        bpp_mode = S3C_G2D_COLOR_RGB_565;
        break;

    case RGB18:
        bpp_mode = S3C_G2D_COLOR_MODE_REG_C2_18BPP;
        break;

    case RGB24:
        bpp_mode = S3C_G2D_COLOR_XRGB_8888;
        break;

    default:
        bpp_mode = S3C_G2D_COLOR_MODE_REG_C3_24BPP;
        break;
    }

    /*set register for soruce image ===============================*/
    __raw_writel(params->src_base_addr, s3c_g2d_base + S3C_G2D_SRC_BASE_ADDR);
    __raw_writel(params->src_full_width, s3c_g2d_base + S3C_G2D_HORI_RES_REG);
    __raw_writel(params->src_full_height, s3c_g2d_base + S3C_G2D_VERT_RES_REG);
    __raw_writel((S3C_G2D_FULL_V(params->src_full_height) |
            S3C_G2D_FULL_H(params->src_full_width)),
            s3c_g2d_base+S3C_G2D_SRC_RES_REG);
    __raw_writel(bpp_mode, s3c_g2d_base + S3C_G2D_SRC_COLOR_MODE);
   
    /*set register for destination image =============================*/
    __raw_writel(params->dst_base_addr, s3c_g2d_base + S3C_G2D_DST_BASE_ADDR);
    __raw_writel(params->dst_full_width, s3c_g2d_base + S3C_G2D_SC_HORI_REG);
    __raw_writel(params->dst_full_height, s3c_g2d_base + S3C_G2D_SC_VERT_REG);
    __raw_writel((S3C_G2D_FULL_V(params->dst_full_height) |
            S3C_G2D_FULL_H(params->dst_full_width)),
            s3c_g2d_base+S3C_G2D_SC_RES_REG);
    __raw_writel(bpp_mode, s3c_g2d_base + S3C_G2D_DST_COLOR_MODE);

    /*set register for clipping window===============================*/
    __raw_writel(params->cw_x1, s3c_g2d_base + S3C_G2D_CW_LT_X_REG);
    __raw_writel(params->cw_y1, s3c_g2d_base + S3C_G2D_CW_LT_Y_REG);
    __raw_writel(params->cw_x2, s3c_g2d_base + S3C_G2D_CW_RB_X_REG);
    __raw_writel(params->cw_y2, s3c_g2d_base + S3C_G2D_CW_RB_Y_REG);

    /*set register for color=======================================*/
    __raw_writel(params->color_val[G2D_WHITE], s3c_g2d_base + S3C_G2D_FG_COLOR_REG);    /* set color to both font and foreground color */
    __raw_writel(params->color_val[G2D_BLACK], s3c_g2d_base + S3C_G2D_BG_COLOR_REG);
    __raw_writel(params->color_val[G2D_BLUE], s3c_g2d_base + S3C_G2D_BS_COLOR_REG);        /* Set blue color to blue screen color */

    /*set register for ROP & Alpha==================================*/
    if(params->alpha_mode == TRUE) {
        if(params->alpha_val > ALPHA_VALUE_MAX) {
            printk(KERN_ALERT "s3c g2d dirver error: exceed alpha value range 0~255/n");
              return -ENOENT;
        }

        __raw_writel(S3C_G2D_ROP_REG_OS_FG_COLOR |
                S3C_G2D_ROP_REG_ABM_REGISTER |
                S3C_G2D_ROP_REG_T_OPAQUE_MODE |
                G2D_ROP_SRC_ONLY,
                s3c_g2d_base + S3C_G2D_ROP_REG);
        __raw_writel(S3C_G2D_ALPHA(params->alpha_val), s3c_g2d_base + S3C_G2D_ALPHA_REG);
    } else {
        __raw_writel(S3C_G2D_ROP_REG_OS_FG_COLOR |
                S3C_G2D_ROP_REG_ABM_NO_BLENDING |
                S3C_G2D_ROP_REG_T_OPAQUE_MODE |
                G2D_ROP_SRC_ONLY,
                s3c_g2d_base + S3C_G2D_ROP_REG);
        __raw_writel(S3C_G2D_ALPHA(0x00), s3c_g2d_base + S3C_G2D_ALPHA_REG);       
    }

    /*set register for color key====================================*/
    if(params->color_key_mode == TRUE) {
        tmp_reg = __raw_readl(s3c_g2d_base + S3C_G2D_ROP_REG);
        tmp_reg |= S3C_G2D_ROP_REG_T_TRANSP_MODE ;
        __raw_writel(tmp_reg , s3c_g2d_base + S3C_G2D_ROP_REG);
        __raw_writel(params->color_key_val, s3c_g2d_base + S3C_G2D_BS_COLOR_REG);    
    }

    /*set register for rotation=====================================*/
    __raw_writel(S3C_G2D_ROTATRE_REG_R0_0, s3c_g2d_base + S3C_G2D_ROT_OC_X_REG);
    __raw_writel(S3C_G2D_ROTATRE_REG_R0_0, s3c_g2d_base + S3C_G2D_ROT_OC_Y_REG);
    __raw_writel(S3C_G2D_ROTATRE_REG_R0_0, s3c_g2d_base + S3C_G2D_ROTATE_REG);
   
    return 0;
}

/*
 * This funtion is very important,because user usually need Bitblt function to accerate;
 * eg,Xfbdev Kdrive need this accerate GUI performance.
 *
 * nothing to note ,just writel
 *
 * send address of SRC and DEST first,send BITBLT Command to FIFO ,begin bitblt
 * */

void s3c_g2d_bitblt(u16 src_x1, u16 src_y1, u16 src_x2, u16 src_y2,
      u16 dst_x1, u16 dst_y1, u16 dst_x2, u16 dst_y2)
{
    u32 cmd_reg_val;

    s3c_g2d_check_fifo(25);

     __raw_writel(src_x1, s3c_g2d_base + S3C_G2D_COORD0_X_REG);
    __raw_writel(src_y1, s3c_g2d_base + S3C_G2D_COORD0_Y_REG);
     __raw_writel(src_x2, s3c_g2d_base + S3C_G2D_COORD1_X_REG);
    __raw_writel(src_y2, s3c_g2d_base + S3C_G2D_COORD1_Y_REG);

      __raw_writel(dst_x1, s3c_g2d_base + S3C_G2D_COORD2_X_REG);
     __raw_writel(dst_y1, s3c_g2d_base + S3C_G2D_COORD2_Y_REG);
     __raw_writel(dst_x2, s3c_g2d_base + S3C_G2D_COORD3_X_REG);
     __raw_writel(dst_y2, s3c_g2d_base + S3C_G2D_COORD3_Y_REG);

    cmd_reg_val = readl(s3c_g2d_base + S3C_G2D_CMD1_REG);
    cmd_reg_val = ~(S3C_G2D_CMD1_REG_S|S3C_G2D_CMD1_REG_N);
    cmd_reg_val |= S3C_G2D_CMD1_REG_N;
    __raw_writel(cmd_reg_val, s3c_g2d_base + S3C_G2D_CMD1_REG);
}


static void s3c_g2d_rotate_with_bitblt(s3c_g2d_params *params, ROT_DEG rot_degree)
{
    u16 org_x=0, org_y=0;
    u32 rot_reg_val = 0;
    u32 src_x1, src_y1, src_x2, src_y2, dst_x1, dst_y1, dst_x2, dst_y2;

    src_x1 = params->src_start_x;
    src_y1 = params->src_start_y;
    src_x2 = params->src_start_x + params->src_work_width;
    src_y2 = params->src_start_y + params->src_work_height;
    dst_x1 = params->dst_start_x;
    dst_y1 = params->dst_start_y;
    dst_x2 = params->dst_start_x + params->dst_work_width;
    dst_y2 = params->dst_start_y + params->dst_work_height;
   
    s3c_g2d_check_fifo(25);
    __raw_writel(S3C_G2D_INTEN_REG_CCF, s3c_g2d_base + S3C_G2D_INTEN_REG);   

    s3c_g2d_get_rotation_origin(src_x1, src_y1, src_x2, src_y2,
                    dst_x1, dst_y1, rot_degree, &org_x, &org_y);
    if(rot_degree != (ROT_0||ROT_X_FLIP||ROT_Y_FLIP)){
        org_x += 1;
        org_y += 1;
    }
    __raw_writel(org_x, s3c_g2d_base + S3C_G2D_ROT_OC_X_REG);
    __raw_writel(org_y, s3c_g2d_base + S3C_G2D_ROT_OC_Y_REG);

    switch(rot_degree) {
    case ROT_0:
        rot_reg_val = S3C_G2D_ROTATRE_REG_R0_0;
        break;
       
    case ROT_90:
        rot_reg_val = S3C_G2D_ROTATRE_REG_R1_90;
        break;

    case ROT_180:
        rot_reg_val = S3C_G2D_ROTATRE_REG_R2_180;
        break;

    case ROT_270:
        rot_reg_val = S3C_G2D_ROTATRE_REG_R3_270;
        break;
       
    case ROT_X_FLIP:
        rot_reg_val = S3C_G2D_ROTATRE_REG_FX;
        break;

    case ROT_Y_FLIP:
        rot_reg_val = S3C_G2D_ROTATRE_REG_FY;
        break;

    default:
        printk(KERN_ERR "[%s : %d] Wrong rotation degree/n", __FUNCTION__, __LINE__);
        break;
    }
    __raw_writel(rot_reg_val, s3c_g2d_base + S3C_G2D_ROTATE_REG);

    switch(rot_degree) {
    case ROT_0:        /* fall through */
    case ROT_X_FLIP:    /* fall through */
    case ROT_Y_FLIP:
        s3c_g2d_bitblt(src_x1, src_y1, src_x2, src_y2,
                dst_x1, dst_y1, dst_x2, dst_y2);
        break;
       
    case ROT_90:
        s3c_g2d_bitblt(src_x1, src_y1, src_x2, src_y2, src_x1,
                src_y1+1, src_x2, src_y2+1);
        break;

    case ROT_180:
        s3c_g2d_bitblt(src_x1, src_y1, src_x2, src_y2, src_x1+1,
                src_y1+1, src_x2+1, src_y2+1);
        break;

    case ROT_270:
        s3c_g2d_bitblt(src_x1, src_y1, src_x2, src_y2, src_x1+1,
                src_y1, src_x2+1, src_y2);
        break;
       
    default:
        break;
    }
}

/*s3c_g2d_get_rotation_origin Get Memory region for rotation from  source image,just memcopy*/
static void s3c_g2d_get_rotation_origin(u16 src_x1, u16 src_y1,
                    u16 src_x2, u16 src_y2,
                    u16 dst_x1, u16 dst_y1,
                    ROT_DEG rot_degree,
                    u16* org_x, u16* org_y)
{
    s3c_g2d_check_fifo(25);

    switch(rot_degree) {
    case ROT_90:
        *org_x = ((dst_x1 - dst_y1 + src_x1 + src_y2)>>1);
        *org_y = ((dst_x1 + dst_y1 - src_x1 + src_y2)>>1);
        break;

    case ROT_180:         /* fall through */
    case ROT_X_FLIP:     /* fall through */
    case ROT_Y_FLIP:
        *org_x = ((dst_x1 + src_x2)>>1);
        *org_y = ((dst_y1 + src_y2)>>1);
        break;

    case ROT_270:
        *org_x = ((dst_x1 + dst_y1 + src_x2 - src_y1)>>1);
        *org_y = ((dst_y1 - dst_x1 + src_x2 + src_y1)>>1);
        break;

    case ROT_0:         /* fall through */
    default:
        break;
    }
}


static void s3c_g2d_rotator_start(s3c_g2d_params *params, ROT_DEG rot_degree)
{

    s3c_g2d_init_regs(params);
    s3c_g2d_rotate_with_bitblt(params, rot_degree);
}

/*s3c_g2d_irq */
irqreturn_t s3c_g2d_irq(int irq, void *dev_id)
{
    if(__raw_readl(s3c_g2d_base + S3C_G2D_INTC_PEND_REG) & S3C_G2D_PEND_REG_INTP_CMD_FIN) {
         __raw_writel ( S3C_G2D_PEND_REG_INTP_CMD_FIN, s3c_g2d_base + S3C_G2D_INTC_PEND_REG );
        wake_up_interruptible(&waitq_g2d);
        s3c_g2d_poll_flag = 1;
    }

    return IRQ_HANDLED;
}


int s3c_g2d_open(struct inode *inode, struct file *file)
{
    s3c_g2d_params *params;
    params = (s3c_g2d_params *)kmalloc(sizeof(s3c_g2d_params), GFP_KERNEL);
    if(params == NULL) {
        printk(KERN_ERR "Instance memory allocation was failed/n");
        return -1;
    }

    memset(params, 0, sizeof(s3c_g2d_params));

    file->private_data    = (s3c_g2d_params *)params;
   
    printk("s3c_g2d_open() /n");

    return 0;
}


int s3c_g2d_release(struct inode *inode, struct file *file)
{
    s3c_g2d_params *params;

    params = (s3c_g2d_params *)file->private_data;
    if (params == NULL) {
        printk(KERN_ERR "Can't release s3c_rotator!!/n");
        return -1;
    }

    kfree(params);//params's memory region is kmalloc ,so here kfree for memory safe
   
    printk("s3c_g2d_release() /n");

    return 0;
}


int s3c_g2d_mmap(struct file* filp, struct vm_area_struct *vma)
{
    unsigned long pageFrameNo = 0;
    unsigned long size;
   
    size = vma->vm_end - vma->vm_start;

    // page frame number of the address for a source G2D_SFR_SIZE to be stored at.
    pageFrameNo = __phys_to_pfn(s3c_g2d_mem->start);
   
    if(size > G2D_SFR_SIZE) {
        printk("The size of G2D_SFR_SIZE mapping is too big!/n");
        return -EINVAL;
    }
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
   
    if((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) {
        printk("Writable G2D_SFR_SIZE mapping must be shared !/n");
        return -EINVAL;
    }
   
    if(remap_pfn_range(vma, vma->vm_start, pageFrameNo, size, vma->vm_page_prot))
        return -EINVAL;

    return 0;
}

//Interface Of UserSpace and Kernel Driver Space
static int s3c_g2d_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    s3c_g2d_params *params;
    ROT_DEG rot_degree;

    params    = (s3c_g2d_params*)file->private_data;
    if (copy_from_user(params, (s3c_g2d_params*)arg, sizeof(s3c_g2d_params)))
        return -EFAULT;

    mutex_lock(h_rot_mutex);
   
    switch(cmd) {
    case S3C_G2D_ROTATOR_0:
        rot_degree = ROT_0;
        s3c_g2d_rotator_start(params, rot_degree);
        break;
       
    case S3C_G2D_ROTATOR_90:
        rot_degree = ROT_90;
        s3c_g2d_rotator_start(params, rot_degree);
        break;

    case S3C_G2D_ROTATOR_180:
        rot_degree = ROT_180;
        s3c_g2d_rotator_start(params, rot_degree);
        break;
       
    case S3C_G2D_ROTATOR_270:
        rot_degree = ROT_270;
        s3c_g2d_rotator_start(params, rot_degree);
        break;

    case S3C_G2D_ROTATOR_X_FLIP:
        rot_degree = ROT_X_FLIP;
        s3c_g2d_rotator_start(params, rot_degree);
        break;

    case S3C_G2D_ROTATOR_Y_FLIP:
        rot_degree = ROT_Y_FLIP;
        s3c_g2d_rotator_start(params, rot_degree);
        break;
   
    default:
        mutex_unlock(h_rot_mutex);
        return -EINVAL;
    }
   
    if(!(file->f_flags & O_NONBLOCK)) {
        if (interruptible_sleep_on_timeout(&waitq_g2d, G2D_TIMEOUT) == 0) {
            printk(KERN_ERR "/n%s: Waiting for interrupt is timeout/n", __FUNCTION__);
        }
    }

    mutex_unlock(h_rot_mutex);

    return 0;
   
}


static unsigned int s3c_g2d_poll(struct file *file, poll_table *wait)
{
    unsigned int mask = 0;

    poll_wait(file, &waitq_g2d, wait);
    if(s3c_g2d_poll_flag == 1) {
        mask = POLLOUT|POLLWRNORM;
        s3c_g2d_poll_flag = 0;
    }

    return mask;
}

                                                        
//Kernel VFS ,GTK open ===>Kernel VFS==>s3c_g2d_fops==s3c_g2d_open
struct file_operations s3c_g2d_fops = {
    .owner         = THIS_MODULE,
    .open         = s3c_g2d_open,
    .release     = s3c_g2d_release,
    .mmap         = s3c_g2d_mmap,
    .ioctl        = s3c_g2d_ioctl,
    .poll        = s3c_g2d_poll,
};

//User for misc_register
static struct miscdevice s3c_g2d_dev = {
    .minor        = G2D_MINOR,
    .name        = "s3c-g2d",
    .fops        = &s3c_g2d_fops,
};


int s3c_g2d_probe(struct platform_device *pdev)
{
    struct resource *res;
    int ret;
    printk(KERN_ALERT"s3c_g2d_probe called/n");

    /* find the IRQs */
    s3c_g2d_irq_num = platform_get_irq(pdev, 0);
    if(s3c_g2d_irq_num <= 0) {
        printk(KERN_ERR "failed to get irq resouce/n");
         return -ENOENT;
    }

    ret = request_irq(s3c_g2d_irq_num, s3c_g2d_irq, IRQF_DISABLED, pdev->name, NULL);
    if (ret) {
        printk("request_irq(g2d) failed./n");
        return ret;
    }


    /* get the memory region */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if(res == NULL) {
        printk(KERN_ERR "failed to get memory region resouce/n");
        return -ENOENT;
    }

    s3c_g2d_mem = request_mem_region(res->start, res->end-res->start+1, pdev->name);
    if(s3c_g2d_mem == NULL) {
        printk(KERN_ERR "failed to reserve memory region/n");
     return -ENOENT;
    }


    s3c_g2d_base = ioremap(s3c_g2d_mem->start, s3c_g2d_mem->end - res->start + 1);
    if(s3c_g2d_base == NULL) {
        printk(KERN_ERR "failed ioremap/n");
     return -ENOENT;
    }

    s3c_g2d_clock = clk_get(&pdev->dev, "g2d");
    if(s3c_g2d_clock == NULL) {
        printk(KERN_ERR "failed to find g2d clock source/n");
        return -ENOENT;
    }

    clk_enable(s3c_g2d_clock);

    h_clk = clk_get(&pdev->dev, "hclk");
    if(h_clk == NULL) {
        printk(KERN_ERR "failed to find h_clk clock source/n");
        return -ENOENT;
    }

    init_waitqueue_head(&waitq_g2d);

    ret = misc_register(&s3c_g2d_dev);
    if (ret) {
        printk (KERN_ERR "cannot register miscdev on minor=%d (%d)/n",
            G2D_MINOR, ret);
        return ret;
    }

    h_rot_mutex = (struct mutex *)kmalloc(sizeof(struct mutex), GFP_KERNEL);
    if (h_rot_mutex == NULL)
        return -1;
   
    mutex_init(h_rot_mutex);

    printk(KERN_ALERT" s3c_g2d_probe Success/n");

    return 0;
}


static int s3c_g2d_remove(struct platform_device *dev)
{
    printk(KERN_INFO "s3c_g2d_remove called !/n");

    free_irq(s3c_g2d_irq_num, NULL);
   
    if (s3c_g2d_mem != NULL) {
        printk(KERN_INFO "S3C Rotator Driver, releasing resource/n");
        iounmap(s3c_g2d_base);
        release_resource(s3c_g2d_mem);
        kfree(s3c_g2d_mem);
    }

    misc_deregister(&s3c_g2d_dev);   
    printk(KERN_INFO "s3c_g2d_remove Success !/n");
    return 0;
}

//the following use for FIMG2D Powermagement,just use clk_disable and clk_enable to achieve
static int s3c_g2d_suspend(struct platform_device *dev, pm_message_t state)
{
    clk_disable(s3c_g2d_clock);
    return 0;
}


static int s3c_g2d_resume(struct platform_device *pdev)
{
    clk_enable(s3c_g2d_clock);
    return 0;
}


static struct platform_driver s3c_g2d_driver = {
    .probe        = s3c_g2d_probe,
    .remove        = s3c_g2d_remove,
    .suspend    = s3c_g2d_suspend,
    .resume        = s3c_g2d_resume,
    .driver        = {
        .owner    = THIS_MODULE,
        .name    = "s3c-g2d",
    },
};


int __init s3c_g2d_init(void)
{
     if(platform_driver_register(&s3c_g2d_driver) != 0) {
         printk("platform device register Failed /n");
         return -1;
     }

    printk(" S3C G2D Init : Done/n");
     return 0;
}


void s3c_g2d_exit(void)
{
    platform_driver_unregister(&s3c_g2d_driver);
    mutex_destroy(h_rot_mutex);
}

module_init(s3c_g2d_init);
module_exit(s3c_g2d_exit);

MODULE_AUTHOR("Jonghun Han <jonghun.han@samsung.com>");
MODULE_DESCRIPTION("S3C FIMG-2D Device Driver");
MODULE_LICENSE("GPL");

Logo

更多推荐