| /* Copyright (c) 2016, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #define pr_fmt(fmt) "FG: %s: " fmt, __func__ |
| |
| #include "fg-core.h" |
| |
| static struct fg_dbgfs dbgfs_data = { |
| .help_msg = { |
| .data = |
| "FG Debug-FS support\n" |
| "\n" |
| "Hierarchy schema:\n" |
| "/sys/kernel/debug/fg_sram\n" |
| " /help -- Static help text\n" |
| " /address -- Starting register address for reads or writes\n" |
| " /count -- Number of registers to read (only used for reads)\n" |
| " /data -- Initiates the SRAM read (formatted output)\n" |
| "\n", |
| }, |
| }; |
| |
| #define EXPONENT_SHIFT 11 |
| #define EXPONENT_OFFSET -9 |
| #define MANTISSA_SIGN_BIT 10 |
| #define MICRO_UNIT 1000000 |
| s64 fg_float_decode(u16 val) |
| { |
| s8 exponent; |
| s32 mantissa; |
| |
| /* mantissa bits are shifted out during sign extension */ |
| exponent = ((s16)val >> EXPONENT_SHIFT) + EXPONENT_OFFSET; |
| /* exponent bits are shifted out during sign extension */ |
| mantissa = sign_extend32(val, MANTISSA_SIGN_BIT) * MICRO_UNIT; |
| |
| if (exponent < 0) |
| return (s64)mantissa >> -exponent; |
| |
| return (s64)mantissa << exponent; |
| } |
| |
| void fill_string(char *str, size_t str_len, u8 *buf, int buf_len) |
| { |
| int pos = 0; |
| int i; |
| |
| for (i = 0; i < buf_len; i++) { |
| pos += scnprintf(str + pos, str_len - pos, "%02x", buf[i]); |
| if (i < buf_len - 1) |
| pos += scnprintf(str + pos, str_len - pos, " "); |
| } |
| } |
| |
| static inline bool fg_sram_address_valid(u16 address, int len) |
| { |
| if (address > FG_SRAM_ADDRESS_MAX) |
| return false; |
| |
| if ((address + DIV_ROUND_UP(len, 4)) > FG_SRAM_ADDRESS_MAX + 1) |
| return false; |
| |
| return true; |
| } |
| |
| #define SOC_UPDATE_WAIT_MS 1500 |
| int fg_sram_write(struct fg_chip *chip, u16 address, u8 offset, |
| u8 *val, int len, int flags) |
| { |
| int rc = 0; |
| bool tried_again = false; |
| bool atomic_access = false; |
| |
| if (!chip) |
| return -ENXIO; |
| |
| if (!fg_sram_address_valid(address, len)) |
| return -EFAULT; |
| |
| if (!(flags & FG_IMA_NO_WLOCK)) |
| vote(chip->awake_votable, SRAM_WRITE, true, 0); |
| mutex_lock(&chip->sram_rw_lock); |
| |
| if ((flags & FG_IMA_ATOMIC) && chip->irqs[SOC_UPDATE_IRQ].irq) { |
| /* |
| * This interrupt need to be enabled only when it is |
| * required. It will be kept disabled other times. |
| */ |
| enable_irq(chip->irqs[SOC_UPDATE_IRQ].irq); |
| atomic_access = true; |
| } else { |
| flags = FG_IMA_DEFAULT; |
| } |
| wait: |
| /* |
| * Atomic access mean waiting upon SOC_UPDATE interrupt from |
| * FG_ALG and do the transaction after that. This is to make |
| * sure that there will be no SOC update happening when an |
| * IMA write is happening. SOC_UPDATE interrupt fires every |
| * FG cycle (~1.47 seconds). |
| */ |
| if (atomic_access) { |
| /* Wait for SOC_UPDATE completion */ |
| rc = wait_for_completion_interruptible_timeout( |
| &chip->soc_update, |
| msecs_to_jiffies(SOC_UPDATE_WAIT_MS)); |
| |
| /* If we were interrupted wait again one more time. */ |
| if (rc == -ERESTARTSYS && !tried_again) { |
| tried_again = true; |
| goto wait; |
| } else if (rc <= 0) { |
| pr_err("wait for soc_update timed out rc=%d\n", rc); |
| goto out; |
| } |
| } |
| |
| rc = fg_interleaved_mem_write(chip, address, offset, val, len, |
| atomic_access); |
| if (rc < 0) |
| pr_err("Error in writing SRAM address 0x%x[%d], rc=%d\n", |
| address, offset, rc); |
| out: |
| if (atomic_access) |
| disable_irq_nosync(chip->irqs[SOC_UPDATE_IRQ].irq); |
| |
| mutex_unlock(&chip->sram_rw_lock); |
| if (!(flags & FG_IMA_NO_WLOCK)) |
| vote(chip->awake_votable, SRAM_WRITE, false, 0); |
| return rc; |
| } |
| |
| int fg_sram_read(struct fg_chip *chip, u16 address, u8 offset, |
| u8 *val, int len, int flags) |
| { |
| int rc = 0; |
| |
| if (!chip) |
| return -ENXIO; |
| |
| if (!fg_sram_address_valid(address, len)) |
| return -EFAULT; |
| |
| if (!(flags & FG_IMA_NO_WLOCK)) |
| vote(chip->awake_votable, SRAM_READ, true, 0); |
| mutex_lock(&chip->sram_rw_lock); |
| |
| rc = fg_interleaved_mem_read(chip, address, offset, val, len); |
| if (rc < 0) |
| pr_err("Error in reading SRAM address 0x%x[%d], rc=%d\n", |
| address, offset, rc); |
| |
| mutex_unlock(&chip->sram_rw_lock); |
| if (!(flags & FG_IMA_NO_WLOCK)) |
| vote(chip->awake_votable, SRAM_READ, false, 0); |
| return rc; |
| } |
| |
| int fg_sram_masked_write(struct fg_chip *chip, u16 address, u8 offset, |
| u8 mask, u8 val, int flags) |
| { |
| int rc = 0; |
| u8 buf[4]; |
| |
| rc = fg_sram_read(chip, address, 0, buf, 4, flags); |
| if (rc < 0) { |
| pr_err("sram read failed: address=%03X, rc=%d\n", address, rc); |
| return rc; |
| } |
| |
| buf[offset] &= ~mask; |
| buf[offset] |= val & mask; |
| |
| rc = fg_sram_write(chip, address, 0, buf, 4, flags); |
| if (rc < 0) { |
| pr_err("sram write failed: address=%03X, rc=%d\n", address, rc); |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| int fg_read(struct fg_chip *chip, int addr, u8 *val, int len) |
| { |
| int rc, i; |
| |
| if (!chip || !chip->regmap) |
| return -ENXIO; |
| |
| rc = regmap_bulk_read(chip->regmap, addr, val, len); |
| |
| if (rc < 0) { |
| dev_err(chip->dev, "regmap_read failed for address %04x rc=%d\n", |
| addr, rc); |
| return rc; |
| } |
| |
| if (*chip->debug_mask & FG_BUS_READ) { |
| pr_info("length %d addr=%04x\n", len, addr); |
| for (i = 0; i < len; i++) |
| pr_info("val[%d]: %02x\n", i, val[i]); |
| } |
| |
| return 0; |
| } |
| |
| int fg_write(struct fg_chip *chip, int addr, u8 *val, int len) |
| { |
| int rc, i; |
| bool sec_access = false; |
| |
| if (!chip || !chip->regmap) |
| return -ENXIO; |
| |
| mutex_lock(&chip->bus_lock); |
| sec_access = (addr & 0xFF00) > 0xD0; |
| if (sec_access) { |
| rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5); |
| if (rc < 0) { |
| dev_err(chip->dev, "regmap_write failed for address %x rc=%d\n", |
| addr, rc); |
| goto out; |
| } |
| } |
| |
| if (len > 1) |
| rc = regmap_bulk_write(chip->regmap, addr, val, len); |
| else |
| rc = regmap_write(chip->regmap, addr, *val); |
| |
| if (rc < 0) { |
| dev_err(chip->dev, "regmap_write failed for address %04x rc=%d\n", |
| addr, rc); |
| goto out; |
| } |
| |
| if (*chip->debug_mask & FG_BUS_WRITE) { |
| pr_info("length %d addr=%04x\n", len, addr); |
| for (i = 0; i < len; i++) |
| pr_info("val[%d]: %02x\n", i, val[i]); |
| } |
| out: |
| mutex_unlock(&chip->bus_lock); |
| return rc; |
| } |
| |
| int fg_masked_write(struct fg_chip *chip, int addr, u8 mask, u8 val) |
| { |
| int rc; |
| bool sec_access = false; |
| |
| if (!chip || !chip->regmap) |
| return -ENXIO; |
| |
| mutex_lock(&chip->bus_lock); |
| sec_access = (addr & 0xFF00) > 0xD0; |
| if (sec_access) { |
| rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5); |
| if (rc < 0) { |
| dev_err(chip->dev, "regmap_write failed for address %x rc=%d\n", |
| addr, rc); |
| goto out; |
| } |
| } |
| |
| rc = regmap_update_bits(chip->regmap, addr, mask, val); |
| if (rc < 0) { |
| dev_err(chip->dev, "regmap_update_bits failed for address %04x rc=%d\n", |
| addr, rc); |
| goto out; |
| } |
| |
| fg_dbg(chip, FG_BUS_WRITE, "addr=%04x mask: %02x val: %02x\n", addr, |
| mask, val); |
| out: |
| mutex_unlock(&chip->bus_lock); |
| return rc; |
| } |
| |
| int64_t twos_compliment_extend(int64_t val, int sign_bit_pos) |
| { |
| int i, nbytes = DIV_ROUND_UP(sign_bit_pos, 8); |
| int64_t mask, val_out; |
| |
| val_out = val; |
| mask = 1 << sign_bit_pos; |
| if (val & mask) { |
| for (i = 8; i > nbytes; i--) { |
| mask = 0xFFLL << ((i - 1) * 8); |
| val_out |= mask; |
| } |
| |
| if ((nbytes * 8) - 1 > sign_bit_pos) { |
| mask = 1 << sign_bit_pos; |
| for (i = 1; i <= (nbytes * 8) - sign_bit_pos; i++) |
| val_out |= mask << i; |
| } |
| } |
| |
| pr_debug("nbytes: %d val: %llx val_out: %llx\n", nbytes, val, val_out); |
| return val_out; |
| } |
| |
| /* All the debugfs related functions are defined below */ |
| static int fg_sram_dfs_open(struct inode *inode, struct file *file) |
| { |
| struct fg_log_buffer *log; |
| struct fg_trans *trans; |
| u8 *data_buf; |
| |
| size_t logbufsize = SZ_4K; |
| size_t databufsize = SZ_4K; |
| |
| if (!dbgfs_data.chip) { |
| pr_err("Not initialized data\n"); |
| return -EINVAL; |
| } |
| |
| /* Per file "transaction" data */ |
| trans = devm_kzalloc(dbgfs_data.chip->dev, sizeof(*trans), GFP_KERNEL); |
| if (!trans) |
| return -ENOMEM; |
| |
| /* Allocate log buffer */ |
| log = devm_kzalloc(dbgfs_data.chip->dev, logbufsize, GFP_KERNEL); |
| if (!log) |
| return -ENOMEM; |
| |
| log->rpos = 0; |
| log->wpos = 0; |
| log->len = logbufsize - sizeof(*log); |
| |
| /* Allocate data buffer */ |
| data_buf = devm_kzalloc(dbgfs_data.chip->dev, databufsize, GFP_KERNEL); |
| if (!data_buf) |
| return -ENOMEM; |
| |
| trans->log = log; |
| trans->data = data_buf; |
| trans->cnt = dbgfs_data.cnt; |
| trans->addr = dbgfs_data.addr; |
| trans->chip = dbgfs_data.chip; |
| trans->offset = trans->addr; |
| |
| file->private_data = trans; |
| return 0; |
| } |
| |
| static int fg_sram_dfs_close(struct inode *inode, struct file *file) |
| { |
| struct fg_trans *trans = file->private_data; |
| |
| if (trans && trans->log && trans->data) { |
| file->private_data = NULL; |
| devm_kfree(trans->chip->dev, trans->log); |
| devm_kfree(trans->chip->dev, trans->data); |
| devm_kfree(trans->chip->dev, trans); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * print_to_log: format a string and place into the log buffer |
| * @log: The log buffer to place the result into. |
| * @fmt: The format string to use. |
| * @...: The arguments for the format string. |
| * |
| * The return value is the number of characters written to @log buffer |
| * not including the trailing '\0'. |
| */ |
| static int print_to_log(struct fg_log_buffer *log, const char *fmt, ...) |
| { |
| va_list args; |
| int cnt; |
| char *buf = &log->data[log->wpos]; |
| size_t size = log->len - log->wpos; |
| |
| va_start(args, fmt); |
| cnt = vscnprintf(buf, size, fmt, args); |
| va_end(args); |
| |
| log->wpos += cnt; |
| return cnt; |
| } |
| |
| /** |
| * write_next_line_to_log: Writes a single "line" of data into the log buffer |
| * @trans: Pointer to SRAM transaction data. |
| * @offset: SRAM address offset to start reading from. |
| * @pcnt: Pointer to 'cnt' variable. Indicates the number of bytes to read. |
| * |
| * The 'offset' is a 12-bit SRAM address. |
| * |
| * On a successful read, the pcnt is decremented by the number of data |
| * bytes read from the SRAM. When the cnt reaches 0, all requested bytes have |
| * been read. |
| */ |
| static int write_next_line_to_log(struct fg_trans *trans, int offset, |
| size_t *pcnt) |
| { |
| int i; |
| u8 data[ITEMS_PER_LINE]; |
| u16 address; |
| struct fg_log_buffer *log = trans->log; |
| int cnt = 0; |
| int items_to_read = min(ARRAY_SIZE(data), *pcnt); |
| int items_to_log = min(ITEMS_PER_LINE, items_to_read); |
| |
| /* Buffer needs enough space for an entire line */ |
| if ((log->len - log->wpos) < MAX_LINE_LENGTH) |
| goto done; |
| |
| memcpy(data, trans->data + (offset - trans->addr), items_to_read); |
| *pcnt -= items_to_read; |
| |
| /* address is in word now and it increments by 1. */ |
| address = trans->addr + ((offset - trans->addr) / ITEMS_PER_LINE); |
| cnt = print_to_log(log, "%3.3d ", address & 0xfff); |
| if (cnt == 0) |
| goto done; |
| |
| /* Log the data items */ |
| for (i = 0; i < items_to_log; ++i) { |
| cnt = print_to_log(log, "%2.2X ", data[i]); |
| if (cnt == 0) |
| goto done; |
| } |
| |
| /* If the last character was a space, then replace it with a newline */ |
| if (log->wpos > 0 && log->data[log->wpos - 1] == ' ') |
| log->data[log->wpos - 1] = '\n'; |
| |
| done: |
| return cnt; |
| } |
| |
| /** |
| * get_log_data - reads data from SRAM and saves to the log buffer |
| * @trans: Pointer to SRAM transaction data. |
| * |
| * Returns the number of "items" read or SPMI error code for read failures. |
| */ |
| static int get_log_data(struct fg_trans *trans) |
| { |
| int cnt, rc; |
| int last_cnt; |
| int items_read; |
| int total_items_read = 0; |
| u32 offset = trans->offset; |
| size_t item_cnt = trans->cnt; |
| struct fg_log_buffer *log = trans->log; |
| |
| if (item_cnt == 0) |
| return 0; |
| |
| if (item_cnt > SZ_4K) { |
| pr_err("Reading too many bytes\n"); |
| return -EINVAL; |
| } |
| |
| pr_debug("addr: %d offset: %d count: %d\n", trans->addr, trans->offset, |
| trans->cnt); |
| rc = fg_sram_read(trans->chip, trans->addr, 0, |
| trans->data, trans->cnt, 0); |
| if (rc < 0) { |
| pr_err("SRAM read failed: rc = %d\n", rc); |
| return rc; |
| } |
| /* Reset the log buffer 'pointers' */ |
| log->wpos = log->rpos = 0; |
| |
| /* Keep reading data until the log is full */ |
| do { |
| last_cnt = item_cnt; |
| cnt = write_next_line_to_log(trans, offset, &item_cnt); |
| items_read = last_cnt - item_cnt; |
| offset += items_read; |
| total_items_read += items_read; |
| } while (cnt && item_cnt > 0); |
| |
| /* Adjust the transaction offset and count */ |
| trans->cnt = item_cnt; |
| trans->offset += total_items_read; |
| |
| return total_items_read; |
| } |
| |
| /** |
| * fg_sram_dfs_reg_read: reads value(s) from SRAM and fills user's buffer a |
| * byte array (coded as string) |
| * @file: file pointer |
| * @buf: where to put the result |
| * @count: maximum space available in @buf |
| * @ppos: starting position |
| * @return number of user bytes read, or negative error value |
| */ |
| static ssize_t fg_sram_dfs_reg_read(struct file *file, char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct fg_trans *trans = file->private_data; |
| struct fg_log_buffer *log = trans->log; |
| size_t ret; |
| size_t len; |
| |
| /* Is the the log buffer empty */ |
| if (log->rpos >= log->wpos) { |
| if (get_log_data(trans) <= 0) |
| return 0; |
| } |
| |
| len = min(count, log->wpos - log->rpos); |
| |
| ret = copy_to_user(buf, &log->data[log->rpos], len); |
| if (ret == len) { |
| pr_err("error copy sram register values to user\n"); |
| return -EFAULT; |
| } |
| |
| /* 'ret' is the number of bytes not copied */ |
| len -= ret; |
| |
| *ppos += len; |
| log->rpos += len; |
| return len; |
| } |
| |
| /** |
| * fg_sram_dfs_reg_write: write user's byte array (coded as string) to SRAM. |
| * @file: file pointer |
| * @buf: user data to be written. |
| * @count: maximum space available in @buf |
| * @ppos: starting position |
| * @return number of user byte written, or negative error value |
| */ |
| static ssize_t fg_sram_dfs_reg_write(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| int bytes_read; |
| int data; |
| int pos = 0; |
| int cnt = 0; |
| u8 *values; |
| char *kbuf; |
| size_t ret = 0; |
| struct fg_trans *trans = file->private_data; |
| u32 address = trans->addr; |
| |
| /* Make a copy of the user data */ |
| kbuf = kmalloc(count + 1, GFP_KERNEL); |
| if (!kbuf) |
| return -ENOMEM; |
| |
| ret = copy_from_user(kbuf, buf, count); |
| if (ret == count) { |
| pr_err("failed to copy data from user\n"); |
| ret = -EFAULT; |
| goto free_buf; |
| } |
| |
| count -= ret; |
| *ppos += count; |
| kbuf[count] = '\0'; |
| |
| /* Override the text buffer with the raw data */ |
| values = kbuf; |
| |
| /* Parse the data in the buffer. It should be a string of numbers */ |
| while ((pos < count) && |
| sscanf(kbuf + pos, "%i%n", &data, &bytes_read) == 1) { |
| pos += bytes_read; |
| values[cnt++] = data & 0xff; |
| } |
| |
| if (!cnt) |
| goto free_buf; |
| |
| pr_debug("address %d, count %d\n", address, cnt); |
| /* Perform the write(s) */ |
| |
| ret = fg_sram_write(trans->chip, address, 0, values, cnt, 0); |
| if (ret) { |
| pr_err("SRAM write failed, err = %zu\n", ret); |
| } else { |
| ret = count; |
| trans->offset += cnt > 4 ? 4 : cnt; |
| } |
| |
| free_buf: |
| kfree(kbuf); |
| return ret; |
| } |
| |
| static const struct file_operations fg_sram_dfs_reg_fops = { |
| .open = fg_sram_dfs_open, |
| .release = fg_sram_dfs_close, |
| .read = fg_sram_dfs_reg_read, |
| .write = fg_sram_dfs_reg_write, |
| }; |
| |
| /* |
| * fg_debugfs_create: adds new fg_sram debugfs entry |
| * @return zero on success |
| */ |
| int fg_sram_debugfs_create(struct fg_chip *chip) |
| { |
| struct dentry *root; |
| struct dentry *file; |
| mode_t dfs_mode = 0600; |
| |
| pr_debug("Creating FG_SRAM debugfs file-system\n"); |
| root = debugfs_create_dir("fg_sram", NULL); |
| if (IS_ERR_OR_NULL(root)) { |
| pr_err("Error creating top level directory err:%ld", |
| (long)root); |
| if (PTR_ERR(root) == -ENODEV) |
| pr_err("debugfs is not enabled in the kernel"); |
| return -ENODEV; |
| } |
| |
| if (!root) |
| return -ENOENT; |
| |
| dbgfs_data.help_msg.size = strlen(dbgfs_data.help_msg.data); |
| file = debugfs_create_blob("help", 0444, root, &dbgfs_data.help_msg); |
| if (!file) { |
| pr_err("error creating help entry\n"); |
| goto err_remove_fs; |
| } |
| |
| dbgfs_data.chip = chip; |
| |
| file = debugfs_create_u32("count", dfs_mode, root, &(dbgfs_data.cnt)); |
| if (!file) { |
| pr_err("error creating 'count' entry\n"); |
| goto err_remove_fs; |
| } |
| |
| file = debugfs_create_x32("address", dfs_mode, |
| root, &(dbgfs_data.addr)); |
| if (!file) { |
| pr_err("error creating 'address' entry\n"); |
| goto err_remove_fs; |
| } |
| |
| file = debugfs_create_file("data", dfs_mode, root, &dbgfs_data, |
| &fg_sram_dfs_reg_fops); |
| if (!file) { |
| pr_err("error creating 'data' entry\n"); |
| goto err_remove_fs; |
| } |
| |
| chip->dentry = root; |
| return 0; |
| |
| err_remove_fs: |
| debugfs_remove_recursive(root); |
| return -ENOMEM; |
| } |