2011-07-21 01:56:53 +04:00
/*
* Register map access API - debugfs
*
* Copyright 2011 Wolfson Microelectronics plc
*
* Author : Mark Brown < broonie @ opensource . wolfsonmicro . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/slab.h>
# include <linux/mutex.h>
# include <linux/debugfs.h>
# include <linux/uaccess.h>
2012-01-22 20:23:42 +04:00
# include <linux/device.h>
2011-07-21 01:56:53 +04:00
# include "internal.h"
static struct dentry * regmap_debugfs_root ;
2011-08-10 12:15:31 +04:00
/* Calculate the length of a fixed format */
static size_t regmap_calc_reg_len ( int max_val , char * buf , size_t buf_size )
{
snprintf ( buf , buf_size , " %x " , max_val ) ;
return strlen ( buf ) ;
}
static int regmap_open_file ( struct inode * inode , struct file * file )
2011-07-21 01:56:53 +04:00
{
file - > private_data = inode - > i_private ;
return 0 ;
}
2012-02-22 18:20:09 +04:00
static ssize_t regmap_name_read_file ( struct file * file ,
char __user * user_buf , size_t count ,
loff_t * ppos )
{
struct regmap * map = file - > private_data ;
int ret ;
char * buf ;
buf = kmalloc ( PAGE_SIZE , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
ret = snprintf ( buf , PAGE_SIZE , " %s \n " , map - > dev - > driver - > name ) ;
if ( ret < 0 ) {
kfree ( buf ) ;
return ret ;
}
ret = simple_read_from_buffer ( user_buf , count , ppos , buf , ret ) ;
kfree ( buf ) ;
return ret ;
}
static const struct file_operations regmap_name_fops = {
. open = regmap_open_file ,
. read = regmap_name_read_file ,
. llseek = default_llseek ,
} ;
2011-07-21 01:56:53 +04:00
static ssize_t regmap_map_read_file ( struct file * file , char __user * user_buf ,
size_t count , loff_t * ppos )
{
2011-08-09 11:47:42 +04:00
int reg_len , val_len , tot_len ;
2011-07-21 01:56:53 +04:00
size_t buf_pos = 0 ;
loff_t p = 0 ;
ssize_t ret ;
int i ;
struct regmap * map = file - > private_data ;
char * buf ;
unsigned int val ;
if ( * ppos < 0 | | ! count )
return - EINVAL ;
buf = kmalloc ( count , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
/* Calculate the length of a fixed format */
2011-08-10 12:15:31 +04:00
reg_len = regmap_calc_reg_len ( map - > max_register , buf , count ) ;
2011-07-21 01:56:53 +04:00
val_len = 2 * map - > format . val_bytes ;
tot_len = reg_len + val_len + 3 ; /* : \n */
2011-09-05 19:13:07 +04:00
for ( i = 0 ; i < map - > max_register + 1 ; i + + ) {
2011-08-10 12:14:41 +04:00
if ( ! regmap_readable ( map , i ) )
2011-07-21 01:56:53 +04:00
continue ;
2011-08-10 12:14:41 +04:00
if ( regmap_precious ( map , i ) )
2011-08-08 10:41:46 +04:00
continue ;
2011-07-21 01:56:53 +04:00
/* If we're in the region the user is trying to read */
if ( p > = * ppos ) {
/* ...but not beyond it */
if ( buf_pos > = count - 1 - tot_len )
break ;
/* Format the register */
snprintf ( buf + buf_pos , count - buf_pos , " %.*x: " ,
reg_len , i ) ;
buf_pos + = reg_len + 2 ;
/* Format the value, write all X if we can't read */
ret = regmap_read ( map , i , & val ) ;
if ( ret = = 0 )
snprintf ( buf + buf_pos , count - buf_pos ,
" %.*x " , val_len , val ) ;
else
memset ( buf + buf_pos , ' X ' , val_len ) ;
buf_pos + = 2 * map - > format . val_bytes ;
buf [ buf_pos + + ] = ' \n ' ;
}
p + = tot_len ;
}
ret = buf_pos ;
if ( copy_to_user ( user_buf , buf , buf_pos ) ) {
ret = - EFAULT ;
goto out ;
}
* ppos + = buf_pos ;
out :
kfree ( buf ) ;
return ret ;
}
2012-02-22 16:43:50 +04:00
# undef REGMAP_ALLOW_WRITE_DEBUGFS
# ifdef REGMAP_ALLOW_WRITE_DEBUGFS
/*
* This can be dangerous especially when we have clients such as
* PMICs , therefore don ' t provide any real compile time configuration option
* for this feature , people who want to use this will need to modify
* the source code directly .
*/
static ssize_t regmap_map_write_file ( struct file * file ,
const char __user * user_buf ,
size_t count , loff_t * ppos )
{
char buf [ 32 ] ;
size_t buf_size ;
char * start = buf ;
unsigned long reg , value ;
struct regmap * map = file - > private_data ;
buf_size = min ( count , ( sizeof ( buf ) - 1 ) ) ;
if ( copy_from_user ( buf , user_buf , buf_size ) )
return - EFAULT ;
buf [ buf_size ] = 0 ;
while ( * start = = ' ' )
start + + ;
reg = simple_strtoul ( start , & start , 16 ) ;
while ( * start = = ' ' )
start + + ;
if ( strict_strtoul ( start , 16 , & value ) )
return - EINVAL ;
/* Userspace has been fiddling around behind the kernel's back */
add_taint ( TAINT_USER ) ;
regmap_write ( map , reg , value ) ;
return buf_size ;
}
# else
# define regmap_map_write_file NULL
# endif
2011-07-21 01:56:53 +04:00
static const struct file_operations regmap_map_fops = {
2011-08-10 12:15:31 +04:00
. open = regmap_open_file ,
2011-07-21 01:56:53 +04:00
. read = regmap_map_read_file ,
2012-02-22 16:43:50 +04:00
. write = regmap_map_write_file ,
2011-07-21 01:56:53 +04:00
. llseek = default_llseek ,
} ;
2011-08-10 12:28:04 +04:00
static ssize_t regmap_access_read_file ( struct file * file ,
char __user * user_buf , size_t count ,
loff_t * ppos )
{
int reg_len , tot_len ;
size_t buf_pos = 0 ;
loff_t p = 0 ;
ssize_t ret ;
int i ;
struct regmap * map = file - > private_data ;
char * buf ;
if ( * ppos < 0 | | ! count )
return - EINVAL ;
buf = kmalloc ( count , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
/* Calculate the length of a fixed format */
reg_len = regmap_calc_reg_len ( map - > max_register , buf , count ) ;
tot_len = reg_len + 10 ; /* ': R W V P\n' */
2011-09-05 19:13:07 +04:00
for ( i = 0 ; i < map - > max_register + 1 ; i + + ) {
2011-08-10 12:28:04 +04:00
/* Ignore registers which are neither readable nor writable */
if ( ! regmap_readable ( map , i ) & & ! regmap_writeable ( map , i ) )
continue ;
/* If we're in the region the user is trying to read */
if ( p > = * ppos ) {
/* ...but not beyond it */
if ( buf_pos > = count - 1 - tot_len )
break ;
/* Format the register */
snprintf ( buf + buf_pos , count - buf_pos ,
" %.*x: %c %c %c %c \n " ,
reg_len , i ,
regmap_readable ( map , i ) ? ' y ' : ' n ' ,
regmap_writeable ( map , i ) ? ' y ' : ' n ' ,
regmap_volatile ( map , i ) ? ' y ' : ' n ' ,
regmap_precious ( map , i ) ? ' y ' : ' n ' ) ;
buf_pos + = tot_len ;
}
p + = tot_len ;
}
ret = buf_pos ;
if ( copy_to_user ( user_buf , buf , buf_pos ) ) {
ret = - EFAULT ;
goto out ;
}
* ppos + = buf_pos ;
out :
kfree ( buf ) ;
return ret ;
}
static const struct file_operations regmap_access_fops = {
. open = regmap_open_file ,
. read = regmap_access_read_file ,
. llseek = default_llseek ,
} ;
2011-07-21 01:56:53 +04:00
void regmap_debugfs_init ( struct regmap * map )
{
map - > debugfs = debugfs_create_dir ( dev_name ( map - > dev ) ,
regmap_debugfs_root ) ;
if ( ! map - > debugfs ) {
dev_warn ( map - > dev , " Failed to create debugfs directory \n " ) ;
return ;
}
2012-02-22 18:20:09 +04:00
debugfs_create_file ( " name " , 0400 , map - > debugfs ,
map , & regmap_name_fops ) ;
2011-08-10 12:28:04 +04:00
if ( map - > max_register ) {
2011-07-21 01:56:53 +04:00
debugfs_create_file ( " registers " , 0400 , map - > debugfs ,
map , & regmap_map_fops ) ;
2011-08-10 12:28:04 +04:00
debugfs_create_file ( " access " , 0400 , map - > debugfs ,
map , & regmap_access_fops ) ;
}
2012-02-06 22:02:06 +04:00
if ( map - > cache_type ) {
debugfs_create_bool ( " cache_only " , 0400 , map - > debugfs ,
& map - > cache_only ) ;
debugfs_create_bool ( " cache_dirty " , 0400 , map - > debugfs ,
& map - > cache_dirty ) ;
debugfs_create_bool ( " cache_bypass " , 0400 , map - > debugfs ,
& map - > cache_bypass ) ;
}
2011-07-21 01:56:53 +04:00
}
void regmap_debugfs_exit ( struct regmap * map )
{
debugfs_remove_recursive ( map - > debugfs ) ;
}
void regmap_debugfs_initcall ( void )
{
regmap_debugfs_root = debugfs_create_dir ( " regmap " , NULL ) ;
if ( ! regmap_debugfs_root ) {
pr_warn ( " regmap: Failed to create debugfs root \n " ) ;
return ;
}
}