歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Linux I2C總線控制器驅動(S3C2440)

Linux I2C總線控制器驅動(S3C2440)

日期:2017/3/1 9:17:57   编辑:Linux編程

s3c2440的i2c控制器驅動(精簡DIY),直接上代碼,注釋很詳細:

#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of_i2c.h>
#include <linux/of_gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/regs-gpio.h>

#include <asm/irq.h>

#include <plat/regs-iic.h>
#include <plat/iic.h>

//#define PRINTK printk
#define PRINTK(...)

enum s3c24xx_i2c_state {
STATE_IDLE,
STATE_START,
STATE_READ,
STATE_WRITE,
STATE_STOP
};

//i2c控制器寄存器
struct s3c2440_i2c_regs {
unsigned int iiccon;
unsigned int iicstat;
unsigned int iicadd;
unsigned int iicds;
unsigned int iiclc;
};

//i2c數據傳輸載體
struct s3c2440_i2c_xfer_data {
struct i2c_msg *msgs;
int msn_num;
int cur_msg;
int cur_ptr;
int state;
int err;
wait_queue_head_t wait;
};

static struct s3c2440_i2c_xfer_data s3c2440_i2c_xfer_data;


static struct s3c2440_i2c_regs *s3c2440_i2c_regs;


static void s3c2440_i2c_start(void)
{
s3c2440_i2c_xfer_data.state = STATE_START;

if (s3c2440_i2c_xfer_data.msgs->flags & I2C_M_RD) /* 讀 */
{
s3c2440_i2c_regs->iicds = s3c2440_i2c_xfer_data.msgs->addr << 1; 
s3c2440_i2c_regs->iicstat = 0xb0; // 主機接收,啟動
}
else /* 寫 */
{
s3c2440_i2c_regs->iicds = s3c2440_i2c_xfer_data.msgs->addr << 1;
s3c2440_i2c_regs->iicstat = 0xf0; // 主機發送,啟動
}
}

static void s3c2440_i2c_stop(int err)
{
s3c2440_i2c_xfer_data.state = STATE_STOP;
s3c2440_i2c_xfer_data.err = err;

PRINTK("STATE_STOP, err = %d\n", err);


if (s3c2440_i2c_xfer_data.msgs->flags & I2C_M_RD) /* 讀 */
{
// 下面兩行恢復I2C操作,發出P信號
s3c2440_i2c_regs->iicstat = 0x90;
s3c2440_i2c_regs->iiccon = 0xaf;
ndelay(50); // 等待一段時間以便P信號已經發出
}
else /* 寫 */
{
// 下面兩行用來恢復I2C操作,發出P信號
s3c2440_i2c_regs->iicstat = 0xd0;
s3c2440_i2c_regs->iiccon = 0xaf;
ndelay(50); // 等待一段時間以便P信號已經發出
}

/* 喚醒 */
wake_up(&s3c2440_i2c_xfer_data.wait);

}

//i2c總線數據傳輸處理函數
static int s3c2440_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
unsigned long timeout;

/* 把num個msg的I2C數據發送出去/讀進來 */
s3c2440_i2c_xfer_data.msgs = msgs;
s3c2440_i2c_xfer_data.msn_num = num;
s3c2440_i2c_xfer_data.cur_msg = 0;
s3c2440_i2c_xfer_data.cur_ptr = 0;
s3c2440_i2c_xfer_data.err = -ENODEV; //確認是否有ack應答

s3c2440_i2c_start(); //發出start信號,判斷read or write

/* 休眠-等待i2c讀寫狀態改變 */
timeout = wait_event_timeout(s3c2440_i2c_xfer_data.wait, (s3c2440_i2c_xfer_data.state == STATE_STOP), HZ * 5); //等待狀態成立或5s
if (0 == timeout)
{
printk("s3c2440_i2c_xfer time out\n");
return -ETIMEDOUT;
}
else
{
return s3c2440_i2c_xfer_data.err;
}
}

static u32 s3c2440_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}


static const struct i2c_algorithm s3c2440_i2c_algo = {
// .smbus_xfer = , //smbus是i2c傳輸的一個子集,支持的話可以在這裡指定處理函數
.master_xfer = s3c2440_i2c_xfer, //傳輸函數
.functionality = s3c2440_i2c_func,
};

/* 1. 分配/設置i2c_adapter
*/
static struct i2c_adapter s3c2440_i2c_adapter = {
.name = "s3c2440_sheldon",
.algo = &s3c2440_i2c_algo, //算法函數
.owner = THIS_MODULE,
};

static int isLastMsg(void)
{
return (s3c2440_i2c_xfer_data.cur_msg == s3c2440_i2c_xfer_data.msn_num - 1);
}

static int isEndData(void)
{
return (s3c2440_i2c_xfer_data.cur_ptr >= s3c2440_i2c_xfer_data.msgs->len);
}

static int isLastData(void)
{
return (s3c2440_i2c_xfer_data.cur_ptr == s3c2440_i2c_xfer_data.msgs->len - 1);
}

static irqreturn_t s3c2440_i2c_xfer_irq(int irq, void *dev_id)
{
unsigned int iicSt;

iicSt = s3c2440_i2c_regs->iicstat; //讀取i2c控制器的狀態寄存器,判斷是否讀寫成功

if(iicSt & 0x8){ printk("Bus arbitration failed\n\r"); }

switch (s3c2440_i2c_xfer_data.state)
{
case STATE_START : /* 發出S和設備地址後,產生中斷 */
{
PRINTK("Start\n");
/* 如果沒有ACK, 返回錯誤 */
if (iicSt & S3C2410_IICSTAT_LASTBIT)
{
s3c2440_i2c_stop(-ENODEV);
break;
}

if (isLastMsg() && isEndData())
{
s3c2440_i2c_stop(0);
break;
}

/* 進入下一個狀態 */
if (s3c2440_i2c_xfer_data.msgs->flags & I2C_M_RD) /* 讀 */
{
s3c2440_i2c_xfer_data.state = STATE_READ;
goto next_read;
}
else
{
s3c2440_i2c_xfer_data.state = STATE_WRITE;
}
}

case STATE_WRITE:
{
PRINTK("STATE_WRITE\n");
/* 如果沒有ACK, 返回錯誤 */
if (iicSt & S3C2410_IICSTAT_LASTBIT)
{
s3c2440_i2c_stop(-ENODEV);
break;
}

if (!isEndData()) /* 如果當前msg還有數據要發送 */
{
s3c2440_i2c_regs->iicds = s3c2440_i2c_xfer_data.msgs->buf[s3c2440_i2c_xfer_data.cur_ptr];
s3c2440_i2c_xfer_data.cur_ptr++;

// 將數據寫入IICDS後,需要一段時間才能出現在SDA線上
ndelay(50);

s3c2440_i2c_regs->iiccon = 0xaf; // 恢復I2C傳輸
break;
}
else if (!isLastMsg())
{
/* 開始處理下一個消息 */
s3c2440_i2c_xfer_data.msgs++;
s3c2440_i2c_xfer_data.cur_msg++;
s3c2440_i2c_xfer_data.cur_ptr = 0;
s3c2440_i2c_xfer_data.state = STATE_START;
/* 發出START信號和發出設備地址 */
s3c2440_i2c_start();
break;
}
else
{
/* 是最後一個消息的最後一個數據 */
s3c2440_i2c_stop(0);
break;
}

break;
}

case STATE_READ:
{
PRINTK("STATE_READ\n");
/* 讀出數據 */
s3c2440_i2c_xfer_data.msgs->buf[s3c2440_i2c_xfer_data.cur_ptr] = s3c2440_i2c_regs->iicds;
s3c2440_i2c_xfer_data.cur_ptr++;
next_read:
if (!isEndData()) /* 如果數據沒讀寫, 繼續發起讀操作 */
{
if (isLastData()) /* 如果即將讀的數據是最後一個, 不發ack */
{
s3c2440_i2c_regs->iiccon = 0x2f; // 恢復I2C傳輸,接收到下一數據時無ACK
}
else
{
s3c2440_i2c_regs->iiccon = 0xaf; // 恢復I2C傳輸,接收到下一數據時發出ACK
}
break;
}
else if (!isLastMsg())
{
/* 開始處理下一個消息 */
s3c2440_i2c_xfer_data.msgs++;
s3c2440_i2c_xfer_data.cur_msg++;
s3c2440_i2c_xfer_data.cur_ptr = 0;
s3c2440_i2c_xfer_data.state = STATE_START;
/* 發出START信號和發出設備地址 */
s3c2440_i2c_start();
break;
}
else
{
/* 是最後一個消息的最後一個數據 */
s3c2440_i2c_stop(0);
break;
}
break;
}

default: break;
}

/* 清中斷 */
s3c2440_i2c_regs->iiccon &= ~(S3C2410_IICCON_IRQPEND);

return IRQ_HANDLED;
}


/*
* I2C初始化
*/
static void s3c2440_i2c_init(void)
{
struct clk *clk;

clk = clk_get(NULL, "i2c");
clk_enable(clk);

// 選擇引腳功能:GPE15:IICSDA, GPE14:IICSCL
    s3c_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);
s3c_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);

/* bit[7] = 1, 使能ACK
* bit[6] = 0, IICCLK = PCLK/16
* bit[5] = 1, 使能中斷
* bit[3:0] = 0xf, Tx clock = IICCLK/16
* PCLK = 50MHz, IICCLK = 3.125MHz, Tx Clock = 0.195MHz
*/
s3c2440_i2c_regs->iiccon = (1<<7) | (0<<6) | (1<<5) | (0xf); // 0xaf

s3c2440_i2c_regs->iicadd = 0x10; // S3C24xx slave address = [7:1]
s3c2440_i2c_regs->iicstat = 0x10; // I2C串行輸出使能(Rx/Tx)
}

static int i2c_bus_s3c2440_init(void)
{
/* 2. 硬件相關的設置 */
s3c2440_i2c_regs = ioremap(0x54000000, sizeof(struct s3c2440_i2c_regs));//映射功能寄存器

s3c2440_i2c_init(); //初始化i2c控制器

request_irq(IRQ_IIC, s3c2440_i2c_xfer_irq, 0, "s3c2440-i2c", NULL); //申請中斷源,加載中斷處理函數-s3c2440_i2c_xfer_irq

init_waitqueue_head(&s3c2440_i2c_xfer_data.wait); //初始化一個等待隊列頭

/* 3. 注冊i2c_adapter */
i2c_add_adapter(&s3c2440_i2c_adapter);

return 0;
}

static void i2c_bus_s3c2440_exit(void)
{
i2c_del_adapter(&s3c2440_i2c_adapter);
free_irq(IRQ_IIC, NULL);
iounmap(s3c2440_i2c_regs);
}

module_init(i2c_bus_s3c2440_init);
module_exit(i2c_bus_s3c2440_exit);
MODULE_LICENSE("GPL");

附一份測試程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "i2c-dev.h"


/* i2c_usr_test </dev/i2c-0> <dev_addr> r addr
* i2c_usr_test </dev/i2c-0> <dev_addr> w addr val
*/

void print_usage(char *file)
{
printf("%s </dev/i2c-0> <dev_addr> r addr\n", file);
printf("%s </dev/i2c-0> <dev_addr> w addr val\n", file);
}

int main(int argc, char **argv)
{
int fd;
unsigned char addr, data;
int dev_addr;

if ((argc != 5) && (argc != 6))
{
print_usage(argv[0]);
return -1;
}

fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can't open %s\n", argv[1]);
return -1;
}

dev_addr = strtoul(argv[2], NULL, 0);
if (ioctl(fd, I2C_SLAVE, dev_addr) < 0)
{
/* ERROR HANDLING; you can check errno to see what went wrong */
printf("set addr error!\n");
return -1;
}

if (strcmp(argv[3], "r") == 0)
{
addr = strtoul(argv[4], NULL, 0);

data = i2c_smbus_read_word_data(fd, addr);

printf("data: %c, %d, 0x%2x\n", data, data, data);
}
else if ((strcmp(argv[3], "w") == 0) && (argc == 6))
{
addr = strtoul(argv[4], NULL, 0);
data = strtoul(argv[5], NULL, 0);
i2c_smbus_write_byte_data(fd, addr, data);
}
else
{
print_usage(argv[0]);
return -1;
}

return 0;
}

Make File:

KERN_DIR = /work/system/linux-3.4.2

all:
make -C $(KERN_DIR) M=`pwd` modules

clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order

obj-m += i2c_bus_s3c2440.o

Copyright © Linux教程網 All Rights Reserved