很多時候由於節省硬件資源,降低成本,會把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 <linux/clk.h>
#include <soc/qcom/clock-local2.h>
#include <linux/io.h>
... ...
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 <linux/io.h>
#include <soc/qcom/clock-local2.h>
#include <linux/clk.h>
... ...
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