- 前言
- 一、电源管理通道PMC概述
- 二、通过电源管理通道PMC完成EC和BIOS或操作系统通信
- 操作系统或BIOS通过PMC通道与EC通信
- 如果PMSTS的BIT2为1时,代表发送过来的信息是命令:
- 如果PMSTS的BIT2为0时,代表发送过来的信息是数据:
- 总结
在EC中的电源管理通道是定义在ACPI规范中的,是用于主机处理器和嵌入式控制器EC中的通信通道。
一、电源管理通道PMC概述电源管理通道提供了PMDIR, PMDOR, PMCMDR和PMSTR四个寄存器它用于Host与EC两端的通信。Host通过写入数据到PMDIR中,EC通过读取Host写入PMDIR中的数据来完成Host->Ec端的通信。EC通过写入数据到PMDOR中,Host通过读取EC写入PMDOR中的数据完成功能EC->Host端的通信。PMCMDR/PMSTR两个寄存器则能同时被Host和EC读取。电源管理通道的接口框图如下图所示。
从上面图片我们也可以得知PMC通道的偏移地址是0x62和0x66。这个框图一步一步来解读,首先是EC Bus的作用,他的作用是用于同步Host和EC端的数据,数据又是如何同步的呢,他是通过在EC端和Host端设置两组数据同步的寄存器来实现数据同步的。比如在Host端有PMDIR, PMDOR和PMSTR三个寄存器,在EC端同样有PMDI,PMDO和PMSTS。如下3张图片是对EC端PMDI,PMDO和PMSTS寄存器的描述。
我们有这样一个场景,操作系统下发了Shutdown指令,这时操作系统开始完成相关的内容保存,将在运行的任务进行停止,将需要保存的数据保存到磁盘中,在所有的保存工作完成后,需要向EC发送一条关机命令,那么这条命令的数据就是通过电源管理通道来完成的。
这里可以先通过我之前的博文《9.嵌入式控制器EC学习 操作系统或BIOS通过KBC(0x60/0x64)和PMC(0x62/0x66)对EC ram进行读写》了解通过PMC对EC Ram的访问。
通过ACPI EC 接口规范,在《ACPI规范》的第十二章内容中有详细介绍通信细节。接下来结合代码详细讨论:
假设此时操作系统或BIOS通过LPC的PMC通道下发了数据给EC,那么EC又该如何正确的接收下发的数据呢?
查看EC端PMSTS和Host端PMSTR寄存器,这两个寄存器是实时同步的,只要其中内容发生变化,会同时同步到两边的寄存器中。寄存器中的具体内容如下图所示:
| 位数 | 描述 | 作用 |
|---|---|---|
| bit1 | IBF | 这个位置位时表示操作系统或BIOS已经向PMDIR寄存器中写入了数据/命令,当Host端的PMDIR寄存器中有内容时,寄存器中的数据将会同步到EC端的PMDI寄存器中,EC端则可以读取PMDI寄存器来获取操作系统或BIOS向EC发送的数据,当数据读取完成后,芯片会自动帮我们将IBF位清零。 |
| bit0 | OBF | 这个位置位时表示EC已经向PMDO寄存器中写入了数据,当EC端的PMDO寄存器中有内容时,寄存器中的数据将会同步到Host端的PMDOR寄存器中,操作系统或BIOS则可以读取PMOR寄存器来获取EC向操作系统或BIOS发送的数据,当数据读取完成后,芯片会自动帮我们将OBF位清零。 |
| 在CORE_MAIN.C主函数中有如下代码: |
if( IsFlag1(PM1STS, BIT1) ) { F_Service_PCI2 = 1; }
当IBF置位时,代表着操作系统或BIOS已经通过PMC通道(0x62/0x66)向EC发送了数据,代码进入CORE_ACPI.C中的Service_PCI2服务函数:
void Service_PCI2(void)
{
if ( IsFlag0(PM1STS,IBF1) ) return;
vSetTotalBurstTime(); // Set Burst mode total time (2ms)
Service_PCI2_Main(); // Process Command/Data
}
首先通过PMSTS的BIT2判断操作系统或BIOS发送给EC的信息时DATA还是CMD:
// PM1STS = Embedded Controller Status, EC_SC (R) if ( PM1STS & C_D1 ) // CMD:1=Byte in data register is a command byte如果PMSTS的BIT2为1时,代表发送过来的信息是命令:
if ( PM1STS & C_D1 ) // CMD:1=Byte in data register is a command byte
{
PM1Cmd = PM1DI; // Load command from Port Buffer
PM1Step = 0;
#if SUPPORTED_RECORDER
if(En_Record66 )
{
RamDebug(0x66); RamDebug(PM1Cmd);
}
#endif
(Port66_Table[PM1Cmd>>4])(); // Handle command
}
在里面通过PMDI寄存器取出操作系统或BIOS发给EC的命令,在将命令右移4位,0x80–0x84这五个命令值右移4位后都为8,查询Port66_Table表:
const FUNCT_PTR_V_V code Port66_Table[16] =
{
EC_Cmd_0X, // Process ACPI command 0x
EC_Cmd_1X, // Process ACPI command 1x
EC_Cmd_2X, // Process ACPI command 2x
EC_Cmd_3X, // Process ACPI command 3x
EC_Cmd_4X, // Process ACPI command 4x
EC_Cmd_5X, // Process ACPI command 5x
EC_Cmd_6X, // Process ACPI command 6x
EC_Cmd_7X, // Process ACPI command 7x
EC_Cmd_8X, // Process ACPI command 8x
EC_Cmd_9X, // Process ACPI command 9x
EC_Cmd_AX, // Process ACPI command Ax
EC_Cmd_BX, // Process ACPI command Bx
EC_Cmd_CX, // Process ACPI command Cx
EC_Cmd_DX, // Process ACPI command Dx
EC_Cmd_EX, // Process ACPI command Ex
EC_Cmd_FX, // Process ACPI command Fx
};
代码将执行EC_Cmd_8X函数:
//-----------------------------------------------------------------------------
void EC_Cmd_8X(void)
{
(EC6266Cmd8X_Table[PM1Cmd&0x0F])();
}
查询命令表EC6266Cmd8X_Table:
//-----------------------------------------------------------------------------
const FUNCT_PTR_V_V code EC6266Cmd8X_Table[16] =
{
EC6266_CMD_80, // Process ACPI command 80
EC6266_CMD_81, // Process ACPI command 81
EC6266_CMD_82, // Process ACPI command 82
EC6266_CMD_83, // Process ACPI command 83
EC6266_CMD_84, // Process ACPI command 84
EC6266_CMD_85, // Process ACPI command 85
EC6266_CMD_86, // Process ACPI command 86
EC6266_CMD_87, // Process ACPI command 87
EC6266_CMD_88, // Process ACPI command 88
EC6266_CMD_89, // Process ACPI command 89
EC6266_CMD_8A, // Process ACPI command 8A
EC6266_CMD_8B, // Process ACPI command 8B
EC6266_CMD_8C, // Process ACPI command 8C
EC6266_CMD_8D, // Process ACPI command 8D
EC6266_CMD_8E, // Process ACPI command 8E
EC6266_CMD_8F // Process ACPI command 8F
};
代码将根据PMCmd中的值进入不同的函数中去执行动作,命令范围在0x80–0x84,通过《ACPI规范》的第十二章内容了解各个命令的作用,这里不再重复讲叙,也可以参照之前的博文《9.嵌入式控制器EC学习 操作系统或BIOS通过KBC(0x60/0x64)和PMC(0x62/0x66)对EC ram进行读写》了解。
函数的详细内容如下:
void EC6266_CMD_80(void)
{
SCI_RESPONSE(); // Interrupt on IBF=0
PM1Step = _PM1_STEP_1;
}
void EC6266_CMD_81(void)
{
SCI_RESPONSE(); // Interrupt on IBF=0
PM1Step = _PM1_STEP_2;
}
void EC6266_CMD_82(void)
{
SET_BIT(PM1STS,4); // PM1STS.4 Set Burst mode flag
PM1DO = 0x90; // Byte #2 (Burst acknowledge byte)
OS_ACPI_Mode = 1; // Auto Set ACPI Mode if Host Do ECCmd82
SCI_RESPONSE(); // Interrupt on IBF=0
#if En_Record62
RamDebug(0x90);
#endif
}
void EC6266_CMD_83(void)
{
CLEAR_BIT(PM1STS,4);
SCI_RESPONSE();
}
void EC6266_CMD_84(void)
{
//OS_ACPI_Mode = 1; // Auto Set ACPI Mode if Host Do ECCmd84
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#if (SUPPORT_ACPI_SMI)
if( Is_Flag1(PM1STS,5) )
{ SCI_LastQueryEvent = ReadSCI_QueryValue(); }
else if( Is_Flag1(PM1STS,6) )
{ SCI_LastQueryEvent = ReadSMI_QueryValue(); }
else
{
SCI_LastQueryEvent = 0x00;
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#else
SCI_LastQueryEvent = ReadSCI_QueryValue();
#endif
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PM1DO = SCI_LastQueryEvent;
SCI_RESPONSE();
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#if SCI_EVENT_LOG
if( SCI_LastQueryEvent > 0 )
{
SCI_SMI_Event_Record[(SCI_SMI_Index_Record&0x0F)] = SCI_LastQueryEvent;
SCI_SMI_Index_Record++;
}
#endif
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SCI_QueryEvent = 0x00;
SCI_StepTimer = 0;
WAIT_SCI_CENTER = 0;
}
如果PMSTS的BIT2为0时,代表发送过来的信息是数据:
else // CMD:0=Byte in data register is a data byte
{
//PM1DI,PM1DO=ACPI Sepc.13.2.3 Embedded Controller Data,EC_DATA(R/W)
PM1Data = PM1DI; // Load data
if (PM1Step!=0x00)
{
#if SUPPORTED_RECORDER
if(En_Record62)
{
RamDebug(0x62); RamDebug(PM1Data);
}
#endif
(Port62_Table[PM1Step&0x07])(); // Handle command data
}
else
{
while( ECUnProcessCnt < 0xFF )
{
ECUnProcessCnt++;
}
}
}
在里面通过PMDI寄存器取出操作系统或BIOS发给EC的数据,这里需要参照博文《9.嵌入式控制器EC学习 操作系统或BIOS通过KBC(0x60/0x64)和PMC(0x62/0x66)对EC ram进行读写》,操作系统或BIOS通过PMC对EC进行读写的具体步骤如下:
读操作
1.往EC的Command Port写入0x80命令
2.往EC的Data Port写入需要读取的数据的Offset
3.读取EC的Data Port,读到的数据便是结果
写操作
1.往EC的Command Port写入0x81命令
2.往EC的Data Port写入所写数据的Offset
3.往EC的Data Port写入所写数据的值
总结,读写操作都分为三步,第一步都是往PMC的Command Port即0x66写入命令,第二步都是往PMC的Data Port即0x62写入地址偏移,第三步有读写的区别。
所以在代码处理数据时也要判断在哪一步,比如在处理读取EC命令操作(往EC的Command Port写入0x80命令)时,会将PM1Step = _PM1_STEP_1,所以后面EC在PMC通道0x62数据端口接收到的数据就是操作系统或BIOS需要读取数据的偏移地址,代码如下:
void EC62_DATA_STEP1(void)
{ // Byte #3 (Data read to host)
// PM1DO = ACPI Sepc.12.3.3 Embedded Controller Data, EC_DATA (R/W)
PM1Data1 = Read_MapECSpace(PM1Data);
PM1DO = PM1Data1;
SCI_RESPONSE(); // Interrupt on OBF=1
#if En_Record62
RamDebug(PM1Data1);
#endif
PM1Step = 0;
}
此时将根据PMData的数据(偏移)取出ECSpace(即EC ram)指定位置的内容,并将内容写入PMDO寄存器,该寄存器用于EC通过PMC通道发送数据给操作系统或BIOS,当该寄存器写入数据后,OBF会置位,EC再通过SCI中断通知操作系统或BIOS,我们已经把指定偏移的数据写入了输出寄存器了,操作系统或BIOS可以取走需要的数据了。
在处理操作系统或BIOS写操作(往EC的Command Port写入0x81命令)时,此时PM1Step = _PM1_STEP_2,所以后面EC在PMC通道0x62数据端口接收到的数据就是操作系统或BIOS需要写入数据的偏移地址,代码如下:
void EC62_DATA_STEP2(void)
{
PM1Data1=PM1Data; // Byte #2 Save Address Index
SCI_RESPONSE(); // Interrupt on OBF=1
PM1Step = _PM1_STEP_3;
}
void EC62_DATA_STEP3(void)
{ // Byte #3 (Data read to host)
SCI_RESPONSE(); // Interrupt on OBF=1
Write_MapECSpace( PM1Data1,PM1Data );
PM1Step = 0;
}
在EC端的处理代码中,写入操作比读取操作多了一个步骤,因为在读取步骤中,EC在接收到偏移地址后可以立马返回EC ram空间指定偏移位置的内容给操作系统或BIOS,而在写入操作中,EC接收完偏移地址后,EC还需要继续接收内容,所以在写操作中分为了两步进行,EC62_DATA_STEP2用来接收偏移地址,EC62_DATA_STEP3用来往EC ram指定偏移地址写入(Write_MapECSpace)相应的值。
总结本文在《9.嵌入式控制器EC学习 操作系统或BIOS通过KBC(0x60/0x64)和PMC(0x62/0x66)对EC ram进行读写》的基础上进一步分析代码的运行过程。



