如何在qualcomm 8960和8921上使用PWM功能 (GPIO24)

来源:互联网 发布:临沂有淘宝交流 编辑:程序博客网 时间:2024/04/30 02:17
一、PWM 分析

1. PWM: Pulse Width Modulator; LPG: Light Pulse Generator; 两者指的是同一东西。

2. PWM driver: /kernel/drivers/mfd/pm8xxx-pwm.c

3. 手机中注册路径: /sys/devices/platform/msm_ssbi.0/pm8921-core/pm8xxx-pwm
4. 利用debugfs调试
    a. #mkdir /data/debugfs
    b. #mount -t debugfs none /data/debugfs
    c. #cd /data/debugfs/pmx8xxx-pwm-dbg
    d. #ls 可以看到共有8路pwm可用
    e. #cd 0; ls
        duty-cycle    //占宽比 (0,100)之间
        enable
        period        //输出波周期 [7, 327*1000000L]微秒之间
    f. 需要先设置period, #echo 50 > period
    g. #echo 50 > duty-cycle

    h. #echo 1 > enable       


二、调查完成,所需要修改的代码
1. @kernel/arch/arm/mach-msm/board-semc_blue_cdb.c, 设置PM8921的GPIO工作在PWM function
    static struct pm8xxx_gpio_init pm8921_gpios[] __initdata = {
        ...
        //PM8XXX_GPIO_DISABLE(24),
        PM8XXX_GPIO_INIT(    24,
                    PM_GPIO_DIR_OUT,
                    PM_GPIO_OUT_BUF_CMOS,
                    1,
                    PM_GPIO_PULL_NO,
                    PMIC_GPIO_VIN0,
                    PM_GPIO_STRENGTH_HIGH,
                    PM_GPIO_FUNC_2,
                    1,
                    0
                ),    

        ...
    }
2. @kernel/arch/arm/mach-msm/board-semc_blue.c, 设置PWM/LPG 的bank1 可用,该bank1 对应的是GPIO24
    static struct pm8xxx_pwm_platform_data pm8xxx_pwm_pdata = {
        //.dtest_channel    = PM8XXX_PWM_DTEST_CHANNEL_NONE,
        .dtest_channel    = 0,  
    };
3. 进入手机adb shell
    a. #mount -t debugfs none /data/debugfs
    b. #cd data/debugfs/ pm8xxx-pwm-dbg/0
    c. #echo 2000 > period //必须先设置period, 单位为usecond, 区间为[7, 327*1000000L]us
    d. #echo 50 > duty-cycle //占空比 (0,100)之间
    e. #echo 1 > enable //现在有LPG 方波输出    
    f. #echo 1 > enable //取消 LPG 方波输出    

三、如何检测PM8921 GPIO 的状态
1. 利用手机系统中 已有 /sys/kernel/debug/pm8921-dbg ,gpio检测接口
    a. #echo 0x??? > addr //输入GPIO register addrress
    b. #echo 0x??? > data //输入控制GPIO,写入register 的值
    c. #cat data //查看当前bank输入的值
2. 查看PM8921 register information 文档
    a. Function,Sub funtion,Register name,Register Address,Type,Reset code,default value,comments
        GPIO,   GPIO_24,     GPIO_CNTRL,    0x167,        8B_WR, 5,    00865A01,    Power-off and Power-on default state is Hi-Z
    b. 所以GPIO24的 Register Address 是0x167
3. 检测GPIO24的配置状态,先设置 bank0-7,再读取在该bank设置的参数
    #echo 0x167 > addr
    echo 0x0 > data //选择bank0
    cat data --> 0x01 //得到在bank0 设置的参数
    echo 0x10 > data
    cat data -->0x18
    echo 0x20> data
    cat data -->0x2A
    echo 0x30 > data
    cat data -->0x34
    echo 0x40 > data
    cat data -->0x46
    echo 0x50 > data
    cat data
    echo 0x60 > data
    cat data
    echo 0x70 > data
    cat data
    直接设置某个bank的参数
    #echo 0x167 > addr
    #echo 0xc6 > data //各bit含义: [7]->(1W,0R),[6:4]->Bank Select, [3:0]->各bank含义不一样
    所以0xC6表示0b11000110, 写入,bank4,写入的参数为0110

四,如何用GPIO24模拟 PWM 输出,主要修改kernel/drivers/mfd/pm8xxx-pwm.c

#define TEST_PWM

#ifdef TEST_PWM
#include <linux/mfd/pm8xxx/gpio.h>
#include <linux/delay.h>
#endif


struct pm8xxx_pwm_dbg_device {
    struct mutex        dbg_mutex;
    struct device        *dev;
    struct dentry        *dent;

    struct pm8xxx_pwm_user    *user;

#ifdef TEST_PWM
    struct work_struct pwm_work;
    struct workqueue_struct *pwm_wq;
    bool pwm_run;
#endif    

};


#ifdef TEST_PWM
static int dbg_pwm_value_set(void *data, u64 val)
{
    struct pm8xxx_pwm_user      *puser = data;
    struct pm8xxx_pwm_dbg_device    *dbgdev = puser->dbgdev;
    int gpio_sys = 175;//PM8921_GPIO_PM_TO_SYS(24);

    mutex_lock(&dbgdev->dbg_mutex);

    gpio_set_value_cansleep(gpio_sys, val);
        
    mutex_unlock(&dbgdev->dbg_mutex);

    pr_debug("#### value = %d\n",(int)val);
    
    return 0;
}

static int dbg_pwm_value_get(void *data, u64 *val)
{
    struct pm8xxx_pwm_user      *puser = data;
    struct pm8xxx_pwm_dbg_device    *dbgdev = puser->dbgdev;
    int gpio_sys = 175;//PM8921_GPIO_PM_TO_SYS(24);

    mutex_lock(&dbgdev->dbg_mutex);
    
    *val =gpio_get_value_cansleep(gpio_sys);

    mutex_unlock(&dbgdev->dbg_mutex);

    pr_debug("#### value = %d\n",(int)*val);

    return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(dbg_pwm_value_fops,
            dbg_pwm_value_get, dbg_pwm_value_set, "%lld\n");

static void pwm_instant_work(struct work_struct *work)
{
    int gpio_sys = 175;//PM8921_GPIO_PM_TO_SYS(24);
    struct pm8xxx_pwm_dbg_device  *dbgdev= container_of(work, struct pm8xxx_pwm_dbg_device, pwm_work);
    struct pm8xxx_pwm_user      *puser;
    int  period;
    int  duty_cycle;
    int  high_us, low_us;
    
    pr_debug(" ### start output signal\n");

    puser = dbgdev->user;
    period = puser->period;
    duty_cycle = puser->duty_cycle;

    if( !period || !duty_cycle )
        return;
    
    high_us = period * duty_cycle /100;
    low_us = period - high_us;
    pr_debug("### hight_us = %d, low_us = %d \n ", high_us, low_us);
    
    while(dbgdev->pwm_run)
    {
        gpio_set_value_cansleep(gpio_sys, 1);
        //usleep(high_us);
        udelay(high_us);
        
        gpio_set_value_cansleep(gpio_sys, 0);
        //usleep(low_us);
        udelay(low_us);
    }
}

static int dbg_pwm_output_set(void *data, u64 val)
{
    struct pm8xxx_pwm_user      *puser = data;
    struct pm8xxx_pwm_dbg_device    *dbgdev = puser->dbgdev;

    mutex_lock(&dbgdev->dbg_mutex);

    pr_debug("#### value = %d\n",(int)val);

    if( val )
    {
        dbgdev->pwm_run = true;
        queue_work(dbgdev->pwm_wq, &dbgdev->pwm_work);
    }
    else
    {
        dbgdev->pwm_run = false;
    }

    mutex_unlock(&dbgdev->dbg_mutex);

    return 0;
}

static int dbg_pwm_output_get(void *data, u64 *val)
{
    struct pm8xxx_pwm_user      *puser = data;
    struct pm8xxx_pwm_dbg_device    *dbgdev = puser->dbgdev;

    mutex_lock(&dbgdev->dbg_mutex);
    
    *val = dbgdev->pwm_run;

    mutex_unlock(&dbgdev->dbg_mutex);

    return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(dbg_pwm_output_fops,
            dbg_pwm_output_get, dbg_pwm_output_set, "%lld\n");
#endif


static int __devinit pm8xxx_pwm_dbg_probe(struct device *dev)

{

     ....

        if (temp == NULL || IS_ERR(temp)) {
            pr_err("ERR: pwm=%d: enable: dent=%p\n", i, dent);
            rc = -ENOMEM;
            goto debug_error;
        }
#ifdef TEST_PWM
        temp = debugfs_create_file("value", S_IRUGO | S_IWUSR,
                        dent, puser, &dbg_pwm_value_fops);
        if (temp == NULL || IS_ERR(temp)) {
            pr_err("ERR: pwm=%d: enable: dent=%p\n", i, dent);
            rc = -ENOMEM;
            goto debug_error;
        }

        temp = debugfs_create_file("output", S_IRUGO | S_IWUSR,
                        dent, puser, &dbg_pwm_output_fops);
        if (temp == NULL || IS_ERR(temp)) {
            pr_err("ERR: pwm=%d: enable: dent=%p\n", i, dent);
            rc = -ENOMEM;
            goto debug_error;
        }
#endif

    ....


    pmic_dbg_device = dbgdev;

#ifdef TEST_PWM
    dbgdev->pwm_wq = create_singlethread_workqueue("pwm_wq");
    INIT_WORK(&dbgdev->pwm_work, pwm_instant_work);
#endif    


    return 0;

   ....

}