从零开始之驱动发开、linux驱动(三十二、简单方式的lcd的背光驱动)

版权声明:本文为博主原创文章,未经博主允许不得转载。 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调试关闭背光的原因。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/83794158
今日推荐