diff options
Diffstat (limited to 'drivers/platform/sprd/adc.c')
| -rw-r--r-- | drivers/platform/sprd/adc.c | 1046 |
1 files changed, 1046 insertions, 0 deletions
diff --git a/drivers/platform/sprd/adc.c b/drivers/platform/sprd/adc.c new file mode 100644 index 00000000..f10bb8f7 --- /dev/null +++ b/drivers/platform/sprd/adc.c @@ -0,0 +1,1046 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * Author: steve.zhan <steve.zhan@spreadtrum.com> + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/irqflags.h> +#include <linux/delay.h> +#include <linux/hwspinlock.h> +#include <linux/slab.h> +#include <linux/sort.h> +#include <linux/miscdevice.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_address.h> + +#include <soc/sprd/sci_glb_regs.h> +#include <soc/sprd/arch_lock.h> +#include <soc/sprd/adi.h> +#include <soc/sprd/adc.h> + +static void __iomem *io_base; /* Mapped base address */ + +#define adc_write(val, reg) \ +do { \ + sci_adi_raw_write((unsigned long)reg, val); \ +} while (0) + +static unsigned adc_read(unsigned long addr) +{ + return (unsigned)sci_adi_read(addr); +} + +/*FIXME: If we have not hwspinlock , we need use spinlock to do it */ +#define sci_adc_lock() \ +do { \ + WARN_ON(IS_ERR_VALUE(hwspin_lock_timeout_irqsave(arch_get_hwlock(HWLOCK_ADC), -1, &flags))); \ +} while(0) + +#define sci_adc_unlock() \ +do { \ + hwspin_unlock_irqrestore(arch_get_hwlock(HWLOCK_ADC), &flags); \ +} while(0) + + +#define ADC_CTL (0x00) +#define ADC_SW_CH_CFG (0x04) +#define ADC_FAST_HW_CHX_CFG(_X_) ((_X_) * 0x4 + 0x8) +#define ADC_SLOW_HW_CHX_CFG(_X_) ((_X_) * 0x4 + 0x28) +#define ADC_HW_CH_DELAY (0x48) + +#define ADC_DAT (0x4c) +#define adc_get_data(_SAMPLE_BITS_) (adc_read((unsigned long)(io_base + ADC_DAT)) & (_SAMPLE_BITS_)) + +#define ADC_IRQ_EN (0x50) +#define adc_enable_irq(_X_) \ +do { \ + adc_write(((_X_) & 0x1), (unsigned long)(io_base + ADC_IRQ_EN)); \ +} while(0) + +#define ADC_IRQ_CLR (0x54) +#define adc_clear_irq() \ +do { \ + adc_write(0x1, (unsigned long)(io_base + ADC_IRQ_CLR)); \ +} while (0) + +#define ADC_IRQ_STS (0x58) +#define adc_mask_irqstatus() adc_read((unsigned long)(io_base + ADC_IRQ_STS)) + +#define ADC_IRQ_RAW (0x5c) +#define adc_raw_irqstatus() adc_read((unsigned long)(io_base + ADC_IRQ_RAW)) + +#define ADC_DEBUG (0x60) + +/*ADC_CTL */ +#define ADC_MAX_SAMPLE_NUM (0x10) +#define BIT_SW_CH_RUN_NUM(_X_) ((((_X_) - 1) & 0xf ) << 4) +#define BIT_ADC_BIT_MODE(_X_) (((_X_) & 0x1) << 2) /*0: adc in 10bits mode, 1: adc in 12bits mode */ +#define BIT_ADC_BIT_MODE_MASK (BIT_ADC_BIT_MODE(1)) +#define BIT_SW_CH_ON ( BIT(1) ) /*WO*/ +#define BIT_ADC_EN ( BIT(0) ) + +/*ADC_SW_CH_CFG && ADC_FAST(SLOW)_HW_CHX_CFG*/ +#define BIT_CH_IN_MODE(_X_) (((_X_) & 0x1) << 8) /*0: resistance path, 1: capacitance path */ +#define BIT_CH_SLOW(_X_) (((_X_) & 0x1) << 6) /*0: quick mode, 1: slow mode */ +#define BIT_CH_SCALE(_X_) (((_X_) & 0x1) << 5) /*0: little scale, 1: big scale */ +#define BIT_CH_ID(_X_) ((_X_) & 0x1f) + +/*ADC_FAST(SLOW)_HW_CHX_CFG*/ +#define BIT_CH_DLY_EN(_X_) (((_X_) & 0x1) << 7) /*0:disable, 1:enable */ + +/*ADC_HW_CH_DELAY*/ +#define BIT_HW_CH_DELAY(_X_) ((_X_) & 0xff) /*its unit is ADC clock */ + +#if defined(CONFIG_ARCH_SC8825) +#define BIT_ADC_EB ( BIT(5) ) +#endif + +#define SPRDBAT_AUXADC_CAL_NO 0 +#define SPRDBAT_AUXADC_CAL_NV 1 +#define SPRDBAT_AUXADC_CAL_CHIP 2 +int cal_type = SPRDBAT_AUXADC_CAL_NO; +static short adc_cali_data[2][2] = { + { 4200 , 3310 }, + { 3600 , 2832 }, +}; + +struct sprd_adc_data{ + struct miscdevice misc_dev; + struct mutex lock; + unsigned short channel; + unsigned char scale; + unsigned int voltage_ratio; +}; + +#ifdef CONFIG_OTP_SPRD +extern int sci_efuse_calibration_get(unsigned int *p_cal_data); +#endif + +static int __init adc_cali_fuse_proc(void); + +static ssize_t sprd_adc_store(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count); +static ssize_t sprd_adc_show(struct device *dev, + struct device_attribute *dev_attr, + char *buf); +static ssize_t sprd_cal_type_show(struct device *dev, + struct device_attribute *dev_attr, + char *buf); + +static struct device_attribute sprd_adc_attr[] = { + __ATTR(adc_channel, S_IRUGO | S_IWUSR | S_IWGRP, sprd_adc_show, sprd_adc_store), + __ATTR(adc_scale, S_IRUGO | S_IWUSR | S_IWGRP, sprd_adc_show, sprd_adc_store), + __ATTR(adc_voltage_ratio, S_IRUGO, sprd_adc_show, NULL), + __ATTR(adc_data_raw, S_IRUGO, sprd_adc_show, NULL), + __ATTR(adc_cal_type, S_IRUGO, sprd_cal_type_show, NULL), +}; + +#define SPRD_MODULE_NAME "sprd-adc" + +#define DIV_ROUND(n, d) (((n) + ((d)/2)) / (d)) +#define MEASURE_TIMES ( 15 ) +#define ADC_DROP_CNT ( DIV_ROUND(MEASURE_TIMES, 5) ) + +static int average_int(int a[], int len) +{ + int i, sum = 0; + for (i = 0; i < len; i++) + sum += a[i]; + return DIV_ROUND(sum, len); +} + +static int compare_val(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +static void dump_adc_data(uint32_t* p_adc_val, int len) +{ + int i = 0; + + printk("dump adc data: "); + for (i = 0; i < len; i++) { + printk("%d ", p_adc_val[i]); + } + printk("\n"); +} + +/* +* Get the raw data of adc channel +* @ channel: adc channel id +* @ scale: adc scale +* +* return 0 if failed +* +*/ +int sci_get_adc_data(unsigned int channel, unsigned int scale) +{ + uint32_t adc_val[MEASURE_TIMES] = {0}, adc_res = 0; + struct adc_sample_data adc_sample = { + .channel_id = channel, + .channel_type = 0, /* sw */ + .hw_channel_delay = 0, + .scale = scale, /* 1: big scale, 0: small scale */ + .pbuf = &adc_val[0], + .sample_num = MEASURE_TIMES, + .sample_bits = 1, /* 12 bit*/ + .sample_speed = 0, /* quick mode */ + .signal_mode = 0, /* resistance path */ + }; + + if (0 != sci_adc_get_values(&adc_sample)) { + pr_err("sprd_adc error occured in function %s\n", __func__); + sci_adc_dump_register(); + //BUG_ON(); + return 0; + } + + dump_adc_data(adc_val, ARRAY_SIZE(adc_val)); + + sort(adc_val, ARRAY_SIZE(adc_val), sizeof(uint32_t), compare_val, 0); + + adc_res = average_int(&adc_val[ADC_DROP_CNT], MEASURE_TIMES - ADC_DROP_CNT * 2); + + return (int)adc_res; +} + +EXPORT_SYMBOL(sci_get_adc_data); + +/* +* The value of adc device node is set to kernel space +* +* Example: +* $ adb root && adb shell +* $ echo 5 > /sys/class/misc/adc_channel # set current adc channel to 5 (vbat channel) +* $ echo 1 > /sys/class/misc/adc_scale #set adc to big scale +*/ +static ssize_t sprd_adc_store(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct sprd_adc_data *adc_data = dev_get_drvdata(dev); + //const ptrdiff_t offset = dev_attr - sprd_adc_attr; + //unsigned long tmp = simple_strtoul(buf, NULL, 10); + unsigned int len = 0, tmp_data = 0; + + if ((NULL == adc_data) || (NULL == buf)) + return -EIO; + + len = sscanf(buf, "%d", &tmp_data); + + if (0 == len) + return -EIO; + + pr_info("%s line: %d, attr name(%s), tmp_data %d, count %d, len %d\n", __func__, __LINE__, + dev_attr->attr.name, tmp_data, (int)count, len); + + mutex_lock(&adc_data->lock); + + if (0 == strcmp(dev_attr->attr.name, "adc_channel")) { + WARN_ON(tmp_data > BIT_CH_ID(-1)); + + adc_data->channel = (unsigned short)tmp_data; + } else if (0 == strcmp(dev_attr->attr.name, "adc_scale")) { + adc_data->scale = (unsigned char)tmp_data; + } + + mutex_unlock(&adc_data->lock); + + return count; +} + +/* +* The value of adc device node is showed to user space +* +* Example: +* $ adb root && adb shell +* $ cat /sys/class/misc/adc_channel # show current adc channel number +* $ cat /sys/class/misc/adc_scale #show adc scale value +* $ cat /sys/class/misc/adc_data_raw #show adc raw data +*/ +static ssize_t sprd_adc_show(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct sprd_adc_data *adc_data = dev_get_drvdata(dev); + int len = 0, adc_value = 0; + + if ((NULL == adc_data) || (NULL == buf)) + return -EIO; + + pr_info("%s line: %d, attr name(%s), channel(%d), scale(%d)\n", __func__, __LINE__, + dev_attr->attr.name, adc_data->channel, adc_data->scale); + + mutex_lock(&adc_data->lock); + + if (0 == strcmp(dev_attr->attr.name, "adc_channel")) { + len += sprintf(buf, "%d\n", adc_data->channel); + } else if (0 == strcmp(dev_attr->attr.name, "adc_scale")) { + len += sprintf(buf, "%d\n", adc_data->scale); + } else if (0 == strcmp(dev_attr->attr.name, "adc_voltage_ratio")) { + unsigned int div_num = 0, div_den = 0; + + sci_adc_get_vol_ratio(adc_data->channel, adc_data->scale, &div_num, &div_den); + adc_data->voltage_ratio = (div_num << 16) | (div_den & 0xFFFF); + len += sprintf(buf, "%d\n", adc_data->voltage_ratio); + } else if (0 == strcmp(dev_attr->attr.name, "adc_data_raw")) { + adc_value = sci_get_adc_data(adc_data->channel, adc_data->scale); + if (adc_value < 0) + adc_value = 0; + len += scnprintf(buf + len, PAGE_SIZE - len, "%d\n", adc_value); + + pr_info("value: %d, len: %d\n", adc_value, len); + } + + mutex_unlock(&adc_data->lock); + + return len; +} +static ssize_t sprd_cal_type_show(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + return sprintf(buf, "%d", cal_type); +} + +static int sprd_device_delete_attributes(struct device *dev, + struct device_attribute *dev_attrs, int len) +{ + int i = 0; + + for (i = 0; i < len; i++) { + device_remove_file(dev, &dev_attrs[i]); + } + + return 0; +} + +static int sprd_device_create_attributes(struct device *dev, + struct device_attribute *dev_attrs, int len) +{ + int i = 0, rc = 0; + + for (i = 0; i < len; i++) { + rc = device_create_file(dev, &dev_attrs[i]); + if (rc) + break; + } + + if (rc) { + for (; i >= 0 ; --i) + device_remove_file(dev, &dev_attrs[i]); + } + + return rc; +} + +#ifndef CONFIG_OF +static int __init sprd_adc_dev_register(void) +{ + static struct platform_device sprdadc_device = { + .name = SPRD_MODULE_NAME, + .id = -1, + }; + + return platform_device_register(&sprdadc_device); +} +#endif + +static int sprd_adc_probe(struct platform_device *pdev) +{ + struct sprd_adc_data *adc_data = NULL; + int rc = 0; + + pr_info("%s()->Line: %d\n", __func__, __LINE__); + + adc_data = devm_kzalloc(&pdev->dev, sizeof(struct sprd_adc_data), GFP_KERNEL); + if (!adc_data) { + pr_err("%s failed to kzalloc sprd_adc_data!\n", __func__); + return -ENOMEM; + } + + adc_data->channel = 5; + adc_data->misc_dev.minor = MISC_DYNAMIC_MINOR; + adc_data->misc_dev.name = SPRD_MODULE_NAME; //pdev->dev.driver->name + adc_data->misc_dev.fops = NULL; + adc_data->misc_dev.parent = NULL; + + dev_set_drvdata(&pdev->dev, adc_data); + + mutex_init(&adc_data->lock); + + rc = misc_register(&adc_data->misc_dev); + if (rc) { + pr_err("%s failed to register misc device.\n", __func__); + return rc; + } + + rc = sprd_device_create_attributes(adc_data->misc_dev.this_device, sprd_adc_attr, ARRAY_SIZE(sprd_adc_attr)); + if (rc) { + pr_err("%s failed to create device attributes.\n", __func__); + return rc; + } + + return rc; +} + +static int sprd_adc_remove(struct platform_device *pdev) +{ + struct sprd_adc_data *adc_data = platform_get_drvdata(pdev); + int rc = 0; + + sprd_device_delete_attributes(adc_data->misc_dev.this_device, sprd_adc_attr, ARRAY_SIZE(sprd_adc_attr)); + + rc = misc_deregister(&adc_data->misc_dev); + if (rc) { + pr_err("%s failed to unregister misc device.\n", __func__); + } + + kfree(adc_data); + + return rc; +} + + +#ifdef CONFIG_OF +static struct of_device_id sprd_adc_of_match[] = { + { .compatible = "sprd,sprd-adc", }, + { } +}; +#endif + +static struct platform_driver sprd_adc_driver = { + .probe = sprd_adc_probe, + .remove = sprd_adc_remove, + .driver = { + .name = SPRD_MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sprd_adc_of_match), + }, +}; + +static int __init sprd_adc_driver_init(void) +{ + return platform_driver_register(&sprd_adc_driver); +} + +static void __exit sprd_adc_driver_exit(void) +{ + platform_driver_unregister(&sprd_adc_driver); +} + +module_init(sprd_adc_driver_init); +module_exit(sprd_adc_driver_exit); + + +static void sci_adc_enable(void) +{ +#if defined(CONFIG_ARCH_SC8825) + /* enable adc */ + sci_adi_set(ANA_REG_GLB_ANA_APB_CLK_EN, + BIT_ANA_ADC_EB | BIT_ANA_CLK_AUXADC_EN | + BIT_ANA_CLK_AUXAD_EN); +#elif defined(CONFIG_ARCH_SCX35) + sci_adi_set(ANA_REG_GLB_ARM_MODULE_EN, + BIT_ANA_ADC_EN); + sci_adi_set(ANA_REG_GLB_ARM_CLK_EN, + BIT_CLK_AUXADC_EN | BIT_CLK_AUXAD_EN); + sci_adi_set(ANA_REG_GLB_XTL_WAIT_CTRL, + BIT_XTL_EN); + sci_glb_set(REG_AON_APB_SINDRV_CTRL, BIT_SINDRV_ENA); //maybe need to modify it in later +#endif +} + +void sci_adc_dump_register() +{ + unsigned long _base = (unsigned long)(io_base); + unsigned long _end = _base + 0x64; + + printk("sci_adc_dump_register begin\n"); + for (; _base < _end; _base += 4) { + printk("_base = 0x%lx, value = 0x%x\n", _base, adc_read(_base)); + } + printk("sci_adc_dump_register end\n"); +} + +EXPORT_SYMBOL(sci_adc_dump_register); + +void sci_adc_init(void) +{ + io_base = (void __iomem *)REGS_ADC_BASE; + +#ifndef CONFIG_OF + sprd_adc_dev_register(); +#endif + + adc_enable_irq(0); + adc_clear_irq(); + sci_adc_enable(); + if (cal_type == SPRDBAT_AUXADC_CAL_NO) { + if (!adc_cali_fuse_proc()) + cal_type = SPRDBAT_AUXADC_CAL_CHIP; + } +} +EXPORT_SYMBOL(sci_adc_init); + +/* +* Notes: for hw channel, config its hardware configuration register and using sw channel to read it. +*/ +static int sci_adc_config(struct adc_sample_data *adc) +{ + unsigned long addr = 0; + unsigned val = 0; + int ret = 0; + + BUG_ON(!adc); + BUG_ON(adc->channel_id > ADC_MAX); + + val = BIT_CH_IN_MODE(adc->signal_mode); + val |= BIT_CH_SLOW(adc->sample_speed); + val |= BIT_CH_SCALE(adc->scale); + val |= BIT_CH_ID(adc->channel_id); + val |= BIT_CH_DLY_EN(adc->hw_channel_delay ? 1 : 0); + + adc_write(val, (unsigned long)(io_base + ADC_SW_CH_CFG)); + + if (adc->channel_type > 0) { /*hardware */ + adc_write(BIT_HW_CH_DELAY(adc->hw_channel_delay), + (unsigned long)(io_base + ADC_HW_CH_DELAY)); + + if (adc->channel_type == 1) { /*slow */ + addr = (unsigned long)(io_base + ADC_SLOW_HW_CHX_CFG(adc->channel_id)); + } else { + addr = (unsigned long)(io_base + ADC_FAST_HW_CHX_CFG(adc->channel_id)); + } + adc_write(val, addr); + } + + return ret; +} + +#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S) +#define RATIO(_n_, _d_) (_n_ << 16 | _d_) +static int sci_adc_ratio(int channel, int scale, int mux) +{ + switch (channel) { + case ADC_CHANNEL_0: + return RATIO(1, 1); + case ADC_CHANNEL_1: + case ADC_CHANNEL_2: + case ADC_CHANNEL_3: + return (scale ? RATIO(400, 1025) : RATIO(1, 1)); + case ADC_CHANNEL_VBAT: //Vbat + case ADC_CHANNEL_ISENSE: + return RATIO(7, 29); + case ADC_CHANNEL_VCHGSEN: + return RATIO(77, 1024); + case ADC_CHANNEL_DCDCCORE: //dcdc core/arm/mem/gen/rf/con/wpa + mux = mux >> 13; + switch (mux) { + case 1: //dcdcarm + case 2: //dcdccore + return (scale ? RATIO(36, 55) : RATIO(9, 11)); + case 3: //dcdcmem + case 5: //dcdcrf + return (scale ? RATIO(12, 25) : RATIO(3, 5)); + case 4: //dcdcgen + return (scale ? RATIO(3, 10) : RATIO(3, 8)); + case 6: //dcdccon + return (scale ? RATIO(9, 20) : RATIO(9, 16)); + case 7: //dcdwpa + return (scale ? RATIO(12, 55) : RATIO(3, 11)); + default: + return RATIO(1, 1); + } + case 0xE: //LP dcxo + return (scale ? RATIO(4, 5) : RATIO(1, 1)); + case 0x14: //headmic + return RATIO(1, 3); + + case ADC_CHANNEL_LDO0: //dcdc supply LDO, vdd18/vddcamd/vddcamio/vddrf0/vddgen1/vddgen0 + return RATIO(1, 2); + case ADC_CHANNEL_VBATBK: //VbatBK + case ADC_CHANNEL_LDO1: //VbatD Domain LDO, vdd25/vddcama/vddsim2/vddsim1/vddsim0 + case ADC_CHANNEL_LDO2: //VbatA Domain LDO, vddwifipa/vddcammot/vddemmccore/vdddcxo/vddsdcore/vdd28 + case ADC_CHANNEL_WHTLED: //kpled/vibr + case ADC_CHANNEL_WHTLED_VFB://vddsdio/vddusb/vddfgu + case ADC_CHANNEL_USBDP: //DP from terminal + case ADC_CHANNEL_USBDM: //DM from terminal + return RATIO(1, 3); + + default: + return RATIO(1, 1); + } + return RATIO(1, 1); +} + +void sci_adc_get_vol_ratio(unsigned int channel_id, int scale, unsigned int *div_numerators, + unsigned int *div_denominators) +{ + unsigned int ratio = sci_adc_ratio(channel_id, scale, 0); + *div_numerators = ratio >> 16; + *div_denominators = ratio << 16 >> 16; +} + +unsigned int sci_adc_get_ratio(unsigned int channel_id, int scale, int mux) +{ + unsigned int ratio = (unsigned int)sci_adc_ratio(channel_id, scale, mux); + + return ratio; +} + +#elif (defined(CONFIG_ARCH_SCX15) || defined(CONFIG_ADIE_SC2711)) +#define RATIO(_n_, _d_) (_n_ << 16 | _d_) +static int sci_adc_ratio(int channel, int scale, int mux) +{ + switch (channel) { + case ADC_CHANNEL_0: + case ADC_CHANNEL_1: + case ADC_CHANNEL_2: + case ADC_CHANNEL_3: + return (scale ? RATIO(400, 1025) : RATIO(1, 1)); + case ADC_CHANNEL_VBAT: //vbat + case ADC_CHANNEL_ISENSE: + return RATIO(7, 29); + case ADC_CHANNEL_VCHGSEN: + return RATIO(77, 1024); + case ADC_CHANNEL_DCDCCORE: //dcdccore + case ADC_CHANNEL_DCDCARM: //dcdcarm + return (scale ? RATIO(4, 5) : RATIO(1, 1)); + case ADC_CHANNEL_DCDCMEM: //dcdcmem + return (scale ? RATIO(3, 5) : RATIO(4, 5)); + case ADC_CHANNEL_DCDCLDO: //dcdcgen + return RATIO(4, 9); + case 0x14 /* ADC_CHANNEL_HEADMIC */: //DCDCHEADMIC + return RATIO(1, 3); + case ADC_CHANNEL_LDO0: //DCDC Supply LDO, VDD18/CAMIO/CAMD/EMMCIO + return RATIO(1, 2); + case ADC_CHANNEL_VBATBK: //DCDCVBATBK + case ADC_CHANNEL_LDO1: //VBATD Domain LDO, VDD25/SD/USB/SIM0/SIM1/SIM2 + case ADC_CHANNEL_LDO2: //VBATA Domain LDO, VDD28/CAMA/CAMMOT/EMMCCORE/CON/DCXO/RF0 + case ADC_CHANNEL_USBDP: //DP from terminal + case ADC_CHANNEL_USBDM: //DM from terminal + return RATIO(1, 3); + + default: + return RATIO(1, 1); + } + return RATIO(1, 1); +} + +void sci_adc_get_vol_ratio(unsigned int channel_id, int scale, unsigned int *div_numerators, + unsigned int *div_denominators) +{ + unsigned int ratio = sci_adc_ratio(channel_id, scale, 0); + *div_numerators = ratio >> 16; + *div_denominators = ratio << 16 >> 16; +} + +#else +void sci_adc_get_vol_ratio(unsigned int channel_id, int scale, unsigned int *div_numerators, + unsigned int *div_denominators) +{ + unsigned int chip_id = 0; + + switch (channel_id) { + + case ADC_CHANNEL_0: + case ADC_CHANNEL_1: + case ADC_CHANNEL_2: + case ADC_CHANNEL_3: + if (scale) { + *div_numerators = 16; + *div_denominators = 41; + } else { + *div_numerators = 1; + *div_denominators = 1; + } + return; + case ADC_CHANNEL_PROG: //channel 4 + case ADC_CHANNEL_VCHGBG: //channel 7 + case ADC_CHANNEL_HEADMIC: //18 + *div_numerators = 1; + *div_denominators = 1; + return; + case ADC_CHANNEL_VBAT: //channel 5 + case ADC_CHANNEL_ISENSE: //channel 8 +#if defined(CONFIG_ARCH_SCX35) + *div_numerators = 7; + *div_denominators = 29; +#else + chip_id = sci_adi_read(CHIP_ID_LOW_REG); +#ifdef CHIP_ID_HIGH_REG + chip_id |= (sci_adi_read(CHIP_ID_HIGH_REG) << 16); +#endif + if (chip_id == 0x8820A001) { //metalfix + *div_numerators = 247; + *div_denominators = 1024; + } else { + *div_numerators = 266; + *div_denominators = 1000; + } +#endif + return; + case ADC_CHANNEL_VCHGSEN: //channel 6 + *div_numerators = 77; + *div_denominators = 1024; + return; + case ADC_CHANNEL_TPYD: //channel 9 + case ADC_CHANNEL_TPYU: //channel 10 + case ADC_CHANNEL_TPXR: //channel 11 + case ADC_CHANNEL_TPXL: //channel 12 + if (scale) { //larger + *div_numerators = 2; + *div_denominators = 5; + } else { + *div_numerators = 3; + *div_denominators = 5; + } + return; + case ADC_CHANNEL_DCDCCORE: //channel 13 + case ADC_CHANNEL_DCDCARM: //channel 14 + if (scale) { //lager + *div_numerators = 4; + *div_denominators = 5; + } else { + *div_numerators = 1; + *div_denominators = 1; + } + return; + case ADC_CHANNEL_DCDCMEM: //channel 15 + if (scale) { //lager + *div_numerators = 3; + *div_denominators = 5; + } else { + *div_numerators = 4; + *div_denominators = 5; + } + return; + case ADC_CHANNEL_DCDCLDO: //16 + *div_numerators = 4; + *div_denominators = 9; + return; + +#if defined(CONFIG_ARCH_SCX35) + case ADC_CHANNEL_VBATBK: + case ADC_CHANNEL_LDO0: + case ADC_CHANNEL_LDO2: + case ADC_CHANNEL_USBDP: + case ADC_CHANNEL_USBDM: + *div_numerators = 1; + *div_denominators = 3; + return; + case ADC_CHANNEL_LDO1: + *div_numerators = 1; + *div_denominators = 2; + return; + case ADC_CHANNEL_DCDCGPU: + if (scale) { + *div_numerators = 4; + *div_denominators = 5; + } else { + *div_numerators = 1; + *div_denominators = 1; + } + return; + case ADC_CHANNEL_DCDCWRF: + if (scale) { + *div_numerators = 1; + *div_denominators = 3; + } else { + *div_numerators = 4; + *div_denominators = 5; + } + return; +#else + case ADC_CHANNEL_VBATBK: //channel 17 + case ADC_CHANNEL_LDO0: //channel 19,20 + case ADC_CHANNEL_LDO1: + *div_numerators = 1; + *div_denominators = 3; + return; + case ADC_CHANNEL_LDO2: //channel 21 + *div_numerators = 1; + *div_denominators = 2; + return; +#endif + default: + *div_numerators = 1; + *div_denominators = 1; + break; + } +} +#endif + +int sci_adc_get_values(struct adc_sample_data *adc) +{ + unsigned long flags; + int cnt = 12; + unsigned long addr = 0; + unsigned val = 0; + int ret = 0; + int num = 0; + int sample_bits_msk = 0; + int *pbuf = 0; + + if (!adc || adc->channel_id > ADC_MAX) + return -EINVAL; + + pbuf = adc->pbuf; + if (!pbuf) + return -EINVAL; + + num = adc->sample_num; + if (num > ADC_MAX_SAMPLE_NUM) + return -EINVAL; + + sci_adc_lock(); + + sci_adc_config(adc); //configs adc sample. + + addr = (unsigned long)(io_base + ADC_CTL); + val = adc_read((unsigned long)addr); + val &= ~(BIT_ADC_EN | BIT_SW_CH_ON | BIT_ADC_BIT_MODE_MASK); + adc_write(val, addr); + + adc_clear_irq(); + + val = BIT_SW_CH_RUN_NUM(num); + val |= BIT_ADC_EN; + val |= BIT_ADC_BIT_MODE(adc->sample_bits); + val |= BIT_SW_CH_ON; + + adc_write(val, addr); + + while ((!adc_raw_irqstatus()) && cnt--) { + udelay(50); + } + + if (!cnt) { + ret = -1; + WARN_ON(1); + goto Exit; + } + + if (adc->sample_bits) + sample_bits_msk = ((1 << 12) - 1); //12 + else + sample_bits_msk = ((1 << 10) - 1); //10 + while (num--) + *pbuf++ = adc_get_data(sample_bits_msk); + +Exit: + val = adc_read((unsigned long)addr); + val &= ~BIT_ADC_EN; + adc_write(val, addr); + + sci_adc_unlock(); + + return ret; +} + +EXPORT_SYMBOL_GPL(sci_adc_get_values); + +static int __average(int a[], int N) +{ +#define __DIV_ROUND(n, d) (((n) + ((d)/2)) / (d)) + int i, sum = 0; + + for (i = 0; i < N; i++) + sum += a[i]; + return __DIV_ROUND(sum, N); +} + +static int sci_adc_set_current(u8 enable, int isen) +{ + if(enable) { + /* BITS_AUXAD_CURRENT_IBS = (isen * 100 / 125 -1) */ + isen = (isen * 100 / 125 -1); + if(isen > BITS_AUXAD_CURRENT_IBS(-1)) + isen = BITS_AUXAD_CURRENT_IBS(-1); + sci_adi_write(ANA_REG_GLB_AUXAD_CTL, (BIT_AUXAD_CURRENTSEN_EN | BITS_AUXAD_CURRENT_IBS(isen)), + BIT_AUXAD_CURRENTSEN_EN | BITS_AUXAD_CURRENT_IBS(-1)); + } else { + sci_adi_clr(ANA_REG_GLB_AUXAD_CTL, BIT_AUXAD_CURRENTSEN_EN); + } + + return 0; +} + +/* + * sci_adc_get_value_by_isen - read adc value by current sense + * @channel: adc software channel id; + * @scale: adc sample scale, 0:little scale, 1:big scale; + * @current: adc current isense(uA), 1.25uA/step, max 40uA; + * + * returns: adc value + */ +int sci_adc_get_value_by_isen(unsigned int channel, int scale, int isen) +{ +#define ADC_MESURE_NUMBER 15 + + struct adc_sample_data adc; + int results[ADC_MESURE_NUMBER + 1] = {0}; + int ret = 0, i = 0; + + /* Fixme: only external adc channel used */ + BUG_ON(channel > 3); + + WARN_ON(isen > 40); + + adc.channel_id = channel; + adc.channel_type = 0; + adc.hw_channel_delay = 0; + adc.pbuf = &results[0]; + adc.sample_bits = 1; + adc.sample_num = ADC_MESURE_NUMBER; + adc.sample_speed = 0; + adc.scale = scale; + adc.signal_mode = 0; + + sci_adc_set_current(1, isen); + if(0 == sci_adc_get_values(&adc)) { + ret = __average(&results[ADC_MESURE_NUMBER/5], + (ADC_MESURE_NUMBER - ADC_MESURE_NUMBER * 2 /5)); + } + sci_adc_set_current(0, 0); + + for(i = 0; i < ARRAY_SIZE(results); i++) { + printk("%d\t", results[i]); + } + printk("\n%s() adc[%d] value: %d\n", __func__, channel, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(sci_adc_get_value_by_isen); + +static int is_valid_adc_cal(void) +{ + return !!(0 != adc_cali_data[0][0]); +} + +static int __init adc_cali_fuse_proc(void) +{ + if (cal_type == SPRDBAT_AUXADC_CAL_NO) { + unsigned int efuse_cal_data[2] = { 0 }; + #ifdef CONFIG_OTP_SPRD + if (sci_efuse_calibration_get(efuse_cal_data)) { + adc_cali_data[0][0] = + efuse_cal_data[0] & 0xffff; + adc_cali_data[0][1] = + (efuse_cal_data[0] >> 16) & 0xffff; + adc_cali_data[1][0] = + efuse_cal_data[1] & 0xffff; + adc_cali_data[1][1] = + (efuse_cal_data[1] >> 16) & 0xffff; + printk("auxadc cal from efuse ok!!!\n"); + printk("efuse_cal_data[0]: 0x%x, efuse_cal_data[1]:0x%x\n", + efuse_cal_data[0], efuse_cal_data[1]); + return 0; + } + #endif + } + return -1; +} + +static int __init adc_cali_proc(char *str) +{ + unsigned int adc_data[2] = { 0 }; + memset(adc_cali_data, 0, ARRAY_SIZE(adc_cali_data)); + + if (str) { + char *cali_data = &str[1]; + sscanf(cali_data, "%d,%d", &adc_data[0], &adc_data[1]); + + adc_cali_data[0][0] = adc_data[0] & 0xFFFF; + adc_cali_data[0][1] = (adc_data[0] >> 16) & 0xFFFF; + adc_cali_data[1][0] = adc_data[1] & 0xFFFF; + adc_cali_data[1][1] = (adc_data[1] >> 16) & 0xFFFF; + + pr_info("%s adc cali data (%d : %d -- %d : %d)\n", __func__, + (int)adc_cali_data[0][0], (int)adc_cali_data[0][1], + (int)adc_cali_data[1][0], (int)adc_cali_data[1][1]); + cal_type = SPRDBAT_AUXADC_CAL_NV; + } + return 1; +} +__setup("adc_cal", adc_cali_proc); + + +static int adc2vbat(int adc_res, int scale) +{ + int t = 0; + + if(!is_valid_adc_cal()) + return 0; + + if (1 || scale) { + t = adc_cali_data[0][0] - adc_cali_data[1][0]; + t *= (adc_res - adc_cali_data[0][1]); + t /= (adc_cali_data[0][1] - adc_cali_data[1][1]); + t += adc_cali_data[0][0]; + } else { + t = adc_cali_data[2][0] - adc_cali_data[3][0]; + t *= (adc_res - adc_cali_data[2][1]); + t /= (adc_cali_data[2][1] - adc_cali_data[3][1]); + t += adc_cali_data[2][0]; + } + + return t; +} + +#if defined(CONFIG_ADIE_SC2723) || defined(CONFIG_ADIE_SC2723S) +/* +* Get adc channel voltage +* @ channel: adc channel id +* @ scale: adc scale +* @ mux: adc calibration +* @ adc_data: adc channel raw data +* +* return 0 if failed +* +*/ +int sci_adc_vol_request(unsigned int channel, int scale, int mux, int* adc_data) +{ + unsigned int bat_numerators; + unsigned int bat_denominators; + unsigned int chan_numerators; + unsigned int chan_denominators; + u32 res; + int chan_adc = 0, chan_vol = 0; + + res = (u32)sci_adc_get_ratio(ADC_CHANNEL_VBAT, 0, 0); + bat_numerators = (res >> 16) & 0xFFFF; + bat_denominators = res & 0xFFFF; + + chan_adc = sci_get_adc_data(channel, scale); + if (chan_adc) { + if(adc_data) + *adc_data = chan_adc; + + chan_vol = adc2vbat(chan_adc, scale); + if(scale) { + res = (u32) sci_adc_get_ratio(channel, scale, mux); + chan_numerators = (res >> 16) & 0xFFFF; + chan_denominators = res & 0xFFFF; + + chan_vol *= (bat_numerators * chan_denominators); + chan_vol /= (bat_denominators * chan_numerators); + } + } + + return chan_vol; +} +EXPORT_SYMBOL_GPL(sci_adc_vol_request); +#endif |
