和手机一样,开发板中也带有调整背光亮度的功能。
调整背光亮度依赖于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