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。
/************************************************************************************//**
*\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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/**************************************************
// 设备树结构:
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");
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