/* * Copyright (C) 2011 ST-Ericsson * License terms: GNU General Public License (GPL) version 2 * Debugfs support for the AB5500 MFD driver */ #include #include #include #include #include #include #include "ab5500-core.h" #include "ab5500-debugfs.h" static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { [AB5500_BANK_LED] = { .bankid = AB5500_BANK_LED, .nranges = 1, .range = (struct ab5500_reg_range[]) { { .first = 0x00, .last = 0x0C, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_ADC] = { .bankid = AB5500_BANK_ADC, .nranges = 6, .range = (struct ab5500_reg_range[]) { { .first = 0x1F, .last = 0x22, .perm = AB5500_PERM_RO, }, { .first = 0x23, .last = 0x24, .perm = AB5500_PERM_RW, }, { .first = 0x26, .last = 0x2D, .perm = AB5500_PERM_RO, }, { .first = 0x2F, .last = 0x34, .perm = AB5500_PERM_RW, }, { .first = 0x37, .last = 0x57, .perm = AB5500_PERM_RW, }, { .first = 0x58, .last = 0x58, .perm = AB5500_PERM_RO, }, }, }, [AB5500_BANK_RTC] = { .bankid = AB5500_BANK_RTC, .nranges = 2, .range = (struct ab5500_reg_range[]) { { .first = 0x00, .last = 0x04, .perm = AB5500_PERM_RW, }, { .first = 0x06, .last = 0x0C, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_STARTUP] = { .bankid = AB5500_BANK_STARTUP, .nranges = 12, .range = (struct ab5500_reg_range[]) { { .first = 0x00, .last = 0x01, .perm = AB5500_PERM_RW, }, { .first = 0x1F, .last = 0x1F, .perm = AB5500_PERM_RW, }, { .first = 0x2E, .last = 0x2E, .perm = AB5500_PERM_RO, }, { .first = 0x2F, .last = 0x30, .perm = AB5500_PERM_RW, }, { .first = 0x50, .last = 0x51, .perm = AB5500_PERM_RW, }, { .first = 0x60, .last = 0x61, .perm = AB5500_PERM_RW, }, { .first = 0x66, .last = 0x8A, .perm = AB5500_PERM_RW, }, { .first = 0x8C, .last = 0x96, .perm = AB5500_PERM_RW, }, { .first = 0xAA, .last = 0xB4, .perm = AB5500_PERM_RW, }, { .first = 0xB7, .last = 0xBF, .perm = AB5500_PERM_RW, }, { .first = 0xC1, .last = 0xCA, .perm = AB5500_PERM_RW, }, { .first = 0xD3, .last = 0xE0, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_DBI_ECI] = { .bankid = AB5500_BANK_DBI_ECI, .nranges = 3, .range = (struct ab5500_reg_range[]) { { .first = 0x00, .last = 0x07, .perm = AB5500_PERM_RW, }, { .first = 0x10, .last = 0x10, .perm = AB5500_PERM_RW, }, { .first = 0x13, .last = 0x13, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_CHG] = { .bankid = AB5500_BANK_CHG, .nranges = 2, .range = (struct ab5500_reg_range[]) { { .first = 0x11, .last = 0x11, .perm = AB5500_PERM_RO, }, { .first = 0x12, .last = 0x1B, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_FG_BATTCOM_ACC] = { .bankid = AB5500_BANK_FG_BATTCOM_ACC, .nranges = 2, .range = (struct ab5500_reg_range[]) { { .first = 0x00, .last = 0x0B, .perm = AB5500_PERM_RO, }, { .first = 0x0C, .last = 0x10, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_USB] = { .bankid = AB5500_BANK_USB, .nranges = 12, .range = (struct ab5500_reg_range[]) { { .first = 0x01, .last = 0x01, .perm = AB5500_PERM_RW, }, { .first = 0x80, .last = 0x83, .perm = AB5500_PERM_RW, }, { .first = 0x87, .last = 0x8A, .perm = AB5500_PERM_RW, }, { .first = 0x8B, .last = 0x8B, .perm = AB5500_PERM_RO, }, { .first = 0x91, .last = 0x92, .perm = AB5500_PERM_RO, }, { .first = 0x93, .last = 0x93, .perm = AB5500_PERM_RW, }, { .first = 0x94, .last = 0x94, .perm = AB5500_PERM_RO, }, { .first = 0xA8, .last = 0xB0, .perm = AB5500_PERM_RO, }, { .first = 0xB2, .last = 0xB2, .perm = AB5500_PERM_RO, }, { .first = 0xB4, .last = 0xBC, .perm = AB5500_PERM_RO, }, { .first = 0xBF, .last = 0xBF, .perm = AB5500_PERM_RO, }, { .first = 0xC1, .last = 0xC5, .perm = AB5500_PERM_RO, }, }, }, [AB5500_BANK_IT] = { .bankid = AB5500_BANK_IT, .nranges = 4, .range = (struct ab5500_reg_range[]) { { .first = 0x00, .last = 0x02, .perm = AB5500_PERM_RO, }, { .first = 0x20, .last = 0x36, .perm = AB5500_PERM_RO, }, { .first = 0x40, .last = 0x56, .perm = AB5500_PERM_RO, }, { .first = 0x60, .last = 0x76, .perm = AB5500_PERM_RO, }, }, }, [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { .bankid = AB5500_BANK_VDDDIG_IO_I2C_CLK_TST, .nranges = 7, .range = (struct ab5500_reg_range[]) { { .first = 0x02, .last = 0x02, .perm = AB5500_PERM_RW, }, { .first = 0x12, .last = 0x12, .perm = AB5500_PERM_RW, }, { .first = 0x30, .last = 0x34, .perm = AB5500_PERM_RW, }, { .first = 0x40, .last = 0x44, .perm = AB5500_PERM_RW, }, { .first = 0x50, .last = 0x54, .perm = AB5500_PERM_RW, }, { .first = 0x60, .last = 0x64, .perm = AB5500_PERM_RW, }, { .first = 0x70, .last = 0x74, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, .nranges = 13, .range = (struct ab5500_reg_range[]) { { .first = 0x01, .last = 0x01, .perm = AB5500_PERM_RW, }, { .first = 0x02, .last = 0x02, .perm = AB5500_PERM_RO, }, { .first = 0x0D, .last = 0x0F, .perm = AB5500_PERM_RW, }, { .first = 0x1C, .last = 0x1C, .perm = AB5500_PERM_RW, }, { .first = 0x1E, .last = 0x1E, .perm = AB5500_PERM_RW, }, { .first = 0x20, .last = 0x21, .perm = AB5500_PERM_RW, }, { .first = 0x25, .last = 0x25, .perm = AB5500_PERM_RW, }, { .first = 0x28, .last = 0x2A, .perm = AB5500_PERM_RW, }, { .first = 0x30, .last = 0x33, .perm = AB5500_PERM_RW, }, { .first = 0x40, .last = 0x43, .perm = AB5500_PERM_RW, }, { .first = 0x50, .last = 0x53, .perm = AB5500_PERM_RW, }, { .first = 0x60, .last = 0x63, .perm = AB5500_PERM_RW, }, { .first = 0x70, .last = 0x73, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_VIBRA] = { .bankid = AB5500_BANK_VIBRA, .nranges = 2, .range = (struct ab5500_reg_range[]) { { .first = 0x10, .last = 0x13, .perm = AB5500_PERM_RW, }, { .first = 0xFE, .last = 0xFE, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_AUDIO_HEADSETUSB] = { .bankid = AB5500_BANK_AUDIO_HEADSETUSB, .nranges = 2, .range = (struct ab5500_reg_range[]) { { .first = 0x00, .last = 0x48, .perm = AB5500_PERM_RW, }, { .first = 0xEB, .last = 0xFB, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_SIM_USBSIM] = { .bankid = AB5500_BANK_SIM_USBSIM, .nranges = 1, .range = (struct ab5500_reg_range[]) { { .first = 0x13, .last = 0x19, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_VDENC] = { .bankid = AB5500_BANK_VDENC, .nranges = 12, .range = (struct ab5500_reg_range[]) { { .first = 0x00, .last = 0x08, .perm = AB5500_PERM_RW, }, { .first = 0x09, .last = 0x09, .perm = AB5500_PERM_RO, }, { .first = 0x0A, .last = 0x12, .perm = AB5500_PERM_RW, }, { .first = 0x15, .last = 0x19, .perm = AB5500_PERM_RW, }, { .first = 0x1B, .last = 0x21, .perm = AB5500_PERM_RW, }, { .first = 0x27, .last = 0x2C, .perm = AB5500_PERM_RW, }, { .first = 0x41, .last = 0x41, .perm = AB5500_PERM_RW, }, { .first = 0x45, .last = 0x5B, .perm = AB5500_PERM_RW, }, { .first = 0x5D, .last = 0x5D, .perm = AB5500_PERM_RW, }, { .first = 0x69, .last = 0x69, .perm = AB5500_PERM_RW, }, { .first = 0x6C, .last = 0x6D, .perm = AB5500_PERM_RW, }, { .first = 0x80, .last = 0x81, .perm = AB5500_PERM_RW, }, }, }, }; static int ab5500_registers_print(struct seq_file *s, void *p) { struct ab5500 *ab = s->private; unsigned int i; u8 bank = (u8)ab->debug_bank; seq_printf(s, "ab5500 register values:\n"); for (bank = 0; bank < AB5500_NUM_BANKS; bank++) { seq_printf(s, " bank %u, %s (0x%x):\n", bank, bankinfo[bank].name, bankinfo[bank].slave_addr); for (i = 0; i < ab5500_reg_ranges[bank].nranges; i++) { u8 reg; int err; for (reg = ab5500_reg_ranges[bank].range[i].first; reg <= ab5500_reg_ranges[bank].range[i].last; reg++) { u8 value; err = ab5500_get_register_interruptible_raw(ab, bank, reg, &value); if (err < 0) { dev_err(ab->dev, "get_reg failed %d" "bank 0x%x reg 0x%x\n", err, bank, reg); return err; } err = seq_printf(s, "[%d/0x%02X]: 0x%02X\n", bank, reg, value); if (err < 0) { dev_err(ab->dev, "seq_printf overflow\n"); /* * Error is not returned here since * the output is wanted in any case */ return 0; } } } } return 0; } static int ab5500_registers_open(struct inode *inode, struct file *file) { return single_open(file, ab5500_registers_print, inode->i_private); } static const struct file_operations ab5500_registers_fops = { .open = ab5500_registers_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .owner = THIS_MODULE, }; static int ab5500_bank_print(struct seq_file *s, void *p) { struct ab5500 *ab = s->private; seq_printf(s, "%d\n", ab->debug_bank); return 0; } static int ab5500_bank_open(struct inode *inode, struct file *file) { return single_open(file, ab5500_bank_print, inode->i_private); } static ssize_t ab5500_bank_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; char buf[32]; int buf_size; unsigned long user_bank; int err; /* Get userspace string and assure termination */ buf_size = min(count, (sizeof(buf) - 1)); if (copy_from_user(buf, user_buf, buf_size)) return -EFAULT; buf[buf_size] = 0; err = strict_strtoul(buf, 0, &user_bank); if (err) return -EINVAL; if (user_bank >= AB5500_NUM_BANKS) { dev_err(ab->dev, "debugfs error input > number of banks\n"); return -EINVAL; } ab->debug_bank = user_bank; return buf_size; } static int ab5500_address_print(struct seq_file *s, void *p) { struct ab5500 *ab = s->private; seq_printf(s, "0x%02X\n", ab->debug_address); return 0; } static int ab5500_address_open(struct inode *inode, struct file *file) { return single_open(file, ab5500_address_print, inode->i_private); } static ssize_t ab5500_address_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; char buf[32]; int buf_size; unsigned long user_address; int err; /* Get userspace string and assure termination */ buf_size = min(count, (sizeof(buf) - 1)); if (copy_from_user(buf, user_buf, buf_size)) return -EFAULT; buf[buf_size] = 0; err = strict_strtoul(buf, 0, &user_address); if (err) return -EINVAL; if (user_address > 0xff) { dev_err(ab->dev, "debugfs error input > 0xff\n"); return -EINVAL; } ab->debug_address = user_address; return buf_size; } static int ab5500_val_print(struct seq_file *s, void *p) { struct ab5500 *ab = s->private; int err; u8 regvalue; err = ab5500_get_register_interruptible_raw(ab, (u8)ab->debug_bank, (u8)ab->debug_address, ®value); if (err) { dev_err(ab->dev, "get_reg failed %d, bank 0x%x" ", reg 0x%x\n", err, ab->debug_bank, ab->debug_address); return -EINVAL; } seq_printf(s, "0x%02X\n", regvalue); return 0; } static int ab5500_val_open(struct inode *inode, struct file *file) { return single_open(file, ab5500_val_print, inode->i_private); } static ssize_t ab5500_val_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; char buf[32]; int buf_size; unsigned long user_val; int err; u8 regvalue; /* Get userspace string and assure termination */ buf_size = min(count, (sizeof(buf)-1)); if (copy_from_user(buf, user_buf, buf_size)) return -EFAULT; buf[buf_size] = 0; err = strict_strtoul(buf, 0, &user_val); if (err) return -EINVAL; if (user_val > 0xff) { dev_err(ab->dev, "debugfs error input > 0xff\n"); return -EINVAL; } err = ab5500_mask_and_set_register_interruptible_raw( ab, (u8)ab->debug_bank, (u8)ab->debug_address, 0xFF, (u8)user_val); if (err) return -EINVAL; ab5500_get_register_interruptible_raw(ab, (u8)ab->debug_bank, (u8)ab->debug_address, ®value); if (err) return -EINVAL; return buf_size; } static const struct file_operations ab5500_bank_fops = { .open = ab5500_bank_open, .write = ab5500_bank_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, .owner = THIS_MODULE, }; static const struct file_operations ab5500_address_fops = { .open = ab5500_address_open, .write = ab5500_address_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, .owner = THIS_MODULE, }; static const struct file_operations ab5500_val_fops = { .open = ab5500_val_open, .write = ab5500_val_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, .owner = THIS_MODULE, }; static struct dentry *ab5500_dir; static struct dentry *ab5500_reg_file; static struct dentry *ab5500_bank_file; static struct dentry *ab5500_address_file; static struct dentry *ab5500_val_file; void __init ab5500_setup_debugfs(struct ab5500 *ab) { ab->debug_bank = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP; ab->debug_address = AB5500_CHIP_ID; ab5500_dir = debugfs_create_dir("ab5500", NULL); if (!ab5500_dir) goto exit_no_debugfs; ab5500_reg_file = debugfs_create_file("all-bank-registers", S_IRUGO, ab5500_dir, ab, &ab5500_registers_fops); if (!ab5500_reg_file) goto exit_destroy_dir; ab5500_bank_file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_bank_fops); if (!ab5500_bank_file) goto exit_destroy_reg; ab5500_address_file = debugfs_create_file("register-address", (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_address_fops); if (!ab5500_address_file) goto exit_destroy_bank; ab5500_val_file = debugfs_create_file("register-value", (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_val_fops); if (!ab5500_val_file) goto exit_destroy_address; return; exit_destroy_address: debugfs_remove(ab5500_address_file); exit_destroy_bank: debugfs_remove(ab5500_bank_file); exit_destroy_reg: debugfs_remove(ab5500_reg_file); exit_destroy_dir: debugfs_remove(ab5500_dir); exit_no_debugfs: dev_err(ab->dev, "failed to create debugfs entries.\n"); return; } void __exit ab5500_remove_debugfs(void) { debugfs_remove(ab5500_val_file); debugfs_remove(ab5500_address_file); debugfs_remove(ab5500_bank_file); debugfs_remove(ab5500_reg_file); debugfs_remove(ab5500_dir); }