在nuc972上实现I2C接口数字电位器isl95311的驱动

本文详细介绍了ISL95311数字电位器的I2C驱动开发过程,包括设备信息的添加、驱动算法的编写以及与硬件电路的连接注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/b7376811/article/details/100023485

 当前的这个项目需要使用一个数字电位器,型号选的是isl95311,控制接口是I2C,折腾了两天,终于实现了这个电位器的驱动,今天记录一下这个过程,以备以后查阅。

    1、首先在nuc972的设备文件中增加isl95311相关的设备信息,在内核中的路径为/arch/arm/mach-nuc970/dev.c,如下所示:

 
  1. static struct i2c_board_info __initdata nuc970_i2c_clients2[] =

  2. {

  3. {

  4. I2C_BOARD_INFO("tsc2007", 0x48),

  5. .platform_data = &tsc2007_info,

  6. /* irq number is run-time assigned */

  7. },

  8. #ifdef CONFIG_SENSOR_OV7725

  9. {I2C_BOARD_INFO("ov7725", 0x21),},

  10. #endif

  11. #ifdef CONFIG_SENSOR_OV5640

  12. {I2C_BOARD_INFO("ov5640", 0x3c),},

  13. #endif

  14. #ifdef CONFIG_SENSOR_NT99141

  15. {I2C_BOARD_INFO("nt99141", 0x2a),},

  16. #endif

  17. #ifdef CONFIG_SENSOR_NT99050

  18. {I2C_BOARD_INFO("nt99050", 0x21),},

  19. #endif

  20. {I2C_BOARD_INFO("lm75a", 0x4e),},

  21.  
  22. {I2C_BOARD_INFO("ds1307", 0x68),},

  23.  
  24. {I2C_BOARD_INFO("isl95311", 0x28),},

  25. };

  26. static struct i2c_gpio_platform_data i2c_gpio_adapter_data = {

  27. .sda_pin = NUC970_PB1,

  28. .scl_pin = NUC970_PB0,

  29. .udelay = 1,

  30. .timeout = 100,

  31. .sda_is_open_drain = 0, //not support open drain mode

  32. .scl_is_open_drain = 0, //not support open drain mode

  33. };

  34.  
  35. static struct platform_device i2c_gpio = {

  36. .name = "i2c-gpio",

  37. .id = 2,

  38. .dev = {

  39. .platform_data = &i2c_gpio_adapter_data,

  40. },

  41. };

在结构体数组nuc972_i2c_clients2中添加一个名字为Isl95311,从设备地址为0x28,这个地址需要配合硬件上的A0与A1的设置,我这里设置的都是低电平,根据isl95311的技术手册,可以计算得到0x28的设备从地址。数组中其他的成员信息,如果不需要,可以屏蔽掉。

结构体i2c_gpio_adapter_data里面保存的是使用gpio模拟I2C时序的方式的I2C适配器需要的数据,因为在当前的内核配置中,使用的是gpio模拟的I2C总线,所以这里也用模拟的I2C,这里面包含了几个重要的信息:

sda_pin:数据线对应的GPIO口;

scl_pin:时钟线对应的GPIO口;

udelay:决定I2C频率的延时,频率=500khz/udelay;

timeout:请求超时时长;

sda_is_open_drain:sda对应的gpio是否支持开漏模式;

scl_is_open_drain:scl对应的gpio是否支持开漏模式;

注意:这两个非常重要,Isl95311的数据线和时钟线都是开漏模式,我刚开始没太弄明白,而且我的板子上也没有设计I2C的上拉电阻,就想当然的把这两项都设置成了1,虽然电平正常,但是就是不通,最后发现这两个选项必须配置为0,外置上拉电阻才行,目前没有搞明白是为什么,可能是这个选项的意思是nuc972的gpio口不支持外部从设备的开漏模式。

结构体i2c_gpio是当前gpio模拟的I2C在系统上注册成平台设备所需要的数据,因为片子的I2C设备不管是硬件I2C还是gpio模拟的I2C在系统上都是注册成平台设备的。

2、第二步是将上面的数据注册到系统中,其实很简单,就一句话:

i2c_register_board_info(2, nuc970_i2c_clients2, ARRAY_SIZE(nuc970_i2c_clients2));

该函数的第一个参数是I2C设备在系统中的编号,我这里用的是I2C2,第二个参数是注册的数据,第三个参数是数据的长度,到这里,isl95311驱动的设备信息部分就完成了。

3、第三步就是编写isl95311驱动的算法部分,直接将所有的代码都贴出来:

 
  1. #include <linux/module.h>

  2. #include <linux/init.h>

  3. #include <linux/slab.h>

  4. #include <linux/i2c.h>

  5. #include <linux/mutex.h>

  6. #include <linux/delay.h>

  7. #include <linux/miscdevice.h>

  8. #include <linux/fs.h>

  9. #include <linux/i2c-algo-bit.h>

  10.  
  11. #include <asm/uaccess.h>

  12.  
  13. #define ISL95311_DRV_NAME "isl95311"

  14. #define DRIVER_VERSION "1.0"

  15.  
  16.  
  17. #define ISL95311_NUM_CACHABLE_REGS 4

  18.  
  19.  
  20. struct isl95311_data {

  21. struct i2c_client *client;

  22. struct mutex lock;

  23. u8 reg_cache[ISL95311_NUM_CACHABLE_REGS];

  24. struct miscdevice i2c_misc;

  25. };

  26.  
  27. struct isl95311_data *isl95311_data_dev;

  28.  
  29.  
  30. /*

  31. * register access helpers

  32. */

  33.  
  34. static int __isl95311_i2c_recv(struct i2c_client *client, char *buf, size_t count)

  35. {

  36.  
  37. return 0;

  38. }

  39.  
  40. static int __isl95311_i2c_send(struct i2c_client *client, char *buf, size_t count)

  41. {

  42. int ret = 0;

  43. //i2c_adapter描述控制器,包含编号、算法等描述

  44. struct i2c_adapter *adapter = client->adapter;

  45. struct i2c_msg msg;

  46.  
  47. msg.addr = client->addr;

  48. msg.buf = (char *)buf;

  49. msg.flags = 0;//0是写,1是读

  50. msg.len = count;

  51.  
  52. ret = i2c_transfer(adapter, &msg, 1);

  53.  
  54. return ret==1?count:ret;

  55. }

  56.  
  57. /*

  58. * I2C layer

  59. */

  60. static int isl95311_open(struct inode *inode, struct file *filp)

  61. {

  62. //printk("-------%s--------\n", __FUNCTION__);

  63.  
  64. return 0;

  65. }

  66. static int isl95311_close(struct inode *inode, struct file *filp)

  67. {

  68. //printk("------%s------\n", __FUNCTION__);

  69.  
  70. return 0;

  71. }

  72. static int isl95311_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)

  73. {

  74. //printk("------%s------\n", __FUNCTION__);

  75.  
  76. return 0;

  77. }

  78. static int isl95311_write(struct file *filp, char __user *buf, size_t count, loff_t *fpos)

  79. {

  80. //printk("------%s------\n", __FUNCTION__);

  81.  
  82. ssize_t ret;

  83. //动态分配一个空间

  84. char *temp = kzalloc(count, GFP_KERNEL);

  85. //从用户空间读取得到的数据

  86. ret = copy_from_user(temp, buf, count);

  87. if (ret > 0) {

  88. printk("copy_from_user error!\n");

  89. ret = -EFAULT;

  90. goto err_free;

  91. }

  92.  
  93. //将数据写入到硬件设备

  94. ret = __isl95311_i2c_send(isl95311_data_dev->client, temp, count);

  95. if (ret < 0) {

  96. printk("isl95311_i2c_send error\n");

  97. goto err_free;

  98. }

  99.  
  100. kfree(temp);

  101. return 0;

  102.  
  103. err_free:

  104. kfree(temp);

  105. return ret;

  106. }

  107.  
  108. const struct file_operations isl95311_i2c_fops = {

  109. .open = isl95311_open,

  110. .read = isl95311_read,

  111. .write = isl95311_write,

  112. .release = isl95311_close,

  113. };

  114. static int isl95311_probe(struct i2c_client *client,

  115. const struct i2c_device_id *id)

  116. {

  117. struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);

  118. int err = 0;

  119.  
  120. if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))

  121. return -EIO;

  122.  
  123. isl95311_data_dev = kzalloc(sizeof(struct isl95311_data), GFP_KERNEL);

  124. if (!isl95311_data_dev)

  125. return -ENOMEM;

  126.  
  127. isl95311_data_dev->client = client;

  128. i2c_set_clientdata(client, isl95311_data_dev);

  129. mutex_init(&isl95311_data_dev->lock);

  130.  
  131. isl95311_data_dev->i2c_misc.fops = &isl95311_i2c_fops;

  132. isl95311_data_dev->i2c_misc.minor = 199;

  133. isl95311_data_dev->i2c_misc.name = "isl95311";

  134. misc_register(&isl95311_data_dev->i2c_misc);

  135.  
  136. dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);

  137. return 0;

  138.  
  139. }

  140.  
  141. static int isl95311_remove(struct i2c_client *client)

  142. {

  143. misc_deregister(&isl95311_data_dev->i2c_misc);

  144. kfree(i2c_get_clientdata(client));

  145. return 0;

  146. }

  147.  
  148. static const struct i2c_device_id isl95311_id[] = {

  149. { "isl95311", 0 },

  150. {}

  151. };

  152. MODULE_DEVICE_TABLE(i2c, isl95311_id);

  153.  
  154. static struct i2c_driver isl95311_driver = {

  155. .driver = {

  156. .name = ISL95311_DRV_NAME,

  157. .owner = THIS_MODULE,

  158. },

  159. .probe = isl95311_probe,

  160. .remove = isl95311_remove,

  161. .id_table = isl95311_id,

  162. };

  163.  
  164. module_i2c_driver(isl95311_driver);

  165.  
  166. MODULE_AUTHOR("jakey <632233021@qq.com>");

  167. MODULE_DESCRIPTION("ISL95311 driver");

  168. MODULE_LICENSE("GPL v2");

  169. MODULE_VERSION(DRIVER_VERSION);

我这里没有用到isl95311的读,只用到了向isl95311写数据,所以只实现了写数据的具体操作方法,其他都只是一个空壳。这个驱动相对比较简单,这里简单的记录说明一下:

驱动一般是从下往上看,最后的几行都是固定格式,其中isl95311id这个结构体数组中的第一个元素的第一个字符串是该驱动的名字,这个名字要和设备文件里面的名字要完全一致,要不然匹配不上,驱动会加载不成功。先看一下这个probe函数,很简单,就是申请一个自定义的和isl95311相关的数据结构空间,并将其中的某些数据填充,我这里是将isl95311注册成了一个杂散类设备,这样可以产生一个字符设备驱动的操作节点,杂散类设备的名字可以随便定,不需要一定和设备信息里面的名字一致。remove函数正好和probe函数相反,注意,操作顺序要相反,比如在probe函数中先申请了空间,后注册设备,在remove函数中要先反注册设备,再释放空间。

最后看一次实现isl95311这个数字电位器写操作的方法,即isl95311_write函数,首先是需要将用户空间的数据拷贝到内核空间,这是内核空间与用户空间交互数据的唯一接口,其次是将这些数据封装成I2C规定的数据包,也就是结构体i2c_msg的这种格式,在这里分析一下这个数据格式,这个结构体主要包括了四个字段:

addr:I2C从机地址,7位或者是10位,在这里,isl95311是7位地址

buf:读或者写数据存放的位置

flags:读或写标志位,0是写,1是读

len:需要读写的数据长度

还有就是调用了一个非常重要的函数i2c_transfer,这个函数是I2C核心层的数据传输函数之一,具体的不做分析,可以翻看内核源码,这里只说明使用方法,该函数的第一个参数是I2C适配器,简单的说就是代表I2C1还是I2C2的相关内容数据,第二个参数是存放要发送的I2C消息缓存的地址,第三个参数是该次发送数据的个数。现在具体的说一下这个函数的工作过程:

(1)、I2C适配器向总线发送设备从机地址,等待ACK;

(2)、收到ACK后,向总线发送从设备片内寄存器地址,等待ACK;

(3)、收到ACK后,向总线发送将要写入片内寄存器的数据,并等待ACK;

(4)、收到ACK后,本次操作就完成了;

以上的每次写操作,如果失败了,默认都是重复3次。

4、好了,到这里驱动部分就完成了,将上面的代码编译成模块,安装到系统,会在/dev的路径下,产生一个名字为isl95311的设备节点,在应用层就可以使用通用的open函数和write函数进行打开和写操作了!

5、有一点需要特别强调一下,硬件电路上,nuc972的gpio不支持开漏模式,所以外部必须对isl95311的时钟线和数据线进行上拉处理,我在这一点儿卡了一天时间,切记切记!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值