引言
之前,我们在eCos下对ORPSoC的SD卡控制器进行了测试,这次我们将进行一次基于linux的测试和验证。
关于基于eCos下的SD card controller测试请参考:
http://blog.csdn.net/rill_zhen/article/details/9365477
和
http://blog.csdn.net/rill_zhen/article/details/9111213
本小节只验证对SD卡的基本的初始化,表示SD卡控制器可以在linux下正常工作。
1,测试目的
前面,我们已经在eCos平台下对ORPSoC的SD卡控制器进行了测试,实现了对SD卡的文件的创建,读写操作。但是在linux下还没有成功,linux在ORPSoC上也是非常重要的OS,所以非常需要完成基于linux的SD card controller的驱动。
2,测试步骤
本小节的操作过程,在之前的blog中已经反复描述,这里就不在赘述,如有疑问请参考:
http://blog.csdn.net/rill_zhen/article/details/8700937
和
http://blog.csdn.net/rill_zhen/article/details/8535317
3,驱动编码
参考之前基于eCos下的驱动,我们可以很容易得到linux下的驱动,code list 如下:
和之前相同,也是三个文件:ip_mkg.c,ip_mkg.h,Makefile
1>ip_mkg.c
/*
*
* rill mkg driver
*
*/
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* get_user and put_user */
//#include <linux/clk.h>
//#include <linux/ioport.h>
#include <asm/io.h> /*ioremap*/
#include <linux/platform_device.h> /*cleanup_module*/
#include <asm-generic/io.h>
#include "ip_mkg.h"
#include <linux/interrupt.h>
#include <linux/delay.h>
volatile int g_irq_test_counter = 0;
void __iomem *g_mkg_mem_base = NULL;
typedef struct sdc_data_s
{
int is_v20;
int is_sdhc;
unsigned int rca;
int connected;
}sdc_data_s;
static int device_open(struct inode *inode, struct file *file)
{
g_mkg_mem_base = ioremap(MKG_MEM_BASE,MKG_MEM_LEN);
if(NULL == g_mkg_mem_base)
{
printk(KERN_ERR "mkg open ioremap error!\n");
return -1;
}
else
{
printk("mkg ioremap addr:%d!\n",(int)g_mkg_mem_base);
}
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset)
{
/*int ret_val = 0;
char * data = NULL;
data = (char*)kmalloc(4, GFP_KERNEL);
if((ret_val = copy_from_user(new_regs, (struct reg_data*)ioctl_param, sizeof(struct reg_data))) != 0)
ioread32(g_mkg_mem_base+length);
printk("============read:%d\n",);*/
printk("mkg g_irq_test_counter:%d!\n",(int)g_irq_test_counter);
return 1;
}
static ssize_t device_write(struct file *filp, const char *buffer, size_t count, loff_t *offset)
{
//iowrite32(2,g_mkg_mem_base);
return 1;
}
long device_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
{
#if 0
int ret_val = 0;
unsigned int ret = 0;
struct reg_data *new_regs;
printk("ioctl======\n");
switch(ioctl_num)
{
case IOCTL_REG_SET:
{
new_regs = (struct reg_data*)kmalloc(sizeof(struct reg_data), GFP_KERNEL);
if((ret_val = copy_from_user(new_regs, (struct reg_data*)ioctl_param, sizeof(struct reg_data))) != 0)
{
kfree(new_regs);
printk(KERN_ERR " error copy line_datafrom user.\n");
return -1;
}
//iowrite16(new_regs->value,g_mkg_mem_base+new_regs->addr);
kfree(new_regs);
}
break;
case IOCTL_REG_GET:
{
new_regs = (struct reg_data*)kmalloc(sizeof(struct reg_data), GFP_KERNEL);
if((ret_val = copy_from_user(new_regs, (struct reg_data*)ioctl_param, sizeof(struct reg_data))) != 0)
{
kfree(new_regs);
printk(KERN_ERR " error copy line_datafrom user.\n");
return -1;
}
//ret = ioread16(g_mkg_mem_base+new_regs->addr);
kfree(new_regs);
return ret;
}
break;
}
#endif
return -1;
}
struct file_operations our_file_ops = {
.unlocked_ioctl = device_ioctl,
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release,
.owner = THIS_MODULE,
};
void reg_set(int addr,unsigned int value)
{
iowrite32(le32_to_cpu(value),g_mkg_mem_base+addr);
}
unsigned int reg_get(int addr,unsigned int * value)
{
unsigned int ret = 0;
ret = cpu_to_le32(ioread32(g_mkg_mem_base+addr));
*value = ret;
return ret;
}
int disk_init(void)
{
// Set highest possible timeout
reg_set(SDCMSC_TIMEOUT,0xfffe);
// Reset the peripheral
reg_set(SDCMSC_SOFTWARE_RESET,1);
reg_set(SDCMSC_CLOCK_DIVIDER,2);
reg_set(SDCMSC_SOFTWARE_RESET,0);
printk("disk init ok!\n");
return 1;
}
static int sdcmsc_card_cmd(unsigned int cmd,unsigned int arg,unsigned int *response,int flag)
{
unsigned int reg;
unsigned int mask;
// Send command to card
reg_set(SDCMSC_COMMAND, cmd);
reg_set(SDCMSC_ARGUMENT, arg);
// Wait for response
mask = SDCMSC_NORMAL_INT_STATUS_EI | SDCMSC_NORMAL_INT_STATUS_CC;
do
{
reg_get(SDCMSC_NORMAL_INT_STATUS, ®);
} while(!(reg & mask));
reg_set(SDCMSC_NORMAL_INT_STATUS, 0);
// Optionally read response register
if(flag)
{
reg_get(SDCMSC_RESPONSE, response);
}
// Check for errors
if(reg & SDCMSC_NORMAL_INT_STATUS_EI)
{
reg_get(SDCMSC_ERROR_INT_STATUS, ®);
if(reg & (1 << 3)) printk("Command index error\n");
if(reg & (1 << 1)) printk("Command CRC error\n");
if(reg & (1 << 0)) printk("Command timeout\n");
reg_set(SDCMSC_ERROR_INT_STATUS, 0);
return 0;
}
else
{
return 1;
}
}
int sdc_card_init(sdc_data_s * data,char* serial,char* firmware_rev,char* model_num,unsigned int* capacity)
{
unsigned int reg = 0;
unsigned int cmd = 0;
unsigned int arg = 0;
unsigned int card_capacity;
unsigned int read_bl_len;
unsigned int c_size;
unsigned int c_size_mult;
// Send CMD0 to switch the card to idle state
cmd = SDCMSC_COMMAND_CMDI(0);
if(!sdcmsc_card_cmd(cmd, 0, NULL,0)) return 0;
// Send CMD8 offering 2.7V to 3.6V range
// If the card doesn't responde it means either:
// 1. Card supports v2.0 but can't communicate using
// current voltage levels
// 2. Card does not support v2.0
cmd = SDCMSC_COMMAND_CMDI(8) |
SDCMSC_COMMAND_CICE |
SDCMSC_COMMAND_CIRC |
SDCMSC_COMMAND_RTS_48;
data->is_v20 = sdcmsc_card_cmd(cmd, 0x1AA, NULL,0);
printk("is_v20:0x%x\n",data->is_v20);//rill add debug
do {
reg_get(SDCMSC_CARD_STATUS, ®);
} while(reg & SDCMSC_CARD_STATUS_CICMD);
// Repeat ACMD41 until card set the busy bit to 1
// Since ACMD is an extended command, it must be preceded
// by CMD55
do {
cmd = SDCMSC_COMMAND_CMDI(55) |
SDCMSC_COMMAND_CICE |
SDCMSC_COMMAND_CIRC |
SDCMSC_COMMAND_RTS_48;
if(!sdcmsc_card_cmd(cmd, 0, NULL,0)) return 0;
cmd = SDCMSC_COMMAND_CMDI(41) |
SDCMSC_COMMAND_RTS_48;
arg = data->is_v20 ?
0x40FF8000 :
0x00FF8000;
if(!sdcmsc_card_cmd(cmd, arg, ®,1)) return 0;
} while(!(reg & 0x80000000));
data->is_sdhc = !!(reg & 0x40000000);
printk("is_sdhc:0x%x\n",data->is_sdhc);
// Issue CMD2 to switch from ready state to ident. Unfortunately, it is
// not possible to read whole CID because the command can be issued only
// once, and the peripheral can store only 32bit of the command at once.
cmd = SDCMSC_COMMAND_CMDI(2) |
SDCMSC_COMMAND_RTS_136;
if(!sdcmsc_card_cmd(cmd, 0, NULL,0)) return 0;
// Issue CMD3 to get RCA and switch from ident state to stby.
cmd = SDCMSC_COMMAND_CMDI(3) |
SDCMSC_COMMAND_CICE |
SDCMSC_COMMAND_CIRC |
SDCMSC_COMMAND_RTS_48;
if(!sdcmsc_card_cmd(cmd, 0, ®,1)) return 0;
data->rca = reg & 0xFFFF0000;
printk("rca:0x%x\n",data->rca);
// Calculate card capacity. Use information stored in CSD register.
if(data->is_sdhc)
{
cmd = SDCMSC_COMMAND_CMDI(9) |
SDCMSC_COMMAND_CMDW(1) |
SDCMSC_COMMAND_RTS_136;
if(!sdcmsc_card_cmd(cmd, data->rca, ®,1)) return 0;
card_capacity = reg & 0x3F;
card_capacity <<= 16;
cmd = SDCMSC_COMMAND_CMDI(9) |
SDCMSC_COMMAND_CMDW(2) |
SDCMSC_COMMAND_RTS_136;
if(!sdcmsc_card_cmd(cmd, data->rca, ®,1)) return 0;
reg >>= 16;
card_capacity |= reg;
card_capacity += 1;
card_capacity *= 1000;
}
else
{
cmd = SDCMSC_COMMAND_CMDI(9) |
SDCMSC_COMMAND_CMDW(1) |
SDCMSC_COMMAND_RTS_136;
if(!sdcmsc_card_cmd(cmd, data->rca, ®,1)) return 0;
read_bl_len = (reg >> 16) & 0x0F;
c_size = reg & 0x3FF;
c_size <<= 2;
cmd = SDCMSC_COMMAND_CMDI(9) |
SDCMSC_COMMAND_CMDW(2) |
SDCMSC_COMMAND_RTS_136;
if(!sdcmsc_card_cmd(cmd, data->rca, ®,1)) return 0;
c_size |= (reg >> 30) & 0x03;
c_size_mult = (reg >> 15) & 0x07;
card_capacity = c_size + 1;
card_capacity *= 1 << (c_size_mult + 2);
card_capacity *= 1 << (read_bl_len);
card_capacity >>= 9;
}
printk("card capacity,sector_num:0x%x,size_bytes:0x%x\n",card_capacity,card_capacity*512);
// Fill disk identification struct using information in CID register
// use OEM/APPlication ID field to fill model_num,
// Product revision field to fill firmware_rev,
// and Product serial number to field to fill serial
cmd = SDCMSC_COMMAND_CMDI(10) |
SDCMSC_COMMAND_CMDW(0) |
SDCMSC_COMMAND_RTS_136;
if(!sdcmsc_card_cmd(cmd, data->rca, ®,1)) return 0;
model_num[0] = (reg >> 16) & 0xFF;
model_num[1] = (reg >> 8) & 0xFF;
model_num[2] = 0;
cmd = SDCMSC_COMMAND_CMDI(10) |
SDCMSC_COMMAND_CMDW(2) |
SDCMSC_COMMAND_RTS_136;
if(!sdcmsc_card_cmd(cmd, data->rca, ®,1)) return 0;
firmware_rev[0] = (reg >> 24) & 0xFF;
firmware_rev[1] = 0;
serial[0] = (reg >> 16) & 0xFF;
serial[1] = (reg >> 8) & 0xFF;
serial[2] = reg & 0xFF;
cmd = SDCMSC_COMMAND_CMDI(10) |
SDCMSC_COMMAND_CMDW(3) |
SDCMSC_COMMAND_RTS_136;
if(!sdcmsc_card_cmd(cmd, data->rca, ®,1)) return 0;
serial[3] = (reg >> 24) & 0xFF;
// Put card in transfer state
cmd = SDCMSC_COMMAND_CMDI(7) |
SDCMSC_COMMAND_CICE |
SDCMSC_COMMAND_CIRC |
SDCMSC_COMMAND_RTS_48;
if(!sdcmsc_card_cmd(cmd, data->rca, ®,1)) return 0;
if(reg != 0x700) return 0;
// Set block size to 512
cmd = SDCMSC_COMMAND_CMDI(16) |
SDCMSC_COMMAND_CICE |
SDCMSC_COMMAND_CIRC |
SDCMSC_COMMAND_RTS_48;
if(!sdcmsc_card_cmd(cmd, 512, NULL,0)) return 0;
// Set 4-bits bus mode
cmd = SDCMSC_COMMAND_CMDI(55) |
SDCMSC_COMMAND_CICE |
SDCMSC_COMMAND_CIRC |
SDCMSC_COMMAND_RTS_48;
if(!sdcmsc_card_cmd(cmd, data->rca, NULL,0)) return 0;
cmd = SDCMSC_COMMAND_CMDI(6) |
SDCMSC_COMMAND_CICE |
SDCMSC_COMMAND_CIRC |
SDCMSC_COMMAND_RTS_48;
if(!sdcmsc_card_cmd(cmd, 0x02, NULL,0)) return 0;
printk("sdc init success!\n");
return 1;
}
int disk_lookup(void)
{
sdc_data_s data;
char serial;
char firmware_rev;
char model_num;
unsigned int capacity;
data.connected = 0;
data.connected = sdc_card_init(&data,&serial,&firmware_rev,&model_num,&capacity);
if(data.connected)
{
printk("sdc_card_init done!\n");
}
else
{
printk("sdc_card_init failed!\n");
}
return 1;
}
void test(void)
{
printk("sdc test start4==\n");
disk_init();
disk_lookup();
printk("sdc test end==\n\n");
}
int init_module()
{
int ret_val;
int ret;
void __iomem *ret_from_request;
//=== Allocate character device
ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &our_file_ops);
if (ret_val < 0)
{
printk(KERN_ALERT " device %s failed(%d)\n", DEVICE_NAME, ret_val);
return ret_val;
}
ret = check_mem_region(MKG_MEM_BASE, MKG_MEM_LEN);
if (ret < 0)
{
printk(KERN_ERR "mkg check_mem_region bussy error!\n");
return -1;
}
ret_from_request = request_mem_region(MKG_MEM_BASE, MKG_MEM_LEN, "ip_mkg");
//===ioremap mkg registers
g_mkg_mem_base = ioremap(MKG_MEM_BASE,MKG_MEM_LEN);
if(NULL == g_mkg_mem_base)
{
printk(KERN_ERR "mkg ioremap error!\n");
return -1;
}
else
{
;//printk("mkg ioremap addr:%d!\n",(unsigned int)g_mkg_mem_base);
}
printk("mkg module init done!\n");
test();
return 0;
}
void cleanup_module()
{
release_mem_region(MKG_MEM_BASE, MKG_MEM_LEN);
unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Rill zhen:rill_zhen@126.com");
2>ip_mkg.h
#ifndef __IP_MKG_H__
#define __IP_MKG_H__
#define MAJOR_NUM 102
#define DEVICE_NAME "ip_mkg"
#define MKG_MEM_BASE 0x9e000000
#define MKG_MEM_LEN 256
#define MKG_IRQ_INDEX 0x3
#define IOCTL_REG_SET 0
#define IOCTL_REG_GET 1
// Register space
#define SDCMSC_ARGUMENT 0x00
#define SDCMSC_COMMAND 0x04
#define SDCMSC_CARD_STATUS 0x08
#define SDCMSC_RESPONSE 0x0C
#define SDCMSC_CONTROLLER_SETTING 0x1C
#define SDCMSC_BLOCK_SIZE 0x20
#define SDCMSC_POWER_CONTROL 0x24
#define SDCMSC_SOFTWARE_RESET 0x28
#define SDCMSC_TIMEOUT 0x2C
#define SDCMSC_NORMAL_INT_STATUS 0x30
#define SDCMSC_ERROR_INT_STATUS 0x34
#define SDCMSC_NORMAL_INT_ENABLE 0x38
#define SDCMSC_ERROR_INT_ENABLE 0x3C
#define SDCMSC_CAPABILITY 0x48
#define SDCMSC_CLOCK_DIVIDER 0x4C
#define SDCMSC_BD_BUFFER_STATUS 0x50
#define SDCMSC_DAT_INT_STATUS 0x54
#define SDCMSC_DAT_INT_ENABLE 0x58
#define SDCMSC_BD_RX 0x60
#define SDCMSC_BD_TX 0x80
// SDCMSC_COMMAND bits
#define SDCMSC_COMMAND_CMDI(x) (x << 8)
#define SDCMSC_COMMAND_CMDW(x) (x << 6)
#define SDCMSC_COMMAND_CICE 0x10
#define SDCMSC_COMMAND_CIRC 0x08
#define SDCMSC_COMMAND_RTS_48 0x02
#define SDCMSC_COMMAND_RTS_136 0x01
//SDCMSC_CARD_STATUS bits
#define SDCMSC_CARD_STATUS_CICMD 0x01
// SDCMSC_NORMAL_INT_STATUS bits
#define SDCMSC_NORMAL_INT_STATUS_EI 0x8000
#define SDCMSC_NORMAL_INT_STATUS_CC 0x0001
// SDCMSC_DAT_INT_STATUS
#define SDCMSC_DAT_INT_STATUS_TRS 0x01
#endif
3>Makefile
# To build modules outside of the kernel tree, we run "make"
# in the kernel source tree; the Makefile these then includes this
# Makefile once again.
# This conditional selects whether we are being included from the
# kernel Makefile or not.
ifeq ($(KERNELRELEASE),)
# Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
KERNELDIR ?= /home/openrisc/soc-design/linux
# The current directory is passed to sub-makes as argument
PWD := $(shell pwd)
modules:
make -C $(KERNELDIR) M=$(PWD) modules ARCH=openrisc CROSS_COMPILE=or32-linux-
modules_install:
make -C $(KERNELDIR) M=$(PWD) modules_install ARCH=openrisc CROSS_COMPILE=or32-linux-
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.order *.symvers
.PHONY: modules modules_install clean
else
# called from kernel build system: just declare what our modules are
obj-m := ip_mkg.o
endif
4,验证结果
参考之前的操作步骤将驱动进行编译,运行linux,将ip_mkg.ko弄到板子上,并insmod。执行结果如下:
从中可以看出:
1>我的SD卡控制器是支持2.0的
2>我的SD卡是SDHC,不是SDSC。
3>我的SD卡的容量是0xea600000,换成十进制就是3932160000,也就是3.9G多一点。这和SD卡表面标示的"4G"是符合的,也是和SDHC的标准符合的。
5,SD卡的IO
1>编码
上面的测试证明SD卡控制器是初始化成功了,但是SD卡能不能读写还是疑问,所以,为了进一步验证,我们需要编写简单的读写测试函数,代码如下:
需要说明的是,我们只读取第一个block的数据(0~511。共512 bytes),所以dma地址为0。
另外需要注意的是sd卡控制器的DMA需要的是物理地址,所以需要把kmalloc获得的虚拟地址转换一下。
还有,这个代码有点小问题,由于virt_adr是4个字节的,所以for循环512是不对的,只需要循环512/4次就可以了。
void sdc_read(void)
{
int loop = 0;
unsigned int reg;
unsigned int * virt_adr;
unsigned int phy_adr;
virt_adr = (unsigned int *)kmalloc(512, GFP_DMA);
phy_adr=virt_to_phys(virt_adr);
reg_set(SDCMSC_BD_RX,phy_adr);
reg_set(SDCMSC_BD_RX,0);
do {
reg_get(SDCMSC_DAT_INT_STATUS, ®);
} while(!reg);
if(reg == SDCMSC_DAT_INT_STATUS_TRS)
{
for(loop=0;loop<512;loop++)
{
printk("virt_adr:0x%x=0x%x\n",loop,*(virt_adr+loop));
}
}
else
{
printk("sdc read error:0x%x!\n",reg);
}
}
void test(void)
{
printk("sdc test start4==\n");
disk_init();
disk_lookup();
sdc_read();
printk("sdc test end==\n\n");
}
2>测试结果
修改完代码之后,重新编译加载就可得到结果,为了验证读出的数据是否正确,我们用winhex软件也读一下,两者做一个比较,一看便知。
左边是驱动中读出的数据,右边是winhex读出的数据,两者相同,说明SD卡的基本I/O是没问题的。下一步的就是文件系统的事情了。
6,小结
经过努力,现在ORPSoC中的SD card controller在eCos下和linux下都可以work了。
有了SD卡的帮助,我们可以实现很多功能,要不然没有SD卡,ORPSoC的外部flash最大只能是8MB,对应稍大点的应用(显示一段视频,图片),显然不够用。
enjoy!
7,参考文献
http://blog.csdn.net/rill_zhen/article/details/9365477