| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2016-2019 The Linux Foundation. All rights reserved. |
| */ |
| |
| #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 MAX_READ_TRIES 5 |
| |
| #define VOLTAGE_24BIT_MSB_MASK GENMASK(27, 16) |
| #define VOLTAGE_24BIT_LSB_MASK GENMASK(11, 0) |
| int fg_decode_voltage_24b(struct fg_sram_param *sp, |
| enum fg_sram_param_id id, int value) |
| { |
| int msb, lsb, val; |
| |
| msb = value & VOLTAGE_24BIT_MSB_MASK; |
| lsb = value & VOLTAGE_24BIT_LSB_MASK; |
| val = (msb >> 4) | lsb; |
| sp[id].value = div_s64((s64)val * 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; |
| } |
| |
| #define VOLTAGE_15BIT_MASK GENMASK(14, 0) |
| 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; |
| } |
| |
| #define CURRENT_24BIT_MSB_MASK GENMASK(27, 16) |
| #define CURRENT_24BIT_LSB_MASK GENMASK(11, 0) |
| int fg_decode_current_24b(struct fg_sram_param *sp, |
| enum fg_sram_param_id id, int value) |
| { |
| int msb, lsb, val; |
| |
| msb = value & CURRENT_24BIT_MSB_MASK; |
| lsb = value & CURRENT_24BIT_LSB_MASK; |
| val = (msb >> 4) | lsb; |
| val = sign_extend32(val, 23); |
| sp[id].value = div_s64((s64)val * 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; |
| struct dentry *file; |
| |
| 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; |
| } |
| |
| file = debugfs_create_u32("debug_mask", 0600, fg->dfs_root, |
| fg->debug_mask); |
| if (IS_ERR_OR_NULL(file)) { |
| pr_err("failed to create debug_mask\n"); |
| goto err_remove_fs; |
| } |
| |
| 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; |
| } |