blob: e1a84ace0c0e518b3d73d34bd7e2fdcfb209752e [file] [log] [blame]
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include<linux/slab.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/ctype.h>
#include "gpiodebug.h"
struct gpiodebug_data {
struct gpio_debug *debug;
int gpio;
unsigned int type;
};
enum {
REGISTER_FOPS = 0,
NORMAL_FOPS,
COUNT_FOPS,
};
static struct {
unsigned fops_type;
unsigned type;
char *available_name;
char *current_name;
} global_array[] = {
{REGISTER_FOPS, TYPE_CONF_REG, "conf_reg", "conf_reg"},
{NORMAL_FOPS, TYPE_PIN_VALUE, "available_value",
"current_value"},
{NORMAL_FOPS, TYPE_DIRECTION, "available_direction",
"current_direction"},
{NORMAL_FOPS, TYPE_IRQ_TYPE, "available_irqtype",
"current_irqtype"},
{NORMAL_FOPS, TYPE_PINMUX, "available_pinmux",
"current_pinmux"},
{NORMAL_FOPS, TYPE_PULLMODE, "available_pullmode",
"current_pullmode"},
{NORMAL_FOPS, TYPE_PULLSTRENGTH, "available_pullstrength",
"current_pullstrength"},
{NORMAL_FOPS, TYPE_OPEN_DRAIN, "available_opendrain",
"current_opendrain"},
{COUNT_FOPS, TYPE_IRQ_COUNT, "irq_count", "irq_count"},
{NORMAL_FOPS, TYPE_WAKEUP, "available_wakeup", "current_wakeup"},
{COUNT_FOPS, TYPE_WAKEUP_COUNT, "wakeup_count", "wakeup_count"},
{NORMAL_FOPS, TYPE_DEBOUNCE, "available_debounce",
"current_debounce"},
{NORMAL_FOPS, TYPE_OVERRIDE_OUTDIR, "available_override_outdir",
"current_override_outdir"},
{NORMAL_FOPS, TYPE_OVERRIDE_OUTVAL, "available_override_outval",
"current_override_outval"},
{NORMAL_FOPS, TYPE_OVERRIDE_INDIR, "available_override_indir",
"current_override_indir"},
{NORMAL_FOPS, TYPE_OVERRIDE_INVAL, "available_override_inval",
"current_override_inval"},
{NORMAL_FOPS, TYPE_SBY_OVR_IO, "available_standby_trigger",
"current_standby_trigger"},
{NORMAL_FOPS, TYPE_SBY_OVR_OUTVAL, "available_standby_outval",
"current_standby_outval"},
{NORMAL_FOPS, TYPE_SBY_OVR_INVAL, "available_standby_inval",
"current_standby_inval"},
{NORMAL_FOPS, TYPE_SBY_OVR_OUTDIR, "available_standby_outdir",
"current_standby_outdir"},
{NORMAL_FOPS, TYPE_SBY_OVR_INDIR, "available_standby_indir",
"current_standby_indir"},
{NORMAL_FOPS, TYPE_SBY_PUPD_STATE, "available_standby_pullmode",
"current_standby_pullmode"},
{NORMAL_FOPS, TYPE_SBY_OD_DIS, "available_standby_opendrain",
"current_standby_opendrain"},
{NORMAL_FOPS, TYPE_IRQ_LINE, "available_irq_line",
"current_irq_line"},
};
static struct dentry *gpio_root[ARCH_NR_GPIOS];
static struct gpiodebug_data global_data[ARCH_NR_GPIOS][TYPE_MAX];
static struct dentry *gpiodebug_debugfs_root;
struct gpio_control *find_gpio_control(struct gpio_control *control, int num,
unsigned type)
{
int i;
for (i = 0; i < num; i++) {
if ((control+i)->type == type)
break;
}
if (i < num)
return control+i;
return NULL;
}
int find_pininfo_num(struct gpio_control *control, const char *info)
{
int num = 0;
while (num < control->num) {
if (!strcmp(*(control->pininfo+num), info))
break;
num++;
}
if (num < control->num)
return num;
return -1;
}
static struct dentry *gpiodebug_create_file(const char *name,
umode_t mode, struct dentry *parent,
void *data, const struct file_operations *fops)
{
struct dentry *ret;
ret = debugfs_create_file(name, mode, parent, data, fops);
if (!ret)
pr_warn("Could not create debugfs '%s' entry\n", name);
return ret;
}
static int gpiodebug_open_file(struct inode *inode, struct file *filp)
{
filp->private_data = inode->i_private;
return 0;
}
static const char readme_msg[] =
"\n GPIO Debug Tool-HOWTO (Example):\n\n"
"# mount -t debugfs nodev /sys/kernel/debug\n\n"
"# cat /sys/kernel/debug/gpio_debug/gpio0/available_pullmode\n"
"nopull pullup pulldown\n\n"
"# cat /sys/kernel/debug/gpio_debug/gpio0/current_pullmode\n"
"nopull\n"
"# echo pullup > /sys/kernel/debug/gpio_debug/gpio0/current_pullmode\n"
"# cat /sys/kernel/debug/gpio_debug/gpio0/current_pullmode\n"
"pullup\n\n"
"# cat conf_reg\n"
"0x00003120\n"
"# echo 0x00003121 > conf_reg\n"
"0x00003121\n\n"
"# cat irq_count\n"
"1\n";
/* gpio_readme_fops */
static ssize_t show_gpio_readme(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
ssize_t ret = 0;
if (*ppos < 0 || !cnt)
return -EINVAL;
ret = simple_read_from_buffer(ubuf, cnt, ppos, readme_msg,
strlen(readme_msg));
return ret;
}
static const struct file_operations gpio_readme_fops = {
.open = gpiodebug_open_file,
.read = show_gpio_readme,
.llseek = generic_file_llseek,
};
/* gpio_reginfo_fops */
static ssize_t show_gpio_reginfo(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
ssize_t ret = 0;
struct gpio_debug *debug = filp->private_data;
unsigned long size;
char *buf;
if (*ppos < 0 || !cnt)
return -EINVAL;
if (debug->ops->get_register_msg) {
debug->ops->get_register_msg(&buf, &size);
ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, size);
}
return ret;
}
static const struct file_operations gpio_reginfo_fops = {
.open = gpiodebug_open_file,
.read = show_gpio_reginfo,
.llseek = generic_file_llseek,
};
/* gpio_conf_fops */
static ssize_t gpio_conf_read(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
ssize_t ret = 0;
struct gpiodebug_data *data = filp->private_data;
struct gpio_debug *debug = data->debug;
int gpio = data->gpio;
char *buf;
unsigned int value = 0;
if (*ppos < 0 || !cnt)
return -EINVAL;
buf = kzalloc(cnt, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (debug->ops->get_conf_reg)
value = debug->ops->get_conf_reg(debug, gpio);
if (value == -EINVAL)
ret = sprintf(buf, "Invalid pin\n");
else
ret = sprintf(buf, "0x%08x\n", value);
ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, ret);
kfree(buf);
return ret;
}
static ssize_t gpio_conf_write(struct file *filp, const char __user *ubuf,
size_t cnt, loff_t *ppos)
{
ssize_t ret = 0;
struct gpiodebug_data *data = filp->private_data;
struct gpio_debug *debug = data->debug;
int i, gpio = data->gpio;
char *buf, *start;
unsigned int value;
ret = cnt;
if (*ppos < 0 || !cnt)
return -EINVAL;
buf = kzalloc(cnt, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, ubuf, cnt)) {
kfree(buf);
return -EFAULT;
}
start = buf;
while (*start == ' ')
start++;
/* strip ending whitespace. */
for (i = cnt - 1; i > 0 && isspace(buf[i]); i--)
buf[i] = 0;
if (kstrtouint(start, 16, &value)) {
kfree(buf);
return -EINVAL;
}
if (debug->ops->set_conf_reg)
debug->ops->set_conf_reg(debug, gpio, value);
*ppos += ret;
kfree(buf);
return ret;
}
static const struct file_operations gpio_conf_fops = {
.open = gpiodebug_open_file,
.read = gpio_conf_read,
.write = gpio_conf_write,
.llseek = generic_file_llseek,
};
/* show_gpiodebug_fops */
static ssize_t gpiodebug_show_read(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
ssize_t ret = 0;
struct gpiodebug_data *data = filp->private_data;
struct gpio_debug *debug = data->debug;
unsigned int type = data->type;
int i, num = 0;
int gpio = data->gpio;
char *buf, **avl_buf = NULL;
if (*ppos < 0 || !cnt)
return -EINVAL;
buf = kzalloc(cnt, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* debug->ops->get_avl_info */
if (debug->ops->get_avl_pininfo) {
avl_buf = debug->ops->get_avl_pininfo(debug, gpio, type, &num);
for (i = 0; i < num; i++)
sprintf(buf, "%s%s\t", buf, *(avl_buf+i));
}
ret = sprintf(buf, "%s\n", buf);
ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, ret);
kfree(buf);
return ret;
}
static const struct file_operations show_gpiodebug_fops = {
.open = gpiodebug_open_file,
.read = gpiodebug_show_read,
.llseek = generic_file_llseek,
};
/* set_gpiodebug_fops */
static ssize_t gpiodebug_set_gpio_read(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
ssize_t ret = 0;
struct gpiodebug_data *data = filp->private_data;
struct gpio_debug *debug = data->debug;
unsigned int type = data->type;
int gpio = data->gpio;
char *buf, *cur_info = NULL;
if (*ppos < 0 || !cnt)
return -EINVAL;
buf = kzalloc(cnt, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (debug->ops->get_cul_pininfo)
cur_info = debug->ops->get_cul_pininfo(debug, gpio, type);
if (cur_info)
ret = sprintf(buf, "%s\n", cur_info);
else
ret = sprintf(buf, "\n");
ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, ret);
kfree(buf);
return ret;
}
static ssize_t gpiodebug_set_gpio_write(struct file *filp,
const char __user *ubuf, size_t cnt, loff_t *ppos)
{
ssize_t ret = 0;
struct gpiodebug_data *data = filp->private_data;
struct gpio_debug *debug = data->debug;
unsigned int type = data->type;
int i, gpio = data->gpio;
char *buf;
ret = cnt;
if (*ppos < 0 || !cnt)
return -EINVAL;
buf = kzalloc(cnt, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, ubuf, cnt)) {
kfree(buf);
return -EFAULT;
}
/* strip ending whitespace. */
for (i = cnt - 1; i > 0 && isspace(buf[i]); i--)
buf[i] = 0;
if (debug->ops->set_pininfo)
debug->ops->set_pininfo(debug, gpio, type, buf);
*ppos += ret;
kfree(buf);
return ret;
}
static const struct file_operations set_gpiodebug_fops = {
.open = gpiodebug_open_file,
.read = gpiodebug_set_gpio_read,
.write = gpiodebug_set_gpio_write,
.llseek = generic_file_llseek,
};
/* show_count_fops */
static ssize_t show_count_read(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
{
ssize_t ret = 0;
struct gpiodebug_data *data = filp->private_data;
struct gpio_debug *debug = data->debug;
unsigned int type = data->type;
unsigned long count = 0;
int gpio = data->gpio;
char *buf;
if (*ppos < 0 || !cnt)
return -EINVAL;
buf = kzalloc(cnt, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (type == TYPE_IRQ_COUNT)
count = debug->irq_count[gpio];
else if (type == TYPE_WAKEUP_COUNT)
count = debug->wakeup_count[gpio];
ret = sprintf(buf, "%ld\n", count);
ret = simple_read_from_buffer(ubuf, cnt, ppos, buf, ret);
kfree(buf);
return ret;
}
static const struct file_operations show_count_fops = {
.open = gpiodebug_open_file,
.read = show_count_read,
.llseek = generic_file_llseek,
};
/******************************************************************************/
struct gpio_debug *gpio_debug_alloc(void)
{
struct gpio_debug *debug;
debug = kzalloc(sizeof(struct gpio_debug), GFP_KERNEL);
if (debug) {
__set_bit(TYPE_CONF_REG, debug->typebit);
__set_bit(TYPE_PIN_VALUE, debug->typebit);
__set_bit(TYPE_DIRECTION, debug->typebit);
__set_bit(TYPE_IRQ_TYPE, debug->typebit);
__set_bit(TYPE_PINMUX, debug->typebit);
__set_bit(TYPE_PULLMODE, debug->typebit);
__set_bit(TYPE_PULLSTRENGTH, debug->typebit);
__set_bit(TYPE_OPEN_DRAIN, debug->typebit);
__set_bit(TYPE_IRQ_COUNT, debug->typebit);
__set_bit(TYPE_DEBOUNCE, debug->typebit);
}
return debug;
}
void gpio_debug_remove(struct gpio_debug *debug)
{
struct gpio_chip *chip = debug->chip;
int base = chip->base;
unsigned ngpio = chip->ngpio;
int i;
for (i = base; i < base+ngpio; i++)
debugfs_remove_recursive(gpio_root[i]);
kfree(debug);
}
int gpio_debug_register(struct gpio_debug *debug)
{
struct gpio_chip *chip = debug->chip;
int base = chip->base;
unsigned ngpio = chip->ngpio;
int i, j;
char gpioname[32];
for (i = base; i < base+ngpio; i++) {
sprintf(gpioname, "gpio%d", i);
gpio_root[i] = debugfs_create_dir(gpioname,
gpiodebug_debugfs_root);
if (!gpio_root[i]) {
pr_warn("gpiodebug: Failed to create debugfs directory\n");
return -ENOMEM;
}
/* register info */
gpiodebug_create_file("register_info", 0400, gpio_root[i],
debug, &gpio_reginfo_fops);
for (j = 0; j < ARRAY_SIZE(global_array); j++) {
if (test_bit(global_array[j].type, debug->typebit)) {
global_data[i][j].gpio = i;
global_data[i][j].debug = debug;
global_data[i][j].type = global_array[j].type;
switch (global_array[j].fops_type) {
case REGISTER_FOPS:
gpiodebug_create_file(
global_array[j].current_name, 0600,
gpio_root[i], &global_data[i][j],
&gpio_conf_fops);
break;
case NORMAL_FOPS:
gpiodebug_create_file(
global_array[j].available_name, 0400,
gpio_root[i], &global_data[i][j],
&show_gpiodebug_fops);
gpiodebug_create_file(
global_array[j].current_name, 0600,
gpio_root[i], &global_data[i][j],
&set_gpiodebug_fops);
break;
case COUNT_FOPS:
gpiodebug_create_file(
global_array[j].current_name, 0400,
gpio_root[i], &global_data[i][j],
&show_count_fops);
break;
default:
break;
}
}
}
}
return 0;
}
static int __init gpio_debug_init(void)
{
gpiodebug_debugfs_root = debugfs_create_dir("gpio_debug", NULL);
if (IS_ERR(gpiodebug_debugfs_root) || !gpiodebug_debugfs_root) {
pr_warn("gpiodebug: Failed to create debugfs directory\n");
gpiodebug_debugfs_root = NULL;
}
/* readme */
gpiodebug_create_file("readme", 0400, gpiodebug_debugfs_root,
NULL, &gpio_readme_fops);
return 0;
}
subsys_initcall(gpio_debug_init);