-
我所知道的
EC====>
ower
Sequence
1. What’s Power
Sequence ?
+ w8 f* m, 4 K9 Y0 {4 H3
x
Power
Sequence
是指
HW
Device
上电的顺序
,
它的大致顺序如下:
< br>/
v+
s#
G&
{: Z' J4 ~, S3 o
1)ALWAYS
! }
P6 m* [2 h! U, v%
u
2)SUS_ON3)DIMM_ON
6
h
4)RUN_ON
- R# g9 U: p!
t( k
5)VR_ON
) Z3
W( k2 S' v8 k/ I, {' C
p>
这基本上是
NB
工作需要的所有
POWER
。
插入
AC<
/p>
或者
DC
后,
机
器内部的
开启的电为
ALWAYS
电,
主要用以保证
EC
的正常运行;系统正常工作进入
os
以后,
所有的
Powe
r
都开启。
完整的过程其实是这样的:
AC
或者
DC
插入以后,
EC
Reset
开始跑
c
ode
,用户按下
Power
Swi
tch
时,
EC
拉
SUS_ON
送给一颗
POWER
IC,+3VSUS,+5VSUS
起来后,
POWER
IC
回
EC
SUS_OK
。接下来
EC
发给南桥一
个称为
?RSMRST#?
的信号。这时候南桥的部分功能开始
初始化并等
待开机信号。这时候的南桥并没有打开全部电源,只有很少一部分的功能可用
,
比如供检测开机信号的
PWRBTN#
信号。
,
在用户按下
Power
p>
键的时候,
EC
检测
到一个中断,然后
duplicate
一个开机信号(
PWRBTN#
)给南桥,南桥收到
PWRB
TN#
信号后依次拉高
SLP_S5#,SLP_S4#,SL
P_S3#
信号,开启了所有的
POWER
,在
V_CORE
电起来后,
IC
会发送
ALLSYSPWOK
信号给<
/p>
EC
这信号
表明外围电源正常开启。南桥
会发出
PCI
RST#
信号到
PCI
总线,于是总线上的
设备都被初始化(
包括北桥)
,然后
CPU
RST#<
/p>
信号送出,
HOST
正式接手并开
始工作。
/
至此,上电时序完成。
4 t- O& f% Q
C7
J
: l/ f, I9 r
@
2. Why Power
Sequence
?
need s
equence
?
(
个人猜测,欢迎指
教
?
)
为什么需要
Power
Sequence
呢?没有
Power
Sequence
直接把所有的电压都供
上可以开机吗?答案是
YES
。直接上电开机是没有问题的,
可是没有办法达成
省电的功能;另外
S3/S4,Long R
un
也会有问题。系统進入待机的时候
(S3)
,机器
内部的电只有
SUS&DIMM
电,主要是提供
DDR
的电压,以保证
RAM
内部的
资料不丢失(
S
3
是将数据
Store
to
p>
Ram
,在
Resume
< br>之后
OS
从
RAM
中获得数
据,所以
DDR
的
电一定要留着)
,而
S4
和关机
(S5)
的电是一样的,都是
Always<
/p>
电
(
当
AC
p>
在的时候其实
SUS
电要留着,
目的是加速
Resume
或者
Power On
的速
度
)
。
0 _- e) Q6 7 O
J9 m
II.S4
和
S5
共用一个信号?
#
M3 H3 M6 v, F( A3 _# S
p>
做过
EC
的同志可能会发现,
线路图上
S4,S5
的信号只会有一个。
根据我们的
使用经验
S4
和
S5
有很大的差异。原因其实是因为
S4,S5
都只有
ALWAYS
电,从
EC
的角度,
根本没有必要区分
S4,S5
所以
SB
只有一个信号拉到
EC
。
. m;
W0 B: q+ {)
我所知道的
EC====>System
Architecture
1
.
p>
EC
功能概述
E
C
是
NB
独有的组成部分。它将
MB
上面的
keyboard
、
mouse
集中于一体在,
EC
内部提供了
KBC
控制器统一控制
它们,这样大大节省了空间实现了便携。
NB
的另一个特点是它
需要电池供电,这样省电是一个重要的问题。
EC
配合
chipset
在
S3
、
S4
、
S5
分别供不同的电源策略以达到节省的目的,另外
EC
也会
控制电池的冲放电的过程,检测电池电量用于决定是否待机
or
紧急关机
or Wakeup
等。另外由于
NB
集成度高、空间受限
,
散热非常重要但同时还要兼顾
噪音的问题,
EC
会通过
thermal sensor<
/p>
获得当前的
CPU
和
VGA
温度,
根据不同
的温度自动
调节风扇的转速。这些都是
EC
提供的功能。
< br>7 T0 F- D: M1 Y
2
< br>.常见
HW
架构
b6 l9 `5 l3 D% Y; o
现在
NB
HW
线路系统中
EC
的常见架构有两种,比较旧一点的案子会将<
/p>
BIOS
和
EC
的
code
放在一起挂在
EC
的
X-BUS
下面。而新一些的做法会将
BIOS
和
EC
分开
;
BIOS
挂在
SB
LPC
或者
SPI
下面而
p>
EC
挂在
EC
的<
/p>
SPI
下面。如下图
所示:
0 A5 R% c/ g6 c0 l% R2 ^
那么为
什么会有两种架构呢?他们有什么区别呢?听我慢慢道来
?
zz
z
。这其实
是个多方原因导致的结果。
1.
出于成本的考虑。大家都知道现在
NB
的价格战是
愈演愈烈,大家都在拼成本。所以低端机种都在拼命的
< br>cost down.
很明显第一种
架构也就是图
1
左边的架构会省掉一颗
IC
的成本。
那么大家可能又会问既然这么
economi
c
为什么还要导入第二种架构呢?
2.
出于性能的考虑
“
有所得就有所失
”<
/p>
没有完美的方案,
第一种架构存在一个致命得缺陷,
就是
BIOS
和
EC
跑
code
时
会抢总
线,也就是说有可能某一段时间
BIOS
狂飙得时候,
EC
就卡住了,反之
亦然。于是就发现
POST
时间过长,
S3
、
S4 Resume
时间过长的问题了。于是乎
第二种架构应运而生;所以在新机种以及那些高性能的
game
系列中就会采用这
种架构。
(
PS
:现在应该以第二种架构为主了)
# Y+ {(
}1 q: r
3
.
EC
如何与
HOST
通信
p>
# d- [) s5 y* C
由图<
/p>
1
可以看出
EC
与
SB
通过
LPC
相连,所以
EC
与
HOST
端的通信主要是通
过
LPC BUS
进行的,除此之外
EC
还会有
< br>SMI
,
SCI
的
pin
拉到
SB
上也就是说
EC
也可以通过发
SCI
,
SMI
的中断通知
Chi
pset, Chipset
再从
LPC BUS
获得相关
的
EVENT
(<
/p>
PS
:后续的会详细讨论这部分)
。
p>
LPC
BUS
通信的部分如下图所示:
1 U& h
B1 d9 W9
T( t& ~0 i! y
<
/p>
总之
EC
是
NB
系统中最为底层的部件,只要有电
EC
就会工作,检测各种输入
信息,同时它还负责提供
power
sequence
和
host
通信等重
要的工作。
(
我所知道的
EC====>LID
<
/p>
1.
What
’
s lid?
Lid
是盖子的意思,
在
NB
上其实就是指
Panel
打开和关闭的过程。不知道有没
有人试过用磁石去碰
< br>NB
的底边缘,试过就会发现一碰
NB
< br>就会进
S3
、
S4
当然
也可能什么动作都没有(取决于
OS
的配置)
。其实这部分是因为大多
NB
都是
通过一颗
magnet switch IC
控制
lid
。
Lid
有两种状态
Open
和
Close HW
的线路
通常还会将它们用作背
光的始能信号
8 q8 {: `; |( B8 M
2.
Lid Open
Lid Open
就是通常上讲的开盖,
HW
会将这支信号拉到
EC
一根
p>
GPIO pin
上,
也就是说一旦
Lid
状态有变化
EC
这端就会收到中断
, EC
收到中断后会发
< br>SCI
通知
host
,
host
再去做其他的动作。可是通常第一次开机的时候
Panel
是打开
的,
NB<
/p>
并没有供电所以
EC
没法更新
lid
的状态,这时
EC
就会在进入
S0
的时
候根据
lid
这根
GPIO
的状
态(
H&L
)去更新
lid
的状态。在
OS
装载的过程中会
< br>主动去
call
BIOS
中的
asl code
去更新
Lid
的状态。
3.
Lid
Close
4 z2 z9 {
Lid Close
指的是合盖,
EC
处理的过程和
Lid Open
基本一
致,
区别之处在于
EC
记录
Lid
的状态有变化。另外在
< br>OS
下有一个选项可以设置
Lid Close
时
OS
可
以进行的动作
。如下图所示
如上图
1
所示
close l
id
可以选择的
action
有三种<
/p>
1. Do
nothing
) n3 V$$ S& W' c
A; f1 {. t' ^
2. Stand by
]0 d3 P) I9 m# P#
?* w( A+ I
3.
Hibernat
X; i) l! i! s! K2 y
这些功能是怎么实现的呢?完整的流程如下图所示:
* d4 }% T8 {+ {(
图
2
p>
就是完整的工作过程,步骤
5
是猜的。要知
道真正的做法恐怕只有问了微软
才知道
?
。
我所知道的
EC====>Ke
yboard
- B- u( W$$ E0 ]% B# f8
j
rd Introduction
Keyboard
是
PC
架构中的一个重要组成部分。
在常见的
PC
系统中主板上都有一颗专用的
8042
接口芯片
去处理
(
现在被集成进了
SB
中
)
,
8042
控制
keyboard
的整个工作过程,包括
加电自
检键盘扫描码的缓冲以及与
chipset
沟通。在
NB
上这部分工作都有
EC
负责,它有一个
keyboard controll
er
,它扮演
8042
相似的角色。<
/p>
NB
都有一个内置
Keyboard
p>
,这个
keyboard
是由
EC
控制的。
Keyboard
和
touchpad
都是
EC
p>
内置的一个部分,它们按照
ps2
协议工作
,
最终的数据通过
EC
送给
host
。常见的
102 key
的键盘如下图
1
所示:
2
.
3 0
Y8 A4 f. c6 p1 N
Scan code and Make &
Break
1 [) H. {: `* S( D0 R&
v
- m1 h- }5 f8 r# e
当键盘上有键被按下,
键盘将产生扫描码
(
scan
code
)
,
scan
code
有两种
Make
code
和
Break
code
p>
,
也就是通常所说的通码和断码。
每一个按
键都有一个唯一的
Make
code
和
Break code
。<
/p>
当一个键被按下就会产生
Make
code
,松开时就会产生一个
Break
code
。
Scan code
一共有
三套称之为
set1
、
set2
、
set3
,
PS2
接口键盘默认使用
set2
。
EC
收到
set2
scan code
以后会
将它转化为
set1
送给
host
。
Set1
的
scan
code
中标准按键的
Scan code Make
code
和
Break
code
都只有一个字节,
Make
code
和
BreakCode
的差别
就在最高位。
Make code
最高位为
0
,
Break code
最高位
为
1
。
A
的
scan code
如下图
1
所示
:
A key
Set1
Set2
Set3
$$ g6 D; U5 K, T6 ]9
R
图
2
Make
code
1Eh
1Ch
1Ch
Break
code
$$ F
x6 }
9Eh
F0h,1Ch
F0h,1Ch
Does
Keyboard Work?
Key board
功能虽然比较简单,
只是让用户可以输入一些字符而已,
可是它的工作原理却不简
单。
从一个键被按下到操作系
统识别它并送给其他的
driver
或者
AP
,
中间经历了很多道工序。
键盘
是一种矩阵结构,
每一个键都有一个行地址和列地址,
用户按下
键以后
,EC
获得该按键的
matri
x address
,
EC
将该
address
转化为
matrix val
ue
然后判断该键的类型是特殊功能键还
是标准按键,
然后采用不同的方法将
matrix value
转成
Set2
,
最后在转成
Set1 value
送给
host
,
host
收到就可以送给其它需要的程序了。其
完整的工作流程如下图
3
所示:
p>
4
.
Customized
- Q2 |- ?6 N. h! x, s) S
Hot Key
NB
上有一些被称为
Hot Key<
/p>
(热键)的东东,比如用户可以按
Fn+F4/F5
调整亮度等。这些
是如何实现的呢?既然
key
board
部分是由
EC FW
处理,
那定制这些特殊功能键就不是什么
难事了。
Fn
没有
scan
code
但是它有
matrix address
所以
EC
收到该键按下后置一个
flag
,
后续检测到
F1-F1
2
被按下后,
EC
发一个
Q_EVENT
(什么是
Q_EVENT?
后续会详细描述
?
)
给
Host,Host
就可以和
EC
p>
通信了。如此便可以定制出各种各样的功能了。
+ k- e&
z& N9 G8 }+ n
% u9 c/ H) I5
Y
5
.
6
N, t* B
K. S! j4
^
IO Port Command
Host
通过
60h
,
64h
这两个
ports
和<
/p>
Keyboard
进行通信,其中
60h
被称为数据端口,
64h
为
命令端口。
Host
对
E
C
发命令是通过
64h
port
实现的命令分别为:
Command
EDh
Description
设置
LED
。
Keyboard
< br>收到此命令后,一个
LED
设置会话开始。
Keyboard
首先回复一个
ACK
(
FAh
)
,然后等待从<
/p>
60h
端口写入的
LED
设置字节,
如果等到一个,
则再次回复一个
ACK
,
然后根据此
字节
设置
LED
。
然后接着等待。
。
。
直到等到一个非
L
ED
设置字节
(
高
位被设置
)
,此时
LED
设置会话结束。
EEh
诊断
Echo
。此命令纯粹为了检测
Keyboard
是否正常,如果正常,
当
Keyboard
收到此命令后,将会回复一个
EEh
字节。
p>
选择
Scan
code
set
。
< br>Keyboard
系统共可能有
3
个
Scan
code
set
p>
。
当
Keyboard
收到此命令后,将回复一个
ACK
,然后等待一个来自
p>
于
60h
端口的
S
can code set
代码。系统必须在此命令之后发送
给
Keyboard
一个
Scan
code set
代码。当
Keyboard
< br>收到此代码
后,将再次回复一个
ACK
< br>,然后将
Scan code
set
设置为收到的
Scan code
set
代码所要求的。
读取
Keyboard
ID
。由于
EC
芯片后不仅仅能够接
Keyboard
。此命
令是为了读取后所接的设备<
/p>
ID
。
设备
ID
为
2
个字节,
Keyboard
ID
为
83ABh
。当键盘收到此命令后,会首先回复一个
ACK
,然后,将
2
字节的
Keyb
oard ID
一个一个回复回去。
设置
Typematic Rate/Delay
。当
Keyboard
收到此命令后,将回
复一个
ACK
。
然后等
待来自于
60h
的设置字节。
一旦收到
,
将回复
一个
ACK
< br>,然后将
Keyboard
Rate/Delay
设置为相应的值。
F0h
F2h
F3h
/ V% h7 p3 R'
O! C
F4h
清理键盘的
Output
Buffe
r
。一旦
Keyboard
收到此命令
,将会将
Output
buffer
清空,
然后回复一个
ACK
。
然后继续接受
Keyboard
的击键。
设置默认状态
(w/Disable)
p>
。一旦
Keyboard
收到此命令,将会
将
Keyboard
完全初始化成默认状态。
< br>之前所有对它的设置都将失效
——
Output
buffer
被清空,
Typematic Rate/Del
ay
被设置成默
认值。然后回复一个
A
CK
,接着等待下一个命令。需要注意的是,
这个命令被执行后
,键盘的击键接受是禁止的。如果想让键盘接
受击键输入,必须
Enable Keyboard
。
设置默认状态。和
F5
命令唯一不同的是,当此命令被执行之后
,
键盘的击键接收是允许的。
Res
end
。
如果
Keyboard
收到此命令,
则必须将刚才发送到
Outpu
t
Register
F5h
F6h
FEh
中的数据重新发送一遍。当系
统检测到一个来自于
Keyboard
的错误之后,
可以使用自命令让
Keyboard
重新发送刚才
发送的字节。
Reset
Keyb
oard
。如果
Keyboard
收到
此命令,则首先回复一个
ACK
,然后
FFh
启动自身的
Reset
程序,
并进行自身基本正确性检测
(
BAT-Basic
Assurance
T
est
)
。等这一切结束之后,将返回给系统一个单字节的结束
码
(
AAh=Success, FCh=Failed
)
,并将键盘的
Scan code set
设置为
2
。
20h
准备读取芯片的
Command
Byte
;其行为是将当前
Command
Byte
的内容放置于
Output Register
中,下一个从
60H
端口的读操作
将会将其读取出来。
准备写入
< br>EC
芯片的
Command
B
yte
;
下一个通过
60h
写入的字节将
会被放入
Command
Byte
。
测试一下键盘密码是否被设置;测试结果放置在
Output
Register
,然后可以通过
60
h
读取出来。测试结果可以有两种值:
FAh=
密码被设置;
F1h=
没有密码。
设置键盘密码。其结果被按照顺序通过
60h
端口一个一个被放置
在
Input Regist
er
中。密码的最后是一个空字节(内容为
0
< br>)
。
让密码生效。在发布这个
命令之前,必须首先使用
A5h
命令设置
密码。
自检。诊断结果放置在
Output
Register
中,可以通过
60h
读取。
55h=OK
。
禁止键盘接口。
Command Byte
的
bit-4
被设置。当此命令被发布
后,
Keyboard
将被禁止发送数据到
Output Register
。
打开键盘接口。
Command Byte
的
bit-4
被清除。当此命令被发布
后,
Keyboard
将被允许发送数据到
Output Register
。
准备读取
Input
Port
。
Input
Port
的内容被放置于
Output
Register
中,随后可以通过
60h
< br>端口读取。
准备读取
Outp
ort
端口。结果被放在
Output Register
p>
中,随后
通过
60h
端口读取出来。
准备写
Outpu
t
端口。随后通过
60h
端口写入的字
节,会被放置在
Output
Port
中。
准备写数据到
Output Register
中。随后通过
60h
写入到
I
nput
Register
的字节会被放入到
Output
Register
中,此功能被用来<
/p>
模拟来自于
Keyboard
发送的数据
。
如果中断被允许,
则会触发一
个中断
。
60h
A4h
A5h
A6h
AAh
ADh
AEh
C0h
D0h
D1h
D2h
% Y8 f1 Q0 X'
O3 c
.
上面的表格就是
EC
支持的全部的
c
ommand
。
那么如何向
EC
发一个命令呢?在向端口
60h
,
64h
写任何信息之前,
EC
输入缓冲区必须为空。读取
64h
获得状态,然后检查
p>
bit1
,如果是
0
表示
buffer
为空可写,否则为满不能写入。
Example
:
+ H# P( U* Q- ^9 q/ h2 Y
in
2 8 y5 J5 Q- P
al,64h
test
al,2
* N$$ s5
c5 M' _/ U
R
jz
( n1 W2 X1 ?) T
H$$ i9 G
. m;
N
send_cmd
1 X,
L, {# p3 h5 Q2 U( f' W/ e
ret
send_cmd:
mov
bl,adh
3 a$$
v3 b7 ^/ H
a0 w0 d8
Q
out 64h,bl
如何从
EC
端读取数据呢?读取任何信息之前
,
必须检查控制器输出缓冲区状态,
以确定可以
读取一个字节。读取
64h bit0
如果是
1
表示
buffer
为
满可读,否则为空不能读取。
Example
:
1 k; ]6 h+ s
in
/ U& o0 Q. t
al
,
64h
4 m/ p1 O; j( O
test
al
,
1
s6 ], 5 M
jnz
read_key_ready
9 r4 h# N5 ]7 [$$ [; M) d! P
ret
4 e
, t
read_key_ready:
! C8 F9 ]! :
a- L' ~
in
% P* Z$$
b9 X1 g# `+ J
al,60h
( i7 4 X& h: v5 _
Status Register
(
状态寄存器)
的状态位如下所述:
Bit7: PARITY-
EVEN(P_E):
从键盘获得的数据奇偶校验错误
Bit6: RCV-TMOUT(R_T):
接收超时,置
1
) M: c i0
d* p- z& O' i* R2 v6 v
Bit5:
TRANS_TMOUT(T_T):
发送超时,置
1
4 Y' s! ]&
O. L6 Q K9 P
Bit4:
KYBD_INH(K_I):
为
1
键盘没有被禁止。为
0
键盘被禁止。
!
C5 `1 f( M+ @% s1 ~2 O) U5 v
Bit3: CMD_DATA(C_D):
为
1
输入缓冲器中的内容为命令,
0
< br>输入缓冲器中的内容为数据。
Bit2:
SYS_FLAG(S_F):
系统标志,加电启动置
0
p>
,自检通过后置
1
Bit1:
INPUT_BUF_FULL(I_B_F):
输入缓冲器满置
1
,
i8042
取走后置
0
' q' g/ X4 ~
G' F
BitO:
OUT_BUF_FULL(O_B_F):
输出缓冲器满置
1
,
CPU
读取后置
< br>0
6
.
Co-Work With
USB Keyboard
$$ I, E& 1 D& j#
a$$ f
大家可能会觉得
Usb k
eyboard
好像和
EC
没什么关系
,其实不然。
Usb
keyboard
在
Legacy mode
< br>需要将数据送给
EC
,由
EC<
/p>
在送给
Host
。
(借腹生子哈哈)
。
完整的流程如图
4
所示:
当
usb keyboard
有数据输
入,
BIOS
将数据转化并通过
D2
command
将数据送给
EC
,
p>
EC
通过
IRQ
1
通知
Host
,
Host
再下来读取
我所知道的
EC====>Batt
ery
1.
Battery
Information
w# c# g) [8 X
探测<
/p>
Battery
的信息是
EC
一个重要的工作,
OS
也要通过读取
ECRAM
中
的内容获取电池电量、温度、电压、充
、放电电流、
Battery
是否存在的信息。
那么
EC
如何获得这些信息呢?
Battery
通常是一个
smbus device
所以它会接
在
EC
的
一组
smbus
上,而
Battery
的
spec
上会给出
< br>Battery
的
smbus
的
地址以及读取
Battery
信息相关
的命令。
EC
通过向
smbus
上送
Battery
的
address
,
cmd
然后就会取
得相应的信息了。
2.
Battery Charge&Discharge
Algorithm
1)Charge
充电的过程是这样的,
AC in
的状
态下
Battery
插入,这时
EC<
/p>
会进入
precharge mode
进
行小电流充电。
如果
precharge
时间过长
(通常是指超过
一个小时)
EC
就会停止充电并且认为
Battery dead
,送给
host
一个
B
attery
Fail
的
Event
。在
precharge mode
如
果充电电流增大到特定的数值后
EC
就
会进入
fastcharge mode
快速充电。
Battery
充满后停止充电。在这个过程中
如
果电池温度异常
EC
也会进行一些处理。这就充电的简单过程。
2)Discharge
AC out & Battery in
这时
Battery
开始放电,
在放电过程中如果电量小<
/p>
Critical
Low
,系统处于<
/p>
S0
,
EC
会发
Critical Even
t
给
host
,然后
< br>OS
紧急关机。系
统处
S3,E
C
将会唤醒
OS
。
如果电量小于
BattLowLowled
将会被点亮
p>
(
Battery
Low Policy
会详述)
。另外放电温度也会被检测,如果温度过高将会做降频
动作
等。
, ~
H. Y6 f
,
O
3.
LowLed Policy
<
/p>
经常使用
NB
的话,大家可能会发现有一
个
led
很有趣。插入
AC
充电时会
看到一个黄色的
led
< br>被点亮,
电池没电了会看到一个红色的
led
亮,
有时还会一
闪一闪的。其实这就是
EC
导入的一个
Function
。黄色的
led
其实跟这个
F
unction
无关,它是充电指示灯,不过它和
LowLed
摆在一个位置。那么这个
红色的
Low
Led
什么时候会恒亮,什么时候会闪烁呢?当
Battery
的电量很低
的时候也就是小于
Batt
Low
时,
LowLed
就会恒亮。当
Battery
Dead
时
LowLed
就会闪烁,导致
Battery
Dead
的原因有
Prechage<
/p>
时间太长温度过
高或过低。
4.
Battery Even
Ba
ttery
在充放电的过程中,因为电量、电流、温度等原因
E
C
需要向
host
端发送
SCI Event
,用于
host
在特定情况下采取相应的补救措施。
1)BattChgEvent
当
Battery
< br>插入或者拔出时
EC
会发送
Ba
ttChgEvent
通知
Host
,
这时
Host
会读取
< br>ECRAM
获得
Battery
是否存在的信息并更新系统端的显示。
2)BattWarnEvent
当
Battery
的剩余电量到达
Bat
tery Warning Level
时,
EC
会发送
BattWarnEvent
通知
Host
,
Host
端收
到后会给出提示信息。
3)BattLowEvent
5
当
Battery
< br>的剩余电量到达
Battery Low Level
时
,
EC
会发送
BattLowEven
t
通知
Host
,而且这时
LowLed
会恒亮。
4)BattCrtEvent
(
d
当
Ba
ttery
的剩余电量接近
0%
时,<
/p>
EC
会发送
BattCrtEvent<
/p>
通知
Host
,
Host
收到后做
shutdown
动
作。如果在很长的一段时间里(几十秒)
,Host
还
没有
shutdown
,那么
EC
将会
Emergency
Shutdown
。
! V3 ^$$ H, ^: `! m5 @3
I
5.
How to Debug
Battery?
EC<
/p>
通过
smbus
不断的获取电池信息
p>
,
所以电池信息在不断的变化之中。
有些时
候为了澄清问题,
或者为了增加新的
Function
该如何
Debu
呢?我的做
法是使用
debug tool
将
smbus
停掉,
然后将一些符合条件的值通
< br>debug tool
写入
ECRAM
< br>中,
然后验证相关的
Function
< br>是否生效。
另外如果是需要给
BIOS
< br>发
SCI Event
,我会直接将
Event
填入
SCI queue
。然后驱动
SCI,
让
SCI
Event
被发送出去。
Peter
件
(31.66 KB)
2009-4-18 10:00
我所知道的
EC====>IDLE & RESET
MODE
1. What
’
s
this?
0 C* Z
m9
Y0 l0 o
IDLE
&
RESET
是
EC
的两种工作模式,从字面上看
IDLE
是空闲的意思而
RESET
则是复位的意思。
他们的使用场景各有不同,
IDLE
主要是用在
BIOS
更
新
NVRAM
区域,
RESET
则是用于
flash bios
的时候。
6
v, N$$ ^0 K5 Y9 j, W! E
2.
Why & How
?
?
IDLE Mode
3 r
k+ - q
BIOS
在
post
过程中会多次
进入
IDLE
Mode
,用于保存一些系统
设置,
以及保存硬件资源供
OS
获取。那么这些操作跟
EC
有什么关系呢?原因
是
N
VRAM
包含多个区域,有些部分保存在
CMOS
之中,可是还有很多信息保
存在
ROM
之中,很多时候
BIOS
和
EC
code
放在一颗
ROM
p>
之中,那么这时
就非常有必要让
EC
进入
IDLE mode
。另外
BIOS
在更新
NVRAM
的时候,
EC
端有可能通过
KBSMI
/KBSCI
以及其他一些接在
SB
上
的
pin
影响到
BIOS
(这
部分纯属猜测
?
)
p>
。
IDLE Mode
的实现过程是这样的
,
当
BIOS
需要跟新
NVRAM
时,它发命令告诉
EC
,
EC
收到命令后关掉
LPC
p>
(假设
EC
通过
L
PC
连到
SB
)
的
write protection,
然后
EC
保存寄存器的内容,
关掉中断只保留一个唤醒源<
/p>
(如
EC host command wake
up
)
,然后回给
BIOS
“
FA
”
表示进入
p>
IDLE
,最后一步
停掉
< br>8051 clock
真正进入
IDLE
。那么为什么要先回
”
FA
”
然后再进
IDLE
呢
< br>,
因为
进
IDLE
以后
8051 clock
停掉了,
EC
无法跑
code
了,这样
也造成了一个隐患,
有时
BIOS
收到
”
FA
”
就开
始更新
NVRAM
了,可是这时
EC<
/p>
还没有进入
IDLE
mode
(
EC
的速度和
hos
t
没法比)那就惨了,系统可能会
hang
住,我就遇到
过这样的
bug
?<
/p>
,所以
BIOS
收到
”
FA
”
最好
delay
一段时间。
BIOS
更
新完成后
给
66port
随便送个
p>
command
,
EC
就会从
IDLE
恢复了。
?
RESET MODE
A6 z%
|& g# B2 y
在
flash
bios
的时候
,BIOS
会发命令让
EC
进入
RE
SET
mode
,只所以称之为
RE
SET
,是由于
ROM
更新以后
EC
要重新开始跑
code
< br>,所以有一个
RESET
的动作。
RESET mode
的做法是
EC
停掉
sustain fetch
,
也
就是说
EC
不去预取
指令了,需要跑一
条指令才会去抓一条。然后
EC
关掉所有中断并关掉
Write
Protection
,让风扇全速
转(因为刷
BIOS
时能耗比较大温度会很高)
,回给
BIOS
”
FA
p>
”
,
接下来
EC<
/p>
就会进入死循环。
BIOS
通过
backdoor
修改
EC
register
让
8051
pc<
/p>
停在
0
地址处。然后
BIOS
开始
flash
,
flash
完成后
BIOS
< br>将会通过
back
door
修改
EC register
让
8051 reset pc
复位,
这时
EC
就会重新加载新的
code<
/p>
开始
run
。
9
K6 K! P4 N* C! z
% H0 m5 a0
e: H# O, `5 k
以上就是
IDLE &RESET
Mode
的完整过程。
我所知道的<
/p>
EC====>KBSMI&KBSCI
# y1 r- _; m* i4 c4 u* Q! `6
c
6 t9 V0 ?9 Q/ b3 K+ r1 _
}$$ D
1. Introduction
q6 V% _1 f* b0 _
3 l0 ]$$ ?0 X; }6 {$$ I
SCI
是指系统控制中断
,
为支持
ACPI
的操作系统提供系统管理,客制
化功能。
SMI
是指系统管理中断,由设备或者软件需要呼叫<
/p>
SMM
功能产生,使
CPU
进
入
SMM
mode
。基本上进入
ACPI
p>
mode
以后
SMI
就很少用到了,对于
EC
来
讲
SCI
和
SMI
则是
互斥的,一旦进入
ACPI mode EC
就只会发
SCI
。通常
EC
会
有两根
pin KBSMI&KBSCI
连接到
SB
,
EC
可以配置这两根<
/p>
pin
的属性,
决定
使用何种方式产生中断,比如
level
trig
,
edge
trig
,
pulse trig
,我
做的案子常常
配置成
64us
低电平的
pulse trig
。
; z-
i& x) J+ V. q3 S0 ~7 H1 _. d7 h
2. KBSMI#
7 D& l7 X0 k+ Q:
v
其实
EC
很少用到
SMI
,除了少数测试项比如
DOS
下
Fn+F2
下切屏等。不过
KBSMI#
的实现原理还是很有趣的
。它的原理是这样的:
EC
将一根
pi
n
接在
SB
上,而
SB
的
GPIO
有些具有
multi function
,可以配置成具有
SMI/SCI
的功能。
BIOS
code
在初始化时将这个信息宣告给
SMI Table
p>
。
一旦
EC
发了一
个
SMI,EC
接在
SB
上面这根
pin
的
# Z'
`2 P) Z' O
status
就会被置位,
SB
检测到以后通过拉接在
cpu
上
SMI pin
产生一个<
/p>
SMI
,
cpu
切换到
SMM mode
然后就会通过之前宣告的那些
pin
的
status identify
smi
Owner
,这时就可以认出是
EC
的
SMI,
随后通过下
command
给
EC
读取
SMI
event id
,并通
过该
id
去调用相关的
method<
/p>
。
8 F+ U' D* V' _#
v
3. KBSCI#
8 k;
s. y* v7 b3 j
?
Q_EVENT
; ~( y.
{' t2 Z3 {$$ n$$ V
w4
B
所谓
Q_EVENT
指的是
OS
收到
EC
的
SCI
后,
OS<
/p>
通过发
84hcommand
给
EC
读取
EC
Ram
中的值,这个值被称为
Q_EVENT id
。这也是
Q_EVENT
得名的
< br>原因
(
Q
是
Query
的缩写,
而
84h
p>
就是
Query Embedded Controller
)
。
然后
OS
中的
asl code
会根据该
id
去调用
_QXX()
如
下面的
code
所示
,
这里的
XX
指的就
是
EVENT
id
。那么
EC
什么时候会发
Q_EVENT
呢
?当
AC
、
Battery
in/out
,
LID
open/close ...
// AC Status
Changed
% k. F( t/ X5 H; l1
Method(_Q83
8 k) N
k& T4 i& W4 A
Store(0x83, DBG8)
! v& u) L'
]: E! P9 [1 |5 O
J
Store(0x00,
Local0)
$$ D4 I- E3 b6 k) R: '
V
# p/ Y: M% k
H/ i3 `& b& {% |
Store(POWS, Local0)
5 P$$ S3
p9 z* X9 d8 F, Q! U* }# e
If(LEqual(Local0,1))
, r5 r6
|$$ G6 _# O1 t; j1 d-
{
9 ]: a# k: f!
?) K% L+ {4 R4 f' T
Store(1,_
2 / f' T: z6 q. K8
P4 z
else{
1 u/ _% F
Store(0,_)
' m9 e5 d2 T
I# ]1 ]1 `
& d7 V! x
O:
m
}
& q! }: k* v;
B3 f# n. K
Notify(_1,0x80)
-
a0 s& m) y+ N+ t
Notify(_PR.P001,0x80)
2 ~5 I;
S8 O: l7 c; A, ?
Notify(_PR.P002,0x80)
:
H
- L- b% t1 ?
K% K( I- U. A# Q+ ~
}
+ D' 7 |6 A
那么又有一个问题,那就是
OS
怎么知道这个<
/p>
SCI
是
EC
的
呢?请看下面的
asl
code
你就
会明白了
?
, k$$ q0 Z( o. o* F2 l/
O; k
Device(EC)
{
6 M0 E4 m+ f2 L(
, l8 f: Z* w% Q4 R%
Y
Name(_HID,EISAID(
4 1 P7 {; M: ' I
Name(_GPE,0x06)
//
KB_SCI
, {- `$$ @7 t, I& T2
q+ X! s1 v7 f! X0 S
...
}
在
Device EC
里面宣告了
p>
EC
的
KB_SCI
接在了
SB
的哪个
pin
上,
这样
OS
识
别
EC
SCI
的过程
就和前面的
SMI
异曲同工了。最后一个问题
< br>OS
如何检测到
SCI
产生了呢
?前面
SMI
已经说过
SB
有一根
SMI pin
接到
CPU
,可以让
CPU
产生
SMI
,可是
SCI
并
没有这样的
pin
,那么
CPU
怎么知道
SCI
产生的呢?我
想到了两种可能:
polling GPE
register status bit
。
4 a' N*
F- Z. b1 G0 u1 |) k% ^
通过
8259
或者
API
C
,产生
IRQ
。
( b9 q5 {6 u4 R% r$$ C0
Chipset spec
印证了我的想法
,SCI
可以配置成通过
8259/APIC
的方式产生中断,
而且中断向量也是可配置的如下图
1
所示:
b4 U+ n5 _8 l% p
2 k/ X. @' C& ?4 D8 N2 V
8 f! j9 c! h!
Y1 J+ A
- P5 S; _7 d- ?& v2
L9 ?/ e
) V- J( X% T( r+
x
8 f8 J3 M4 @3 Z
T( W: Q6 J4 V:
k6 j# q
图
1
p>
BIOS
在设置好该寄存器后会将
SCI
INT
信息存入
ACPI
FADT
中,这样
OS
就
可以通过
FADT
获得
SCI
使用的中断号码,
从而能够在
< br>SCI
产生时处理该中断,
这部分请参考
ACPI SPEC FADT
部分。下图
2
是使用
ACPIVIEW
看到我的工作
机的
FADT dump,
如图
2
所示我的机器
ACPI
使用
APIC INT 9
。
7 u: d% f7 U3 u: K5 [% Z
0 x- ?; T/ H5 r1 X0 G/ V3
d2 f
& P
, E. h;
|4 m( U- v, w5 Y
% Q# i7 N& _
Q
图
2
3
j1 [$$ j( i6 t
{
?
?
GPE
9
Q' {% U1 e. K# L) n
GPE
其实是属于
BIOS
的范畴了(当然
Q_EVENT
也是
GPE
的一种)
,其他部
分跟
EC
并没有多少关系,不过既然讲了
SCI
,就顺便提
提
GPE
。所谓
GPE
指
的是
ACPI
定义的一个<
/p>
general-purpose
event
namespace
与
SB
中的
GP
registers
相对应。
GPE
register
包括
GPE_ST
S
和
GPE_EN
两个部分对应该
p>
GPIO
的
status
< br>和
enableEvent
的触发分
Level
和
edge
两种,分别
对应
ACPI
Method
_LXX(),_EXX(),XX
分别代表具体的
GPIO
pin
。
所以当系统在
S0
一旦相
关的
GPIO
status
有变化并且
SCI
p>
enable
了,那么就触发
SCI,
p>
于是形如
_LXX(),_EXX(),
就
会被调用了。
That
?
s all!
我所知道的
EC====>Auto
Detect
1.
What
’
s it?
+ o1 h7 J: `4 P
.
P
. l( C& ?
:
`/ b8 l2 W7 Q8 E9 H9 [
r
G! P$$ f
Auto
Detect Function
指的是
BIOS
和
EC
配合探测
NB
p>
上存在哪些
device
。常
见的
device
如
wla
n
,
bluetooth
,
camera
,
3G
等
都是
USBDevice
,它们都接
在
usb port
上面,而始能信号则是由
EC
控制。探测结束以后,当用户按下
hot
key
时
EC
可以根据探
测的结果给出相应
Function
。比如当
< br>wlan
不存在时,该
hotkey
的功能可能就是
search key
功能。
2. Why need it?
! m( Y4 X* K
[& }- t
1 p, P)
|( k
Q$$ v3 r
这个功能的存在主要是因为同一个
model
在出货的时候因为客户要求,或者市
场地位的不同可能配置会不一样,比如搭配不同的<
/p>
cpu,
上不同的外围
Device
p>
等
等。这样
EC
就
需要知道哪些设备存在,否则就会出现漏电以及配合
NB
上层软
体出错的状况。另外
Auto
Det
ect
还有一个功能就是它能够保持
device
的前一
次的开关状态,
这样只要没有断电就算是下了
S5
再回到
S0
,
这些设备还会保持
下
S5
之前的状态,这样可能会比较人性化一些。
$$
A! O: @2 f1 ~2 U) ^( Q# Z
3.
How to implement
?
9 x$$ {9 D
F; c%
7 d1 P1 n/ J
)
Q! u! U. s& u' K* W) h* }
Auto
Detect
听上去挺神秘
,其实实现起来也还是挺简单的
?
。因为这些
< br>device
都是接在
usb
port
上,
如果它们
power
on
那么
usb
port
上的
Connect status
就
会显示
device is
present on port
,
如此
BIOS
就可以检测出设备存在与否。
具体
< br>来讲是这样首先
BIOS
要从
E
C
读回所有
device
的上次
power status
,
& u! 6
u! g) D%
l1 l
然后<
/p>
BIOS
发命令让
EC power
on device
,
BIOS
读取相
关
usb port
上的状态确
定
p>
device
是否存在;接下来
BIOS<
/p>
回填探测到的设备存在状况到
EC
相应位
置;
最后
BIOS
将读回的
power status
送给
EC
,
EC
根据这些状态给
de
vice
送始能
信号,这样
devic
e
就保持了上次的状态,这就是
Auto detect
的完整过程,完整
流程如下图
1
所示:
# s: `
我所知道的
EC====>Uart
# ) u, X/ t& F
m5 x, M
1. Introduction
- ^& u* k) Y!
h
k
0
I4 ?
“没有输出之前调试是一
门艺术,
有了输出以后调试就是一门技术!
”这句话准
确的道出了调试程序时能够查看运行状况和环境信息的重要性;
尤其在<
/p>
FW
环境
下
de
bug
手段非常有限,
uart
是几乎
所有
FW
都会采用的
debug
方式。
Uart
全
称
是
Universal Asynchronous Rec
eiver/Transmitter
即通用异步收发器
(异步
串
行通信口)它包括了
RS232
、<
/p>
RS485
等接口和总线标准规范
,
p>
它作为一种低速通
信协议广泛应用于通信领域。
# F: k8 S0 o6 ]; y)
r
2. Hardware
Interface
7 _* Z3 ^! h
! C. v. f0 {
n5
S- f
Uart spec
定义了
非常多的信号,但
debug
过程中通常只需要接三根信号
p>
RX
、
TX
、
p>
GND
就可以了,故常见串口
debug<
/p>
线路如下图
1
所示:
& ~2 i1 D* {+ L6 P
串口读写数据时数据送往内部的<
/p>
fifo
,如果
fifo
满了数据就会按照顺讯逐个
bit
送往总线。数据读写
的时序和格式如下图
2
所示:
; [- Q1 }2 - S;
e+ 0 c. P
串口通信常见的波特率有
19200
,
115200
,
9600
,
4800
< br>,
2400
,
1200
Bps
,波
特率可以通过寄存器配置。
( o9 u+ }2 R) v0 Y# o
3. How to
Implement
) @. G
3
a1 u# U, L. A: d
前一段时间有同事问我一个问题
:
”
为什么我们
EC
之中使用
printf
、
puts
、
putchar
这些函数输出字符,字符会送给串口呢
?
以前写的
DOS
下的程序是输出到终端
的呀。
”
,他的困惑不无道理,为什么呢?不
要着急,让我来告诉你其中的奥秘。
若干年以前,我曾经在
ar
m9
上面
portting
过
bootloader
(
u-boot
)
,u-boot
也有
一个串口
debug
的部分,所以我就碰巧大致翻了翻这部分的
代码,关注过这个
问题
?
.
先来看看
printf
的实现吧
< br>,
下述代码是
u-boot
中的
printf
code
:
$$ [; p0 ?: a3 a.
e9 v/ o) ?8 D
voidprintf
(
const
char
*fmt, ...)
{
6 ]# S+ m7 L,
~+ M! c: x/ M3 `
@
4 Z' T' d4 w6
D- G9 V) Q6 L
va_list
args;
- i& N! Q0 {& j4 O( N! y%
L
uint
i;
char
printbuffer[CFG_PBSIZE];
)
_. N& M% X6 & P* T- o. _
va_start (args, fmt);
9 p/ Y
. M+ o* F! r! q1
@
/* For this to work,
printbuffer must be larger than
* anything we
ever want to print.
2 g6 k&
U0 Z1 c$$ Z
*/
i = vsprintf
(printbuffer, fmt, args);
va_end (args);
/* Print the string */
puts (printbuffer);
5 ]% B
|( q7 Y
}
4 t! P2 H% E4 d2
b
注意看红色部分的代码,
printf
先使用
vsprintf
处理输入的参数,并将处理后的
结果存入
pr
intbuffer
,随后调用
puts
输出。那么我们看一下
vsprintf
的实现吧,
下面的代码是
vsprintf
的
code
。
/ W6
g! n& ~5 Y7 g
7 U# u1 p2 {:
P& u
intvsprintf(
char
*buf,
const
char
*fmt, va_list
args)
. q) I$$ {* m) F' n/ l
+ `4 U: K( t
{
int
len;
. {' p1 t& W' C; I9 T
unsigned
long
num;
int
i, base;
+ r6
o4 X1 ~; B- z* @/ s4 o/ g
char
* str;
$$ q0
q' ' w
[
const
char
*s;
- e: ]1 D6 J( S/ ~
int
flags;
/*
flags to number() */
: [9 u-
t9 s8 u1 Q/ l2 |
int
field_width;
/* width of output field */
int
precision;
*
b$$ H3 {# U) E% R5 O
/* min.
# of digits for integers; max
' E5 F- }9
K/ G2 d
number of chars for from string
*/
4 Q! E6 c1 M1 a( p( E: k
int
qualifier;
/*
'h', 'l', or 'q' for integer fields */
4 ~) Q5 }. v$$ `7 1 n) H: `
for
(str=buf *fmt ++fmt)
{
0 i( G& & X; 9 y6 e4 P
if
(*fmt
!=
'%'
) {
1 j4 M:
G7 a5 V2 f
*str++ = *fmt;
- U0 o4 E2 Y)
a* 2 [: h
continue
;
}
/ `( U2 {, B#
f
3 ?- {) c, u4 G/
c
/* process flags
*/
flags = 0;
repeat:
- o% Q4 S$$ A$$ ^( N)
O( f
++fmt;
2 K$$ P4 |; S6 m1
M
/* this also skips first
'%' */
6 D4 C+ d6 ]0 a; ]/ P% G.
q
switch
(*fmt)
{
case
'-'
: flags |=
LEFT;
goto
repeat;
# # M: _0 j' Z6 _
V9 X
case
'+'
: flags |=
PLUS;
goto
repeat;
1 x, j3 `
Z' S
l
}$$ F' M
case
'
'
: flags |=
SPACE;
goto
repeat;
) P) s5 ]- g0 e&
`.
_2 A! ?
case
'#'
: flags |=
SPECIAL;
goto
repeat;
0 R# P& I2 j:
Q8 b,
y
case
'0'
: flags |=
ZEROPAD;
goto
repeat;
2 }+ H7 T. K0 o-
s
}
/* get field width */
field_width =
-1;
! d% A( b* x2 s- v7 `
if
(is_digit(*fmt))
) E9 u6 J:
g8 A
field_width =
skip_atoi(&fmt);
+ c& M) V0 f- u0 H2 b$$
C: F
else
if
(*fmt
==
'*'
) {
3 R9 j!
r3 }* F( n& l4 v! y
( ?3 O4
E$$ x) n/ j3 B9 @
++fmt;
2 m2 X4 J1 g4 N+ b.
e
/* it's the next argument
*/
/ D; K6 z& _/ }
( i( |& D0 A
R
field_width = v
a_arg(args,
int
);
'
c( o
r1 _; Y
if
(field_width < 0)
{
# T. @% H2 M4 J2 q7 W9 -
z* N- o
field_width =
-field_width;
1 z& r/ c2 U. d, {6 ],
J
5 y. M( U+ d- S& Z0 q( x2
X+ w* b/ P
flags |=
LEFT;
; h+ @/ ?7 C) p- y' x+ h.
j
) e6 L1 U7 C. S5
r
}
, W/ X, E6 {+
V: S5 P
s. }
}
% x$$ b3 z! Q'
S1 A
P: C* I# y! h8
E
+ N7 O4 [( h+ N4
U
/* get the precision
*/
precision =
-1;
if
(*fmt
==
'.'
) {
* P# ^+
N+ b) u) w7 c
++fmt;
/ O7 |2 ]+ `0
J
' g& O: Z1 B7 O& [%
h
8 H+ ]+ H0 h8 e- m&
[
if
(is_digit(*fmt))
- v' H5 s2
_0 i5 S3 H' w
precision =
skip_atoi(&fmt);
else
if
(*fmt
==
'*'
) {
% [2 z*
q# W7 t9 B
++fmt;
/*
it's the next argument */
; c) j. A* k- ~0 p
precision = va_arg(args,
int<
/p>
);
( t
?) k/ k2 r5 ]
}
if
(precision < 0)
precision = 0;
}
% C$$ N+ w( J% Y% I: _* Z$$
d
- y6 U* c& L, M0 q- j+ r.
l. d# J
/* get the
conversion qualifier */
qualifier = -1;
!
m: `: Z+ Q; B9 p0 C1 I
if
(*fmt
==
'h'
|| *fmt
==
'l'
|| *fmt
==
'L'
||
7 c- Y5
G$$ F; U
* D
`: l! {
j
*fmt ==
'Z'
||
*fmt ==
'z'
|| *fmt
==
't'
||
4 u% C# F, E' v6 n8 M%
J
*fmt
==
'q'
) {
+ u: c' k( Q$$ w, [9 S9 @
qualifier = *fmt;
! H0 g) Y#
7 M
if
(qualifier
==
'l'
&& *(fmt+1)
==
'l'
) {
qualifier
=
'q'
;
++fmt;
}
8 E3 x; c0 @3 P
++fmt;
/ f( e
p( l* u'
F2 g: z/ L# c
}
1
L- Y) z' }
s0 1 I1
w
/* default base
*/
! M! d) ?
base = 10;
5 Y8
H2 @7 D2 / U+ u0 p
switch
(*fmt) {
#
{7 I6 C& n0 ~: e3 G; ~7 [8 c
case
'c'
:
if
(!(flags &
LEFT))
while
(--field_width >
0)
*str++
=
' '
;
' {. }4 0 ~(
l3 t
*str++ =
(
unsigned
char
) va_arg(args,
int
);
/ C2 n- l% z3 U*
^
while
(--field_width > 0)
! U) Z#
M- v/ n, D
*str++
=
' '
;
continue
;
case
's'
:
7 w6 z% ^9 O' K; |$$ u
s =
va_arg(args,
char
*);
. J: R& P( ~; |. T% G6 o
if
(!s)
s
=
;
len = strnlen(s,
precision);
if
(!(flags &
LEFT))
while
(len <
field_width--)
8 f2 G+ P7 Q,
c# U% U' w
*str++
=
' '
;
- ~# V+ U+
o6 y' ]7 `
for
(i = 0; i < len; ++i)
*str++ = *s++;
while
(len <
field_width--)
X5 M5 C8 u: J$$ b1 R/ x. _4 A
*str++ =
'
'
;
8 Z0 [& F1 b: G: 2
G
. W
continue
;
! A# t
case
'p'
:
if
(field_width == -1)
{
8 d& m, ?. O
A,
P
c
field_width = 2*
sizeof
(
void
*);
flags |=
ZEROPAD;
7 _9 D( E# B, }6 N6
{
' ~# G( J0 D+ B*
M
}
6 d5 L9 k: j'
H3 }
: d- Q
str = number(str,
+ b; k& _$$ B! Z1 L
(
unsigned
long
)
va_arg(args,
void
*),
16,
$$ t4 x& t+ O( I+ D+ ]5 |5
Y
0 w7 |) k7 B4 ?
field_width, precision,
flags);
8 O3 h; s, h8 y$$ Y5 ?4 g* D5 ]#
]
continue
;
% f) M,
`% [
|
5 T: ~( @5 b$$ g2 y, @
case
'n'
:
! W; w8 R& L)
Z
' h1 Q0 ! G
[1 V
5 A4 k' q#
l) {) G4 |2 }
if
(qualifier ==
'l'
)
{
long
* ip =
va_arg(args,
long
*);
; |3 s7 [
p( o
*ip = (str -
buf);
}
else
{
9 {3 r0 ~' j% ~: S9 G0 Z! m
int
* ip =
va_arg(args,
int
*);
% t; e: F( k6 {5 h' l)
j
*ip = (str -
buf);
, f. z+ ~* l
+ N7 f/ n* y$$ k! d* D
}
continue
;
# Z8 B: e8 l5 M8 `
case
'%'
:
) b* @3 L- y)
M# x% c
& P: o1 K0 F& B)
g
*str++
=
'%'
;
continue
;
/ i$$ u)
P3 ]- y+ O
% s$$ |1 c( I
h# P& b- }; h
/* integer number formats - set up the
flags and
case
'o'
:
+
n5 d) j# j: n6 b7 }
base =
8;
7 T) s0 S: L2 z1 m( _
break
;
0 }* I( J)
J3 t, y; O/ w: _
case
'X'
:
( m3 v* U
w4 @; c8 I
flags |=
LARGE;
w. B0 j-
c0 p: x4 O! W- a
case
'x'
:
'
T+ i6 w( z+ r& n' }
base =
16;
break
;
1 h8 R
j- |
u% B/ u' Z* p
case
'd'
:
case
'i'
:
4
p: j2 o% b/ J
flags |=
SIGN;
- b' |. }- m9 x
case
'u'
:
, [$$ X. K! L0
1 ~5 |& V
break
;
4 g& D5 V:
H+ R* C6 W
- `( e1 s. h$$ g$$
Y
default
:
% a+ I- Q# h6 u% q9 C3 h( Y
*str++
=
'%'
;
if
(*fmt)
. ?& A; t& h( `. @( v$$ r$$ }
*str++ = *fmt;
6
D, x& Y% {9 ~9 t
]0
N
3 n
else
0 w, [/ ^8 O: e' X6 @(
a
3 W( O) x, D& ^! d' |: Z#
w1 s
--fmt;
continue
;
}
if
(qualifier
==
'l'
) {
9 [5 Q6 a
J, L# Y+ u! q+
+ c9 y7 p) p' l3 d: N0 b
|5 D
num =
va_arg(args,
unsigned
long
);
$$ {1 h3 x0 L4 r+ s+ R9 R
}
else
if
(qualifier
==
'Z'
|| qualifier
==
'z'
) {
( l5 {: t1 l8 i- D* `( j
num = va_arg(args, size_t);
1
q# E: a& r- ^* s
5 Y4 B0 n3 D& {&
M6 U2 j
}
else
if
(qualifier
==
't'
) {
* v, f/
c* {
b4 R: X) K
: x; g, ]: Y
num
= va_arg(args, ptrdiff_t);
!
P
}
else
if
(qualifier
==
'h'
) {
}4 Z5 Q7 B( U5
E
num =
(
unsigned
short
) va_arg(args,
int
);
% F6 d: Q/ m$$ E+ W$$
Y1 V9 A
if
(flags & SIGN)
# H; V) E, v-
T' W3 [$$ P# Q
num =
(
short
) num;
}
else
if
(flags &
SIGN)
; x
o9 k0
q5 b3 F9 E
num =
va_arg(args,
int
);
. n1 H- l0 n
X
a; ^4 ^
else
7 n3 J7 c
C3 A
) v1 p1 ])
D3 F. n0 l( q8 a
num =
va_arg(args,
unsigned
int
);
' F$$ P9 v
p8 B$$
u% N
str = number(str, num,
base, field_width, precision, flags);
8 K5 s& [; F
?8
c4 O& W
}
1 k5 ?:
D; R, A
0 Q. W4 ~$$ O- y# K%
J/ V# u8 ]
*str
=
'0'
;
return
str-buf;
$$
M6 s4 L4 _) a/ R
}
上述代
码大致的意思就是解析
fmt
字符串,并据此分析计算
args,
最后将结果写
入
buf
之中,有兴趣可以自己单步
debug
玩一玩。下面我们来看一看
puts
的实
现代码:
9 y9 a1 X- u# k' i
8 j7 B$$ L3 r- T0 ^& f+ Y+
U
voidputs (
const
char
*s)
{
+ f/ Y: @1 v( @
[3
o
#ifdefCONFIG_SILENT_CONSOLE
9
g#
H. m- & X7 D)
c
if
(gd->flags &
GD_FLG_SILENT)
return
;
Y9 X- }% V5 L
#endif
if
(gd->flags &
GD_FLG_DEVINIT)
{
1 A- ?# b$$ U* S- U; h+ r5
m
/* Send to the standard
output */
- R% x/ H# a3 k0 Z# O%
m
( ^: a6 r6 X/ n8 R;
}
fputs (stdout,
s);
* j5 L% q5 H6 t0
l
}
else
% w& ~+ L-
V, K( t/ v
{
/* Send
directly to the handler */
serial_puts (s);
: T! h' ~8
A) D( o, o
}
& c& ~/ i
}
8 w) c7 v8 l# F8 m8 ~6 U+
D6 K
这段
code
一出问题就明了了
?
p>
,puts
函数会先检查
GD_FLG_S
ILENT flag
如置
起就什么都不干直接返回,
silent
就是安静的意思嘛
?
,
而后会看
GD_FLG_DEVINIT fla
g
如置起,那么输出到
stdout(
也就是通常所说的终端
)
,
否则就是红
色部分的代码了,这里就是问题的关键,它会调用
serial_puts
输出
到串口了,到
serial_puts
这里就已经很明了了,反正一不做二不休,我们一追
到底看个明白
(
我参考的是
s3c24x0
的
code):
voidserial_puts
(
const
char
*s)
5 X% |
@: E
`
{
0
h: G
q
4 f
_serial_puts(s, UART_NR);
}
voi
d_serial_puts(
const
char
*s,
const
int
dev_index)
7 *
L% f5 ?% m0 h( X/ |! B: {
{
while
(*s)
{
; u( W4 E/ L+ l
4 n!
?/ ^0 X3 f
_serial_putc
(*s++, dev_index);
2 z4 r5 w% F% m1
B
}
}
7 w8 n8 ) n$$
k
/*
* Output a single byte to the serial
port.
1 }3 r9 |, X&
A
*/
void_serial_putc
(
const
char
c,
const
int
dev_index)
{
- l7 T2 A8 e)
{! {. S
S3C24X0_UART
*
const
uart =
S3C24X0_GetBase_UART(dev_index);
#ifdefCONFIG_MODEM_SUPPORT
;
s; T; P6 I) L: p, m; ?
if
(be_quiet)
return
;
E- _# I
#endif
$$ k6 J( B- 7 R4
H
$$ }- V$$ ]# S: @( K9
L
/* wait for room in the tx
FIFO */
. ]7 E& t3 j8 w+ E# T
while (!(uart->UTRSTAT &
0x2));
s( A$$ ?3
X+ y% t& l- r& _+ I
9 X6 K1
E2 c. _$$ [3 p$$ b
#ifdefCONFIG_HWFLOW
/* Wait for CTS up */
3 t; g&
Y& n4 }1 }! L8 d4 Y
while
(hwflow &&
!(uart->UMSTAT & 0x1))
s
s* S, U#
n
;
&
y: 1 z
G! I
#endif
H5 U# P; _7 }
uart->UTXH = c;
4 N( @. H, p:
m! u; @) ~: Q- m; w% C
& [9
T: |' @0 q- ^* ?
/*
If n, also do r */
if
(c
==
'n'
)
$$ q. V$$ I7 C6 o
serial_putc
(
'r'
);
}
' `8 q8 o( D. A# o9
i
图穷匕见,秘密终于揭开,红色
部分的
code
已经说明一切了
,最终
是通过操纵
s3c24x0 uart register
将数
据抛出去的。经这一追
printf
、
puts
、
putchar
都解决
p>
了
看完上面的分析之后,大家应该不会再困惑了吧!
: Z% T
N/
c6 N/ t
That
?
s
all!
+
我所知道的
EC====>SPI
2 N0 x( _4
Q$$ u, [
1.
Introduction
/ C* u& j$$
' _+ Y: |: @5 u
V( m5
m
SPI
全称为
Serial Peripheral
Interface Bus
即
串行外围总线。它是由
Motorola
制定的四线式全双工的同步串行数据通信标准。
spi
允许
mcu
和
各种外围设备进
行全双工的串行通信。常见的
spi
device
有
flash rom
,
触摸屏,
LCD
等。它有
比较高的传输
速率,传输速度通常可以达到几
Mbps
。
spi
采用主从模式。通常
master
< br>只有一个,但是可以有多个
slave
,多个
slave
通过片选定址。
2. Hardware
Interface
Spi
接口如下图
1
所示,通常就四根
pin
,
EC Chip
有按
照
Motorola
的经典命名方式将这四根
< br>pin
分别称为
MISO
、
p>
MOSI
、
SPICLK
< br>、
SPICS#
。不同的
IC<
/p>
厂商
pin
命名的方式可能会有不同,比
如有些也会命名为
SCK
、
SDO
p>
、
SDI
、
CS#
等。
$$ Y7 [- n# U& C- P) t
y
)
Z! n
d4 Z7 {6 ]
m4 Y
其中
SP
ICLK
提供通信所需要的
clock
,
clock
太重要了,没有了它,那就全乱
< br>套了,什么时候开始、结束,何时是有效数据,何时为跳变都无法分辨,由此可
见
SPICLK
是非常重要的同步时钟信号。
SPICS#
是片选信号,如果要使用某一
个
slave device
首要做的就是先片选该设备,也就是将该
SPICS# pin pull
low
。
MISO master input slave
output
反过来读就是
output slave
input master
,正反
读都讲的通,
MOSI
也同解
Motorola
真是太牛了!名字取得这么好。当
master
向
slave
发送信息时,
mater
将数据送到
MOSI
上,
slave
从
MOSI
上读走该信
p>
息,如果
slave
要回信息给
master
,
slave
就会将数据送到
MISO
,
mast
er
从
MISO
上获得信息。
spi
支持单
master
单
slave
,
单
mater
多
slave
模式
,
在
NB
上我们常用的就是将
BIOS
rom
通过
spi
接在南桥,或者接在
EC
的
p>
spi
接口,
都是单
master
单
slave
的模式。
因此我们只讨论这种模式。下图
2
是我手上的
< br>NB
专案的线路,
EC
spi
接口上接了一颗
w25x80 flash rom
p>
,我们后续的讨论将
基于这颗
IC
。
& u' L0 U
d. ^1 Q
) L$$ @)
d. B/ k3 @& f
+ E
k+ N0 |* i' B
1 r, p
3. SPI Instructions
为了方便的操纵
slave
device
,
slave
device
定义一些
instruction
s
。这些
instructions
包
括了操作
device
的基本操作如:
读数据,
写数据,
擦除数据等等。
W2
5x80
这颗
IC
的
< br>spec
中定义了
enable
disable
status
register
status register data read read dual
program
erase
10.
block
erase
erase
down
e power down
manu/devid jedec
id
等具体可以参考
w25x80
spec
。对一颗
flash rom
我们常用的操作就是通过写里面的内容完成刷
bios
的功能,
而刷
bios
之前
spec
规定还需要做一个
erase
的动作,将
rom
中
所有的内容清为“
< br>1
”。另外有时无法开机时,我们还需要读取
flash
rom
的内
容判断出错的原因。针对上
述讨论我们只需要如下几个
instructions
即可完成
任
务。
& }; @3 T
Z# ?* X' a+ }
?
Read data
Read
data
指令可以从
flash rom
中一次读取多个字节,执行该指令需要先片选
SPICS#(
将该
pin pull
low)
,然后送
read
data
指令给
spi flash rom
< br>,随后将
24
位的地址按照
MS
B
格式分成三个字节
A2,A1,A0
,然后分别送给
spi flash rom
,
然后就可以从
SPIDAT
中读取其中该地址上的数据
了,
地址会自动累加,
该指令
会一直读
下去直到将
SPICS# pull high
。时序如下图<
/p>
3
所示:
0 W1 c& C1 _! n2 B4 q! V# C.
f
; X- B3 h& b:
M3 ?8 j
?
Sector
erase
; m8 Z: G7 A) h# k- _9
g
Sector
erase
指令将
flash
rom
的指定的
4kBytes
内容全部清为‘
1
’。执行该
指令之前要先发
< br>write enable
指令而且
status
register
的
block protect bits<
/p>
必须
要清‘
0
’
,否则
sector erase
指令将不会执行,做完上述准
备工作以后,仍然
要做的是片选
SPICS#
< br>,然后送
sector
erase
指令给
spi
flash
rom
,随后按照
MSB
格式送
24
位
地址
A2,A1,A0
。
要注意的是判
断
sector erase
指令是否完成要
< br>使用
read
status
register
指令检查
BUSY
位
,一次指令完成要将
SPICS#
pull
high
。时序如下图
4
所示
:
, V( b: S& o% @1 A( Q2 ^
d# s
2 K- w8 U9 {5 W) `2 H
?
Page
program
n7 T0 X/
U
Page
program
指令允许一次写特定地址开始的
256
个字节
,前提是该区域必须
被清为‘
1
’,所
以要先发
sector
erase
指令将
flash rom
清为‘
1
’然后才能执
行
page
program
。
执行
page
program
之前要先送
write
enable
指令而且
status
register
的
block
protect bits
必须要清‘
0
’,
接下来送
page program
指令并给
出
MSB
格式的
24
位地址
A2,A1,A0
< br>,
后续将数据送到
spi bus
上,
如果数据小于
256bytes
,则只修改特定的字节数,如果大于
256bytes
,写的内
容就会回绕到
开始地址的头部。可以通过
read
status
register
指令
检查
BUSY
位确定指令是
否执行完毕
,一次指令完成要将
SPICS# pull high
。时序
如下图
5
所示: