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

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

作者:oshan2012 时间: 2020-08-04 03:37:43
oshan2012 2020-08-04 03:37:43

https://blog.csdn.net/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

  2. #include

  3. #include

  4. #include

  5. #include

  6. #include

  7. #include

  8. #include

  9. #include

  10.  
  11. #include

  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的时钟线和数据线进行上拉处理,我在这一点儿卡了一天时间,切记切记!

勿删,copyright占位
分享文章到微博
分享文章到朋友圈

上一篇:VR全景的后期制作

下一篇:Rank() over (partition by……order by……)与dense_rank() over (partition by……order by……)的区别

您可能感兴趣

  • Linux 内核配置项详解 myimx6

    CONFIG_LOCALVERSION="-myimx6" #本地版本 CONFIG_KERNEL_LZO=y #内核混合算法 CONFIG_DEFAULT_HOSTNAME="myzr" #默认主机名称 CONFIG_SYSVIPC=y #进程间通信 CONFIG_NO_HZ=y #时钟相关 CONFIG_HIGH_RES_TIMERS=y #内核敏感计时 CONFIG_IKCONFIG...

  • stm32 学习 转

    如果为了学习单片机而去学习单片机,这样的思路是不对的。 有人会问,我该如何系统地入门学习stm32? 其实这本身就是一个错误的问题。假如你会使用8051 , 会写C语言,那么STM32本身并不需要刻意的学习。 你要考虑的是, 我可以用STM32实现什么? 为什么使用STM32而不是8051? 是因为51的频率太低,无法满足计算需求?是51的管脚太少,无法满足众多外设的IO? 是51的功耗太大...

  • V4L2官方开发文档中文版

    下面的文档摘自高通源码的kernel\Documentation\zh_CN\video4linux\目录下的v4l2-framework.txt文档,如有侵权请相告,会及时处理。 Chinese translated version of Documentation/video4linux/v4l2-framework.txt If you have any comment or upda...

  • 内存器件之介绍篇

    内存器件之介绍篇1 & 4比较值得看. 内存器件之介绍篇1 注:了解一下SRAM, DRAM,DDR的概念特点即可. https://blog.csdn.net/yangcuncunzhang/article/details/6127788 提到内存,相信大家都不陌生,几乎所有的计算机系统中都有它的身影,按照内存的工作原理划分,可将内存分为RAM和ROM两大类。 RAM(Random Acc...

  • 【华为中央硬件部】最新社会招聘公告!

    你一定要点蓝色字体关注我哦 华为招聘 中央硬件部 引领AI潮流,创造“融合计算,智慧感知”新境界! 中央硬件部隶属于2012实验室中央硬件工程院,中央硬件部从算法、软件、硬件、硬件安全等领域构建全栈AI解决方案,打造面向大规模AI集群系统,智能安防,自动驾驶、智能云服务、智慧终端、5G和智慧物联网全场景AI解决方案,引领AI研究和应用潮流。 在这里,你或者钻研业界最前沿、先进的AI技术,构建...

  • MPU6050 - 陀螺仪 - 技术总结

    博主福利:100G+电子设计学习资源包! http://mp.weixin.qq.com/mp/homepage?__biz=MzU3OTczMzk5Mg==&hid=7&sn=ad5d5d0f15df84f4a92ebf72f88d4ee8&scene=18#wechat_redirect --------------------------------------------------...

  • 单片机EEPROM的理解

    EEPROM是“Eleally Eras-able Programmable Read-only”(电可擦写可编程只读)的缩写,EEPROM在正常情况下和EPROM-样,可以在掉电的情况下保存数据,所不同的是,它在特定引脚上施加特定或使用特定的总线擦写命令就可以在在线的情况下方便地完成数据的擦除和写入,这个特点使EEPROM被用于广阔的消费领域,如汽车、电信、医疗、工业和个人计算机相关的市场...

  • SPI、I2C和UART

    一、SPI SPI(Serial Peripheral Interface,串行外设接口)是Motorola公司提出的一种同步串行数据传输标准,在很多器件中被广泛应用。 接口 SPI接口经常被称为4线串行总线,以主/从方式工作,数据传输过程由主机初始化。如图1所示,其使用的4条信号线分别为: 1) SCLK:串行时钟,用来同步数据传输,由主机输出; 2) MOSI:主机输出从机输入数据线,通...

华为云40多款云服务产品0元试用活动

免费套餐,马上领取!
CSDN

CSDN

中国开发者社区CSDN (Chinese Software Developer Network) 创立于1999年,致力为中国开发者提供知识传播、在线学习、职业发展等全生命周期服务。