| /* |
| * 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; |
| } |
| |