diff options
Diffstat (limited to 'package/broadcom-mmc')
-rw-r--r-- | package/broadcom-mmc/Makefile | 48 | ||||
-rw-r--r-- | package/broadcom-mmc/src/Makefile | 7 | ||||
-rw-r--r-- | package/broadcom-mmc/src/mmc.c | 676 |
3 files changed, 731 insertions, 0 deletions
diff --git a/package/broadcom-mmc/Makefile b/package/broadcom-mmc/Makefile new file mode 100644 index 000000000..f3d30bc1c --- /dev/null +++ b/package/broadcom-mmc/Makefile @@ -0,0 +1,48 @@ +# +# Copyright (C) 2007 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# +# $Id: $ + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=broadcom-mmc +PKG_VERSION:=0.1 +PKG_RELEASE:=1 + +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) + +include $(INCLUDE_DIR)/package.mk + +MAKEFLAGS_KMOD:= -C "$(LINUX_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + PATH="$(TARGET_PATH)" \ + SUBDIRS="$(PKG_BUILD_DIR)" + +define KernelPackage/broadcom-mmc + SUBMENU:=Other modules + DEPENDS:=@LINUX_2_4_BRCM + TITLE:=Kernel driver for the Linksys WRT54G MM/SD-Card Mod + DESCRIPTION:=\ + Kernel driver for the Linksys WRT54G MM/SD-Card Mod + URL:=http://kiel.kool.dk/ + FILES:=$(PKG_BUILD_DIR)/mmc.$(LINUX_KMOD_SUFFIX) + VERSION:=$(LINUX_VERSION)+$(PKG_VERSION)-$(PKG_RELEASE) + AUTOLOAD:=$(call AutoLoad,80,mmc) +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) $(MAKEFLAGS_KMOD) \ + modules +endef + +$(eval $(call KernelPackage,broadcom-mmc)) diff --git a/package/broadcom-mmc/src/Makefile b/package/broadcom-mmc/src/Makefile new file mode 100644 index 000000000..ccf07b169 --- /dev/null +++ b/package/broadcom-mmc/src/Makefile @@ -0,0 +1,7 @@ +obj-m := mmc.o + +ifeq ($(MAKING_MODULES),1) + +-include $(TOPDIR)/Rules.make +endif + diff --git a/package/broadcom-mmc/src/mmc.c b/package/broadcom-mmc/src/mmc.c new file mode 100644 index 000000000..64cb8d97b --- /dev/null +++ b/package/broadcom-mmc/src/mmc.c @@ -0,0 +1,676 @@ +#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/uaccess.h> +#include <asm/io.h> + +#define DEVICE_NAME "mmc" +#define DEVICE_NR(device) (MINOR(device)) +#define DEVICE_ON(device) +#define DEVICE_OFF(device) +#define MAJOR_NR 121 + +#include <linux/blk.h> + +MODULE_AUTHOR("Madsuk/Rohde"); +MODULE_DESCRIPTION("Driver MMC/SD-Cards"); +MODULE_SUPPORTED_DEVICE("WRT54G"); +MODULE_LICENSE("GPL"); + +#define SD_DI 0x20 +#define SD_DO 0x10 +#define SD_CLK 0x08 +#define SD_CS 0x80 + +/* 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; +static int mmc_media_detect = 0; +static int mmc_media_changed = 1; + +typedef unsigned int uint32; + +static unsigned char port_state = 0x00; +static volatile uint32 *gpioaddr_input = (uint32 *)0xb8000060; +static volatile uint32 *gpioaddr_output = (uint32 *)0xb8000064; +static volatile uint32 *gpioaddr_enable = (uint32 *)0xb8000068; +static volatile uint32 *gpioaddr_control = (uint32 *)0xb800006c; + +static void mmc_spi_cs_low(void) +{ + port_state &= ~(SD_CS); + *gpioaddr_output = port_state; +} + +static void mmc_spi_cs_high(void) +{ + port_state |= SD_CS; + *gpioaddr_output = port_state; +} + +static unsigned char mmc_spi_io(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))) + port_state |= SD_DI; + else + port_state &= ~SD_DI; + + *gpioaddr_output = port_state; + port_state |= SD_CLK; + *gpioaddr_output = port_state; + + tmp_data = *gpioaddr_input; + + port_state &= ~SD_CLK; + *gpioaddr_output = port_state; + + result <<= 1; + + if(tmp_data & SD_DO) + result |= 1; + } + + return(result); +} + +static int mmc_write_block(unsigned int dest_addr, unsigned char *data) +{ + unsigned int address; + unsigned char r = 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; + mmc_spi_cs_low(); + for (i = 0; i < 4; i++) mmc_spi_io(0xff); + mmc_spi_io(0x58); + mmc_spi_io(ab3); /* msb */ + mmc_spi_io(ab2); + mmc_spi_io(ab1); + mmc_spi_io(ab0); /* lsb */ + mmc_spi_io(0xff); + for (i = 0; i < 8; i++) + { + r = mmc_spi_io(0xff); + if (r == 0x00) break; + } + if (r != 0x00) + { + mmc_spi_cs_high(); + mmc_spi_io(0xff); + return(1); + } + + mmc_spi_io(0xfe); + for (i = 0; i < 512; i++) mmc_spi_io(data[i]); + for (i = 0; i < 2; i++) mmc_spi_io(0xff); + + for (i = 0; i < 1000000; i++) + { + r = mmc_spi_io(0xff); + if (r == 0xff) break; + } + if (r != 0xff) + { + mmc_spi_cs_high(); + mmc_spi_io(0xff); + return(3); + } + mmc_spi_cs_high(); + mmc_spi_io(0xff); + return(0); +} + +static int mmc_read_block(unsigned char *data, unsigned int src_addr) +{ + unsigned int address; + unsigned char r = 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; + + mmc_spi_cs_low(); + for (i = 0; i < 4; i++) mmc_spi_io(0xff); + mmc_spi_io(0x51); + mmc_spi_io(ab3); /* msb */ + mmc_spi_io(ab2); + mmc_spi_io(ab1); + mmc_spi_io(ab0); /* lsb */ + + mmc_spi_io(0xff); + for (i = 0; i < 8; i++) + { + r = mmc_spi_io(0xff); + if (r == 0x00) break; + } + if (r != 0x00) + { + mmc_spi_cs_high(); + mmc_spi_io(0xff); + return(1); + } + for (i = 0; i < 100000; i++) + { + r = mmc_spi_io(0xff); + if (r == 0xfe) break; + } + if (r != 0xfe) + { + mmc_spi_cs_high(); + mmc_spi_io(0xff); + return(2); + } + for (i = 0; i < 512; i++) + { + r = mmc_spi_io(0xff); + data[i] = r; + } + for (i = 0; i < 2; i++) + { + r = mmc_spi_io(0xff); + } + mmc_spi_cs_high(); + mmc_spi_io(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 rc, 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++) + { + rc = mmc_read_block(buffer_address, mmc_address); + if (rc != 0) + { + printk("mmc: error in mmc_read_block (%d)\n", rc); + 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++) + { + rc = mmc_write_block(mmc_address, buffer_address); + if (rc != 0) + { + printk("mmc: error in mmc_write_block (%d)\n", rc); + 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; + + 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; +} + +extern struct gendisk hd_gendisk; +static int mmc_revalidate(kdev_t dev) +{ + int target, max_p, start, i; + 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_card_init(void) +{ + unsigned char r = 0; + short i, j; + unsigned long flags; + + save_flags(flags); + cli(); + + printk("mmc Card init\n"); + mmc_spi_cs_high(); + for (i = 0; i < 20; i++) mmc_spi_io(0xff); + + mmc_spi_cs_low(); + + mmc_spi_io(0x40); + for (i = 0; i < 4; i++) mmc_spi_io(0x00); + mmc_spi_io(0x95); + for (i = 0; i < 8; i++) + { + r = mmc_spi_io(0xff); + if (r == 0x01) break; + } + mmc_spi_cs_high(); + mmc_spi_io(0xff); + if (r != 0x01) + { + restore_flags(flags); + return(1); + } + + printk("mmc Card init *1*\n"); + for (j = 0; j < 10000; j++) + { + mmc_spi_cs_low(); + + mmc_spi_io(0x41); + for (i = 0; i < 4; i++) mmc_spi_io(0x00); + mmc_spi_io(0xff); + for (i = 0; i < 8; i++) + { + r = mmc_spi_io(0xff); + if (r == 0x00) break; + } + mmc_spi_cs_high(); + mmc_spi_io(0xff); + if (r == 0x00) + { + restore_flags(flags); + printk("mmc Card init *2*\n"); + return(0); + } + } + restore_flags(flags); + + return(2); +} + +static int mmc_card_config(void) +{ + unsigned char r = 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; + + mmc_spi_cs_low(); + for (i = 0; i < 4; i++) mmc_spi_io(0xff); + mmc_spi_io(0x49); + for (i = 0; i < 4; i++) mmc_spi_io(0x00); + mmc_spi_io(0xff); + for (i = 0; i < 8; i++) + { + r = mmc_spi_io(0xff); + if (r == 0x00) break; + } + if (r != 0x00) + { + mmc_spi_cs_high(); + mmc_spi_io(0xff); + return(1); + } + for (i = 0; i < 8; i++) + { + r = mmc_spi_io(0xff); + if (r == 0xfe) break; + } + if (r != 0xfe) + { + mmc_spi_cs_high(); + mmc_spi_io(0xff); + return(2); + } + for (i = 0; i < 16; i++) + { + r = mmc_spi_io(0xff); + csd[i] = r; + } + for (i = 0; i < 2; i++) + { + r = mmc_spi_io(0xff); + } + mmc_spi_cs_high(); + mmc_spi_io(0xff); + if (r == 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; +} + +static int mmc_hardware_init(void) +{ + unsigned char gpio_outen; + + // Set inputs/outputs here + printk("mmc Hardware init\n"); + gpio_outen = *gpioaddr_enable; + + gpio_outen = (gpio_outen | SD_DI | SD_CLK | SD_CS) & ~SD_DO; + *gpioaddr_enable = gpio_outen; + + port_state = *gpioaddr_input; + + // Clock low + port_state &= ~(SD_CLK | SD_DI | SD_CS); + *gpioaddr_output = port_state; + + return 0; +} + +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, +#if 0 + check_media_change: mmc_check_media_change, + revalidate: mmc_revalidate, +#endif +}; + +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 rc; + + rc = mmc_hardware_init(); + + if ( rc != 0) + { + printk("mmc: error in mmc_hardware_init (%d)\n", rc); + return -1; + } + + rc = mmc_card_init(); + if ( rc != 0) + { + // Give it an extra shot + rc = mmc_card_init(); + if ( rc != 0) + { + printk("mmc: error in mmc_card_init (%d)\n", rc); + return -1; + } + } + + memset(hd_sizes, 0, sizeof(hd_sizes)); + rc = mmc_card_config(); + if ( rc != 0) + { + printk("mmc: error in mmc_card_config (%d)\n", rc); + 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; + int rc; + + old_state = mmc_media_detect; + + // TODO: Add card detection here + mmc_media_detect = 1; + if (old_state != mmc_media_detect) + { + mmc_media_changed = 1; + if (mmc_media_detect == 1) + { + rc = mmc_init(); + if (rc != 0) printk("mmc: error in mmc_init (%d)\n", rc); + } + 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 rc; + + rc = devfs_register_blkdev(MAJOR_NR, DEVICE_NAME, &mmc_bdops); + if (rc < 0) + { + printk(KERN_WARNING "mmc: can't get major %d\n", MAJOR_NR); + return rc; + } + + blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), mmc_request); + + read_ahead[MAJOR_NR] = 8; + add_gendisk(&hd_gendisk); + + mmc_check_media(); + + /*init_timer(&mmc_timer); + mmc_timer.expires = jiffies + HZ; + mmc_timer.function = (void *)mmc_check_media; + add_timer(&mmc_timer);*/ + + 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); |