IIC系列文章
1.【linux驱动】IIC驱动-硬件、协议
2.【linux驱动】IIC驱动OLED屏(GPIO 模拟)
3.【linux驱动】IIC驱动(4418读取EEPROM:24AA025E48T-I/OT)
4.【linux驱动】IIC驱动OLED屏
driver
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <mach/soc.h>
#include <linux/delay.h>
#include <mach/platform.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/errno.h>
#include <linux/i2c.h>
#include <linux/irq.h> //含有IRQ_HANDLED\IRQ_TYPE_EDGE_RISING
#include <linux/interrupt.h> //含有request_irq、free_irq函数
#include "iic.h"
MODULE_LICENSE("GPL");
dev_t devid;
struct cdev char_dev;
struct class * char_class;
int buffer_size = 100;
char * char_data;
int iic_int = 0;
#define _SETDATA(p, d) (((struct i2c_register *)p)->IDSR = d)
#define _GETDATA(p) (((struct i2c_register *)p)->IDSR)
void * base;
void * reset_addr;
void * clk_base;
struct IIC_DEV{
u8 addr; //对方硬件地址
u8 mode; //传输模式
u8 *data; //写入或者读取的数据
int data_count; //传输数据数量
int data_idx; //数据读写位置
bool is_irq; //是否收到中断
};
struct IIC_DEV iic_dev;
static inline void iic_set_clock(void)
{
int cksrc = 1; //pclk/256
int ckscl = 0xe;
unsigned int ICCR = 0;
ICCR = readl(base+I2C_ICCR_OFFS);
ICCR &= ~( 0x0f | 1 << ICCR_CLK_SRC_POS); //清除发送时钟相关寄存器
ICCR |= ((cksrc << ICCR_CLK_SRC_POS) | (ckscl)); //设置时钟源
writel(ICCR,(base+I2C_ICCR_OFFS));
}
static inline void start_dev(unsigned int mode){
unsigned int ICCR = 0,ICSR = 0;
ICSR = readl(base+I2C_ICSR_OFFS);
ICSR = (1<<ICSR_OUT_ENB_POS);
writel(ICSR,(base+I2C_ICSR_OFFS)); // 这里设置一遍,确保输出启用下面数据才可以写入IDSR
writel(iic_dev.addr,(base+I2C_IDSR_OFFS));// 设置从机地址
ICCR = readl(base+I2C_ICCR_OFFS);
ICCR &= ~(1<<ICCR_ACK_ENB_POS);
ICCR |= 1 << ICCR_IRQ_ENB_POS;
writel(ICCR,(base+I2C_ICCR_OFFS));
ICSR = readl(base+I2C_ICSR_OFFS);
ICSR |= (mode & 3) << ICSR_MOD_SEL_POS; // 主机发送
ICSR |= 1 << ICSR_SIG_GEN_POS; // 开始传输
ICSR |= 1 << ICSR_OUT_ENB_POS; // 使能输出
writel(ICSR,(base+I2C_ICSR_OFFS));// 启动发送数据
}
static inline int i2c_wait_dev(int wait)
{
unsigned int ICSR = 0;
do {
ICSR = readl(base+I2C_ICSR_OFFS);
if ( !(ICSR & (1<<ICSR_BUS_BUSY_POS)) && !(ICSR & (1<<ICSR_ARI_STA_POS)) )
return 0;
mdelay(1);
} while (wait-- > 0);
return -1;
}
static inline void trans_dev(unsigned int ack)
{
unsigned int ICCR = 0;
ICCR = readl(base+I2C_ICCR_OFFS);
ICCR &= ~(1<<ICCR_ACK_ENB_POS);
ICCR |= ack << ICCR_ACK_ENB_POS;
writel(ICCR, (base+I2C_ICCR_OFFS));
ICCR = readl((base+I2C_ICCR_OFFS));
ICCR &= (~ (1 << ICCR_IRQ_PND_POS));
ICCR |= 1<<ICCR_IRQ_CLR_POS;
ICCR |= 1<<ICCR_IRQ_ENB_POS;
writel(ICCR, (base+I2C_ICCR_OFFS));
}
static void stop_sig(void)
{
unsigned int ICSR = 0, STOP = 0;
trans_dev(0); //启动传输
udelay(1);
STOP = readl(base+I2C_STOP_OFFS);
STOP |= 1<<STOP_ACK_GEM_POS;
writel(STOP, base+I2C_STOP_OFFS); //发送停止信号
udelay(1);
ICSR = iic_dev.mode << ICSR_MOD_SEL_POS;
writel(ICSR, (base+I2C_ICSR_OFFS)); //清除寄存器
}
static bool is_ack(int is_ack){
unsigned int ICSR = 0xff;
ICSR= readl(base+I2C_ICSR_OFFS);
if ((ICSR & 1) == 0 || !is_ack){
return true;
}else{
printk("ack not receive\n");
}
ICSR = ICSR >> 1;
if ((ICSR & 1) == 0){
printk("start/stop\n");
}else{
printk("0x00 addr\n");
}
ICSR = ICSR >> 1;
if ((ICSR & 1) == 0){
printk("start/stop 2\n");
}else{
printk("match addr\n");
}
ICSR = ICSR >> 1;
if ((ICSR & 1) == 0){
printk("arbitration ok\n");
}else{
printk("arbitration fail\n");
}
return false;
}
static irqreturn_t iic_irq(int irq,void* dev_id)
{
unsigned int ICCR = 0;
ICCR = readl((base+I2C_ICCR_OFFS));
ICCR &= (~ (1 << ICCR_IRQ_ENB_POS)); //禁用中断
ICCR |= 1<<ICCR_IRQ_CLR_POS; //清除中断
writel(ICCR, (base+I2C_ICCR_OFFS));
if(is_ack(iic_dev.data_idx == 0)){
int ack = (iic_dev.data_count <= iic_dev.data_idx + 1) ? 0: 1;// 不是最后一个都要发ack
if (iic_dev.mode == I2C_TXRXMODE_SLAVE_TX \
|| iic_dev.mode == I2C_TXRXMODE_MASTER_TX){
if(iic_dev.data_idx == iic_dev.data_count){
// stop
stop_sig();
}else{
//write
writel(iic_dev.data[iic_dev.data_idx],(base+I2C_IDSR_OFFS));// 设置数据
trans_dev(0);
}
}else{
//read
if(iic_dev.data_idx == iic_dev.data_count){
// stop
iic_dev.data[iic_dev.data_idx - 1] = _GETDATA(base);
stop_sig();
}else{
if(iic_dev.data_idx == 0) // 第一次ack
{
}else{
iic_dev.data[iic_dev.data_idx - 1] = _GETDATA(base);
}
trans_dev(ack);
}
}
iic_dev.data_idx++;
}else{
printk("canot find device:%x\n", iic_dev.addr);
}
return (IRQ_HANDLED);
}
static int write(u8 slave_addr,u8 * data,u8 count)
{
int wait_time = 500;
iic_dev.addr = (slave_addr & ~0x1);
iic_dev.data = data;
iic_dev.mode = I2C_TXRXMODE_MASTER_TX;
iic_dev.data_count = count;
iic_dev.data_idx = 0;
iic_dev.is_irq = false;
iic_set_clock(); //设置iic时钟
if (i2c_wait_dev(wait_time) != -1){
start_dev(I2C_TXRXMODE_MASTER_TX);
}else{
stop_sig();
printk("device is busy\n");
return -1;
}
wait_time = 0;
while(iic_dev.data_idx <= iic_dev.data_count\
&& WAIT_ACK_TIME > wait_time){
mdelay(10);
wait_time += 10;
}
if(iic_dev.data_idx <= iic_dev.data_count)
{
stop_sig();
printk("write time out\n");
}
return 0;
}
static int read(u8 slave_addr,u8 * read_buffer, u8 count)
{
int wait_time = 500;
iic_dev.addr = (slave_addr | 0x1);
iic_dev.data = read_buffer;
iic_dev.mode = I2C_TXRXMODE_MASTER_RX;
iic_dev.data_count = count;
iic_dev.data_idx = 0;
iic_dev.is_irq = false;
iic_set_clock(); //设置iic时钟
if (i2c_wait_dev(wait_time) != -1){
start_dev(I2C_TXRXMODE_MASTER_RX);
}else{
stop_sig();
printk("device is busy\n");
return -1;
}
wait_time = 0;
while(iic_dev.data_idx <= iic_dev.data_count\
&& WAIT_ACK_TIME > wait_time){
mdelay(10);
wait_time += 10;
}
if(iic_dev.data_idx <= iic_dev.data_count)
{
stop_sig();
printk("read time out\n");
}
return 0;
}
static inline void i2c_bus_off(void)
{
unsigned int ICSR = 0;
ICSR &= ~(1<<ICSR_OUT_ENB_POS);
writel(ICSR, (base+I2C_ICSR_OFFS));
}
static long ioctl(struct file * fl, unsigned int cmd, unsigned long arg){
unsigned int dir,size,i;
unsigned char dev_addr;
dir = _IOC_DIR(cmd);
size = _IOC_SIZE(cmd);
i = copy_from_user(&dev_addr,(unsigned char *)arg,1);
i = size - 1;
if(dir == _IOC_WRITE){
while(i > 0){
i = copy_from_user(char_data,(unsigned char *)arg + 1,i);
}
write(dev_addr,char_data,size - 1);
}else{
read(dev_addr,char_data,size - 1);
while(i > 0){
i = copy_to_user((unsigned char *)arg + 1, char_data, i);
}
}
return 0;
}
struct file_operations my_opts = {
.owner = THIS_MODULE,
.unlocked_ioctl = ioctl
};
static int __init iic_init(void){
unsigned int reset;
int ret = 0;
devid = MKDEV(241, 1); //换算设备号
ret = register_chrdev_region(devid, 1, "char_test");//注册设备,在/proc/drivers下面可以看到
if (ret < 0)
goto err0;
cdev_init(&char_dev,&my_opts); //绑定opt结构体
char_dev.owner = THIS_MODULE;
ret = cdev_add(&char_dev,devid,1); //注册字符设备驱动
if (ret < 0)
goto err1;
char_class = class_create(THIS_MODULE,"char_test"); //在/sys/class中创建文件夹
device_create(char_class,NULL,devid,NULL,"char_test_dev_%d",1);//在上一步文件夹中创建char_test_dev_1
char_data = kzalloc(buffer_size,GFP_KERNEL);
base = (void *)ioremap(IIC_BASE,0xf0); //映射地址
clk_base = (void *)ioremap(CLK_BASE,0xf0); //映射地址
reset_addr = (void *)ioremap(RESET_BASE,0xf0); //映射地址
nxp_soc_gpio_set_io_pull_enb(i2c_gpio[0],0);//禁用上拉
nxp_soc_gpio_set_io_pull_enb(i2c_gpio[1],0);//禁用上拉
nxp_soc_gpio_set_io_func(i2c_gpio[0], 1);//设置gpio功能
nxp_soc_gpio_set_io_func(i2c_gpio[1], 1);//设置gpio功能
writel(0x1 << 3,clk_base);// 使能时钟
reset = readl(reset_addr);
reset &= ~(0x1 << 20);
writel(reset,reset_addr);// 复位硬件
mdelay(1);
reset |= 0x1 << 20;
writel(reset,reset_addr);// 结束复位
i2c_bus_off();
if (!request_irq(IRQ_PHY_I2C0,iic_irq,IRQF_DISABLED | IRQF_SHARED,"iic_irq0",(void *)1)){
printk("irq registed %d\n", IRQ_PHY_I2C0);
iic_int = IRQ_PHY_I2C0;
}else{
printk("irq regist fail %d\n",IRQ_PHY_I2C0);
}
printk("iic init\n");
return 0;
err1:
unregister_chrdev_region(devid, 1);
err0:
return ret;
}
static void __exit iic_exit(void){
if(iic_int){
free_irq(IRQ_PHY_I2C0,(void *)1);
}
nxp_soc_gpio_set_io_func(i2c_gpio[0], 0);//恢复gpio功能
nxp_soc_gpio_set_io_func(i2c_gpio[1], 0);//恢复gpio功能
iounmap(base);
iounmap(clk_base);
iounmap(reset_addr);
unregister_chrdev_region(devid, 1);
cdev_del(&char_dev);
device_destroy(char_class,devid);
class_destroy(char_class);
printk("iic exit\n");
}
module_init(iic_init);
module_exit(iic_exit);
application
#include <stdio.h>
#include <linux/ioctl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include "codetab.h"
#define Brightness 0xCF
#define X_WIDTH 128
#define Y_WIDTH 64
int fd;
/*********************OLED写数据************************************/
inline int OLED_WrDat(unsigned char IIC_Data)
{
int ret,cmd;
unsigned char data[] = {0x78,0x40,IIC_Data};
cmd = _IOC(_IOC_WRITE,0x00,0x00,0x03);
ret = ioctl(fd,cmd,data);
if(ret < 0){
perror("ioctl error");
return ret;
}
return 0;
}
/*********************OLED写命令************************************/
inline int OLED_WrCmd(unsigned char IIC_Command)
{
int ret,cmd;
unsigned char data[] = {0x78,0x00,IIC_Command};
cmd = _IOC(_IOC_WRITE,0x00,0x00,0x03);
ret = ioctl(fd,cmd,data);
if(ret < 0){
perror("ioctl error");
return ret;
}
return 0;
}
/*********************OLED 设置坐标************************************/
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WrCmd(0xb0+y);
OLED_WrCmd(((x&0xf0)>>4)|0x10);
OLED_WrCmd((x&0x0f)|0x01);
}
/*********************OLED全屏************************************/
void OLED_Fill(unsigned char bmp_dat)
{
unsigned char y,x;
for(y=0;y<8;y++)
{
OLED_WrCmd(0xb0+y);
OLED_WrCmd(0x01);
OLED_WrCmd(0x10);
for(x=0;x<X_WIDTH;x++)
OLED_WrDat(bmp_dat);
}
}
/*********************OLED复位************************************/
void OLED_CLS(void)
{
unsigned char y,x;
for(y=0;y<8;y++)
{
OLED_WrCmd(0xb0+y);
OLED_WrCmd(0x01);
OLED_WrCmd(0x10);
for(x=0;x<X_WIDTH;x++)
OLED_WrDat(0);
}
}
/*********************OLED初始化************************************/
void OLED_Init(void)
{
OLED_WrCmd(0xae);//--turn off oled panel
OLED_WrCmd(0x00);//---set low column address
OLED_WrCmd(0x10);//---set high column address
OLED_WrCmd(0x40);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WrCmd(0x81);//--set contrast control register
OLED_WrCmd(Brightness); // Set SEG Output Current Brightness
OLED_WrCmd(0xa1);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WrCmd(0xc8);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WrCmd(0xa6);//--set normal display
OLED_WrCmd(0xa8);//--set multiplex ratio(1 to 64)
OLED_WrCmd(0x3f);//--1/64 duty
OLED_WrCmd(0xd3);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WrCmd(0x00);//-not offset
OLED_WrCmd(0xd5);//--set display clock divide ratio/oscillator frequency
OLED_WrCmd(0x80);//--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WrCmd(0xd9);//--set pre-charge period
OLED_WrCmd(0xf1);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WrCmd(0xda);//--set com pins hardware configuration
OLED_WrCmd(0x12);
OLED_WrCmd(0xdb);//--set vcomh
OLED_WrCmd(0x40);//Set VCOM Deselect Level
OLED_WrCmd(0x20);//-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WrCmd(0x02);//
OLED_WrCmd(0x8d);//--set Charge Pump enable/disable
OLED_WrCmd(0x14);//--set(0x10) disable
OLED_WrCmd(0xa4);// Disable Entire Display On (0xa4/0xa5)
OLED_WrCmd(0xa6);// Disable Inverse Display On (0xa6/a7)
OLED_WrCmd(0xaf);//--turn on oled panel
OLED_Fill(0x00); //初始清屏
OLED_Set_Pos(0,0);
}
/***************功能描述:显示6*8一组标准ASCII字符串 显示的坐标(x,y),y为页范围0~7****************/
void OLED_P6x8Str(unsigned char x,unsigned char y,unsigned char ch[])
{
unsigned char c=0,i=0,j=0;
while (ch[j]!='\0')
{
c =ch[j]-32;
if(x>126){x=0;y++;}
OLED_Set_Pos(x,y);
for(i=0;i<6;i++)
OLED_WrDat(F6x8[c][i]);
x+=6;
j++;
}
}
/*******************功能描述:显示8*16一组标准ASCII字符串 显示的坐标(x,y),y为页范围0~7****************/
void OLED_P8x16Str(unsigned char x,unsigned char y,unsigned char ch[])
{
unsigned char c=0,i=0,j=0;
while (ch[j]!='\0')
{
c =ch[j]-32;
if(x>120){x=0;y++;}
OLED_Set_Pos(x,y);
for(i=0;i<8;i++)
OLED_WrDat(F8X16[c*16+i]);
OLED_Set_Pos(x,y+1);
for(i=0;i<8;i++)
OLED_WrDat(F8X16[c*16+i+8]);
x+=8;
j++;
}
}
/*****************功能描述:显示16*16点阵 显示的坐标(x,y),y为页范围0~7****************************/
void OLED_P16x16Ch(unsigned char x,unsigned char y,unsigned char N)
{
unsigned char wm=0;
unsigned int adder=32*N;
OLED_Set_Pos(x , y);
for(wm = 0;wm < 16;wm++)
{
OLED_WrDat(F16x16[adder]);
adder += 1;
}
OLED_Set_Pos(x,y + 1);
for(wm = 0;wm < 16;wm++)
{
OLED_WrDat(F16x16[adder]);
adder += 1;
}
}
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
void Draw_BMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;
if(y1%8==0) y=y1/8;
else y=y1/8+1;
for(y=y0;y<y1;y++)
{
OLED_Set_Pos(x0,y);
for(x=x0;x<x1;x++)
{
OLED_WrDat(BMP[j++]);
}
}
}
static void OLED_ON(void)
{
OLED_WrCmd(0X8D); //设置电荷泵
OLED_WrCmd(0X14); //开启电荷泵
OLED_WrCmd(0XAF); //OLED唤醒
}
int main(){
int ret,cmd;
fd = open("/dev/char_test_dev_1",O_RDWR);
ret = fd;
if(ret < 0){
perror("open /dev/char_test_dev_1 error");
return ret;
}
OLED_Init();
OLED_Fill(0xff); //初始清屏
close(ret);
return 0;
}