使用pwm_bl驅動和backlight class實現背光調整
上節中梳理了dts
中lvds_backlight
設備節點的解析注冊過程,以及pwm_bl
驅動注冊過程,由平台總線對設備與驅動進行匹配,調用probe回調函數,最終實現設備的初始化。
本次梳理驅動的具體實現,從probe調用到用戶空間實現對設備節點的操作,即調整背光亮度。
1. 設備樹的重新修改
背光控制由兩個IO口,一個作為GPIO,給背光芯片提供使能信號;一個作為PWM輸出,給背光芯片提供不同占空比的PWM波形,實現亮度控制。之前設備樹節點配置中,沒有配置GPIO使能信號,不能通過控制使能而打開/關閉背光。因此重新在設備節點中增加以下三行,加入GPIO節點屬性,實現使能信號的控制。
pinctrl_bl_power: bl_power{
fsl,pins = <
SC_P_LVDS0_I2C0_SCL_LSIO_GPIO1_IO06 0x00000021
>;
};
...
lvds_backlight0: lvds_backlight {
...
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_bl_power>;
bl-power = <&gpio1 6 GPIO_ACTIVE_HIGH>;
...
};
pinctrl-0 pinctrl
的驅動解析,配置管腳屬性,復用狀態等
其中SC_P_LVDS0_I2C0_SCL_LSIO_GPIO1_IO06
為宏,定義在include/dt-bindings/pinctrl/pads-imx8qm.h
頭文件中,具體如下:
...
#define SC_P_LVDS0_I2C0_SCL 52 /* LVDS0.I2C0.SCL, LVDS0.GPIO0.IO02, LSIO.GPIO1.IO06 */ //端子號
...
#define SC_P_LVDS0_I2C0_SCL_LSIO_GPIO1_IO06 SC_P_LVDS0_I2C0_SCL 3 //復用功能
...
0x00000021
為pad/mux 寄存器設置,需要根據電路設計情況進行設置。參考手冊,摘取手冊部分寄存器配置如下:
// bit filed
6-5
Pull Down Pull Up
Pull Down Pull Up
00b - prohibited
01b - pull-up
10b - pull-down
11b - pull disabled
4-1
—
reserved
reserved
0
PDRV
Drive
0b - high drive strength
1b - low drive strength
根據寄存器手冊,0x21
設置狀態為:pull-up
,low drive strength
。
根據pinctrl-0
節點屬性,初始化時,內核將該復用端子初始化成GPIO。
bl-power
節點屬性由gpio驅動解析,標識出gpio組與組中的id,可以使用gpio模塊的接口通過此節點獲取gpio,從而實現對gpio的輸入輸出設置與輸出狀態設置。
2. pwm_bl的驅動實現
2.1 probe實現
pwm_bl.c
文件是對lvds_backlight
驅動的實現,當注冊驅動后,匹配到設備,調用probe函數,函數內容如下:
static int pwm_backlight_probe(struct platform_device *pdev)
{
struct platform_pwm_backlight_data *data = dev_get_platdata(&pdev->dev);
struct platform_pwm_backlight_data defdata;
struct backlight_properties props;
struct backlight_device *bl;
struct device_node *node = pdev->dev.of_node;
struct pwm_bl_data *pb;
struct pwm_args pargs;
int ret;
pr_debug("enter probe +%s\n","1");
if (!data) {
ret = pwm_backlight_parse_dt(&pdev->dev, &defdata); // 解析dts節點中的數據,包括默認背光,背光等級等
if (ret < 0) {
dev_err(&pdev->dev, "failed to find platform data\n");
return ret;
}
data = &defdata;
pr_debug("parser dtb data \n");
}
pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL); // 申請一塊內存用於保存pwm_bl_data數據
data->enable_gpio = of_get_named_gpio(pdev->dev.of_node, "bl-power", 0); // 使用of函數得到gpio的id,提供給gpio的api使用
/*
* Compatibility fallback for drivers still using the integer GPIO
* platform data. Must go away soon.
*/
if (!pb->enable_gpio && gpio_is_valid(data->enable_gpio)) {
ret = devm_gpio_request_one(&pdev->dev, data->enable_gpio, // 申請gpio
GPIOF_OUT_INIT_HIGH, "bl-power");
if (ret < 0) {
dev_err(&pdev->dev, "failed to request GPIO#%d: %d\n",
data->enable_gpio, ret);
goto err_alloc;
}
pb->enable_gpio = gpio_to_desc(data->enable_gpio); // 將gpio number轉成其描述符,gpiolib的api使用
}
/*
* If the GPIO is not known to be already configured as output, that
* is, if gpiod_get_direction returns either 1 or -EINVAL, change the
* direction to output and set the GPIO as active.
* Do not force the GPIO to active when it was already output as it
* could cause backlight flickering or we would enable the backlight too
* early. Leave the decision of the initial backlight state for later.
*/
if (pb->enable_gpio &&
gpiod_get_direction(pb->enable_gpio) != 0) // 設置gpio為輸出
gpiod_direction_output(pb->enable_gpio, 0); // 設置gpio輸出電平為低
pb->power_supply = devm_regulator_get(&pdev->dev, "power"); // 獲取power資源
if (IS_ERR(pb->power_supply)) {
ret = PTR_ERR(pb->power_supply);
goto err_alloc;
}
pb->pwm = devm_pwm_get(&pdev->dev, NULL); // 獲取pwm資源,由於devm_*類接口是后來增加的獲取資源的接口形式,如果獲取失敗,則使用傳統接口獲取
if (IS_ERR(pb->pwm) && PTR_ERR(pb->pwm) != -EPROBE_DEFER && !node) {
dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
pb->legacy = true;
pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
}
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = data->max_brightness;
pr_debug("register device, name is: %s\n", dev_name(&pdev->dev));
// 使用backlight class接口注冊設備
bl = devm_backlight_device_register(&pdev->dev, dev_name(&pdev->dev), pdev->dev.parent, pb,
&pwm_backlight_ops, &props);
// 更新占背光信息
bl->props.brightness = data->dft_brightness;
bl->props.power = pwm_backlight_initial_power_state(pb);
backlight_update_status(bl);
}
devm_backlight_device_register
是backlight class提供的接口函數,將設備注冊到backlight class上,屬性文件向文件系統注冊,讀寫操作的實現均在backlight中實現,最終的gpio和pwm控制通過在pwm_backlight_ops
中設置的回調實現。
2.2 為backlight class注冊設置回調
上節提到的設置回調函數的結構體變量定義如下,其中有update_status
成員變量,通過后續的backlight分析可知,在更新背光亮度和開關狀態時,會調用此函數:
static const struct backlight_ops pwm_backlight_ops = {
.update_status = pwm_backlight_update_status,
.check_fb = pwm_backlight_check_fb,
};
函數參數是一個backlight_device的結構指針。函數實現如下:
static int pwm_backlight_update_status(struct backlight_device *bl)
{
struct pwm_bl_data *pb = bl_get_data(bl);
int brightness = bl->props.brightness; // 亮度值
int duty_cycle;
if (bl->props.power != 1 ||//FB_BLANK_UNBLANK || // 電源狀態
bl->props.fb_blank != FB_BLANK_UNBLANK ||
bl->props.state & BL_CORE_FBBLANK)
brightness = 0;
if (pb->notify)
{
brightness = pb->notify(pb->dev, brightness);
pr_debug("invoke notify function\n");
}
pr_debug("update backlight brightenss %d\n", brightness);
if (brightness > 0) {
duty_cycle = compute_duty_cycle(pb, brightness);
pwm_config(pb->pwm, duty_cycle, pb->period); // 更新亮度
pwm_backlight_power_on(pb, brightness); // 拉高bl_power, 打開pwm power
} else{
pwm_backlight_power_off(pb); // 拉低bl_power gpio,關閉pwm power
}
if (pb->notify_after)
pb->notify_after(pb->dev, brightness);
return 0;
}
從設備結構指針中獲取pwm的數據結構,根據power及其他屬性狀態,對背光亮度和開關狀態進行設定。
3 backlight class
3.1 backlight class屬性導出
backlight將bl_power,brightness,type等屬性導出至用戶空間,根據導出屬性的讀寫權限,為屬性編寫配置*_show()
*_store()
函數,以brightness為例:
// 讀導出的屬性文件時的回調函數
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.brightness);
}
// 寫導出的屬性文件時的回調函數
static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rc;
struct backlight_device *bd = to_backlight_device(dev);
unsigned long brightness;
rc = kstrtoul(buf, 0, &brightness);
if (rc)
return rc;
rc = backlight_device_set_brightness(bd, brightness);
return rc ? rc : count;
}
static DEVICE_ATTR_RW(brightness); // 宏,導出屬性
其中static DEVICE_ATTR_RW(brightness)
是宏,展開后如下:
static struct device_attribute dev_attr_brightness =
{
.attr = {
.name = "brightness", // 屬性名
.mode = VERIFY_OCTAL_PERMISSIONS((S_IWUSR | (S_IRUSR|S_IRGRP|S_IROTH))) // 屬性文件的讀寫屬性
},
.show = brightness_show, // 讀回調函數
.store = brightness_store, // 寫回調函數
};
引用brightness屬性的屬性列表
// 引用brightness屬性結構體變量的屬性列表
static struct attribute *bl_device_attrs[] = {
&dev_attr_bl_power.attr,
&dev_attr_brightness.attr,
&dev_attr_actual_brightness.attr,
&dev_attr_max_brightness.attr,
&dev_attr_type.attr,
NULL,
};
// 宏
ATTRIBUTE_GROUPS(bl_device);
對上述宏展開如下:
static const struct attribute_group bl_device_group = {
.attrs = bl_device_attrs,
};
static const struct attribute_group *bl_device_groups[] = {
&bl_device_group,
((void *)0), // 空指針,標志設備的屬性組配置完成
};
其中bl_device_groups
作為backlight class
的默認屬性配置在初始化時賦值給class的結構體變量:
static int __init backlight_class_init(void)
{
backlight_class = class_create(THIS_MODULE, "backlight");
if (IS_ERR(backlight_class)) {
pr_warn("Unable to create backlight class; errno = %ld\n",
PTR_ERR(backlight_class));
return PTR_ERR(backlight_class);
}
backlight_class->dev_groups = bl_device_groups;
backlight_class->pm = &backlight_class_dev_pm_ops;
INIT_LIST_HEAD(&backlight_dev_list);
mutex_init(&backlight_dev_list_mutex);
BLOCKING_INIT_NOTIFIER_HEAD(&backlight_notifier);
return 0;
}
至此,當backlight class初始化完成后,將注冊的backlight device的屬性導出到用戶空間,並設置讀寫屬性函數,導出的屬性與其路徑如下:
root:~# ls /sys/devices/platform/backlight/lvds_backlight/ -lh
total 0
-r--r--r-- 1 root root 4.0K Jan 1 00:00 actual_brightness
-rw-r--r-- 1 root root 4.0K Jan 1 00:00 bl_power
-rw-r--r-- 1 root root 4.0K Jan 1 00:00 brightness
lrwxrwxrwx 1 root root 0 Jan 1 00:00 device -> ../../../platform
-r--r--r-- 1 root root 4.0K Jan 1 00:00 max_brightness
drwxr-xr-x 2 root root 0 Jan 1 00:00 power
lrwxrwxrwx 1 root root 0 Jan 1 00:00 subsystem -> ../../../../class/backlight
-r--r--r-- 1 root root 4.0K Jan 1 00:00 type
-rw-r--r-- 1 root root 4.0K Jan 1 00:00 uevent
3.2 設置背光狀態與調整背光
通過對bl_power
和brightness
文件的修改與讀取,便能夠控制背光的開關,設置背光亮度與獲取當前背光的狀態和亮度。當執行寫操作時,如:
echo 80 > /sys/devices/platform/backlight/lvds_backlight/brightness
則調用brightness_store
函數,更新pwm占空比,此時函數調用棧關系為: brightness_store --> backlight_device_set_brightness --> backlight_update_status --> 回調update_status
,最后回調函數即為在bl_power中注冊的回調函數,最終調用pwm的接口,實現對pwm占空比的調整,從而實現對背光亮度的控制。