从零开始之驱动发开、linux驱动(五十七、linux4.19的IIC驱动的编写和使用1)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/89036654

第一点,配置内核支持i2c

Device Drivers  --->          
    I2C support  --->   
        <*>   I2C device interface 
        [*]   Autoselect pertinent helper modules
            I2C Hardware Bus support  --->
                <*> S3C2410 I2C Driver
        [*]   I2C Core debugging messages             
        [*]   I2C Algorithm debugging messages  
        [*]   I2C Bus debugging messages                                                                                                                                    

I2C device interface 为使用i2cdev这个字符接口。

首先我们在设备树文件中定义这个设备,

&i2c0 {
    status = "okay";

    audio-codec@1b {
        compatible = "wlf,wm8580";
        reg = <0x1b>;
    };  

    eeprom@50 {
        compatible = "atmel,24c08";
        reg = <0x50>;
    };  
};

可以看到在i2c总线下面会有这两个设备。

接下来我们在设备树文件的i2c总线下面去掉这两个设备。

&i2c0 {
    status = "okay";
};

第一节我们使用比较简单的方式在内核下创建一个i2c设备。

此时我们需要写一个i2c的驱动程序。

这里我们就给at24c02写一个简单的驱动程序。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/capability.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>

#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/cdev.h>



#define DEVICE_NAME "at24cxx"

static struct i2c_client * at24cxx_client;
static struct class *drv_class;

static struct cdev *chr_dev;
static struct device *device ;

static dev_t devnum;

/*
 * open函数,没什么时可做,暂时就绑定i2c设备的客户端到这个设备文件
 */
static int at24cxx_open(struct inode *inode, struct file *file)
{
    file->private_data = at24cxx_client;

    return 0;
}

static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t count,
        loff_t *offset)
{

    unsigned char data[50];
    unsigned char addr;
    int ret = 0;


    /*
     * 读数据包前要发送读的起始寄存器地址,之后才开始读操作
     */
    struct i2c_msg msg[] = {
        [0] = {
            .addr = at24cxx_client->addr,
            .flags = 0,
            .len = 1,
            .buf = &addr,
        },
        [1] = {
            .addr = at24cxx_client->addr,
            .flags = I2C_M_RD,
            .len = count,
            .buf = data,
        }
    };

   /*
     * 获取设备地址
     */

    if(copy_from_user(&addr, buf, 1)) {
        return -EFAULT;
    }

    if (i2c_transfer(at24cxx_client->adapter, msg, 2) != 2) {
        printk(KERN_ERR"i2c_transfer fail\n");
        ret = -EIO;
    }

    if (copy_to_user(buf ,data, count) )
        return  -EFAULT;


    return count;
}
}


static ssize_t at24cxx_write(struct file *file, const char __user *buf,
                    size_t count, loff_t *off)
{
    unsigned char data[50];

    /*
     * 写是数据包
     */
    struct i2c_msg msg = { 
            .addr = at24cxx_client->addr,
            .flags = 0,            
            .len = count,
            .buf = data,
    };  
    
    /*
     * 要写的数据拷贝到data,第一个字节是寄存器地址
     */
    if(copy_from_user(data, buf, count )) {
         return -EFAULT;
    }   

    if (i2c_transfer(at24cxx_client->adapter,& msg, 1) != 1) {
        printk(KERN_ERR"i2c_transfer fail\n");
        return  -EIO;
    }   

    return count;   
}



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




static int at24cxx_probe(struct i2c_client *client,
            const struct i2c_device_id *id)
{
    int ret;

    printk(KERN_INFO"at24cxx_probe \n");

    at24cxx_client = client;

    /*
     * 获取一个主设备号
     */
    ret = alloc_chrdev_region(&devnum, 0 , 1, "xxx" );
    if(ret < 0) {
        printk(KERN_ERR"alloc_chrdev_region fail\n");
        goto err_alloc_chrdev_region;
    }

    printk(KERN_INFO"major = %d\b",MAJOR(devnum) );

    /*
     * 获取一个struct cdev结构体,这个结构体是自动释放的
     */
    chr_dev = cdev_alloc();
    if(!chr_dev ) {
        goto err_cdev_alloc;
    }

    /*
     * 初始化cdev
     */
    cdev_init(chr_dev, &at24cxx_fops);
    chr_dev->owner = THIS_MODULE;

    /*
     * 把这个cdev加入字符设备表
     */
    ret = cdev_add(chr_dev, devnum, 1);
    if(ret) {
        printk(KERN_ERR"cdev_add fail");
        goto err_cdev_add;

    }
    /*
     * 创建一个类
     */
    drv_class = class_create(THIS_MODULE, "xxxx");
    if(!drv_class) {
        printk(KERN_ERR"class_create fail\n");
        goto err_class_create;
    }

    /*
     * 创建一个设备
     */
    device = device_create(drv_class,NULL, devnum, NULL, DEVICE_NAME);
    if( !device) {
        printk(KERN_ERR"device_create fail \n");
        goto err_device_create;
    }

    return 0;
err_device_create:
    class_destroy(drv_class);
err_class_create:  
err_cdev_add:
     cdev_del(chr_dev);
err_cdev_alloc:
    unregister_chrdev_region(devnum, 1);
err_alloc_chrdev_region:

    return ret;
}


static int at24cxx_remove(struct i2c_client *client)
{
    device_destroy(drv_class, devnum);
    class_destroy(drv_class);
    cdev_del(chr_dev);
    unregister_chrdev_region(devnum, 1);

    return 0;
}


/*
 * i2c驱动是根据下面这个i2c_device_id表中的名字来匹配,client的名字的
 */
static const struct i2c_device_id at24cxx_id_table[] = {
    { "at24cxx", 0 },
    {}
};



/* Addresses to scan */
static const unsigned short normal_i2c[] = { 0x50, 0x51, 0x52, 0x53, 0x54, I2C_CLIENT_END };


static struct i2c_driver at24cxx_driver = {
    .driver = {
        .name   = "at24cxx",        /* 这个随便起名字 */
    },
    .probe      = at24cxx_probe,
    .remove     = at24cxx_remove,
    .id_table   = at24cxx_id_table,
    .address_list = normal_i2c,
};


module_i2c_driver(at24cxx_driver);
MODULE_LICENSE("GPL");

测试程序,应用程序

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

void usge_print(char *file)
{
    /*  
     * 使用说明
     */
    printf("%s  </dev/at24cxx>  <r> <addr> <num>\n", file);
    printf("%s  </dev/at24cxx>  <w> <addr> <str>\n", file);
    
}



int main(int argc,char *argv[])
{
    int fd; 
    
    unsigned char buf[50], addr;
    int  len, i;    


    if(argc != 5) {
        usge_print(argv[0]);
        exit(1);
    }   


    fd = open(argv[1], O_RDWR);
    if(fd < 0) {
        perror("open fail\n");
        exit(1);
    }

    memset(buf, 0, 50);


    if(strcmp(argv[2], "r") == 0)   {
        /*
         * 提取要写的寄存器地址和要写的数据长度
         */
        addr = strtoul(argv[3], NULL, 0);    
        len  = strtoul(argv[4], NULL, 0);
        printf("read addr =  %x , len = %d\n", addr, len);
        buf[0] = addr;

        /*
         * 读数据
         */
        if( read(fd,buf, len ) < len) {
            perror("read fail\n");
            exit(1);
        }
        for(i = 0; i < len; i ++)
            printf("read data = %03d, %02x, %c\n", buf[i], buf[i],buf[i]);

    } else if(strcmp(argv[2], "w") == 0) {
        /*
         * 提取要写的寄存器地址和要写的数据长度
         */
        addr = strtoul(argv[3], NULL, 0);
        buf[0] = addr;
        len = strlen(argv[4]);

        /*
         * 第一个地址是要写的寄存器地址,所有数据要向后延长一个字节
         */
        for(i = 0; i < len; i++) {
            buf[i + 1] = argv[4][i];
        }
        if(write(fd, buf, len + 1) < (len + 1)) {
            perror("write fail\n");
            exit(1);
        }
    }
    else {
        usge_print(argv[0]);
        exit(1);
    }
    return 0;
}

测试前的基础知识

我们用的这块at24c02,有32个页,每页有8个字节。

对于读操作

手册中说的很明确,在确定读的地址之后,每次读取地址会增加1,超出地址范围后,会重新返回,传过来第一个读的地址位置开始,继续顺序读取。

对于写,则不一样。

每次写到本页的最后一个字节,如果继续写,则会返回到本页的第一个字节重新开始写。这个也是为了保证,写出现问题后,不会破坏过多的数据。

我们的at24c02,每页有8个字节,所以如果从每个页的起始位置开始,最多写8个字节。

如果要继续写,就要重新开始一次传输,从下一页的首地址开始。

我们的测试程序和驱动都写的比较,没有考虑写的时候的页翻转问题,正式的代码必须考虑着这种情况,并作出处理。

测试结果

用到的知识:


static int i2c_register_adapter(struct i2c_adapter *adap)
{
    ......

	dev_set_name(&adap->dev, "i2c-%d", adap->nr);
	adap->dev.bus = &i2c_bus_type;
	adap->dev.type = &i2c_adapter_type;        //type,也就是sys文件系统中的属性文件
	res = device_register(&adap->dev);
	if (res) {
		pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
		goto out_list;
	}

    .....
}
static struct attribute *i2c_adapter_attrs[] = {
	&dev_attr_name.attr,                /* 查看adaptor名字 */
	&dev_attr_new_device.attr,          /* 创建一个client */
	&dev_attr_delete_device.attr,       /* 删除一个client */
	NULL
};
ATTRIBUTE_GROUPS(i2c_adapter);

/* 
 * 属性的集合
 */
struct device_type i2c_adapter_type = {
	.groups		= i2c_adapter_groups,
	.release	= i2c_adapter_dev_release,
};

这里我们主要看创建和删除


/*
 * Let users instantiate I2C devices through sysfs. This can be used when
 * platform initialization code doesn't contain the proper data for
 * whatever reason. Also useful for drivers that do device detection and
 * detection fails, either because the device uses an unexpected address,
 * or this is a compatible device with different ID register values.
 *
 * Parameter checking may look overzealous, but we really don't want
 * the user to provide incorrect parameters.
 */
static ssize_t
i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,
		     const char *buf, size_t count)
{
	struct i2c_adapter *adap = to_i2c_adapter(dev);
	struct i2c_board_info info;
	struct i2c_client *client;
	char *blank, end;
	int res;

	memset(&info, 0, sizeof(struct i2c_board_info));

    /*
     * 提取完空格信息 
     */
	blank = strchr(buf, ' ');
	if (!blank) {
		dev_err(dev, "%s: Missing parameters\n", "new_device");
		return -EINVAL;
	}
	if (blank - buf > I2C_NAME_SIZE - 1) {
		dev_err(dev, "%s: Invalid device name\n", "new_device");
		return -EINVAL;
	}
	memcpy(info.type, buf, blank - buf);    /* 设置设的名字 */

	/* Parse remaining parameters, reject extra parameters */
	res = sscanf(++blank, "%hi%c", &info.addr, &end);        /* 提取设备的地址 */
	if (res < 1) {
		dev_err(dev, "%s: Can't parse I2C address\n", "new_device");
		return -EINVAL;
	}
	if (res > 1  && end != '\n') {
		dev_err(dev, "%s: Extra parameters\n", "new_device");
		return -EINVAL;
	}
    
    /*
     * 根据设备地址,增加一个属性给这个设备
     */
	if ((info.addr & I2C_ADDR_OFFSET_TEN_BIT) == I2C_ADDR_OFFSET_TEN_BIT) {
		info.addr &= ~I2C_ADDR_OFFSET_TEN_BIT;
		info.flags |= I2C_CLIENT_TEN;
	}

	if (info.addr & I2C_ADDR_OFFSET_SLAVE) {
		info.addr &= ~I2C_ADDR_OFFSET_SLAVE;
		info.flags |= I2C_CLIENT_SLAVE;
	}
    
    /*
     * 创建一个client,设备会在这个里面device_register中和驱动进行一次匹配
     */
	client = i2c_new_device(adap, &info);
	if (!client)
		return -EINVAL;

	/* Keep track of the added device */
	mutex_lock(&adap->userspace_clients_lock);

    /*
     * 因为这个client是从用户空间创建的,所以加入用户空间client链表
     * 保证从用户空间删除时,不是删掉内核空间创建的client
     */
	list_add_tail(&client->detected, &adap->userspace_clients);
	mutex_unlock(&adap->userspace_clients_lock);
	dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
		 info.type, info.addr);

	return count;
}
static DEVICE_ATTR(new_device, S_IWUSR, NULL, i2c_sysfs_new_device);

/*
 * And of course let the users delete the devices they instantiated, if
 * they got it wrong. This interface can only be used to delete devices
 * instantiated by i2c_sysfs_new_device above. This guarantees that we
 * don't delete devices to which some kernel code still has references.
 *
 * Parameter checking may look overzealous, but we really don't want
 * the user to delete the wrong device.
 */
static ssize_t
i2c_sysfs_delete_device(struct device *dev, struct device_attribute *attr,
			const char *buf, size_t count)
{
	struct i2c_adapter *adap = to_i2c_adapter(dev);
	struct i2c_client *client, *next;
	unsigned short addr;
	char end;
	int res;

	/* Parse parameters, reject extra parameters */
    /* 提取设备地址 */
	res = sscanf(buf, "%hi%c", &addr, &end);
	if (res < 1) {
		dev_err(dev, "%s: Can't parse I2C address\n", "delete_device");
		return -EINVAL;
	}
	if (res > 1  && end != '\n') {
		dev_err(dev, "%s: Extra parameters\n", "delete_device");
		return -EINVAL;
	}

	/* Make sure the device was added through sysfs */
	res = -ENOENT;
	mutex_lock_nested(&adap->userspace_clients_lock,
			  i2c_adapter_depth(adap));
    /* 根据设备地址,查看用户空间链表是不是有这个地址,有的话则从这个链表删除,且注销这个设备 */
	list_for_each_entry_safe(client, next, &adap->userspace_clients,
				 detected) {
		if (i2c_encode_flags_to_addr(client) == addr) {
			dev_info(dev, "%s: Deleting device %s at 0x%02hx\n",
				 "delete_device", client->name, client->addr);

			list_del(&client->detected);
			i2c_unregister_device(client);
			res = count;
			break;
		}
	}
	mutex_unlock(&adap->userspace_clients_lock);

	if (res < 0)
		dev_err(dev, "%s: Can't find device in list\n",
			"delete_device");
	return res;
}

设和驱动的匹配规则match

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;


	/* Attempt an OF style match,设备树中的优先级是最高的 */
	if (i2c_of_match_device(drv->of_match_table, client))
		return 1;

	/* Then ACPI style match 高级配置电源管理的也比较高 */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);

	/* Finally an I2C match 普通情况下的匹配,使用的是id_table */
	if (i2c_match_id(driver->id_table, client))
		return 1;

	return 0;
}

/*
 * 注意这里使用的是id_table中的名字和设备名字匹配,而不是driver中的名字
 */

const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	if (!(id && client))
		return NULL;

	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)
			return id;
		id++;
	}
	return NULL;
}



本文所有源码都已经编译测试通过,代码仓库位置如下

https://github.com/To-run-away/linux-i2c-driver/tree/master/i2c_user_space_creare_client

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/89036654