從零開始之驅動發開、linux驅動(三十二、簡單方式的lcd的背光碟機動)
阿新 • • 發佈:2018-11-19
前面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除錯關閉背光的原因。