Subject: cell: driver for DDR2 memory on AXON From: Maxim Shchetynin Signed-off-by: Arnd Bergmann Index: linux-2.6/arch/powerpc/Kconfig =================================================================== --- linux-2.6.orig/arch/powerpc/Kconfig +++ linux-2.6/arch/powerpc/Kconfig @@ -700,6 +700,13 @@ config TAU_AVERAGE If in doubt, say N here. +config AXON_RAM + tristate "Axon DDR2 memory device driver" + depends on PPC_IBM_CELL_BLADE + default y + help + Block and character device driver for Axon DDR2 RAM support. + endmenu source arch/powerpc/platforms/embedded6xx/Kconfig Index: linux-2.6/arch/powerpc/sysdev/Makefile =================================================================== --- linux-2.6.orig/arch/powerpc/sysdev/Makefile +++ linux-2.6/arch/powerpc/sysdev/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_MMIO_NVRAM) += mmio_nvram.o obj-$(CONFIG_FSL_SOC) += fsl_soc.o obj-$(CONFIG_TSI108_BRIDGE) += tsi108_pci.o tsi108_dev.o obj-$(CONFIG_QUICC_ENGINE) += qe_lib/ +obj-$(CONFIG_AXON_RAM) += axonram.o ifeq ($(CONFIG_PPC_MERGE),y) obj-$(CONFIG_PPC_I8259) += i8259.o Index: linux-2.6/arch/powerpc/sysdev/axonram.c =================================================================== --- /dev/null +++ linux-2.6/arch/powerpc/sysdev/axonram.c @@ -0,0 +1,498 @@ +/* + * (C) Copyright IBM Corp. 2006 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Axon DDR2 driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AXON_RAM_MODULE_NAME "axonram" +#define AXON_RAM_DEVICE_NAME "axonram" +#define AXON_RAM_MINORS_PER_DISK 16 +#define AXON_RAM_BLOCK_SHIFT 12 /* block size 4KB */ +#define AXON_RAM_SECTOR_SHIFT 9 /* sector size 512 */ + +#define axon_ram_error(format, args...) \ + printk(KERN_ERR AXON_RAM_MODULE_NAME ": " format, ##args); + +static int +axon_ram_open(struct inode *, struct file *); +static int +axon_ram_release(struct inode *, struct file *); +static loff_t +axon_ram_llseek(struct file *, loff_t, int); +static ssize_t +axon_ram_read(struct file *, char __user *, size_t, loff_t *); +static ssize_t +axon_ram_write(struct file *, const char __user *, size_t, loff_t *); +static int +axon_ram_mmap(struct file *, struct vm_area_struct *); +static int +axon_ram_direct_access(struct block_device *, sector_t, unsigned long *); +static int +axon_ram_make_request(struct request_queue *, struct bio *); +static int +axon_ram_probe(struct of_device *, const struct of_device_id *); +static int +axon_ram_remove(struct of_device *); + +struct axon_ram_bank { + char name[DEVICE_NAME_SIZE]; + rwlock_t lock; + phys_addr_t phys_addr; + void __iomem *addr; + size_t size; + struct gendisk *disk; + struct miscdevice misc; + struct file_operations fops; +}; + +/** + * axon_ram_open - open() method for device + * @inode, @file: see file_operations method + */ +static int +axon_ram_open(struct inode *inode, struct file *file) +{ + struct axon_ram_bank *bank; + + /* Prevent access to memory bank using simultaneously both */ + /* block and character device methods. */ + if (S_ISCHR(inode->i_mode)) { + file->private_data = (void*) file->f_op + + sizeof(struct file_operations) - + sizeof(struct axon_ram_bank); + bank = (struct axon_ram_bank*) file->private_data; + + /* Allow only a single open of character device. */ + /* No access to device if it is already read-locked */ + /* (opened at least one time as block device). */ + if (!write_trylock(&bank->lock)) + return -EBUSY; + } else if (S_ISBLK(inode->i_mode)) { + bank = (struct axon_ram_bank*) + inode->i_bdev->bd_disk->private_data; + + /* Block device maybe opened multiple times therefore */ + /* read-lock. No access to device if it is already */ + /* write-locked (opened as character device). */ + if (!read_trylock(&bank->lock)) + return -EBUSY; + inode->i_bdev->bd_block_size = 1 << AXON_RAM_BLOCK_SHIFT; + } else { + return -ENODEV; + } + + return 0; +} + +/** + * axon_ram_release - release() method for device + * @inode, @file: see file_operations method + */ +static int +axon_ram_release(struct inode *inode, struct file *file) +{ + struct axon_ram_bank *bank; + + if (S_ISCHR(inode->i_mode)) { + bank = (struct axon_ram_bank*) + file->private_data; + write_unlock(&bank->lock); + } else if (S_ISBLK(inode->i_mode)) { + bank = (struct axon_ram_bank*) + inode->i_bdev->bd_disk->private_data; + read_unlock(&bank->lock); + } else { + /* Paranoja */ + return -ENODEV; + } + + return 0; +} + +/** + * axon_ram_llseek - llseek() method for character device + * @file, @offset, @origin: see file_operations method + */ +static loff_t +axon_ram_llseek(struct file *file, loff_t offset, int origin) +{ + struct axon_ram_bank *bank; + + bank = (struct axon_ram_bank*) file->private_data; + + switch (origin) { + case SEEK_CUR: + offset += file->f_pos; + break; + + case SEEK_END: + offset += bank->size; + break; + } + + /* No access outside a memory DIMM. */ + if (offset < 0 || offset > bank->size) + return -EINVAL; + + return file->f_pos = offset; +} + +/** + * axon_ram_read - read() method for character device + * @file, @buffer, @size, @offset: see file_operations method + */ +static ssize_t +axon_ram_read(struct file *file, + char __user *buffer, size_t size, loff_t *offset) +{ + struct axon_ram_bank *bank; + + bank = (struct axon_ram_bank*) file->private_data; + /* No access outside a memory DIMM but still do as much as possible. */ + if (*offset + size > bank->size) + size = bank->size - *offset; + copy_to_user(__user buffer, __iomem bank->addr + *offset, size); + *offset += size; + + return size; +} + +/** + * axon_ram_write - write() method for character device + * @file, @buffer, @size, @offset: see file_operations method + */ +static ssize_t +axon_ram_write(struct file *file, + const char __user *buffer, size_t size, loff_t *offset) +{ + struct axon_ram_bank *bank; + + bank = (struct axon_ram_bank*) file->private_data; + /* No access outside a memory DIMM but still do as much as possible. */ + if (*offset + size > bank->size) + size = bank->size - *offset; + copy_from_user(__iomem bank->addr + *offset, __user buffer, size); + *offset += size; + + return size; +} + +/** + * axon_ram_mmap - mmap() method for character device + * @file, @vm: see file_operations method + */ +static int +axon_ram_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct axon_ram_bank *bank; + unsigned long page; + unsigned long size; + pgprot_t page_prot; + int retval = 0; + + bank = (struct axon_ram_bank*) file->private_data; + + size = vma->vm_end - vma->vm_start; + + /* No access outside a memory DIMM. */ + if ((vma->vm_pgoff << PAGE_SHIFT) + size > bank->size) { + retval = -ERANGE; + goto out; + } + + /* Transfer real address to page number. */ + page = bank->phys_addr; + page >>= PAGE_SHIFT; + page += vma->vm_pgoff; + + /* Switch off page-guard. */ + page_prot = pgprot_val(vma->vm_page_prot); + page_prot &= ~(_PAGE_NO_CACHE | _PAGE_GUARDED); + vma->vm_page_prot = __pgprot(page_prot); + if (remap_pfn_range(vma, vma->vm_start, page, size, vma->vm_page_prot)) + retval = -EAGAIN; + +out: + return retval; +} + +/** + * axon_ram_direct_access - direct_access() method for block device + * @device, @sector, @data: see block_device_operations method + */ +static int +axon_ram_direct_access(struct block_device *device, sector_t sector, + unsigned long *data) +{ + struct axon_ram_bank *bank; + loff_t offset; + + bank = (struct axon_ram_bank*) device->bd_disk->private_data; + /* Paranoja in it's best traditions. */ + if (bank == NULL) + return -ENODEV; + + /* No access outside a memory DIMM. */ + offset = sector << AXON_RAM_SECTOR_SHIFT; + if (offset > bank->size) + return -ERANGE; + + *data = (unsigned long) (bank->phys_addr + offset); + + return 0; +} + +/** + * axon_ram_make_request - make_request() method for block device + * @queue, @bio: see blk_queue_make_request() + */ +static int +axon_ram_make_request(struct request_queue *queue, struct bio *bio) +{ + struct axon_ram_bank *bank; + unsigned long phys_mem, phys_end, user_mem; + unsigned long transfered; + struct bio_vec *vec; + int i; + + bank = (struct axon_ram_bank*) bio->bi_bdev->bd_disk->private_data; + phys_mem = (unsigned long) + bank->addr + (bio->bi_sector << AXON_RAM_SECTOR_SHIFT); + phys_end = (unsigned long) + bank->addr + bank->size; + transfered = 0; + bio_for_each_segment(vec, bio, i) { + /* No access outside a memory DIMM. */ + if (phys_mem + vec->bv_len > phys_end) { + bio_io_error(bio, bio->bi_size); + return -ERANGE; + } + + user_mem = (unsigned long) + page_address(vec->bv_page) + vec->bv_offset; + if (bio_data_dir(bio) == READ) + memcpy((void*) user_mem, (void*) phys_mem, vec->bv_len); + else + memcpy((void*) phys_mem, (void*) user_mem, vec->bv_len); + + phys_mem += vec->bv_len; + transfered += vec->bv_len; + } + bio_endio(bio, transfered, 0); + + return 0; +} + +static struct block_device_operations axon_ram_devops = { + .owner = THIS_MODULE, + .open = axon_ram_open, + .release = axon_ram_release, + .direct_access = axon_ram_direct_access +}; + +/** + * axon_ram_probe - probe() method for platform driver + * @device, @device_id: see of_platform_driver method + */ +static int +axon_ram_probe(struct of_device *device, const struct of_device_id *device_id) +{ + static int axon_ram_bank_id = -1; + struct axon_ram_bank *bank; + const u32 *reg0; + const u64 *reg1; + phys_addr_t phys_addr; + size_t size; + + /* There should be a better way... */ + axon_ram_bank_id++; + + printk("Found DDR2 memory DIMM %s%d on %s\n", + AXON_RAM_DEVICE_NAME, axon_ram_bank_id, + device->node->full_name); + + reg0 = get_property(device->node, "reg", NULL); + if (reg0 == NULL) { + axon_ram_error("Cannot access device tree!\n"); + return -EFAULT; + } + reg1 = (void*) reg0 + sizeof(u64); + + phys_addr = of_translate_address(device->node, reg0); + if (phys_addr == OF_BAD_ADDR) { + axon_ram_error("Cannot access device tree!\n"); + return -EFAULT; + } + +/* Wait until Firmware is fixed. */ +#if 0 + size = *reg1; +#else + size = 512 * 1024 * 1024; +#endif + + bank = kzalloc(sizeof(struct axon_ram_bank), GFP_KERNEL); + if (bank == NULL) { + axon_ram_error("Out of memory!\n"); + return -ENOMEM; + } + rwlock_init(&bank->lock); + device->dev.platform_data = (void*) bank; + + snprintf(bank->name, DEVICE_NAME_SIZE, "%s%d", + AXON_RAM_DEVICE_NAME, axon_ram_bank_id); + bank->phys_addr = phys_addr; + bank->addr = __iomem ioremap_flags(phys_addr, size, 0); + bank->size = size; + + bank->misc.minor = MISC_DYNAMIC_MINOR; + bank->misc.name = bank->name; + bank->misc.fops = &bank->fops; + bank->misc.parent = &device->dev; + + bank->fops.owner = THIS_MODULE; + bank->fops.open = axon_ram_open; + bank->fops.release = axon_ram_release; + bank->fops.llseek = axon_ram_llseek; + bank->fops.read = axon_ram_read; + bank->fops.write = axon_ram_write; + bank->fops.mmap = axon_ram_mmap; + + /* Don't quit if it fails - maybe at least block device would work. */ + if (misc_register(&bank->misc) != 0) + axon_ram_error("Cannot register character device!\n"); + + bank->disk = alloc_disk(AXON_RAM_MINORS_PER_DISK); + if (bank->disk == NULL) { + axon_ram_error("Cannot register disk!\n"); + if (bank->misc.minor != MISC_DYNAMIC_MINOR) + misc_deregister(&bank->misc); + kfree(bank); + return -EFAULT; + } + + strcpy(bank->disk->disk_name, bank->name); + + bank->disk->major = register_blkdev(0, bank->disk->disk_name); + if (bank->disk->major < 0) { + axon_ram_error("Cannot register block device!\n"); + if (bank->misc.minor != MISC_DYNAMIC_MINOR) + misc_deregister(&bank->misc); + del_gendisk(bank->disk); + kfree(bank); + return -EFAULT; + } + + bank->disk->first_minor = 0; + bank->disk->fops = &axon_ram_devops; + bank->disk->queue = blk_alloc_queue(GFP_KERNEL); + bank->disk->private_data = (void*) bank; + bank->disk->driverfs_dev = &device->dev; + + set_capacity(bank->disk, bank->size >> AXON_RAM_SECTOR_SHIFT); + blk_queue_make_request(bank->disk->queue, axon_ram_make_request); + blk_queue_hardsect_size(bank->disk->queue, 1 << AXON_RAM_BLOCK_SHIFT); + add_disk(bank->disk); + + return 0; +} + +/** + * axon_ram_remove - remove() method for platform driver + * @device: see of_platform_driver method + */ +static int +axon_ram_remove(struct of_device *device) +{ + struct axon_ram_bank *bank; + int rc = 0; + + bank = (struct axon_ram_bank*) device->dev.platform_data; + + if (bank->misc.minor != MISC_DYNAMIC_MINOR) + rc |= misc_deregister(&bank->misc); + + if (bank->disk != NULL) { + if (bank->disk->major > 0) + rc |= unregister_blkdev(bank->disk->major, + bank->disk->disk_name); + unlink_gendisk(bank->disk); + del_gendisk(bank->disk); + } + + if (rc == 0) + kfree(bank); + + return rc == 0 ? 0 : -EFAULT; +} + +static struct of_device_id axon_ram_device_id[] = { + { + .type = "dma-memory" + }, + {} +}; + +static struct of_platform_driver axon_ram_driver = { + .owner = THIS_MODULE, + .name = AXON_RAM_MODULE_NAME, + .match_table = axon_ram_device_id, + .probe = axon_ram_probe, + .remove = axon_ram_remove +}; + +/** + * axon_ram_init + */ +static int __init +axon_ram_init(void) +{ + return of_register_platform_driver(&axon_ram_driver); +} + +/** + * axon_ram_exit + */ +static void __exit +axon_ram_exit(void) +{ + of_unregister_platform_driver(&axon_ram_driver); +} + +module_init(axon_ram_init); +module_exit(axon_ram_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("IBM Deutschland Entwicklung GmbH"); +MODULE_DESCRIPTION("Axon DDR2 RAM device driver for IBM Cell BE");