MTK6735(Android6.0)-按键灯的实现

一、按键灯的简介
    最近调试一下按键灯,今天抽空顺便把的流程分析了一下。按键灯也是一种led,它的使用规则如命名一样,当按键按
下亮灯,如果一定时间不操作的话,一会会灭灯。其实这里的按键灯亮灭策略通常不是驱动来完成的,而是有用户空间来
控制。正如一句老话“驱动注重的不是策略,而是机制”;所以我们在编写驱动只需要完成led的点亮和熄灭功能即可。当
然在实际使用中我们会发现不是所有驱动都如此,有时候平台中也会添加一定的策略,这个本章不作分析。 调试按键灯,
当然首先从硬件开始。按键灯的原理图如下:


从原理图中我们发现,button-backlight是由两路ISINK控制的,ISNIK是一种类似于PWM的控制器,它可以发出类似PWM的信号,可以通过寄存器的配置

调整其占空比等参数,进入调节输出电流,从而控制led的亮度。
二、按键灯的驱动实现
1. 设备和驱动的匹配
//驱动文件中定义platform_driver
file:kernel-3.18/drivers/misc/mediatek/leds/leds_drv.c
#define USE_PINCTRL
#ifdef USE_PINCTRL
static const struct of_device_id leds_of_ids[] = {
	{.compatible = "mediatek,leds-mt65xx",},
	{}
};
#endif
static struct platform_driver mt65xx_leds_driver = {
	.driver = {
		   .name = "leds-mt65xx",
		   .owner = THIS_MODULE,
#ifdef USE_PINCTRL
	.of_match_table = leds_of_ids,  //和dts中定义一致
#endif
		   },
	.probe = mt65xx_leds_probe,
	.remove = mt65xx_leds_remove,
	/* .suspend      = mt65xx_leds_suspend, */
	.shutdown = mt65xx_leds_shutdown,


};
//驱动模块的加载
static int __init mt65xx_leds_init(void)
{
	ret = platform_driver_register(&mt65xx_leds_driver);


	....
	return ret;
}
//dts中定义leds 的相关节点如:red,green,blue,keyboard-backlight,button-backlight等(后面会用到)
file:kernel-3.18/arch/arm/boot/dts/rlk6737m_35g_c_m0.dts
led0:led@0 {
	compatible = "mediatek,red";
	led_mode = <0>;
	data = <1>;
	pwm_config = <0 0 0 0 0>;
};
led1:led@1 {
	compatible = "mediatek,green";
	led_mode = <0>;
	data = <1>;
	pwm_config = <0 0 0 0 0>;
};
led2:led@2 {
	compatible = "mediatek,blue";
	led_mode = <0>;
	data = <1>;
	pwm_config = <0 0 0 0 0>;
};
led4:led@4 {
	compatible = "mediatek,keyboard-backlight";
	led_mode = <0>;
	data = <1>;
	pwm_config = <0 0 0 0 0>;
};
led5:led@5 {
	compatible = "mediatek,button-backlight"; //这里着重分析按键灯button-backlight
	led_mode = <3>;
	data = <1>;
	pwm_config = <0 0 0 0 0>;
};
led6:led@6 {
	compatible = "mediatek,lcd-backlight";
	led_mode = <5>;
	data = <1>;
	pwm_config = <0 0 0 0 0>;
};
//dts中定义和platform_device相关的节点信息
file:kernel-3.18/arch/arm/boot/dts/mt6735m.dtsi
lightsystem: leds {
	compatible = "mediatek,leds-mt65xx"; //这里的定义和上面platform_driver中定义的一致
};
  上述dts中定义按键灯leds节点配置,内核起来后会解析dts生成相关的设备,并与驱动中的driver匹配,如果匹配成功就执行下面的probe
2.leds probe 函数实现
file:kernel-3.18/drivers/misc/mediatek/leds/leds_drv.c
static int mt65xx_leds_probe(struct platform_device *pdev)
{
	int i;
	int ret;/* rc; */
	//进入probe后,会从dts中获取led节点的mode和data
	struct cust_mt65xx_led *cust_led_list = mt_get_cust_led_list();


	LEDS_DRV_DEBUG("%s\n", __func__);
	get_div_array();
	//MT65XX_LED_TYPE_TOTAL为改通用(common)驱动所支持灯的个数
	for (i = 0; i < MT65XX_LED_TYPE_TOTAL; i++) { 
	//观察上面dts中mode如果mode为0(MT65XX_LED_MODE_NONE),则遍历下个元素
		if (cust_led_list[i].mode == MT65XX_LED_MODE_NONE) {  
			g_leds_data[i] = NULL;
			continue;
		}
		...
		//将dts中配置的mode,name ,data 保存起来后面会用到
		//通过观察上面的button-backlight 的配置,得出其mode为3,data为1,name为button-backlight
		g_leds_data[i]->cust.mode = cust_led_list[i].mode; 
		g_leds_data[i]->cust.data = cust_led_list[i].data;
		g_leds_data[i]->cust.name = cust_led_list[i].name;


		g_leds_data[i]->cdev.name = cust_led_list[i].name;
		g_leds_data[i]->cust.config_data = cust_led_list[i].config_data;	/* bei add */


		g_leds_data[i]->cdev.brightness_set = mt65xx_led_set; //设置led亮度的函数
		
		//创建sys目录下的brightness等属性节点,提供给用户空间调用
		ret = led_classdev_register(&pdev->dev, &g_leds_data[i]->cdev);
		...
	return 0;
		...
}
//后面会用到的一些结构的定义
file:kernel-3.18/drivers/misc/mediatek/leds/mt6735/leds_sw.h
enum mt65xx_led_type {
	MT65XX_LED_TYPE_RED = 0,
	MT65XX_LED_TYPE_GREEN,
	MT65XX_LED_TYPE_BLUE,
	MT65XX_LED_TYPE_JOGBALL,
	MT65XX_LED_TYPE_KEYBOARD,
	MT65XX_LED_TYPE_BUTTON,  
	MT65XX_LED_TYPE_LCD,
	MT65XX_LED_TYPE_TOTAL,
};
/**
 * led customization data structure
 * name : must the same as lights HAL
 * mode : control mode
 * data :
 *    PWM:  pwm number
 *    GPIO: gpio id
 *    PMIC: enum mt65xx_led_pmic
 *    CUST: custom set brightness function pointer
 * config_data: pwm config data
 */
struct cust_mt65xx_led {
	char *name;
	enum mt65xx_led_mode mode;
	long data;
	struct PWM_config config_data;
};
/**
 * led device node structure with mtk extentions
 * cdev: common led device structure
 * cust: customization data from device tree
 * work: workqueue for specialfied led device
 * level: brightness level
 * delay_on: on time if led is blinking
 * delay_off: off time if led is blinking
 */
struct mt65xx_led_data {
	struct led_classdev cdev;
	struct cust_mt65xx_led cust;
	struct work_struct work;
	int level;
	int delay_on;
	int delay_off;
};
file:kernel-3.18/include/linux/leds.h
enum led_brightness {
	LED_OFF		= 0,
	LED_HALF	= 127,
	LED_FULL	= 255,
};
struct led_classdev {
	const char		*name;
	enum led_brightness	 brightness;
	enum led_brightness	 max_brightness;
	int			 flags;
	...
	/* Set LED brightness level */
	/* Must not sleep, use a workqueue if needed */
	void		(*brightness_set)(struct led_classdev *led_cdev,
					  enum led_brightness brightness);
	/* Get LED brightness level */
	enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);


	.....


	struct device		*dev;
	const struct attribute_group	**groups;


	struct list_head	 node;			/* LED Device list */
	...
};
file:kernel-3.18/drivers/misc/mediatek/leds/mt6735/leds.c
char *leds_name[MT65XX_LED_TYPE_TOTAL] = {
	"red",
	"green",
	"blue",
	"jogball-backlight",
	"keyboard-backlight",
	"button-backlight",
	"lcd-backlight",
}
3.从dts中获取各种led的配置信息
struct cust_mt65xx_led *mt_get_cust_led_list(void)
{
	struct cust_mt65xx_led *cust_led_list = get_cust_led_dtsi();
	return cust_led_list;
}
struct cust_mt65xx_led *get_cust_led_dtsi(void)
{
	struct device_node *led_node = NULL;
	...
		//MT65XX_LED_TYPE_TOTAL 为led数组长度,即可以支持led的个数
		for (i = 0; i < MT65XX_LED_TYPE_TOTAL; i++) {


			char node_name[32] = "mediatek,";


			pled_dtsi[i].name = leds_name[i];
			
			//使用"mediatek,button-backlight"寻找dtsi中定义的节点
			led_node =
			    of_find_compatible_node(NULL, NULL,
						    strcat(node_name,
							   leds_name[i])); 
			if (!led_node) {
				LEDS_DEBUG("Cannot find LED node from dts\n");
				pled_dtsi[i].mode = 0;
				pled_dtsi[i].data = -1;
			} else {
				isSupportDTS = true;
				//读取led_mode值
				ret =
				    of_property_read_u32(led_node, "led_mode",  
							 &mode);
				if (!ret) {
					pled_dtsi[i].mode = mode;
					LEDS_DEBUG
					    ("The %s's led mode is : %d\n",
					     pled_dtsi[i].name,
					     pled_dtsi[i].mode);
				}
				//读取led的data值
				ret =
				    of_property_read_u32(led_node, "data",
							 &data);
				if (!ret) {
					pled_dtsi[i].data = data;
					LEDS_DEBUG
					    ("The %s's led data is : %ld\n",
					     pled_dtsi[i].name,
					     pled_dtsi[i].data);
				} 
				...
	return pled_dtsi;
}
4. 创建相关的设备节点
**
 * led_classdev_register - register a new object of led_classdev class.
 * @parent: The device to register.
 * @led_cdev: the led_classdev structure for this device.
 */
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
	led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
					led_cdev, led_cdev->groups,
					"%s", led_cdev->name);
	...


	return 0;
}
//device_create_with_groups的实现
file:kernel-3.18/drivers/base/core.c
/**
 * device_create_with_groups - creates a device and registers it with sysfs
 * @class: pointer to the struct class that this device should be registered to
 * @parent: pointer to the parent struct device of this new device, if any
 * @devt: the dev_t for the char device to be added
 * @drvdata: the data to be added to the device for callbacks
 * @groups: NULL-terminated list of attribute groups to be created
 * @fmt: string for the device's name
 *
 * This function can be used by char device classes.  A struct device
 * will be created in sysfs, registered to the specified class.
 * Additional attributes specified in the groups parameter will also
 * be created automatically.
 *
 * A "dev" file will be created, showing the dev_t for the device, if
 * the dev_t is not 0,0.
 * If a pointer to a parent struct device is passed in, the newly created
 * struct device will be a child of that device in sysfs.
 * The pointer to the struct device will be returned from the call.
 * Any further sysfs files that might be required can be created using this
 * pointer.
 *
 * Returns &struct device pointer on success, or ERR_PTR() on error.
 *
 * Note: the struct class passed to this function must have previously
 * been created with a call to class_create().
 */
struct device *device_create_with_groups(struct class *class,
					 struct device *parent, dev_t devt,
					 void *drvdata,
					 const struct attribute_group **groups,
					 const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;


	va_start(vargs, fmt);
	dev = device_create_groups_vargs(class, parent, devt, drvdata, groups,
					 fmt, vargs);
	va_end(vargs);
	return dev;
}
//device_create_groups_vargs 的实现
static struct device *
device_create_groups_vargs(struct class *class, struct device *parent,
			   dev_t devt, void *drvdata,
			   const struct attribute_group **groups,
			   const char *fmt, va_list args)
{
	struct device *dev = NULL;
	int retval = -ENODEV;


	if (class == NULL || IS_ERR(class))
		goto error;


	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev) {
		retval = -ENOMEM;
		goto error;
	}


	device_initialize(dev);
	dev->devt = devt;
	dev->class = class;
	dev->parent = parent;
	dev->groups = groups;
	dev->release = device_create_release;
	dev_set_drvdata(dev, drvdata);


	retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
	if (retval)
		goto error;


	retval = device_add(dev);
	if (retval)
		goto error;


	return dev;


error:
	put_device(dev);
	return ERR_PTR(retval);
}
//device_add的实现
int device_add(struct device *dev)
{
	...
	/* first, register with generic layer. */
	/* we require the name to be set before, and pass NULL */
	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
	if (error)
		goto Error;


	...
	error = device_add_attrs(dev);
	...
}
device_add_attrs的实现这里将会调用device_add_groups,class->dev_groups 作为参数呗传入,此时节点/sys/class/leds/xxx/brightness 已经被
创建 这里的xxx 对应驱动中的red,green,button_backlight,
lcd-backlight ... 已经创建
static int device_add_attrs(struct device *dev)
{
	struct class *class = dev->class;
	const struct device_type *type = dev->type;
	int error;
	
	 //这里class->dev_groups先前已经在led-class.c的leds_init中被赋值 leds_class->dev_groups = led_groups;
	if (class) {
		error = device_add_groups(dev, class->dev_groups);
		if (error)
			return error;
	}
	...
	return 0;
}
5、属性节点的读写方法定义
file:kernel-3.18/drivers/leds/led-class.c
static int __init leds_init(void)
{
	leds_class = class_create(THIS_MODULE, "leds"); //创建class对象
	...
	leds_class->dev_groups = led_groups; //传入brightness节点参数,led属性节点组赋值给leds_class
	...
	return 0;
}
再看led_groups的定义如下:
static const struct attribute_group *led_groups[] = {
	&led_group,
#ifdef CONFIG_LEDS_TRIGGERS
	&led_trigger_group,
#endif
	NULL,
};
static const struct attribute_group led_group = {
	.attrs = led_class_attrs,
};
static struct attribute *led_class_attrs[] = {
	&dev_attr_brightness.attr,
	&dev_attr_max_brightness.attr,
	NULL,
};
当用户空间读取属性节点时候,会直接输入当前亮度值
static ssize_t brightness_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);


	/* no lock needed for this */
	led_update_brightness(led_cdev);


	return sprintf(buf, "%u\n", led_cdev->brightness);
}
static ssize_t brightness_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t size)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	unsigned long state;
	ssize_t ret = -EINVAL;


	ret = kstrtoul(buf, 10, &state); //将传入的字符串转换为十进制
	if (ret)
		return ret;


	if (state == LED_OFF)
		led_trigger_remove(led_cdev);
	__led_set_brightness(led_cdev, state); //设置灯的亮度(亮灭)


	return size;
}
定义brightness属性的变量
static DEVICE_ATTR_RW(brightness);
6.button-backlight 亮灯的实现
通过上面节点的 /sys/class/leds/button-backlight/brightness 写方法brightness_store的定义可知,当brightness节点被用户空间写入后,将触发
执行__led_set_brightness,我们可以通过用户空间传入的参数调节灯的亮度,这里这里的传入参数范围0~255 
这里的__led_set_brightness如下定义:
file:kernel-3.18/drivers/leds/leds.h
static inline void __led_set_brightness(struct led_classdev *led_cdev,
					enum led_brightness value)
{
	if (value > led_cdev->max_brightness)
		value = led_cdev->max_brightness; //对传入的值作越界处理
	led_cdev->brightness = value;
	if (!(led_cdev->flags & LED_SUSPENDED))
		led_cdev->brightness_set(led_cdev, value);//执行亮灯操作
}
这个函数最终会调用led_cdev->brightness_set,而 led_cdev->brightness_set在leds_drv.c 中已经被赋值过如下:
file:kernel-3.18/drivers/misc/mediatek/leds/leds_drv.c
g_leds_data[i]->cdev.brightness_set = mt65xx_led_set;
static void mt65xx_led_set(struct led_classdev *led_cdev,
			   enum led_brightness level)
{
	struct mt65xx_led_data *led_data =
	    container_of(led_cdev, struct mt65xx_led_data, cdev);
	...
	mt_mt65xx_led_set(led_cdev, level);
	...
}
file:kernel-3.18/drivers/misc/mediatek/leds/mt6735/leds.c
void mt_mt65xx_led_set(struct led_classdev *led_cdev, enum led_brightness level)
{
	struct mt65xx_led_data *led_data =
	    container_of(led_cdev, struct mt65xx_led_data, cdev);
	/* unsigned long flags; */
	/* spin_lock_irqsave(&leds_lock, flags); */
	...

	/* do something only when level is changed */
	if (led_data->level != level) {
		led_data->level = level;
		if (strcmp(led_data->cust.name, "lcd-backlight") != 0) {
			LEDS_DEBUG("Set NLED directly %d at time %lu\n",
				   led_data->level, jiffies);
			schedule_work(&led_data->work);
		} else {
			if (level != 0
			    && level * CONFIG_LIGHTNESS_MAPPING_VALUE < 255) {
				level = 1;
			} else {
				level =
				    (level * CONFIG_LIGHTNESS_MAPPING_VALUE) /
				    255;
			}
			LEDS_DEBUG
			    ("Set Backlight directly %d at time %lu, mapping level is %d\n",
			     led_data->level, jiffies, level);
			if (MT65XX_LED_MODE_CUST_BLS_PWM == led_data->cust.mode) {
				mt_mt65xx_led_set_cust(&led_data->cust,
						       ((((1 <<
							   MT_LED_INTERNAL_LEVEL_BIT_CNT)
							  - 1) * level +
							 127) / 255));
			} else {
			//最终调用mt_mt65xx_led_set_cust
				mt_mt65xx_led_set_cust(&led_data->cust, level); 
			}
		}
	}
}
mt_mt65xx_led_set_cust的实现
file:kernel-3.18/drivers/misc/mediatek/leds/mt6735/leds.c
int mt_mt65xx_led_set_cust(struct cust_mt65xx_led *cust, int level)
{
	struct nled_setting led_tmp_setting = { 0, 0, 0 };
	int tmp_level = level;
	static bool button_flag;
	unsigned int BacklightLevelSupport =
	    Cust_GetBacklightLevelSupport_byPWM();


	switch (cust->mode) {


	case MT65XX_LED_MODE_PWM:
		if (strcmp(cust->name, "lcd-backlight") == 0) {
			bl_brightness_hal = level;
			if (level == 0) {
				mt_pwm_disable(cust->data,
					       cust->config_data.pmic_pad);


			} else {


				if (BacklightLevelSupport ==
				    BACKLIGHT_LEVEL_PWM_256_SUPPORT)
					level = brightness_mapping(tmp_level);
				else
					level = brightness_mapto64(tmp_level);
				mt_backlight_set_pwm(cust->data, level,
						     bl_div_hal,
						     &cust->config_data);
			}
			bl_duty_hal = level;


		} else {
			if (level == 0) {
				led_tmp_setting.nled_mode = NLED_OFF;
				mt_led_set_pwm(cust->data, &led_tmp_setting);
				mt_pwm_disable(cust->data,
					       cust->config_data.pmic_pad);
			} else {
				led_tmp_setting.nled_mode = NLED_ON;
				mt_led_set_pwm(cust->data, &led_tmp_setting);
			}
		}
		return 1;


	case MT65XX_LED_MODE_GPIO:
		LEDS_DEBUG("brightness_set_cust:go GPIO mode!!!!!\n");
		return ((cust_set_brightness) (cust->data)) (level);
   //这里的MT65XX_LED_MODE_PMIC对应button-backlight的配置的mode=3 
	case MT65XX_LED_MODE_PMIC:  
		/* for button baclight used SINK channel, when set button ISINK,
			don't do disable other ISINK channel */
		//使用button-backlight的调用如下:
		if ((strcmp(cust->name, "button-backlight") == 0)) { 
			if (button_flag == false) {
				switch (cust->data) {
				case MT65XX_LED_PMIC_NLED_ISINK0:
					button_flag_isink0 = 1;
					break;
				case MT65XX_LED_PMIC_NLED_ISINK1:
					button_flag_isink1 = 1;
					break;
				case MT65XX_LED_PMIC_NLED_ISINK2:
					button_flag_isink2 = 1;
					break;
				case MT65XX_LED_PMIC_NLED_ISINK3:
					button_flag_isink3 = 1;
					break;
				default:
					break;
				}
				button_flag = true;
			}
		}
		return mt_brightness_set_pmic(cust->data, level, bl_div_hal);


	case MT65XX_LED_MODE_CUST_LCM:
		if (strcmp(cust->name, "lcd-backlight") == 0)
			bl_brightness_hal = level;
		LEDS_DEBUG("brightness_set_cust:backlight control by LCM\n");
		/* warning for this API revork */
		return ((cust_brightness_set) (cust->data)) (level, bl_div_hal);


	case MT65XX_LED_MODE_CUST_BLS_PWM:
		if (strcmp(cust->name, "lcd-backlight") == 0)
			bl_brightness_hal = level;
		return ((cust_set_brightness) (cust->data)) (level);


	case MT65XX_LED_MODE_NONE:
	default:
		break;
	}
	return -1;
}
也就是说当用户对属性节点 /sys/class/leds/button-backlight/brightness 写入时最终调用mt_brightness_set_pmic函数,
mt_brightness_set_pmic的实现如下:
file:kernel-3.18/drivers/misc/mediatek/leds/mt6735/leds.c
int mt_brightness_set_pmic(enum mt65xx_led_pmic pmic_type, u32 level, u32 div)
{
	static bool first_time = true;


	LEDS_DEBUG("PMIC#%d:%d\n", pmic_type, level);
	mutex_lock(&leds_pmic_mutex);
	if (pmic_type == MT65XX_LED_PMIC_NLED_ISINK0) {
		if ((button_flag_isink0 == 0) && (first_time == true)) {	/* button
			flag ==0, means this ISINK is not for button backlight */
			if (button_flag_isink1 == 0)
				pmic_set_register_value(PMIC_ISINK_CH1_EN, NLED_OFF);	/* sw
			workround for sync leds status */
			if (button_flag_isink2 == 0)
				pmic_set_register_value(PMIC_ISINK_CH2_EN,
							NLED_OFF);
			if (button_flag_isink3 == 0)
				pmic_set_register_value(PMIC_ISINK_CH3_EN,
							NLED_OFF);
			first_time = false;
		}
		pmic_set_register_value(PMIC_RG_DRV_32K_CK_PDN, 0x0);	/* Disable power down */
		pmic_set_register_value(PMIC_RG_DRV_ISINK0_CK_PDN, 0);
		pmic_set_register_value(PMIC_RG_DRV_ISINK0_CK_CKSEL, 0);
		pmic_set_register_value(PMIC_ISINK_CH0_MODE, ISINK_PWM_MODE);
		pmic_set_register_value(PMIC_ISINK_CH0_STEP, ISINK_3);	/* 16mA */
		pmic_set_register_value(PMIC_ISINK_DIM0_DUTY, 15);
		pmic_set_register_value(PMIC_ISINK_DIM0_FSEL, ISINK_1KHZ);	/* 1KHz */
		if (level)
			pmic_set_register_value(PMIC_ISINK_CH0_EN, NLED_ON);
		else
			pmic_set_register_value(PMIC_ISINK_CH0_EN, NLED_OFF);
		mutex_unlock(&leds_pmic_mutex);
		return 0;
	} else if (pmic_type == MT65XX_LED_PMIC_NLED_ISINK1) {
		if ((button_flag_isink1 == 0) && (first_time == true)) {	/* button
			flag ==0, means this ISINK is not for button backlight */
			if (button_flag_isink0 == 0)
				pmic_set_register_value(PMIC_ISINK_CH0_EN, NLED_OFF);	/* sw
				workround for sync leds status */
			if (button_flag_isink2 == 0)
				pmic_set_register_value(PMIC_ISINK_CH2_EN,
							NLED_OFF);
			if (button_flag_isink3 == 0)
				pmic_set_register_value(PMIC_ISINK_CH3_EN,
							NLED_OFF);
			first_time = false;
		}
		pmic_set_register_value(PMIC_RG_DRV_32K_CK_PDN, 0x0);	/* Disable power down */
		pmic_set_register_value(PMIC_RG_DRV_ISINK1_CK_PDN, 0);
		pmic_set_register_value(PMIC_RG_DRV_ISINK1_CK_CKSEL, 0);
		pmic_set_register_value(PMIC_ISINK_CH1_MODE, ISINK_PWM_MODE);
		pmic_set_register_value(PMIC_ISINK_CH1_STEP, ISINK_3);	/* 16mA */
		pmic_set_register_value(PMIC_ISINK_DIM1_DUTY, 15);
		pmic_set_register_value(PMIC_ISINK_DIM1_FSEL, ISINK_1KHZ);	/* 1KHz */
		if (level)
			pmic_set_register_value(PMIC_ISINK_CH1_EN, NLED_ON);
		else
			pmic_set_register_value(PMIC_ISINK_CH1_EN, NLED_OFF);
		mutex_unlock(&leds_pmic_mutex);
		return 0;
	}
	mutex_unlock(&leds_pmic_mutex);
	return -1;
}
上述pmic_set_register_value的操作就是对ISINK具体寄存器的操作,本文不作深入研究
三、总结
通过上述的分析,我们大致可以看出在mtk平台上leds系列的驱动流程大致如下,先在dts中定义各个led节点的配置,配
置如mode,name,data 预留给driver调用,然后创建common drver (通用驱动)对各个led统一管理,在通用驱动中各个不
同类型led 通过数组区分,common driver对各个led进行统一的设备注册,属性节点创建等。当然led的种类繁多还有充
电指示灯,呼吸灯等,在加上每种灯硬件配置不一样驱动实现方式也不同,这个需要另行分析了。







  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值