很多时候由于节省硬件资源,降低成本,会把PWM控制芯片去掉或者是改做它用,导致当我们想用PWM方式控制背光时只能使用带有clk功能的GPIO口。本篇文档就来讲解下如何使用GPIO模拟PWM功能进行背光的控制。本文以MSM8909为例。
一、选取GPIO口并进行配置
1、需要查看寄存器手册,选取对应的具有GP_CLK功能的GPIO口——gpio49。
2、我们需要先看下,该管脚是否被其他模块应用。查看配置信息,发现该管脚被默认用作了UIM2。我们要先关闭UIM模块对其的操作,如果你仅仅负责BSP领域,那么请找对应modem的同事帮忙把对这个脚的使用关掉。
3、接下来就是将该管脚配置为clk模式。
3.1 dtsi中添加节点
kernel/msm-3.18/arch/arm/boot/dts/qcom/msm8909-pinctrl.dtsi
gpio_pwm_default: gpio_pwm_default {
mux {
pins = "gpio49";
function = "gcc_gp1_clk_a";
};
config {
pins = "gpio49";
drive-strength = <16>;
bias-disable;
};
};
3.2 定义设备节点
kernel/msm-3.18/arch/arm/boot/dts/qcom/msm8909-mdss.dtsi
beeper: beeper {
compatible = "gpio-beeper";
pinctrl-names = "default";
pinctrl-0 = <&gpio_pwm_default>;
clocks = <&clock_gcc clk_gp1_clk_src>;
clock-names = "gpio-pwm-clk";
};
二、配置时钟信息
既然我们作为CLK使用,必然需要配置其对应的频率。
kernel/msm-3.18/drivers/clk/msm/clock-gcc-8909.c
static struct clk_freq_tbl ftbl_gcc_gp1_3_clk[] = {
F( 150000, xo, 1, 1, 128),
F( 19200000, xo, 1, 0, 0),
F( 9375, xo, 16, 1, 128),//添加对应的频率信息
F_END
};
分别解析一下对应的这几个值的信息:9375——对应的clk频率,xo——时钟源,16——SRC_DIV,1——M,128——N。
三、驱动适配
添加设备初始化信息 kernel/msm-3.18/drivers/video/msm/mdss/mdss_dsi.c
#include
#include
#include
... ...
struct clk *pclk;
struct rcg_clk *gp1_rcg_clk;
extern void mdss_set_gpio_pwm(int level);
static int mdss_dsi_bl_probe(struct platform_device *pdev)
{
int ret;
int level = 50;
if (!pdev || !pdev->dev.of_node) {
pr_err("%s: pdev not found for DSI controller\n", __func__);
return -ENODEV;
}
pclk = devm_clk_get(&pdev->dev, "gpio-pwm-clk");//获取clk信息
ret = clk_set_rate(pclk, 9375);//设置clk频率
if (ret){
printk("clk set rate fail, ret = %d\n", ret);
}
ret = clk_prepare_enable(pclk);//使能clk
if (ret){
printk("%s: clk_prepare error!!!\n", __func__);
}else{
printk("%s: clk_prepare success!\n", __func__);
}
gp1_rcg_clk = to_rcg_clk(pclk);
mdss_set_gpio_pwm(level);//设置背光,个人添加的方法
return 0;
}
static const struct of_device_id mdss_dsi_bl_dt_match[] = {
{.compatible = "gpio-beeper"},
{}
};
MODULE_DEVICE_TABLE(of, mdss_dsi_bl_dt_match);
static struct platform_driver mdss_dsi_bl_driver = {
.probe = mdss_dsi_bl_probe,
.shutdown = NULL,
.driver = {
.name = "beeper",
.of_match_table = mdss_dsi_bl_dt_match,
},
};
static int mdss_dsi_bl_register_driver(void)
{
return platform_driver_register(&mdss_dsi_bl_driver);
}
static int __init mdss_dsi_bl_driver_init(void)
{
int ret;
ret = mdss_dsi_bl_register_driver();
if (ret) {
pr_err("mdss_dsi_bl_register_driver() failed!\n");
return ret;
}
return ret;
}
fs_initcall(mdss_dsi_bl_driver_init);
自定义方法,该方法作为设置背光等级 kernel/msm-3.18/drivers/video/msm/mdss/mdss_dsi_panel.c
#include
#include
#include
... ...
extern struct clk *pclk;
extern struct rcg_clk *gp1_rcg_clk;
#define CBCR_OFFSET 0x0
#define CMD_RCGR_OFFSET 0x4
#define D_OFFSET 0x14
#define GP1_CLK_BASE 0x8000
void mdss_set_gpio_pwm(int level)
{
int ret;
ret = clk_set_rate(pclk, 9375);
if (ret){
printk("clk set rate fail, ret = %d\n", ret);
}
ret = clk_prepare_enable(pclk);
if (ret){
printk("%s: clk_prepare error!!!\n", __func__);
}else{
printk("%s: clk_prepare success!\n", __func__);
}
gp1_rcg_clk = to_rcg_clk(pclk);
if(level == 0){
mb();
writel_relaxed(0x0, *gp1_rcg_clk->base + GP1_CLK_BASE + CBCR_OFFSET);//根据寄存器手册,该寄存器代表clk是否使能,写1代表enable,写0表示disable。当level值为0时将clk关闭。
}else{
writel_relaxed(((~(128 * level / 50)) & 0x0ff), *gp1_rcg_clk->base + GP1_CLK_BASE + D_OFFSET ); //128 is the value of N, if just output clock, please remove this line.
//此处的level即为对应的等级,这里只能为1-100,所以我们传值时要对0-255进行转换再使用。另外注意前面有取反的符号,这个是高通定死的,我们写的背光等级是对应值取反。
mb();
writel_relaxed(0x3, *gp1_rcg_clk->base+ GP1_CLK_BASE + CMD_RCGR_OFFSET); //RCGR
mb();
writel_relaxed(0x1, *gp1_rcg_clk->base + GP1_CLK_BASE + CBCR_OFFSET); //CBCR
}
}
添加BL_GPIO控制选项 kernel/msm-3.18/drivers/video/msm/mdss/mdss_dsi.h
enum dsi_panel_bl_ctrl {
BL_PWM,
BL_WLED,
BL_DCS_CMD,
BL_GPIO, //gpio控制选项
UNKNOWN_CTRL,
};
根据qcom,mdss-dsi-bl-pmic-control-type选择对应的控制方式:
int mdss_panel_parse_bl_settings(struct device_node *np,
struct mdss_dsi_ctrl_pdata *ctrl_pdata)
{
... ...
}else if (!strcmp(data, "bl_ctrl_gpio")) {
ctrl_pdata->bklt_ctrl = BL_GPIO;
} else if (!strcmp(data, "bl_ctrl_pwm")) {
ctrl_pdata->bklt_ctrl = BL_PWM;
ctrl_pdata->pwm_pmi = of_property_read_bool(np,
"qcom,mdss-dsi-bl-pwm-pmi");
... ...
}
在背光控制方法中根据控制方式添加对应的背光控制函数:
static void mdss_dsi_panel_bl_ctrl(struct mdss_panel_data *pdata,
u32 bl_level)
{
... ...
case BL_PWM:
mdss_dsi_panel_bklt_pwm(ctrl_pdata, bl_level);
break;
case BL_GPIO:
bl_level = 100*bl_level/255;//将0-255等级划分为0-100
if(bl_level>99){
bl_level = 99;
}
mdss_set_gpio_pwm(bl_level);
break;
... ...
}
此时我们已经设配完成,已经可以进行正常的背光亮度调节,以及量灭屏操作。
四、修改占空比
首先看下占空比公式:D/N
假设我们设置的F(100000, xo, 3, 1, 64),那么M = 1 N = 64 D = 0.5。当N为128时,D最小也可为0.5。此时我们可以看到占空比可以为1-128个等级。
M = 1 M = 0x1
N = 64 NOT_N_M = 0xFFC0
D = 0.5 NOT_2D = 0xFFFE
Resulting Clock = 0.100 MHz
Error = 0.000 MHz
Duty Cycle = 0.78%
Resulting Jitter = 0.000 ns
Period = 10000.000 ns
Pulse High = 78.125 ns
Pulse Low = 9921.875 ns