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 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
#include
#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;
....
}