自制AMD CS5536关机代码和嵌入式

2019-07-14 03:23发布

AMD CS5536是一款很流行的嵌入式处理器,在基于它的架构上,可以做成各种小器具,然后如果操作系统是linux,且内核低于2.6.18的话,据我所知,它不能实现软关机,也就是说调用shutdown之后机器无法关闭,怎么办呢?最近我搞到一块AMD CS5536的板子,想自己做点东西,可无奈发现它在linux下无法关机,按电源也必须4秒,索性只好将开关做成纯电气的而不是电子的,也就是说按下开关直接切断电源,然而这决不是长久之计,最重要的是要实现软关机,即使实现不了也要实现按下电源立马关闭,也就是说不用再等4秒。
     谁让咱是搞IT的呢?自己动手的乐趣不亚于厨师为自己做一桌子菜的乐趣,马上下载了CS5536的手册《AMDG_CS5536.pdf》(网上一搜便是,第一步如此顺利),接下来就是漫无边际的“浏览”了,浏览过后终于找到了PM河ACPI的章节,唉只怪英文太差,足足浏览了我一天。接下来就是写写看了,我觉得只要手册看懂了,搞硬件这种东西无非就是写写端口,比软件容易多了,然则最难的就是看手册啊,照这么说,软件只要设计好了流程和数据结构,写代码也很简单;买了房子之后,搬进去也是很简单的…,不是吗
     不管怎么说,直觉上觉得按下power键立马关机要简单一些,毕竟“按下键”这个动作产生的signal已经被你的手一个动作完成了,所剩下的只是设法设置一下按下的delay时间了,而实现软关机,想产生power键被按下的信号,不知要写多少寄存器或者端口呢,想想都恐怖,还要注意时序…还是先搞瞬时关机吧,首先看到的是以下一段 1 看起来也没有那么复杂,只要拉低两个引脚信号就可以了,但是真要做起来还是要写寄存器的,接下来找要搞到PMC寄存器的内容,于是找到下面这段: 2 3 首先注意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
耗了将近一天所得到的成果就是找到了几个有用的信息,比如下面的这个: 4 那么下面的代码就找到了这个io口:
addr = base + 0x40;
于是往里面写些什么呢?下面的信息绝对有用 5 于是下面的代码加上以后一切就完成了,按下电源,立马关机:
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内核,只要在用户态就可以,另外找一块板子,什么操作系统也没有也可以照着手册读写端口和寄存器…