blob: 4de9eb6c9c463717270b23d483a105cda0d5b9f5 [file] [log] [blame]
/*
* ADC driver for ASR PM803 PMIC
*
* Copyright 2021 ASR Microelectronics (Shanghai) Co., Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/power_supply.h>
#include <linux/mfd/88pm80x.h>
#include <linux/mfd/pm803.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/of_device.h>
#include "mrvl_battery_common.h"
#include "88pm80x_fg.h"
#include "88pm80x_table.h"
#include <soc/asr/addr-map.h>
#define ADC_DO_AVERAGE 1
#ifndef ADC_DO_AVERAGE
#define ADC_READ_INTERVAL (9 * HZ / 10) /* 900ms + 50ms extra delay */
#else
#define ADC_READ_INTERVAL (HZ/10)
#endif
#define ADC_READ_WAIT_MS (50)
#define MAX_ADC_GROUP_CNT 10
#define CRASHKERNEL_OFFSET (0x380)
/* #define ENABLE_ADC_DEBUG 1 */
#define ADC_ERR(stuff...) pr_err(stuff)
#define ADC_INFO(stuff...) pr_info(stuff)
#ifdef ENABLE_ADC_DEBUG
#define ADC_DBG(stuff...) pr_info(stuff)
#else
#define ADC_DBG(stuff...) do{ }while(0)
#endif
struct adc_vbat{
u16 adc; /* rtx adc value */
u16 vbat; /* vbat value */
};
static struct delayed_work adc_update_work;
static int should_stop;
static int rtp_adc_val;
static int rtn_adc_val;
static DEFINE_MUTEX(adc_mutex);
#define ADC_REG0 (0x10020)
#define ADC_REG1 (0x10024)
#define NR_RTX_ARRAY_SIZE (8)
static u32 rtn_array[NR_RTX_ARRAY_SIZE];
static u32 rtp_array[NR_RTX_ARRAY_SIZE];
static u64 rtx_index;
static int last_sample_count;
static u32 rtp_adc_code1, rtp_adc_code2;
extern bool cp_is_synced;
extern struct resource crashk_res;
extern int get_rtp_caldata(u32 *adc_code1, u32 *adc_code2);
static struct adc_vbat adc0_vbat_def_pair[MAX_ADC_GROUP_CNT] =
{
{355, 3217},
{362, 3323},
{367, 3404},
{373, 3505},
{379, 3605},
{387, 3732},
{393, 3835},
{399, 3941},
{405, 4020},
{413, 4170}
};
static struct adc_vbat adc1_vbat_def_pair[MAX_ADC_GROUP_CNT] =
{
{355, 3217},
{362, 3323},
{367, 3404},
{373, 3505},
{379, 3605},
{387, 3732},
{393, 3835},
{399, 3941},
{405, 4020},
{413, 4170}
};
static void update_vbat_caldata(void)
{
int i = 0;
struct adc_vbat *tmp_adc_vbat
= (struct adc_vbat *)(phys_to_virt(crashk_res.start) + CRASHKERNEL_OFFSET);
/* sanity check */
for(i = 0; i < MAX_ADC_GROUP_CNT; i++) {
if (tmp_adc_vbat[i].vbat > 5000) {
ADC_ERR("error: wrong adc vbat: %d\n", tmp_adc_vbat[i].vbat);
return;
}
}
memcpy((void *)adc0_vbat_def_pair,
(void *)tmp_adc_vbat,
sizeof(struct adc_vbat) * MAX_ADC_GROUP_CNT);
memcpy((void *)adc1_vbat_def_pair,
(void *)((u32)tmp_adc_vbat
+ (sizeof(struct adc_vbat) * MAX_ADC_GROUP_CNT)),
sizeof(struct adc_vbat) * MAX_ADC_GROUP_CNT);
ADC_INFO("adc0: 0:[%d-%d] 1:[%d-%d] 2:[%d-%d] 3:[%d-%d] 4:[%d-%d]\n"
" 5:[%d-%d] 6:[%d-%d] 7:[%d-%d] 8:[%d-%d] 9:[%d-%d]\n",
adc0_vbat_def_pair[0].adc, adc0_vbat_def_pair[0].vbat,
adc0_vbat_def_pair[1].adc, adc0_vbat_def_pair[1].vbat,
adc0_vbat_def_pair[2].adc, adc0_vbat_def_pair[2].vbat,
adc0_vbat_def_pair[3].adc, adc0_vbat_def_pair[3].vbat,
adc0_vbat_def_pair[4].adc, adc0_vbat_def_pair[4].vbat,
adc0_vbat_def_pair[5].adc, adc0_vbat_def_pair[5].vbat,
adc0_vbat_def_pair[6].adc, adc0_vbat_def_pair[6].vbat,
adc0_vbat_def_pair[7].adc, adc0_vbat_def_pair[7].vbat,
adc0_vbat_def_pair[8].adc, adc0_vbat_def_pair[8].vbat,
adc0_vbat_def_pair[9].adc, adc0_vbat_def_pair[9].vbat);
ADC_INFO("adc1: 0:[%d-%d] 1:[%d-%d] 2:[%d-%d] 3:[%d-%d] 4:[%d-%d]\n"
" 5:[%d-%d] 6:[%d-%d] 7:[%d-%d] 8:[%d-%d] 9:[%d-%d]\n",
adc1_vbat_def_pair[0].adc, adc1_vbat_def_pair[0].vbat,
adc1_vbat_def_pair[1].adc, adc1_vbat_def_pair[1].vbat,
adc1_vbat_def_pair[2].adc, adc1_vbat_def_pair[2].vbat,
adc1_vbat_def_pair[3].adc, adc1_vbat_def_pair[3].vbat,
adc1_vbat_def_pair[4].adc, adc1_vbat_def_pair[4].vbat,
adc1_vbat_def_pair[5].adc, adc1_vbat_def_pair[5].vbat,
adc1_vbat_def_pair[6].adc, adc1_vbat_def_pair[6].vbat,
adc1_vbat_def_pair[7].adc, adc1_vbat_def_pair[7].vbat,
adc1_vbat_def_pair[8].adc, adc1_vbat_def_pair[8].vbat,
adc1_vbat_def_pair[9].adc, adc1_vbat_def_pair[9].vbat);
}
static int convert_adc_to_vbat(int adc_value, struct adc_vbat *adc_vbat_def_pair)
{
int i, index = 0;
short y2, y1, x2, x1;
for (i = 1; i < MAX_ADC_GROUP_CNT; i++) {
if (adc_value < adc_vbat_def_pair[i].adc) {
index = i - 1;
break;
}
}
if (i == MAX_ADC_GROUP_CNT)
index = i - 2;
y2 = adc_vbat_def_pair[index+1].vbat;
y1 = adc_vbat_def_pair[index].vbat;
x2 = adc_vbat_def_pair[index+1].adc;
x1 = adc_vbat_def_pair[index].adc;
return (((y2-y1)/(x2-x1)) * adc_value)
+ y1 - (short)(((y2-y1)/(x2-x1))*x1);
}
#if 0
static int convert_adc_to_tbat(int adc_value)
{
int result;
int adc_code_delta = rtp_adc_code2 - rtp_adc_code1;
if (rtp_adc_code1 == 0 && rtp_adc_code2 == 0)
return 0;
result = 1600 + adc_value * 1200 / adc_code_delta
- rtp_adc_code2 * 1200 / adc_code_delta;
return result;
}
#endif
static void pm803_update_adc_status(int *rtp_val, int *rtn_val)
{
u32 val1;
int ret;
val1 = readl(APB_VIRT_BASE + ADC_REG0);
#ifdef CONFIG_MRVL_MMP_MODEM
if (!cp_is_synced) {
#else
if (0) {
#endif
if (val1 == 0) {
mutex_unlock(&adc_mutex);
msleep(ADC_READ_WAIT_MS);
mutex_lock(&adc_mutex);
return;
} else if ((val1 >> 16) != last_sample_count) {
val1 = readl(APB_VIRT_BASE + ADC_REG0);
last_sample_count = (val1 >> 16);
rtn_array[rtx_index % NR_RTX_ARRAY_SIZE] = readl(APB_VIRT_BASE + ADC_REG0) & 0xffff;
rtp_array[rtx_index % NR_RTX_ARRAY_SIZE] = readl(APB_VIRT_BASE + ADC_REG1) & 0xffff;
ADC_DBG("rtn_adc_val:%d, rtp_adc_val:%d, sample:%d\n",
rtn_array[rtx_index % NR_RTX_ARRAY_SIZE],
rtp_array[rtx_index % NR_RTX_ARRAY_SIZE],
last_sample_count);
rtx_index++;
mutex_unlock(&adc_mutex);
msleep(ADC_READ_WAIT_MS);
mutex_lock(&adc_mutex);
} else {
mutex_unlock(&adc_mutex);
msleep(ADC_READ_WAIT_MS);
mutex_lock(&adc_mutex);
}
} else {
if ((val1 >> 16) == last_sample_count) {
#ifdef CONFIG_MRVL_MMP_MODEM
if (!cp_is_synced)
return;
#else
return;
#endif
ret = acipc_notify_cp_read_adc();
if (ret < 0) {
ADC_ERR("modem adc is not working\n");
return;
}
mutex_unlock(&adc_mutex);
/* read it again */
msleep(ADC_READ_WAIT_MS);
mutex_lock(&adc_mutex);
val1 = readl(APB_VIRT_BASE + ADC_REG0);
if ((val1 >> 16) == last_sample_count)
ADC_ERR("sample count err: 0x%x\n", val1);
last_sample_count = (val1 >> 16);
rtn_array[rtx_index % NR_RTX_ARRAY_SIZE] = readl(APB_VIRT_BASE + ADC_REG0) & 0xffff;
rtp_array[rtx_index % NR_RTX_ARRAY_SIZE] = readl(APB_VIRT_BASE + ADC_REG1) & 0xffff;
ADC_DBG("rtn_adc_val:%d, rtp_adc_val:%d, sample:%d\n",
rtn_array[rtx_index % NR_RTX_ARRAY_SIZE],
rtp_array[rtx_index % NR_RTX_ARRAY_SIZE],
last_sample_count);
rtx_index++;
} else {
val1 = readl(APB_VIRT_BASE + ADC_REG0);
last_sample_count = (val1 >> 16);
rtn_array[rtx_index % NR_RTX_ARRAY_SIZE] = readl(APB_VIRT_BASE + ADC_REG0) & 0xffff;
rtp_array[rtx_index % NR_RTX_ARRAY_SIZE] = readl(APB_VIRT_BASE + ADC_REG1) & 0xffff;
ADC_DBG("rtn_adc_val:%d, rtp_adc_val:%d, sample:%d\n",
rtn_array[rtx_index % NR_RTX_ARRAY_SIZE],
rtp_array[rtx_index % NR_RTX_ARRAY_SIZE],
last_sample_count);
rtx_index++;
mutex_unlock(&adc_mutex);
msleep(ADC_READ_WAIT_MS);
mutex_lock(&adc_mutex);
}
}
}
static void pm803_adc_work(struct work_struct *work)
{
mutex_lock(&adc_mutex);
pm803_update_adc_status(&rtp_adc_val, &rtn_adc_val);
mutex_unlock(&adc_mutex);
if (!should_stop)
schedule_delayed_work(&adc_update_work,
ADC_READ_INTERVAL);
}
#ifdef ADC_DO_AVERAGE
static void data_swap(u32 *data1, u32 *data2)
{
u32 temp;
temp = *data2;
*data2 = *data1;
*data1 = temp;
}
static void bubble_sort(u32 *data_array, const u32 data_num)
{
int i, j;
for( i = 0; i < data_num - 1; i++)
for( j = 0; j < data_num - 1 - i; j++)
if(data_array[j] > data_array[j + 1])
data_swap(&data_array[j], &data_array[j + 1]);
}
#endif
int pm803_get_adc_mvolt(int adc_id, int *value)
{
#ifdef ADC_DO_AVERAGE
u32 i;
int total_mvolt = 0;
static u32 tmp_rtx_array[NR_RTX_ARRAY_SIZE];
#endif
might_sleep();
mutex_lock(&adc_mutex);
if (rtx_index == 0) {
*value = 0;
mutex_unlock(&adc_mutex);
return -ENODEV;
} else if (rtx_index < NR_RTX_ARRAY_SIZE) {
if (adc_id == PM800_GPADC0)
*value = convert_adc_to_vbat(rtn_array[rtx_index - 1],
adc0_vbat_def_pair);
else
*value = convert_adc_to_vbat(rtp_array[rtx_index - 1],
adc1_vbat_def_pair);
if ((*value) < 0) {
*value = 0;
mutex_unlock(&adc_mutex);
return -EINVAL;
}
mutex_unlock(&adc_mutex);
return 0;
}
#ifndef ADC_DO_AVERAGE
if (adc_id == PM800_GPADC0)
rtn_adc_val = rtn_array[(rtx_index - 1) % NR_RTX_ARRAY_SIZE];
else
rtp_adc_val = rtp_array[(rtx_index - 1) % NR_RTX_ARRAY_SIZE];
mutex_unlock(&adc_mutex);
#else
if (adc_id == PM800_GPADC0)
memcpy(tmp_rtx_array, rtn_array, sizeof(rtn_array));
else
memcpy(tmp_rtx_array, rtp_array, sizeof(rtp_array));
mutex_unlock(&adc_mutex);
bubble_sort(tmp_rtx_array, NR_RTX_ARRAY_SIZE);
ADC_DBG("ADC RTX: %d %d %d %d %d %d %d %d\n",
tmp_rtx_array[0],
tmp_rtx_array[1],
tmp_rtx_array[2],
tmp_rtx_array[3],
tmp_rtx_array[4],
tmp_rtx_array[5],
tmp_rtx_array[6],
tmp_rtx_array[7]);
/* remove the 2/2 smallest/largest items */
for (i = 2; i < (NR_RTX_ARRAY_SIZE - 2); i++)
total_mvolt += tmp_rtx_array[i];
if (adc_id == PM800_GPADC0)
rtn_adc_val = total_mvolt / (NR_RTX_ARRAY_SIZE - 4);
else
rtp_adc_val = total_mvolt / (NR_RTX_ARRAY_SIZE - 4);
#endif
if (adc_id == PM800_GPADC0) {
*value = convert_adc_to_vbat(rtn_adc_val,
adc0_vbat_def_pair);
if ((*value) <= 0) {
ADC_ERR("ADC error val %d, rtn_adc_val: 0x%x, rtp_adc_val: 0x%x\n",
(*value), rtn_adc_val, rtp_adc_val);
*value = 0;
return -EINVAL;
}
} else {
*value = convert_adc_to_vbat(rtp_adc_val,
adc1_vbat_def_pair);
if ((*value) <= 0) {
ADC_ERR("ADC error val %d, rtp_adc_code1: 0x%x, rtp_adc_code2: 0x%x\n",
*value, rtp_adc_code1, rtp_adc_code2);
*value = 0;
return -EINVAL;
}
}
return 0;
}
int pm803_adc_init(void)
{
static bool pm803_adc_inited = false;
if (pm803_adc_inited) {
BUG();
return 0;
}
INIT_DEFERRABLE_WORK(&adc_update_work, pm803_adc_work);
schedule_delayed_work(&adc_update_work, 0);
update_vbat_caldata();
#if 0 /* bankup solution to get calibration data from fuse */
get_rtp_caldata(&rtp_adc_code1, &rtp_adc_code2);
if (rtp_adc_code1 == 0 && rtp_adc_code2 == 0)
WARN(1, "ADC fuse is NULL");
#endif
pm803_adc_inited = true;
return 0;
}
int pm803_adc_deinit(void)
{
should_stop = 1;
cancel_delayed_work_sync(&adc_update_work);
return 0;
}