1. 程式人生 > >高通平臺GPIO模擬PWM控制背光

高通平臺GPIO模擬PWM控制背光

    很多時候由於節省硬體資源,降低成本,會把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