/*
 * Copyright (c) Clément Ballabriga, 2005 - GPL
 * Copyright (c) Guylhem Aznar, 2005 - GPL
 *
 * Please check http://externe.net/zaurus/simpad-bluetooth reference design first.
 *
 * Based on Madsuk/Rohde work on a MMC driver for the WRT54G.
 *
 * This is an ugly hack of a driver. I am surprised if it ever works!
 * So please use a real driver or contribute one to the 2.4/2.6 mmc framework
 */

#include <linux/delay.h>
#include <linux/timer.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/blkpg.h>
#include <linux/hdreg.h>
#include <linux/major.h>

#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/*
 * *******************************************************************
 *
 *                This is the only configurable part.
 *
 * *******************************************************************
 *
 */

// #define DEBUG 1

#define DEVICE_NAME "mmc"
#define DEVICE_NR(device) (MINOR(device))
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
#define MAJOR_NR 121

/* Let that include where it is or compilation fails on INIT_REQUEST/CURRENT */

#include <linux/blk.h>

MODULE_AUTHOR("Guylhem Aznar <mmc-driver @externe.net>");
MODULE_DESCRIPTION("Driver for MMC/SD-Cards in SPI mode by GPIO");
MODULE_SUPPORTED_DEVICE("Simpad");
MODULE_LICENSE("GPL");

/* Registers should be architecture independant - but it's not ! */

#define MAP_START 0x90040000
#define MAP_SIZE  0x00001000

#define MY_GPLR 0
#define MY_GPDR 1
#define MY_GPSR 2
#define MY_GPCR 3
#define MY_GRER 4
#define MY_GFER 5
#define MY_GEDR 6
#define MY_GAFR 7

/*
 * If you are using different GPIOs in your hardware hack, you must
 * first make sure they are unused for other functions and then
 * configure them here.
 *
 * On the simpad I use spare pins from the UART1 (internal serial port):
 * - DCD (in)  : GPIO 23 : DO
 * - DTR (out) : GPIO 07 : CS 
 * - RI  (in)  : GPIO 19 : CLK
 * - DSR (in)  : GPIO 06 : DI
 *
 * Don't worry about in/out original function - the GPIOs will be
 * reprogrammed.
 */

#define GPIO_SD_DO  23
#define GPIO_SD_CS  7
#define GPIO_SD_CLK 19
#define GPIO_SD_DI  6

/*
 * *******************************************************************
 *
 *               Do not change anything below !
 *
 * *******************************************************************
 *
 */


/* GPIO states */
#define LOW 0
#define HIGH 1

#define INPUT 0
#define OUTPUT 1

#define PRESENT 1
#define ABSENT 0

typedef unsigned int uint32;
typedef unsigned long u32_t;
typedef unsigned short u16_t;
typedef unsigned char u8_t;

/* we have only one device */
static int hd_sizes[1 << 6];
static int hd_blocksizes[1 << 6];
static int hd_hardsectsizes[1 << 6];
static int hd_maxsect[1 << 6];
static struct hd_struct hd[1 << 6];

static struct timer_list mmc_timer;

/* start with no card */
static int mmc_media_detect = 0;
static int mmc_media_changed = 1;

extern struct gendisk hd_gendisk;

/* Use only one global device */
typedef struct gpio_s gpio_t;
struct gpio_s {
    volatile u32_t *base;
};

static gpio_t gp = {
	(void *) io_p2v(MAP_START)
};

/*
 * *******************************************************************
 *
 *             Begin GPIO hardware access functions.
 *
 * *******************************************************************
 *
 */

gpio_t *gpio_open(void)
{
    static gpio_t tmp;
    tmp.base = (void *) io_p2v(MAP_START);
    return (&tmp);
}

void gpio_setdir(gpio_t * g, int num, int dir)
{
    if (dir == 1) {
	g->base[MY_GPDR] |= (1 << num);
    } else {
	g->base[MY_GPDR] &= ~(1 << num);

    }
}

void gpio_setalt(gpio_t * g, int num, int alt)
{
    if (alt == 1) {
	g->base[MY_GAFR] |= (1 << num);
    } else {
	g->base[MY_GAFR] &= ~(1 << num);
    }
}

int gpio_getdir(gpio_t * g, int num)
{
    return ((g->base[MY_GPDR] & (1 << num)) ? 1 : 0);
}

int gpio_getalt(gpio_t * g, int num)
{
    return ((g->base[MY_GAFR] & (1 << num)) ? 1 : 0);
}

static int gpio_read(gpio_t * g, int num)
{
	int what;
    
	what=(g->base[MY_GPLR] & (1 << num)) ? 1 : 0;
	
#ifdef DEBUG
    if (num == GPIO_SD_DO) {
	    printk ("GPIO_SD_DO read: %u\n", what);
    }
#endif
    return (what);
}

static int gpio_write(gpio_t * g, int num, int val)
{
	int check;
	
    if (val == 1) {
	g->base[MY_GPSR] = 1 << num;
    } else {
	g->base[MY_GPCR] = 1 << num;
    }
#ifdef DEBUG
    check=gpio_read(g,num);
    if (check != val)
    {
	  printk ("Error while write to %d: found %d after writing %d\n",num, check, val);
	return (1);
    }
    else return(0);
#endif

}

/*
 * *******************************************************************
 *
 *             Begin SPI hardware access functions.
 *
 * *******************************************************************
 *
 */
static int mmc_spi_media_detect(void)
{
// FIXME: add card detection/test by SPI

    return 1;
}

static int mmc_spi_hardware_init(void)
{
    unsigned char gpio_outen;

    printk("mmc: GPIO init\n");

    /* Now global
     * gp = gpio_open(); */

    /* Cut existing functions */
    gpio_setalt(&gp, GPIO_SD_CLK, 0);
    gpio_setalt(&gp, GPIO_SD_DI, 0);
    gpio_setalt(&gp, GPIO_SD_DO, 0);
    gpio_setalt(&gp, GPIO_SD_CS, 0);

    /* Remap directions */
    gpio_setdir(&gp, GPIO_SD_CLK, OUTPUT);
    gpio_setdir(&gp, GPIO_SD_DI, OUTPUT);
    gpio_setdir(&gp, GPIO_SD_DO, INPUT);
    gpio_setdir(&gp, GPIO_SD_CS, OUTPUT);

    printk("mmc: initialising MMC\n");

    /* Start */
    gpio_write(&gp, GPIO_SD_CLK, LOW);
    gpio_write(&gp, GPIO_SD_DI, LOW);
    gpio_write(&gp, GPIO_SD_CS, LOW);
    return 0;
}

/* return what has been read, write the parameter */

static unsigned char mmc_spi_readwrite(unsigned char data_out)
{
    int i;
    unsigned char result = 0, tmp_data = 0;

    for (i = 0; i < 8; i++) {
	if (data_out & (0x01 << (7 - i)))
	    gpio_write(&gp, GPIO_SD_DI, HIGH);
	else
	    gpio_write(&gp, GPIO_SD_DI, LOW);

	gpio_write(&gp, GPIO_SD_CLK, HIGH);

	result <<= 1;

	if (gpio_read(&gp, GPIO_SD_DO) == 1)
	    result |= 1;

	gpio_write(&gp, GPIO_SD_CLK, LOW);
    }

    return (result);
}

static int mmc_spi_card_init(void)
{
    unsigned char result = 0;
    short i, j;
    unsigned long flags;

    save_flags(flags);
    cli();

    printk("GPIO_SD_CS dir: %u alt: %u\n", gpio_getdir(&gp, GPIO_SD_CS), gpio_getalt(&gp, GPIO_SD_CS));
    printk("GPIO_SD_DI dir: %u alt: %u\n", gpio_getdir(&gp, GPIO_SD_DI), gpio_getalt(&gp, GPIO_SD_DI));
    printk("GPIO_SD_DO dir: %u alt: %u\n", gpio_getdir(&gp, GPIO_SD_DO), gpio_getalt(&gp, GPIO_SD_DO));
    printk("GPIO_SD_CS dir: %u alt: %u\n", gpio_getdir(&gp, GPIO_SD_CLK), gpio_getalt(&gp, GPIO_SD_CLK));
    
    printk("mmc: card init 1/2\n");
    gpio_write(&gp, GPIO_SD_CS, HIGH);
    for (i = 0; i < 20; i++)
	mmc_spi_readwrite(0xff);

    gpio_write(&gp, GPIO_SD_CS, LOW);

    mmc_spi_readwrite(0x40);
    for (i = 0; i < 4; i++)
	mmc_spi_readwrite(0x00);
    mmc_spi_readwrite(0x95);
    for (i = 0; i < 8; i++) {
	result = mmc_spi_readwrite(0xff);
	if (result == 0x01)
	    break;
    }
    gpio_write(&gp, GPIO_SD_CS, HIGH);
    mmc_spi_readwrite(0xff);
    if (result != 0x01) {
        printk("mmc: card init %d error\n", result);
	restore_flags(flags);
	return (1);
    }

    printk("mmc: card init 2/2\n");
    for (j = 0; j < 10000; j++) {
	gpio_write(&gp, GPIO_SD_CS, LOW);

	mmc_spi_readwrite(0x41);
	for (i = 0; i < 4; i++)
	    mmc_spi_readwrite(0x00);
	mmc_spi_readwrite(0xff);
	for (i = 0; i < 8; i++) {
	    result = mmc_spi_readwrite(0xff);
	    if (result == 0x00)
		break;
	}
	gpio_write(&gp, GPIO_SD_CS, HIGH);
	mmc_spi_readwrite(0xff);
	if (result == 0x00) {
	    restore_flags(flags);
	    printk("mmc: card init 3/3\n");
	    return (0);
	}
    }
    restore_flags(flags);

    return (2);
}


static int mmc_spi_card_config(void)
{
    unsigned char result = 0;
    short i;
    unsigned char csd[32];
    unsigned int c_size;
    unsigned int c_size_mult;
    unsigned int mult;
    unsigned int read_bl_len;
    unsigned int blocknr = 0;
    unsigned int block_len = 0;
    unsigned int size = 0;

    gpio_write(&gp, GPIO_SD_CS, LOW);
    for (i = 0; i < 4; i++)
	mmc_spi_readwrite(0xff);
    mmc_spi_readwrite(0x49);
    for (i = 0; i < 4; i++)
	mmc_spi_readwrite(0x00);
    mmc_spi_readwrite(0xff);
    for (i = 0; i < 8; i++) {
	result = mmc_spi_readwrite(0xff);
	if (result == 0x00)
	    break;
    }
    if (result != 0x00) {
	gpio_write(&gp, GPIO_SD_CS, HIGH);
	mmc_spi_readwrite(0xff);
	return (1);
    }
    for (i = 0; i < 8; i++) {
	result = mmc_spi_readwrite(0xff);
	if (result == 0xfe)
	    break;
    }
    if (result != 0xfe) {
	gpio_write(&gp, GPIO_SD_CS, HIGH);
	mmc_spi_readwrite(0xff);
	return (2);
    }
    for (i = 0; i < 16; i++) {
	result = mmc_spi_readwrite(0xff);
	csd[i] = result;
    }
    for (i = 0; i < 2; i++) {
	result = mmc_spi_readwrite(0xff);
    }
    gpio_write(&gp, GPIO_SD_CS, HIGH);
    mmc_spi_readwrite(0xff);
    if (result == 0x00)
	return (3);

    c_size = csd[8] + csd[7] * 256 + (csd[6] & 0x03) * 256 * 256;
    c_size >>= 6;
    c_size_mult = csd[10] + (csd[9] & 0x03) * 256;
    c_size_mult >>= 7;
    read_bl_len = csd[5] & 0x0f;
    mult = 1;
    mult <<= c_size_mult + 2;
    blocknr = (c_size + 1) * mult;
    block_len = 1;
    block_len <<= read_bl_len;
    size = block_len * blocknr;
    size >>= 10;

    for (i = 0; i < (1 << 6); i++) {
	hd_blocksizes[i] = 1024;
	hd_hardsectsizes[i] = block_len;
	hd_maxsect[i] = 256;
    }
    hd_sizes[0] = size;
    hd[0].nr_sects = blocknr;


    printk("Size = %d, hardsectsize = %d, sectors = %d\n",
	   size, block_len, blocknr);

    return 0;
}


/*
 * *******************************************************************
 *
 *             End of SPI hardware access functions.
 *
 * *******************************************************************
 */


static int mmc_write_block(unsigned int dest_addr, unsigned char *data)
{
    unsigned int address;
    unsigned char result = 0;
    unsigned char ab0, ab1, ab2, ab3;
    int i;

    address = dest_addr;

    ab3 = 0xff & (address >> 24);
    ab2 = 0xff & (address >> 16);
    ab1 = 0xff & (address >> 8);
    ab0 = 0xff & address;
    gpio_write(&gp, GPIO_SD_CS, LOW);
    for (i = 0; i < 4; i++)
	mmc_spi_readwrite(0xff);
    mmc_spi_readwrite(0x58);
    mmc_spi_readwrite(ab3);		/* msb */
    mmc_spi_readwrite(ab2);
    mmc_spi_readwrite(ab1);
    mmc_spi_readwrite(ab0);		/* lsb */
    mmc_spi_readwrite(0xff);
    for (i = 0; i < 8; i++) {
	result = mmc_spi_readwrite(0xff);
	if (result == 0x00)
	    break;
    }
    if (result != 0x00) {
	gpio_write(&gp, GPIO_SD_CS, HIGH);
	mmc_spi_readwrite(0xff);
	return (1);
    }

    mmc_spi_readwrite(0xfe);
    for (i = 0; i < 512; i++)
	mmc_spi_readwrite(data[i]);
    for (i = 0; i < 2; i++)
	mmc_spi_readwrite(0xff);

    for (i = 0; i < 1000000; i++) {
	result = mmc_spi_readwrite(0xff);
	if (result == 0xff)
	    break;
    }
    if (result != 0xff) {
	gpio_write(&gp, GPIO_SD_CS, HIGH);
	mmc_spi_readwrite(0xff);
	return (3);
    }
    gpio_write(&gp, GPIO_SD_CS, HIGH);
    mmc_spi_readwrite(0xff);
    return (0);
}

static int mmc_read_block(unsigned char *data, unsigned int src_addr)
{
    unsigned int address;
    unsigned char result = 0;
    unsigned char ab0, ab1, ab2, ab3;
    int i;

    address = src_addr;

    ab3 = 0xff & (address >> 24);
    ab2 = 0xff & (address >> 16);
    ab1 = 0xff & (address >> 8);
    ab0 = 0xff & address;

    gpio_write(&gp, GPIO_SD_CS, LOW);
    for (i = 0; i < 4; i++)
	mmc_spi_readwrite(0xff);
    mmc_spi_readwrite(0x51);
    mmc_spi_readwrite(ab3);		/* msb */
    mmc_spi_readwrite(ab2);
    mmc_spi_readwrite(ab1);
    mmc_spi_readwrite(ab0);		/* lsb */

    mmc_spi_readwrite(0xff);
    for (i = 0; i < 8; i++) {
	result = mmc_spi_readwrite(0xff);
	if (result == 0x00)
	    break;
    }
    if (result != 0x00) {
	gpio_write(&gp, GPIO_SD_CS, HIGH);
	mmc_spi_readwrite(0xff);
	return (1);
    }
    for (i = 0; i < 100000; i++) {
	result = mmc_spi_readwrite(0xff);
	if (result == 0xfe)
	    break;
    }
    if (result != 0xfe) {
	gpio_write(&gp, GPIO_SD_CS, HIGH);
	mmc_spi_readwrite(0xff);
	return (2);
    }
    for (i = 0; i < 512; i++) {
	result = mmc_spi_readwrite(0xff);
	data[i] = result;
    }
    for (i = 0; i < 2; i++) {
	result = mmc_spi_readwrite(0xff);
    }
    gpio_write(&gp, GPIO_SD_CS, HIGH);
    mmc_spi_readwrite(0xff);

    return (0);
}

static void mmc_request(request_queue_t * q)
{
    unsigned int mmc_address;
    unsigned char *buffer_address;
    int nr_sectors;
    int i;
    int cmd;
    int result, code;

    (void) q;
    while (1) {
	code = 1;		// Default is success
	INIT_REQUEST;
	mmc_address =
	    (CURRENT->sector +
	     hd[MINOR(CURRENT->rq_dev)].start_sect) * hd_hardsectsizes[0];
	buffer_address = CURRENT->buffer;
	nr_sectors = CURRENT->current_nr_sectors;
	cmd = CURRENT->cmd;
	if (((CURRENT->sector + CURRENT->current_nr_sectors +
	      hd[MINOR(CURRENT->rq_dev)].start_sect) > hd[0].nr_sects)
	    || (mmc_media_detect == 0)) {
	    code = 0;
	} else if (cmd == READ) {
	    spin_unlock_irq(&io_request_lock);
	    for (i = 0; i < nr_sectors; i++) {
		result = mmc_read_block(buffer_address, mmc_address);
		if (result != 0) {
		    printk("mmc: error %d in mmc_read_block\n", result);
		    code = 0;
		    break;
		} else {
		    mmc_address += hd_hardsectsizes[0];
		    buffer_address += hd_hardsectsizes[0];
		}
	    }
	    spin_lock_irq(&io_request_lock);
	} else if (cmd == WRITE) {
	    spin_unlock_irq(&io_request_lock);
	    for (i = 0; i < nr_sectors; i++) {
		result = mmc_write_block(mmc_address, buffer_address);
		if (result != 0) {
		    printk("mmc: error %d in mmc_write_block\n", result);
		    code = 0;
		    break;
		} else {
		    mmc_address += hd_hardsectsizes[0];
		    buffer_address += hd_hardsectsizes[0];
		}
	    }
	    spin_lock_irq(&io_request_lock);
	} else {
	    code = 0;
	}
	end_request(code);
    }
}


static int mmc_open(struct inode *inode, struct file *filp)
{
    int device;
    (void) filp;
    mmc_media_detect = mmc_spi_media_detect();

    if (mmc_media_detect == 0)
	return -ENODEV;

#if defined(MODULE)
    MOD_INC_USE_COUNT;
#endif
    return 0;
}

static int mmc_release(struct inode *inode, struct file *filp)
{
    (void) filp;
    fsync_dev(inode->i_rdev);
    invalidate_buffers(inode->i_rdev);

#if defined(MODULE)
    MOD_DEC_USE_COUNT;
#endif
    return 0;
}

static int mmc_revalidate(kdev_t dev)
{
    int target, max_p, start, i;

    mmc_media_detect = mmc_spi_media_detect();

    if (mmc_media_detect == 0)
	return -ENODEV;

    target = DEVICE_NR(dev);

    max_p = hd_gendisk.max_p;
    start = target << 6;
    for (i = max_p - 1; i >= 0; i--) {
	int minor = start + i;
	invalidate_device(MKDEV(MAJOR_NR, minor), 1);
	hd_gendisk.part[minor].start_sect = 0;
	hd_gendisk.part[minor].nr_sects = 0;
    }

    grok_partitions(&hd_gendisk, target, 1 << 6, hd_sizes[0] * 2);

    return 0;
}

static int mmc_ioctl(struct inode *inode, struct file *filp,
		     unsigned int cmd, unsigned long arg)
{
    if (!inode || !inode->i_rdev)
	return -EINVAL;

    switch (cmd) {
    case BLKGETSIZE:
	return put_user(hd[MINOR(inode->i_rdev)].nr_sects,
			(unsigned long *) arg);
    case BLKGETSIZE64:
	return put_user((u64) hd[MINOR(inode->i_rdev)].
			nr_sects, (u64 *) arg);
    case BLKRRPART:
	if (!capable(CAP_SYS_ADMIN))
	    return -EACCES;

	return mmc_revalidate(inode->i_rdev);
    case HDIO_GETGEO:
	{
	    struct hd_geometry *loc, g;
	    loc = (struct hd_geometry *) arg;
	    if (!loc)
		return -EINVAL;
	    g.heads = 4;
	    g.sectors = 16;
	    g.cylinders = hd[0].nr_sects / (4 * 16);
	    g.start = hd[MINOR(inode->i_rdev)].start_sect;
	    return copy_to_user(loc, &g, sizeof(g)) ? -EFAULT : 0;
	}
    default:
	return blk_ioctl(inode->i_rdev, cmd, arg);
    }
}


static int mmc_check_media_change(kdev_t dev)
{
    (void) dev;
    if (mmc_media_changed == 1) {
	mmc_media_changed = 0;
	return 1;
    } else
	return 0;
}

static struct block_device_operations mmc_bdops = {
  open:mmc_open,
  release:mmc_release,
  ioctl:mmc_ioctl,
/* FIXME: add media change support
 *	check_media_change: mmc_check_media_change,
 *	revalidate: mmc_revalidate,
 */
};

static struct gendisk hd_gendisk = {
  major:MAJOR_NR,
  major_name:DEVICE_NAME,
  minor_shift:6,
  max_p:1 << 6,
  part:hd,
  sizes:hd_sizes,
  fops:&mmc_bdops,
};

static int mmc_init(void)
{
    int result;

    result = mmc_spi_hardware_init();

    if (result != 0) {
	printk("mmc: error %d in mmc_spi_hardware_init\n", result);
	return -1;
    }

    result = mmc_spi_card_init();
    if (result != 0) {
	// Give it an extra shot
	result = mmc_spi_card_init();
	if (result != 0) {
	    printk("mmc: error %d in mmc_card_init\n", result);
	    return -1;
	}
    }

    memset(hd_sizes, 0, sizeof(hd_sizes));
    result = mmc_spi_card_config();
    if (result != 0) {
	printk("mmc: error %d in mmc_card_config\n", result);
	return -1;
    }


    blk_size[MAJOR_NR] = hd_sizes;

    memset(hd, 0, sizeof(hd));
    hd[0].nr_sects = hd_sizes[0] * 2;

    blksize_size[MAJOR_NR] = hd_blocksizes;
    hardsect_size[MAJOR_NR] = hd_hardsectsizes;
    max_sectors[MAJOR_NR] = hd_maxsect;

    hd_gendisk.nr_real = 1;

    register_disk(&hd_gendisk, MKDEV(MAJOR_NR, 0), 1 << 6,
		  &mmc_bdops, hd_sizes[0] * 2);

    return 0;
}

static void mmc_exit(void)
{
    blk_size[MAJOR_NR] = NULL;
    blksize_size[MAJOR_NR] = NULL;
    hardsect_size[MAJOR_NR] = NULL;
    max_sectors[MAJOR_NR] = NULL;
    hd[0].nr_sects = 0;
}

static void mmc_check_media(void)
{
    int old_state, new_state;
    int result;

    old_state = mmc_media_detect;
    new_state = mmc_spi_media_detect();

    if (old_state != new_state) {
	mmc_media_changed = 1;
	if (new_state == PRESENT) {
	    result = mmc_init();
	    if (result != 0)
		printk("mmc: error %d in mmc_init\n", result);
	} else {
	    mmc_exit();
	}
    }

    /* del_timer(&mmc_timer);
       mmc_timer.expires = jiffies + 10*HZ;
       add_timer(&mmc_timer); */
}

static int __init mmc_driver_init(void)
{
    int result;

    result = devfs_register_blkdev(MAJOR_NR, DEVICE_NAME, &mmc_bdops);
    if (result < 0) {
	printk(KERN_WARNING "mmc: can't get major %d\n", MAJOR_NR);
	return result;
    }

    blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), mmc_request);

    mmc_check_media();

    /*init_timer(&mmc_timer);
       mmc_timer.expires = jiffies + HZ;
       mmc_timer.function = (void *)mmc_check_media;
       add_timer(&mmc_timer); */


    read_ahead[MAJOR_NR] = 8;
    add_gendisk(&hd_gendisk);


    return 0;
}

static void __exit mmc_driver_exit(void)
{
    int i;
    del_timer(&mmc_timer);

    for (i = 0; i < (1 << 6); i++)
	fsync_dev(MKDEV(MAJOR_NR, i));

    blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
    del_gendisk(&hd_gendisk);
    devfs_unregister_blkdev(MAJOR_NR, DEVICE_NAME);
    mmc_exit();
}

module_init(mmc_driver_init);
module_exit(mmc_driver_exit);
This page was last modified 13:58, 21 June 2006. | This page has been accessed 1,487 times. | About OpenSIMpad.org
Designed by Anna Boheim | Powerd by mediawiki