#include #include #include #include #include #include #include #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)) 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)) return -EINVAL; if (debug->ops->set_conf_reg) debug->ops->set_conf_reg(debug, gpio, value); *ppos += ret; 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)) 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; 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);