AMD CS5536是一款很流行的嵌入式处理器,在基于它的架构上,可以做成各种小器具,然后如果操作系统是linux,且内核低于2.6.18的话,据我所知,它不能实现软关机,也就是说调用shutdown之后机器无法关闭,怎么办呢?最近我搞到一块AMD CS5536的板子,想自己做点东西,可无奈发现它在linux下无法关机,按电源也必须4秒,索性只好将开关做成纯电气的而不是电子的,也就是说按下开关直接切断电源,然而这决不是长久之计,最重要的是要实现软关机,即使实现不了也要实现按下电源立马关闭,也就是说不用再等4秒。
谁让咱是搞IT的呢?自己动手的乐趣不亚于厨师为自己做一桌子菜的乐趣,马上下载了CS5536的手册《AMDG_CS5536.pdf》(网上一搜便是,第一步如此顺利),接下来就是漫无边际的“浏览”了,浏览过后终于找到了PM河ACPI的章节,唉只怪英文太差,足足浏览了我一天。接下来就是写写看了,我觉得只要手册看懂了,搞硬件这种东西无非就是写写端口,比软件容易多了,然则最难的就是看手册啊,照这么说,软件只要设计好了流程和数据结构,写代码也很简单;买了房子之后,搬进去也是很简单的…,不是吗
不管怎么说,直觉上觉得按下power键立马关机要简单一些,毕竟“按下键”这个动作产生的signal已经被你的手一个动作完成了,所剩下的只是设法设置一下按下的delay时间了,而实现软关机,想产生power键被按下的信号,不知要写多少寄存器或者端口呢,想想都恐怖,还要注意时序…还是先搞瞬时关机吧,首先看到的是以下一段
看起来也没有那么复杂,只要拉低两个引脚信号就可以了,但是真要做起来还是要写寄存器的,接下来找要搞到PMC寄存器的内容,于是找到下面这段:
首先注意7到15位,32位,44到47位,比较重要的是7到15位,通过它我们可以得到pm的base地址,然后就可以在这个base的基础上根据其它的功能的offset来寻找其它的功能IO端口了:
addr = 0x5140000f; //pmc这个msr的地址
msr = rdmsr(addr); //读取msr,rdmsr可从linux代码中找到
unsigned long base = msr.lo&0xff80; //0xff80为7到15位的mask
耗了将近一天所得到的成果就是找到了几个有用的信息,比如下面的这个:
那么下面的代码就找到了这个io口:
addr = base + 0x40;
于是往里面写些什么呢?下面的信息绝对有用
于是下面的代码加上以后一切就完成了,按下电源,立马关机:
outl( 0x40001750, addr );
总的代码就是:
#include
#include
#include
#include
typedef struct msr_struct
{
unsigned long lo;
unsigned long hi;
} msr_t;
static inline msr_t rdmsr(unsigned index)
{
msr_t result;
__asm__ __volatile__ (" /
movw $0xac1c, %%dx ; /
movw $0xfc53, %%ax ; /
outw %%ax, %%dx; /
movw $0x0007, %%ax ; /
outw %%ax, %%dx; /
movw $0xac1e, %%dx ; /
inw %%dx, %%ax; "
: "=a" (result.lo), "=d" (result.hi)
: "c" (index)
);
return result;
}
int main (int argc, char *argv[]) {
msr_t msr;
unsigned long addr = 0;
msr.lo = 0;
msr.hi = 0;
addr = 0x5140000f;
iopl(3);
msr = rdmsr(addr);
unsigned long base = msr.lo&0xff80;
addr = base + 0x40;
outl( 0x40001750, addr );
return 0;
}
这个程序运行之后,按下电源键,直接就关机了,很是快乐,接下来开始搞软关机了,至于软关机,最重要的就是能使硬件产生几个序列,这些序列最终拉低WORK和WORK-AUX引脚从而实现关机,现在的关键是如何找到这个序列,这就要看手册了,实现快速关机时仅仅使用了PMC这个msr,这是因为你的手已经产生了一个序列,但是如果要靠写端口产生类似的序列就不得不使用别的msr或者端口,通过看手册知道其中比较重要的是GPIO,ACPI以及PMC,搞了一天之后,终于成功了,代码如下:
void power_off(void)
{
unsigned long acpi_low = 0,acpi_high = 0,
pmc_low = 0, pmc_high = 0,
gpio_low = 0,gpio_high = 0;
int acpi_addr = 0,pmc_addr = 0,gpio_addr = 0;
msr_t acpi = rdmsr( 0x5140000e);
acpi_low = acpi.lo;
acpi_high = acpi.hi;
msr_t pmc = rdmsr( 0x5140000f);
pmc_low = pmc.lo;
pmc_high = pmc.hi;
msr_t gpio = rdmsr( 0x5140000c);
gpio_low = gpio.lo;
gpio_high = gpio.hi;
acpi_addr = acpi_low&0xffe0;
pmc_addr = pmc_low&0xff80;
gpio_addr = gpio_low&0xff00;
outl(0x08000000,gpio_addr+0x04);
outl(0x08000000,gpio_addr+0x10);
outl(0x40000008,pmc_addr+0x10);
outl(0x40000002,pmc_addr+0x30);
outl(0x40000005,pmc_addr+0x34);
outl(0x2ffff,pmc_addr+0x54);
int p = acpi_addr+0x02;
outw(inw(p)|0x0100,p);
inw(p);
p = acpi_addr+0x1C;
outl(inl(p)&(0x40000000|0x80000000),p);
inl(p);
p = acpi_addr+0x18;
outl(0xffffffff,p);
inl(p);
p = acpi_addr+0x00;
outw(0xffff,p);
inw(p);
p = acpi_addr+0x08;
int iTyp = 5<<10; //5就是关机的type值,将之移位到它该到的位置
iTyp = iTyp|0x2000; //使能位置位
outw(iTyp,p); //触发序列
}
前面的设置都是设置使能位的,只有最后的那个outw才触发了序列,所有的规范都在那个手册里面,这里就不贴图了。
总结起来就是,搞硬件虽然写了一个寄存器,但是带来的快乐却很多,因为你真的能实际控制硬件了,就好像心里想了一件事,吹了一口气,顿时风雨大作一样。然而现如今嵌入式开发的定义是不是太滥了啊,就我上面做的这件事就是“嵌入式开发”了,实际上并不是的,嵌入式开发是很复杂的,要考虑很多,甚至更多,比如可扩展性,比如资源的使用情况,等等等等…只要你到了一个搞linux内核的公司,那十有八九是照着硬件手册写寄存器端口的,linux内核的内容那么多,当你兴致勃勃去公司报到后,最终却落实到了in/out两条指令上,事实上,要真的写端口,大可不必搞什么linux内核,只要在用户态就可以,另外找一块板子,什么操作系统也没有也可以照着手册读写端口和寄存器…