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 <asm/io.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> 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内核,只要在用户态就可以,另外找一块板子,什么操作系统也没有也可以照着手册读写端口和寄存器…