和手機一樣,開發板中也帶有調整背光亮度的功能。
調整背光亮度依賴於PWM,它通過調節脈沖寬度來控制背光亮度,此方式需要使用PWM驅動。本章將對其進行講解。
一、用戶空間調整背光亮度
一般應用程序可以通過/sys/class/目錄下的節點間接調整各個外設的參數。如下圖,可通過命令行來控制背光亮度。設備節點不同開發板的目錄不一定相同,讀者需自行測試。

如果讀者確定自己的開發板有PWM控制背光的功能,但是在LCD、背光和PWM等相關目錄沒有找到調整亮度操作,可能的原因有PWM沒有被編譯進內核。
我們可以在配置中執行搜索操作,如:
$ make menuconfig 進入內核配置頁面
按 / 鍵搜索,確認背光PWM是否被編譯進內核。
如下圖,我的背光PWM被編譯進內核中了。需要注意,不同內核可能選項位置不同。

二、PWM子系統
在Linux3.5版本中並沒有引入PWM子系統,而是使用總線設備驅動模型。我在此以Linux-4.4為例談論PWM子系統。
PWM也分為設備屬性和行為。屬性使用struct pwm_device表示,行為使用struct pwm_ops表示,兩結構體定義如下:
struct pwm_device { const char *label; unsigned long flags; unsigned int hwpwm; unsigned int pwm; struct pwm_chip *chip; void *chip_data; struct mutex lock; unsigned int period; /* PWM周期 */ unsigned int duty_cycle; /* 占空比 */ enum pwm_polarity polarity; /* 極性反轉 */ };
enum pwm_polarity { PWM_POLARITY_NORMAL, PWM_POLARITY_INVERSED, }; struct pwm_ops { int (*request)(struct pwm_chip *chip, struct pwm_device *pwm); void (*free)(struct pwm_chip *chip, struct pwm_device *pwm); int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns); int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm, enum pwm_polarity polarity); int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm); void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm); #ifdef CONFIG_DEBUG_FS void (*dbg_show)(struct pwm_chip *chip, struct seq_file *s); #endif struct module *owner; };
pwm_ops的上層函數有:
/* 下列函數與pwm_ops位置一一對應 */ struct pwm_device *pwm_request(int pwm, const char *label); void pwm_free(struct pwm_device *pwm); int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity); int pwm_enable(struct pwm_device *pwm); void pwm_disable(struct pwm_device *pwm);
PWM雖然分層和LCD一致,但這兩個結構體並不像LCD中可以互相訪問。如LCD中fb_info定義有:
struct fb_info { ... struct fb_ops *fbops; /* 可通過fb_info訪問fb操作函數 */ ... };
既然struct pwm_device和struct pwm_ops不能互相訪問,那么一定會有其他結構體連接兩者。它就是struct pwm_chip:
struct pwm_chip { struct device *dev; struct list_head list; const struct pwm_ops *ops; /* PWM操作函數 */ int base; unsigned int npwm; /* 這個chip中的PWM個數 */ struct pwm_device *pwms; /* PWM屬性 */ struct pwm_device * (*of_xlate)(struct pwm_chip *pc, const struct of_phandle_args *args); unsigned int of_pwm_n_cells; bool can_sleep; };
如果我們需要注冊或注銷PWM,使用的結構體應該也是struct pwm_chip。也就是PWM以chip注冊。
注冊函數pwmchip_add()實現如下:
1 /* chip的屬性 */ 2 static struct attribute *pwm_chip_attrs[] = { 3 &dev_attr_export.attr, /* 注冊pwm,創建設備節點 */ 4 &dev_attr_unexport.attr, /* 注銷pwm,銷毀設備節點 */ 5 &dev_attr_npwm.attr, /* 當前chip中pwm個數 */ 6 NULL, 7 }; 8 9 int pwmchip_add(struct pwm_chip *chip) 10 { 11 return pwmchip_add_with_polarity(chip, PWM_POLARITY_NORMAL); 12 } 13 EXPORT_SYMBOL_GPL(pwmchip_add); 14 15 int pwmchip_add_with_polarity(struct pwm_chip *chip, enum pwm_polarity polarity) 16 { 17 struct pwm_device *pwm; 18 unsigned int i; 19 int ret; 20 ... 21 /* pwm子系統和input子系統類似,按順序填充的,但pwm子系統沒有設備號 */ 22 ret = alloc_pwms(chip->base, chip->npwm); 23 ... 24 /* 申請空間 */ 25 chip->pwms = kzalloc(chip->npwm * sizeof(*pwm), GFP_KERNEL); 26 ... 27 chip->base = ret; 28 /* 設置pwm_device */ 29 for (i = 0; i < chip->npwm; i++) { 30 pwm = &chip->pwms[i]; 31 pwm->chip = chip; 32 pwm->pwm = chip->base + i; 33 pwm->hwpwm = i; 34 pwm->polarity = polarity; 35 ... 36 } 37 ... 38 /* 創建pwmchip%d設備 */ 39 pwmchip_sysfs_export(chip); 40 ... 41 } 42 43 void pwmchip_sysfs_export(struct pwm_chip *chip) 44 { 45 ... 46 parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip, "pwmchip%d", chip->base); 47 ... 48 }
注銷函數pwmchip_remove()實現如下:
1 int pwmchip_remove(struct pwm_chip *chip) 2 { 3 unsigned int i; 4 int ret = 0; 5 /* 此函數會卸載此chip下的所有PWM */ 6 pwmchip_sysfs_unexport_children(chip); 7 ... 8 free_pwms(chip); 9 /* 注銷設備 */ 10 pwmchip_sysfs_unexport(chip); 11 ... 12 } 13 EXPORT_SYMBOL_GPL(pwmchip_remove); 14 15 void pwmchip_sysfs_unexport(struct pwm_chip *chip) 16 { 17 struct device *parent; 18 19 parent = class_find_device(&pwm_class, NULL, chip, 20 pwmchip_sysfs_match); 21 if (parent) { 22 /* for class_find_device() */ 23 put_device(parent); 24 device_unregister(parent); 25 } 26 }
在注冊和注銷pwmchip函數中,只是創建和刪除和設備節點。但是在此之前,它並沒有創建類,而是使用了類pwm_class。分析可知PWM子系統在初始化過程中一定創建了類pwm_class。
1 static struct class pwm_class = { 2 .name = "pwm", /* 類名為pwm */ 3 .owner = THIS_MODULE, 4 .dev_groups = pwm_chip_groups, /* 存放所有chip */ 5 }; 6 7 static int __init pwm_sysfs_init(void) 8 { 9 return class_register(&pwm_class); 10 }
現在,我們來整理一下整體框架。

二、三星平台驅動分析
1. platform_device
1 static struct resource samsung_pwm_resource[] = { 2 DEFINE_RES_MEM(SAMSUNG_PA_TIMER, SZ_4K), 3 }; 4 5 struct platform_device samsung_device_pwm = { 6 .name = "samsung-pwm", 7 .id = -1, 8 .num_resources = ARRAY_SIZE(samsung_pwm_resource), 9 .resource = samsung_pwm_resource, 10 };
2. platform_driver
1 static struct platform_driver pwm_samsung_driver = { 2 .driver = { 3 .name = "samsung-pwm", 4 .pm = &pwm_samsung_pm_ops, 5 .of_match_table = of_match_ptr(samsung_pwm_matches), 6 }, 7 .probe = pwm_samsung_probe, 8 .remove = pwm_samsung_remove, 9 }; 10 module_platform_driver(pwm_samsung_driver);
我們來分析probe()函數,它設置並注冊了pwmchip:
1 struct samsung_pwm_chip { 2 struct pwm_chip chip; /* pwm_chip */ 3 struct samsung_pwm_variant variant; 4 u8 inverter_mask; 5 6 void __iomem *base; 7 struct clk *base_clk; 8 struct clk *tclk0; 9 struct clk *tclk1; 10 }; 11 12 static int pwm_samsung_probe(struct platform_device *pdev) 13 { 14 struct device *dev = &pdev->dev; 15 struct samsung_pwm_chip *chip; 16 struct resource *res; 17 unsigned int chan; 18 int ret; 19 20 /* 分配內存 */ 21 chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); 22 /* 設置chip */ 23 chip->chip.dev = &pdev->dev; 24 chip->chip.ops = &pwm_samsung_ops; 25 chip->chip.base = -1; 26 chip->chip.npwm = SAMSUNG_PWM_NUM; 27 chip->inverter_mask = BIT(SAMSUNG_PWM_NUM) - 1; 28 ... 29 memcpy(&chip->variant, pdev->dev.platform_data, sizeof(chip->variant)); 30 31 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 32 chip->base = devm_ioremap_resource(&pdev->dev, res); 33 ... 34 chip->base_clk = devm_clk_get(&pdev->dev, "timers"); 35 ... 36 ret = clk_prepare_enable(chip->base_clk); 37 ... 38 for (chan = 0; chan < SAMSUNG_PWM_NUM; ++chan) 39 if (chip->variant.output_mask & BIT(chan)) 40 pwm_samsung_set_invert(chip, chan, true); 41 42 /* Following clocks are optional. */ 43 chip->tclk0 = devm_clk_get(&pdev->dev, "pwm-tclk0"); 44 chip->tclk1 = devm_clk_get(&pdev->dev, "pwm-tclk1"); 45 46 platform_set_drvdata(pdev, chip); 47 /* 注冊pwm */ 48 ret = pwmchip_add(&chip->chip); 49 ... 50 return 0; 51 }
remove()應該注銷pwmchip:
1 static int pwm_samsung_remove(struct platform_device *pdev) 2 { 3 struct samsung_pwm_chip *chip = platform_get_drvdata(pdev); 4 int ret; 5 6 ret = pwmchip_remove(&chip->chip); 7 if (ret < 0) 8 return ret; 9 10 clk_disable_unprepare(chip->base_clk); 11 12 return 0; 13 }
關於三星平台的struct pwm_ops pwm_samsung_ops在此不做分析,這些函數只是對寄存器進行讀寫操作。
最后總結一下:
1. PWM是通過注冊chip來注冊一個芯片中所有的PWM的
2. 對於具體的PWM,可以使用sysfs中的attributre屬性來分辨
3. 注冊完成某個PWM后,它的周期、占空比、極性和使能可以在sysfs中更改
4. 在sysfs中修改降低了驅動程序修改的次數
pwmchip在/sys/class/目錄下生成,我們可以執行以下命令查看和修改pwmchip0的占空比:
# cd /sys/class/pwm/
# ls
# cd ./pwmchip0
# ls
# cd ./pwm0
# ls
# ls duty_cycle
# echo 50 > duty_cycle
# ls duty_cycle
下一章 13、GPIO子系統
