从原理图中我们发现,button-backlight是由两路ISINK控制的,ISNIK是一种类似于PWM的控制器,它可以发出类似PWM的信号,可以通过寄存器的配置
调整其占空比等参数,进入调节输出电流,从而控制led的亮度。#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等(后面会用到)
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
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;
...
}
//后面会用到的一些结构的定义
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的实现
/**
* 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 已经被
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、属性节点的读写方法定义
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 亮灯的实现
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 中已经被赋值过如下:
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的实现
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函数,
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具体寄存器的操作,本文不作深入研究