1.概述
整体来说,i.MX8MP中的电源是由General Power Controller (GPC) 来控制的。GPC可以提供各种电源模式的控制,如低功耗模式、深度睡眠模式等等。GPC包含两个模块,一个是系统模式控制器(SMC),控制系统的电源模式;另一个是电源门控控制器(PGC),控制模块的电源开关。详细信息请参考RM。本文重点分析其中的PGC(Power Gating Controller )。
如下图所示,每个模块都有单独的PGC进行控制,从而实现对单个模块的电源开关功能。
PGC的上下电控制逻辑如下,所有CPU外的MIX和PU_PGCs,PGC_CPU_MAPPING
会被映射到A53/M7 domain。
- 一个PGC可以被映射到一个domain,或者A53和M7两个domain
- 对于硬件上电请求,如果PGC是被分配到任意CPU域内,则直接上电
- 对于软件上电请求,如果PGC是被分配到任意CPU域内,则可以被对应CPU域内的软件控制上电。对应
MIX_PGC_SW_PUP_REQ
和PU_PGC_SW_PUP_REQ
。 - 对于硬件下电请求,如果PGC是被分配到一个CPU域内,则可以被对应CPU域内的软件控制下电。如果PGC是被分配到两个CPU域内,若一个CPU处于LPM模式,另一个企图下电,则可以被对应CPU域内的软件控制下电。
- 对于软件下电请求,同硬件下电。
PGC_CPU_MAPPING
由RDC控制访问。
在设备树中,pgc的定义和上面的表格保持一致,其中reg是每个pgc的idex值,设备树和驱动中的这个数值一一对应。参考include/dt-bindings/power/imx8mp-power.h
。
2.gpcv2驱动分析
2.1cpu资源结构体分析
每一个芯片都有一个imx_pgc_domain_data
来描述pgc的信息。这个数据结构与手册中的pgc信息保持一致。
static const struct imx_pgc_domain_data imx8mp_pgc_domain_data = {
.domains = imx8mp_pgc_domains,
.domains_num = ARRAY_SIZE(imx8mp_pgc_domains),
.reg_access_table = &imx8mp_access_table,
.pgc_regs = &imx8mp_pgc_regs,
};
其中imx8mp_pgc_domains
定义如下所示,以mlmix为例,IMX8MP_POWER_DOMAIN_MLMIX
和设备树中一致。.genpd
保存这个电源域在内核中的名字。
static const struct imx_pgc_domain imx8mp_pgc_domains[] = {
[IMX8MP_POWER_DOMAIN_MIPI_PHY1] = {
.genpd = {
.name = "mipi-phy1",
},
.bits = {
.pxx = IMX8MP_MIPI_PHY1_SW_Pxx_REQ,
.map = IMX8MP_MIPI_PHY1_A53_DOMAIN,
},
.pgc = BIT(IMX8MP_PGC_MIPI1),
},
[IMX8MP_POWER_DOMAIN_PCIE_PHY] = {
.genpd = {
.name = "pcie-phy1",
},
.bits = {
.pxx = IMX8MP_PCIE_PHY_SW_Pxx_REQ,
.map = IMX8MP_PCIE_PHY_A53_DOMAIN,
},
.pgc = BIT(IMX8MP_PGC_PCIE),
},
..........
[IMX8MP_POWER_DOMAIN_MLMIX] = {
.genpd = {
.name = "mlmix",
},
.bits = {
.pxx = IMX8MP_MLMIX_Pxx_REQ,
.map = IMX8MP_MLMIX_A53_DOMAIN,
.hskreq = IMX8MP_MLMIX_PWRDNREQN,
.hskack = IMX8MP_MLMIX_PWRDNACKN,
},
.pgc = BIT(IMX8MP_PGC_MLMIX),
.keep_clocks = true,
.noc_data = {
&imx8mp_pgc_noc_data[IMX8MP_MLMIX],
},
},
};
.bits
中的寄存器位分别来自对应上下电的寄存器(GPC_PU_PGC_SW_PUP_REQ/GPC_PU_PGC_SW_PDN_REQ),domain map控制寄存器GPC_PGC_CPU_A53_MAPPING,上下电握手请求寄存器GPC_PU_PWRHSK低16位,上下电握手响应寄存器GPC_PU_PWRHSK高16位。对应这些寄存器中的功能位。
const struct {
u32 pxx;
u32 map;
u32 hskreq;
u32 hskack;
} bits;
imx8mp_access_table
存储寄存器映射之后的虚拟地址。
imx8mp_pgc_regs
存储的是寄存器偏移量,分别控制domain映射、软件上下电请求和握手。
#define IMX8MP_GPC_PGC_CPU_MAPPING 0x1cc
#define IMX8MP_GPC_PU_PGC_SW_PUP_REQ 0x0d8
#define IMX8MP_GPC_PU_PGC_SW_PDN_REQ 0x0e4
#define IMX8MP_GPC_PU_PWRHSK 0x190
static const struct imx_pgc_regs imx8mp_pgc_regs = {
.map = IMX8MP_GPC_PGC_CPU_MAPPING,
.pup = IMX8MP_GPC_PU_PGC_SW_PUP_REQ,
.pdn = IMX8MP_GPC_PU_PGC_SW_PDN_REQ,
.hsk = IMX8MP_GPC_PU_PWRHSK,
};
2.2NOC优先级
imx_pgc_noc_set
使用 regmap_write
函数向 NOC 寄存器映射(regmap
)中的特定偏移量写入参数值以配置 NOC 控制器的行为。
data[i]->off + 0x8
:写入优先级(priority)。data[i]->off + 0xc
:写入模式(mode)。data[i]->off + 0x18
:写入扩展控制(extctrl)。
static const struct imx_pgc_noc_data imx8mp_pgc_noc_data[] = {
[IMX8MP_MLMIX] = {
.off = 0x180,
.priority = 0x80000303,
},
[IMX8MP_GPU2D] = {
.off = 0x500,
.priority = 0x80000303,
},
[IMX8MP_GPU3D] = {
.off = 0x580,
.priority = 0x80000303,
},
[IMX8MP_AUDIO_DSP] = {
.off = 0x200,
.priority = 0x80000303,
},
[IMX8MP_AUDIO_SDMA2_PER] = {
.off = 0x280,
.priority = 0x80000404,
},
[IMX8MP_AUDIO_SDMA2_BURST] = {
.off = 0x300,
.priority = 0x80000404,
},
[IMX8MP_AUDIO_SDMA3_PER] = {
.off = 0x380,
.priority = 0x80000404,
},
[IMX8MP_AUDIO_SDMA3_BURST] = {
.off = 0x400,
.priority = 0x80000404,
},
[IMX8MP_AUDIO_EDMA3] = {
.off = 0x480,
.priority = 0x80000404,
},
};
2.3驱动设置ops
在probe函数中,遍历设备树中的pgc单元,设置每个单元的domain信息,domain->regs
获取imx8mp_pgc_regs
中定义的寄存器偏移量。最后设置对于每个pgc单元,它们的上下电函数分别为imx_pgc_power_up
和imx_pgc_power_down
。因此设备树中所有的pgc对应的上下电函数都一样。
for_each_child_of_node(pgc_np, np) {
struct platform_device *pd_pdev;
struct imx_pgc_domain *domain;
u32 domain_index;
....
.....
domain = pd_pdev->dev.platform_data;
domain->regmap = regmap;
domain->regs = domain_data->pgc_regs;
for (i = 0; i < DOMAIN_MAX_NOC; i++)
domain->noc_data[i] = domain_data->domains[domain_index].noc_data[i];
domain->genpd.power_on = imx_pgc_power_up;
domain->genpd.power_off = imx_pgc_power_down;
}
2.4imx_pgc_power_up
函数逻辑如下:
- 初始化:初始化一些变量,并从给定的
genpd
指针中获取imx_pgc_domain
结构体。 - PM运行时管理::
pm_runtime_get_sync
获取设备的运行时 PM 引用,并确保该设备处于活动状态。它会阻塞当前线程,直到设备被激活为止。通常,在访问设备之前,需要确保设备处于活动状态,以便正常操作。 - 调节器启用:通过regulator框架启用对应的电源。
- 复位控制:
reset_control_assert
施加该域的复位控制。- 使用
clk_bulk_prepare_enable
为该域中的所有设备启用复位时钟。
- 通知:使用
raw_notifier_call_chain
通知电源通知者已经转换到“时钟已启用”状态。 - 延迟:使用
udelay
引入延迟,以便复位传播一段时间。 - 域上电:
- 如果
domain->bits.pxx
字段已设置,则通过更新寄存器中的对应位来为域上电。 - 使用
regmap_read_poll_timeout
等待寄存器中的特定位清除。 - 对于
domain->pgc
位图中的每个设置的位,禁用电源控制。
- 如果
- 复位控制去除:解除该域的复位控制。
- 通知:使用
raw_notifier_call_chain
通知电源通知者已经转换到“ADB400”状态。 - ADB400 上电:如果
domain->bits.hskreq
已设置,则通过更新寄存器中的某些位来请求 ADB400 上电。 - 时钟禁用:如果
domain->keep_clocks
未设置,则使用clk_bulk_disable_unprepare
禁用和取消准备域中所有设备的复位时钟。 - 配置NOC控制器:调用
imx_pgc_noc_set
函数,可能是为特定于电源域的配置设置一些参数。
static int imx_pgc_power_up(struct generic_pm_domain *genpd)
{
struct imx_pgc_domain *domain = to_imx_pgc_domain(genpd);
u32 reg_val, pgc;
int ret;
ret = pm_runtime_get_sync(domain->dev);
ret = regulator_enable(domain->regulator);
reset_control_assert(domain->reset);
/* Enable reset clocks for all devices in the domain */
ret = clk_bulk_prepare_enable(domain->num_clks, domain->clks);
raw_notifier_call_chain(&genpd->power_notifiers, IMX_GPCV2_NOTIFY_ON_CLK_ENABLED, NULL);
/* delays for reset to propagate */
udelay(5);
if (domain->bits.pxx) {
/* request the domain to power up */
regmap_update_bits(domain->regmap, domain->regs->pup,
domain->bits.pxx, domain->bits.pxx);
/*
* As per "5.5.9.4 Example Code 4" in IMX7DRM.pdf wait
* for PUP_REQ/PDN_REQ bit to be cleared
*/
ret = regmap_read_poll_timeout(domain->regmap,
domain->regs->pup, reg_val,
!(reg_val & domain->bits.pxx),
0, USEC_PER_MSEC);
/* disable power control */
for_each_set_bit(pgc, &domain->pgc, 32) {
regmap_clear_bits(domain->regmap, GPC_PGC_CTRL(pgc),
GPC_PGC_CTRL_PCR);
}
}
/* delay for reset to propagate */
udelay(5);
reset_control_deassert(domain->reset);
raw_notifier_call_chain(&genpd->power_notifiers, IMX_GPCV2_NOTIFY_ON_ADB400, NULL);
/* request the ADB400 to power up */
if (domain->bits.hskreq) {
regmap_update_bits(domain->regmap, domain->regs->hsk,
domain->bits.hskreq, domain->bits.hskreq);
}
/* Disable reset clocks for all devices in the domain */
if (!domain->keep_clocks)
clk_bulk_disable_unprepare(domain->num_clks, domain->clks);
imx_pgc_noc_set(domain);
return 0;
}
2.5imx_pgc_power_down
- 初始化:初始化一些变量,并从给定的
genpd
指针中获取imx_pgc_domain
结构体。 - 复位时钟启用:如果
domain->keep_clocks
未被设置,那么启用该域中所有设备的复位时钟,以准备关闭操作。 - 请求 ADB400 关闭:如果
domain->bits.hskreq
被设置,那么向 ADB400 发送关闭请求。等待 ADB400 确认关闭完成。 - 通知:使用
raw_notifier_call_chain
通知电源通知者已经将 ADB400 关闭。 - 关闭域:如果
domain->bits.pxx
被设置,那么启用电源控制,然后请求关闭该域。等待关闭请求的确认。 - 关闭复位时钟:禁用并取消准备该域中所有设备的复位时钟。
- 关闭调节器:如果
domain->regulator
非空,那么禁用该域关联的调节器。 - 释放运行时 PM 引用:使用
pm_runtime_put_sync_suspend
函数释放与该域关联的设备的运行时 PM 引用。
static int imx_pgc_power_down(struct generic_pm_domain *genpd)
{
struct imx_pgc_domain *domain = to_imx_pgc_domain(genpd);
u32 reg_val, pgc;
int ret;
/* Enable reset clocks for all devices in the domain */
if (!domain->keep_clocks) {
ret = clk_bulk_prepare_enable(domain->num_clks, domain->clks);
}
/* request the ADB400 to power down */
if (domain->bits.hskreq) {
regmap_clear_bits(domain->regmap, domain->regs->hsk,
domain->bits.hskreq);
ret = regmap_read_poll_timeout(domain->regmap, domain->regs->hsk,
reg_val,
!(reg_val & domain->bits.hskack),
0, USEC_PER_MSEC);
}
raw_notifier_call_chain(&genpd->power_notifiers, IMX_GPCV2_NOTIFY_OFF_ADB400, NULL);
if (domain->bits.pxx) {
/* enable power control */
for_each_set_bit(pgc, &domain->pgc, 32) {
regmap_update_bits(domain->regmap, GPC_PGC_CTRL(pgc),
GPC_PGC_CTRL_PCR, GPC_PGC_CTRL_PCR);
}
/* request the domain to power down */
regmap_update_bits(domain->regmap, domain->regs->pdn,
domain->bits.pxx, domain->bits.pxx);
/*
* As per "5.5.9.4 Example Code 4" in IMX7DRM.pdf wait
* for PUP_REQ/PDN_REQ bit to be cleared
*/
ret = regmap_read_poll_timeout(domain->regmap,
domain->regs->pdn, reg_val,
!(reg_val & domain->bits.pxx),
0, USEC_PER_MSEC);
}
/* Disable reset clocks for all devices in the domain */
clk_bulk_disable_unprepare(domain->num_clks, domain->clks);
if (!IS_ERR(domain->regulator)) {
ret = regulator_disable(domain->regulator);
}
pm_runtime_put_sync_suspend(domain->dev);
return 0;
}