You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1652 lines
37 KiB
1652 lines
37 KiB
3 years ago
|
/* Copyright (c) 2016-2018, 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.
|
||
|
*/
|
||
|
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_irq.h>
|
||
|
#include <linux/sort.h>
|
||
|
#include "fg-core.h"
|
||
|
#include "fg-reg.h"
|
||
|
|
||
|
/* 3 byte address + 1 space character */
|
||
|
#define ADDR_LEN 4
|
||
|
/* Format is 'XX ' */
|
||
|
#define CHARS_PER_ITEM 3
|
||
|
/* 4 data items per line */
|
||
|
#define ITEMS_PER_LINE 4
|
||
|
#define MAX_LINE_LENGTH (ADDR_LEN + (ITEMS_PER_LINE * \
|
||
|
CHARS_PER_ITEM) + 1) \
|
||
|
|
||
|
#define VOLTAGE_15BIT_MASK GENMASK(14, 0)
|
||
|
#define MAX_READ_TRIES 5
|
||
|
|
||
|
int fg_decode_voltage_15b(struct fg_sram_param *sp,
|
||
|
enum fg_sram_param_id id, int value)
|
||
|
{
|
||
|
value &= VOLTAGE_15BIT_MASK;
|
||
|
sp[id].value = div_u64((u64)value * sp[id].denmtr, sp[id].numrtr);
|
||
|
pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
|
||
|
sp[id].value);
|
||
|
return sp[id].value;
|
||
|
}
|
||
|
|
||
|
int fg_decode_current_16b(struct fg_sram_param *sp,
|
||
|
enum fg_sram_param_id id, int value)
|
||
|
{
|
||
|
value = sign_extend32(value, 15);
|
||
|
sp[id].value = div_s64((s64)value * sp[id].denmtr, sp[id].numrtr);
|
||
|
pr_debug("id: %d raw value: %x decoded value: %d\n", id, value,
|
||
|
sp[id].value);
|
||
|
return sp[id].value;
|
||
|
}
|
||
|
|
||
|
int fg_decode_cc_soc(struct fg_sram_param *sp,
|
||
|
enum fg_sram_param_id id, int value)
|
||
|
{
|
||
|
sp[id].value = div_s64((s64)value * sp[id].denmtr, sp[id].numrtr);
|
||
|
sp[id].value = sign_extend32(sp[id].value, 31);
|
||
|
pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
|
||
|
sp[id].value);
|
||
|
return sp[id].value;
|
||
|
}
|
||
|
|
||
|
int fg_decode_value_16b(struct fg_sram_param *sp,
|
||
|
enum fg_sram_param_id id, int value)
|
||
|
{
|
||
|
sp[id].value = div_u64((u64)(u16)value * sp[id].denmtr, sp[id].numrtr);
|
||
|
pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
|
||
|
sp[id].value);
|
||
|
return sp[id].value;
|
||
|
}
|
||
|
|
||
|
int fg_decode_default(struct fg_sram_param *sp, enum fg_sram_param_id id,
|
||
|
int value)
|
||
|
{
|
||
|
sp[id].value = value;
|
||
|
return sp[id].value;
|
||
|
}
|
||
|
|
||
|
int fg_decode(struct fg_sram_param *sp, enum fg_sram_param_id id,
|
||
|
int value)
|
||
|
{
|
||
|
if (!sp[id].decode) {
|
||
|
pr_err("No decoding function for parameter %d\n", id);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return sp[id].decode(sp, id, value);
|
||
|
}
|
||
|
|
||
|
void fg_encode_voltage(struct fg_sram_param *sp,
|
||
|
enum fg_sram_param_id id, int val_mv, u8 *buf)
|
||
|
{
|
||
|
int i, mask = 0xff;
|
||
|
int64_t temp;
|
||
|
|
||
|
val_mv += sp[id].offset;
|
||
|
temp = (int64_t)div_u64((u64)val_mv * sp[id].numrtr, sp[id].denmtr);
|
||
|
pr_debug("temp: %llx id: %d, val_mv: %d, buf: [ ", temp, id, val_mv);
|
||
|
for (i = 0; i < sp[id].len; i++) {
|
||
|
buf[i] = temp & mask;
|
||
|
temp >>= 8;
|
||
|
pr_debug("%x ", buf[i]);
|
||
|
}
|
||
|
pr_debug("]\n");
|
||
|
}
|
||
|
|
||
|
void fg_encode_current(struct fg_sram_param *sp,
|
||
|
enum fg_sram_param_id id, int val_ma, u8 *buf)
|
||
|
{
|
||
|
int i, mask = 0xff;
|
||
|
int64_t temp;
|
||
|
s64 current_ma;
|
||
|
|
||
|
current_ma = val_ma;
|
||
|
temp = (int64_t)div_s64(current_ma * sp[id].numrtr, sp[id].denmtr);
|
||
|
pr_debug("temp: %llx id: %d, val: %d, buf: [ ", temp, id, val_ma);
|
||
|
for (i = 0; i < sp[id].len; i++) {
|
||
|
buf[i] = temp & mask;
|
||
|
temp >>= 8;
|
||
|
pr_debug("%x ", buf[i]);
|
||
|
}
|
||
|
pr_debug("]\n");
|
||
|
}
|
||
|
|
||
|
void fg_encode_default(struct fg_sram_param *sp,
|
||
|
enum fg_sram_param_id id, int val, u8 *buf)
|
||
|
{
|
||
|
int i, mask = 0xff;
|
||
|
int64_t temp;
|
||
|
|
||
|
temp = (int64_t)div_s64((s64)val * sp[id].numrtr, sp[id].denmtr);
|
||
|
pr_debug("temp: %llx id: %d, val: %d, buf: [ ", temp, id, val);
|
||
|
for (i = 0; i < sp[id].len; i++) {
|
||
|
buf[i] = temp & mask;
|
||
|
temp >>= 8;
|
||
|
pr_debug("%x ", buf[i]);
|
||
|
}
|
||
|
pr_debug("]\n");
|
||
|
}
|
||
|
|
||
|
void fg_encode(struct fg_sram_param *sp, enum fg_sram_param_id id,
|
||
|
int val, u8 *buf)
|
||
|
{
|
||
|
if (!sp[id].encode) {
|
||
|
pr_err("No encoding function for parameter %d\n", id);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sp[id].encode(sp, id, val, buf);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Please make sure *_sram_params table has the entry for the parameter
|
||
|
* obtained through this function. In addition to address, offset,
|
||
|
* length from where this SRAM parameter is read, a decode function
|
||
|
* need to be specified.
|
||
|
*/
|
||
|
int fg_get_sram_prop(struct fg_dev *fg, enum fg_sram_param_id id,
|
||
|
int *val)
|
||
|
{
|
||
|
int temp, rc, i;
|
||
|
u8 buf[4];
|
||
|
|
||
|
if (id < 0 || id > FG_SRAM_MAX || fg->sp[id].len > sizeof(buf))
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (fg->battery_missing)
|
||
|
return 0;
|
||
|
|
||
|
rc = fg_sram_read(fg, fg->sp[id].addr_word, fg->sp[id].addr_byte,
|
||
|
buf, fg->sp[id].len, FG_IMA_DEFAULT);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error reading address %d[%d] rc=%d\n",
|
||
|
fg->sp[id].addr_word, fg->sp[id].addr_byte, rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
for (i = 0, temp = 0; i < fg->sp[id].len; i++)
|
||
|
temp |= buf[i] << (8 * i);
|
||
|
|
||
|
*val = fg_decode(fg->sp, id, temp);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
buf->size = 0;
|
||
|
buf->head = 0;
|
||
|
memset(buf->arr, 0, sizeof(buf->arr));
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
static int cmp_int(const void *a, const void *b)
|
||
|
{
|
||
|
return *(int *)a - *(int *)b;
|
||
|
}
|
||
|
|
||
|
int fg_circ_buf_median(struct fg_circ_buf *buf, int *median)
|
||
|
{
|
||
|
int *temp;
|
||
|
|
||
|
if (buf->size == 0)
|
||
|
return -ENODATA;
|
||
|
|
||
|
if (buf->size == 1) {
|
||
|
*median = buf->arr[0];
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
temp = kmalloc_array(buf->size, sizeof(*temp), GFP_KERNEL);
|
||
|
if (!temp)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
memcpy(temp, buf->arr, buf->size * sizeof(*temp));
|
||
|
sort(temp, buf->size, sizeof(*temp), cmp_int, NULL);
|
||
|
|
||
|
if (buf->size % 2)
|
||
|
*median = temp[buf->size / 2];
|
||
|
else
|
||
|
*median = (temp[buf->size / 2 - 1] + temp[buf->size / 2]) / 2;
|
||
|
|
||
|
kfree(temp);
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
bool usb_psy_initialized(struct fg_dev *fg)
|
||
|
{
|
||
|
if (fg->usb_psy)
|
||
|
return true;
|
||
|
|
||
|
fg->usb_psy = power_supply_get_by_name("usb");
|
||
|
if (!fg->usb_psy)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool is_usb_present(struct fg_dev *fg)
|
||
|
{
|
||
|
union power_supply_propval pval = {0, };
|
||
|
int rc;
|
||
|
|
||
|
if (!usb_psy_initialized(fg))
|
||
|
return false;
|
||
|
|
||
|
rc = power_supply_get_property(fg->usb_psy,
|
||
|
POWER_SUPPLY_PROP_PRESENT, &pval);
|
||
|
if (rc < 0)
|
||
|
return false;
|
||
|
|
||
|
return pval.intval != 0;
|
||
|
}
|
||
|
|
||
|
bool dc_psy_initialized(struct fg_dev *fg)
|
||
|
{
|
||
|
if (fg->dc_psy)
|
||
|
return true;
|
||
|
|
||
|
fg->dc_psy = power_supply_get_by_name("dc");
|
||
|
if (!fg->dc_psy)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool is_dc_present(struct fg_dev *fg)
|
||
|
{
|
||
|
union power_supply_propval pval = {0, };
|
||
|
int rc;
|
||
|
|
||
|
if (!dc_psy_initialized(fg))
|
||
|
return false;
|
||
|
|
||
|
rc = power_supply_get_property(fg->dc_psy,
|
||
|
POWER_SUPPLY_PROP_PRESENT, &pval);
|
||
|
if (rc < 0)
|
||
|
return false;
|
||
|
|
||
|
return pval.intval != 0;
|
||
|
}
|
||
|
|
||
|
bool is_input_present(struct fg_dev *fg)
|
||
|
{
|
||
|
return is_usb_present(fg) || is_dc_present(fg);
|
||
|
}
|
||
|
|
||
|
void fg_notify_charger(struct fg_dev *fg)
|
||
|
{
|
||
|
union power_supply_propval prop = {0, };
|
||
|
int rc;
|
||
|
|
||
|
if (!fg->batt_psy)
|
||
|
return;
|
||
|
|
||
|
if (!fg->profile_available)
|
||
|
return;
|
||
|
|
||
|
if (fg->bp.float_volt_uv > 0) {
|
||
|
prop.intval = fg->bp.float_volt_uv;
|
||
|
rc = power_supply_set_property(fg->batt_psy,
|
||
|
POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error in setting voltage_max property on batt_psy, rc=%d\n",
|
||
|
rc);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fg->bp.fastchg_curr_ma > 0) {
|
||
|
prop.intval = fg->bp.fastchg_curr_ma * 1000;
|
||
|
rc = power_supply_set_property(fg->batt_psy,
|
||
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
||
|
&prop);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error in setting constant_charge_current_max property on batt_psy, rc=%d\n",
|
||
|
rc);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool batt_psy_initialized(struct fg_dev *fg)
|
||
|
{
|
||
|
if (fg->batt_psy)
|
||
|
return true;
|
||
|
|
||
|
fg->batt_psy = power_supply_get_by_name("battery");
|
||
|
if (!fg->batt_psy)
|
||
|
return false;
|
||
|
|
||
|
/* batt_psy is initialized, set the fcc and fv */
|
||
|
fg_notify_charger(fg);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool is_qnovo_en(struct fg_dev *fg)
|
||
|
{
|
||
|
union power_supply_propval pval = {0, };
|
||
|
int rc;
|
||
|
|
||
|
if (!batt_psy_initialized(fg))
|
||
|
return false;
|
||
|
|
||
|
rc = power_supply_get_property(fg->batt_psy,
|
||
|
POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE, &pval);
|
||
|
if (rc < 0)
|
||
|
return false;
|
||
|
|
||
|
return pval.intval != 0;
|
||
|
}
|
||
|
|
||
|
bool pc_port_psy_initialized(struct fg_dev *fg)
|
||
|
{
|
||
|
if (fg->pc_port_psy)
|
||
|
return true;
|
||
|
|
||
|
fg->pc_port_psy = power_supply_get_by_name("pc_port");
|
||
|
if (!fg->pc_port_psy)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool is_parallel_charger_available(struct fg_dev *fg)
|
||
|
{
|
||
|
if (!fg->parallel_psy)
|
||
|
fg->parallel_psy = power_supply_get_by_name("parallel");
|
||
|
|
||
|
if (!fg->parallel_psy)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#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, " ");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void dump_sram(struct fg_dev *fg, u8 *buf, int addr, int len)
|
||
|
{
|
||
|
int i;
|
||
|
char str[16];
|
||
|
|
||
|
/*
|
||
|
* Length passed should be in multiple of 4 as each GEN3 FG SRAM word
|
||
|
* holds 4 bytes and GEN4 FG SRAM word holds 2 bytes. To keep this
|
||
|
* simple, even if a length which is not a multiple of 4 bytes or less
|
||
|
* than 4 bytes is passed, SRAM registers dumped will be always in
|
||
|
* multiple of 4 bytes.
|
||
|
*/
|
||
|
for (i = 0; i < len; i += 4) {
|
||
|
str[0] = '\0';
|
||
|
fill_string(str, sizeof(str), buf + i, 4);
|
||
|
|
||
|
/*
|
||
|
* We still print 4 bytes per line. However, the address
|
||
|
* should be incremented by 2 for GEN4 FG as each word holds
|
||
|
* 2 bytes.
|
||
|
*/
|
||
|
if (fg->version == GEN3_FG)
|
||
|
pr_info("%03d %s\n", addr + (i / 4), str);
|
||
|
else
|
||
|
pr_info("%03d %s\n", addr + (i / 2), str);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline bool fg_sram_address_valid(struct fg_dev *fg, u16 address,
|
||
|
int len)
|
||
|
{
|
||
|
if (address > fg->sram.address_max || !fg->sram.num_bytes_per_word)
|
||
|
return false;
|
||
|
|
||
|
if ((address + DIV_ROUND_UP(len, fg->sram.num_bytes_per_word))
|
||
|
> fg->sram.address_max + 1)
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#define SOC_UPDATE_WAIT_MS 1500
|
||
|
int fg_sram_write(struct fg_dev *fg, u16 address, u8 offset,
|
||
|
u8 *val, int len, int flags)
|
||
|
{
|
||
|
int rc = 0, tries = 0;
|
||
|
bool atomic_access = false;
|
||
|
|
||
|
if (!fg)
|
||
|
return -ENXIO;
|
||
|
|
||
|
if (fg->battery_missing)
|
||
|
return 0;
|
||
|
|
||
|
if (!fg_sram_address_valid(fg, address, len))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (!(flags & FG_IMA_NO_WLOCK))
|
||
|
vote(fg->awake_votable, SRAM_WRITE, true, 0);
|
||
|
|
||
|
if (flags & FG_IMA_ATOMIC)
|
||
|
atomic_access = true;
|
||
|
|
||
|
/* With DMA granted, SRAM transaction is already atomic */
|
||
|
if (fg->use_dma)
|
||
|
atomic_access = false;
|
||
|
|
||
|
mutex_lock(&fg->sram_rw_lock);
|
||
|
|
||
|
if (atomic_access && fg->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(&fg->soc_update);
|
||
|
enable_irq(fg->irqs[SOC_UPDATE_IRQ].irq);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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) {
|
||
|
for (tries = 0; tries < 2; tries++) {
|
||
|
/* Wait for SOC_UPDATE completion */
|
||
|
rc = wait_for_completion_interruptible_timeout(
|
||
|
&fg->soc_update,
|
||
|
msecs_to_jiffies(SOC_UPDATE_WAIT_MS));
|
||
|
if (rc > 0) {
|
||
|
rc = 0;
|
||
|
break;
|
||
|
} else if (!rc) {
|
||
|
rc = -ETIMEDOUT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (rc < 0) {
|
||
|
pr_err("wait for soc_update timed out rc=%d\n", rc);
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fg->use_dma)
|
||
|
rc = fg_direct_mem_write(fg, address, offset, val, len,
|
||
|
false);
|
||
|
else
|
||
|
rc = fg_interleaved_mem_write(fg, 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 && fg->irqs[SOC_UPDATE_IRQ].irq)
|
||
|
disable_irq_nosync(fg->irqs[SOC_UPDATE_IRQ].irq);
|
||
|
|
||
|
mutex_unlock(&fg->sram_rw_lock);
|
||
|
if (!(flags & FG_IMA_NO_WLOCK))
|
||
|
vote(fg->awake_votable, SRAM_WRITE, false, 0);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
int fg_sram_read(struct fg_dev *fg, u16 address, u8 offset,
|
||
|
u8 *val, int len, int flags)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
if (!fg)
|
||
|
return -ENXIO;
|
||
|
|
||
|
if (fg->battery_missing)
|
||
|
return 0;
|
||
|
|
||
|
if (!fg_sram_address_valid(fg, address, len))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (!(flags & FG_IMA_NO_WLOCK))
|
||
|
vote(fg->awake_votable, SRAM_READ, true, 0);
|
||
|
|
||
|
mutex_lock(&fg->sram_rw_lock);
|
||
|
|
||
|
if (fg->use_dma)
|
||
|
rc = fg_direct_mem_read(fg, address, offset, val, len);
|
||
|
else
|
||
|
rc = fg_interleaved_mem_read(fg, 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(&fg->sram_rw_lock);
|
||
|
if (!(flags & FG_IMA_NO_WLOCK))
|
||
|
vote(fg->awake_votable, SRAM_READ, false, 0);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
int fg_sram_masked_write(struct fg_dev *fg, u16 address, u8 offset,
|
||
|
u8 mask, u8 val, int flags)
|
||
|
{
|
||
|
int rc = 0, length = 4;
|
||
|
u8 buf[4];
|
||
|
|
||
|
if (fg->version == GEN4_FG)
|
||
|
length = 2;
|
||
|
|
||
|
rc = fg_sram_read(fg, address, 0, buf, length, 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(fg, address, 0, buf, length, 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_dev *fg, int addr, u8 *val, int len)
|
||
|
{
|
||
|
int rc, i;
|
||
|
|
||
|
if (!fg || !fg->regmap)
|
||
|
return -ENXIO;
|
||
|
|
||
|
rc = regmap_bulk_read(fg->regmap, addr, val, len);
|
||
|
|
||
|
if (rc < 0) {
|
||
|
dev_err(fg->dev, "regmap_read failed for address %04x rc=%d\n",
|
||
|
addr, rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
if (*fg->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;
|
||
|
}
|
||
|
|
||
|
static inline bool is_sec_access(struct fg_dev *fg, int addr)
|
||
|
{
|
||
|
if (fg->version != GEN3_FG)
|
||
|
return false;
|
||
|
|
||
|
return ((addr & 0x00FF) > 0xD0);
|
||
|
}
|
||
|
|
||
|
int fg_write(struct fg_dev *fg, int addr, u8 *val, int len)
|
||
|
{
|
||
|
int rc, i;
|
||
|
|
||
|
if (!fg || !fg->regmap)
|
||
|
return -ENXIO;
|
||
|
|
||
|
mutex_lock(&fg->bus_lock);
|
||
|
if (is_sec_access(fg, addr)) {
|
||
|
rc = regmap_write(fg->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
|
||
|
if (rc < 0) {
|
||
|
dev_err(fg->dev, "regmap_write failed for address %x rc=%d\n",
|
||
|
addr, rc);
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (len > 1)
|
||
|
rc = regmap_bulk_write(fg->regmap, addr, val, len);
|
||
|
else
|
||
|
rc = regmap_write(fg->regmap, addr, *val);
|
||
|
|
||
|
if (rc < 0) {
|
||
|
dev_err(fg->dev, "regmap_write failed for address %04x rc=%d\n",
|
||
|
addr, rc);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (*fg->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(&fg->bus_lock);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
int fg_masked_write(struct fg_dev *fg, int addr, u8 mask, u8 val)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
if (!fg || !fg->regmap)
|
||
|
return -ENXIO;
|
||
|
|
||
|
mutex_lock(&fg->bus_lock);
|
||
|
if (is_sec_access(fg, addr)) {
|
||
|
rc = regmap_write(fg->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
|
||
|
if (rc < 0) {
|
||
|
dev_err(fg->dev, "regmap_write failed for address %x rc=%d\n",
|
||
|
addr, rc);
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rc = regmap_update_bits(fg->regmap, addr, mask, val);
|
||
|
if (rc < 0) {
|
||
|
dev_err(fg->dev, "regmap_update_bits failed for address %04x rc=%d\n",
|
||
|
addr, rc);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
fg_dbg(fg, FG_BUS_WRITE, "addr=%04x mask: %02x val: %02x\n", addr,
|
||
|
mask, val);
|
||
|
out:
|
||
|
mutex_unlock(&fg->bus_lock);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
int fg_dump_regs(struct fg_dev *fg)
|
||
|
{
|
||
|
int i, rc;
|
||
|
u8 buf[256];
|
||
|
|
||
|
if (!fg)
|
||
|
return -EINVAL;
|
||
|
|
||
|
rc = fg_read(fg, fg->batt_soc_base, buf, sizeof(buf));
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
pr_info("batt_soc_base registers:\n");
|
||
|
for (i = 0; i < sizeof(buf); i++)
|
||
|
pr_info("%04x:%02x\n", fg->batt_soc_base + i, buf[i]);
|
||
|
|
||
|
rc = fg_read(fg, fg->mem_if_base, buf, sizeof(buf));
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
pr_info("mem_if_base registers:\n");
|
||
|
for (i = 0; i < sizeof(buf); i++)
|
||
|
pr_info("%04x:%02x\n", fg->mem_if_base + i, buf[i]);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fg_restart(struct fg_dev *fg, int wait_time_ms)
|
||
|
{
|
||
|
union power_supply_propval pval = {0, };
|
||
|
int rc;
|
||
|
bool tried_again = false;
|
||
|
|
||
|
if (!fg->fg_psy)
|
||
|
return -ENODEV;
|
||
|
|
||
|
rc = power_supply_get_property(fg->fg_psy, POWER_SUPPLY_PROP_CAPACITY,
|
||
|
&pval);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error in getting capacity, rc=%d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
fg->last_soc = pval.intval;
|
||
|
fg->fg_restarting = true;
|
||
|
reinit_completion(&fg->soc_ready);
|
||
|
rc = fg_masked_write(fg, BATT_SOC_RESTART(fg), RESTART_GO_BIT,
|
||
|
RESTART_GO_BIT);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error in writing to %04x, rc=%d\n",
|
||
|
BATT_SOC_RESTART(fg), rc);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
wait:
|
||
|
rc = wait_for_completion_interruptible_timeout(&fg->soc_ready,
|
||
|
msecs_to_jiffies(wait_time_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_ready timed out rc=%d\n", rc);
|
||
|
}
|
||
|
|
||
|
rc = fg_masked_write(fg, BATT_SOC_RESTART(fg), RESTART_GO_BIT, 0);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error in writing to %04x, rc=%d\n",
|
||
|
BATT_SOC_RESTART(fg), rc);
|
||
|
goto out;
|
||
|
}
|
||
|
out:
|
||
|
fg->fg_restarting = false;
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
/* All fg_get_* , fg_set_* functions here */
|
||
|
|
||
|
int fg_get_msoc_raw(struct fg_dev *fg, int *val)
|
||
|
{
|
||
|
u8 cap[2];
|
||
|
int rc, tries = 0;
|
||
|
|
||
|
while (tries < MAX_READ_TRIES) {
|
||
|
rc = fg_read(fg, BATT_SOC_FG_MONOTONIC_SOC(fg), cap, 2);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to read addr=0x%04x, rc=%d\n",
|
||
|
BATT_SOC_FG_MONOTONIC_SOC(fg), rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
if (cap[0] == cap[1])
|
||
|
break;
|
||
|
|
||
|
tries++;
|
||
|
}
|
||
|
|
||
|
if (tries == MAX_READ_TRIES) {
|
||
|
pr_err("MSOC: shadow registers do not match\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
fg_dbg(fg, FG_POWER_SUPPLY, "raw: 0x%02x\n", cap[0]);
|
||
|
*val = cap[0];
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fg_get_msoc(struct fg_dev *fg, int *msoc)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
rc = fg_get_msoc_raw(fg, msoc);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
/*
|
||
|
* To have better endpoints for 0 and 100, it is good to tune the
|
||
|
* calculation discarding values 0 and 255 while rounding off. Rest
|
||
|
* of the values 1-254 will be scaled to 1-99. DIV_ROUND_UP will not
|
||
|
* be suitable here as it rounds up any value higher than 252 to 100.
|
||
|
*/
|
||
|
if (*msoc == FULL_SOC_RAW)
|
||
|
*msoc = 100;
|
||
|
else if (*msoc == 0)
|
||
|
*msoc = 0;
|
||
|
else
|
||
|
*msoc = DIV_ROUND_CLOSEST((*msoc - 1) * (FULL_CAPACITY - 2),
|
||
|
FULL_SOC_RAW - 2) + 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#define DEFAULT_BATT_TYPE "Unknown Battery"
|
||
|
#define MISSING_BATT_TYPE "Missing Battery"
|
||
|
#define LOADING_BATT_TYPE "Loading Battery"
|
||
|
#define SKIP_BATT_TYPE "Skipped loading battery"
|
||
|
const char *fg_get_battery_type(struct fg_dev *fg)
|
||
|
{
|
||
|
switch (fg->profile_load_status) {
|
||
|
case PROFILE_MISSING:
|
||
|
return DEFAULT_BATT_TYPE;
|
||
|
case PROFILE_SKIPPED:
|
||
|
return SKIP_BATT_TYPE;
|
||
|
case PROFILE_LOADED:
|
||
|
if (fg->bp.batt_type_str)
|
||
|
return fg->bp.batt_type_str;
|
||
|
break;
|
||
|
case PROFILE_NOT_LOADED:
|
||
|
return MISSING_BATT_TYPE;
|
||
|
default:
|
||
|
break;
|
||
|
};
|
||
|
|
||
|
if (fg->battery_missing)
|
||
|
return MISSING_BATT_TYPE;
|
||
|
|
||
|
if (fg->profile_available)
|
||
|
return LOADING_BATT_TYPE;
|
||
|
|
||
|
return DEFAULT_BATT_TYPE;
|
||
|
}
|
||
|
|
||
|
int fg_get_battery_resistance(struct fg_dev *fg, int *val)
|
||
|
{
|
||
|
int rc, esr_uohms, rslow_uohms;
|
||
|
|
||
|
rc = fg_get_sram_prop(fg, FG_SRAM_ESR, &esr_uohms);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to get ESR, rc=%d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = fg_get_sram_prop(fg, FG_SRAM_RSLOW, &rslow_uohms);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to get Rslow, rc=%d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
*val = esr_uohms + rslow_uohms;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#define BATT_CURRENT_NUMR 488281
|
||
|
#define BATT_CURRENT_DENR 1000
|
||
|
int fg_get_battery_current(struct fg_dev *fg, int *val)
|
||
|
{
|
||
|
int rc = 0, tries = 0;
|
||
|
int64_t temp = 0;
|
||
|
u8 buf[2], buf_cp[2];
|
||
|
|
||
|
while (tries++ < MAX_READ_TRIES) {
|
||
|
rc = fg_read(fg, BATT_INFO_IBATT_LSB(fg), buf, 2);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to read addr=0x%04x, rc=%d\n",
|
||
|
BATT_INFO_IBATT_LSB(fg), rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = fg_read(fg, BATT_INFO_IBATT_LSB_CP(fg), buf_cp, 2);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to read addr=0x%04x, rc=%d\n",
|
||
|
BATT_INFO_IBATT_LSB_CP(fg), rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
if (buf[0] == buf_cp[0] && buf[1] == buf_cp[1])
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (tries == MAX_READ_TRIES) {
|
||
|
pr_err("IBATT: shadow registers do not match\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (fg->wa_flags & PMI8998_V1_REV_WA)
|
||
|
temp = buf[0] << 8 | buf[1];
|
||
|
else
|
||
|
temp = buf[1] << 8 | buf[0];
|
||
|
|
||
|
pr_debug("buf: %x %x temp: %llx\n", buf[0], buf[1], temp);
|
||
|
/* Sign bit is bit 15 */
|
||
|
temp = sign_extend32(temp, 15);
|
||
|
*val = div_s64((s64)temp * BATT_CURRENT_NUMR, BATT_CURRENT_DENR);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#define BATT_VOLTAGE_NUMR 122070
|
||
|
#define BATT_VOLTAGE_DENR 1000
|
||
|
int fg_get_battery_voltage(struct fg_dev *fg, int *val)
|
||
|
{
|
||
|
int rc = 0, tries = 0;
|
||
|
u16 temp = 0;
|
||
|
u8 buf[2], buf_cp[2];
|
||
|
|
||
|
while (tries++ < MAX_READ_TRIES) {
|
||
|
rc = fg_read(fg, BATT_INFO_VBATT_LSB(fg), buf, 2);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to read addr=0x%04x, rc=%d\n",
|
||
|
BATT_INFO_VBATT_LSB(fg), rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = fg_read(fg, BATT_INFO_VBATT_LSB_CP(fg), buf_cp, 2);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to read addr=0x%04x, rc=%d\n",
|
||
|
BATT_INFO_VBATT_LSB_CP(fg), rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
if (buf[0] == buf_cp[0] && buf[1] == buf_cp[1])
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (tries == MAX_READ_TRIES) {
|
||
|
pr_err("VBATT: shadow registers do not match\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (fg->wa_flags & PMI8998_V1_REV_WA)
|
||
|
temp = buf[0] << 8 | buf[1];
|
||
|
else
|
||
|
temp = buf[1] << 8 | buf[0];
|
||
|
|
||
|
pr_debug("buf: %x %x temp: %x\n", buf[0], buf[1], temp);
|
||
|
*val = div_u64((u64)temp * BATT_VOLTAGE_NUMR, BATT_VOLTAGE_DENR);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fg_set_constant_chg_voltage(struct fg_dev *fg, int volt_uv)
|
||
|
{
|
||
|
u8 buf[2];
|
||
|
int rc;
|
||
|
|
||
|
if (volt_uv <= 0 || volt_uv > 15590000) {
|
||
|
pr_err("Invalid voltage %d\n", volt_uv);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
fg_encode(fg->sp, FG_SRAM_VBATT_FULL, volt_uv, buf);
|
||
|
|
||
|
rc = fg_sram_write(fg, fg->sp[FG_SRAM_VBATT_FULL].addr_word,
|
||
|
fg->sp[FG_SRAM_VBATT_FULL].addr_byte, buf,
|
||
|
fg->sp[FG_SRAM_VBATT_FULL].len, FG_IMA_DEFAULT);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error in writing vbatt_full, rc=%d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int fg_set_esr_timer(struct fg_dev *fg, int cycles_init,
|
||
|
int cycles_max, bool charging, int flags)
|
||
|
{
|
||
|
u8 buf[2];
|
||
|
int rc, timer_max, timer_init;
|
||
|
|
||
|
if (cycles_init < 0 || cycles_max < 0)
|
||
|
return 0;
|
||
|
|
||
|
if (charging) {
|
||
|
timer_max = FG_SRAM_ESR_TIMER_CHG_MAX;
|
||
|
timer_init = FG_SRAM_ESR_TIMER_CHG_INIT;
|
||
|
} else {
|
||
|
timer_max = FG_SRAM_ESR_TIMER_DISCHG_MAX;
|
||
|
timer_init = FG_SRAM_ESR_TIMER_DISCHG_INIT;
|
||
|
}
|
||
|
|
||
|
fg_encode(fg->sp, timer_max, cycles_max, buf);
|
||
|
rc = fg_sram_write(fg,
|
||
|
fg->sp[timer_max].addr_word,
|
||
|
fg->sp[timer_max].addr_byte, buf,
|
||
|
fg->sp[timer_max].len, flags);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error in writing esr_timer_dischg_max, rc=%d\n",
|
||
|
rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
fg_encode(fg->sp, timer_init, cycles_init, buf);
|
||
|
rc = fg_sram_write(fg,
|
||
|
fg->sp[timer_init].addr_word,
|
||
|
fg->sp[timer_init].addr_byte, buf,
|
||
|
fg->sp[timer_init].len, flags);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Error in writing esr_timer_dischg_init, rc=%d\n",
|
||
|
rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
fg_dbg(fg, FG_STATUS, "esr_%s_timer set to %d/%d\n",
|
||
|
charging ? "charging" : "discharging", cycles_init, cycles_max);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int fg_get_irq_index_byname(struct fg_dev *fg, const char *name,
|
||
|
int size)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < size; i++) {
|
||
|
if (!fg->irqs[i].name)
|
||
|
continue;
|
||
|
|
||
|
if (strcmp(fg->irqs[i].name, name) == 0)
|
||
|
return i;
|
||
|
}
|
||
|
|
||
|
pr_err("%s is not in irq list\n", name);
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
int fg_register_interrupts(struct fg_dev *fg, int size)
|
||
|
{
|
||
|
struct device_node *child, *node = fg->dev->of_node;
|
||
|
struct property *prop;
|
||
|
const char *name;
|
||
|
int rc, irq, irq_index;
|
||
|
|
||
|
for_each_available_child_of_node(node, child) {
|
||
|
of_property_for_each_string(child, "interrupt-names", prop,
|
||
|
name) {
|
||
|
irq = of_irq_get_byname(child, name);
|
||
|
if (irq < 0) {
|
||
|
dev_err(fg->dev, "failed to get irq %s irq:%d\n",
|
||
|
name, irq);
|
||
|
return irq;
|
||
|
}
|
||
|
|
||
|
irq_index = fg_get_irq_index_byname(fg, name, size);
|
||
|
if (irq_index < 0)
|
||
|
return irq_index;
|
||
|
|
||
|
rc = devm_request_threaded_irq(fg->dev, irq, NULL,
|
||
|
fg->irqs[irq_index].handler,
|
||
|
IRQF_ONESHOT, name, fg);
|
||
|
if (rc < 0) {
|
||
|
dev_err(fg->dev, "failed to register irq handler for %s rc:%d\n",
|
||
|
name, rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
fg->irqs[irq_index].irq = irq;
|
||
|
if (fg->irqs[irq_index].wakeable)
|
||
|
enable_irq_wake(fg->irqs[irq_index].irq);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void fg_unregister_interrupts(struct fg_dev *fg, void *data, int size)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < size; i++) {
|
||
|
if (fg->irqs[i].irq)
|
||
|
devm_free_irq(fg->dev, fg->irqs[i].irq, data);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/* All the debugfs related functions are defined below */
|
||
|
|
||
|
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 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.fg) {
|
||
|
pr_err("Not initialized data\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Per file "transaction" data */
|
||
|
trans = devm_kzalloc(dbgfs_data.fg->dev, sizeof(*trans), GFP_KERNEL);
|
||
|
if (!trans)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
/* Allocate log buffer */
|
||
|
log = devm_kzalloc(dbgfs_data.fg->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.fg->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->fg = dbgfs_data.fg;
|
||
|
trans->offset = trans->addr;
|
||
|
mutex_init(&trans->fg_dfs_lock);
|
||
|
|
||
|
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;
|
||
|
mutex_destroy(&trans->fg_dfs_lock);
|
||
|
devm_kfree(trans->fg->dev, trans->log);
|
||
|
devm_kfree(trans->fg->dev, trans->data);
|
||
|
devm_kfree(trans->fg->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;
|
||
|
|
||
|
if (trans->fg->version == GEN4_FG) {
|
||
|
/*
|
||
|
* For GEN4 FG, address is in word and it increments by 1.
|
||
|
* Each word holds 2 bytes. To keep the SRAM dump format
|
||
|
* compatible, print 4 bytes per line which holds 2 words.
|
||
|
*/
|
||
|
address = trans->addr + ((offset - trans->addr) * 2 /
|
||
|
ITEMS_PER_LINE);
|
||
|
} else {
|
||
|
/*
|
||
|
* For GEN3 FG, address is in word and it increments by 1.
|
||
|
* Each word holds 4 bytes.
|
||
|
*/
|
||
|
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->fg, 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;
|
||
|
|
||
|
mutex_lock(&trans->fg_dfs_lock);
|
||
|
/* Is the the log buffer empty */
|
||
|
if (log->rpos >= log->wpos) {
|
||
|
if (get_log_data(trans) <= 0) {
|
||
|
len = 0;
|
||
|
goto unlock_mutex;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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");
|
||
|
len = -EFAULT;
|
||
|
goto unlock_mutex;
|
||
|
}
|
||
|
|
||
|
/* 'ret' is the number of bytes not copied */
|
||
|
len -= ret;
|
||
|
|
||
|
*ppos += len;
|
||
|
log->rpos += len;
|
||
|
|
||
|
unlock_mutex:
|
||
|
mutex_unlock(&trans->fg_dfs_lock);
|
||
|
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;
|
||
|
|
||
|
mutex_lock(&trans->fg_dfs_lock);
|
||
|
/* Make a copy of the user data */
|
||
|
kbuf = kmalloc(count + 1, GFP_KERNEL);
|
||
|
if (!kbuf) {
|
||
|
ret = -ENOMEM;
|
||
|
goto unlock_mutex;
|
||
|
}
|
||
|
|
||
|
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->fg, 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);
|
||
|
unlock_mutex:
|
||
|
mutex_unlock(&trans->fg_dfs_lock);
|
||
|
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,
|
||
|
};
|
||
|
|
||
|
static int fg_sram_debugfs_create(struct fg_dev *fg)
|
||
|
{
|
||
|
struct dentry *dfs_sram;
|
||
|
struct dentry *file;
|
||
|
mode_t dfs_mode = 0600;
|
||
|
|
||
|
pr_debug("Creating FG_SRAM debugfs file-system\n");
|
||
|
dfs_sram = debugfs_create_dir("sram", fg->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", 0444, dfs_sram,
|
||
|
&dbgfs_data.help_msg);
|
||
|
if (!file) {
|
||
|
pr_err("error creating help entry\n");
|
||
|
goto err_remove_fs;
|
||
|
}
|
||
|
|
||
|
dbgfs_data.fg = fg;
|
||
|
|
||
|
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_dev *fg = file->private_data;
|
||
|
char buf[512];
|
||
|
u8 alg_flags = 0;
|
||
|
int rc, i, len;
|
||
|
|
||
|
rc = fg_sram_read(fg, fg->sp[FG_SRAM_ALG_FLAGS].addr_word,
|
||
|
fg->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 (fg->alg_flags[i].invalid)
|
||
|
continue;
|
||
|
|
||
|
len += snprintf(buf + len, sizeof(buf) - sizeof(*buf) * len,
|
||
|
"%s = %d\n", fg->alg_flags[i].name,
|
||
|
(bool)(alg_flags & fg->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,
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* fg_debugfs_create: adds new fg_sram debugfs entry
|
||
|
* @return zero on success
|
||
|
*/
|
||
|
int fg_debugfs_create(struct fg_dev *fg)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
pr_debug("Creating debugfs file-system\n");
|
||
|
fg->dfs_root = debugfs_create_dir("fg", NULL);
|
||
|
if (IS_ERR_OR_NULL(fg->dfs_root)) {
|
||
|
if (PTR_ERR(fg->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)fg->dfs_root);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
rc = fg_sram_debugfs_create(fg);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to create sram dfs rc=%d\n", rc);
|
||
|
goto err_remove_fs;
|
||
|
}
|
||
|
|
||
|
if (fg->alg_flags) {
|
||
|
if (!debugfs_create_file("alg_flags", 0400, fg->dfs_root, fg,
|
||
|
&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(fg->dfs_root);
|
||
|
return -ENOMEM;
|
||
|
}
|