| /* |
| * Copyright (c) 2011, Code Aurora Forum. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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. |
| * |
| * Qualcomm's PM8921 ADC Arbiter driver |
| */ |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/mutex.h> |
| #include <linux/module.h> |
| #include <linux/err.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/completion.h> |
| #include <linux/platform_device.h> |
| #include <linux/mfd/pm8xxx/core.h> |
| #include <linux/mfd/pm8xxx/mpp.h> |
| #include <linux/mfd/pm8921-adc.h> |
| #include <linux/debugfs.h> |
| |
| /* User Bank register set */ |
| #define PM8921_ADC_ARB_USRP_CNTRL1 0x197 |
| #define PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB BIT(0) |
| #define PM8921_ADC_ARB_USRP_CNTRL1_RSV1 BIT(1) |
| #define PM8921_ADC_ARB_USRP_CNTRL1_RSV2 BIT(2) |
| #define PM8921_ADC_ARB_USRP_CNTRL1_RSV3 BIT(3) |
| #define PM8921_ADC_ARB_USRP_CNTRL1_RSV4 BIT(4) |
| #define PM8921_ADC_ARB_USRP_CNTRL1_RSV5 BIT(5) |
| #define PM8921_ADC_ARB_USRP_CNTRL1_EOC BIT(6) |
| #define PM8921_ADC_ARB_USRP_CNTRL1_REQ BIT(7) |
| |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL 0x198 |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL_RSV0 BIT(0) |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL_RSV1 BIT(1) |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL_PREMUX0 BIT(2) |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL_PREMUX1 BIT(3) |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL_SEL0 BIT(4) |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL_SEL1 BIT(5) |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL_SEL2 BIT(6) |
| #define PM8921_ADC_ARB_USRP_AMUX_CNTRL_SEL3 BIT(7) |
| |
| #define PM8921_ADC_ARB_USRP_ANA_PARAM 0x199 |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM 0x19A |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 BIT(0) |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 BIT(1) |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM_CLK_RATE0 BIT(2) |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM_CLK_RATE1 BIT(3) |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM_EOC BIT(4) |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 BIT(5) |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1 BIT(6) |
| #define PM8921_ADC_ARB_USRP_DIG_PARAM_EN BIT(7) |
| |
| #define PM8921_ADC_ARB_USRP_RSV 0x19B |
| #define PM8921_ADC_ARB_USRP_RSV_RST BIT(0) |
| #define PM8921_ADC_ARB_USRP_RSV_DTEST0 BIT(1) |
| #define PM8921_ADC_ARB_USRP_RSV_DTEST1 BIT(2) |
| #define PM8921_ADC_ARB_USRP_RSV_OP BIT(3) |
| #define PM8921_ADC_ARB_USRP_RSV_IP_SEL0 BIT(4) |
| #define PM8921_ADC_ARB_USRP_RSV_IP_SEL1 BIT(5) |
| #define PM8921_ADC_ARB_USRP_RSV_IP_SEL2 BIT(6) |
| #define PM8921_ADC_ARB_USRP_RSV_TRM BIT(7) |
| |
| #define PM8921_ADC_ARB_USRP_DATA0 0x19D |
| #define PM8921_ADC_ARB_USRP_DATA1 0x19C |
| |
| #define PM8921_ADC_ARB_BTM_CNTRL1 0x17e |
| #define PM8921_ADC_ARB_BTM_CNTRL1_EN_BTM BIT(0) |
| #define PM8921_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE BIT(1) |
| #define PM8921_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL1 BIT(2) |
| #define PM8921_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL2 BIT(3) |
| #define PM8921_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL3 BIT(4) |
| #define PM8921_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL4 BIT(5) |
| #define PM8921_ADC_ARB_BTM_CNTRL1_EOC BIT(6) |
| #define PM8921_ADC_ARB_BTM_CNTRL1_REQ BIT(7) |
| |
| #define PM8921_ADC_ARB_BTM_CNTRL2 0x18c |
| #define PM8921_ADC_ARB_BTM_AMUX_CNTRL 0x17f |
| #define PM8921_ADC_ARB_BTM_ANA_PARAM 0x180 |
| #define PM8921_ADC_ARB_BTM_DIG_PARAM 0x181 |
| #define PM8921_ADC_ARB_BTM_RSV 0x182 |
| #define PM8921_ADC_ARB_BTM_DATA1 0x183 |
| #define PM8921_ADC_ARB_BTM_DATA0 0x184 |
| #define PM8921_ADC_ARB_BTM_BAT_COOL_THR1 0x185 |
| #define PM8921_ADC_ARB_BTM_BAT_COOL_THR0 0x186 |
| #define PM8921_ADC_ARB_BTM_BAT_WARM_THR1 0x187 |
| #define PM8921_ADC_ARB_BTM_BAT_WARM_THR0 0x188 |
| |
| #define PM8921_ADC_ARB_ANA_DIG 0xa0 |
| #define PM8921_ADC_BTM_RSV 0x10 |
| #define PM8921_ADC_AMUX_MPP_SEL 2 |
| #define PM8921_ADC_AMUX_SEL 4 |
| #define PM8921_ADC_RSV_IP_SEL 4 |
| #define PM8921_ADC_BTM_CHANNEL_SEL 4 |
| #define PM8921_MAX_CHANNEL_PROPERTIES 2 |
| #define PM8921_ADC_IRQ_0 0 |
| #define PM8921_ADC_IRQ_1 1 |
| #define PM8921_ADC_IRQ_2 2 |
| #define PM8921_ADC_BTM_INTERVAL_SEL_MASK 0xF |
| #define PM8921_ADC_BTM_INTERVAL_SEL_SHIFT 2 |
| #define PM8921_ADC_BTM_DECIMATION_SEL 5 |
| #define PM8921_ADC_MUL 10 |
| #define PM8921_ADC_CONV_TIME_MIN 2000 |
| #define PM8921_ADC_CONV_TIME_MAX 2100 |
| |
| struct pm8921_adc { |
| struct device *dev; |
| struct pm8921_adc_properties *adc_prop; |
| int adc_irq; |
| struct mutex adc_lock; |
| spinlock_t btm_lock; |
| uint32_t adc_num_channel; |
| struct completion adc_rslt_completion; |
| struct pm8921_adc_amux *adc_channel; |
| struct pm8921_adc_amux_properties *conv; |
| struct pm8921_adc_arb_btm_param *batt; |
| int btm_warm_irq; |
| int btm_cool_irq; |
| struct dentry *dent; |
| struct work_struct warm_work; |
| struct work_struct cool_work; |
| }; |
| |
| struct pm8921_adc_amux_properties { |
| uint32_t amux_channel; |
| uint32_t decimation; |
| uint32_t amux_ip_rsv; |
| uint32_t amux_mpp_channel; |
| struct pm8921_adc_chan_properties *chan_prop; |
| }; |
| |
| static const struct pm8921_adc_scaling_ratio pm8921_amux_scaling_ratio[] = { |
| {1, 1}, |
| {1, 3}, |
| {1, 4}, |
| {1, 6} |
| }; |
| |
| static struct pm8921_adc *pmic_adc; |
| |
| static struct pm8921_adc_scale_fn adc_scale_fn[] = { |
| [ADC_SCALE_DEFAULT] = {pm8921_adc_scale_default}, |
| [ADC_SCALE_BATT_THERM] = {pm8921_adc_scale_batt_therm}, |
| [ADC_SCALE_PMIC_THERM] = {pm8921_adc_scale_pmic_therm}, |
| [ADC_SCALE_XTERN_CHGR_CUR] = {pm8921_adc_scale_xtern_chgr_cur}, |
| }; |
| |
| static bool pm8921_adc_calib_first_adc; |
| static bool pm8921_adc_initialized, pm8921_adc_calib_device_init; |
| |
| static int32_t pm8921_adc_arb_cntrl(uint32_t arb_cntrl) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| int i, rc; |
| u8 data_arb_cntrl = 0; |
| |
| if (arb_cntrl) |
| data_arb_cntrl |= PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB; |
| |
| /* Write twice to the CNTRL register for the arbiter settings |
| to take into effect */ |
| for (i = 0; i < 2; i++) { |
| rc = pm8xxx_writeb(adc_pmic->dev->parent, |
| PM8921_ADC_ARB_USRP_CNTRL1, data_arb_cntrl); |
| if (rc < 0) { |
| pr_err("PM8921 arb cntrl write failed with %d\n", rc); |
| return rc; |
| } |
| } |
| |
| if (arb_cntrl) { |
| data_arb_cntrl |= PM8921_ADC_ARB_USRP_CNTRL1_REQ; |
| rc = pm8xxx_writeb(adc_pmic->dev->parent, |
| PM8921_ADC_ARB_USRP_CNTRL1, data_arb_cntrl); |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t pm8921_adc_read_reg(uint32_t reg, u8 *data) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| int rc; |
| |
| rc = pm8xxx_readb(adc_pmic->dev->parent, reg, data); |
| if (rc < 0) { |
| pr_err("PM8921 adc read reg %d failed with %d\n", reg, rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t pm8921_adc_write_reg(uint32_t reg, u8 data) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| int rc; |
| |
| rc = pm8xxx_writeb(adc_pmic->dev->parent, reg, data); |
| if (rc < 0) { |
| pr_err("PM8921 adc write reg %d failed with %d\n", reg, rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int32_t pm8921_adc_configure( |
| struct pm8921_adc_amux_properties *chan_prop) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| u8 data_amux_chan = 0, data_arb_rsv = 0, data_dig_param = 0; |
| int rc; |
| |
| data_amux_chan |= chan_prop->amux_channel << PM8921_ADC_AMUX_SEL; |
| |
| if (chan_prop->amux_mpp_channel) |
| data_amux_chan |= chan_prop->amux_mpp_channel << |
| PM8921_ADC_AMUX_MPP_SEL; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_USRP_AMUX_CNTRL, |
| data_amux_chan); |
| if (rc < 0) |
| return rc; |
| |
| data_arb_rsv &= (PM8921_ADC_ARB_USRP_RSV_RST | |
| PM8921_ADC_ARB_USRP_RSV_DTEST0 | |
| PM8921_ADC_ARB_USRP_RSV_DTEST1 | |
| PM8921_ADC_ARB_USRP_RSV_OP | |
| PM8921_ADC_ARB_USRP_RSV_TRM); |
| data_arb_rsv |= chan_prop->amux_ip_rsv << PM8921_ADC_RSV_IP_SEL; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_USRP_RSV, data_arb_rsv); |
| if (rc < 0) |
| return rc; |
| |
| rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_DIG_PARAM, |
| &data_dig_param); |
| if (rc < 0) |
| return rc; |
| |
| /* Default 2.4Mhz clock rate */ |
| /* Client chooses the decimation */ |
| switch (chan_prop->decimation) { |
| case ADC_DECIMATION_TYPE1: |
| data_dig_param |= PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0; |
| break; |
| case ADC_DECIMATION_TYPE2: |
| data_dig_param |= (PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 |
| | PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1); |
| break; |
| default: |
| data_dig_param |= PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0; |
| break; |
| } |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_USRP_DIG_PARAM, |
| PM8921_ADC_ARB_ANA_DIG); |
| if (rc < 0) |
| return rc; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_USRP_ANA_PARAM, |
| PM8921_ADC_ARB_ANA_DIG); |
| if (rc < 0) |
| return rc; |
| |
| if (!pm8921_adc_calib_first_adc) |
| enable_irq(adc_pmic->adc_irq); |
| |
| rc = pm8921_adc_arb_cntrl(1); |
| if (rc < 0) { |
| pr_err("Configuring ADC Arbiter" |
| "enable failed with %d\n", rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static uint32_t pm8921_adc_read_adc_code(int32_t *data) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| uint8_t rslt_lsb, rslt_msb; |
| int32_t rc, max_ideal_adc_code = 1 << adc_pmic->adc_prop->bitresolution; |
| |
| rc = pm8xxx_readb(adc_pmic->dev->parent, |
| PM8921_ADC_ARB_USRP_DATA0, &rslt_lsb); |
| if (rc < 0) { |
| pr_err("PM8921 adc result read failed with %d\n", rc); |
| return rc; |
| } |
| |
| rc = pm8xxx_readb(adc_pmic->dev->parent, |
| PM8921_ADC_ARB_USRP_DATA1, &rslt_msb); |
| if (rc < 0) { |
| pr_err("PM8921 adc result read failed with %d\n", rc); |
| return rc; |
| } |
| |
| *data = (rslt_msb << 8) | rslt_lsb; |
| |
| /* Use the midpoint to determine underflow or overflow */ |
| if (*data > max_ideal_adc_code + (max_ideal_adc_code >> 1)) |
| *data |= ((1 << (8 * sizeof(*data) - |
| adc_pmic->adc_prop->bitresolution)) - 1) << |
| adc_pmic->adc_prop->bitresolution; |
| |
| /* Default value for switching off the arbiter after reading |
| the ADC value. Bit 0 set to 0. */ |
| rc = pm8921_adc_arb_cntrl(0); |
| if (rc < 0) { |
| pr_err("%s: Configuring ADC Arbiter disable" |
| "failed\n", __func__); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static void pm8921_adc_btm_warm_scheduler_fn(struct work_struct *work) |
| { |
| struct pm8921_adc *adc_pmic = container_of(work, struct pm8921_adc, |
| warm_work); |
| unsigned long flags = 0; |
| bool warm_status; |
| |
| spin_lock_irqsave(&adc_pmic->btm_lock, flags); |
| warm_status = irq_read_line(adc_pmic->btm_warm_irq); |
| if (adc_pmic->batt->btm_warm_fn != NULL) |
| adc_pmic->batt->btm_warm_fn(warm_status); |
| spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); |
| } |
| |
| static void pm8921_adc_btm_cool_scheduler_fn(struct work_struct *work) |
| { |
| struct pm8921_adc *adc_pmic = container_of(work, struct pm8921_adc, |
| cool_work); |
| unsigned long flags = 0; |
| bool cool_status; |
| |
| spin_lock_irqsave(&adc_pmic->btm_lock, flags); |
| cool_status = irq_read_line(adc_pmic->btm_cool_irq); |
| if (adc_pmic->batt->btm_cool_fn != NULL) |
| adc_pmic->batt->btm_cool_fn(cool_status); |
| spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); |
| } |
| |
| static irqreturn_t pm8921_adc_isr(int irq, void *dev_id) |
| { |
| struct pm8921_adc *adc_8921 = dev_id; |
| |
| disable_irq_nosync(adc_8921->adc_irq); |
| |
| if (pm8921_adc_calib_first_adc) |
| return IRQ_HANDLED; |
| /* TODO Handle spurius interrupt condition */ |
| complete(&adc_8921->adc_rslt_completion); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t pm8921_btm_warm_isr(int irq, void *dev_id) |
| { |
| struct pm8921_adc *btm_8921 = dev_id; |
| |
| schedule_work(&btm_8921->warm_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t pm8921_btm_cool_isr(int irq, void *dev_id) |
| { |
| struct pm8921_adc *btm_8921 = dev_id; |
| |
| schedule_work(&btm_8921->cool_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static uint32_t pm8921_adc_calib_device(void) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| struct pm8921_adc_amux_properties conv; |
| int rc, offset_adc, slope_adc, calib_read_1, calib_read_2; |
| u8 data_arb_usrp_cntrl1 = 0; |
| |
| conv.amux_channel = CHANNEL_125V; |
| conv.decimation = ADC_DECIMATION_TYPE2; |
| conv.amux_ip_rsv = AMUX_RSV1; |
| conv.amux_mpp_channel = PREMUX_MPP_SCALE_0; |
| pm8921_adc_calib_first_adc = true; |
| rc = pm8921_adc_configure(&conv); |
| if (rc) { |
| pr_err("pm8921_adc configure failed with %d\n", rc); |
| goto calib_fail; |
| } |
| |
| while (data_arb_usrp_cntrl1 != (PM8921_ADC_ARB_USRP_CNTRL1_EOC | |
| PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB)) { |
| rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_CNTRL1, |
| &data_arb_usrp_cntrl1); |
| if (rc < 0) |
| return rc; |
| usleep_range(PM8921_ADC_CONV_TIME_MIN, |
| PM8921_ADC_CONV_TIME_MAX); |
| } |
| data_arb_usrp_cntrl1 = 0; |
| |
| rc = pm8921_adc_read_adc_code(&calib_read_1); |
| if (rc) { |
| pr_err("pm8921_adc read adc failed with %d\n", rc); |
| pm8921_adc_calib_first_adc = false; |
| goto calib_fail; |
| } |
| pm8921_adc_calib_first_adc = false; |
| |
| conv.amux_channel = CHANNEL_625MV; |
| conv.decimation = ADC_DECIMATION_TYPE2; |
| conv.amux_ip_rsv = AMUX_RSV1; |
| conv.amux_mpp_channel = PREMUX_MPP_SCALE_0; |
| pm8921_adc_calib_first_adc = true; |
| rc = pm8921_adc_configure(&conv); |
| if (rc) { |
| pr_err("pm8921_adc configure failed with %d\n", rc); |
| goto calib_fail; |
| } |
| |
| while (data_arb_usrp_cntrl1 != (PM8921_ADC_ARB_USRP_CNTRL1_EOC | |
| PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB)) { |
| rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_CNTRL1, |
| &data_arb_usrp_cntrl1); |
| if (rc < 0) |
| return rc; |
| usleep_range(PM8921_ADC_CONV_TIME_MIN, |
| PM8921_ADC_CONV_TIME_MAX); |
| } |
| data_arb_usrp_cntrl1 = 0; |
| |
| rc = pm8921_adc_read_adc_code(&calib_read_2); |
| if (rc) { |
| pr_err("pm8921_adc read adc failed with %d\n", rc); |
| pm8921_adc_calib_first_adc = false; |
| goto calib_fail; |
| } |
| pm8921_adc_calib_first_adc = false; |
| |
| slope_adc = (((calib_read_1 - calib_read_2) << PM8921_ADC_MUL)/ |
| PM8921_CHANNEL_ADC_625_MV); |
| offset_adc = calib_read_2 - |
| ((slope_adc * PM8921_CHANNEL_ADC_625_MV) >> |
| PM8921_ADC_MUL); |
| |
| adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].offset |
| = offset_adc; |
| adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].dy = |
| (calib_read_1 - calib_read_2); |
| adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].dx |
| = PM8921_CHANNEL_ADC_625_MV; |
| rc = pm8921_adc_arb_cntrl(0); |
| if (rc < 0) { |
| pr_err("%s: Configuring ADC Arbiter disable" |
| "failed\n", __func__); |
| return rc; |
| } |
| /* Ratiometric Calibration */ |
| conv.amux_channel = CHANNEL_MUXOFF; |
| conv.decimation = ADC_DECIMATION_TYPE2; |
| conv.amux_ip_rsv = AMUX_RSV5; |
| conv.amux_mpp_channel = PREMUX_MPP_SCALE_0; |
| pm8921_adc_calib_first_adc = true; |
| rc = pm8921_adc_configure(&conv); |
| if (rc) { |
| pr_err("pm8921_adc configure failed with %d\n", rc); |
| goto calib_fail; |
| } |
| |
| while (data_arb_usrp_cntrl1 != (PM8921_ADC_ARB_USRP_CNTRL1_EOC | |
| PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB)) { |
| rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_CNTRL1, |
| &data_arb_usrp_cntrl1); |
| if (rc < 0) |
| return rc; |
| usleep_range(PM8921_ADC_CONV_TIME_MIN, |
| PM8921_ADC_CONV_TIME_MAX); |
| } |
| data_arb_usrp_cntrl1 = 0; |
| |
| rc = pm8921_adc_read_adc_code(&calib_read_1); |
| if (rc) { |
| pr_err("pm8921_adc read adc failed with %d\n", rc); |
| pm8921_adc_calib_first_adc = false; |
| goto calib_fail; |
| } |
| pm8921_adc_calib_first_adc = false; |
| |
| conv.amux_channel = CHANNEL_MUXOFF; |
| conv.decimation = ADC_DECIMATION_TYPE2; |
| conv.amux_ip_rsv = AMUX_RSV4; |
| conv.amux_mpp_channel = PREMUX_MPP_SCALE_0; |
| pm8921_adc_calib_first_adc = true; |
| rc = pm8921_adc_configure(&conv); |
| if (rc) { |
| pr_err("pm8921_adc configure failed with %d\n", rc); |
| goto calib_fail; |
| } |
| |
| while (data_arb_usrp_cntrl1 != (PM8921_ADC_ARB_USRP_CNTRL1_EOC | |
| PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB)) { |
| rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_CNTRL1, |
| &data_arb_usrp_cntrl1); |
| if (rc < 0) |
| return rc; |
| usleep_range(PM8921_ADC_CONV_TIME_MIN, |
| PM8921_ADC_CONV_TIME_MAX); |
| } |
| data_arb_usrp_cntrl1 = 0; |
| |
| rc = pm8921_adc_read_adc_code(&calib_read_2); |
| if (rc) { |
| pr_err("pm8921_adc read adc failed with %d\n", rc); |
| pm8921_adc_calib_first_adc = false; |
| goto calib_fail; |
| } |
| pm8921_adc_calib_first_adc = false; |
| |
| slope_adc = (((calib_read_1 - calib_read_2) << PM8921_ADC_MUL)/ |
| adc_pmic->adc_prop->adc_vdd_reference); |
| offset_adc = calib_read_2 - |
| ((slope_adc * adc_pmic->adc_prop->adc_vdd_reference) |
| >> PM8921_ADC_MUL); |
| |
| adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].offset |
| = offset_adc; |
| adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].dy = |
| (calib_read_1 - calib_read_2); |
| adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].dx = |
| adc_pmic->adc_prop->adc_vdd_reference; |
| calib_fail: |
| rc = pm8921_adc_arb_cntrl(0); |
| if (rc < 0) { |
| pr_err("%s: Configuring ADC Arbiter disable" |
| "failed\n", __func__); |
| } |
| |
| return rc; |
| } |
| |
| uint32_t pm8921_adc_read(enum pm8921_adc_channels channel, |
| struct pm8921_adc_chan_result *result) |
| { |
| return pm8921_adc_mpp_read(channel, result, PREMUX_MPP_SCALE_0); |
| } |
| EXPORT_SYMBOL_GPL(pm8921_adc_read); |
| |
| uint32_t pm8921_adc_mpp_read(enum pm8921_adc_mpp_channels channel, |
| struct pm8921_adc_chan_result *result, |
| enum pm8921_adc_premux_mpp_scale_type mpp_scale) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| int i = 0, rc, amux_prescaling, scale_type; |
| |
| if (!pm8921_adc_initialized) |
| return -ENODEV; |
| |
| if (!pm8921_adc_calib_device_init) { |
| if (pm8921_adc_calib_device() == 0) |
| pm8921_adc_calib_device_init = true; |
| } |
| |
| mutex_lock(&adc_pmic->adc_lock); |
| |
| for (i = 0; i < adc_pmic->adc_num_channel; i++) { |
| if (channel == adc_pmic->adc_channel[i].channel_name) |
| break; |
| } |
| |
| if (i == adc_pmic->adc_num_channel) { |
| mutex_unlock(&adc_pmic->adc_lock); |
| return -EBADF; /* unknown channel */ |
| } |
| |
| adc_pmic->conv->amux_channel = i; |
| adc_pmic->conv->amux_mpp_channel = mpp_scale; |
| |
| adc_pmic->conv->amux_ip_rsv = adc_pmic->adc_channel[i].adc_rsv; |
| adc_pmic->conv->decimation = adc_pmic->adc_channel[i].adc_decimation; |
| amux_prescaling = adc_pmic->adc_channel[i].chan_path_prescaling; |
| |
| adc_pmic->conv->chan_prop->offset_gain_numerator = |
| pm8921_amux_scaling_ratio[amux_prescaling].num; |
| adc_pmic->conv->chan_prop->offset_gain_denominator = |
| pm8921_amux_scaling_ratio[amux_prescaling].den; |
| |
| rc = pm8921_adc_configure(adc_pmic->conv); |
| if (rc) { |
| mutex_unlock(&adc_pmic->adc_lock); |
| return -EINVAL; |
| } |
| |
| wait_for_completion(&adc_pmic->adc_rslt_completion); |
| |
| rc = pm8921_adc_read_adc_code(&result->adc_code); |
| if (rc) { |
| mutex_unlock(&adc_pmic->adc_lock); |
| return -EINVAL; |
| } |
| |
| scale_type = adc_pmic->adc_channel[i].adc_scale_fn; |
| if (scale_type >= ADC_SCALE_NONE) { |
| mutex_unlock(&adc_pmic->adc_lock); |
| return -EBADF; |
| } |
| |
| adc_scale_fn[scale_type].chan(result->adc_code, |
| adc_pmic->adc_prop, adc_pmic->conv->chan_prop, result); |
| |
| mutex_unlock(&adc_pmic->adc_lock); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(pm8921_adc_mpp_read); |
| |
| uint32_t pm8921_adc_btm_configure(struct pm8921_adc_arb_btm_param *btm_param) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| u8 data_btm_cool_thr0, data_btm_cool_thr1; |
| u8 data_btm_warm_thr0, data_btm_warm_thr1; |
| u8 arb_btm_cntrl1; |
| unsigned long flags = 0; |
| int rc; |
| |
| if (adc_pmic == NULL) { |
| pr_err("PMIC ADC not valid\n"); |
| return -EINVAL; |
| } |
| |
| if ((btm_param->btm_cool_fn == NULL) && |
| (btm_param->btm_warm_fn == NULL)) { |
| pr_err("No BTM warm/cool notification??\n"); |
| return -EINVAL; |
| } |
| |
| rc = pm8921_adc_batt_scaler(btm_param); |
| if (rc < 0) { |
| pr_err("Failed to lookup the BTM thresholds\n"); |
| return rc; |
| } |
| |
| spin_lock_irqsave(&adc_pmic->btm_lock, flags); |
| |
| data_btm_cool_thr0 = ((btm_param->low_thr_voltage << 24) >> 24); |
| data_btm_cool_thr1 = ((btm_param->low_thr_voltage << 16) >> 24); |
| data_btm_warm_thr0 = ((btm_param->high_thr_voltage << 24) >> 24); |
| data_btm_warm_thr1 = ((btm_param->high_thr_voltage << 16) >> 24); |
| |
| if (btm_param->btm_cool_fn != NULL) { |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_BAT_COOL_THR0, |
| data_btm_cool_thr0); |
| if (rc < 0) |
| goto write_err; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_BAT_COOL_THR1, |
| data_btm_cool_thr1); |
| if (rc < 0) |
| goto write_err; |
| |
| adc_pmic->batt->btm_cool_fn = btm_param->btm_cool_fn; |
| } |
| |
| if (btm_param->btm_warm_fn != NULL) { |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_BAT_WARM_THR0, |
| data_btm_warm_thr0); |
| if (rc < 0) |
| goto write_err; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_BAT_WARM_THR1, |
| data_btm_warm_thr1); |
| if (rc < 0) |
| goto write_err; |
| |
| adc_pmic->batt->btm_warm_fn = btm_param->btm_warm_fn; |
| } |
| |
| rc = pm8921_adc_read_reg(PM8921_ADC_ARB_BTM_CNTRL1, &arb_btm_cntrl1); |
| if (rc < 0) |
| goto bail_out; |
| |
| btm_param->interval &= PM8921_ADC_BTM_INTERVAL_SEL_MASK; |
| arb_btm_cntrl1 |= |
| btm_param->interval << PM8921_ADC_BTM_INTERVAL_SEL_SHIFT; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_CNTRL1, arb_btm_cntrl1); |
| if (rc < 0) |
| goto write_err; |
| |
| spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); |
| |
| return rc; |
| bail_out: |
| write_err: |
| spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); |
| pr_debug("%s: with error code %d\n", __func__, rc); |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(pm8921_adc_btm_configure); |
| |
| static uint32_t pm8921_adc_btm_read(uint32_t channel) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| int rc, i; |
| u8 arb_btm_dig_param, arb_btm_ana_param, arb_btm_rsv; |
| u8 arb_btm_amux_cntrl, data_arb_btm_cntrl = 0; |
| unsigned long flags; |
| |
| arb_btm_amux_cntrl = channel << PM8921_ADC_BTM_CHANNEL_SEL; |
| arb_btm_rsv = adc_pmic->adc_channel[channel].adc_rsv; |
| arb_btm_dig_param = arb_btm_ana_param = PM8921_ADC_ARB_ANA_DIG; |
| |
| spin_lock_irqsave(&adc_pmic->btm_lock, flags); |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_AMUX_CNTRL, |
| arb_btm_amux_cntrl); |
| if (rc < 0) |
| goto write_err; |
| |
| arb_btm_rsv = PM8921_ADC_BTM_RSV; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_RSV, arb_btm_rsv); |
| if (rc < 0) |
| goto write_err; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_DIG_PARAM, |
| arb_btm_dig_param); |
| if (rc < 0) |
| goto write_err; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_ANA_PARAM, |
| arb_btm_ana_param); |
| if (rc < 0) |
| goto write_err; |
| |
| data_arb_btm_cntrl |= PM8921_ADC_ARB_BTM_CNTRL1_EN_BTM; |
| |
| for (i = 0; i < 2; i++) { |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_CNTRL1, |
| data_arb_btm_cntrl); |
| if (rc < 0) |
| goto write_err; |
| } |
| |
| data_arb_btm_cntrl |= PM8921_ADC_ARB_BTM_CNTRL1_REQ |
| | PM8921_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE; |
| |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_CNTRL1, |
| data_arb_btm_cntrl); |
| if (rc < 0) |
| goto write_err; |
| |
| if (pmic_adc->batt->btm_warm_fn != NULL) |
| enable_irq(adc_pmic->btm_warm_irq); |
| |
| if (pmic_adc->batt->btm_cool_fn != NULL) |
| enable_irq(adc_pmic->btm_cool_irq); |
| |
| write_err: |
| spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); |
| return rc; |
| } |
| |
| uint32_t pm8921_adc_btm_start(void) |
| { |
| return pm8921_adc_btm_read(CHANNEL_BATT_THERM); |
| } |
| EXPORT_SYMBOL_GPL(pm8921_adc_btm_start); |
| |
| uint32_t pm8921_adc_btm_end(void) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| int i, rc; |
| u8 data_arb_btm_cntrl; |
| unsigned long flags; |
| |
| disable_irq_nosync(adc_pmic->btm_warm_irq); |
| disable_irq_nosync(adc_pmic->btm_cool_irq); |
| |
| spin_lock_irqsave(&adc_pmic->btm_lock, flags); |
| /* Set BTM registers to Disable mode */ |
| rc = pm8921_adc_read_reg(PM8921_ADC_ARB_BTM_CNTRL1, |
| &data_arb_btm_cntrl); |
| if (rc < 0) { |
| spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); |
| return rc; |
| } |
| |
| data_arb_btm_cntrl |= ~PM8921_ADC_ARB_BTM_CNTRL1_EN_BTM; |
| /* Write twice to the CNTRL register for the arbiter settings |
| to take into effect */ |
| for (i = 0; i < 2; i++) { |
| rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_CNTRL1, |
| data_arb_btm_cntrl); |
| if (rc < 0) { |
| spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); |
| return rc; |
| } |
| } |
| |
| spin_unlock_irqrestore(&adc_pmic->btm_lock, flags); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(pm8921_adc_btm_end); |
| |
| static int get_adc(void *data, u64 *val) |
| { |
| struct pm8921_adc_chan_result result; |
| int i = (int)data; |
| int rc; |
| |
| rc = pm8921_adc_read(i, &result); |
| pr_info("ADC value raw:%x physical:%lld\n", |
| result.adc_code, result.physical); |
| *val = result.physical; |
| |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_adc, NULL, "%llu\n"); |
| |
| #ifdef CONFIG_DEBUG_FS |
| static void create_debugfs_entries(void) |
| { |
| pmic_adc->dent = debugfs_create_dir("pm8921_adc", NULL); |
| |
| if (IS_ERR(pmic_adc->dent)) { |
| pr_err("pmic adc debugfs dir not created\n"); |
| return; |
| } |
| |
| debugfs_create_file("vbat", 0644, pmic_adc->dent, |
| (void *)CHANNEL_VBAT, ®_fops); |
| debugfs_create_file("625mv", 0644, pmic_adc->dent, |
| (void *)CHANNEL_625MV, ®_fops); |
| debugfs_create_file("125v", 0644, pmic_adc->dent, |
| (void *)CHANNEL_125V, ®_fops); |
| debugfs_create_file("die_temp", 0644, pmic_adc->dent, |
| (void *)CHANNEL_DIE_TEMP, ®_fops); |
| debugfs_create_file("vcoin", 0644, pmic_adc->dent, |
| (void *)CHANNEL_VCOIN, ®_fops); |
| debugfs_create_file("dc_in", 0644, pmic_adc->dent, |
| (void *)CHANNEL_DCIN, ®_fops); |
| debugfs_create_file("vph_pwr", 0644, pmic_adc->dent, |
| (void *)CHANNEL_VPH_PWR, ®_fops); |
| debugfs_create_file("usb_in", 0644, pmic_adc->dent, |
| (void *)CHANNEL_USBIN, ®_fops); |
| debugfs_create_file("batt_therm", 0644, pmic_adc->dent, |
| (void *)CHANNEL_BATT_THERM, ®_fops); |
| debugfs_create_file("batt_id", 0644, pmic_adc->dent, |
| (void *)CHANNEL_BATT_ID, ®_fops); |
| debugfs_create_file("chg_temp", 0644, pmic_adc->dent, |
| (void *)CHANNEL_CHG_TEMP, ®_fops); |
| debugfs_create_file("charger_current", 0644, pmic_adc->dent, |
| (void *)CHANNEL_ICHG, ®_fops); |
| debugfs_create_file("ibat", 0644, pmic_adc->dent, |
| (void *)CHANNEL_IBAT, ®_fops); |
| } |
| #else |
| static inline void create_debugfs_entries(void) |
| { |
| } |
| #endif |
| |
| static int __devexit pm8921_adc_teardown(struct platform_device *pdev) |
| { |
| struct pm8921_adc *adc_pmic = pmic_adc; |
| |
| device_init_wakeup(&pdev->dev, 0); |
| free_irq(adc_pmic->adc_irq, adc_pmic); |
| free_irq(adc_pmic->btm_warm_irq, adc_pmic); |
| free_irq(adc_pmic->btm_cool_irq, adc_pmic); |
| platform_set_drvdata(pdev, NULL); |
| pmic_adc = NULL; |
| kfree(adc_pmic->conv->chan_prop); |
| kfree(adc_pmic->adc_channel); |
| kfree(adc_pmic->batt); |
| kfree(adc_pmic); |
| pm8921_adc_initialized = false; |
| |
| return 0; |
| } |
| |
| static int __devinit pm8921_adc_probe(struct platform_device *pdev) |
| { |
| const struct pm8921_adc_platform_data *pdata = pdev->dev.platform_data; |
| struct pm8921_adc *adc_pmic; |
| struct pm8921_adc_amux_properties *adc_amux_prop; |
| struct pm8921_adc_chan_properties *adc_pmic_chanprop; |
| struct pm8921_adc_arb_btm_param *adc_btm; |
| int rc = 0; |
| |
| if (!pdata) { |
| dev_err(&pdev->dev, "no platform data?\n"); |
| return -EINVAL; |
| } |
| |
| adc_pmic = kzalloc(sizeof(struct pm8921_adc), |
| GFP_KERNEL); |
| if (!adc_pmic) { |
| dev_err(&pdev->dev, "Unable to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| adc_amux_prop = kzalloc(sizeof(struct pm8921_adc_amux_properties), |
| GFP_KERNEL); |
| if (!adc_amux_prop) { |
| dev_err(&pdev->dev, "Unable to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| adc_pmic_chanprop = kzalloc(sizeof(struct pm8921_adc_chan_properties), |
| GFP_KERNEL); |
| if (!adc_pmic_chanprop) { |
| dev_err(&pdev->dev, "Unable to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| adc_btm = kzalloc(sizeof(struct pm8921_adc_arb_btm_param), |
| GFP_KERNEL); |
| if (!adc_btm) { |
| dev_err(&pdev->dev, "Unable to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| adc_pmic->dev = &pdev->dev; |
| adc_pmic->adc_prop = pdata->adc_prop; |
| adc_pmic->conv = adc_amux_prop; |
| adc_pmic->conv->chan_prop = adc_pmic_chanprop; |
| adc_pmic->batt = adc_btm; |
| |
| init_completion(&adc_pmic->adc_rslt_completion); |
| adc_pmic->adc_channel = pdata->adc_channel; |
| adc_pmic->adc_num_channel = pdata->adc_num_channel; |
| |
| mutex_init(&adc_pmic->adc_lock); |
| spin_lock_init(&adc_pmic->btm_lock); |
| |
| adc_pmic->adc_irq = platform_get_irq(pdev, PM8921_ADC_IRQ_0); |
| if (adc_pmic->adc_irq < 0) { |
| rc = -ENXIO; |
| goto err_cleanup; |
| } |
| |
| rc = request_irq(adc_pmic->adc_irq, |
| pm8921_adc_isr, |
| IRQF_TRIGGER_RISING, "pm8921_adc_interrupt", adc_pmic); |
| if (rc) { |
| dev_err(&pdev->dev, "failed to request adc irq " |
| "with error %d\n", rc); |
| goto err_cleanup; |
| } |
| |
| disable_irq_nosync(adc_pmic->adc_irq); |
| |
| adc_pmic->btm_warm_irq = platform_get_irq(pdev, PM8921_ADC_IRQ_1); |
| if (adc_pmic->btm_warm_irq < 0) { |
| rc = -ENXIO; |
| goto err_cleanup; |
| } |
| |
| rc = request_irq(adc_pmic->btm_warm_irq, |
| pm8921_btm_warm_isr, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "pm8921_btm_warm_interrupt", adc_pmic); |
| if (rc) { |
| pr_err("btm warm irq failed %d with interrupt number %d\n", |
| rc, adc_pmic->btm_warm_irq); |
| dev_err(&pdev->dev, "failed to request btm irq\n"); |
| goto err_cleanup; |
| } |
| |
| disable_irq_nosync(adc_pmic->btm_warm_irq); |
| |
| adc_pmic->btm_cool_irq = platform_get_irq(pdev, PM8921_ADC_IRQ_2); |
| if (adc_pmic->btm_cool_irq < 0) { |
| rc = -ENXIO; |
| goto err_cleanup; |
| } |
| |
| rc = request_irq(adc_pmic->btm_cool_irq, |
| pm8921_btm_cool_isr, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "pm8921_btm_cool_interrupt", adc_pmic); |
| if (rc) { |
| pr_err("btm cool irq failed with return %d and number %d\n", |
| rc, adc_pmic->btm_cool_irq); |
| dev_err(&pdev->dev, "failed to request btm irq\n"); |
| goto err_cleanup; |
| } |
| |
| disable_irq_nosync(adc_pmic->btm_cool_irq); |
| device_init_wakeup(&pdev->dev, pdata->adc_wakeup); |
| platform_set_drvdata(pdev, adc_pmic); |
| pmic_adc = adc_pmic; |
| |
| INIT_WORK(&adc_pmic->warm_work, pm8921_adc_btm_warm_scheduler_fn); |
| INIT_WORK(&adc_pmic->cool_work, pm8921_adc_btm_cool_scheduler_fn); |
| create_debugfs_entries(); |
| pm8921_adc_calib_first_adc = false; |
| pm8921_adc_calib_device_init = false; |
| pm8921_adc_initialized = true; |
| return 0; |
| |
| err_cleanup: |
| pm8921_adc_teardown(pdev); |
| return rc; |
| } |
| |
| static struct platform_driver pm8921_adc_driver = { |
| .probe = pm8921_adc_probe, |
| .remove = __devexit_p(pm8921_adc_teardown), |
| .driver = { |
| .name = PM8921_ADC_DEV_NAME, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __init pm8921_adc_init(void) |
| { |
| return platform_driver_register(&pm8921_adc_driver); |
| } |
| module_init(pm8921_adc_init); |
| |
| static void __exit pm8921_adc_exit(void) |
| { |
| platform_driver_unregister(&pm8921_adc_driver); |
| } |
| module_exit(pm8921_adc_exit); |
| |
| MODULE_ALIAS("platform:" PM8921_ADC_DEV_NAME); |
| MODULE_DESCRIPTION("PMIC8921 ADC driver"); |
| MODULE_LICENSE("GPL v2"); |