Secure Digital/MMC card driver

"Rob Reilink" <[email protected]>
Newsgroups gmane.linux.ports.game-cube.devel
Message-ID <000001c4c1e6$603213f0$0464a8c0@ULTIMEDIA>
Hello All,

The past week I've been working on a hardware & software solution to
interface SD- and MMC memorycards to the cube. I've attached the driver
source to this message. The source includes a description on how to link
the cube and the card. No electronics are required except a capacitor
(it might even work without that).

I know the drivers should be put in CVS, but as of yet I do not have CVS
access.

If other developers are interested, I might make some units on PCB and
send them for free, so more people can test.

If it proves usefull, I could make a more detailled howto to put on the
web.

Kind Regards,

Rob Reilink
gcn-sd.c (application/octet-stream, 10.5 KB)
/*
 * drivers/block/gcn-sd.c
 *
 * Nintendo GameCube SD/MMC memory card driver
 * Copyright (C) 2004 The GameCube Linux Team
 *
 * Copyright (C) 2004 by Rob Reilink ([email protected])
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 */

/*
 *
 *   THE AUTHOR OF THIS SOFTWARE AND HARDWARE CAN NOT BE HELD LIABLE
 *   FOR ANY POSSIBLE DAMAGE CAUSED TO THE GAMECUBE, THE SD- OR MMC
 *   CARD OR ANY OTHER HARDWARE USED. YOU USE THIS INFORMATION AND
 *   SOFTWARE AT YOUR OWN RISK
 *
 *		How to connect the SD/MMC card to the GC
 *
 *   GC P5 (mem card B)           SD/MMC
 *    2  gnd                    3,6 vss1,vss2
 *    4  3v3                    4   vdd
 *    5  do                     2   DataIn
 *    7  di                     7   DataOut 
 *    9  /cs                    1   /CS
 *    11 clk                    5   clk
 *    1  connected to 12 (card detect)
 *
 *   According to 'good electronics practise' it is advised to solder a 100nF
 *   capacitor between vdd and vss near the SD/MMC connector
 *
 *   Please note the layout of the pads on the sd card:
 *     __________________
 *    /                  |
 *   /   1 2 3 4 5 6 7 8 |
 *   | 9                 |
 *   |                   |
 *   |                   |
 *   |                   |
 *   |                   |
 *   |                   |
 *   |                   |
 *   |                   |
 *   |                   |
 *   |___________________|
 *
 *   Pin 8 and 9 do not exist on the MMC card; that explains the 'weird' numbering
 *
 *  To connect the card to the gamecube you can either solder wires to the bottom
 *  side of the mainboard (they can be led to the outside through a hole in the metal
 *  shielding on the right of the cube, but be sure the wires do not get damaged by
 *  the sharp edge) or scrap a GC memory card. For the last option, ne sure that the
 *  original electronics are disconnected!
 *
 *  If you choose to connect it by soldering, do not use the memory card slot anymore!
 *  
 *  For the connection to the card, it seems you can use an old floppy connector, but
 *  there are also special SD connectors available. The SD connector accepts both SD 
 *  and MMC cards; the MMC connector only accepts MMC cards (they are thinner). It is
 *  therefore better to buy an SD connector.
 *
 *  Note that this software has only been tested with an SD card.
 *  The MMC-adaptor commercially available has not been tested, but might work as well
 */

/* Software hints
 *
 * # This driver is in alpha stage:
 *     Only reading supported, not writing
 *     Card size is reported as about 100MB, it is not read from the card
 *     Cards are NOT hot-pluggable
 *     The driver cannot be compiled as module due to dependence of the
 *         EXI driver which is still 'work in progress'
 *
 * # add the following line to /drivers/block/Makefile:
 *    obj-$(CONFIG_GAMECUBE_SD)	+= gcn-sd.o
 * 
 * # add the following lines to /drivers/block/Kconfig:
 *    config GAMECUBE_SD
 *      tristate "Nintendo GameCube SD and MMC memory card (EXPERIMENTAL)"
 *      depends on GAMECUBE && EXI && EXPERIMENTAL
 *      help
 *        This enables support for SD and MMC memory cards
 *
 * # to support fat, it might be nescessary to select cp437=y under Filesystems->NLS
 *
 * # This is the author's first real kernel driver, so please tell me
 *   how it 'should have been done' ;)
 */

/*
 *  TODO: clean up this
 */
#include <linux/module.h>	/* Needed by all modules */
#include <linux/kernel.h>	/* Needed for KERN_ALERT */
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/exi.h>
#include <asm/uaccess.h>
#include <linux/blkdev.h>

#ifndef EXI_LITE
#error Sorry, this driver needs currently the gcn-exi-lite framework.
#endif

#define DEVICE_NAME            "gcn-sd"
#define DRIVER_AUTHOR          "Rob Reilink <[email protected]>"
#define DRIVER_DESC            "Gamecube SD-card driver"
#define DRIVER_MODULE_NAME     "gcn-sd"
#define PFX DRIVER_MODULE_NAME ": "

#define GCN_SD_MAJOR 0

#define SUCCESS 0
#define FAIL -1

#define GCN_SD_SPEED 4

static int Major;
static struct request_queue *queue;

static struct gcn_sd_device_struct {
	unsigned long size;
	spinlock_t lock;
	struct gendisk *gd;
} gcn_sd_device;



/*
 *  Funtions start here
 *
 */

/*
 *  EXI/SD functions
 *
 */
static char gcn_sd_cmd_short_nofinish(unsigned char cmd) {
	unsigned char dat;
	int tries;
	exi_select(1,0,GCN_SD_SPEED);
  dat=cmd|0x40;
	exi_write(1,&dat,1);
	dat=255;
	for (tries=0;(tries < 30) && (dat == 0xff);tries++)
		exi_read(1,&dat,1);

/*	exi_deselect(1);

	//last dummy write
	exi_write(1,&dat,1);
//	exi_write(1,&zero,2);*/
	return dat;
}

static char gcn_sd_cmd_nofinish(unsigned char cmd, unsigned long data) {
  unsigned char snd[6],ret;
	int tries;
	exi_select(1,0,GCN_SD_SPEED);
  snd[0]=cmd|0x40;
	snd[1]=data >> 24;
	snd[2]=(data >> 16) & 0xff;
	snd[3]=(data >> 8) & 0xff;
	snd[4]=data & 0xff;
	snd[5]=0x95;

	exi_write(1,&snd,6);
	
	//wait loop
	ret=0xff;
	for(tries=0;(tries<100) && (ret==0xff);tries++)
		exi_read(1,&ret,1);
	return ret;
}

static void gcn_sd_finish(void) {
	unsigned char zero=0;
	exi_deselect(1);
	//last dummy write
	exi_write(1, &zero, 1);

}

inline static char gcn_sd_cmd(unsigned char cmd) {
	char i=gcn_sd_cmd_nofinish(cmd,0);
	gcn_sd_finish();
	return i;
} 

inline static char gcn_sd_cmd_short(unsigned char cmd) {
	char i=gcn_sd_cmd_short_nofinish(cmd);
	gcn_sd_finish();
	return i;
}

/* returns: found?SUCCESS:FAIL */
static int gcn_sd_init(void) {
  int i,ret; long c;
	int tries=0;

	//Send 80 clocks while not selected
	exi_select(1,1,GCN_SD_SPEED);  //SLOOW TODO:fast, select=-1 ofzo (niets selecten)
	exi_deselect(1);
	for(i=0;i<20;i++) {
	 	c=0xffffffff;
		exi_write(1,&c,4);
	}

	
	//send CMD0 to init
	gcn_sd_cmd(0);
	gcn_sd_cmd_short(0);
	for(tries=0;tries<100;tries++) {
		if ((ret=gcn_sd_cmd_short(58))==0) return SUCCESS;
		printk(KERN_ALERT PFX "cmd 58 returned %d\n",ret);
		if ((ret=gcn_sd_cmd_short(1))==0) return SUCCESS;
		printk(KERN_ALERT PFX "cmd 1 returned %d\n",ret);
	}
	return FAIL;
}

static int gcn_sd_read_csd(void) {
	unsigned char c;
	unsigned char data[16];
	int tries;
	c=gcn_sd_cmd_short_nofinish(9);
	if (c!=0) {
		gcn_sd_finish();
		printk(KERN_INFO PFX "could not read csd(%d)\n",c);
		return FAIL;
	}
	for (tries=0;(tries<100000) & (c!=0xfe);tries++)
		exi_read(1,&c,1);

	if (c!=0xfe) {
		gcn_sd_finish();
		printk(KERN_ERR PFX "error: no start token after csd read request\n");
		return FAIL;		
	}
	exi_read(1,data,16);
	gcn_sd_finish();
	int i;
	printk(KERN_INFO PFX "CSD data:");
	for (i=0;i<16;i++) printk ("%x ",data[i]);
	printk("\n");

	return SUCCESS;
}

static int gcn_sd_block_read(unsigned long sector,void * data) {
	unsigned char c;
	long dummy;
	int tries;
	c=gcn_sd_cmd_nofinish(17,(sector) << 9);
	if (c!=0)	{
		gcn_sd_finish();
		printk(KERN_ERR PFX "error: read command returned %d while reading sector %lu\n" ,c,sector);
		return FAIL;
	}

	//Wait for start of data token 0xfe TODO put this in a function
	for (tries=0;(tries<100000) & (c!=0xfe);tries++)
		exi_read(1,&c,1);

	if (c!=0xfe) {
		gcn_sd_finish();
		printk(KERN_ERR PFX "error: no start token after read request sector=%lu\n" ,sector);
		return FAIL;
	}

	exi_read(1,data,512);
	exi_read(1,&dummy,3); //response,CRC, dummy. todo: use it?
	gcn_sd_finish();

	return SUCCESS;
	
}

/*
 * Block driver functions
 *
 */ 

static void gcn_sd_request(request_queue_t *q) {
	struct request *req;
	int sector,tries,success;
	while ((req=elv_next_request(q)) != NULL) {
		if (!blk_fs_request(req)) {
			end_request(req,0);
			continue;
		}
		if (rq_data_dir(req)) {  //write
			printk(KERN_ALERT "WRITE not implemented yet\n");
			
/*			gcn_sd_read_csd();
			gcn_sd_init();
			if (gcn_sd_init()==SUCCESS) {
				printk(KERN_ALERT PFX "resetting succeeded\n");
			} else {
				printk(KERN_ALERT PFX "resetting failed\n");
			}
			end_request(req,1);*/
	
		} else {	
			//read
   		//printk(KERN_ALERT "READ start=%lu num=%u\n", (unsigned long)req->sector,req->current_nr_sectors);
			success=1;
			tries=0;
			//process one sector at a time
			for (sector=0;sector < req->current_nr_sectors;sector++) {
				if (gcn_sd_block_read(req->sector+sector,req->buffer+(sector<<9))==SUCCESS) {
					tries=0;
				} else {
					tries++;
					sector--;
					if (tries>10) {
						success=0;
						break;
					}						
				}
			}
			end_request(req,success); //TODO 0
		}
	}
}


static int gcn_sd_open (struct inode *inode, struct file *file) {
//	printk(KERN_INFO "gcn-sd device driver: open\n");
	return SUCCESS;
}

static int gcn_sd_release(struct inode *inode, struct file *file) {
//	printk(KERN_INFO "gcn-sd device driver: release\n");
	return SUCCESS;
}


struct block_device_operations gcn_sd_bdops = {
  //TODO: ioctl?
	open: gcn_sd_open,
	release: gcn_sd_release
};

static int __init init_gcn_sd(void)
{

	if (gcn_sd_init()!=SUCCESS) {
		printk(KERN_WARNING PFX "No SD-card found; driver not registered\n");
		return 0; //TODO: what should we return?
	}
	printk(KERN_INFO PFX "SD-card found\n");
//	gcn_sd_read_csd();
	//Init device struct
	gcn_sd_device.size=200000*512;
	spin_lock_init(&gcn_sd_device.lock);

  //Registed device, get major number
 	Major = register_blkdev(GCN_SD_MAJOR, DEVICE_NAME);

	if (Major <= 0) {
		printk(KERN_ALERT PFX "Registering the block device failed with %d\n", Major);
		return Major;
	}

	printk(KERN_INFO PFX "got major %d\n",Major);

	//Init gendisk
	gcn_sd_device.gd=alloc_disk(16);
	if (!gcn_sd_device.gd) {
		printk(KERN_ALERT PFX "Could not allocate gendisk\n");
		//TODO:unreg
		return -EFAULT;

  }

	gcn_sd_device.gd->major=Major;
	gcn_sd_device.gd->first_minor=0;
	gcn_sd_device.gd->fops=&gcn_sd_bdops;
	gcn_sd_device.gd->private_data=&gcn_sd_device;
	strcpy(gcn_sd_device.gd->disk_name,"gcnsd0");
	set_capacity(gcn_sd_device.gd, 200000); //TODO

  //init queue
	queue = blk_init_queue(gcn_sd_request, &gcn_sd_device.lock);
	if (queue == NULL) {
		printk(KERN_ALERT PFX "Could not init queue\n");
		//TODO: unreg
		return -EFAULT;
	}

	blk_queue_hardsect_size(queue, 512);
	gcn_sd_device.gd->queue = queue;	

	//add the disk
	add_disk(gcn_sd_device.gd);

	return 0;
}
		    
static void __exit cleanup_gcn_sd(void)
{
	printk(KERN_INFO PFX "unloading\n");

	int ret = unregister_blkdev(Major, DRIVER_MODULE_NAME);
	if (ret < 0)
		printk(KERN_ALERT "Error in unregister_blkdev: %d\n", ret);
}


MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

module_init(init_gcn_sd);
module_exit(cleanup_gcn_sd);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.