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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/83833734

上面一节pwm子系统已经可以在应用层注册pwm0来调节我们的lcd背光亮度了。但linux设备中有专门为lcd做了一个调光驱动,而且调光只需要设置一各参数,所以会方便许多,当然也是依赖于pwm子系统来实现的。

这里首先勾选pwm调节背光驱动

Device Drivers  --->   
    [*] Pulse-Width Modulation (PWM) Support  --->
        <*>   Samsung PWM support  

    Graphics support  --->
        [*] Backlight & LCD device support  ---> 
            <*>     Generic PWM based Backlight Driver     

接下来修改硬件信息

/* LCD Backlight data */
static struct samsung_bl_gpio_info smdkv210_bl_gpio_info = {
	.no = S5PV210_GPD0(3),
	.func = S3C_GPIO_SFN(2),
};

static struct platform_pwm_backlight_data smdkv210_bl_data = {
	.pwm_id = 3,
	.pwm_period_ns = 1000,
	.enable_gpio = -1,
};

和前面GPIO方式一样,我们用的GPD0(0)端口,即定时器0的PWM0功能做调节背光的。所以修改端口如下:


/* LCD Backlight data */
static struct samsung_bl_gpio_info smdkv210_bl_gpio_info = { 
    .no = S5PV210_GPD0(0),
    .func = S3C_GPIO_SFN(2),
};

static struct platform_pwm_backlight_data smdkv210_bl_data = { 
    .pwm_id = 0,
    .pwm_period_ns = 1000,
    .enable_gpio = -1, 
}

这里我们pwm周期是1us的。

最后我们看一下背光相关的平台设备是怎么注册的

samsung_bl_set(&smdkv210_bl_gpio_info, &smdkv210_bl_data);
/* Initialize few important fields of platform_pwm_backlight_data
 * structure with default values. These fields can be overridden by
 * board-specific values sent from machine file.
 * For ease of operation, these fields are initialized with values
 * used by most samsung boards.
 * Users has the option of sending info about other parameters
 * for their specific boards
 */

static struct samsung_bl_drvdata samsung_dfl_bl_data __initdata = {
	.plat_data = {
		.max_brightness = 255,                /* 最大亮度255 */
		.dft_brightness = 255,                /* 默认最大亮度 */
		.pwm_period_ns  = 78770,
		.enable_gpio    = -1,
		.init           = samsung_bl_init,    /* 背光引脚申请和初始化为PWM模式 */
		.exit           = samsung_bl_exit,    /* 背光引脚释放和初始化为输出模式(输出低电平) */
	},
};

static struct platform_device samsung_dfl_bl_device __initdata = {
	.name		= "pwm-backlight",
};


/* samsung_bl_set - Set board specific data (if any) provided by user for
 * PWM Backlight control and register specific PWM and backlight device.
 * @gpio_info:	structure containing GPIO info for PWM timer
 * @bl_data:	structure containing Backlight control data
 */
void __init samsung_bl_set(struct samsung_bl_gpio_info *gpio_info,
	struct platform_pwm_backlight_data *bl_data)
{
	int ret = 0;
	struct platform_device *samsung_bl_device;
	struct samsung_bl_drvdata *samsung_bl_drvdata;
	struct platform_pwm_backlight_data *samsung_bl_data;

    /* 申请一段platform_device空间,把samsung_dfl_bl_device拷贝到里面 */
	samsung_bl_device = kmemdup(&samsung_dfl_bl_device,
			sizeof(struct platform_device), GFP_KERNEL);
	if (!samsung_bl_device) {
		printk(KERN_ERR "%s: no memory for platform dev\n", __func__);
		return;
	}

     /* 申请一段samsung_dfl_bl_data空间,把samsung_dfl_bl_data数据拷贝到里面 */
	samsung_bl_drvdata = kmemdup(&samsung_dfl_bl_data,
				sizeof(samsung_dfl_bl_data), GFP_KERNEL);
	if (!samsung_bl_drvdata) {
		printk(KERN_ERR "%s: no memory for platform dev\n", __func__);
		goto err_data;
	}
    /* 把动态填充的设备数据绑定到平台数据 */
	samsung_bl_device->dev.platform_data = &samsung_bl_drvdata->plat_data;
    /* 平台驱动数据绑定pwm用到的gpio号 */
	samsung_bl_drvdata->gpio_info = gpio_info;
    /* 把驱动数据绑定到平台数据 */
	samsung_bl_data = &samsung_bl_drvdata->plat_data;

	/* Copy board specific data provided by user */
	samsung_bl_data->pwm_id = bl_data->pwm_id;        /* 确定pwm号 */
	samsung_bl_device->dev.parent = &samsung_device_pwm.dev;  /* 背光依赖PWM子系统 */

    /* 设置背光参数,默认是有的,三星这里使用默认的 */
	if (bl_data->max_brightness)
		samsung_bl_data->max_brightness = bl_data->max_brightness;
	if (bl_data->dft_brightness)
		samsung_bl_data->dft_brightness = bl_data->dft_brightness;
	if (bl_data->lth_brightness)
		samsung_bl_data->lth_brightness = bl_data->lth_brightness;
	if (bl_data->pwm_period_ns)
		samsung_bl_data->pwm_period_ns = bl_data->pwm_period_ns;
	if (bl_data->enable_gpio >= 0)
		samsung_bl_data->enable_gpio = bl_data->enable_gpio;
	if (bl_data->init)
		samsung_bl_data->init = bl_data->init;
	if (bl_data->notify)
		samsung_bl_data->notify = bl_data->notify;
	if (bl_data->notify_after)
		samsung_bl_data->notify_after = bl_data->notify_after;
	if (bl_data->exit)
		samsung_bl_data->exit = bl_data->exit;
	if (bl_data->check_fb)
		samsung_bl_data->check_fb = bl_data->check_fb;

	/* Register the Backlight dev */
	ret = platform_device_register(samsung_bl_device);
	if (ret) {
		printk(KERN_ERR "failed to register backlight device: %d\n", ret);
		goto err_plat_reg2;
	}

	return;

err_plat_reg2:
	kfree(samsung_bl_data);
err_data:
	kfree(samsung_bl_device);
	return;
}






struct samsung_bl_drvdata {
	struct platform_pwm_backlight_data plat_data;
	struct samsung_bl_gpio_info *gpio_info;
};

/* 背光引脚初始化为pwm模式 */
static int samsung_bl_init(struct device *dev)
{
	int ret = 0;
	struct platform_pwm_backlight_data *pdata = dev->platform_data;
	struct samsung_bl_drvdata *drvdata = container_of(pdata,
					struct samsung_bl_drvdata, plat_data);
	struct samsung_bl_gpio_info *bl_gpio_info = drvdata->gpio_info;

	ret = gpio_request(bl_gpio_info->no, "Backlight");
	if (ret) {
		printk(KERN_ERR "failed to request GPIO for LCD Backlight\n");
		return ret;
	}

	/* Configure GPIO pin with specific GPIO function for PWM timer */
	s3c_gpio_cfgpin(bl_gpio_info->no, bl_gpio_info->func);

	return 0;
}

/* 背光引脚初始化为输出模式 */
static void samsung_bl_exit(struct device *dev)
{
	struct platform_pwm_backlight_data *pdata = dev->platform_data;
	struct samsung_bl_drvdata *drvdata = container_of(pdata,
					struct samsung_bl_drvdata, plat_data);
	struct samsung_bl_gpio_info *bl_gpio_info = drvdata->gpio_info;

	s3c_gpio_cfgpin(bl_gpio_info->no, S3C_GPIO_OUTPUT);
	gpio_free(bl_gpio_info->no);
}

下面是平台驱动部分


static const struct dev_pm_ops pwm_backlight_pm_ops = {
#ifdef CONFIG_PM_SLEEP
	.suspend = pwm_backlight_suspend,
	.resume = pwm_backlight_resume,
	.poweroff = pwm_backlight_suspend,
	.restore = pwm_backlight_resume,
#endif
};



static struct platform_driver pwm_backlight_driver = {
	.driver		= {
		.name		= "pwm-backlight",
		.owner		= THIS_MODULE,
		.pm		= &pwm_backlight_pm_ops,       /* 电源管理这里不细说 */
		.of_match_table	= of_match_ptr(pwm_backlight_of_match),
	},
	.probe		= pwm_backlight_probe,
	.remove		= pwm_backlight_remove,
	.shutdown	= pwm_backlight_shutdown,
};

module_platform_driver(pwm_backlight_driver);

static int pwm_backlight_probe(struct platform_device *pdev)
{
    /* 拿到平台设备数据 */
	struct platform_pwm_backlight_data *data = dev_get_platdata(&pdev->dev);
	struct platform_pwm_backlight_data defdata;
	struct backlight_properties props;
	struct backlight_device *bl;
	struct pwm_bl_data *pb;
	int ret;

    /* 如果没有平台设备的话,则从设备树中查找 */
	if (!data) {
		ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
		if (ret < 0) {
			dev_err(&pdev->dev, "failed to find platform data\n");
			return ret;
		}

		data = &defdata;
	}

    /* 初始化pwm的gpio模式 */
	if (data->init) {
		ret = data->init(&pdev->dev);
		if (ret < 0)
			return ret;
	}

    /* 申请一个背光结构体 */
	pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL);
	if (!pb) {
		ret = -ENOMEM;
		goto err_alloc;
	}

	if (data->levels) {        /* 我们没背光等级数组 */
		unsigned int i;

		for (i = 0; i <= data->max_brightness; i++)
			if (data->levels[i] > pb->scale)
				pb->scale = data->levels[i];

		pb->levels = data->levels;
	} else
		pb->scale = data->max_brightness;        /*  */

    /* 参数从dev拷贝的drv */
	pb->notify = data->notify;
	pb->notify_after = data->notify_after;
	pb->check_fb = data->check_fb;
	pb->exit = data->exit;
	pb->dev = &pdev->dev;
	pb->enabled = false;    /* 默认没使能 */

    /* 标记gpio使能 */
	pb->enable_gpio = devm_gpiod_get(&pdev->dev, "enable");
	if (IS_ERR(pb->enable_gpio)) {
		ret = PTR_ERR(pb->enable_gpio);
		if (ret == -ENOENT)
			pb->enable_gpio = NULL;
		else
			goto err_alloc;
	}

	/*
	 * Compatibility fallback for drivers still using the integer GPIO
	 * platform data. Must go away soon.
	 */
	if (!pb->enable_gpio && gpio_is_valid(data->enable_gpio)) {
		ret = devm_gpio_request_one(&pdev->dev, data->enable_gpio,
					    GPIOF_OUT_INIT_HIGH, "enable");
		if (ret < 0) {
			dev_err(&pdev->dev, "failed to request GPIO#%d: %d\n",
				data->enable_gpio, ret);
			goto err_alloc;
		}
        
		pb->enable_gpio = gpio_to_desc(data->enable_gpio);
	}

    /* gpio先关处理 */
	if (pb->enable_gpio)
		gpiod_direction_output(pb->enable_gpio, 1);

	pb->power_supply = devm_regulator_get(&pdev->dev, "power");
	if (IS_ERR(pb->power_supply)) {
		ret = PTR_ERR(pb->power_supply);
		goto err_alloc;
	}

    /* 申请这个pwm作为背光 */
	pb->pwm = devm_pwm_get(&pdev->dev, NULL);
	if (IS_ERR(pb->pwm)) {
		dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");

		pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
		if (IS_ERR(pb->pwm)) {
			dev_err(&pdev->dev, "unable to request legacy PWM\n");
			ret = PTR_ERR(pb->pwm);
			goto err_alloc;
		}
	}

	dev_dbg(&pdev->dev, "got pwm for backlight\n");

	/*
	 * The DT case will set the pwm_period_ns field to 0 and store the
	 * period, parsed from the DT, in the PWM device. For the non-DT case,
	 * set the period from platform data if it has not already been set
	 * via the PWM lookup table.
	 */
	pb->period = pwm_get_period(pb->pwm);        /* 得到pwm的周期 */
	if (!pb->period && (data->pwm_period_ns > 0)) {
		pb->period = data->pwm_period_ns;
		pwm_set_period(pb->pwm, data->pwm_period_ns);    /* 设置pwm的周期 */
	}

	pb->lth_brightness = data->lth_brightness * (pb->period / pb->scale);

	memset(&props, 0, sizeof(struct backlight_properties));
	props.type = BACKLIGHT_RAW;
	props.max_brightness = data->max_brightness;        /* 最大亮度 */
	bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
				       &pwm_backlight_ops, &props);        /* 注册背光 */
	if (IS_ERR(bl)) {
		dev_err(&pdev->dev, "failed to register backlight\n");
		ret = PTR_ERR(bl);
		goto err_alloc;
	}

    /* 默认背光不能大于最大背光亮度 */
	if (data->dft_brightness > data->max_brightness) {
		dev_warn(&pdev->dev,
			 "invalid default brightness level: %u, using %u\n",
			 data->dft_brightness, data->max_brightness);
		data->dft_brightness = data->max_brightness;
	}

	bl->props.brightness = data->dft_brightness;
	backlight_update_status(bl);        /* 更新背光亮度 */

	platform_set_drvdata(pdev, bl);        /* dev和drv绑定 */
	return 0;

err_alloc:
	if (data->exit)
		data->exit(&pdev->dev);
	return ret;
}

static int pwm_backlight_remove(struct platform_device *pdev)
{
	struct backlight_device *bl = platform_get_drvdata(pdev);
	struct pwm_bl_data *pb = bl_get_data(bl);

	backlight_device_unregister(bl);        /* 卸载背光设备 */
	pwm_backlight_power_off(pb);        /* 关背光pwm */

	if (pb->exit)
		pb->exit(&pdev->dev);

	return 0;
}

static void pwm_backlight_shutdown(struct platform_device *pdev)
{
	struct backlight_device *bl = platform_get_drvdata(pdev);
	struct pwm_bl_data *pb = bl_get_data(bl);

	pwm_backlight_power_off(pb);        /* 关背光pwm */
}

这里主要有两点要注意,一个是背光驱动模型和pwm一样也做成了类的形式。

另一点是这里注册时用的fops

struct backlight_ops {
	unsigned int options;

#define BL_CORE_SUSPENDRESUME	(1 << 0)

	/* Notify the backlight driver some property has changed */
	int (*update_status)(struct backlight_device *);
	/* Return the current backlight brightness (accounting for power,
	   fb_blank etc.) */
	int (*get_brightness)(struct backlight_device *);
	/* Check if given framebuffer device is the one bound to this backlight;
	   return 0 if not, !=0 if it is. If NULL, backlight always matches the fb. */
	int (*check_fb)(struct backlight_device *, struct fb_info *);
};




static const struct backlight_ops pwm_backlight_ops = {
	.update_status	= pwm_backlight_update_status,
	.get_brightness	= pwm_backlight_get_brightness,
	.check_fb	= pwm_backlight_check_fb,
};

static void pwm_backlight_power_on(struct pwm_bl_data *pb, int brightness)
{
	int err;

	if (pb->enabled)
		return;

	err = regulator_enable(pb->power_supply);        /* 使能调节器 */
	if (err < 0)
		dev_err(pb->dev, "failed to enable power supply\n");

	if (pb->enable_gpio)
		gpiod_set_value(pb->enable_gpio, 1);        /* 使能gpio口(可能睡眠了) */

	pwm_enable(pb->pwm);        /* 可以看到还是调用的pwm使能 */
	pb->enabled = true;
}

static void pwm_backlight_power_off(struct pwm_bl_data *pb)
{
	if (!pb->enabled)
		return;

	pwm_config(pb->pwm, 0, pb->period);    /* 占空比设置为0 */
	pwm_disable(pb->pwm);        /* 关pwm */

	if (pb->enable_gpio)
		gpiod_set_value(pb->enable_gpio, 0);    /* 睡眠gpio */

	regulator_disable(pb->power_supply);        /* 关调节器 */
	pb->enabled = false;
}

/* 用要设置的亮度和已知周期,计算占空比的时间 */
static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness)
{
	unsigned int lth = pb->lth_brightness;
	int duty_cycle;

	if (pb->levels)
		duty_cycle = pb->levels[brightness];
	else
		duty_cycle = brightness;

	return (duty_cycle * (pb->period - lth) / pb->scale) + lth;
}

/* 用最新数据配置pwm控制器 */
static int pwm_backlight_update_status(struct backlight_device *bl)
{
	struct pwm_bl_data *pb = bl_get_data(bl);
	int brightness = bl->props.brightness;
	int duty_cycle;

	if (bl->props.power != FB_BLANK_UNBLANK ||
	    bl->props.fb_blank != FB_BLANK_UNBLANK ||
	    bl->props.state & BL_CORE_FBBLANK)
		brightness = 0;

	if (pb->notify)
		brightness = pb->notify(pb->dev, brightness);

	if (brightness > 0) {
		duty_cycle = compute_duty_cycle(pb, brightness);
		pwm_config(pb->pwm, duty_cycle, pb->period);
		pwm_backlight_power_on(pb, brightness);
	} else
		pwm_backlight_power_off(pb);

	if (pb->notify_after)
		pb->notify_after(pb->dev, brightness);

	return 0;
}

static int pwm_backlight_get_brightness(struct backlight_device *bl)
{
	return bl->props.brightness;
}

static int pwm_backlight_check_fb(struct backlight_device *bl,
				  struct fb_info *info)
{
	struct pwm_bl_data *pb = bl_get_data(bl);

	return !pb->check_fb || pb->check_fb(pb->dev, info);
}

可以看到上面的pwm调节背光的还是依赖的pwm的class中chip中的pwm来调光的。

同时,同最开始背光设备是依赖pwm,可以根据设备传过来的pwm号,直接通过parent找到具体的pwm来控制。

背光类可想而知也是和pwm类近似,也是做了好多的attribute,用来使用户层和驱动层交互。

而我们上面提供的几个钩子函数,比如计算亮度和占空比所占时间函数,比如把最新计算或设置好的参数更新到硬件寄存器等函数,则是背光类中atteibute操作接口里面调用的核心函数。

总共代码也就五百行,这里就不再具体分析。

下面就列出操纵pwm亮度的方法

因为我们的pwm接的的背光的负极,所以参数越小背光越亮。

本节暂时结束framebuffer相关的底层驱动分析了。

至于tty相关的驱动,预计我还得再升入学习一下其他驱动后,再结合input,fb,uart等驱动一起分析。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/83833734