版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/83794158
前面lcd章节我们知道了LCD的背光可以由两种方式决定调节:
1.一种是I/O口直接输出高低电平来控制背光的量灭,这种方式简单,但不能调背光亮度。
2.另一种是采用PWM调节脉冲宽度的方式来控制背光,这种方式需要采用PWM驱动来实现,优点是可以调节屏幕亮度,节省电量。
lcd背光是一个专有的类,lcd类,这个类相比input,fb类都要简单很多,这里就不介绍了。
本节以控制GPIO的方式,来分析linux中的lcd背光驱动。
lcd背光驱动,也是利用平台总线来实现的。
首先我们看设备部分(硬件)
static void smdkv210_lte480wv_set_power(struct plat_lcd_data *pd,
unsigned int power)
{
if (power) {
#if !defined(CONFIG_BACKLIGHT_PWM) /* 没配置PWM调光才用GPIO作背光控制 */
gpio_request_one(S5PV210_GPD0(3), GPIOF_OUT_INIT_HIGH, "GPD0");
gpio_free(S5PV210_GPD0(3));
#endif
/* fire nRESET on power up */
gpio_request_one(S5PV210_GPH0(6), GPIOF_OUT_INIT_HIGH, "GPH0");
gpio_set_value(S5PV210_GPH0(6), 0);
mdelay(10);
gpio_set_value(S5PV210_GPH0(6), 1);
mdelay(10);
gpio_free(S5PV210_GPH0(6));
} else {
#if !defined(CONFIG_BACKLIGHT_PWM)
gpio_request_one(S5PV210_GPD0(3), GPIOF_OUT_INIT_LOW, "GPD0");
gpio_free(S5PV210_GPD0(3));
#endif
}
}
static struct plat_lcd_data smdkv210_lcd_lte480wv_data = {
.set_power = smdkv210_lte480wv_set_power, /* 背光设置函数 */
};
static struct platform_device smdkv210_lcd_lte480wv = {
.name = "platform-lcd",
.dev.parent = &s3c_device_fb.dev, /* 背光依赖于lcd */
.dev.platform_data = &smdkv210_lcd_lte480wv_data,
};
因为我们的硬件上是背光驱动接在LCD的低电平上驱动的,和三星公板是刚好相反。同时用的GPIO也不一样,我们这用了SYS_OE端口来使能LCD(详解见前面三星移植部分)
所以对上面函数修改后如下
static void smdkv210_lte480wv_set_power(struct plat_lcd_data *pd,
unsigned int power)
{
if (power) {
#if !defined(CONFIG_BACKLIGHT_PWM)
gpio_request_one(S5PV210_GPD0(0), GPIOF_OUT_INIT_LOW, "GPD0");
gpio_free(S5PV210_GPD0(0));
printk(KERN_INFO"GPD0_LOW###################################\n");
#endif
/* backlight enable pin */
// gpio_request_one(S5PV210_GPF3(5), GPIOF_OUT_INIT_LOW, "GPF3_5");
//gpio_free(S5PV210_GPF3(5));
s3c_gpio_cfgpin(S5PV210_GPF3(5), S3C_GPIO_SFN(3));
s5p_gpio_set_drvstr(S5PV210_GPF3(5), S5P_GPIO_DRVSTR_LV4);
} else {
#if !defined(CONFIG_BACKLIGHT_PWM)
gpio_request_one(S5PV210_GPD0(0), GPIOF_OUT_INIT_HIGH, "GPD0");
gpio_free(S5PV210_GPD0(0));
printk(KERN_INFO"GPD0_HIGH###################################\n");
#endif
}
}
接下来我们看驱动部分(通用)
static struct platform_driver platform_lcd_driver = {
.driver = {
.name = "platform-lcd", /* 平台总线,按照名字匹配 */
.owner = THIS_MODULE,
.pm = &platform_lcd_pm_ops, /* 电源管理相关(暂不分析) */
.of_match_table = of_match_ptr(platform_lcd_of_match),
},
.probe = platform_lcd_probe, /* 初始化函数 */
};
static int platform_lcd_probe(struct platform_device *pdev)
{
struct plat_lcd_data *pdata;
struct platform_lcd *plcd;
struct device *dev = &pdev->dev;
int err;
/* 得到dev部分传过来的函数 */
pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(dev, "no platform data supplied\n");
return -EINVAL;
}
/* 有的dev要初始化,也定义了probe函数,三星没有 */
if (pdata->probe) {
err = pdata->probe(pdata);
if (err)
return err;
}
/* 申请平台lcd设备 */
plcd = devm_kzalloc(&pdev->dev, sizeof(struct platform_lcd),
GFP_KERNEL);
if (!plcd)
return -ENOMEM;
/* 绑定设备参数 */
plcd->us = dev;
plcd->pdata = pdata;
/* 注册lcd设备驱动 */
plcd->lcd = devm_lcd_device_register(&pdev->dev, dev_name(dev), dev,
plcd, &platform_lcd_ops);
if (IS_ERR(plcd->lcd)) {
dev_err(dev, "cannot register lcd device\n");
return PTR_ERR(plcd->lcd);
}
/* 把plcd绑定到dev里面的driver_data */
platform_set_drvdata(pdev, plcd);
/* 点亮背光 */
platform_lcd_set_power(plcd->lcd, FB_BLANK_NORMAL);
return 0;
}
这里对上面的platform_lcd_ops进行说明
static struct lcd_ops platform_lcd_ops = {
.get_power = platform_lcd_get_power,
.set_power = platform_lcd_set_power,
.check_fb = platform_lcd_match,
};
static int platform_lcd_get_power(struct lcd_device *lcd)
{
struct platform_lcd *plcd = to_our_lcd(lcd);
return plcd->power; /* 查看背光电源状态 */
}
static int platform_lcd_set_power(struct lcd_device *lcd, int power)
{
struct platform_lcd *plcd = to_our_lcd(lcd);
int lcd_power = 1;
if (power == FB_BLANK_POWERDOWN || plcd->suspended) /* 如果掉电或挂起则关闭背光,否则打开 */
lcd_power = 0;
plcd->pdata->set_power(plcd->pdata, lcd_power);
plcd->power = power;
return 0;
}
/* 绑定背光的父设备 */
static int platform_lcd_match(struct lcd_device *lcd, struct fb_info *info)
{
struct platform_lcd *plcd = to_our_lcd(lcd);
struct plat_lcd_data *pdata = plcd->pdata;
if (pdata->match_fb)
return pdata->match_fb(pdata, info);
return plcd->us->parent == info->device;
}
lcd背光这里为了用户层方便使用,做了很多的attribute接口来,操纵背光硬件。
/* 打印背光状态 */
static ssize_t lcd_power_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int rc;
struct lcd_device *ld = to_lcd_device(dev);
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->get_power)
rc = sprintf(buf, "%d\n", ld->ops->get_power(ld));
else
rc = -ENXIO;
mutex_unlock(&ld->ops_lock);
return rc;
}
/* 设置背光 */
static ssize_t lcd_power_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rc;
struct lcd_device *ld = to_lcd_device(dev);
unsigned long power;
rc = kstrtoul(buf, 0, &power);
if (rc)
return rc;
rc = -ENXIO;
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->set_power) {
pr_debug("set power to %lu\n", power);
ld->ops->set_power(ld, power);
rc = count;
}
mutex_unlock(&ld->ops_lock);
return rc;
}
/* 把上面两个函数定义成设备属性 */
static DEVICE_ATTR_RW(lcd_power);
static ssize_t contrast_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int rc = -ENXIO;
struct lcd_device *ld = to_lcd_device(dev);
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->get_contrast)
rc = sprintf(buf, "%d\n", ld->ops->get_contrast(ld));
mutex_unlock(&ld->ops_lock);
return rc;
}
static ssize_t contrast_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rc;
struct lcd_device *ld = to_lcd_device(dev);
unsigned long contrast;
rc = kstrtoul(buf, 0, &contrast);
if (rc)
return rc;
rc = -ENXIO;
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->set_contrast) {
pr_debug("set contrast to %lu\n", contrast);
ld->ops->set_contrast(ld, contrast);
rc = count;
}
mutex_unlock(&ld->ops_lock);
return rc;
}
static DEVICE_ATTR_RW(contrast);
static ssize_t max_contrast_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lcd_device *ld = to_lcd_device(dev);
return sprintf(buf, "%d\n", ld->props.max_contrast);
}
static DEVICE_ATTR_RO(max_contrast);
/* 所有的属性宏 */
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
/* show和store方法 */
#define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
#define DEVICE_ATTR_RO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
#define DEVICE_ULONG_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_ulong, device_store_ulong), &(_var) }
#define DEVICE_INT_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_int, device_store_int), &(_var) }
#define DEVICE_BOOL_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_bool, device_store_bool), &(_var) }
#define DEVICE_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = \
__ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store)
/**
* Use these macros to make defining attributes easier. See include/linux/device.h
* for examples..
*/
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}
#define __ATTR_RO(_name) { \
.attr = { .name = __stringify(_name), .mode = S_IRUGO }, \
.show = _name##_show, \
}
/* 只读属性(权限) */
#define __ATTR_RO_MODE(_name, _mode) { \
.attr = { .name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _name##_show, \
}
#define __ATTR_WO(_name) { \
.attr = { .name = __stringify(_name), .mode = S_IWUSR }, \
.store = _name##_store, \
}
/* 读写属性都有的方法 */
#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO), \
_name##_show, _name##_store)
#define __ATTR_NULL { .attr = { .name = NULL } }
#ifdef CONFIG_DEBUG_LOCK_ALLOC
#define __ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), .mode = _mode, \
.ignore_lockdep = true }, \
.show = _show, \
.store = _store, \
}
#else
#define __ATTR_IGNORE_LOCKDEP __ATTR
#endif
#define __ATTRIBUTE_GROUPS(_name) \
static const struct attribute_group *_name##_groups[] = { \
&_name##_group, \
NULL, \
}
#define ATTRIBUTE_GROUPS(_name) \
static const struct attribute_group _name##_group = { \
.attrs = _name##_attrs, \
}; \
__ATTRIBUTE_GROUPS(_name)
我们上面的那些属性操作最终被分为三组,被放入下面这个数组
static struct attribute *lcd_device_attrs[] = {
&dev_attr_lcd_power.attr,
&dev_attr_contrast.attr,
&dev_attr_max_contrast.attr,
NULL,
};
ATTRIBUTE_GROUPS(lcd_device);
通过ATTRIBUTE_GROUPS重新定义,又放到lcd_device_groups里面。
最终是在创建类的时候,绑定到类属性设备组里面
static void __exit lcd_class_exit(void)
{
class_destroy(lcd_class);
}
static int __init lcd_class_init(void)
{
lcd_class = class_create(THIS_MODULE, "lcd");
if (IS_ERR(lcd_class)) {
pr_warn("Unable to create backlight class; errno = %ld\n",
PTR_ERR(lcd_class));
return PTR_ERR(lcd_class);
}
lcd_class->dev_groups = lcd_device_groups; /* 绑定属性数组 */
return 0;
}
上面的stroe函数调用的是fops里面的函数,也就是下面这个
static int platform_lcd_set_power(struct lcd_device *lcd, int power)
{
struct platform_lcd *plcd = to_our_lcd(lcd);
int lcd_power = 1;
if (power == FB_BLANK_POWERDOWN || plcd->suspended)
lcd_power = 0;
plcd->pdata->set_power(plcd->pdata, lcd_power);
plcd->power = power;
return 0;
}
可以看到,在lcd没挂起的情况下,只有power == FB_BLANK_POWERDOWN的情况下才会关背光,否则会一直开背光的。
/deiver/video/fbdev/sis/sis.h中有定义如下
#ifndef FB_BLANK_UNBLANK
#define FB_BLANK_UNBLANK 0
#endif
#ifndef FB_BLANK_NORMAL
#define FB_BLANK_NORMAL 1
#endif
#ifndef FB_BLANK_VSYNC_SUSPEND
#define FB_BLANK_VSYNC_SUSPEND 2
#endif
#ifndef FB_BLANK_HSYNC_SUSPEND
#define FB_BLANK_HSYNC_SUSPEND 3
#endif
#ifndef FB_BLANK_POWERDOWN
#define FB_BLANK_POWERDOWN 4
#endif
可见只有在给
/sys/class/lcd/platform-lcd.0/lcd_power
里面写4的时候才会关掉lcd的背光,否则其它数字都是打开背光的。
这也是我们前面章节写4调试关闭背光的原因。