diff options
Diffstat (limited to 'target/linux/rb532/files/drivers/block/rb500/bdev.c')
-rw-r--r-- | target/linux/rb532/files/drivers/block/rb500/bdev.c | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/target/linux/rb532/files/drivers/block/rb500/bdev.c b/target/linux/rb532/files/drivers/block/rb500/bdev.c new file mode 100644 index 0000000000..e4532739b9 --- /dev/null +++ b/target/linux/rb532/files/drivers/block/rb500/bdev.c @@ -0,0 +1,339 @@ +/* CF-mips driver + This is a block driver for the direct (mmaped) interface to the CF-slot, + found in Routerboard.com's RB532 board + See SDK provided from routerboard.com. + + Module adapted By P.Christeas <p_christeas@yahoo.com>, 2005-6. + Cleaned up and adapted to platform_device by Felix Fietkau <nbd@openwrt.org> + + This work is redistributed under the terms of the GNU General Public License. +*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/fs.h> +#include <linux/genhd.h> +#include <linux/blkdev.h> +#include <linux/blkpg.h> +#include <linux/hdreg.h> +#include <linux/platform_device.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +#include <asm/rc32434/rb.h> + +#ifdef DEBUG +#define DEBUGP printk +#define DLEVEL 1 +#else +#define DEBUGP(format, args...) +#define DLEVEL 0 +#endif + +#define CF_MIPS_MAJOR 13 +#define MAJOR_NR CF_MIPS_MAJOR +#define CF_MAX_PART 16 /* max 15 partitions */ + +#include "ata.h" + +//extern struct block_device_operations cf_bdops; + +// static struct hd_struct cf_parts[CF_MAX_PART]; +// static int cf_part_sizes[CF_MAX_PART]; +// static int cf_hsect_sizes[CF_MAX_PART]; +// static int cf_max_sectors[CF_MAX_PART]; +// static int cf_blksize_sizes[CF_MAX_PART]; + +// static spinlock_t lock = SPIN_LOCK_UNLOCKED; + +// volatile int cf_busy = 0; + +static struct request *active_req = NULL; + +static int cf_open (struct inode *, struct file *); +static int cf_release (struct inode *, struct file *); +static int cf_ioctl (struct inode *, struct file *, unsigned, unsigned long); + +static void cf_request(request_queue_t * q); +static int cf_transfer(const struct request *req); + +/*long (*unlocked_ioctl) (struct file *, unsigned, unsigned long); +long (*compat_ioctl) (struct file *, unsigned, unsigned long);*/ +// int (*direct_access) (struct block_device *, sector_t, unsigned long *); +// int (*media_changed) (struct gendisk *); +// int (*revalidate_disk) (struct gendisk *); + +static struct block_device_operations cf_bdops = { + .owner = THIS_MODULE, + .open = cf_open, + .release = cf_release, + .ioctl = cf_ioctl, + .media_changed = NULL, + .unlocked_ioctl = NULL, + .revalidate_disk = NULL, + .compat_ioctl = NULL, + .direct_access = NULL +}; + + +int cf_mips_probe(struct platform_device *pdev) +{ + struct gendisk* cf_gendisk=NULL; + struct cf_device *cdev = (struct cf_device *) pdev->dev.platform_data; + struct cf_mips_dev *dev; + struct resource *r; + int reg_result; + + reg_result = register_blkdev(MAJOR_NR, "cf-mips"); + if (reg_result < 0) { + printk(KERN_WARNING "cf-mips: can't get major %d\n", MAJOR_NR); + return reg_result; + } + + dev = (struct cf_mips_dev *)kmalloc(sizeof(struct cf_mips_dev),GFP_KERNEL); + if (!dev) + goto out_err; + memset(dev, 0, sizeof(struct cf_mips_dev)); + cdev->dev = dev; + + dev->pin = cdev->gpio_pin; + dev->irq = platform_get_irq_byname(pdev, "cf_irq"); + r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cf_membase"); + dev->base = (void *) r->start; + + if (cf_init(dev)) goto out_err; + printk("init done"); + + spin_lock_init(&dev->lock); + dev->queue = blk_init_queue(cf_request,&dev->lock); + if (!dev->queue){ + printk(KERN_ERR "cf-mips: no mem for queue\n"); + goto out_err; + } + blk_queue_max_sectors(dev->queue,ATA_MAX_SECT_PER_CMD); + + /* For memory devices, it is always better to avoid crossing segments + inside the same request. */ +/* if (dev->dtype==0x848A){ + printk(KERN_INFO "Setting boundary for cf to 0x%x",(dev->block_size*512)-1); + blk_queue_segment_boundary(dev->queue, (dev->block_size*512)-1); + }*/ + + dev->gd = alloc_disk(CF_MAX_PART); + cf_gendisk = dev->gd; + cdev->gd = dev->gd; + if (!cf_gendisk) goto out_err; /* Last of these goto's */ + + cf_gendisk->major = MAJOR_NR; + cf_gendisk->first_minor = 0; + cf_gendisk->queue=dev->queue; + BUG_ON(cf_gendisk->minors != CF_MAX_PART); + strcpy(cf_gendisk->disk_name,"cfa"); + cf_gendisk->fops = &cf_bdops; + cf_gendisk->flags = 0 ; /* is not yet GENHD_FL_REMOVABLE */ + cf_gendisk->private_data=dev; + + set_capacity(cf_gendisk,dev->sectors * CF_KERNEL_MUL); + + /* Let the disk go live */ + add_disk(cf_gendisk); +#if 0 + result = cf_init(); + + /* default cfg for all partitions */ + memset(cf_parts, 0, sizeof (cf_parts[0]) * CF_MAX_PART); + memset(cf_part_sizes, 0, sizeof (cf_part_sizes[0]) * CF_MAX_PART); + for (i = 0; i < CF_MAX_PART; ++i) { + cf_hsect_sizes[i] = CF_SECT_SIZE; + cf_max_sectors[i] = ATA_MAX_SECT_PER_CMD; + cf_blksize_sizes[i] = BLOCK_SIZE; + } + + /* setup info for whole disk (partition 0) */ + cf_part_sizes[0] = cf_sectors / 2; + cf_parts[0].nr_sects = cf_sectors; + + blk_size[MAJOR_NR] = cf_part_sizes; + blksize_size[MAJOR_NR] = cf_blksize_sizes; + max_sectors[MAJOR_NR] = cf_max_sectors; + hardsect_size[MAJOR_NR] = cf_hsect_sizes; + read_ahead[MAJOR_NR] = 8; /* (4kB) */ + + blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), DEVICE_REQUEST); + + add_gendisk(&cf_gendisk); +#endif +// printk(KERN_INFO "cf-mips partition check: \n"); +// register_disk(cf_gendisk, MKDEV(MAJOR_NR, 0), CF_MAX_PART, +// &cf_bdops, dev->sectors); + return 0; + +out_err: + if (dev->queue){ + blk_cleanup_queue(dev->queue); + } + if (reg_result) { + unregister_blkdev(MAJOR_NR, "cf-mips"); + return reg_result; + } + if (dev){ + cf_cleanup(dev); + kfree(dev); + } + return 1; +} + +static int +cf_mips_remove(struct platform_device *pdev) +{ + struct cf_device *cdev = (struct cf_device *) pdev->dev.platform_data; + struct cf_mips_dev *dev = (struct cf_mips_dev *) cdev->dev; + + unregister_blkdev(MAJOR_NR, "cf-mips"); + blk_cleanup_queue(dev->queue); + + del_gendisk(dev->gd); + cf_cleanup(dev); + return 0; +} + + +static struct platform_driver cf_driver = { + .driver.name = "rb500-cf", + .probe = cf_mips_probe, + .remove = cf_mips_remove, +}; + +static int __init cf_mips_init(void) +{ + printk(KERN_INFO "cf-mips module loaded\n"); + return platform_driver_register(&cf_driver); +} + +static void cf_mips_cleanup(void) +{ + platform_driver_unregister(&cf_driver); + printk(KERN_INFO "cf-mips module removed\n"); +} + +module_init(cf_mips_init); +module_exit(cf_mips_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_BLOCKDEV_MAJOR(CF_MIPS_MAJOR); + + +static int cf_open(struct inode *inode, struct file *filp) +{ + struct cf_mips_dev *dev=inode->i_bdev->bd_disk->private_data; + int minor = MINOR(inode->i_rdev); + + if (minor >= CF_MAX_PART) + return -ENODEV; + //DEBUGP(KERN_INFO "cf-mips module opened, minor %d\n", minor); + spin_lock(&dev->lock); + dev->users++; + spin_unlock(&dev->lock); + filp->private_data=dev; + + /* dirty workaround to set CFRDY GPIO as an input when some other + program sets it as an output */ + gpio_set(CFG, (1 << dev->pin), 0); + return 0; /* success */ +} + +static int cf_release(struct inode *inode, struct file *filp) +{ + int minor = MINOR(inode->i_rdev); + struct cf_mips_dev *dev=inode->i_bdev->bd_disk->private_data; + spin_lock(&dev->lock); + dev->users--; + spin_unlock(&dev->lock); + return 0; +} + +static int cf_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + unsigned minor = MINOR(inode->i_rdev); + struct cf_mips_dev *dev=inode->i_bdev->bd_disk->private_data; + + DEBUGP(KERN_INFO "cf_ioctl cmd %u\n", cmd); + switch (cmd) { + case BLKRRPART: /* re-read partition table */ + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + printk(KERN_INFO "cf-mips partition check: \n"); + register_disk(dev->gd); + return 0; + + case HDIO_GETGEO: + { + struct hd_geometry geo; + geo.cylinders = dev->cyl; + geo.heads = dev->head; + geo.sectors = dev->spt; + geo.start = (*dev->gd->part)[minor].start_sect; + if (copy_to_user((void *) arg, &geo, sizeof (geo))) + return -EFAULT; + } + return 0; + } + + return -EINVAL; /* unknown command */ +} + +static void cf_request(request_queue_t * q) +{ + struct cf_mips_dev* dev; + + struct request * req; + int status; + + /* We could have q->queuedata = dev , but haven't yet. */ + if (active_req) + return; + + while ((req=elv_next_request(q))!=NULL){ + dev=req->rq_disk->private_data; + status=cf_transfer(req); + if (status==CF_TRANS_IN_PROGRESS){ + active_req=req; + return; + } + end_request(req,status); + } +} + +static int cf_transfer(const struct request *req) +{ + struct cf_mips_dev* dev=req->rq_disk->private_data; + + if (!blk_fs_request(req)){ + if (printk_ratelimit()) + printk(KERN_WARNING "cf-mips: skipping non-fs request 0x%x\n",req->cmd[0]); + return CF_TRANS_FAILED; + } + + return cf_do_transfer(dev,req->sector,req->current_nr_sectors,req->buffer,rq_data_dir(req)); +} + +void cf_async_trans_done(struct cf_mips_dev * dev,int result) +{ + struct request *req; + + spin_lock(&dev->lock); + req=active_req; + active_req=NULL; + end_request(req,result); + spin_unlock(&dev->lock); + + spin_lock(&dev->lock); + cf_request(dev->queue); + spin_unlock(&dev->lock); +} + |