zynq7000 采用AXI + EMIO模拟SCCB接口控制ov5640

linux的i2c驱动模型比较复杂,控制ov5640这种SCCB接口非标准的I2C比较困难;
采用AXI + EMIO接口,模拟SCCB接口协议控制ov5640更加简洁


硬件

PL端采用2个EMIO口:
一个EMIO口作为 SCL时钟输出
一个EMIO口作为SDA数据输入输出, 通过SDA_T控制输入/输出方向,通过SDA_I/SDA_O输入/输入数据.
PL可以采用2个GPIO模块IP控制EMIO, 也可以verilog代码控制EMIO。


软件

  • 控制寄存器
    输入寄存器地址:0x4123000
    SCL_I : 只读 bit[0] // SCL为输入端口时,从该bit位读数据
    SDA_I : 只读 bit[1] // SDA为输入端口时,从该bit位读数据
    控制寄存器地址:0x4124000
    SCL_O : 只写 bit[0] // SCL为输出端口时,从该bit位写数据
    SCL_T : 只写 bit[1] // 设置SCL端口方向,1=输入端口 0=输出端口
    SDA_O : 只写 bit[2] // SDA为输出端口时,从该bit位写数据
    SDA_T : 只写 bit[3] // 设置SDA端口方向,1=输入端口 0=输出端口
    RESETN : 只写 bit[15] // 复位

完整驱动程序: ov5640.c


/************************************************************************************//**
*\n  @file       ov5640.c
*\n  @brief      ov5640 驱动
*\n  @details
*\n -------------------------------------------------------------------------
*\n  文件说明:
*\n       1. ov5640 驱动
*\n       2. PL使用2个EMIO口作为SCL/SDA; 软件通过AXI接口直接控制,模拟SCCB接口。
*\n       
*\n -------------------------------------------------------------------------
*\n  版本:     修改人:       修改日期:                描述:
*\n  V1.0    luo_xian_neng		2018.5.3      		创建
*\n  V1.0A   luo_xian_neng 		2018.6.11      		fpga修改为2个GPIO IP方式,采用2段地址
*\n 
*\n 
****************************************************************************************/

/***************************************************************************************
* 头文件
****************************************************************************************/
#include <linux/bitops.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/clk.h>

#include <linux/cdev.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
#include <linux/wait.h>
#include <linux/fs.h>



/**************************************************
// 设备树结构:
jimu_camera { 
	compatible = "jimu,camera";
	reg = < 0x41230000 0x1000 0x41240000 0x1000 >;
	path = "jimu.camera";
};

**************************************************/


/***************************************************************************************
* 宏定、常量、结构定义
****************************************************************************************/
#define DRIVER_NAME	  "jimu.camera"
#define DEVICE_NAME	  "jimu.camera"
#define CLASS_NAME	  "jimu.camera"
#define MODULE_NAME	  "jimu.camera"


// OV5640相关寄存器定义
#define OV5640_ADDR        	 (0x78)		// OV5640的IIC地址
#define OV5640_ID            (0x5640)  	//OV5640的芯片ID
#define OV5640_CHIPIDH       (0x300A)  	//OV5640芯片ID高字节
#define OV5640_CHIPIDL       (0x300B)  	//OV5640芯片ID低字节


/***************************************************************************************
* 私有变量声明、定义
****************************************************************************************/

/***************************************************************************************
* 私有函数声明、定义
****************************************************************************************/

#if 1
typedef struct ov5640_device
{
	int  used;
	char 			*name;
	char 			*path;
	spinlock_t gpio_lock;

	// driver option
	dev_t			devno;
	struct cdev    	cdev;
	struct class  	*class;
	struct device 	*dev;

	struct resource *in_reg,  *out_reg;  // 寄存器
	void __iomem 	*in_base, *out_base; // 基地址 

} ov5640_device;
#endif


#if 1
// 0x4123000
//SCL_I    	:  GP_I  bit[0]
//SDA_I    	:  GP_I  bit[1]
// 0x4124000
//SCL_O  	:  GP_O  bit[0]
//SCL_T  	:  GP_O  bit[1]   // 1= in, 0 = out
//SDA_O  	:  GP_O  bit[2]
//SDA_T  	:  GP_O  bit[3]   // 1= in, 0 = out
//RESETN	:  GP_O  bit[4]   // 

#define  SCL_I     (1 << 0)
#define  SDA_I     (1 << 1)

#define  SCL_O     (1 << 0)
#define  SCL_T     (1 << 1)   // 1= in, 0 = out
#define  SDA_O     (1 << 2)
#define  SDA_T     (1 << 3)   // 1= in, 0 = out
#define  RESETN    (1 << 15)

#define read_reg(offset)		 readl(offset)
#define write_reg(offset, val)	 writel(val, offset)

		

#if 3


#define  SCCB_SCL(val) \
do{ \
	value  = read_reg(this->out_base);   	\
	value  = value & (~SCL_T)  & (~SCL_O); 	\
	value |= val ? SCL_O : 0;             \
	write_reg(this->out_base, value);     \
} while(0)
	
#define  SCCB_SDA(val) \
do{ \
	value  = read_reg(this->out_base);    \
	value  = value & (~SDA_T)  & (~SDA_O);\
	value |= val ? SDA_O : 0;             \
	write_reg(this->out_base, value);     \
} while(0)

//设置SDA为输出  
#define  SCCB_SDA_OUT() \
do{ \
	value  = read_reg(this->out_base);    \
	value &= ~SDA_T;                      \
	write_reg(this->out_base, value);     \
} while(0)

// 设置SDA为输入 
#define  SCCB_SDA_IN()  \
do{ \
	value  = read_reg(this->out_base);    \
	value |= SDA_T;                       \
	write_reg(this->out_base, value);     \
} while(0)

// 读取SDA电压
static inline u32 SCCB_READ_SDA(struct ov5640_device *this)
{
	u32 value;
	
	value  = read_reg(this->in_base);
	value &= SDA_I; 

	return value ? 1 : 0;
}

static void SCCB_Delay(void)
{
	volatile int loop = 1000;
	while(loop--);
}

static void SCCB_Start(struct ov5640_device *this)
{
	u32 value;
	
    SCCB_SDA(1);     //数据线高电平	   
    SCCB_SCL(1);	    //在时钟线高的时候数据线由高至低
    SCCB_Delay();  
    SCCB_SDA(0);
    SCCB_Delay();	 
    SCCB_SCL(0);	    //数据线恢复低电平,单操作函数必要	  
}

//SCCB停止信号
static void SCCB_Stop(struct ov5640_device *this)
{
	u32 value;
	
    SCCB_SDA(0);
    SCCB_Delay();	 
    SCCB_SCL(1);	
    SCCB_Delay(); 
    SCCB_SDA(1);	
    SCCB_Delay();
}  

static void SCCB_No_Ack(struct ov5640_device *this)
{
	u32 value;
	
	SCCB_Delay();
	SCCB_SDA(1);	
	SCCB_SCL(1);	
	SCCB_Delay();
	SCCB_SCL(0);	
	SCCB_Delay();
	SCCB_SDA(0);	
	SCCB_Delay();
}

//SCCB,写入一个字节
//返回值:0,成功;-1,失败. 
int SCCB_WR_Byte(struct ov5640_device *this,  u8 dat)
{
	u32 value;
	u8 j,res;
	
	for (j=0; j<8; j++)
	{
		if (dat & 0x80) 
			 SCCB_SDA(1);	
		else SCCB_SDA(0);
		dat <<= 1;
		SCCB_Delay();
		SCCB_SCL(1);	
		SCCB_Delay();
		SCCB_SCL(0);		   
	}			 
	SCCB_SDA_IN();		    // 设置SDA为输入 
	SCCB_Delay();
	SCCB_SCL(1);			// 接收第九位,以判断是否发送成功
	SCCB_Delay();
	if(SCCB_READ_SDA(this))  
		res = -1;  //SDA=1发送失败,返回1
	else 			         
		res =  0;  //SDA=0发送成功,返回0
	SCCB_SCL(0);		 
	SCCB_SDA_OUT();		//设置SDA为输出    
	
	return res;  
}	

//SCCB 读取一个字节
//在SCL的上升沿,数据锁存
//返回值:读到的数据
u8 SCCB_RD_Byte(struct ov5640_device *this)
{
	u32 value;
	
	u8 temp=0,j;    
	SCCB_SDA_IN();		//设置SDA为输入  
	for(j=8;j>0;j--) 	//循环8次接收数据
	{		     	  
		SCCB_Delay();
		SCCB_SCL(1);
		temp=temp<<1;
		if(SCCB_READ_SDA(this)) temp++;   
		SCCB_Delay();
		SCCB_SCL(0);
	}	
	SCCB_SDA_OUT();		//设置SDA为输出    
	return temp;
} 							    
#endif

static int ov5640_reg_read(struct ov5640_device *this, u16 reg, u8 *val)
{
	u32 value;
	int ret = 1;
	
	SCCB_Start(this); 				
	if(SCCB_WR_Byte(this, OV5640_ADDR)) ret = -1;
   	if(SCCB_WR_Byte(this, reg>>8))		ret = -1;
  	if(SCCB_WR_Byte(this, reg))			ret = -1;	
 	SCCB_Stop(this);   
 	
	SCCB_Start(this);
	if(SCCB_WR_Byte(this, OV5640_ADDR | 0X01)) ret = -1;
   	*val = SCCB_RD_Byte(this);	
  	SCCB_No_Ack(this);
  	SCCB_Stop(this);

	return 1;
}

static int ov5640_reg_write(struct ov5640_device *this, u16 reg, u8 data)
{
	u32 value;
	
	int res = 3;
	
	SCCB_Start(this); 								//	启动SCCB传输
	if(SCCB_WR_Byte(this, OV5640_ADDR)) res =-1;	//写器件ID	  
   	if(SCCB_WR_Byte(this, reg>>8))	  	res =-1;	//写寄存器高8位地址
   	if(SCCB_WR_Byte(this, reg))		  	res =-1;	//写寄存器低8位地址		  
   	if(SCCB_WR_Byte(this, data))		res =-1; 	//写数据	 
  	SCCB_Stop(this);
	
  	return	3;
}


static ssize_t ov5640_read(struct file *file, char __user *buf, size_t count,
							loff_t *offset)
{
	int ret;
	u16  reg;
	u8   val;
	u8   rxbuf[4];
	
	struct ov5640_device *this = file->private_data;
	
	if ((NULL == buf) || (count < 2))
		return -1;

	//printk("%s(),%d ...\n",  __func__, __LINE__);
	
	ret = copy_from_user(rxbuf, buf, 2);
	if (ret)
	{
		printk("%s(),%d err\n", __func__, __LINE__);
		return -1;
	}
	
	reg = (u16)(rxbuf[0] << 8) + rxbuf[1];
	ret = ov5640_reg_read(this, reg, &val);
	if (ret < 0)
	{
		printk("%s(),%d err\n", __func__, __LINE__);
		return -1;
	}
		
	ret = copy_to_user(buf, &val, 1);
	if (ret)
	{
		printk("%s(),%d err\n",	__func__, __LINE__);
		return -1;
	}
	
	return 1;
}


static ssize_t ov5640_write(struct file *file, const char __user *buf, size_t count, 
							loff_t *offset)
{
	int  ret;
	u16  reg;
	u8   val;
	u8   rxbuf[4];
	struct ov5640_device *this = file->private_data;
	
	if ((NULL == buf) || (count < 3))
		return -1;

	//printk("%s(),%d ...\n",  __func__, __LINE__);
	
	ret = copy_from_user(rxbuf, buf, 3);
	if (ret)
		return	-1;

	reg = (u16)(rxbuf[0] << 8) + rxbuf[1];
	val = rxbuf[2];
	ret = ov5640_reg_write(this, reg, val);
	if (ret < 3)
	{
		printk("%s(),%d  err\n",  __func__, __LINE__);
		return -1;
	}

	return ret;
}

							
static int ov5640_open(struct inode *inode, struct file *file)
{
	struct ov5640_device *this;

	//printk("%s() ok\n", __func__);
	this = container_of(inode->i_cdev, struct ov5640_device, cdev);
	file->private_data = this;

	this->used = 1;
	return 0;
}


static int ov5640_release(struct inode *inode, struct file *file)
{
	struct ov5640_device *this;
	
	this = container_of(inode->i_cdev, struct ov5640_device, cdev);

	this->used = 0;
	return 0;
}

struct file_operations fops =
{
	.owner = THIS_MODULE,
	.open  			= ov5640_open,
	.read			= ov5640_read,
	.write			= ov5640_write,
	.release 		= ov5640_release,
};
#endif


#if 1
/* 用来匹配ov5640的设备树 */
static const struct of_device_id  ov5640_of_match[] =
{
	{ .compatible = "jimu,camera", },
	{  },
};
MODULE_DEVICE_TABLE(of, ov5640_of_match);


static int ov5640_probe(struct platform_device *pdev)
{
#if 1
	int ret = 0;
	struct device	   	*dev = &pdev->dev;
	struct device_node 	*np  =  pdev->dev.of_node;

	dev_info(dev, "%s() ...\n", __func__);

	//1. 分配内存
	struct ov5640_device *this = devm_kzalloc(dev, sizeof(*this), GFP_KERNEL);
	if (!this)
	{
		dev_err(dev, "%s(),%d err\n",  __func__, __LINE__);
		return -ENOMEM;
	}
	this->dev  		= &(pdev->dev);
	this->name 		= DRIVER_NAME;
	this->path 		= DRIVER_NAME;
	this->used = 0;

	//2. 映射寄存器
	this->in_reg  = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	this->in_base = devm_ioremap_resource(dev, this->in_reg);
	if (IS_ERR(this->in_base))
	{
		dev_err(dev, "%s(),%d err\n",  __func__, __LINE__);
		return -ENOMEM;
	}
	
	this->out_reg = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	this->out_base = devm_ioremap_resource(dev, this->out_reg);
	if (IS_ERR(this->out_base))
	{
		dev_err(dev, "%s(),%d err\n",  __func__, __LINE__);
		return -ENOMEM;
	}

	ret = of_property_read_string(np, "path", (const char**)&(this->path));
	if (ret)
	{
		dev_err(this->dev,"not find 'path' in device tree \n");
	}
	platform_set_drvdata(pdev, this);

	//3. 注册设备驱动
	//int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
	ret = alloc_chrdev_region(&(this->devno), 0, 1, this->name);
	if (ret < 0)
	{
		dev_err(dev, "alloc_chrdev_region(),%d err\n",  __LINE__);
		goto failed5;
	}
	// dev_info(dev,  "MAJOR(%d),Minor(%d) \n", MAJOR(devt), MINOR(devt));
	cdev_init(&this->cdev, &fops);
	this->cdev.owner = THIS_MODULE;
	ret = cdev_add(&this->cdev, this->devno, 1);
	if (ret)
	{
		dev_err(dev, "cdev_add(),%d err\n",  __LINE__);
		goto failed6;
	}

	//4. 建立设备节点名称
	this->class = class_create(THIS_MODULE, CLASS_NAME);
	if (IS_ERR(this->class))
	{
		dev_err(dev, "class_create(),%d err\n",  __LINE__);
		goto failed7;
	}
	//struct device *device_create(struct class *class, struct device *parent,
	//                              dev_t devt, void *drvdata, 
	//								const char *fmt, ...)
	dev = device_create(this->class, NULL, this->devno, NULL, 
						"%s", this->path);
	if (IS_ERR(dev))
	{
		dev_err(this->dev, "device_create(),%d err\n",  __LINE__);
		goto failed8;
	}
#endif

	dev_info(dev, "/dev/%s create ok\n", this->name);
	return 0;


//failed9:
//	device_destroy(this->class, this->devno);
failed8:
	class_destroy(this->class);
failed7:
	cdev_del(&this->cdev);
failed6:
	unregister_chrdev_region(this->devno, 1);
failed5:

	return -1;
}


static int ov5640_remove(struct platform_device *pdev)
{
	struct ov5640_device  *this;

	this = platform_get_drvdata(pdev);
	if (!this)
	{
		dev_err(&pdev->dev, "platform_get_drvdata(),%d err\n",  __LINE__);
		return -ENODEV;
	}

	device_destroy(this->class, this->devno);
	class_destroy(this->class);
	cdev_del(&this->cdev);
	unregister_chrdev_region(this->devno, 1);

	dev_info(&pdev->dev, "%s(),%d ok\n", __func__, __LINE__);
	return 0;
}

static struct platform_driver ov5640_driver =
{
	.probe  = ov5640_probe,
	.remove = ov5640_remove,
	.driver = {
		.name  = MODULE_NAME,
		.owner = THIS_MODULE,
		.of_match_table = ov5640_of_match,
	},
};

module_platform_driver(ov5640_driver);
#endif



MODULE_DESCRIPTION("Camera driver for ov5640");
MODULE_AUTHOR("jimu.ltd");
MODULE_LICENSE("GPL v2");


  • Makefile文件如下:

export ARCH          = arm
export CROSS_COMPILE = arm-linux-gnueabihf-

ccflags-y := -std=gnu99 
ccflags-y += -Wno-declaration-after-statement  -Wno-unused-function  -Wno-unused-variable

#// linux kernel路径
KERNEL_PATH=/work/sdk2017/kernel/linux-xlnx-xilinx-v2017.4

#// 模块名称 MODULE_NAME.ko
MODULE_NAME	      =  jimu_camera
obj-m      	     +=  $(MODULE_NAME).o 

#// 添加源文件
$(MODULE_NAME)-objs  :=  ov5640.o 
#$(MODULE_NAME)-objs  +=  xxxx.o 

SRC_PATH := $(shell pwd)
build:
	make -C $(KERNEL_PATH) M=$(SRC_PATH)  modules
	cp   -f $(MODULE_NAME).ko             /work/nfs

clean:
	#make -C $(KERNEL_PATH)  M=$(SRC_PATH) clean
	rm   -f  *.o *~ core .depend .*.cmd *.ko *.mod.c
	rm   -f  Module.markers Module.symvers modules.order
	rm   -rf .tmp_versions Modules.symvers

all: clean  build

modules_install:
	$(MAKE) -C $(KERNEL_PATH) M=$(SRC_PATH) modules_install
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值