| /* |
| * Copyright (C) 2011 ST-Ericsson |
| * License terms: GNU General Public License (GPL) version 2 |
| * Debugfs support for the AB5500 MFD driver |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/debugfs.h> |
| #include <linux/seq_file.h> |
| #include <linux/mfd/abx500.h> |
| #include <linux/mfd/abx500/ab5500.h> |
| #include <linux/uaccess.h> |
| |
| #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); |
| } |