blob: 85ef24c09c3b2200366f6b41c20855f8bbe4229c [file] [log] [blame]
/**
* linux/drivers/parrot/i2c/p7-i2cm-debugfs.c
*
* Copyright (C) 2011 Parrot S.A.
*
* @author Lionel Flandrin <lionel.flandrin@parrot.com>
* @date 26-Sep-2011
*/
#include <linux/module.h>
#include <linux/seq_file.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "p7-i2cm_debugfs.h"
#include "p7-i2cm_regs.h"
static struct dentry *p7i2cm_debugfs_root = NULL;
static atomic_t p7i2cm_debugfs_cnt = ATOMIC_INIT(0);
static int p7i2cm_display_freq(struct seq_file *s,
struct p7i2cm_debugfs *d,
u32 prescale)
{
seq_printf(s, "%luHz (prescale: 0x%04x)\n",
d->in_clk / (12 * (prescale + 1)),
prescale);
return 0;
}
static ssize_t p7i2cm_read_prescale(struct p7i2cm_debugfs *d,
const char __user *_buf,
size_t size,
loff_t *ppos)
{
unsigned long freq;
char *last;
char buf[16];
unsigned tmp;
if (*ppos > 0)
return -EOPNOTSUPP;
if (size > ARRAY_SIZE(buf) - 1)
return -ERANGE;
if (copy_from_user(buf, _buf, size))
return -EFAULT;
buf[size] = '\0';
freq = simple_strtoul(buf, &last, 0);
switch (*last) {
case 'M':
freq *= 1000;
case 'k':
freq *= 1000;
case '\0':
case '\n':
break;
default:
return -EINVAL;
}
/* Compute the value of prescale for the desired frequency */
tmp = 12 * freq;
if (tmp == 0)
return -ERANGE;
tmp = (d->in_clk + tmp - 1) / tmp - 1;
if (tmp > 0xffff)
return 0xffff;
return tmp;
}
static int p7i2cm_speed_show(struct seq_file *s, void *unused)
{
struct p7i2cm_debugfs *d = s->private;
return p7i2cm_display_freq(s, d, __raw_readl(d->base + I2CM_PRESCALE));
}
static ssize_t p7i2cm_speed_store(struct file *file,
const char __user *buf,
size_t size,
loff_t *ppos)
{
struct seq_file *m = file->private_data;
struct p7i2cm_debugfs *d = m->private;
ssize_t prescale;
prescale = p7i2cm_read_prescale(d, buf, size, ppos);
if (prescale < 0)
return prescale;
__raw_writel(prescale, d->base + I2CM_PRESCALE);
return size;
}
static int p7i2cm_speed_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, p7i2cm_speed_show, inode->i_private);
}
static const struct file_operations p7i2cm_speed_debug_ops = {
.open = p7i2cm_speed_debug_open,
.read = seq_read,
.write = p7i2cm_speed_store,
.llseek = seq_lseek,
.release = single_release,
};
static int p7i2cm_highspeed_show(struct seq_file *s, void *unused)
{
struct p7i2cm_debugfs *d = s->private;
return p7i2cm_display_freq(s, d,
__raw_readl(d->base + I2CM_HIGH_PRESCALE));
}
static ssize_t p7i2cm_highspeed_store(struct file *file,
const char __user *buf,
size_t size,
loff_t *ppos)
{
struct seq_file *m = file->private_data;
struct p7i2cm_debugfs *d = m->private;
ssize_t prescale;
prescale = p7i2cm_read_prescale(d, buf, size, ppos);
if (prescale < 0)
return prescale;
__raw_writel(prescale, d->base + I2CM_HIGH_PRESCALE);
return size;
}
static int p7i2cm_highspeed_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, p7i2cm_highspeed_show, inode->i_private);
}
static const struct file_operations p7i2cm_highspeed_debug_ops = {
.open = p7i2cm_highspeed_debug_open,
.read = seq_read,
.write = p7i2cm_highspeed_store,
.llseek = seq_lseek,
.release = single_release,
};
static int p7i2cm_dbglvl_show(struct seq_file *s, void *unused)
{
struct p7i2cm_debugfs *d = s->private;
seq_printf(s, "%d\n",d->dbglvl);
return 0;
}
static ssize_t p7i2cm_dbglvl_store(struct file *file,
const char __user *buf,
size_t size,
loff_t *ppos)
{
struct seq_file *m = file->private_data;
struct p7i2cm_debugfs *d = m->private;
unsigned long level;
char *last;
char _buf[16];
if (*ppos > 0)
return -EOPNOTSUPP;
if (size > ARRAY_SIZE(_buf) - 1)
return -ERANGE;
if (copy_from_user(_buf, buf, size))
return -EFAULT;
_buf[size] = '\0';
level = simple_strtoul(_buf, &last, 0);
d->dbglvl = (unsigned int) level;
return size;
}
static int p7i2cm_dbglvl_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, p7i2cm_dbglvl_show, inode->i_private);
}
static const struct file_operations p7i2cm_dbglvl_debug_ops = {
.open = p7i2cm_dbglvl_debug_open,
.read = seq_read,
.write = p7i2cm_dbglvl_store,
.llseek = seq_lseek,
.release = single_release,
};
#define VALIDATION_TESTS
#ifdef VALIDATION_TESTS
static int p7i2cm_test_clkstch_show(struct seq_file *s, void *unused)
{
seq_printf(s, "Write a value in this file to launch the clock stretching test\n");
return 0;
}
static ssize_t p7i2cm_test_clkstch_store(struct file *file,
const char __user *buf,
size_t size,
loff_t *ppos)
{
struct seq_file *s = file->private_data;
struct p7i2cm_debugfs *d = s->private;
/* Read the 4 ID registers from P7MU (8 bytes at @ 0x1A) */
__raw_writel(I2CM_WFIFO_START | I2CM_WFIFO_WR | (0x31 << 1),
d->base + I2CM_WFIFO);
__raw_writel(I2CM_WFIFO_WR | 0x0,
d->base + I2CM_WFIFO);
__raw_writel(I2CM_WFIFO_WR | 0x1A,
d->base + I2CM_WFIFO);
__raw_writel(I2CM_WFIFO_START | I2CM_WFIFO_WR | (0x31 << 1) | 1,
d->base + I2CM_WFIFO);
__raw_writel(I2CM_WFIFO_LAST_NACK | I2CM_WFIFO_RD | 8,
d->base + I2CM_WFIFO);
__raw_writel(I2CM_WFIFO_STOP, d->base + I2CM_WFIFO);
return size;
}
static int p7i2cm_test_clkstch_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, p7i2cm_test_clkstch_show, inode->i_private);
}
static const struct file_operations p7i2cm_test_clkstch_debug_ops = {
.open = p7i2cm_test_clkstch_debug_open,
.read = seq_read,
.write = p7i2cm_test_clkstch_store,
.llseek = seq_lseek,
.release = single_release,
};
#endif /* VALIDATION_TESTS */
int p7i2cm_debugfs_init(struct p7i2cm_debugfs *d,
void __iomem *base,
const char *name,
unsigned long in_clk)
{
struct dentry *file;
int cnt;
int ret = 0;
/* We have to create the root debugfs directory if we're the first one
* using it */
if (atomic_inc_return(&p7i2cm_debugfs_cnt) == 1)
p7i2cm_debugfs_root = debugfs_create_dir("i2c", NULL);
if (IS_ERR_OR_NULL(p7i2cm_debugfs_root)) {
ret = PTR_ERR(p7i2cm_debugfs_root) ?: -ENOMEM;
goto release;
}
d->base = base;
d->in_clk = in_clk;
d->dbglvl = 0;
d->dir = debugfs_create_dir(name, p7i2cm_debugfs_root);
if (IS_ERR_OR_NULL(d->dir)) {
ret = PTR_ERR(d->dir) ?: -ENOMEM;
goto release;
}
file = debugfs_create_file("speed", S_IRUGO, d->dir,
d, &p7i2cm_speed_debug_ops);
if (IS_ERR_OR_NULL(file)) {
ret = PTR_ERR(file) ?: -ENOMEM;
goto rm;
}
file = debugfs_create_file("highspeed", S_IRUGO, d->dir,
d, &p7i2cm_highspeed_debug_ops);
if (IS_ERR_OR_NULL(file)) {
ret = PTR_ERR(file) ?: -ENOMEM;
goto rm;
}
file = debugfs_create_file("dbglvl", S_IRUGO, d->dir,
d, &p7i2cm_dbglvl_debug_ops);
if (IS_ERR_OR_NULL(file)) {
ret = PTR_ERR(file) ?: -ENOMEM;
goto rm;
}
#ifdef VALIDATION_TESTS
file = debugfs_create_file("test1_clkstrch", S_IRUGO, d->dir,
d, &p7i2cm_test_clkstch_debug_ops);
if (IS_ERR_OR_NULL(file)) {
ret = PTR_ERR(file) ?: -ENOMEM;
goto rm;
}
#endif /* VALIDATION_TESTS */
printk(KERN_INFO "%s debugfs interface registered\n", name);
return 0;
rm:
debugfs_remove_recursive(d->dir);
release:
cnt = atomic_dec_return(&p7i2cm_debugfs_cnt);
if (cnt == 0 && !IS_ERR_OR_NULL(p7i2cm_debugfs_root))
/* nobody is using the rootdir anymore, destroy it */
debugfs_remove_recursive(p7i2cm_debugfs_root);
d->base = NULL;
return ret;
}
EXPORT_SYMBOL(p7i2cm_debugfs_init);
void p7i2cm_debugfs_remove(struct p7i2cm_debugfs *d)
{
int cnt;
if (d->base) {
debugfs_remove_recursive(d->dir);
cnt = atomic_dec_return(&p7i2cm_debugfs_cnt);
if (cnt == 0 && !IS_ERR_OR_NULL(p7i2cm_debugfs_root))
/* nobody is using the rootdir anymore, destroy it */
debugfs_remove_recursive(p7i2cm_debugfs_root);
}
}
EXPORT_SYMBOL(p7i2cm_debugfs_remove);
MODULE_DESCRIPTION("P7-I2CM debugfs module");
MODULE_AUTHOR("Lionel Flandrin <lionel.flandrin@parrot.com>");
MODULE_LICENSE("GPL");