高通msm8996平台上的pa电源管理(wsa881x)


  • 高通msm8996平台上的pa电源管理(wsa881x)

    • 1 相关dts定义
    • 2 swr_master设备加载
    • 3 swr_master电源管理
    • 4 wcd9335关于的电源管理
    • 5 关于wsa881x的电源管理
    • 6 关于wsa881x的widget是怎么链接到音频链路上的?

wsa881x为msm8996的pa芯片,该芯片的电源管理通过gpio控制
wsa881x driver定义:

static struct swr_driver wsa881x_codec_driver = {.driver = {.name = "wsa881x",.owner = THIS_MODULE,.pm = &wsa881x_swr_pm_ops,//这个好像没看到用,应该是在system pm这个级别用到吧?猜测.of_match_table = wsa881x_swr_dt_match,},.probe = wsa881x_swr_probe,.remove = wsa881x_swr_remove,.id_table = wsa881x_swr_id,.device_up = wsa881x_swr_up,//设备上电.device_down = wsa881x_swr_down,//设备下电.reset_device = wsa881x_swr_reset,//设备重置.startup = wsa881x_swr_startup,
};

从定义来看wsa881x设备驱动是一个struct swr_driver类型,该类型是高通平台的soundwire driver,这个driver应该是高通平台自己定的,主流linux kernel中好像没有。

那么,音频链路相关的电源管理,一般都是依赖dapm来完成的,这里wsa881x并没有任何设计dapm的东西,没有定义dapm中的widget。所以这里关键问题就是要搞清楚wsa881x_swr_up、wsa881x_swr_down这些函数在哪里被、以及什么时候被调用的。要搞清楚这个问题,必须从最根源看起。

1 相关dts定义

dts文件名:msm8996-wsa881x.dtsi

&slim_msm {tasha_codec {swr_master {compatible = "qcom,swr-wcd";#address-cells = <2>;#size-cells = <0>;wsa881x_211: wsa881x@20170211 {compatible = "qcom,wsa881x";reg = <0x00 0x20170211>;qcom,spkr-sd-n-gpio = <&pmi8994_mpps 1 0>;};wsa881x_212: wsa881x@20170212 {compatible = "qcom,wsa881x";reg = <0x00 0x20170212>;qcom,spkr-sd-n-gpio = <&pmi8994_gpios 3 0>;};……};};
};

这里没贴全,就列出两个节点,能说明问题就行。

高通的套路:
1、首先高通有个slim总线?(好像算是总线吧),反正就是一个供多个设备数据交互的接口。
2、总线下挂了一个tasha_codec,这个东西就是msm8996平台codec的代号,每一个平台codec的代号还不一样,在msm8996平台上实际的codec是一个叫wcd9335的东西(具体在soc内部还是外部暂时不确定,好像是在外部的独立芯片)
3、tasha_codec底下有一个叫swr_master的东西,这个东西其实自己没什么实际作用,主要是管理挂接在该swr_master下的所有设备。
4、定义具体的wsa881x设备节点。
那么这里暂时只是把dts描述记录下来,具体跟电源管理有什么关系,后面来记录,因为后面的设备管理和这里的dts结构息息相关。

2 swr_master设备加载

相关文件:swr-wcd-ctrl.c
设备定义:

static struct platform_driver swr_mstr_driver = {.probe = swrm_probe,.remove = swrm_remove,.driver = {.name = SWR_WCD_NAME,.owner = THIS_MODULE,.pm = &swrm_dev_pm_ops,.of_match_table = swrm_dt_match,},
};static int __init swrm_init(void)
{return platform_driver_register(&swr_mstr_driver);
}

这个东西就是一个普通的platform driver。
swrm_probe()函数中还是完成驱动和设备匹配后的初始化工作,这里主要分析pm,所以其他代码就不记录。
swrm_probe()中,有一个地方:

static int swrm_probe(struct platform_device *pdev)
{struct swr_mstr_ctrl *swrm;……swrm = kzalloc(sizeof(struct swr_mstr_ctrl), GFP_KERNEL);……if (pdev->dev.of_node)of_register_swr_devices(&swrm->master);……
}

字面意思很简单,其实就是把dts中swr_master下的所有子节点,加载到swrm->master->devices链表中,这里of_register_swr_devices()函数中会创建所有的子设备,并且调用device_register()函数,触发子设备的probe,也就是wsa881x驱动的probe()函数。
那么这里记录devices的创建过程,其实是为了说明后面pm时,swr_master管理的wsa881x设备是怎么来的这个问题。

3 swr_master电源管理

相关文件:swr-wcd-ctrl.c
继续关于swr_master设备的驱动定义:

static const struct dev_pm_ops swrm_dev_pm_ops = {SET_SYSTEM_SLEEP_PM_OPS(swrm_suspend,swrm_resume)SET_RUNTIME_PM_OPS(swrm_runtime_suspend,swrm_runtime_resume,NULL)
};static struct platform_driver swr_mstr_driver = {.probe = swrm_probe,.remove = swrm_remove,.driver = {.name = SWR_WCD_NAME,.owner = THIS_MODULE,.pm = &swrm_dev_pm_ops,.of_match_table = swrm_dt_match,},
};

这里,driver的pm相关操作在swrm_dev_pm_ops中,提供了system sleep pm的操作以及runtime pm的操作。后面的wsa881x的上下电,都是基于runtime pm来进行的。这里就把swrm_runtime_resume()函数拿出来稍微分析一下,其实很简单:

static int swrm_runtime_resume(struct device *dev)
{……list_for_each_entry(swr_dev, &mstr->devices, dev_list) {ret = swr_device_up(swr_dev);if (ret) {dev_err(dev,"%s: failed to wakeup swr dev %d\n",__func__, swr_dev->dev_num);swrm_clk_request(swrm, false);goto exit;}}……
}

简单来说就是遍历swr_master下的所有子设备,并且对这些子设备调用swr_device_up(swr_dev)完成子设备上电。

int swr_device_up(struct swr_device *swr_dev)
{……/* 实际调用子设备driver中的device_up函数对于wsa881x来说就是调用了wsa881x_swr_up函数*/if (sdrv->device_up)return sdrv->device_up(to_swr_device(dev));……
}

那么,swrm_runtime_resume()怎么被调用的,这里就跟linux kernel的电源管理系统相关了,这里大致记录一下,具体的linux kernel如何实现runtime电源管理的就不展开分析。
linux kernel在pm_runtime.h中提供了很多接口,让程序可以对设备的电源进行实时管理,比如这里设备上电用到的:

static inline int pm_runtime_get_sync(struct device *dev)
{return __pm_runtime_resume(dev, RPM_GET_PUT);
}

关于runtime pm的相关内容可以参考:
linux runtime pm机制的深入理解
Linux电源管理-Runtime PM
Linux电源管理(11)_Runtime PM之功能描述

这里,总之,调用pm_runtime_get_sync()经过一系列的复杂过程之后,会调用设备driver注册时填写的pm字段中的swrm_runtime_resume()函数,完成设备的上电控制。

那么,接着,可以跟踪到swr-wcd-ctrl.c中的另一个函数:

int swrm_wcd_notify(struct platform_device *pdev, u32 id, void *data)
{……switch (id) {
……case SWR_DEVICE_DOWN:dev_dbg(swrm->dev, "%s: swr master down called\n", __func__);mutex_lock(&swrm->mlock);if ((swrm->state == SWR_MSTR_PAUSE) ||(swrm->state == SWR_MSTR_DOWN))dev_dbg(swrm->dev, "%s: SWR master is already Down: %d\n",__func__, swrm->state);elseswrm_device_down(&pdev->dev);//这里没用!!下电好像不是走的这里!!至少一般不是走的这里!!mutex_unlock(&swrm->mlock);break;case SWR_DEVICE_UP:dev_dbg(swrm->dev, "%s: swr master up called\n", __func__);mutex_lock(&swrm->mlock);mutex_lock(&swrm->reslock);if ((swrm->state == SWR_MSTR_RESUME) ||(swrm->state == SWR_MSTR_UP)) {dev_dbg(swrm->dev, "%s: SWR master is already UP: %d\n",__func__, swrm->state);} else {pm_runtime_mark_last_busy(&pdev->dev);mutex_unlock(&swrm->reslock);pm_runtime_get_sync(&pdev->dev);//关键操作!!调用pm的runtime上电操作,从而使设备上电mutex_lock(&swrm->reslock);list_for_each_entry(swr_dev, &mstr->devices, dev_list) {ret = swr_reset_device(swr_dev);//直接调用设备的reset操作,也是wsa881x注册时提供的if (ret) {dev_err(swrm->dev,"%s: failed to reset swr device %d\n",__func__, swr_dev->dev_num);swrm_clk_request(swrm, false);}}pm_runtime_mark_last_busy(&pdev->dev);pm_runtime_put_autosuspend(&pdev->dev);}mutex_unlock(&swrm->reslock);mutex_unlock(&swrm->mlock);break;……}return ret;
}

所以这里如果要控制swr_master下面的所有设备状态,则需要调用swrm_wcd_notify()函数。
swrm_wcd_notify()函数的调用位置,实在wcd9335的驱动中,也就是codec的驱动中,这里也印证了最开始的dts文件中的描述:swr_master设备是codec设备的一部分。

这里特别说明一下,上电的过程,是wcd9335中的widget调用swrm_wcd_notify()函数实现的,而且是通过同步方式进行的上电,但是下电过程是wsa881x中的widget调用swr_slvdev_datapath_control()函数实现的,而且是通过异步方式进行的下电,但两者最终都是调用的swr-wcd-ctrl.c中swrm_runtime_suspend()swrm_runtime_resume()执行的最终电源回调
这里下电的过程不是在swrm_wcd_notify()函数中执行的,这一点后面会详细记录。

4 wcd9335关于的电源管理

这里只分析一下跟wsa881x相关的。
相关文件:wcd9335.c
tasha_codec_enable_swr()函数中实际调用前面所说的swrm_wcd_notify()函数,来实现wsa881x设备的实际电源控制,而真正音频链路中的dapm系统都依赖于widget实现,所以这里必须出现widget的相关代码了。
这里可以查看到tasha_codec_enable_swr()函数被封装到了多个widget的event回调函数中:

    SND_SOC_DAPM_MUX_E("RX INT7_1 MIX1 INP0", SND_SOC_NOPM, 0, 0,&rx_int7_1_mix_inp0_mux, tasha_codec_enable_swr,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_MUX_E("RX INT7_1 MIX1 INP1", SND_SOC_NOPM, 0, 0,&rx_int7_1_mix_inp1_mux, tasha_codec_enable_swr,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_MUX_E("RX INT7_1 MIX1 INP2", SND_SOC_NOPM, 0, 0,&rx_int7_1_mix_inp2_mux, tasha_codec_enable_swr,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_MUX_E("RX INT8_1 MIX1 INP0", SND_SOC_NOPM, 0, 0,&rx_int8_1_mix_inp0_mux, tasha_codec_enable_swr,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_MUX_E("RX INT8_1 MIX1 INP1", SND_SOC_NOPM, 0, 0,&rx_int8_1_mix_inp1_mux, tasha_codec_enable_swr,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_MUX_E("RX INT8_1 MIX1 INP2", SND_SOC_NOPM, 0, 0,&rx_int8_1_mix_inp2_mux, tasha_codec_enable_swr,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),

5 关于wsa881x的电源管理

wsa881x其实在wsa881x_swr_startup()函数中注册了一个codec驱动,其中包含了wsa881x的一些widget和route,如下:

static const struct snd_soc_dapm_widget wsa881x_dapm_widgets[] = {SND_SOC_DAPM_INPUT("IN"),SND_SOC_DAPM_MIXER_E("SWR DAC_Port", SND_SOC_NOPM, 0, 0, swr_dac_port,ARRAY_SIZE(swr_dac_port), wsa881x_enable_swr_dac_port,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_DAC_E("RDAC", NULL, WSA881X_SPKR_DAC_CTL, 7, 0,wsa881x_rdac_event,SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_PGA_E("SPKR PGA", WSA881X_SPKR_DRV_EN, 7, 0, NULL, 0,wsa881x_spkr_pa_event, SND_SOC_DAPM_PRE_PMU |SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),SND_SOC_DAPM_OUTPUT("SPKR"),
};static const struct snd_soc_dapm_route wsa881x_audio_map[] = {{"SWR DAC_Port", "Switch", "IN"},{"RDAC", NULL, "SWR DAC_Port"},{"SPKR PGA", NULL, "RDAC"},{"SPKR", NULL, "SPKR PGA"},
};

这里在wsa881x需要下电的时候,可以通过控制”SPKR PGA”控件,以消息的方式调用该控件的回调函数:wsa881x_spkr_pa_event(),并在回调函数中调用’swr_slvdev_datapath_control()’函数,该函数又调用了’swrm_slvdev_datapath_control()’函数,’swrm_slvdev_datapath_control()’函数内部最终通过调用pm_runtime_put_autosuspend()函数,实现设备的下电。这里要注意的是该下电过程为一个异步操作,所以trace的时候会发现该函数生命周期结束后,swrm_runtime_suspend()函数才会被调用。

但是!!!!!最终下电好像并不是这里触发的
真正的下电操作发现其实是swr-wcd-ctrl.c中swr_mstr_interrupt()函数被调用后,再调用pm_runtime_put_autosuspend()函数完成的下电,因为把这句话屏蔽后,下电相关函数就不会被调用了,所以推测其实真正的下电操作是在这里完成的。(由于下电是一个异步调用,所以也不可能通过在前后加两个打印来确定)。

swr_mstr_interrupt()是一个中断回调函数,这个函数到底把中断注册到哪里了?这里稍微分析一下:

static int swrm_probe(struct platform_device *pdev)
{struct swr_mstr_ctrl *swrm;struct swr_ctrl_platform_data *pdata;struct swr_device *swr_dev, *safe;int ret;……/* 其实就是:return dev->platform_data; */pdata = dev_get_platdata(&pdev->dev);……swrm->reg_irq = pdata->reg_irq;……
}

这里关键要找出pdata是哪里来的,单纯通过搜索struct swr_ctrl_platform_data结构体的调用发现,除了这里就没第二个位置用了,所以pdata的填充位置用的还不是struct swr_ctrl_platform_data结构,但是那边使用的结构定义肯定和struct swr_ctrl_platform_data一样,只是名称不同而已。

这样看来想找到pdata的出处只能从dts上来想办法。在dts中swr master属于codec,也就是wcd9335,所以这里的中断的注册位置肯定和wcd9335有关,去wcd9335初始化函数找:

static int tasha_probe(struct platform_device *pdev)
{int ret = 0;struct tasha_priv *tasha;struct clk *wcd_ext_clk, *wcd_native_clk;struct wcd9xxx_resmgr_v2 *resmgr;struct wcd9xxx_power_region *cdc_pwr;……/*  *//* 创建codec的swr ctrl类型的子设备 */INIT_WORK(&tasha->swr_add_devices_work, wcd_swr_ctrl_add_devices);……/* 绑定中断注册函数给swr_plat_data结构 */tasha->swr_plat_data.handle_irq = tasha_swrm_handle_irq;……
}static void wcd_swr_ctrl_add_devices(struct work_struct *work)
{……platdata = &tasha->swr_plat_data;for_each_child_of_node(wcd9xxx->dev->of_node, node) {……/*主要就是:pdev->dev.platform_data = d;*/ret = platform_device_add_data(pdev, platdata,sizeof(*platdata));……}……
}

其实这里的wcd_swr_ctrl_add_devices()函数就是创建codec的子swr ctrl设备,并且把tasha->swr_plat_data的值最为该设备的platform_data进行保存,从而使swr ctrl设备驱动中可以获取platform_data相关内容,包括irq注册的函数。这里可以看出两个地方对platform_data的定义名称不同,但字段相同:
wcd9335.h :

struct wcd_swr_ctrl_platform_data {void *handle; /* holds codec private data */int (*read)(void *handle, int reg);int (*write)(void *handle, int reg, int val);int (*bulk_write)(void *handle, u32 *reg, u32 *val, size_t len);int (*clk)(void *handle, bool enable);int (*handle_irq)(void *handle,irqreturn_t (*swrm_irq_handler)(int irq,void *data),void *swrm_handle,int action);
};

swr-wcd-ctrl.h :

struct swr_ctrl_platform_data {void *handle; /* holds priv data */int (*read)(void *handle, int reg);int (*write)(void *handle, int reg, int val);int (*bulk_write)(void *handle, u32 *reg, u32 *val, size_t len);int (*clk)(void *handle, bool enable);int (*reg_irq)(void *handle, irqreturn_t(*irq_handler)(int irq,void *data), void *swr_handle, int type);
};

6 关于wsa881x的widget是怎么链接到音频链路上的?

这里qcom用了前缀去进一步定义这个widget的名称,所以光靠搜RDAC或者SPKR是找不到这个空间具体的调用和操作的。
在文件:msm8996.c中,也就是mechine driver文件中的msm8996_init_wsa_dev()函数,在该函数中从dts文件中读入前缀:

static int msm8996_init_wsa_dev(struct platform_device *pdev,struct snd_soc_card *card)
{……/* Make sure prefix string passed for each WSA device */ret = of_property_count_strings(pdev->dev.of_node,"qcom,wsa-aux-dev-prefix");……for (i = 0; i < card->num_aux_devs; i++) {……dev_name_str = devm_kzalloc(&pdev->dev, DEV_NAME_STR_LEN,GFP_KERNEL);/* 读入前缀,前缀定义在msm8996-cdp.dtsi文件中 */ret = of_property_read_string_index(pdev->dev.of_node,"qcom,wsa-aux-dev-prefix",wsa881x_dev_info[i].index,wsa_auxdev_name_prefix);if (ret) {dev_err(&pdev->dev,"%s: failed to read wsa aux dev prefix, ret = %d\n",__func__, ret);return -EINVAL;}……}……return 0;
}

通过定义的前缀,加上wsa8181x中定义的widget的名称就可以构成有效的widget名称了,例如:“SpkrLeft IN”这个widget。
再根据msm8996.dtsi中所定义的mechine级的route:

sound-9335 {compatible = "qcom,msm8996-asoc-snd-tasha";qcom,model = "msm8996-tasha-snd-card";qcom,audio-routing ="AIF4 VI", "MCLK","RX_BIAS", "MCLK","MADINPUT", "MCLK","AMIC1", "MIC BIAS3","MIC BIAS3", "Analog Mic1","AMIC2", "MIC BIAS2","MIC BIAS2", "Headset Mic","AMIC3", "MIC BIAS3","MIC BIAS3", "ANCRight Headset Mic","AMIC4", "MIC BIAS3","MIC BIAS3", "ANCLeft Headset Mic","AMIC5", "MIC BIAS4","MIC BIAS4", "Handset Mic","AMIC6", "MIC BIAS4","MIC BIAS4", "Analog Mic6","DMIC0", "MIC BIAS1","MIC BIAS1", "Digital Mic0","DMIC1", "MIC BIAS1","MIC BIAS1", "Digital Mic1","DMIC2", "MIC BIAS3","MIC BIAS3", "Digital Mic2","DMIC3", "MIC BIAS3","MIC BIAS3", "Digital Mic3","DMIC4", "MIC BIAS4","MIC BIAS4", "Digital Mic4","DMIC5", "MIC BIAS4","MIC BIAS4", "Digital Mic5","SpkrLeft IN", "SPK1 OUT","SpkrRight IN", "SPK2 OUT";……
}

最终完成了整个音频链路的定义。
这里由于前缀的原因,所以浪费了很多时间来查找wsa881xwidget是如何被音频链路所作用的。

高通msm8996平台上的pa电源管理(wsa881x)相关推荐

  1. 高通MSM平台上的AMSS

    AMSS(Advanced Mobile Subscriber Station)的source实际上是QC BREW(Binary Runtime Environment For Wireless)平 ...

  2. 【转载】高通msm8996平台的ASOC音频路径分析(基于androidN及linux3.1x)

    高通msm8996平台的ASOC音频路径分析(基于androidN及linux3.1x) tags : msm8996 sound linux android 原文:高通msm8996平台的ASOC音 ...

  3. 高通Android平台下zoom4X实验原理分析

    最近负责一个项目(手机)上camera的功能,其中有要求做zoom这个功能(项目上要求对所有的分辨率都可以支持4X的zoom),所以把这个部分比较全面的学习了一下,本文对高通在android平台上zo ...

  4. android原理分析博客,高通Android平台下zoom4X实验原理分析(一)

    最近负责一个项目(手机)上camera的功能,其中有要求做zoom这个功能(项目上要求对所有的分辨率都可以支持4X的zoom),所以把这个部分比较全面的学习了一下,本文对高通在android平台上zo ...

  5. 高通sxr2130平台下(aarch64系统),死机问题分析

    高通sxr2130平台下(aarch64系统),死机问题分析 举例 二级目录 三级目录 正文 通过qcap解析得到结果如下(参看附件中的QCAP 3.0 Report.html): 61.586598 ...

  6. 高通msm8996配置wlan0 Mac地址

    1.wlan_mac.bin添加 Intf0MacAddress=000000000001 END2.push到/persist

  7. 高通msm8996启动流程

    摘自"80-nv396-1_c_msm8996_boot_and_corebsp_architecture_overview.pdf" 系统上电并且把Kryo应用处理器退出rese ...

  8. 高通驱动开发参考(二)

    第1章 Driver相关模块介绍 1.1 REX简介 虽说目前QSC60x5平台上采用L4操作系统,REX只是L4上面的一个Task.但高通为了开发的兼容性,提供的API仍然采用老的一套接口(可能内部 ...

  9. QXRService:高通SnapdragonXR OpenXR SDK v1.x 概略

    上文提到,高通以前自主研发的非OpenXR SDK,也就是SnapdragonXR-SDK(SXR SDK)在4.0.6版本之后就不会再维护了,取而代之的是基于OpenXR标准的Snapdragon ...

最新文章

  1. 【CSS3进阶】酷炫的3D旋转透视
  2. Windows XP下屏蔽Ctrl_Alt_Del键的方法
  3. luncene 查询字符串的解析—QueryParser类
  4. chart绑定mysql数据源_MSChart图表之Chart组件 学习一篇(5个主要属性+数据源绑定)...
  5. 大型网站技术架构03
  6. css中position的两种定位(absolute、relative)
  7. 1154. 一年中的第几天 golang
  8. linux shell中各种分号和括号,linux shell 各种分号,括号使用方法总结
  9. Qt实用快捷键(较全面)
  10. java的线程池的基础类
  11. dubbo源代码编译打包错误解决
  12. PROBLEM F: 切煎饼
  13. athletes 表包含运动员姓名,年纪和代表国家。下面哪个查询可以找出代表每个国家最年轻的运动员情况?
  14. iphone连电脑服务器未响应,今天要闻iphone8无限转圈黑屏强制关机没反应(苹果电脑开不了机黑屏)...
  15. Mac下的Kali虚拟机的安装
  16. COB-ID的简单理解分析
  17. 二维数组作为参数传递问题
  18. item_get - 根据ID获取拼多多商品详情
  19. 前瞻: 拥抱量子计算时代!详解2020年全球十大杰出量子计算公司
  20. html怎么安装系统,上网本怎么装系统_上网本装系统

热门文章

  1. 七剑下天山,谈谈我认识的精准营销
  2. 创业者的噩梦 - 怎么就侵权了
  3. Eclipse,STS系列IDE 启动阻塞,启动一直加载问题
  4. Web 字体 font-family 浅谈
  5. java实现-强智教务系统API文档-全部java封装
  6. java.net.URISyntaxException: Illegal character in scheme name at index XX
  7. 史上最全的WSL安装教程
  8. H266 ISP 帧内子划分
  9. c语言开发简单小游戏扫雷,利用C语言开发一个扫雷小游戏
  10. 计算机网络的基础学习