/* * 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 #include #include #include #include #include #include #include #include #include #include #include /* * ******************************************************************* * * 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 MODULE_AUTHOR("Guylhem Aznar "); 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);