=======pwm频率修改
当前ALPS branch上,disp_pwm driver采用的是turnkey code,不同芯片型号,或不同branch,disp_pwm频率设定可能会有差异,因此客户有时会遇到如下问题:
(1)如何修改lk和kernel下disp_pwm频率?
(2)如何修改disp_pwm clock source?
(3)为什么修改dts无效?
首先 介绍下pwm
[SOLUTION]
对于以上几种问题,归根结底就是:disp_pwm频率如何设定的问题。下面就概括一下,当前遇到的设定disp_pwm频率(fre)的方法。
1、Disp_PWM频率计算方法
fre=source clock/(divider+1)/(period+1)
source clock:时钟源,26MHz/104MHz/125MHz/62.5MHz/15.625/……,与IC design有关。若没有特别说明,一般是26MHz
divider:对source clock除频
period:disp_pwm周期,默认是1023
因此,想达到特定的fre,只需要适当的修改source、divider参数即可(period一般不建议修改)
2、disp_pwm频率代码位置
lk: ddp_pwm.c/disp_pwm_init()
kernel: ddp_pwm.c/disp_pwm_config_init()
3、dts配置方式方式
若lk & kernel都从dts读取disp_pwm参数,则只需针对性修改dts即可
led6:led@6 {
compatible = "mediatek,lcd-backlight";
led_mode = <5>;
data = <1>;
pwm_config = <0 0 0 0 0>;
};
pwm_config参数:clock source、divider、low_duration、high_duration、pmic_pad。与dts配置中pwm_config = <0 0 0 0 0>参数一一对应。只需关注前面2个参数即可
如上设定:clock source=26MHz,divider=0
fre=26MHz/1/1024=25.29KHz
3、不使用dts
divider: PWM_DEFAULT_DIV_VALUE ///设定成你需要的值
CLK_CFG_1 bit[2:0]:0/1/2/…… ///选择不同的source
所以遇到频率无法修改成功的问题,先看是使用dts配置,还是disp_pwm driver init时hardcode写死,再相对应的修改设定。
5、kernel下source clock切换问题
【MT8788】ALPS04829940
说明:MT8788(MT8183,Sylvia)芯片上,disp_pwm source有如下5种:
0x100000B0 CLK_CFG_7 | bit[2:0] | 0 | clk26m | 26MHz | |||||||
1 | univpll_d3_d4 | 104MHz | |||||||||
2 | osc_d2 | 125MHz | |||||||||
3 | osc_d4 | 62.5MHz | |||||||||
4 | osc_d16 | 15.625MHz |
kernel下使用dts设定,ddp_pwm.c/disp_pwm_config_init(),会调用disp_pwm_set_pwmmux()函数对如上的5种clock source进行重新mapping,使用mapping后的clock ID进行配置。8788上我司目前使用的CLK_CFG_7
例如,想设定source=26MHz,则dts中应该这样填写:pwm_config = <4 0 0 0 0>;
寄存器定义 | mapping后的clock ID | clock source | 预期clk(mhz) | ||||||
bit[2:0]=0 | 4 | clk26m | 26 | ||||||
bit[2:0]=1 | 3 | univpll_d3_d4 | 104 | ||||||
bit[2:0]=2 | 2 | osc_d2 | 125 | ||||||
bit[2:0]=3 | 1 | osc_d4 | 62.5 | ||||||
bit[2:0]=4 | 0 | osc_d16 | 15.625 |
=======pwm背光亮度调节
mtk方案并没有使用原生.level = EARLY_SUSPEND_LEVEL_DISABLE_FB去设置亮度,而是通过直接写属性节点brightness的方式,hal层通过读写此节点从而控制亮度
想要知道原生亮度控制方式可以参考此此链接:https://www.cnblogs.com/reality-soul/p/4757728.html
1 hal层控制
节点路径:sys/devices/platform/leds-mt65xx/leds/lcd-backlight/brightness
上层调用流程课参考http://www.52rd.com/Blog/Detail_RD.Blog_apu981_69251.html,此博客已做了解释
2 kernel如何响应亮度
首先看看驱动层brightness设备节点如何创建的
kernel-4.4/drivers/leds/led-class.c模块中会执行static DEVICE_ATTR_RW(brightness);这里要说下这个DEVICE_ATTR_RW宏
#define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
struct device_attribute dev_attr_(wakealarm) = __ATTR_RW(wakealarm);
#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO), _name##_show, _name##_store)
struct device_attribute dev_attr_(wakealarm) = __ATTR(wakealarm, (S_IWUSR | S_IRUGO),wakealarm_show, wakealarm_store)
#define __ATTR(_name, _mode, _show, _store) {
.attr = {.name = __stringify(_name),
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) },
.show = _show,
.store = _store,
}
struct device_attribute dev_attr_(wakealarm) = {
.attr = {.name = __stringify(wakealarm),
.mode = VERIFY_OCTAL_PERMISSIONS((S_IWUSR | S_IRUGO)) },
.show = wakealarm_show,
.store = wakealarm_store,
}
最终这个宏 static DEVICE_ATTR_RW(wakealarm); 生成了
struct device_attribute dev_attr_(wakealarm) = {
.attr = {.name = __stringify(wakealarm),
.mode = VERIFY_OCTAL_PERMISSIONS((S_IWUSR | S_IRUGO)) },
.show = wakealarm_show,
.store = wakealarm_store,
}
由此可以知道DEVICE_ATTR_RW(brightness)也会去指定一个store函数即brightness_store,在写brightness时会调用brightness_store,一搜果然存在
static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
unsigned long state;
ssize_t ret;
mutex_lock(&led_cdev->led_access);
if (led_sysfs_is_disabled(led_cdev)) {
ret = -EBUSY;
goto unlock;
}
ret = kstrtoul(buf, 10, &state);/*上层写数据时会保存到buf里*/
if (ret)
goto unlock;
if (state == LED_OFF)
led_trigger_remove(led_cdev);
led_set_brightness(led_cdev, state);/*当上层写节点时会调用led_set_brightness,我们的brightness也会传下去*/
ret = size;
unlock:
mutex_unlock(&led_cdev->led_access);
return ret;
}
接下来led_set_brightness(leds/led-core.c) 会调用led_set_brightness_async(led_cdev, brightness)这里面还涉及到soft-blink可以不管,我们主要分析亮如何传进去的,以及驱动如何把0-255的亮度转化成pwm的占空比。接下来看下led_set_brightness_async干了啥
//led_set_brightness_async定义在leds.h里
static inline void led_set_brightness_async(struct led_classdev *led_cdev,
enum led_brightness value)
{
value = min(value, led_cdev->max_brightness);
led_cdev->brightness = value;
if (!(led_cdev->flags & LED_SUSPENDED))
led_cdev->brightness_set(led_cdev, value);//接着brightness_set,这个
//brightness_set在哪儿定义
}
想要知道brightness_set如何来的就要看kernel-4.4/driver/misc/mediatek/leds/mtk_leds_drv.c中的probe函数
static int mt65xx_leds_probe(struct platform_device *pdev){
int i;
int ret;/* rc; */
struct cust_mt65xx_led *cust_led_list = mt_get_cust_led_list();
if (cust_led_list == NULL) {
LEDS_DRV_INFO("%s: get dts fail.\n", __func__);
return -1;
}
................//省略中间代码
g_leds_data[i]->cust.mode = cust_led_list[i].mode;
g_leds_data[i]->cust.data = cust_led_list[i].data;
g_leds_data[i]->cust.name = cust_led_list[i].name;
g_leds_data[i]->cdev.name = cust_led_list[i].name;
g_leds_data[i]->cust.config_data = cust_led_list[i].config_data;
g_leds_data[i]->cdev.brightness_set = mt65xx_led_set;//brightness_set实际上
//是调用的
//mt65xx_led_set,不清楚的需要去了解下Linux设备与驱动
g_leds_data[i]->cdev.blink_set = mt65xx_blink_set;
INIT_WORK(&g_leds_data[i]->work, mt_mt65xx_led_work);
ret = led_classdev_register(&pdev->dev, &g_leds_data[i]->cdev);
}
mt65xx_led_set接下来会调用mt_mt65xx_led_set 进而调用mt_mt65xx_led_set_cust(misc/mediatek/leds/mt6771/mtk_leds.c)
//这个函数里重点讲下上层0-255级数如何跟驱动pwm级数(0-1024)发生联系的
void mt_mt65xx_led_set(struct led_classdev *led_cdev, enum led_brightness level)
{
struct mt65xx_led_data *led_data =
container_of(led_cdev, struct mt65xx_led_data, cdev);
/* unsigned long flags; */
/* spin_lock_irqsave(&leds_lock, flags); */
if (disp_aal_is_support() == true) {
if (led_data->level != level) {
led_data->level = level;
if (strcmp(led_data->cust.name, "lcd-backlight") != 0) {
LEDS_DEBUG("Set NLED directly %d at time %lu\n",
led_data->level, jiffies);
schedule_work(&led_data->work);
} else {
if (level != 0
&& level * CONFIG_LIGHTNESS_MAPPING_VALUE < 255) {
level = 1;
} else {
level =
(level * CONFIG_LIGHTNESS_MAPPING_VALUE) /
255;
}
backlight_debug_log(led_data->level, level);
disp_pq_notify_backlight_changed((((1 <<
MT_LED_INTERNAL_LEVEL_BIT_CNT)
- 1) * level +
127) / 255);
//mt_mt65xx_led_set_cust第二个参数就是即将传给驱动的真正值,1《MT_LED_INTERNAL_LEVEL_BIT_CNT是固定的1023,这个计算公式将上层brightness跟pwm(0-1023)对应起来了,至于为什么时驱动中pwm级数为0-1023驱动有涉及,查找一下就知道了
disp_aal_notify_backlight_changed((((1 <<
MT_LED_INTERNAL_LEVEL_BIT_CNT)
- 1) * level +
127) / 255);
}
}
} else {
.............
}
/* spin_unlock_irqrestore(&leds_lock, flags); */
}
/* if(0!=aee_kernel_Powerkey_is_press()) */
/* aee_kernel_wdt_kick_Powkey_api("mt_mt65xx_led_set",WDT_SETBY_Backlight); */
}
int mt_mt65xx_led_set_cust(struct cust_mt65xx_led *cust, int level)
{
#ifdef CONFIG_MTK_PWM
struct nled_setting led_tmp_setting = { 0, 0, 0 };
int tmp_level = level;
unsigned int BacklightLevelSupport =
Cust_GetBacklightLevelSupport_byPWM();
#endif
static bool button_flag;
switch (cust->mode) {//这些mode实在dts中定义的,8788背光使用的是
//MT65XX_LED_MODE_CUST_BLS_PWM
case MT65XX_LED_MODE_GPIO:
LEDS_DEBUG("brightness_set_cust:go GPIO mode!!!!!\n");
return ((cust_set_brightness) (cust->data)) (level);
case MT65XX_LED_MODE_PMIC:
...................// 省略中间代码
case MT65XX_LED_MODE_CUST_BLS_PWM:
if (strcmp(cust->name, "lcd-backlight") == 0)
bl_brightness_hal = level;
#ifdef MET_USER_EVENT_SUPPORT
if (enable_met_backlight_tag())
output_met_backlight_tag(level);
#endif
return ((cust_set_brightness) (cust->data)) (level);//此函数才是这一些列调用
//中最关键的
......................
}
((cust_set_brightness) (cust->data)) (level)这个就是一个函数 指针,使用typedef 定义了,那这个函数到底指向那里了?它指向vendor/.../cust_leds.c定义的cust_mt65xx_led结构体中,如下:
static struct cust_mt65xx_led cust_led_list[MT65XX_LED_TYPE_TOTAL] = {
{"red", MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK3,{255}},
{"green", MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK1,{25}},
{"blue", MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK2,{25}},
{"jogball-backlight", MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
{"keyboard-backlight",MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
{"button-backlight", MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
{"lcd-backlight", MT65XX_LED_MODE_CUST_BLS_PWM, (int)disp_bls_set_backlight,{1,0,0,0,0}},
};//MT65XX_LED_MODE_CUST_BLS_PWM模式下设置会调用disp_bls_set_backlight函数
然后继续调用disp_pwm_set_backlight->disp_pwm_set_backlight_cmdq(/misc/mediatek/video/common/pwm10/ddp_pwm.c)
int disp_pwm_set_backlight_cmdq(enum disp_pwm_id_t id, int level_1024, void *cmdq)
{
#ifndef CONFIG_FPGA_EARLY_PORTING
/* PWM is excluded from FPGA bitfile */
unsigned long reg_base;
int old_pwm;
int index;
int abs_diff;
int max_level_1024;
if ((DISP_PWM_ALL & id) == 0) {
PWM_ERR("[ERROR] disp_pwm_set_backlight_cmdq: invalid PWM ID = 0x%x", id);
return -EFAULT;
}
index = index_of_pwm(id);//id为0选用pwm0输出 1 pwm1输出
/* we have to change backlight after config init or max backlight changed */
old_pwm = atomic_xchg(&g_pwm_backlight[index], level_1024);
if (old_pwm != level_1024 || atomic_cmpxchg(&g_pwm_is_change_state[index], 1, 0) == 1) {
abs_diff = level_1024 - old_pwm;
if (abs_diff < 0)
abs_diff = -abs_diff;
if (old_pwm == 0 || level_1024 == 0 || abs_diff > 64) {
/* To be printed in UART log */
disp_pwm_log(level_1024, MSG_LOG);
if (old_pwm != level_1024) {
/* Print information if backlight is changed */
PWM_NOTICE("disp_pwm_set_backlight_cmdq(id = 0x%x, level_1024 = %d), old = %d",
id, level_1024, old_pwm);
}
} else {
disp_pwm_log(level_1024, MSG_LOG);
}
max_level_1024 = disp_pwm_get_max_backlight(id);
if (level_1024 > max_level_1024)
level_1024 = max_level_1024;
else if (level_1024 < 0)
level_1024 = 0;
level_1024 = disp_pwm_level_remap(id, level_1024);
PWM_NOTICE("disp_pwm_set_backlight_cmdq max_level_1024 : %d\n",max_level_1024);
reg_base = pwm_get_reg_base(id);//读取pwm0的基地址 8788上是1 1 00E000
PWM_NOTICE("disp_pwm_set_backlight_cmdq(reg_base = 0x%x, DISP_PWM_CON_1_OFF = 0x%x), reg = 0x%x",
reg_base, DISP_PWM_CON_1_OFF, reg_base + DISP_PWM_CON_1_OFF);
if (level_1024 > 0) {
printk("[lcm][kernel] (unsigned int)(INREG32(reg32)&~(mask))|(val)-->%d\n",
(unsigned int)(INREG32(reg_base + DISP_PWM_CON_1_OFF)&~(0x1fff << 16))|(level_1024 << 16));
DISP_REG_MASK(cmdq, reg_base + DISP_PWM_CON_1_OFF, level_1024 << 16, 0x1fff << 16);
disp_pwm_set_enabled(cmdq, id, 1);
} else {
/* Avoid to set 0 */
DISP_REG_MASK(cmdq, reg_base + DISP_PWM_CON_1_OFF, 1 << 16, 0x1fff << 16);
//reg_base + DISP_PWM_CON_1_OFF值为1100E01C DISP_PWM_CON_1寄存器
disp_pwm_set_enabled(cmdq, id, 0); /* To save power */
}
DISP_REG_MASK(cmdq, reg_base + DISP_PWM_COMMIT_OFF, 1, ~0);
DISP_REG_MASK(cmdq, reg_base + DISP_PWM_COMMIT_OFF, 0, ~0);
}
if (g_pwm_led_mode == MT65XX_LED_MODE_CUST_BLS_PWM &&
atomic_read(&g_pwm_is_power_on[index]) == 0 && level_1024 > 0) {
/* print backlight once after device resumed */
disp_pwm_backlight_status(id, true);
}
#endif
return 0;
}
//介绍下如何获取pwm寄存器基地址
#define pwm_get_reg_base(id) ((id == DISP_PWM0) ? DISPSYS_PWM0_BASE : DISPSYS_PWM1_BASE)
传下去的id为0,所以返回DISPSYS_PWM0_BASE,看下这个DISPSYS_PWM0_BASE
#define DISPSYS_PWM0_BASE ddp_get_module_va(DISP_MODULE_PWM0)
这个ddp_get_module_va实际上就是从ddp_module结构体中根据module_id获取地址, 贴出来部分ddp_module代码:
static ddp_module ddp_modules[DISP_MODULE_NUM] = {
....................
{DISP_MODULE_PWM0,
DISP_T_PWM,
"pwm0",
0,
&ddp_driver_pwm,
{"mediatek,disp_pwm0",
0x1100e000,
158,
0,
0,
0}
},
{DISP_MODULE_PWM1,
DISP_T_PWM,
"pwm1",
0,
NULL,
{reg_magic,}
},
{DISP_MODULE_CONFIG,
DISP_T_UNKNOWN,
"config",
0,
NULL,
{"mediatek,mmsys_config",
0x14000000,
0,
0,
0,
0}
},
.........................
}
pwm寄存器如下
DISP_REG_MASK(cmdq, reg_base + DISP_PWM_CON_1_OFF, level_1024 << 16, 0x1fff << 16);如上所说reg_base + DISP_PWM_CON_1_OFF是pwm1,看看这个寄存器说明
到这里就完全清晰了,DISP_REG_MASK将计算后的pwm级数写进寄存器 转换成占空比(PWM_HIGH_WIDTH/PWM_PERIOD)