blob: b30b880743f3260c5dcaf582706470c79927b162 [file] [log] [blame]
/* 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;
}