diff options
| -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);  | 
