linux 驱动 --- I2C总线及驱动

I2C总线

I2C总线和 platform 总线非常类似,platform 总线初始化位置如下:

kernel_init();
    kernel_init_freeable();
        do_basic_setup();
            driver_init();
                platform_bus_init();

I2C总线是作为一个模块进行初始化

postcore_initcall(i2c_init);
 #define postcore_initcall(fn)       __define_initcall(fn, 2)

由此可见,platform 总线不可或缺,I2C总线是可以被裁剪的。

I2C总线的全局变量如下:

I2C适配器(控制器)设备、I2C设备

每一个soc上基本都会有一个或者多个I2C控制器,每一个I2C控制器下面接的就是SCL 和 SDA 两条线。I2C设备都是挂在这两条线下面的,这就是就硬件层的基本结构。

硬件层的上面就是Linux内核中的适配器驱动层,每一个soc厂家都会在Linux框架上实现自家I2C适配器设备的的驱动程序,这部分是由soc厂家的bsp工程师提供的。适配器的的驱动程序其实就是根据I2C控制器的寄存器操作实现了如何将数据送到I2C总线下的设备中去,以及怎么读取数据。厂家将这一层封装好,驱动工程师写驱动的时候就只需要根据具体的I2C设备来读写数据,不需要自己来实现I2C时序。简单点就说设备驱动只负责准备要发的数据,告诉适配器就可以了。至于怎么发就是厂家自己去实现了,毕竟只有他们才最熟悉自家的芯片。

 

I2C适配器就是根据设备树提供的I2C控制器信息配置芯片,如下i2c1

            i2c1: i2c@21a0000 {
                #address-cells = <1>;
                #size-cells = <0>;
                compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
                reg = <0x021a0000 0x4000>;
                interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
                clocks = <&clks IMX6UL_CLK_I2C1>;
                status = "disabled";
            };

 

&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    magnetometer@e {
        compatible = "fsl,mag3110";
        reg = <0x0e>;
        vdd-supply = <&reg_peri_3v3>;
        vddio-supply = <&reg_peri_3v3>;
        position = <2>;
    };

    fxls8471@1e {
        compatible = "fsl,fxls8471";
        reg = <0x1e>;
        position = <0>;
        interrupt-parent = <&gpio5>;
        interrupts = <0 8>;
    };
};

 

static const struct of_device_id i2c_imx_dt_ids[] = {
    { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
    { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
    { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
    { .compatible = "fsl,imx7d-i2c", .data = &imx7d_i2c_hwdata, },
    { /* sentinel */ }
};

static struct platform_driver i2c_imx_driver = {
    .probe = i2c_imx_probe,
    .remove = i2c_imx_remove,
    .driver = {
        .name = DRIVER_NAME,
        .pm = &i2c_imx_pm_ops,
        .of_match_table = i2c_imx_dt_ids,
        .acpi_match_table = i2c_imx_acpi_ids,
    },
    .id_table = imx_i2c_devtype,
};

static int __init i2c_adap_imx_init(void)
{
    return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);
 

i2c_adap_imx_init() 是芯片厂家提供的适配器驱动,设备和驱动匹配执行 i2c_imx_probe() ,内容有:

  i2c_register_adapter() 添加 i2c_adapter_type 类型设备到 I2C 总线 (bus_add_device),即添加到链表 i2c_bus_type->p->klist_devices,此设备是适配器,即以上设备树的 i2c1
  of_i2c_register_devices() --->i2c_new_client_device 添加 i2c_client_type 类型设备到 I2C 总线,即以上设备树的 magnetometer 和 fxls8471,是实际的I2C接口的外部芯片,具体的I2C设备。一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client。
 
总结:设备树上有I2C适配器设备和I2C设备,都需要对应的驱动程序,I2C适配器设备的驱动程序由厂家提供,I2C设备由开发者自行实现。

I2C驱动

驱动程序实例

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <asm/ioctl.h>
#include <asm/uaccess.h>
 
#define MY_DRV_NAME     "at24cxx"
#define MY_CLASS        "at24cxxClass"
 
 
static int AT24CXX_SIZE = 256;
 
 
struct my_at24_dev {
    struct i2c_client *    m_client; 
    struct class *         m_eeprom_class;
    int                 m_major;
    struct mutex          m_mutex;
};
static struct my_at24_dev * my_at24_devp;
 
unsigned int i2c_RxAddr;
 
 
 
static const struct i2c_device_id my_at24_ids[] = {
    {"my_24cxx", 0},  //和设备树节点 .compatible 属性值匹配
    { },
 
};
 
static ssize_t my_at24c_read(struct file * flip, char __user * buff, size_t count, loff_t * offset)
{
    ssize_t retval = 0;
    unsigned char addr = i2c_RxAddr&0xff;
 
    unsigned char recv_buff[512];
    struct i2c_msg msgs[2];
    struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data;
    //printk(KERN_INFO "%s enter,count=%d,i2c_RxAddr[0]=%d,i2c_RxAddr[1]=%d\r\n", __FUNCTION__,count,i2c_RxAddr[0],i2c_RxAddr[1]);
    mutex_lock(&at24_dev->m_mutex);
    /*first write the register offset address */
    msgs[0].addr = at24_dev->m_client->addr;    // address of slave device
    msgs[0].flags = 0;    // 0:write, 1:read
    msgs[0].len = 1;
    msgs[0].buf = &addr;
 
    msgs[1].addr = at24_dev->m_client->addr;    
    msgs[1].flags = 1;    
    msgs[1].len = sizeof(recv_buff);
    msgs[1].buf = recv_buff;
 
    retval = i2c_transfer(at24_dev->m_client->adapter, msgs, ARRAY_SIZE(msgs));
    //printk(KERN_INFO "%s: i2c_transfer return %d\r\n", __FUNCTION__,retval);
    if(retval != 2){
        printk(KERN_INFO "my_at24c_read failed");
        goto err;
    }
    
    retval = copy_to_user(buff, recv_buff, count);
    retval = count;
    
err:    
    mutex_unlock(&at24_dev->m_mutex);
    return retval;
}
 
static ssize_t my_at24c_write(struct file *flip, const char __user *buff, size_t count, loff_t *offset)
{
    ssize_t retval = 0;
    unsigned char send_buff[512];
    struct i2c_msg msgs[1];
    unsigned int len = 0;
    struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data;
    mutex_lock(&at24_dev->m_mutex);
 
    if(count > AT24CXX_SIZE){
        retval = -1;
        goto err;
    }
    //offset address
    send_buff[len++] = i2c_RxAddr&0xff;
 
    retval = copy_from_user(&send_buff[len], buff, count);
    msgs[0].addr = at24_dev->m_client->addr;  // address of slave device
    msgs[0].flags = 0;                        // 0:write, 1:read
    msgs[0].len = len+count;
    msgs[0].buf = send_buff;
    retval = i2c_transfer(at24_dev->m_client->adapter, msgs, ARRAY_SIZE(msgs));
    if(retval > 0){
        retval = 0;
        //printk(KERN_INFO "my_at24c_write successful\r\n");
    }
    
    
err:    
    mutex_unlock(&at24_dev->m_mutex);
    return retval;
}
static loff_t  my_at24c_llseek(struct file * flip, loff_t offset, int flag)
{
    loff_t retval = 0;
    struct my_at24_dev * at24_dev = (struct my_at24_dev *)flip->private_data;
    //printk(KERN_INFO "%s enter,offset=%d, flag=%d\r\n", __FUNCTION__, (int)offset, flag);
 
    mutex_lock(&at24_dev->m_mutex);
    if(flag == SEEK_SET){
        if(offset >= AT24CXX_SIZE){
            i2c_RxAddr = 0;
        }else{
            i2c_RxAddr = offset;
        }
        
    }
    mutex_unlock(&at24_dev->m_mutex);
    return retval;
}
static int my_at24c_open(struct inode * node, struct file * flip)
{
    mutex_init(&my_at24_devp->m_mutex);
    i2c_RxAddr = 0;
    flip->private_data = my_at24_devp;              //将at24结构存到flip中,便于后续读写使用
    return 0;
}
 
static long my_at24c_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
    return 0;
}
 
static int my_at24c_release (struct inode *inodep, struct file *flip) 
{ 
   flip->private_data = NULL; 
   i2c_RxAddr = 0;
   return 0; 
}
 
struct file_operations my_at24_fops = {
    .owner = THIS_MODULE,
    .llseek = my_at24c_llseek,
    .read =   my_at24c_read,
    .write=   my_at24c_write,
    .open =   my_at24c_open,
    .compat_ioctl = my_at24c_ioctl,
    .release =      my_at24c_release, 
};
 
/*will be called when the device and driver matched success*/
static int my_at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int retval = 0;
    
    my_at24_devp = kmalloc(sizeof(struct my_at24_dev), GFP_KERNEL);
    if(!my_at24_devp){
        retval = -ENOMEM;
        goto err;
    }
    memset (my_at24_devp, 0, sizeof (struct my_at24_dev));
    my_at24_devp->m_client = client;
 
    //register char device
    my_at24_devp->m_major = register_chrdev(0, MY_DRV_NAME, &my_at24_fops);   
    if(my_at24_devp->m_major < 0){
        printk (KERN_NOTICE "register_chrdev failed return %d",my_at24_devp->m_major); 
        goto err;
    }
 
    //create class
    my_at24_devp->m_eeprom_class = class_create(THIS_MODULE, MY_CLASS);
    if (IS_ERR(my_at24_devp->m_eeprom_class)) {
        printk(KERN_ERR "%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
        unregister_chrdev(my_at24_devp->m_major, MY_DRV_NAME);
        retval = -1;
        goto err;
     }
    
    device_create(my_at24_devp->m_eeprom_class, NULL, MKDEV(my_at24_devp->m_major,0), NULL, "%s%d",MY_DRV_NAME, 0);  // 在 /dev下创建 MY_DRV_NAME 设备文件(/dev/Myled0, 1, ...)
    
err:
    return retval;
}
 
 
static int my_at24_remove(struct i2c_client *client)
{
    int retval = 0;
    device_destroy(my_at24_devp->m_eeprom_class, MKDEV(my_at24_devp->m_major, 0));
    class_destroy(my_at24_devp->m_eeprom_class);
    unregister_chrdev(my_at24_devp->m_major, MY_DRV_NAME);
    kfree (my_at24_devp); 
    return retval;
}
 
 
 
static struct i2c_driver my_at24_driver = {
    .driver = {
        .name = "at24cxx",
        .owner = THIS_MODULE,
    },
    .probe = my_at24_probe,
    .remove = my_at24_remove,
    .id_table = my_at24_ids,
};
 
 
static int __init my_at24_init(void)
{
    return i2c_add_driver(&my_at24_driver);
}
module_init(my_at24_init);
 
static void __exit my_at24_exit(void)
{
    i2c_del_driver(&my_at24_driver);
}
module_exit(my_at24_exit);
 
MODULE_DESCRIPTION("Driver for I2C EEPROM");
MODULE_AUTHOR("Daniel.Dai");
MODULE_LICENSE("GPL");

 

posted @ 2023-04-05 23:15  流水灯  阅读(221)  评论(0编辑  收藏  举报