1. 程式人生 > >從零開始之驅動發開、linux驅動(三十二、簡單方式的lcd的背光碟機動)

從零開始之驅動發開、linux驅動(三十二、簡單方式的lcd的背光碟機動)

前面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除錯關閉背光的原因。