/* 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" void fg_circ_buf_add(struct fg_circ_buf *buf, int val) { buf->arr[buf->head] = val; buf->head = (buf->head + 1) % ARRAY_SIZE(buf->arr); buf->size = min(++buf->size, (int)ARRAY_SIZE(buf->arr)); } void fg_circ_buf_clr(struct fg_circ_buf *buf) { memset(buf, 0, sizeof(*buf)); } int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg) { s64 result = 0; int i; if (buf->size == 0) return -ENODATA; for (i = 0; i < buf->size; i++) result += buf->arr[i]; *avg = div_s64(result, buf->size); return 0; } int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input, s32 *output) { int i; s64 temp; if (pts == NULL) { pr_err("Table is NULL\n"); return -EINVAL; } if (tablesize < 1) { pr_err("Table has no entries\n"); return -ENOENT; } if (tablesize == 1) { *output = pts[0].y; return 0; } if (pts[0].x > pts[1].x) { pr_err("Table is not in acending order\n"); return -EINVAL; } if (input <= pts[0].x) { *output = pts[0].y; return 0; } if (input >= pts[tablesize - 1].x) { *output = pts[tablesize - 1].y; return 0; } for (i = 1; i < tablesize; i++) { if (input >= pts[i].x) continue; temp = (s64)(pts[i].y - pts[i - 1].y) * (s64)(input - pts[i - 1].x); temp = div_s64(temp, pts[i].x - pts[i - 1].x); *output = temp + pts[i - 1].y; return 0; } return -EINVAL; } 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", }, }; static bool is_usb_present(struct fg_chip *chip) { union power_supply_propval pval = {0, }; if (!chip->usb_psy) chip->usb_psy = power_supply_get_by_name("usb"); if (chip->usb_psy) power_supply_get_property(chip->usb_psy, POWER_SUPPLY_PROP_PRESENT, &pval); else return false; return pval.intval != 0; } static bool is_dc_present(struct fg_chip *chip) { union power_supply_propval pval = {0, }; if (!chip->dc_psy) chip->dc_psy = power_supply_get_by_name("dc"); if (chip->dc_psy) power_supply_get_property(chip->dc_psy, POWER_SUPPLY_PROP_PRESENT, &pval); else return false; return pval.intval != 0; } bool is_input_present(struct fg_chip *chip) { return is_usb_present(chip) || is_dc_present(chip); } #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 (chip->battery_missing) return -ENODATA; 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. */ reinit_completion(&chip->soc_update); 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 (chip->battery_missing) return -ENODATA; 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) { /* * We shouldn't be receiving a string of characters that * exceeds a size of 5 to keep this functionally correct. * Also, we should make sure that pos never gets overflowed * beyond the limit. */ if (bytes_read > 5 || bytes_read > INT_MAX - pos) { cnt = 0; ret = -EINVAL; break; } 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 */ static int fg_sram_debugfs_create(struct fg_chip *chip) { struct dentry *dfs_sram; struct dentry *file; mode_t dfs_mode = S_IRUSR | S_IWUSR; pr_debug("Creating FG_SRAM debugfs file-system\n"); dfs_sram = debugfs_create_dir("sram", chip->dfs_root); if (!dfs_sram) { pr_err("error creating fg sram dfs rc=%ld\n", (long)dfs_sram); return -ENOMEM; } dbgfs_data.help_msg.size = strlen(dbgfs_data.help_msg.data); file = debugfs_create_blob("help", S_IRUGO, dfs_sram, &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, dfs_sram, &(dbgfs_data.cnt)); if (!file) { pr_err("error creating 'count' entry\n"); goto err_remove_fs; } file = debugfs_create_x32("address", dfs_mode, dfs_sram, &(dbgfs_data.addr)); if (!file) { pr_err("error creating 'address' entry\n"); goto err_remove_fs; } file = debugfs_create_file("data", dfs_mode, dfs_sram, &dbgfs_data, &fg_sram_dfs_reg_fops); if (!file) { pr_err("error creating 'data' entry\n"); goto err_remove_fs; } return 0; err_remove_fs: debugfs_remove_recursive(dfs_sram); return -ENOMEM; } static int fg_alg_flags_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; return 0; } static ssize_t fg_alg_flags_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { struct fg_chip *chip = file->private_data; char buf[512]; u8 alg_flags = 0; int rc, i, len; rc = fg_sram_read(chip, chip->sp[FG_SRAM_ALG_FLAGS].addr_word, chip->sp[FG_SRAM_ALG_FLAGS].addr_byte, &alg_flags, 1, FG_IMA_DEFAULT); if (rc < 0) { pr_err("failed to read algorithm flags rc=%d\n", rc); return -EFAULT; } len = 0; for (i = 0; i < ALG_FLAG_MAX; ++i) { if (len > ARRAY_SIZE(buf) - 1) return -EFAULT; if (chip->alg_flags[i].invalid) continue; len += snprintf(buf + len, sizeof(buf) - sizeof(*buf) * len, "%s = %d\n", chip->alg_flags[i].name, (bool)(alg_flags & chip->alg_flags[i].bit)); } return simple_read_from_buffer(userbuf, count, ppos, buf, len); } static const struct file_operations fg_alg_flags_fops = { .open = fg_alg_flags_open, .read = fg_alg_flags_read, }; int fg_debugfs_create(struct fg_chip *chip) { int rc; pr_debug("Creating debugfs file-system\n"); chip->dfs_root = debugfs_create_dir("fg", NULL); if (IS_ERR_OR_NULL(chip->dfs_root)) { if (PTR_ERR(chip->dfs_root) == -ENODEV) pr_err("debugfs is not enabled in the kernel\n"); else pr_err("error creating fg dfs root rc=%ld\n", (long)chip->dfs_root); return -ENODEV; } rc = fg_sram_debugfs_create(chip); if (rc < 0) { pr_err("failed to create sram dfs rc=%d\n", rc); goto err_remove_fs; } if (!debugfs_create_file("alg_flags", S_IRUSR, chip->dfs_root, chip, &fg_alg_flags_fops)) { pr_err("failed to create alg_flags file\n"); goto err_remove_fs; } return 0; err_remove_fs: debugfs_remove_recursive(chip->dfs_root); return -ENOMEM; }