2007-07-20 21:39:26 +02:00
/*
* ( C ) Copyright IBM Deutschland Entwicklung GmbH 2006
*
* Author : Maxim Shchetynin < maxim @ de . ibm . com >
*
* Axon DDR2 device driver .
* It registers one block device per Axon ' s DDR2 memory bank found on a system .
* Block devices are called axonram ? , their major and minor numbers are
* available in / proc / devices , / proc / partitions or in / sys / block / axonram ? / dev .
*
* 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 , or ( at your option )
* any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/bio.h>
# include <linux/blkdev.h>
# include <linux/buffer_head.h>
# include <linux/device.h>
# include <linux/errno.h>
# include <linux/fs.h>
# include <linux/genhd.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/irq.h>
# include <linux/irqreturn.h>
# include <linux/kernel.h>
# include <linux/mm.h>
# include <linux/mod_devicetable.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/string.h>
# include <linux/types.h>
2007-11-14 04:13:09 +11:00
# include <linux/of_device.h>
# include <linux/of_platform.h>
2007-07-20 21:39:26 +02:00
# include <asm/page.h>
# include <asm/prom.h>
# define AXON_RAM_MODULE_NAME "axonram"
# define AXON_RAM_DEVICE_NAME "axonram"
# define AXON_RAM_MINORS_PER_DISK 16
# define AXON_RAM_BLOCK_SHIFT PAGE_SHIFT
# define AXON_RAM_BLOCK_SIZE 1 << AXON_RAM_BLOCK_SHIFT
# define AXON_RAM_SECTOR_SHIFT 9
# define AXON_RAM_SECTOR_SIZE 1 << AXON_RAM_SECTOR_SHIFT
# define AXON_RAM_IRQ_FLAGS IRQF_SHARED | IRQF_TRIGGER_RISING
2008-07-16 05:51:41 +10:00
static int azfs_major , azfs_minor ;
2007-07-20 21:39:26 +02:00
struct axon_ram_bank {
struct of_device * device ;
struct gendisk * disk ;
2007-08-23 03:01:27 +10:00
unsigned int irq_id ;
2007-07-20 21:39:26 +02:00
unsigned long ph_addr ;
unsigned long io_addr ;
unsigned long size ;
unsigned long ecc_counter ;
} ;
static ssize_t
axon_ram_sysfs_ecc ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct of_device * device = to_of_device ( dev ) ;
struct axon_ram_bank * bank = device - > dev . platform_data ;
BUG_ON ( ! bank ) ;
return sprintf ( buf , " %ld \n " , bank - > ecc_counter ) ;
}
static DEVICE_ATTR ( ecc , S_IRUGO , axon_ram_sysfs_ecc , NULL ) ;
/**
* axon_ram_irq_handler - interrupt handler for Axon RAM ECC
* @ irq : interrupt ID
* @ dev : pointer to of_device
*/
static irqreturn_t
axon_ram_irq_handler ( int irq , void * dev )
{
struct of_device * device = dev ;
struct axon_ram_bank * bank = device - > dev . platform_data ;
BUG_ON ( ! bank ) ;
2007-08-23 03:01:27 +10:00
dev_err ( & device - > dev , " Correctable memory error occured \n " ) ;
bank - > ecc_counter + + ;
return IRQ_HANDLED ;
2007-07-20 21:39:26 +02:00
}
/**
* 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 = bio - > bi_bdev - > bd_disk - > private_data ;
unsigned long phys_mem , phys_end ;
void * user_mem ;
struct bio_vec * vec ;
unsigned int transfered ;
unsigned short idx ;
int rc = 0 ;
phys_mem = bank - > io_addr + ( bio - > bi_sector < < AXON_RAM_SECTOR_SHIFT ) ;
phys_end = bank - > io_addr + bank - > size ;
transfered = 0 ;
bio_for_each_segment ( vec , bio , idx ) {
if ( unlikely ( phys_mem + vec - > bv_len > phys_end ) ) {
2007-10-12 07:00:19 +01:00
bio_io_error ( bio ) ;
2007-07-20 21:39:26 +02:00
rc = - ERANGE ;
break ;
}
user_mem = page_address ( vec - > bv_page ) + vec - > bv_offset ;
if ( bio_data_dir ( bio ) = = READ )
memcpy ( user_mem , ( void * ) phys_mem , vec - > bv_len ) ;
else
memcpy ( ( void * ) phys_mem , user_mem , vec - > bv_len ) ;
phys_mem + = vec - > bv_len ;
transfered + = vec - > bv_len ;
}
2007-10-12 07:00:19 +01:00
bio_endio ( bio , 0 ) ;
2007-07-20 21:39:26 +02:00
return rc ;
}
/**
* 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 ,
2008-04-28 02:13:02 -07:00
void * * kaddr , unsigned long * pfn )
2007-07-20 21:39:26 +02:00
{
struct axon_ram_bank * bank = device - > bd_disk - > private_data ;
loff_t offset ;
2008-07-16 05:51:42 +10:00
offset = sector ;
if ( device - > bd_part ! = NULL )
offset + = device - > bd_part - > start_sect ;
offset < < = AXON_RAM_SECTOR_SHIFT ;
2007-07-20 21:39:26 +02:00
if ( offset > = bank - > size ) {
dev_err ( & bank - > device - > dev , " Access outside of address space \n " ) ;
return - ERANGE ;
}
2008-04-28 02:13:02 -07:00
* kaddr = ( void * ) ( bank - > ph_addr + offset ) ;
* pfn = virt_to_phys ( kaddr ) > > PAGE_SHIFT ;
2007-07-20 21:39:26 +02:00
return 0 ;
}
2009-09-21 17:01:13 -07:00
static const struct block_device_operations axon_ram_devops = {
2007-07-20 21:39:26 +02:00
. owner = THIS_MODULE ,
. 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 ;
struct resource resource ;
int rc = 0 ;
axon_ram_bank_id + + ;
dev_info ( & device - > dev , " Found memory controller on %s \n " ,
device - > node - > full_name ) ;
bank = kzalloc ( sizeof ( struct axon_ram_bank ) , GFP_KERNEL ) ;
if ( bank = = NULL ) {
dev_err ( & device - > dev , " Out of memory \n " ) ;
rc = - ENOMEM ;
goto failed ;
}
device - > dev . platform_data = bank ;
bank - > device = device ;
if ( of_address_to_resource ( device - > node , 0 , & resource ) ! = 0 ) {
dev_err ( & device - > dev , " Cannot access device tree \n " ) ;
rc = - EFAULT ;
goto failed ;
}
bank - > size = resource . end - resource . start + 1 ;
if ( bank - > size = = 0 ) {
dev_err ( & device - > dev , " No DDR2 memory found for %s%d \n " ,
AXON_RAM_DEVICE_NAME , axon_ram_bank_id ) ;
rc = - ENODEV ;
goto failed ;
}
dev_info ( & device - > dev , " Register DDR2 memory device %s%d with %luMB \n " ,
AXON_RAM_DEVICE_NAME , axon_ram_bank_id , bank - > size > > 20 ) ;
bank - > ph_addr = resource . start ;
bank - > io_addr = ( unsigned long ) ioremap_flags (
bank - > ph_addr , bank - > size , _PAGE_NO_CACHE ) ;
if ( bank - > io_addr = = 0 ) {
dev_err ( & device - > dev , " ioremap() failed \n " ) ;
rc = - EFAULT ;
goto failed ;
}
bank - > disk = alloc_disk ( AXON_RAM_MINORS_PER_DISK ) ;
if ( bank - > disk = = NULL ) {
dev_err ( & device - > dev , " Cannot register disk \n " ) ;
rc = - EFAULT ;
goto failed ;
}
2008-07-16 05:51:41 +10:00
bank - > disk - > major = azfs_major ;
bank - > disk - > first_minor = azfs_minor ;
2007-07-20 21:39:26 +02:00
bank - > disk - > fops = & axon_ram_devops ;
bank - > disk - > private_data = bank ;
bank - > disk - > driverfs_dev = & device - > dev ;
sprintf ( bank - > disk - > disk_name , " %s%d " ,
AXON_RAM_DEVICE_NAME , axon_ram_bank_id ) ;
bank - > disk - > queue = blk_alloc_queue ( GFP_KERNEL ) ;
if ( bank - > disk - > queue = = NULL ) {
dev_err ( & device - > dev , " Cannot register disk queue \n " ) ;
rc = - EFAULT ;
goto failed ;
}
set_capacity ( bank - > disk , bank - > size > > AXON_RAM_SECTOR_SHIFT ) ;
blk_queue_make_request ( bank - > disk - > queue , axon_ram_make_request ) ;
2009-05-22 17:17:49 -04:00
blk_queue_logical_block_size ( bank - > disk - > queue , AXON_RAM_SECTOR_SIZE ) ;
2007-07-20 21:39:26 +02:00
add_disk ( bank - > disk ) ;
2007-08-23 03:01:27 +10:00
bank - > irq_id = irq_of_parse_and_map ( device - > node , 0 ) ;
if ( bank - > irq_id = = NO_IRQ ) {
2007-07-20 21:39:26 +02:00
dev_err ( & device - > dev , " Cannot access ECC interrupt ID \n " ) ;
rc = - EFAULT ;
goto failed ;
}
2007-08-23 03:01:27 +10:00
rc = request_irq ( bank - > irq_id , axon_ram_irq_handler ,
2007-07-20 21:39:26 +02:00
AXON_RAM_IRQ_FLAGS , bank - > disk - > disk_name , device ) ;
if ( rc ! = 0 ) {
dev_err ( & device - > dev , " Cannot register ECC interrupt handler \n " ) ;
2007-08-23 03:01:27 +10:00
bank - > irq_id = NO_IRQ ;
2007-07-20 21:39:26 +02:00
rc = - EFAULT ;
goto failed ;
}
rc = device_create_file ( & device - > dev , & dev_attr_ecc ) ;
if ( rc ! = 0 ) {
dev_err ( & device - > dev , " Cannot create sysfs file \n " ) ;
rc = - EFAULT ;
goto failed ;
}
2008-07-16 05:51:41 +10:00
azfs_minor + = bank - > disk - > minors ;
2007-07-20 21:39:26 +02:00
return 0 ;
failed :
if ( bank ! = NULL ) {
2007-08-23 03:01:27 +10:00
if ( bank - > irq_id ! = NO_IRQ )
free_irq ( bank - > irq_id , device ) ;
2007-07-20 21:39:26 +02:00
if ( bank - > disk ! = NULL ) {
if ( bank - > disk - > major > 0 )
unregister_blkdev ( bank - > disk - > major ,
bank - > disk - > disk_name ) ;
del_gendisk ( bank - > disk ) ;
}
device - > dev . platform_data = NULL ;
if ( bank - > io_addr ! = 0 )
iounmap ( ( void __iomem * ) bank - > io_addr ) ;
kfree ( bank ) ;
}
return rc ;
}
/**
* 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 = device - > dev . platform_data ;
BUG_ON ( ! bank | | ! bank - > disk ) ;
device_remove_file ( & device - > dev , & dev_attr_ecc ) ;
2007-08-23 03:01:27 +10:00
free_irq ( bank - > irq_id , device ) ;
2007-07-20 21:39:26 +02:00
del_gendisk ( bank - > disk ) ;
iounmap ( ( void __iomem * ) bank - > io_addr ) ;
kfree ( bank ) ;
return 0 ;
}
static struct of_device_id axon_ram_device_id [ ] = {
{
. type = " dma-memory "
} ,
{ }
} ;
static struct of_platform_driver axon_ram_driver = {
. match_table = axon_ram_device_id ,
. probe = axon_ram_probe ,
2007-10-11 15:19:03 +10:00
. remove = axon_ram_remove ,
. driver = {
. owner = THIS_MODULE ,
. name = AXON_RAM_MODULE_NAME ,
} ,
2007-07-20 21:39:26 +02:00
} ;
/**
* axon_ram_init
*/
static int __init
axon_ram_init ( void )
{
2008-07-16 05:51:41 +10:00
azfs_major = register_blkdev ( azfs_major , AXON_RAM_DEVICE_NAME ) ;
if ( azfs_major < 0 ) {
printk ( KERN_ERR " %s cannot become block device major number \n " ,
AXON_RAM_MODULE_NAME ) ;
return - EFAULT ;
}
azfs_minor = 0 ;
2007-07-20 21:39:26 +02:00
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 ) ;
2008-07-16 05:51:41 +10:00
unregister_blkdev ( azfs_major , AXON_RAM_DEVICE_NAME ) ;
2007-07-20 21:39:26 +02:00
}
module_init ( axon_ram_init ) ;
module_exit ( axon_ram_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Maxim Shchetynin <maxim@de.ibm.com> " ) ;
MODULE_DESCRIPTION ( " Axon DDR2 RAM device driver for IBM Cell BE " ) ;