blob: 75b27554307cb538e7951ee11093af1b4f37ea6f [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Support For Battery EEPROM
*
* Copyright 2018 Google, LLC
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": %s " fmt, __func__
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/nvmem-consumer.h>
#include <linux/module.h>
#include <linux/delay.h>
#include "gbms_storage.h"
#define BATT_EEPROM_TAG_MINF_OFFSET 0x00
#define BATT_EEPROM_TAG_MINF_LEN GBMS_MINF_LEN
#define BATT_EEPROM_TAG_BGPN_OFFSET 0x03
#define BATT_EEPROM_TAG_BGPN_LEN GBMS_BGPN_LEN
#define BATT_EEPROM_TAG_BRID_OFFSET 0x17
#define BATT_EEPROM_TAG_BRID_LEN 1
#define BATT_EEPROM_TAG_STRD_OFFSET 0x1E
#define BATT_EEPROM_TAG_STRD_LEN 12
#define BATT_EEPROM_TAG_RSOC_OFFSET 0x2A
#define BATT_EEPROM_TAG_RSOC_LEN 2
#define BATT_EEPROM_TAG_ACIM_OFFSET 0x2C
#define BATT_EEPROM_TAG_ACIM_LEN 2
#define BATT_EEPROM_TAG_BCNT_OFFSET 0x2E
#define BATT_EEPROM_TAG_BCNT_LEN (GBMS_CCBIN_BUCKET_COUNT * 2)
#define BATT_EEPROM_TAG_GMSR_OFFSET 0x42
#define BATT_EEPROM_TAG_GMSR_LEN GBMS_GMSR_LEN
#define BATT_EEPROM_TAG_LOTR_OFFSET 0x59 // Layout Track
#define BATT_EEPROM_TAG_LOTR_LEN 1
#define BATT_EEPROM_TAG_CNHS_OFFSET 0x5A
#define BATT_EEPROM_TAG_CNHS_LEN 2
#define BATT_EEPROM_TAG_SELC_OFFSET 0x5C
#define BATT_EEPROM_TAG_SELC_LEN 1
#define BATT_EEPROM_TAG_CELC_OFFSET 0x5D
#define BATT_EEPROM_TAG_CELC_LEN 1
#define BATT_EEPROM_TAG_HIST_OFFSET 0x5E
#define BATT_EEPROM_TAG_HIST_LEN BATT_ONE_HIST_LEN
#define BATT_TOTAL_HIST_LEN (BATT_ONE_HIST_LEN * BATT_MAX_HIST_CNT)
#define BATT_EEPROM_TAG_EXTRA_START (BATT_EEPROM_TAG_HIST_OFFSET + BATT_TOTAL_HIST_LEN)
// 0x3E2 is the first free with 75 history entries
#define BATT_EEPROM_TAG_GCFE_OFFSET 0x3E8
#define BATT_EEPROM_TAG_GCFE_LEN 2
#define BATT_EEPROM_TAG_RAVG_OFFSET 0x3EA
#define BATT_EEPROM_TAG_RAVG_LEN 2
#define BATT_EEPROM_TAG_RFCN_OFFSET 0x3EC
#define BATT_EEPROM_TAG_RFCN_LEN 2
#define BATT_EEPROM_TAG_DINF_OFFSET 0x3EE
#define BATT_EEPROM_TAG_DINF_LEN GBMS_DINF_LEN
#define BATT_EEPROM_TAG_THAS_OFFSET 0x3FE
#define BATT_EEPROM_TAG_THAS_LEN 2
static struct gbms_storage_desc *gbee_desc;
#define GBEE_GET_NVRAM(ptr) ((struct nvmem_device *)(ptr))
#define GBEE_STORAGE_INFO(tag, addr, count, ptr) \
(gbee_desc && gbee_desc->info) ? gbee_desc->info(tag, addr, count, ptr) : \
gbee_storage_info(tag, addr, count, ptr)
/*
* I2C error when try to write continuous data.
* Add delay before write to wait previous internal write complete
* http://b/179235291#comment8
*/
#define BATT_WAIT_INTERNAL_WRITE_MS 1
int gbee_storage_info(gbms_tag_t tag, size_t *addr, size_t *count, void *ptr)
{
int ret = 0;
switch (tag) {
case GBMS_TAG_MINF:
*addr = BATT_EEPROM_TAG_MINF_OFFSET;
*count = BATT_EEPROM_TAG_MINF_LEN;
break;
case GBMS_TAG_DINF:
*addr = BATT_EEPROM_TAG_DINF_OFFSET;
*count = BATT_EEPROM_TAG_DINF_LEN;
break;
case GBMS_TAG_HIST:
*addr = BATT_EEPROM_TAG_HIST_OFFSET;
*count = BATT_EEPROM_TAG_HIST_LEN;
break;
case GBMS_TAG_SNUM:
case GBMS_TAG_BGPN:
*addr = BATT_EEPROM_TAG_BGPN_OFFSET;
*count = BATT_EEPROM_TAG_BGPN_LEN;
break;
case GBMS_TAG_GMSR:
*addr = BATT_EEPROM_TAG_GMSR_OFFSET;
*count = BATT_EEPROM_TAG_GMSR_LEN;
break;
case GBMS_TAG_BCNT:
*addr = BATT_EEPROM_TAG_BCNT_OFFSET;
*count = BATT_EEPROM_TAG_BCNT_LEN;
break;
case GBMS_TAG_CNHS:
*addr = BATT_EEPROM_TAG_CNHS_OFFSET;
*count = BATT_EEPROM_TAG_CNHS_LEN;
break;
case GBMS_TAG_LOTR:
*addr = BATT_EEPROM_TAG_LOTR_OFFSET;
*count = BATT_EEPROM_TAG_LOTR_LEN;
break;
case GBMS_TAG_SELC:
*addr = BATT_EEPROM_TAG_SELC_OFFSET;
*count = BATT_EEPROM_TAG_SELC_LEN;
break;
case GBMS_TAG_CELC:
*addr = BATT_EEPROM_TAG_CELC_OFFSET;
*count = BATT_EEPROM_TAG_CELC_LEN;
break;
case GBMS_TAG_STRD:
*addr = BATT_EEPROM_TAG_STRD_OFFSET;
*count = BATT_EEPROM_TAG_STRD_LEN;
break;
case GBMS_TAG_RSOC:
*addr = BATT_EEPROM_TAG_RSOC_OFFSET;
*count = BATT_EEPROM_TAG_RSOC_LEN;
break;
case GBMS_TAG_ACIM:
*addr = BATT_EEPROM_TAG_ACIM_OFFSET;
*count = BATT_EEPROM_TAG_ACIM_LEN;
break;
case GBMS_TAG_GCFE:
*addr = BATT_EEPROM_TAG_GCFE_OFFSET;
*count = BATT_EEPROM_TAG_GCFE_LEN;
break;
case GBMS_TAG_RAVG:
*addr = BATT_EEPROM_TAG_RAVG_OFFSET;
*count = BATT_EEPROM_TAG_RAVG_LEN;
break;
case GBMS_TAG_RFCN:
*addr = BATT_EEPROM_TAG_RFCN_OFFSET;
*count = BATT_EEPROM_TAG_RFCN_LEN;
break;
case GBMS_TAG_THAS:
*addr = BATT_EEPROM_TAG_THAS_OFFSET;
*count = BATT_EEPROM_TAG_THAS_LEN;
break;
default:
ret = -ENOENT;
break;
}
return ret;
}
static int gbee_storage_iter(int index, gbms_tag_t *tag, void *ptr)
{
static const gbms_tag_t keys[] = { GBMS_TAG_BGPN, GBMS_TAG_MINF,
GBMS_TAG_DINF, GBMS_TAG_HIST,
GBMS_TAG_BRID, GBMS_TAG_SNUM,
GBMS_TAG_GMSR, GBMS_TAG_BCNT,
GBMS_TAG_CNHS, GBMS_TAG_SELC,
GBMS_TAG_CELC, GBMS_TAG_LOTR,
GBMS_TAG_STRD, GBMS_TAG_RSOC,
GBMS_TAG_ACIM, GBMS_TAG_GCFE,
GBMS_TAG_RAVG, GBMS_TAG_RFCN,
GBMS_TAG_THAS};
const int count = ARRAY_SIZE(keys);
if (index < 0 || index >= count)
return -ENOENT;
*tag = keys[index];
return 0;
}
static int gbee_storage_read(gbms_tag_t tag, void *buff, size_t size, void *ptr)
{
struct nvmem_device *nvmem = GBEE_GET_NVRAM(ptr);
size_t offset = 0, len = 0;
int ret;
if (tag == GBMS_TAG_BRID) {
u8 temp;
if (size != sizeof(u32))
return -ENOMEM;
ret = nvmem_device_read(nvmem, BATT_EEPROM_TAG_BRID_OFFSET,
1, &temp);
if (ret < 0)
return ret;
((u32*)buff)[0] = temp;
return len;
}
ret = GBEE_STORAGE_INFO(tag, &offset, &len, ptr);
if (ret < 0)
return ret;
if (!len)
return -ENOENT;
if (len > size)
return -ENOMEM;
ret = nvmem_device_read(nvmem, offset, len, buff);
if (ret == 0)
ret = len;
return ret;
}
static bool gbee_storage_is_writable(gbms_tag_t tag)
{
switch (tag) {
case GBMS_TAG_DINF:
case GBMS_TAG_GMSR:
case GBMS_TAG_BCNT:
case GBMS_TAG_CNHS:
case GBMS_TAG_SELC:
case GBMS_TAG_CELC:
case GBMS_TAG_BPST:
case GBMS_TAG_STRD:
case GBMS_TAG_RSOC:
case GBMS_TAG_ACIM:
case GBMS_TAG_GCFE:
case GBMS_TAG_RAVG:
case GBMS_TAG_RFCN:
case GBMS_TAG_THAS:
return true;
default:
return false;
}
}
static int gbee_storage_write(gbms_tag_t tag, const void *buff, size_t size,
void *ptr)
{
struct nvmem_device *nvmem = ptr;
size_t offset = 0, len = 0;
int ret, write_size = 0;
if (!gbee_storage_is_writable(tag))
return -ENOENT;
ret = GBEE_STORAGE_INFO(tag, &offset, &len, ptr);
if (ret < 0)
return ret;
if (size > len)
return -ENOMEM;
for (write_size = 0; write_size < size; write_size++) {
ret = nvmem_device_write(nvmem, write_size + offset, 1,
&((char *)buff)[write_size]);
if (ret < 0)
return ret;
msleep(BATT_WAIT_INTERNAL_WRITE_MS);
}
ret = size;
return ret;
}
static int gbee_storage_read_data(gbms_tag_t tag, void *data, size_t count,
int idx, void *ptr)
{
struct nvmem_device *nvmem = GBEE_GET_NVRAM(ptr);
size_t offset = 0, len = 0;
int ret;
switch (tag) {
case GBMS_TAG_HIST:
ret = GBEE_STORAGE_INFO(tag, &offset, &len, ptr);
break;
default:
ret = -ENOENT;
break;
}
if (ret < 0)
return ret;
if (!data || !count) {
if (idx == GBMS_STORAGE_INDEX_INVALID)
return 0;
else
return BATT_MAX_HIST_CNT;
}
if (idx < 0)
return -EINVAL;
/* index == 0 is ok here */
if (idx >= BATT_MAX_HIST_CNT)
return -ENODATA;
if (len > count)
return -EINVAL;
offset += len * idx;
ret = nvmem_device_read(nvmem, offset, len, data);
if (ret == 0)
ret = len;
return ret;
}
static int gbee_storage_write_data(gbms_tag_t tag, const void *data,
size_t count, int idx, void *ptr)
{
struct nvmem_device *nvmem = GBEE_GET_NVRAM(ptr);
size_t offset = 0, len = 0;
int ret, write_size = 0;
switch (tag) {
case GBMS_TAG_HIST:
ret = GBEE_STORAGE_INFO(tag, &offset, &len, ptr);
break;
default:
ret = -ENOENT;
break;
}
if (ret < 0)
return ret;
if (idx < 0 || !data || !count)
return -EINVAL;
/* index == 0 is ok here */
if (idx >= BATT_MAX_HIST_CNT)
return -ENODATA;
if (count > len)
return -EINVAL;
offset += len * idx;
for (write_size = 0; write_size < len; write_size++) {
ret = nvmem_device_write(nvmem, write_size + offset, 1,
&((char *)data)[write_size]);
if (ret < 0)
return ret;
msleep(BATT_WAIT_INTERNAL_WRITE_MS);
}
ret = len;
return ret;
}
static struct gbms_storage_desc gbee_storage_dsc = {
.info = gbee_storage_info,
.iter = gbee_storage_iter,
.read = gbee_storage_read,
.write = gbee_storage_write,
.read_data = gbee_storage_read_data,
.write_data = gbee_storage_write_data,
};
struct gbms_storage_desc gbee_storage01_dsc = {
.info = gbee_storage01_info,
.iter = gbee_storage01_iter,
.read = gbee_storage_read,
.write = gbee_storage_write,
.read_data = gbee_storage_read_data,
.write_data = gbee_storage_write_data,
};
/* TODO: factor history mechanics out of google battery? */
static int gbms_hist_move(struct nvmem_device *nvmem, int from, int to, int len)
{
u8 *buff, *p;
int index, ret;
buff = kzalloc(len, GFP_KERNEL);
if (!buff)
return -ENOMEM;
/* move only the entries that are used */
p = buff;
for (index = 0; index < BATT_MAX_HIST_CNT; index++) {
ret = nvmem_device_read(nvmem, from, BATT_ONE_HIST_LEN, p);
if (ret < 0) {
pr_err("%s: cannot read history data (%d)\n", __func__, ret);
goto exit;
}
/* verify 1st byte for tempco */
if (*p == 0xff)
break;
/* move to next history entry */
from += BATT_ONE_HIST_LEN;
p += BATT_ONE_HIST_LEN;
}
/* when the history data is empty */
if (index == 0)
goto exit;
ret = nvmem_device_write(nvmem, to, (BATT_ONE_HIST_LEN * index), buff);
if (ret < 0)
pr_err("%s: cannot write history data (%d)\n", __func__, ret);
exit:
kfree(buff);
return ret;
}
/* LOTR is in a fixed position, move */
static int gbms_lotr_update(struct nvmem_device *nvmem, int lotr_to)
{
int ret, lotr_from = 0;
static u8 init_data[5]= { 0 };
ret = nvmem_device_read(nvmem, BATT_EEPROM_TAG_LOTR_OFFSET,
BATT_EEPROM_TAG_LOTR_LEN, &lotr_from);
if (ret < 0 || lotr_from == lotr_to)
return ret;
if (lotr_to != GBMS_LOTR_V1 || lotr_from != GBMS_LOTR_DEFAULT)
return 0;
ret = gbms_hist_move(nvmem, 0x5E, 0x64, BATT_TOTAL_HIST_LEN);
if (ret < 0) {
pr_err("%s: cannot move history\n", __func__);
return ret;
/* TODO: flag this in BPST? */
}
ret = nvmem_device_write(nvmem, 0x5E, sizeof(init_data), init_data);
if (ret != sizeof(init_data)) {
pr_err("%s: cannot init new fields\n", __func__);
return ret < 0 ? ret : -EINVAL;
}
/* TODO: how do we handle backporting? */
/* now write lotr to the right place */
ret = nvmem_device_write(nvmem, BATT_EEPROM_TAG_LOTR_OFFSET,
BATT_EEPROM_TAG_LOTR_LEN, &lotr_to);
if (ret == BATT_EEPROM_TAG_LOTR_LEN)
pr_info("%s: lotr migrated %d->%d\n", __func__, lotr_from, lotr_to);
return ret;
}
static struct gbms_storage_desc *gbms_lotr_2_dsc(int lotr_ver)
{
switch (lotr_ver) {
case GBMS_LOTR_V1:
return &gbee_storage01_dsc;
default:
return &gbee_storage_dsc;
}
}
/*
* Caller will use something like of_nvmem_device_get() to retrieve the
* nvmem_device instance.
* TODO: this only supports a singleton but the model can be extended to
* multiple eeproms passing a structure to gbms_storage_register() and
* modifying the implementation of GBEE_GET_NVRAM and GBEE_STORAGE_INFO
* TODO: map nvram cells to tags
*/
int gbee_register_device(const char *name, int lotr, struct nvmem_device *nvram)
{
int ret;
gbee_desc = gbms_lotr_2_dsc(lotr);
if (!gbee_desc)
return -EINVAL;
/* convert the layout (if needed) */
ret = gbms_lotr_update(nvram, lotr);
if (ret < 0) {
pr_err("gbee %s update lotr failed, %d\n", name, ret);
goto error_exit;
}
/* watch out for races on gbee_desc */
ret = gbms_storage_register(gbee_desc, name, nvram);
if (ret == 0)
return 0;
error_exit:
gbee_desc = NULL;
return ret;
}
EXPORT_SYMBOL_GPL(gbee_register_device);
void gbee_destroy_device(void)
{
}
EXPORT_SYMBOL_GPL(gbee_destroy_device);
MODULE_AUTHOR("AleX Pelosi <apelosi@google.com>");
MODULE_DESCRIPTION("Google EEPROM");
MODULE_LICENSE("GPL");