AM437x——I2C驱动

CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2017/09/11/AM437x——I2C驱动/#more

记录AM437x的I2C适配器驱动和I2C设备驱动,以及去除am437x EEPROM验证ID功能。


1.I2C驱动

在Linux中,I2C驱动有I2C适配器驱动(就是SOC的I2C控制器驱动)和I2C设备驱动(就是I2C设备,比如AT24C256)。

本次写驱动的过程,先是利用内核自带的I2C适配器驱动写了I2C设备驱动,待I2C设备驱动验证好了,再写的I2C适配器驱动。因此记录的顺序也是先是I2C设备驱动,再是I2C适配器驱动。

2.去除am437x EEPROM验证ID功能

前面裸机的对EEPROM随意的测试读写,改变了里面的一些数据。
Ti发布的SDK需要在不更改代码的情况下运行在Ti不同的EVM上,所以Ti在代码中会添加对EVM板上EEPROM内部ID的判断。
这就尴尬了,没人给我说过不能改啊 = = 。验证ID的数据被我改了,现在Uboot没法引导内核了。
= = 没办法,要么改回EEPROM的数据,要么去除Uboot的验证,权衡后,选择了后者。
通过查阅和测试,相关代码在:
~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/u-boot-2014.07+gitAUTOINC+fb6ab76dad-gfb6ab76/board/ti/am43xx/路径下的board.cboard.h

修改原理是,屏蔽掉去读eeprom的数据,手动指定板子和版本信息。
read_eeprom()函数中,注释掉原来的验证,在后面的strncpy()函数中指定板子信息。
修改后该部分代码如下:
{% codeblock lang:c%}
/*

  • Read header information from EEPROM into global structure.
    */
    static int read_eeprom(struct am43xx_board_id header)
    {
    #if 0
    /
    Check if baseboard eeprom is available */
    if (i2c_probe(CONFIG_SYS_I2C_EEPROM_ADDR)) {
    printf(“Could not probe the EEPROM at 0x%x\n”,
    CONFIG_SYS_I2C_EEPROM_ADDR);
    return -ENODEV;
    }

    /* read the eeprom using i2c */
    if (i2c_read(CONFIG_SYS_I2C_EEPROM_ADDR, 0, 2, (uchar *)header,
    sizeof(struct am43xx_board_id))) {
    printf(“Could not read the EEPROM\n”);
    return -EIO;
    }

    if (header->magic != 0xEE3355AA) {
    /*
    * read the eeprom using i2c again,
    * but use only a 1 byte address
    */
    if (i2c_read(CONFIG_SYS_I2C_EEPROM_ADDR, 0, 1, (uchar *)header,
    sizeof(struct am43xx_board_id))) {
    printf(“Could not read the EEPROM at 0x%x\n”,
    CONFIG_SYS_I2C_EEPROM_ADDR);
    return -EIO;
    }

     if (header->magic != 0xEE3355AA) {
     	printf("Incorrect magic number (0x%x) in EEPROM\n",
     	       header->magic);
         return -EINVAL;
     }
    

    }
    #endif

    //strncpy(am43xx_board_name, (char *)header->name, sizeof(header->name));
    strncpy(am43xx_board_name, “AM43__SK”, sizeof(header->name));
    am43xx_board_name[sizeof(header->name)] = 0;

    //strncpy(am43xx_board_rev, (char *)header->version, sizeof(header->version));
    strncpy(am43xx_board_rev, “1.2”,sizeof(header->version));
    am43xx_board_rev[sizeof(header->version)] = 0;

    return 0;
    }
    {% endcodeblock %}

修改后,重新编译Uboot,重新下载,又能愉快启动了。

3.I2C设备驱动

3.1 编写I2C设备驱动的四种方法

在内核linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b\Documentation\i2c\instantiating-devices里面介绍了How to instantiate I2C devices

说明了I2C设备驱动有以下四种编写方式:

Method 1:
  Method 1a: Declare the I2C devices by bus number
  Method 1b: Declare the I2C devices via devicetree
  Method 1c: Declare the I2C devices via ACPI
Method 2: Instantiate the devices explicitly
Method 3: Probe an I2C bus for certain devices
Method 4: Instantiate from user-space

  • Method 1a中,先定义一个板载信息结构体i2c_board_info,然后通过i2c_register_board_info()注册板载信息。
    然后i2c_board_info就被放在了i2c_board_list链表中。

但调用i2c_ragister_adapter()时,会使用i2c_scan_static_board_infoi2c_board_list进行扫描,调用i2c_new_device()创建client。

该方式的i2c_scan_static_board_infoi2c_ragister_adapter()之前,不适合动态加载(insmod方式)。

  • Method 1b中,就是把资源信息,以设备树的方式提供,换汤不换药。

  • Method 1c中,ACPI不懂,跳过。

  • Method 2中,翻译的意思是 明确实例化设备。还是先定义一个板载信息结构体i2c_board_info,然后通过i2c_new_device()i2c_new_probed_device()去创建client。这两个函数前者认为设备一定存在,根据i2c_board_info的地址创建设备。而i2c_new_probed_device()则会先检查i2c_board_info描述的地址设备是否真的存在,只有存在才会创建client。
    本次I2C设备驱动就是用的这种方式。

  • Method 3中,不需要事先确定适配器,内核不太推荐该方法,跳过。

  • Method 4,这个就NB了,直接从用户层创建I2C设备。但需要下个i2c-tools使用里面的头文件i2c-dev.h,才好在应用程序中,调用i2c_smbus_read_word_data进行访问。感觉统一了驱动,减少了驱动的工作量,但稍微加大了应用程序设计的工作量。这里我没使用,以后遇到了再说。
    从用户空间创建设备的方法:

//创建
echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-03/new_device
//删除
# echo 0x50 > /sys/bus/i2c/devices/i2c-0/new_device

3.2 采用Method 2编写驱动

采用设备平台驱动模型编写。

  • 首先分析at24cxx_dev.c,按照前面介绍的步骤:
    1.先定义一个板载信息结构体i2c_board_info
static struct i2c_board_info at24cxx_info = {	
    I2C_BOARD_INFO("at24c256", 0x50),
};

2.通过i2c_new_device()创建client:

static int at24cxx_dev_init(void)
{
    struct i2c_adapter *i2c_adap;

    printk(KERN_INFO"%s OK.\n",__func__); 

    i2c_adap = i2c_get_adapter(0);
    at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
    i2c_put_adapter(i2c_adap);

    return 0;
}

这里还通过i2c_get_adapter()获取了适配器0(AM437X的I2C控制器0),然后调用i2c_put_adapter再将其挂起。
现在I2C设备就有了。


  • 再分析at24cxx_drv.c:
    1.分配/设置一个i2c_driver:
static struct i2c_driver at24cxx_driver = {
    .driver	= {
        .name	= "ti_i2c0",
        .owner	= THIS_MODULE,
    },
    .probe		= at24cxx_probe,
    .remove		= at24cxx_remove,
    .id_table	= at24cxx_id_table,
};

name无所谓,这里还有probe()remove()函数,分别在模块加载和卸载时调用。这里的probe()加载,必须要id_table里面的设备名字与i2c_board_info相同才能加载。

2.注册i2c_driver:

    i2c_add_driver(&at24cxx_driver);

3.注册字符设备:
id_table里面的设备名字与i2c_board_info相同,调用probe()函数。
probe()函数进行字符设备注册那一套操作。

3.1申请设备号:

    if(alloc_chrdev_region(&devid, 0, TI_EEPROM_CNT, "at24cxx") < 0)
    {
        printk(KERN_INFO"%s ERROR.\n",__func__);
        goto error;
    }
    major = MAJOR(devid);

3.2绑定操作函数、创建类、创建设备:

    cdev_init(&at24cxx_cdev, &at24cxx_fops);        
    cdev_add(&at24cxx_cdev, devid, TI_EEPROM_CNT);   
    at24cxx_cls = class_create(THIS_MODULE, "ti_at24cxx");
    device_create(at24cxx_cls, NULL, MKDEV(major, 0), NULL, "at24cxx");      

4.释放资源:
在卸载模块时,释放probe()申请的资源:

    for(i=0;i<TI_EEPROM_CNT;i++)
    {
        device_destroy(at24cxx_cls,  MKDEV(major, i));	
    }
    class_destroy(at24cxx_cls);

    unregister_chrdev(major, "at24cxx");

5.完善操作函数:
这里只实现了EEPROM的随机读写。
先说写EEPROM,前面裸机的时候,写EEPROM是先发送一个8位的设备地址和写命令(这个控制器自动的,不管),然后是发送两个8位的数据地址,最后是一个8位要写的数据。
因此,用户层传进来的的参数是地址和数据,需要分解成两个8位地址和数据再发送,一共三个数据。

    if (copy_from_user(&ker_buf, user_buf, count))
        return -EFAULT;

    send[0] = ker_buf[0]>>8;
    send[1] = ker_buf[0];
    send[2] = ker_buf[1];

    if(i2c_master_send(at24cxx_client, send, 3))
        return 2;
    else 
        return -EIO;

再分析下写,裸机的时候对EEPROM写,需要先发送一个8位的设备地址和写命令(这个控制器自动的,不管),再发送两个数据8位地址,再发送一个8位的设备地址和读命令(这个控制器自动的,也不管),再读取8位数据。

因此,用户层传进来的是一个地址数据,需要分解成两个8位地址,发送出去,再接收一个8位数据。

    if (copy_from_user(&addr, user_buf, count))
        return -EFAULT;

    send[0] = addr>>8;
    send[1] = addr;

    i2c_master_send(at24cxx_client, send, 2);

    i2c_master_recv(at24cxx_client, &data, 1);

    ret = copy_to_user(user_buf, &data, 1);

  • 最后看看应用程序app_at24cxx.c:
    读取EEPROM格式是./app_at24cxx r addr,写EEPROM格式是./app_at24cxx w addr val
    1.先判断传入的参数:
    if ((argc != 3) && (argc != 4))
    {
        print_usage(argv[0]);
        return -1;
    }

2.打开EEPROM设备:

    fd = open("/dev/at24cxx", O_RDWR);

3.判断如果是读操作:

    if (strcmp(argv[1], "r") == 0)
    {
        buf[0] = strtoul(argv[2], NULL, 0);
        read(fd, buf, 1);
        printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
    }

strcmp()比较字符串,strtoul()将字符串转换成整型。

4.判断如果是写操作:

    else if ((strcmp(argv[1], "w") == 0) && (argc == 4))
    {
        buf[0] = strtoul(argv[2], NULL, 0);
        buf[1] = strtoul(argv[3], NULL, 0);

        if (write(fd, buf, 2) == 2)
            printf("write ok, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
        else
            printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
    }

write()的写成功返回的是实际写入的字节数,这个靠驱动的写函数的返回值。

3.3 完整代码

{% codeblock lang:c [at24cxx_dev.c] https://github.com/hceng/am437x/blob/master/drive/3th_i2c/v1.0/at24cxx_dev.c %}
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>

static struct i2c_board_info at24cxx_info = {
I2C_BOARD_INFO(“at24c256”, 0x50),
};

static struct i2c_client *at24cxx_client;

static int at24cxx_dev_init(void)
{
struct i2c_adapter *i2c_adap;

printk(KERN_INFO"%s OK.\n",__func__); 

i2c_adap = i2c_get_adapter(0);
at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info);
i2c_put_adapter(i2c_adap);

return 0;

}

static void at24cxx_dev_exit(void)
{
printk(KERN_INFO"%s OK.\n",func);
i2c_unregister_device(at24cxx_client);
}

module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am437x board i2c device to at24c256”);
MODULE_ALIAS(“platform:device tree:ti_i2c”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

{% codeblock lang:c [at24cxx_drv.c] https://github.com/hceng/am437x/blob/master/drive/3th_i2c/v1.0/at24cxx_drv.c %}
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>

#define TI_EEPROM_CNT 1

static int major;
static struct cdev at24cxx_cdev;
static struct class *at24cxx_cls;
static struct i2c_client *at24cxx_client;

/*

  • 传入: buf[0] : addr

  • 输出: buf[0] : data
    */
    static ssize_t at24cxx_read(struct file * file, char __user *user_buf, size_t count, loff_t *off)
    {
    unsigned char addr, data;
    char send[2];
    int ret;

    if(count != 1){
    printk(KERN_INFO"at24cxx_read count != 1.\n");
    return 1;
    }

    if (copy_from_user(&addr, user_buf, count))
    return -EFAULT;

    send[0] = addr>>8;
    send[1] = addr;

    i2c_master_send(at24cxx_client, send, 2);

    i2c_master_recv(at24cxx_client, &data, 1);

    ret = copy_to_user(user_buf, &data, 1);

    return 1;
    }

/* buf[0] : addr

  • buf[1] : data
    */
    static ssize_t at24cxx_write(struct file *file, const char __user *user_buf, size_t count, loff_t *off)
    {
    unsigned char ker_buf[2];
    char send[3];

    printk(KERN_INFO"%s OK.\n",func);

    if(count != 2){
    printk(KERN_INFO"at24cxx_write count != 2.\n");
    return 1;
    }

    if (copy_from_user(&ker_buf, user_buf, count))
    return -EFAULT;

    //printk(“ker_buf[0]= 0x%02x, ker_buf[1]= x%02x\n”, ker_buf[0], ker_buf[1]);

    send[0] = ker_buf[0]>>8;
    send[1] = ker_buf[0];
    send[2] = ker_buf[1];

    if(i2c_master_send(at24cxx_client, send, 3))
    return 2;
    else
    return -EIO;
    }

static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
};

static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
dev_t devid;
at24cxx_client = client;

printk(KERN_INFO"%s OK.\n",__func__); 

//1.申请设备号
if(alloc_chrdev_region(&devid, 0, TI_EEPROM_CNT, "at24cxx") < 0)
{
    printk(KERN_INFO"%s ERROR.\n",__func__);
    goto error;
}
major = MAJOR(devid);

//2.注册到系统中
cdev_init(&at24cxx_cdev, &at24cxx_fops);        
cdev_add(&at24cxx_cdev, devid, TI_EEPROM_CNT);   
at24cxx_cls = class_create(THIS_MODULE, "ti_at24cxx");
device_create(at24cxx_cls, NULL, MKDEV(major, 0), NULL, "at24cxx");    

error:
unregister_chrdev_region(MKDEV(major, 0), TI_EEPROM_CNT);

return 0;

}

static int at24cxx_remove(struct i2c_client *client)
{
unsigned int i;

printk(KERN_INFO"%s OK.\n",__func__); 

for(i=0;i<TI_EEPROM_CNT;i++)
{
    device_destroy(at24cxx_cls,  MKDEV(major, i));	
}
class_destroy(at24cxx_cls);

unregister_chrdev(major, "at24cxx");

return 0;

}

static const struct i2c_device_id at24cxx_id_table[] = {
{ “at24c256”, 0 },
{}
};

/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver = {
.driver = {
.name = “ti_i2c0”,
.owner = THIS_MODULE,
},
.probe = at24cxx_probe,
.remove = at24cxx_remove,
.id_table = at24cxx_id_table,
};

static int at24cxx_drv_init(void)
{
/* 2. 注册i2c_driver */
printk(KERN_INFO"%s OK.\n",func);

i2c_add_driver(&at24cxx_driver);

return 0;

}

static void at24cxx_drv_exit(void)
{
printk(KERN_INFO"%s OK.\n",func);

i2c_del_driver(&at24cxx_driver);

}

module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am437x board i2c drvice to at24c256”);
MODULE_ALIAS(“platform:device tree:ti_i2c”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

{% codeblock lang:c [app_at24cxx.c] https://github.com/hceng/am437x/blob/master/drive/3th_i2c/v1.0/app_at24cxx.c %}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/*

  • ./app_at24cxx r addr
  • ./app_at24cxx w addr val
    */

void print_usage(char *file)
{
printf("%s r addr\n", file);
printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
int fd;
unsigned char buf[2];
int x;

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

fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
{
    printf("can't open /dev/at24cxx\n");
    return -1;
}

if (strcmp(argv[1], "r") == 0)
{
    buf[0] = strtoul(argv[2], NULL, 0);
    read(fd, buf, 1);
    printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
}
else if ((strcmp(argv[1], "w") == 0) && (argc == 4))
{
    buf[0] = strtoul(argv[2], NULL, 0);
    buf[1] = strtoul(argv[3], NULL, 0);

    if (write(fd, buf, 2) == 2)
        printf("write ok, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
    else
        printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
}
else
{
    print_usage(argv[0]);
    return -1;
}

close(fd);

return 0;

}
{% endcodeblock %}

4.I2C适配器驱动

下面开始比较好玩的I2C适配器驱动。
去除内核的I2C适配器模块,删除设备树文件里相关的I2C资源信息。

4.1 编写I2C适配器驱动

I2C适配器驱动采用最原始的方法编写。
1.入口函数
入口函数做了四件事情:硬件相关设置、注册中断、初始化等待队列、注册I2C适配器。

static const struct i2c_algorithm am437x_i2c_algo = {
    //	.smbus_xfer     = ,
    .master_xfer	= am437x_i2c_xfer,
    .functionality	= am437x_i2c_func,
};

static struct i2c_adapter am437x_i2c_adapter = {
    .name	        = "i2c_adapter",
    .algo		    = &am437x_i2c_algo,//i2c总线通信方法
    .owner 		    = THIS_MODULE,
};

static void i2c_init(void)
{
    printk(KERN_INFO"%s OK.\n",__func__); 

    /*clk*/
    PRCM_CM_WKUP_I2C0_CLKCTRL   = ioremap(0x44DF2800+0x340, 0x04*1);
    *PRCM_CM_WKUP_I2C0_CLKCTRL  |= (0x02<<0);

    PRCM_CM_WKUP_CLKSTCTRL      = ioremap(0x44DF2800+0x300, 0x04*1);
    *PRCM_CM_WKUP_CLKSTCTRL     |= (0x01<<14);

    PRCM_CM_PER_L4LS_CLKSTCTRL  = ioremap(0x44DF8800+0x400, 0x04*1);
    *PRCM_CM_PER_L4LS_CLKSTCTRL |= (0x01<<27);

    /*GPIO:gpio3_5->I2C0_SDA;gpio3_6->I2C0_SCL*/

    CTRL_CONF_I2C0_SDA = ioremap(0x44E10000+0x0988, 0x04*1);
    CTRL_CONF_I2C0_SCL = ioremap(0x44E10000+0x098C, 0x04*1);

    *CTRL_CONF_I2C0_SDA &= ~(0x07<<0 | 0x01<<16 | 0x01<<19);
    *CTRL_CONF_I2C0_SCL &= ~(0x07<<0 | 0x01<<16 | 0x01<<19);

    /*I2C0 set*/
    I2C0 = ioremap(0x44E0B000, sizeof(struct am437x_i2c_regs));

    I2C0->CON  &= ~(0x01<<15);//reset

    I2C0->SYSC &= ~(0x01<<0);//Auto Idle disabled.

    I2C0->PSC  = 3;//the module divided by (PSC + 1) -> 48M/(3+1)=12M

    I2C0->SCLL = 63;//tLOW = (SCLL + 7) * ICLK time period
    I2C0->SCLH = 65;//tHIGH = (SCLH + 5) * ICLK time period

    //I2C0->SA = 0x50;//Slave address.x 1010 000

    I2C0->CON  |=  (0x01<<15);//Module enabled	
}

static int i2c_bus_drv_init(void)
{
    printk(KERN_INFO"%s OK.\n",__func__); 

    //硬件相关的设置
    i2c_init();

    if (request_irq(102, i2c_xfer_irq, 0, "am437x-i2c", NULL))
        return -EAGAIN;

    init_waitqueue_head(&i2c_data.wait);

    i2c_add_adapter(&am437x_i2c_adapter);//会在/sys/class/i2c-adapter下生成i2c-x

    return 0;
}

硬件设置部分,初始化了相关时钟,设置GPIO复用,设置了I2C0控制器的一些设置。
这里设置直接用ioremap()映射寄存器进程操作,是很不对的,内核是个整体,相关之间有关联,理论上时钟部分内核已经封装好了,我现在只需要调用相关就行。但秉着先调通,再优化改进的思想,映射是最快,最方便的= =。
同理GPIO的设置和中断号也是不规范的。

i2c_add_adapter()会调用i2c_register_adapter()注册适配器,自动完成字符设备注册那一套。
绑定了结构体i2c_adapteri2c_adapter里面有I2C总线通信方法i2c_algorithmi2c_algorithm里面两个函数:
.master_xfer = am437x_i2c_xfer,:传输函数;
.functionality = am437x_i2c_func,:支持的协议;

I2C适配器驱动的关键就是实现这两个函数。

2.出口函数
与入口函数相反操作:

static void i2c_bus_drv_exit(void)
{
    printk(KERN_INFO"%s OK.\n",__func__); 

    iounmap(CTRL_CONF_I2C0_SDA);
    iounmap(CTRL_CONF_I2C0_SCL);

    iounmap(PRCM_CM_WKUP_I2C0_CLKCTRL);
    iounmap(PRCM_CM_WKUP_CLKSTCTRL);
    iounmap(PRCM_CM_PER_L4LS_CLKSTCTRL);

    iounmap(I2C0);

    free_irq(102, NULL);

    i2c_del_adapter(&am437x_i2c_adapter);
}

3.实现关键函数一:am437x_i2c_func()
虽说是关键函数,但这个是送分题,返回支持协议即可。

static u32  am437x_i2c_func(struct i2c_adapter *adap)
{
    printk(KERN_INFO"%s OK.\n",__func__); 
    //用于返回总线支持的协议
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}

4.实现关键函数二:am437x_i2c_xfer()
先定义个传输数据的结构体:

struct am437x_i2c_xfer_data {
    struct i2c_msg *msgs;
    uint32_t cur_rec;
    uint32_t cur_send;
    uint32_t reg_cnt;
    uint32_t err;
    wait_queue_head_t wait;
};
static struct am437x_i2c_xfer_data i2c_data;

里面包含了i2c_msg结构体、当前接收位置cur_rec、当前发送位置cur_send、CNT寄存器计数reg_cnt、状态标志err、唤醒队列wait

i2c_msg结构体比较重要,包含了从机地址,发送/接收标志、数据长度和数据指针。

struct i2c_msg {  
    __u16 addr;     // 从机地址  
    __u16 flags;    // 标志  
    #define I2C_M_TEN   0x0010  // 十位地址标志  
    #define I2C_M_RD    0x0001  // 接收数据标志  
    __u16 len;      // 数据长度  
    __u8 *buf;      // 数据指针  
}; 

前面at24cxx_drv.c中调用发送i2c_master_send()和接收i2c_master_recv()时,就会调用这里的传输函数am437x_i2c_xfer()
先初始化相关变量:

    i2c_data.msgs    = msgs;
    i2c_data.cur_rec = 0;
    i2c_data.cur_send = 0;
    i2c_data.err     = -ENODEV;

    I2C0->CNT = i2c_data.msgs->len;
    i2c_data.reg_cnt = I2C0->CNT;

    I2C0->SA = i2c_data.msgs->addr;

保存传入的i2c_msg,设置当前接收位置cur_rec和当前发送位置cur_send为0,赋值给I2C0->CNT寄存器传输长度,因为I2C0->CNT在后面不好把握什么时候会清零,再将I2C0->CNT赋值给reg_cnt,作为要传输数据的总长度。再将设备地址传给寄存器。

接着开始传输:

static void am437x_i2c_start(void)
{
    printk(KERN_INFO"%s OK.\n",__func__); 
    
    I2C0->IRQSTS	|=  0x7FFF;
    I2C0->IRQEN_CLR |=  0x7FFF;

    if(i2c_data.msgs->flags & I2C_M_RD) //read
    {

        I2C0->CON |=  (0x01<<10 | (0x01<<15));  //MST=1  TRX=0 
        I2C0->CON &= ~(0x01<<9);

        I2C0->IRQEN_SET |= (0x01<<3);
    }
    else//write
    {
        I2C0->CON |= (0x01<<9 | 0x01<<10 | 0x01<<15); //MST=1  TRX=1    

        I2C0->IRQEN_SET |= (0x01<<4);
    }
    
    I2C0->CON |= (0x01<<0);
}

先清除所有中断,利用i2c_data.msgs->flags判断时读还是写,设置对应模式和对应中断,最后启动传输。

随后进入休眠,中断就会根据情况发送/接收数据,待满足 当前传输长度等于总传输长度时,唤醒该函数,完成本次传输。

5.中断函数
前面am437x_i2c_start()中设置I2C0->CON |= (0x01<<0);后,AM437X的I2C控制器就会自动传输数据,等完成后,生成对应的标志位。
但发生中断后,进入中断函数,先清除除 接收标志和发送标志 以外的中断(更好的方式保留ACK响应标志和bus空闲标志,这里简化,暂不管)。
判断是接收完成还是发送完成,
如果是接收完成:
清除接收中断,保存I2C0->DATA数据,同时,当前接收标志i2c_data.cur_rec加1,直到 当前接收等于总传输,即可关闭中断,发送停止信号。
如果是发送完成:
清除发送中断,赋值I2C0->DATA数据,同时,当前接收标志i2c_data.cur_send加1,直到 当前发送等于总传输,即可关闭中断,发送停止信号。

static irqreturn_t i2c_xfer_irq(int irq, void *dev_id)
{
    unsigned int status = 0;

    printk(KERN_INFO"%s OK.\n",__func__); 

    status = I2C0->IRQSTS;

    //I2C0->IRQSTS = (status & (0x01<<1 |0x01<<3 | 0x01<<4));
    I2C0->IRQSTS = (status & (0x01<<3 | 0x01<<4));

    if(status & (0x01<<3))//receive
    {
        //printk(KERN_INFO"i2c receive.\n"); 
        I2C0->IRQSTS |= (0x01<<3);

        i2c_data.msgs->buf[i2c_data.cur_rec] = I2C0->DATA;
        i2c_data.cur_rec++;

        if(i2c_data.cur_rec == i2c_data.reg_cnt) 
        {
            I2C0->IRQEN_CLR |= (0x01<<3); 
            am437x_i2c_stop(1);
        }

    }
    if (status & (0x01<<4))//send
    {
        //printk(KERN_INFO"i2c send.\n");

        I2C0->IRQSTS |= (0x01<<4);	

        I2C0->DATA = i2c_data.msgs->buf[i2c_data.cur_send];
        i2c_data.cur_send++;

        if(i2c_data.cur_send == i2c_data.reg_cnt)
        {
            I2C0->IRQEN_CLR |= (0x01<<4);
            am437x_i2c_stop(1);
        }
    } 
    
    //判断是否有ack和bus空闲

    I2C0->IRQSTS	|=  0x7FFF;

    return IRQ_HANDLED;
}

am437x_i2c_stop()里,除了要发送停止信号,还要唤醒队列。

static void am437x_i2c_stop(int err)
{
    printk(KERN_INFO"%s OK.\n",__func__); 

    i2c_data.err = err;

    I2C0->CON |= (0x01<<1); //stop
    ndelay(50);

    /*唤醒*/
    wake_up(&i2c_data.wait);
}

这就完了,还是比较清晰的。

4.2 完整代码

{% codeblock lang:c [i2c_bus_am437x.c] https://github.com/hceng/am437x/blob/master/drive/3th_i2c/v2.0/i2c_bus_am437x.c %}
#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 <asm/irq.h>

struct am437x_i2c_xfer_data {
struct i2c_msg *msgs;
uint32_t cur_rec;
uint32_t cur_send;
uint32_t reg_cnt;
uint32_t err;
wait_queue_head_t wait;
};
static struct am437x_i2c_xfer_data i2c_data;

static volatile unsigned long *CTRL_CONF_I2C0_SDA;
static volatile unsigned long *CTRL_CONF_I2C0_SCL;
static volatile unsigned long *PRCM_CM_WKUP_I2C0_CLKCTRL;
static volatile unsigned long *PRCM_CM_WKUP_CLKSTCTRL;
static volatile unsigned long *PRCM_CM_PER_L4LS_CLKSTCTRL;

struct am437x_i2c_regs {
uint32_t REVNB_LO; //00h
uint32_t REVNB_HI; //04h
uint32_t RESERVED0[2]; //08h
uint32_t SYSC; //10h
uint32_t RESERVED1[4]; //14h
uint32_t IRQSTS_RAW; //24h
uint32_t IRQSTS; //28h
uint32_t IRQEN_SET; //2ch
uint32_t IRQEN_CLR; //30h
uint32_t WE; //34h
uint32_t DMARXEN_SET; //38h
uint32_t DMATXEN_SET; //3ch
uint32_t DMARXEN_CLR; //40h
uint32_t DMATXEN_CLR; //44h
uint32_t DMARXWAKE_EN; //48h
uint32_t DMATXWAKE_EN; //4ch
uint32_t RESERVED2[16]; //50h
uint32_t SYSS; //90h
uint32_t BUF; //94h
uint32_t CNT; //98h
uint32_t DATA; //9ch
uint32_t RESERVED3; //a0h
uint32_t CON; //a4h
uint32_t OA; //a8h
uint32_t SA; //ach
uint32_t PSC; //b0h
uint32_t SCLL; //b4h
uint32_t SCLH; //b8h
uint32_t SYSTEST; //bch
uint32_t BUFSTAT; //c0h
uint32_t OA1; //c4h
uint32_t OA2; //c8h
uint32_t OA3; //cch
uint32_t ACTOA; //d0h
};

static volatile struct am437x_i2c_regs *I2C0;

static void am437x_i2c_start(void)
{
printk(KERN_INFO"%s OK.\n",func);

I2C0->IRQSTS	|=  0x7FFF;
I2C0->IRQEN_CLR |=  0x7FFF;

if(i2c_data.msgs->flags & I2C_M_RD) //read
{

    I2C0->CON |=  (0x01<<10 | (0x01<<15));  //MST=1  TRX=0 
    I2C0->CON &= ~(0x01<<9);

    I2C0->IRQEN_SET |= (0x01<<3);
}
else//write
{
    I2C0->CON |= (0x01<<9 | 0x01<<10 | 0x01<<15); //MST=1  TRX=1    

    I2C0->IRQEN_SET |= (0x01<<4);
}

I2C0->CON |= (0x01<<0);

}

static void am437x_i2c_stop(int err)
{
printk(KERN_INFO"%s OK.\n",func);
i2c_data.err = err;

mdelay(2);//为了防止读EEPROM的时候,写操作后,马上就读操作,导致错误;
I2C0->CON |= (0x01<<1); //stop
ndelay(50);//等待停止信号发完

/*唤醒*/
wake_up(&i2c_data.wait);

}

static irqreturn_t i2c_xfer_irq(int irq, void *dev_id)
{
unsigned int status = 0;

printk(KERN_INFO"%s OK.\n",__func__); 

status = I2C0->IRQSTS;

//I2C0->IRQSTS = (status & (0x01<<1 |0x01<<3 | 0x01<<4));
I2C0->IRQSTS = (status & (0x01<<3 | 0x01<<4));

if(status & (0x01<<3))//receive
{
    //printk(KERN_INFO"i2c receive.\n"); 
    I2C0->IRQSTS |= (0x01<<3);

    i2c_data.msgs->buf[i2c_data.cur_rec] = I2C0->DATA;
    i2c_data.cur_rec++;

    if(i2c_data.cur_rec == i2c_data.reg_cnt) 
    {
        I2C0->IRQEN_CLR |= (0x01<<3); 
        am437x_i2c_stop(1);
    }

}
if (status & (0x01<<4))//send
{
    //printk(KERN_INFO"i2c send.\n");

    I2C0->IRQSTS |= (0x01<<4);	

    I2C0->DATA = i2c_data.msgs->buf[i2c_data.cur_send];
    i2c_data.cur_send++;

    if(i2c_data.cur_send == i2c_data.reg_cnt)
    {
        I2C0->IRQEN_CLR |= (0x01<<4);
        am437x_i2c_stop(1);
    }
} 

//判断是否有ack和bus空闲

I2C0->IRQSTS	|=  0x7FFF;

return IRQ_HANDLED;

}

static int am437x_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
//用于实现I2C协议,将i2c_msg消息传给i2c设备
unsigned long timeout;

printk(KERN_INFO"%s OK.\n",__func__); 

//把num个i2c_msg的I2C数据发送出去/读进来
i2c_data.msgs    = msgs;
i2c_data.cur_rec = 0;
i2c_data.cur_send = 0;
i2c_data.err     = -ENODEV;

I2C0->CNT = i2c_data.msgs->len;
i2c_data.reg_cnt = I2C0->CNT;

I2C0->SA = i2c_data.msgs->addr;

am437x_i2c_start();

/*休眠*/

if(i2c_data.msgs->flags & I2C_M_RD) //读
{
    timeout = wait_event_timeout(i2c_data.wait,(i2c_data.cur_rec == i2c_data.reg_cnt), HZ * 5);//5S    
}
else
{
    timeout = wait_event_timeout(i2c_data.wait, (i2c_data.cur_send == i2c_data.reg_cnt), HZ * 5);//5S
}

if(0 == timeout)
{
    printk("am437x i2c timeout.\n");
    return -ETIMEDOUT;
}
else
{
    return i2c_data.err;
}

}

static u32 am437x_i2c_func(struct i2c_adapter *adap)
{
printk(KERN_INFO"%s OK.\n",func);
//用于返回总线支持的协议
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}

static const struct i2c_algorithm am437x_i2c_algo = {
// .smbus_xfer = ,
.master_xfer = am437x_i2c_xfer,
.functionality = am437x_i2c_func,
};

static struct i2c_adapter am437x_i2c_adapter = {
.name = “i2c_adapter”,
.algo = &am437x_i2c_algo,//i2c总线通信方法
.owner = THIS_MODULE,
};

static void i2c_init(void)
{
printk(KERN_INFO"%s OK.\n",func);

/*clk*/
PRCM_CM_WKUP_I2C0_CLKCTRL   = ioremap(0x44DF2800+0x340, 0x04*1);
*PRCM_CM_WKUP_I2C0_CLKCTRL  |= (0x02<<0);

PRCM_CM_WKUP_CLKSTCTRL      = ioremap(0x44DF2800+0x300, 0x04*1);
*PRCM_CM_WKUP_CLKSTCTRL     |= (0x01<<14);

PRCM_CM_PER_L4LS_CLKSTCTRL  = ioremap(0x44DF8800+0x400, 0x04*1);
*PRCM_CM_PER_L4LS_CLKSTCTRL |= (0x01<<27);

/*GPIO:gpio3_5->I2C0_SDA;gpio3_6->I2C0_SCL*/

CTRL_CONF_I2C0_SDA = ioremap(0x44E10000+0x0988, 0x04*1);
CTRL_CONF_I2C0_SCL = ioremap(0x44E10000+0x098C, 0x04*1);

*CTRL_CONF_I2C0_SDA &= ~(0x07<<0 | 0x01<<16 | 0x01<<19);
*CTRL_CONF_I2C0_SCL &= ~(0x07<<0 | 0x01<<16 | 0x01<<19);

/*I2C0 set*/
I2C0 = ioremap(0x44E0B000, sizeof(struct am437x_i2c_regs));

I2C0->CON  &= ~(0x01<<15);//reset

I2C0->SYSC &= ~(0x01<<0);//Auto Idle disabled.

I2C0->PSC  = 3;//the module divided by (PSC + 1) -> 48M/(3+1)=12M

I2C0->SCLL = 63;//tLOW = (SCLL + 7) * ICLK time period
I2C0->SCLH = 65;//tHIGH = (SCLH + 5) * ICLK time period

//I2C0->SA = 0x50;//Slave address.x 1010 000

I2C0->CON  |=  (0x01<<15);//Module enabled	

}

static int i2c_bus_drv_init(void)
{
printk(KERN_INFO"%s OK.\n",func);

//硬件相关的设置
i2c_init();

if (request_irq(102, i2c_xfer_irq, 0, "am437x-i2c", NULL))
    return -EAGAIN;

init_waitqueue_head(&i2c_data.wait);

i2c_add_adapter(&am437x_i2c_adapter);//会在/sys/class/i2c-adapter下生成i2c-x

return 0;

}

static void i2c_bus_drv_exit(void)
{
printk(KERN_INFO"%s OK.\n",func);

iounmap(CTRL_CONF_I2C0_SDA);
iounmap(CTRL_CONF_I2C0_SCL);

iounmap(PRCM_CM_WKUP_I2C0_CLKCTRL);
iounmap(PRCM_CM_WKUP_CLKSTCTRL);
iounmap(PRCM_CM_PER_L4LS_CLKSTCTRL);

iounmap(I2C0);

free_irq(102, NULL);

i2c_del_adapter(&am437x_i2c_adapter);

}

module_init(i2c_bus_drv_init);
module_exit(i2c_bus_drv_exit);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am437x board i2c bus drvice.”);
MODULE_ALIAS(“ti_i2c”);
MODULE_VERSION(“V2.0”);
{% endcodeblock %}

5.测试结果

加载i2c0总线驱动,再加载at24cxx设备驱动,最后运行应用程序。
写1地址1,2地址2,3地址3,4地址4; 再读取1地址数据,2地址数据,3地址数据,4地址数据。

再看下逻辑分析仪效果:
向地址0x02写入数据2:

向地址0x02读出数据2:

(PS:图片经过处理,让其在一张图上显示,因此时间标识有误。)

6.心得

本次I2C适配器驱动,算是第一次写一个较复杂的驱动,目的是先写通,其次才是优化,因此存在以下一些问题:
1.没有使用系统提供的时钟、GPIO配置等函数,全是自己映射;
2.中断没有对总线空闲、没有ack进行判断;
3.有些函数返回值没有处理;

收获:
1.先调通再优化,现在调通了,优化等下次有机会在优化;
2.调不动的时候,拿逻辑分析仪抓,再对照标准波形,一找一个准;

在写博客的时候,发现个BUG:
printk()打印调试的时候,一切正常。
在关闭printk()打印后,先对EEPROM写操作,再去读操作,发现怎么都是255。
这种情况,多半是哪里延时时序问题。
用逻辑分析仪抓波形,一个个printk()的关,最终发现问题在am437x_i2c_stop()。
波形如下:

可以看到地址发错了,加上一个2ms的延时后,即正常。

I2C驱动完了,下一个计划,LCD驱动。?

7. i2c-tools的使用

i2c-tools是Linux应用层,用于对i2c总线、设备进行简单测试、操作的工具。
包含的工具有:

i2cdetect – 用来列举I2C Bus上的所有设备
i2cdump – 显示指定I2C设备的寄存器值
i2cget – 读取指定I2C设备的某个寄存器的值
i2cset – 写入指定I2C设备的某个寄存器的值

eg:

  • 1.用i2cdetect检测有几组i2c总线在系统上:
# i2cdetect -l
i2c-0   i2c             OMAP I2C adapter                        I2C adapter
i2c-1   i2c             OMAP I2C adapter                        I2C adapter
  • 2.用i2cdetect检测挂载在i2c总线上器件:
# i2cdetect -r -y  1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

可以看到0x1b0x50地址上挂有有I2C设备;
UU的代表该设备有被侦测到并正在被内核驱动器使用着;

  • 3.用i2cdump查看器件所有寄存器的值:
# i2cdump -f -y 1 0x50
No size specified (using byte-data access)
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 00 08 04 06 08 ff ff ff ff ff ff ff ff ff ff ff    .????...........
10: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
20: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
30: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
40: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
50: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
60: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
70: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
80: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
90: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff    ................
  • 3.用i2cset设置单个寄存器值,用i2cget读取单个寄存器值:
# i2cget -f -y 1 0x50 0x01
0x08
# i2cset -f -y 1 0x50 0x01 0x44
# i2cget -f -y 1 0x50 0x01
0x44
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值