文档章节

lwn拾遗:一个sn3218 led driver引发的驱动范例

 鼎之轻重-似可问焉
发布于 2016/02/17 20:07
字数 2201
阅读 215
收藏 0

前言:

注意到lwn上,device drviers中发表了一个led chev 的patch:“leds: add SN3218 LED driver”(http://lwn.net/Articles/673991/)

根据这个我们可以学习一下该patch包含的led chev思想。


patch-0

Si-En SN3218是一款18路呼吸灯驱动芯片,每路单独256级细腻亮度可控,采用i2c接口。sn3218并不支持read操作,所以寄存器的值都会被cached。

其内部寄存器如下:

具体每个寄存器含义可以查看相关文档(sn3218 datasheet.pdf)。

 http://www.si-en.com/uploadpdf/s2011517171720.pdf

patch-1

/Documentation/devicetree/bindings/vendor-prefixes.txt

中添加vid的描述

+si-en	Si-En Technology Ltd.



patch-2

添加dts对sn3218的描述,主要是修改/Documentation/devicetree/bindings/leds/leds-sn3218.txt的文件。该bindings文件描述了dts该如何支持sn3218.

+* Si-En Technology - SN3218 18-Channel LED Driver
+
+Required properties:
+- compatible :
+	"si-en,sn3218"
+- address-cells : must be 1
+- size-cells : must be 0
+- reg : I2C slave address, typically 0x54
+
+There must be at least 1 LED which is represented as a sub-node
+of the sn3218 device.
+
+LED sub-node properties:
+- label : (optional) see Documentation/devicetree/bindings/leds/common.txt
+- reg : number of LED line (could be from 0 to 17)
+- linux,default-trigger : (optional)
+   see Documentation/devicetree/bindings/leds/common.txt
+
+Example:
+
+sn3218: led-controller@54 {           //    i2c的slave偏移地址是0x54,典型值
+	compatible = "si-en,sn3218";
+	#address-cells = <1>;          //   它的address是1个字节,size为0
+	#size-cells = <0>;
+	reg = <0x54>;
+
+	led@0 {
+		label = "led1";        //   label描述名字
+		reg = <0x0>;           //   reg描述了该led的第几个
+	};
+
+	led@1 {
+		label = "led2";
+		reg = <0x1>;
+	};
+
+	led@2 {
+		label = "led3";
+		reg = <0x2>;
+	};
+};
+



patch-3 实际led chev的内容,kconfig,makefile以及code

1,kconfig中添加描述

/drivers/leds/Kconfig
+config LEDS_SN3218
+	tristate "LED support for Si-En SN3218 I2C chip"
+	depends on LEDS_CLASS && I2C
+	depends on OF
+	select REGMAP_I2C
+	help
+	  This option enables support for the Si-EN SN3218 LED driver
+	  connected through I2C. Say Y to enable support for the SN3218 LED.
+
+	  This driver can also be built as a module. If so the module
+	  will be called leds-sn3218.
+

2,makefile中添加kconfig的选项

drivers/leds/Makefile b/drivers/leds/Makefile
+obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o

3,添加c code

/drivers/leds/leds-sn3218.c

3-1,一些基本的头文件

当我们要写i2c的led时需要加上的头文件

+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>

3-2,描述sn3218的寄存器分布的宏

+#define SN3218_MODE		0x00   //  设置shutdown模式
+#define SN3218_PWM_1		0x01   //  1~18 leds的pwm控制
+#define SN3218_PWM_2		0x02
+#define SN3218_PWM_3		0x03
+#define SN3218_PWM_4		0x04
+#define SN3218_PWM_5		0x05
+#define SN3218_PWM_6		0x06
+#define SN3218_PWM_7		0x07
+#define SN3218_PWM_8		0x08
+#define SN3218_PWM_9		0x09
+#define SN3218_PWM_10		0x0a
+#define SN3218_PWM_11		0x0b
+#define SN3218_PWM_12		0x0c
+#define SN3218_PWM_13		0x0d
+#define SN3218_PWM_14		0x0e
+#define SN3218_PWM_15		0x0f
+#define SN3218_PWM_16		0x10
+#define SN3218_PWM_17		0x11
+#define SN3218_PWM_18		0x12
+#define SN3218_LED_1_6		0x13    //   led1~6的使能控制寄存器
+#define SN3218_LED_7_12		0x14
+#define SN3218_LED_13_18	0x15
+#define SN3218_UPDATE		0x16	/* applies to reg 0x01 .. 0x15 */  //  更新响应pwm寄存器和控制寄存器的值
+#define SN3218_RESET		0x17   //   复位寄存器,让所有寄存器的值恢复到default
+
+#define SN3218_LED_MASK		0x3f
+#define SN3218_LED_ON		0x01
+#define SN3218_LED_OFF		0x00

3-3,关键数据结构

+struct sn3218_led;
+
+/**
+ * struct sn3218 -
+ * @client - Pointer to the I2C client
+ * @leds - Pointer to the individual LEDs
+ * @num_leds - Actual number of LEDs
+**/
+struct sn3218 {
+	struct i2c_client *client;    //  i2c client结构体指针
+	struct regmap *regmap;        //  采用regmap协议
+	struct sn3218_led *leds;
+	int num_leds;                 //  led个数
+};
+
+/**
+ * struct sn3218_led -
+ * @chip - Pointer to the container
+ * @led_cdev - led class device pointer
+ * @led_num - LED index ( 0 .. 17 )
+**/
+struct sn3218_led {
+	struct sn3218 *chip;
+	struct led_classdev led_cdev;      //   led chev结构体
+	int led_num;
+};

3-4,驱动的思路

首先要有匹配入口点

+static const struct of_device_id of_sn3218_match[] = {
+	{ .compatible = "si-en,sn3218", },                        //   和dts的node的compatible属性匹配字符串
+	{},
+};
+MODULE_DEVICE_TABLE(of, of_sn3218_match);
+
+static struct i2c_driver sn3218_driver = {
+	.driver = {
+		.name	= "leds-sn3218",
+		.of_match_table = of_match_ptr(of_sn3218_match),   //  和dts中匹配时,调用probe函数
+	},
+	.probe	= sn3218_probe,
+	.remove	= sn3218_remove,
+	.id_table = sn3218_id,
+};
+
+module_i2c_driver(sn3218_driver);                                  //  属于i2c驱动的一部分,i2cdevice
+
+MODULE_DESCRIPTION("Si-En SN3218 LED Driver");
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren@xxxxxxxx>");
+MODULE_LICENSE("GPL v2");

of_match_table的机制是在dts引入之后,之前的是根据name来匹配的。其实原理是一样的,在MACHINE_INIT中会根据dts中node来建立device。这里的device只有几个很简单的结构,包括名字等等。等kernel初始化快结束前,调用__initcall,会对module_init修饰的所有函数调用到。在此期间,bus中会匹配,调用of_match用platform_driver结构体的of_match_table和device的node的compatile字段匹配。如果匹配了就调用platform_driver的probe函数。具体的流程可以以后系统列一下。

假如此时匹配,调用probe函数

+static int sn3218_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct sn3218 *sn3218;
+	struct sn3218_led *leds;
+	struct device *dev = &client->dev;   //   几个必要的函数指针
+	int i, ret;
+
+	sn3218 = devm_kzalloc(dev, sizeof(*sn3218), GFP_KERNEL);   //   分配空间
+	if (!sn3218)
+		return -ENOMEM;
+
+	ret = sn3218_init(client, sn3218);                        //  初始化
+	if (ret)
+		return ret;
+
+	i2c_set_clientdata(client, sn3218);
+	leds = sn3218->leds;
+
+	/*
+	 * Since the chip is write-only we need to reset him into
+	 * a defined state (all LEDs off).
+	 */
+	ret = regmap_write(sn3218->regmap, SN3218_RESET, 0xff);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < sn3218->num_leds; i++) {
+		ret = devm_led_classdev_register(dev, &leds[i].led_cdev);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Set normal mode */
+	return regmap_write(sn3218->regmap, SN3218_MODE, 0x01);
+}
+

1,需要devm_kzalloc给sn3218结构体分配空间,这里的kevm_kzallloc和传统的kzalloc的区别是,以前用kzalloc,驱动工程师必须要记得kfree,然而一单驱动结构复杂,很容易就会忘记kfree,造成很繁琐的bug。现在用kevm_kzalloc后,就像c++的类一样,当这个device给release时,它会自动把其用kevm_kzalloc分配的空间给release掉。

2,调用sn3218_init来给sn3218结构体初始化。

3,调用i2c_set_clientdata,把sn3218结构体和i2c挂钩。

4,调用regmap_write向SN3218_RESET描述的reset寄存器写入0xff,含义是把所有寄存器都复位。因为sn3218是只写寄存器,所以最开始不能读,需要把他们设定成一个已知状态。

5,对于18个led,轮询调用devm_led_classdev_register给每个led描述的led chevshe

6,调用regmap_write向SN3218_MODE描述的normal寄存器写入1,让其进入normal模式。

normal寄存器的含义如下:

我们知道probe函数中会调用sn3218_init,该函数会对sn3218寄存器进行初始配置。

+static int sn3218_init(struct i2c_client *client, struct sn3218 *sn3218)
+{
+	struct device_node *np = client->dev.of_node, *child;
+	struct sn3218_led *leds;
+	int ret, count;
+
+	count = of_get_child_count(np);
+	if (!count)
+		return -ENODEV;
+
+	if (count > NUM_LEDS) {
+		dev_err(&client->dev, "Invalid LED count %d\n", count);
+		return -EINVAL;
+	}
+
+	leds = devm_kzalloc(&client->dev, count * sizeof(*leds), GFP_KERNEL);
+	if (!leds)
+		return -ENOMEM;
+
+	sn3218->leds = leds;
+	sn3218->num_leds = count;
+	sn3218->client = client;
+
+	sn3218->regmap = devm_regmap_init_i2c(client, &sn3218_regmap_config);
+	if (IS_ERR(sn3218->regmap)) {
+		ret = PTR_ERR(sn3218->regmap);
+		dev_err(&client->dev, "Failed to allocate register map: %d\n",
+			ret);
+		return ret;
+	}
+
+	for_each_child_of_node(np, child) {
+		u32 reg;
+
+		ret = of_property_read_u32(child, "reg", &reg);
+		if (ret)
+			goto fail;
+
+		if (reg >= count) {
+			dev_err(&client->dev, "Invalid LED (%u >= %d)\n", reg,
+				count);
+			ret = -EINVAL;
+			goto fail;
+		}
+
+		sn3218_led_init(sn3218, child, reg);
+	}
+
+	return 0;
+
+fail:
+	of_node_put(np);
+	return ret;
+}

1,调用of_get_child_count计算出dtb中的led的node个数。

2,调用devm_kzalloc分配count个sn3218_led 结构体。每个led对应一个该结构体。然后完善sn3218结构体。

3,调用devm_regmap_init_i2c ,传入sn3218_regmap_config 描述的regmap配置。得到一个描述i2c的regmap。

4,对每一个led都调用sn3218_led_init初始化sn3218结构体。

其中的regmap config描述了sn3218的regmap的特点。

+static const struct regmap_config sn3218_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = SN3218_RESET,
+	.reg_defaults = sn3218_reg_defs,
+	.num_reg_defaults = ARRAY_SIZE(sn3218_reg_defs),
+	.cache_type = REGCACHE_RBTREE,     //   红黑树作为cache的方法,特点查找特别快。
+};


sn3218_init 中调用的sn3218_led_init对每一个led chev设备进行初始化。

void sn3218_led_init(struct sn3218 *sn3218, struct device_node *node, u32 reg)
+{
+	struct sn3218_led *leds = sn3218->leds;
+	struct led_classdev *cdev = &leds[reg].led_cdev;
+
+	leds[reg].led_num = reg;
+	leds[reg].chip = sn3218;
+
+	if (of_property_read_string(node, "label", &cdev->name))
+		cdev->name = node->name;
+
+	of_property_read_string(node, "linux,default-trigger",
+				&cdev->default_trigger);
+
+	cdev->brightness_set_blocking = sn3218_led_set;
+	cdev->max_brightness = LED_FULL;

这里的reg描述的是leds数组的index。leds描述的是sn3218_led结构体,之前分配了count个该结构体。这里用reg来找到第reg个led。接着配置leds[reg]的各项参数。

cdev->brightness_set_blocking = sn3218_led_set;

该语句设定了,当控制该reg亮度时的函数点。

接下来分析空reg亮度的函数。

+static int sn3218_led_set(struct led_classdev *led_cdev,
+			  enum led_brightness brightness)
+{
+	struct sn3218_led *led =
+			container_of(led_cdev, struct sn3218_led, led_cdev);
+	struct regmap *regmap = led->chip->regmap;
+	u8 bank = led->led_num / 6;
+	u8 mask = 0x1 << (led->led_num % 6);
+	u8 val;
+	int ret;
+
+	if (brightness == LED_OFF)
+		val = 0;
+	else
+		val = mask;
+
+	ret = regmap_update_bits(regmap, SN3218_LED_1_6 + bank, mask, val);
+	if (ret < 0)
+		return ret;
+
+	if (brightness > LED_OFF) {
+		ret = regmap_write(regmap, SN3218_PWM_1 + led->led_num,
+				   brightness);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = regmap_write(regmap, SN3218_UPDATE, 0xff);
+
+	return ret;
+}
+

1,拿到必要的数据结构。regmap等。

+	u8 bank = led->led_num / 6;
+	u8 mask = 0x1 << (led->led_num % 6);

这里要/6和%6在于0x13~0x15描述的led control寄存器,每个寄存器控制6个led。

2,亮度控制有两方面,一个是亮灭,一个是pwm的可编程亮度。

首先,先更新一个亮灭:

ret = regmap_update_bits(regmap, SN3218_LED_1_6 + bank, mask, val);

如果有pwm控制的话,更新pwm亮度:

if (brightness > LED_OFF) {
+		ret = regmap_write(regmap, SN3218_PWM_1 + led->led_num,
+				   brightness);
+		if (ret < 0)
+			return ret;
+	}

最后,对所有寄存器更新一下。

ret = regmap_write(regmap, SN3218_UPDATE, 0xff);

至此,sn3218 led chev驱动基本思路分析完了。里边会用到dts、regmap和i2c的一些api函数,下一节分析一下这些api函数。核心就是如何利用regmap来最后实现i2c的对sn3218的写操作。当然read操作是cache的。

© 著作权归作者所有

粉丝 0
博文 4
码字总数 5943
作品 0
天津
私信 提问
lwn拾遗:[sn3218 led drivers]-api解释-1

前言 针对sn3218涉及到的dts、i2c、regmap等api函数做一些解释。 1,dts ofgetchildcount 拿到子节点的个数,方法是从根节点""轮训每个node,记录node个数。 foreachchildof_node(np, child)...

鼎之轻重-似可问焉
2016/02/18
44
0
STM32 USB 之从0开始移植笔记

-----------------------------------动机----------------------------------- 写在前面的话:最近逛淘宝无意间发现RC522居然只要10元左右就可以包邮买到,真是太便宜了,就忍不住买了个回来...

imxiangzi
2018/04/22
0
0
使用JSTL的SQL标签库碰到的问题

Date:2012-10-23 Environment:Eclipse 4.2,Tomcat 7,PostgreSQL9.2 今天使用sql:setDataSource碰到的问题,在这里总结一下: 1.错误描述:invalid driver class name: "java.lang.ClassNotFoun......

陈好
2012/10/24
0
1
Calibre v2.71.0 发布,电子书管理软件

calibre v2.71.0 发布了。更新内容如下: Bug 修复 Get Books: Update ebookpoint and woblink store plugins for website changes Edit Book: When un-marking text auto-change the 'sear......

达尔文
2016/11/01
549
3
lwn拾遗:[sn3218 led driver]-api解释-2

regcachehwinit有些i2c设备的寄存器是volatile的,是不能cache的.1,先计算不是volatile类型的寄存器个数轮询0到map->numregdefaultsraw,并用regmapvolatile判断当前的寄存器是否是volatile的....

鼎之轻重-似可问焉
2016/02/18
133
0

没有更多内容

加载失败,请刷新页面

加载更多

怎么选择数据服务器?请记住这五条

我们应当都知道“数据无价”这个词,既然数据那么主要,选择一款安全稳定的数据服务器是很有必要的,那么如何选择一款牢靠的,稳定的数据服务器呢?我们从五个方面下手,协助您体系的了解数据...

linux-tao
12分钟前
0
0
第一讲:编写TCP Socket小程序

根据下面的步骤编写TCP Socket小程序。注意:作者必须是自己的名字。结果如下: 步骤: 1、用VC建立服务器程序 创建一个win32控制台程序 上面的代码如下,编写代码的时候切换到FileView界面,...

一匹狼工作室
14分钟前
1
0
python-protobuf2uml

首先这篇文章是参考的https://github.com/vak/protobuf2uml 本篇文章都是基于python3的 下面首先我们说一下python3的安装 wget https://github.com/protocolbuffers/protobuf/releases/down...

yiduwangkai
16分钟前
0
0
mysql通过source恢复大表小计

最近迁移一个数据库,500多张表大概600多万条数据,通过navicat导出的数据,再通过source命令导入到mysql8.0 之前也做过类似的工作,但是却从来没有这么慢过,一个小时了还没有完成,之前在笔...

休辞醉倒
16分钟前
0
0
Java、Python、C++、PHP、JavaScript这5大编程语言,我究竟该选哪个?

很多朋友在后台给我留言,问:现在这么多编程语言,Python、Java、PHP、C++、JavaScript等,究竟哪种最流行?我想这应该也是你的困扰吧! 其实他们各有优势,但是对于选择困难的程序员而言,...

Java领航员
33分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部