1333 lines
36 KiB
C
1333 lines
36 KiB
C
|
/*
|
||
|
* Copyright (c) 2011-2013, The Linux Foundation. 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/PM8018 ADC Arbiter driver
|
||
|
*/
|
||
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/hwmon.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/completion.h>
|
||
|
#include <linux/hwmon-sysfs.h>
|
||
|
#include <linux/mfd/pm8xxx/mpp.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/mfd/pm8xxx/core.h>
|
||
|
#include <linux/regulator/consumer.h>
|
||
|
#include <linux/mfd/pm8xxx/pm8xxx-adc.h>
|
||
|
#include <mach/msm_xo.h>
|
||
|
|
||
|
/* User Bank register set */
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1 0x197
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB BIT(0)
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV1 BIT(1)
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV2 BIT(2)
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV3 BIT(3)
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV4 BIT(4)
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV5 BIT(5)
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1_EOC BIT(6)
|
||
|
#define PM8XXX_ADC_ARB_USRP_CNTRL1_REQ BIT(7)
|
||
|
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL 0x198
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_RSV0 BIT(0)
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_RSV1 BIT(1)
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_PREMUX0 BIT(2)
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_PREMUX1 BIT(3)
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL0 BIT(4)
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL1 BIT(5)
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL2 BIT(6)
|
||
|
#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL3 BIT(7)
|
||
|
|
||
|
#define PM8XXX_ADC_ARB_USRP_ANA_PARAM 0x199
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM 0x19A
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 BIT(0)
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 BIT(1)
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_CLK_RATE0 BIT(2)
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_CLK_RATE1 BIT(3)
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_EOC BIT(4)
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 BIT(5)
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1 BIT(6)
|
||
|
#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_EN BIT(7)
|
||
|
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV 0x19B
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV_RST BIT(0)
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV_DTEST0 BIT(1)
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV_DTEST1 BIT(2)
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV_OP BIT(3)
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL0 BIT(4)
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL1 BIT(5)
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL2 BIT(6)
|
||
|
#define PM8XXX_ADC_ARB_USRP_RSV_TRM BIT(7)
|
||
|
|
||
|
#define PM8XXX_ADC_ARB_USRP_DATA0 0x19D
|
||
|
#define PM8XXX_ADC_ARB_USRP_DATA1 0x19C
|
||
|
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1 0x17e
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1_EN_BTM BIT(0)
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE BIT(1)
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL1 BIT(2)
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL2 BIT(3)
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL3 BIT(4)
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL4 BIT(5)
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1_EOC BIT(6)
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL1_REQ BIT(7)
|
||
|
|
||
|
#define PM8XXX_ADC_ARB_BTM_CNTRL2 0x18c
|
||
|
#define PM8XXX_ADC_ARB_BTM_AMUX_CNTRL 0x17f
|
||
|
#define PM8XXX_ADC_ARB_BTM_ANA_PARAM 0x180
|
||
|
#define PM8XXX_ADC_ARB_BTM_DIG_PARAM 0x181
|
||
|
#define PM8XXX_ADC_ARB_BTM_RSV 0x182
|
||
|
#define PM8XXX_ADC_ARB_BTM_DATA1 0x183
|
||
|
#define PM8XXX_ADC_ARB_BTM_DATA0 0x184
|
||
|
#define PM8XXX_ADC_ARB_BTM_BAT_COOL_THR1 0x185
|
||
|
#define PM8XXX_ADC_ARB_BTM_BAT_COOL_THR0 0x186
|
||
|
#define PM8XXX_ADC_ARB_BTM_BAT_WARM_THR1 0x187
|
||
|
#define PM8XXX_ADC_ARB_BTM_BAT_WARM_THR0 0x188
|
||
|
|
||
|
#define PM8XXX_ADC_ARB_ANA_DIG 0xa0
|
||
|
#define PM8XXX_ADC_BTM_RSV 0x10
|
||
|
#define PM8XXX_ADC_AMUX_MPP_SEL 2
|
||
|
#define PM8XXX_ADC_AMUX_SEL 4
|
||
|
#define PM8XXX_ADC_RSV_IP_SEL 4
|
||
|
#define PM8XXX_ADC_BTM_CHANNEL_SEL 4
|
||
|
#define PM8XXX_MAX_CHANNEL_PROPERTIES 2
|
||
|
#define PM8XXX_ADC_IRQ_0 0
|
||
|
#define PM8XXX_ADC_IRQ_1 1
|
||
|
#define PM8XXX_ADC_IRQ_2 2
|
||
|
#define PM8XXX_ADC_BTM_INTERVAL_SEL_MASK 0xF
|
||
|
#define PM8XXX_ADC_BTM_INTERVAL_SEL_SHIFT 2
|
||
|
#define PM8XXX_ADC_BTM_DECIMATION_SEL 5
|
||
|
#define PM8XXX_ADC_MUL 10
|
||
|
#define PM8XXX_ADC_CONV_TIME_MIN 2000
|
||
|
#define PM8XXX_ADC_CONV_TIME_MAX 2100
|
||
|
#define PM8XXX_ADC_MPP_SETTLE_TIME_MIN 200
|
||
|
#define PM8XXX_ADC_MPP_SETTLE_TIME_MAX 200
|
||
|
#define PM8XXX_ADC_PA_THERM_VREG_UV_MIN 1800000
|
||
|
#define PM8XXX_ADC_PA_THERM_VREG_UV_MAX 1800000
|
||
|
#define PM8XXX_ADC_PA_THERM_VREG_UA_LOAD 100000
|
||
|
#define PM8XXX_ADC_HWMON_NAME_LENGTH 32
|
||
|
#define PM8XXX_ADC_BTM_INTERVAL_MAX 0x14
|
||
|
#define PM8XXX_ADC_COMPLETION_TIMEOUT (2 * HZ)
|
||
|
|
||
|
struct pm8xxx_adc {
|
||
|
struct device *dev;
|
||
|
struct pm8xxx_adc_properties *adc_prop;
|
||
|
int adc_irq;
|
||
|
struct mutex adc_lock;
|
||
|
struct mutex mpp_adc_lock;
|
||
|
spinlock_t btm_lock;
|
||
|
uint32_t adc_num_board_channel;
|
||
|
struct completion adc_rslt_completion;
|
||
|
struct pm8xxx_adc_amux *adc_channel;
|
||
|
int btm_warm_irq;
|
||
|
int btm_cool_irq;
|
||
|
struct dentry *dent;
|
||
|
struct work_struct warm_work;
|
||
|
struct work_struct cool_work;
|
||
|
uint32_t mpp_base;
|
||
|
struct device *hwmon;
|
||
|
struct msm_xo_voter *adc_voter;
|
||
|
int msm_suspend_check;
|
||
|
struct pm8xxx_adc_amux_properties *conv;
|
||
|
struct pm8xxx_adc_arb_btm_param batt;
|
||
|
struct sensor_device_attribute sens_attr[0];
|
||
|
};
|
||
|
|
||
|
struct pm8xxx_adc_amux_properties {
|
||
|
uint32_t amux_channel;
|
||
|
uint32_t decimation;
|
||
|
uint32_t amux_ip_rsv;
|
||
|
uint32_t amux_mpp_channel;
|
||
|
struct pm8xxx_adc_chan_properties chan_prop[0];
|
||
|
};
|
||
|
|
||
|
static const struct pm8xxx_adc_scaling_ratio pm8xxx_amux_scaling_ratio[] = {
|
||
|
{1, 1},
|
||
|
{1, 3},
|
||
|
{1, 4},
|
||
|
{1, 6}
|
||
|
};
|
||
|
|
||
|
static struct pm8xxx_adc *pmic_adc;
|
||
|
static struct regulator *pa_therm;
|
||
|
|
||
|
static struct pm8xxx_adc_scale_fn adc_scale_fn[] = {
|
||
|
[ADC_SCALE_DEFAULT] = {pm8xxx_adc_scale_default},
|
||
|
[ADC_SCALE_BATT_THERM] = {pm8xxx_adc_scale_batt_therm},
|
||
|
[ADC_SCALE_PA_THERM] = {pm8xxx_adc_scale_pa_therm},
|
||
|
[ADC_SCALE_PMIC_THERM] = {pm8xxx_adc_scale_pmic_therm},
|
||
|
[ADC_SCALE_XOTHERM] = {pm8xxx_adc_tdkntcg_therm},
|
||
|
};
|
||
|
|
||
|
/* On PM8921 ADC the MPP needs to first be configured
|
||
|
as an analog input to the AMUX pre-mux channel before
|
||
|
issuing a read request. PM8921 MPP 8 is mapped to AMUX8
|
||
|
and is common between remote processor's.
|
||
|
On PM8018 ADC the MPP is directly connected to the AMUX
|
||
|
pre-mux. Therefore clients of the PM8018 MPP do not need
|
||
|
to configure the MPP as an analog input to the pre-mux.
|
||
|
Clients can directly issue request on the pre-mux AMUX
|
||
|
channel to read the ADC on the MPP */
|
||
|
static struct pm8xxx_mpp_config_data pm8xxx_adc_mpp_config = {
|
||
|
.type = PM8XXX_MPP_TYPE_A_INPUT,
|
||
|
/* AMUX6 is dedicated to be used for apps processor */
|
||
|
.level = PM8XXX_MPP_AIN_AMUX_CH6,
|
||
|
.control = PM8XXX_MPP_AOUT_CTRL_DISABLE,
|
||
|
};
|
||
|
|
||
|
/* MPP Configuration for default settings */
|
||
|
static struct pm8xxx_mpp_config_data pm8xxx_adc_mpp_unconfig = {
|
||
|
.type = PM8XXX_MPP_TYPE_SINK,
|
||
|
.level = PM8XXX_MPP_AIN_AMUX_CH5,
|
||
|
.control = PM8XXX_MPP_AOUT_CTRL_DISABLE,
|
||
|
};
|
||
|
|
||
|
static bool pm8xxx_adc_calib_first_adc;
|
||
|
static bool pm8xxx_adc_initialized, pm8xxx_adc_calib_device_init;
|
||
|
|
||
|
static int32_t pm8xxx_adc_check_channel_valid(uint32_t channel)
|
||
|
{
|
||
|
if (channel < CHANNEL_VCOIN ||
|
||
|
(channel > CHANNEL_MUXOFF && channel < ADC_MPP_1_ATEST_8) ||
|
||
|
(channel > ADC_MPP_1_ATEST_7 && channel < ADC_MPP_2_ATEST_8)
|
||
|
|| (channel >= ADC_CHANNEL_MAX_NUM))
|
||
|
return -EBADF;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int32_t pm8xxx_adc_arb_cntrl(uint32_t arb_cntrl,
|
||
|
uint32_t channel)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
int i, rc;
|
||
|
u8 data_arb_cntrl = 0;
|
||
|
|
||
|
if (arb_cntrl) {
|
||
|
if (adc_pmic->msm_suspend_check)
|
||
|
pr_err("PM8xxx ADC request made after suspend_noirq "
|
||
|
"with channel: %d\n", channel);
|
||
|
data_arb_cntrl |= PM8XXX_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,
|
||
|
PM8XXX_ADC_ARB_USRP_CNTRL1, data_arb_cntrl);
|
||
|
if (rc < 0) {
|
||
|
pr_err("PM8xxx arb cntrl write failed with %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (arb_cntrl) {
|
||
|
data_arb_cntrl |= PM8XXX_ADC_ARB_USRP_CNTRL1_REQ;
|
||
|
INIT_COMPLETION(adc_pmic->adc_rslt_completion);
|
||
|
rc = pm8xxx_writeb(adc_pmic->dev->parent,
|
||
|
PM8XXX_ADC_ARB_USRP_CNTRL1, data_arb_cntrl);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int32_t pm8xxx_adc_patherm_power(bool on)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
if (!pa_therm) {
|
||
|
pr_err("pm8xxx adc pa_therm not valid\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (on) {
|
||
|
rc = regulator_set_voltage(pa_therm,
|
||
|
PM8XXX_ADC_PA_THERM_VREG_UV_MIN,
|
||
|
PM8XXX_ADC_PA_THERM_VREG_UV_MAX);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to set the voltage for "
|
||
|
"pa_therm with error %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = regulator_set_optimum_mode(pa_therm,
|
||
|
PM8XXX_ADC_PA_THERM_VREG_UA_LOAD);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to set optimum mode for "
|
||
|
"pa_therm with error %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = regulator_enable(pa_therm);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to enable pa_therm vreg "
|
||
|
"with error %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
} else {
|
||
|
rc = regulator_disable(pa_therm);
|
||
|
if (rc < 0) {
|
||
|
pr_err("failed to disable pa_therm vreg "
|
||
|
"with error %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int32_t pm8xxx_adc_xo_vote(bool on)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
|
||
|
if (on)
|
||
|
msm_xo_mode_vote(adc_pmic->adc_voter, MSM_XO_MODE_ON);
|
||
|
else
|
||
|
msm_xo_mode_vote(adc_pmic->adc_voter, MSM_XO_MODE_OFF);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int32_t pm8xxx_adc_channel_power_enable(uint32_t channel,
|
||
|
bool power_cntrl)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
switch (channel) {
|
||
|
case ADC_MPP_1_AMUX8:
|
||
|
rc = pm8xxx_adc_patherm_power(power_cntrl);
|
||
|
break;
|
||
|
case CHANNEL_DIE_TEMP:
|
||
|
case CHANNEL_MUXOFF:
|
||
|
rc = pm8xxx_adc_xo_vote(power_cntrl);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
|
||
|
static uint32_t pm8xxx_adc_read_reg(uint32_t reg, u8 *data)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
int rc;
|
||
|
|
||
|
rc = pm8xxx_readb(adc_pmic->dev->parent, reg, data);
|
||
|
if (rc < 0) {
|
||
|
pr_err("PM8xxx adc read reg %d failed with %d\n", reg, rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static uint32_t pm8xxx_adc_write_reg(uint32_t reg, u8 data)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
int rc;
|
||
|
|
||
|
rc = pm8xxx_writeb(adc_pmic->dev->parent, reg, data);
|
||
|
if (rc < 0) {
|
||
|
pr_err("PM8xxx adc write reg %d failed with %d\n", reg, rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int32_t pm8xxx_adc_configure(
|
||
|
struct pm8xxx_adc_amux_properties *chan_prop)
|
||
|
{
|
||
|
u8 data_amux_chan = 0, data_arb_rsv = 0, data_dig_param = 0;
|
||
|
int rc;
|
||
|
|
||
|
data_amux_chan |= chan_prop->amux_channel << PM8XXX_ADC_AMUX_SEL;
|
||
|
|
||
|
if (chan_prop->amux_mpp_channel)
|
||
|
data_amux_chan |= chan_prop->amux_mpp_channel <<
|
||
|
PM8XXX_ADC_AMUX_MPP_SEL;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_AMUX_CNTRL,
|
||
|
data_amux_chan);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_RSV, &data_arb_rsv);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
data_arb_rsv &= (PM8XXX_ADC_ARB_USRP_RSV_RST |
|
||
|
PM8XXX_ADC_ARB_USRP_RSV_DTEST0 |
|
||
|
PM8XXX_ADC_ARB_USRP_RSV_DTEST1 |
|
||
|
PM8XXX_ADC_ARB_USRP_RSV_OP);
|
||
|
data_arb_rsv |= (chan_prop->amux_ip_rsv << PM8XXX_ADC_RSV_IP_SEL |
|
||
|
PM8XXX_ADC_ARB_USRP_RSV_TRM);
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_RSV, data_arb_rsv);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
rc = pm8xxx_adc_read_reg(PM8XXX_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 |= PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0;
|
||
|
break;
|
||
|
case ADC_DECIMATION_TYPE2:
|
||
|
data_dig_param |= (PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0
|
||
|
| PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1);
|
||
|
break;
|
||
|
default:
|
||
|
data_dig_param |= PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0;
|
||
|
break;
|
||
|
}
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_DIG_PARAM,
|
||
|
PM8XXX_ADC_ARB_ANA_DIG);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_ANA_PARAM,
|
||
|
PM8XXX_ADC_ARB_ANA_DIG);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
rc = pm8xxx_adc_arb_cntrl(1, data_amux_chan);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Configuring ADC Arbiter"
|
||
|
"enable failed with %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static uint32_t pm8xxx_adc_read_adc_code(int32_t *data)
|
||
|
{
|
||
|
struct pm8xxx_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,
|
||
|
PM8XXX_ADC_ARB_USRP_DATA0, &rslt_lsb);
|
||
|
if (rc < 0) {
|
||
|
pr_err("PM8xxx adc result read failed with %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = pm8xxx_readb(adc_pmic->dev->parent,
|
||
|
PM8XXX_ADC_ARB_USRP_DATA1, &rslt_msb);
|
||
|
if (rc < 0) {
|
||
|
pr_err("PM8xxx 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 = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE);
|
||
|
if (rc < 0) {
|
||
|
pr_err("%s: Configuring ADC Arbiter disable"
|
||
|
"failed\n", __func__);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void pm8xxx_adc_btm_warm_scheduler_fn(struct work_struct *work)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = container_of(work, struct pm8xxx_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 pm8xxx_adc_btm_cool_scheduler_fn(struct work_struct *work)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = container_of(work, struct pm8xxx_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);
|
||
|
}
|
||
|
|
||
|
void trigger_completion(struct work_struct *work)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_8xxx = pmic_adc;
|
||
|
|
||
|
complete(&adc_8xxx->adc_rslt_completion);
|
||
|
}
|
||
|
DECLARE_WORK(trigger_completion_work, trigger_completion);
|
||
|
|
||
|
static irqreturn_t pm8xxx_adc_isr(int irq, void *dev_id)
|
||
|
{
|
||
|
|
||
|
if (pm8xxx_adc_calib_first_adc)
|
||
|
return IRQ_HANDLED;
|
||
|
|
||
|
schedule_work(&trigger_completion_work);
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
static irqreturn_t pm8xxx_btm_warm_isr(int irq, void *dev_id)
|
||
|
{
|
||
|
struct pm8xxx_adc *btm_8xxx = dev_id;
|
||
|
|
||
|
schedule_work(&btm_8xxx->warm_work);
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
static irqreturn_t pm8xxx_btm_cool_isr(int irq, void *dev_id)
|
||
|
{
|
||
|
struct pm8xxx_adc *btm_8xxx = dev_id;
|
||
|
|
||
|
schedule_work(&btm_8xxx->cool_work);
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
static uint32_t pm8xxx_adc_calib_device(void)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
struct pm8xxx_adc_amux_properties conv;
|
||
|
int rc, 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;
|
||
|
pm8xxx_adc_calib_first_adc = true;
|
||
|
rc = pm8xxx_adc_configure(&conv);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_adc configure failed with %d\n", rc);
|
||
|
goto calib_fail;
|
||
|
}
|
||
|
|
||
|
while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
|
||
|
PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) {
|
||
|
rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
|
||
|
&data_arb_usrp_cntrl1);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
usleep_range(PM8XXX_ADC_CONV_TIME_MIN,
|
||
|
PM8XXX_ADC_CONV_TIME_MAX);
|
||
|
}
|
||
|
data_arb_usrp_cntrl1 = 0;
|
||
|
|
||
|
rc = pm8xxx_adc_read_adc_code(&calib_read_1);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_adc read adc failed with %d\n", rc);
|
||
|
pm8xxx_adc_calib_first_adc = false;
|
||
|
goto calib_fail;
|
||
|
}
|
||
|
pm8xxx_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;
|
||
|
pm8xxx_adc_calib_first_adc = true;
|
||
|
rc = pm8xxx_adc_configure(&conv);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_adc configure failed with %d\n", rc);
|
||
|
goto calib_fail;
|
||
|
}
|
||
|
|
||
|
while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
|
||
|
PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) {
|
||
|
rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
|
||
|
&data_arb_usrp_cntrl1);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
usleep_range(PM8XXX_ADC_CONV_TIME_MIN,
|
||
|
PM8XXX_ADC_CONV_TIME_MAX);
|
||
|
}
|
||
|
data_arb_usrp_cntrl1 = 0;
|
||
|
|
||
|
rc = pm8xxx_adc_read_adc_code(&calib_read_2);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_adc read adc failed with %d\n", rc);
|
||
|
pm8xxx_adc_calib_first_adc = false;
|
||
|
goto calib_fail;
|
||
|
}
|
||
|
pm8xxx_adc_calib_first_adc = false;
|
||
|
|
||
|
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
|
||
|
= PM8XXX_CHANNEL_ADC_625_UV;
|
||
|
adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].adc_vref =
|
||
|
calib_read_1;
|
||
|
adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].adc_gnd =
|
||
|
calib_read_2;
|
||
|
rc = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE);
|
||
|
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;
|
||
|
pm8xxx_adc_calib_first_adc = true;
|
||
|
rc = pm8xxx_adc_configure(&conv);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_adc configure failed with %d\n", rc);
|
||
|
goto calib_fail;
|
||
|
}
|
||
|
|
||
|
while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
|
||
|
PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) {
|
||
|
rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
|
||
|
&data_arb_usrp_cntrl1);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
usleep_range(PM8XXX_ADC_CONV_TIME_MIN,
|
||
|
PM8XXX_ADC_CONV_TIME_MAX);
|
||
|
}
|
||
|
data_arb_usrp_cntrl1 = 0;
|
||
|
|
||
|
rc = pm8xxx_adc_read_adc_code(&calib_read_1);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_adc read adc failed with %d\n", rc);
|
||
|
pm8xxx_adc_calib_first_adc = false;
|
||
|
goto calib_fail;
|
||
|
}
|
||
|
pm8xxx_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;
|
||
|
pm8xxx_adc_calib_first_adc = true;
|
||
|
rc = pm8xxx_adc_configure(&conv);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_adc configure failed with %d\n", rc);
|
||
|
goto calib_fail;
|
||
|
}
|
||
|
|
||
|
while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
|
||
|
PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) {
|
||
|
rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
|
||
|
&data_arb_usrp_cntrl1);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
usleep_range(PM8XXX_ADC_CONV_TIME_MIN,
|
||
|
PM8XXX_ADC_CONV_TIME_MAX);
|
||
|
}
|
||
|
data_arb_usrp_cntrl1 = 0;
|
||
|
|
||
|
rc = pm8xxx_adc_read_adc_code(&calib_read_2);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_adc read adc failed with %d\n", rc);
|
||
|
pm8xxx_adc_calib_first_adc = false;
|
||
|
goto calib_fail;
|
||
|
}
|
||
|
pm8xxx_adc_calib_first_adc = false;
|
||
|
|
||
|
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;
|
||
|
adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].adc_vref =
|
||
|
calib_read_1;
|
||
|
adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].adc_gnd =
|
||
|
calib_read_2;
|
||
|
calib_fail:
|
||
|
rc = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE);
|
||
|
if (rc < 0) {
|
||
|
pr_err("%s: Configuring ADC Arbiter disable"
|
||
|
"failed\n", __func__);
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
uint32_t pm8xxx_adc_read(enum pm8xxx_adc_channels channel,
|
||
|
struct pm8xxx_adc_chan_result *result)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
int i = 0, rc = 0, rc_fail, amux_prescaling, scale_type;
|
||
|
enum pm8xxx_adc_premux_mpp_scale_type mpp_scale;
|
||
|
|
||
|
if (!pm8xxx_adc_initialized)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (!pm8xxx_adc_calib_device_init) {
|
||
|
if (pm8xxx_adc_calib_device() == 0)
|
||
|
pm8xxx_adc_calib_device_init = true;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&adc_pmic->adc_lock);
|
||
|
|
||
|
for (i = 0; i < adc_pmic->adc_num_board_channel; i++) {
|
||
|
if (channel == adc_pmic->adc_channel[i].channel_name)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (i == adc_pmic->adc_num_board_channel ||
|
||
|
(pm8xxx_adc_check_channel_valid(channel) != 0)) {
|
||
|
rc = -EBADF;
|
||
|
goto fail_unlock;
|
||
|
}
|
||
|
|
||
|
if (channel < PM8XXX_CHANNEL_MPP_SCALE1_IDX) {
|
||
|
mpp_scale = PREMUX_MPP_SCALE_0;
|
||
|
adc_pmic->conv->amux_channel = channel;
|
||
|
} else if (channel >= PM8XXX_CHANNEL_MPP_SCALE1_IDX &&
|
||
|
channel < PM8XXX_CHANNEL_MPP_SCALE3_IDX) {
|
||
|
mpp_scale = PREMUX_MPP_SCALE_1;
|
||
|
adc_pmic->conv->amux_channel = channel %
|
||
|
PM8XXX_CHANNEL_MPP_SCALE1_IDX;
|
||
|
} else {
|
||
|
mpp_scale = PREMUX_MPP_SCALE_1_DIV3;
|
||
|
adc_pmic->conv->amux_channel = channel %
|
||
|
PM8XXX_CHANNEL_MPP_SCALE3_IDX;
|
||
|
}
|
||
|
|
||
|
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 =
|
||
|
pm8xxx_amux_scaling_ratio[amux_prescaling].num;
|
||
|
adc_pmic->conv->chan_prop->offset_gain_denominator =
|
||
|
pm8xxx_amux_scaling_ratio[amux_prescaling].den;
|
||
|
|
||
|
rc = pm8xxx_adc_channel_power_enable(channel, true);
|
||
|
if (rc) {
|
||
|
rc = -EINVAL;
|
||
|
goto fail_unlock;
|
||
|
}
|
||
|
|
||
|
rc = pm8xxx_adc_configure(adc_pmic->conv);
|
||
|
if (rc) {
|
||
|
rc = -EINVAL;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
rc = wait_for_completion_timeout(&adc_pmic->adc_rslt_completion,
|
||
|
PM8XXX_ADC_COMPLETION_TIMEOUT);
|
||
|
if (!rc) {
|
||
|
u8 data_arb_usrp_cntrl1 = 0;
|
||
|
rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
|
||
|
&data_arb_usrp_cntrl1);
|
||
|
if (rc < 0)
|
||
|
goto fail;
|
||
|
if (data_arb_usrp_cntrl1 == (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
|
||
|
PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB))
|
||
|
pr_debug("End of conversion status set\n");
|
||
|
else {
|
||
|
pr_err("EOC interrupt not received\n");
|
||
|
rc = -EINVAL;
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rc = pm8xxx_adc_read_adc_code(&result->adc_code);
|
||
|
if (rc) {
|
||
|
rc = -EINVAL;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
scale_type = adc_pmic->adc_channel[i].adc_scale_fn;
|
||
|
if (scale_type >= ADC_SCALE_NONE) {
|
||
|
rc = -EBADF;
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
adc_scale_fn[scale_type].chan(result->adc_code,
|
||
|
adc_pmic->adc_prop, adc_pmic->conv->chan_prop, result);
|
||
|
|
||
|
rc = pm8xxx_adc_channel_power_enable(channel, false);
|
||
|
if (rc) {
|
||
|
rc = -EINVAL;
|
||
|
goto fail_unlock;
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&adc_pmic->adc_lock);
|
||
|
|
||
|
return 0;
|
||
|
fail:
|
||
|
rc_fail = pm8xxx_adc_channel_power_enable(channel, false);
|
||
|
if (rc_fail)
|
||
|
pr_err("pm8xxx adc power disable failed\n");
|
||
|
fail_unlock:
|
||
|
mutex_unlock(&adc_pmic->adc_lock);
|
||
|
pr_err("pm8xxx adc error with %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pm8xxx_adc_read);
|
||
|
|
||
|
uint32_t pm8xxx_adc_mpp_config_read(uint32_t mpp_num,
|
||
|
enum pm8xxx_adc_channels channel,
|
||
|
struct pm8xxx_adc_chan_result *result)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (!pm8xxx_adc_initialized)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (!adc_pmic->mpp_base) {
|
||
|
rc = -EINVAL;
|
||
|
pr_info("PM8xxx MPP base invalid with error %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
if (mpp_num == PM8XXX_AMUX_MPP_8) {
|
||
|
rc = -EINVAL;
|
||
|
pr_info("PM8xxx MPP8 is already configured "
|
||
|
"to AMUX8. Use pm8xxx_adc_read() instead.\n");
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&adc_pmic->mpp_adc_lock);
|
||
|
|
||
|
rc = pm8xxx_mpp_config(((mpp_num - 1) + adc_pmic->mpp_base),
|
||
|
&pm8xxx_adc_mpp_config);
|
||
|
if (rc < 0) {
|
||
|
pr_err("pm8xxx adc mpp config error with %d\n", rc);
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
usleep_range(PM8XXX_ADC_MPP_SETTLE_TIME_MIN,
|
||
|
PM8XXX_ADC_MPP_SETTLE_TIME_MAX);
|
||
|
|
||
|
rc = pm8xxx_adc_read(channel, result);
|
||
|
if (rc < 0)
|
||
|
pr_err("pm8xxx adc read error with %d\n", rc);
|
||
|
|
||
|
rc = pm8xxx_mpp_config(((mpp_num - 1) + adc_pmic->mpp_base),
|
||
|
&pm8xxx_adc_mpp_unconfig);
|
||
|
if (rc < 0)
|
||
|
pr_err("pm8xxx adc mpp config error with %d\n", rc);
|
||
|
fail:
|
||
|
mutex_unlock(&adc_pmic->mpp_adc_lock);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pm8xxx_adc_mpp_config_read);
|
||
|
|
||
|
uint32_t pm8xxx_adc_btm_configure(struct pm8xxx_adc_arb_btm_param *btm_param)
|
||
|
{
|
||
|
struct pm8xxx_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 = pm8xxx_adc_batt_scaler(btm_param, adc_pmic->adc_prop,
|
||
|
adc_pmic->conv->chan_prop);
|
||
|
if (rc < 0) {
|
||
|
pr_err("Failed to lookup the BTM thresholds\n");
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
if (btm_param->interval > PM8XXX_ADC_BTM_INTERVAL_MAX) {
|
||
|
pr_info("Bug in PMIC BTM interval time and cannot set"
|
||
|
" a value greater than 0x14 %x\n", btm_param->interval);
|
||
|
btm_param->interval = PM8XXX_ADC_BTM_INTERVAL_MAX;
|
||
|
}
|
||
|
|
||
|
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 = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_COOL_THR0,
|
||
|
data_btm_cool_thr0);
|
||
|
if (rc < 0)
|
||
|
goto write_err;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_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 = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_WARM_THR0,
|
||
|
data_btm_warm_thr0);
|
||
|
if (rc < 0)
|
||
|
goto write_err;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_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 = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_BTM_CNTRL1, &arb_btm_cntrl1);
|
||
|
if (rc < 0)
|
||
|
goto bail_out;
|
||
|
|
||
|
btm_param->interval &= PM8XXX_ADC_BTM_INTERVAL_SEL_MASK;
|
||
|
arb_btm_cntrl1 |=
|
||
|
btm_param->interval << PM8XXX_ADC_BTM_INTERVAL_SEL_SHIFT;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_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(pm8xxx_adc_btm_configure);
|
||
|
|
||
|
static uint32_t pm8xxx_adc_btm_read(uint32_t channel)
|
||
|
{
|
||
|
struct pm8xxx_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 << PM8XXX_ADC_BTM_CHANNEL_SEL;
|
||
|
arb_btm_rsv = adc_pmic->adc_channel[channel].adc_rsv;
|
||
|
arb_btm_dig_param = arb_btm_ana_param = PM8XXX_ADC_ARB_ANA_DIG;
|
||
|
|
||
|
spin_lock_irqsave(&adc_pmic->btm_lock, flags);
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_AMUX_CNTRL,
|
||
|
arb_btm_amux_cntrl);
|
||
|
if (rc < 0)
|
||
|
goto write_err;
|
||
|
|
||
|
arb_btm_rsv = PM8XXX_ADC_BTM_RSV;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_RSV, arb_btm_rsv);
|
||
|
if (rc < 0)
|
||
|
goto write_err;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_DIG_PARAM,
|
||
|
arb_btm_dig_param);
|
||
|
if (rc < 0)
|
||
|
goto write_err;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_ANA_PARAM,
|
||
|
arb_btm_ana_param);
|
||
|
if (rc < 0)
|
||
|
goto write_err;
|
||
|
|
||
|
data_arb_btm_cntrl |= PM8XXX_ADC_ARB_BTM_CNTRL1_EN_BTM;
|
||
|
|
||
|
for (i = 0; i < 2; i++) {
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1,
|
||
|
data_arb_btm_cntrl);
|
||
|
if (rc < 0)
|
||
|
goto write_err;
|
||
|
}
|
||
|
|
||
|
data_arb_btm_cntrl |= PM8XXX_ADC_ARB_BTM_CNTRL1_REQ
|
||
|
| PM8XXX_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE;
|
||
|
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_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 pm8xxx_adc_btm_start(void)
|
||
|
{
|
||
|
return pm8xxx_adc_btm_read(CHANNEL_BATT_THERM);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pm8xxx_adc_btm_start);
|
||
|
|
||
|
uint32_t pm8xxx_adc_btm_end(void)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
int i, rc;
|
||
|
u8 data_arb_btm_cntrl = 0;
|
||
|
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);
|
||
|
|
||
|
/* Write twice to the CNTRL register for the arbiter settings
|
||
|
to take into effect */
|
||
|
for (i = 0; i < 2; i++) {
|
||
|
rc = pm8xxx_adc_write_reg(PM8XXX_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(pm8xxx_adc_btm_end);
|
||
|
|
||
|
static ssize_t pm8xxx_adc_show(struct device *dev,
|
||
|
struct device_attribute *devattr, char *buf)
|
||
|
{
|
||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||
|
struct pm8xxx_adc_chan_result result;
|
||
|
int rc = -1;
|
||
|
|
||
|
rc = pm8xxx_adc_read(attr->index, &result);
|
||
|
|
||
|
if (rc)
|
||
|
return 0;
|
||
|
|
||
|
return snprintf(buf, PM8XXX_ADC_HWMON_NAME_LENGTH,
|
||
|
"Result:%lld Raw:%d\n", result.physical, result.adc_code);
|
||
|
}
|
||
|
|
||
|
static int get_adc(void *data, u64 *val)
|
||
|
{
|
||
|
struct pm8xxx_adc_chan_result result;
|
||
|
int i = (int)data;
|
||
|
int rc;
|
||
|
|
||
|
rc = pm8xxx_adc_read(i, &result);
|
||
|
if (!rc)
|
||
|
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, "%lld\n");
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
static void create_debugfs_entries(void)
|
||
|
{
|
||
|
int i = 0;
|
||
|
pmic_adc->dent = debugfs_create_dir("pm8xxx_adc", NULL);
|
||
|
|
||
|
if (IS_ERR(pmic_adc->dent)) {
|
||
|
pr_err("pmic adc debugfs dir not created\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < pmic_adc->adc_num_board_channel; i++)
|
||
|
debugfs_create_file(pmic_adc->adc_channel[i].name,
|
||
|
0644, pmic_adc->dent,
|
||
|
(void *)pmic_adc->adc_channel[i].channel_name,
|
||
|
®_fops);
|
||
|
}
|
||
|
#else
|
||
|
static inline void create_debugfs_entries(void)
|
||
|
{
|
||
|
}
|
||
|
#endif
|
||
|
static struct sensor_device_attribute pm8xxx_adc_attr =
|
||
|
SENSOR_ATTR(NULL, S_IRUGO, pm8xxx_adc_show, NULL, 0);
|
||
|
|
||
|
static int32_t pm8xxx_adc_init_hwmon(struct platform_device *pdev)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
int rc = 0, i, channel;
|
||
|
|
||
|
for (i = 0; i < pmic_adc->adc_num_board_channel; i++) {
|
||
|
channel = adc_pmic->adc_channel[i].channel_name;
|
||
|
if (pm8xxx_adc_check_channel_valid(channel)) {
|
||
|
pr_err("Invalid ADC init HWMON channel: %d\n", channel);
|
||
|
continue;
|
||
|
}
|
||
|
pm8xxx_adc_attr.index = adc_pmic->adc_channel[i].channel_name;
|
||
|
pm8xxx_adc_attr.dev_attr.attr.name =
|
||
|
adc_pmic->adc_channel[i].name;
|
||
|
memcpy(&adc_pmic->sens_attr[i], &pm8xxx_adc_attr,
|
||
|
sizeof(pm8xxx_adc_attr));
|
||
|
sysfs_attr_init(&adc_pmic->sens_attr[i].dev_attr.attr);
|
||
|
rc = device_create_file(&pdev->dev,
|
||
|
&adc_pmic->sens_attr[i].dev_attr);
|
||
|
if (rc) {
|
||
|
dev_err(&pdev->dev, "device_create_file failed for "
|
||
|
"dev %s\n",
|
||
|
adc_pmic->adc_channel[i].name);
|
||
|
goto hwmon_err_sens;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
hwmon_err_sens:
|
||
|
pr_info("Init HWMON failed for pm8xxx_adc with %d\n", rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_PM
|
||
|
static int pm8xxx_adc_suspend_noirq(struct device *dev)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
|
||
|
adc_pmic->msm_suspend_check = 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int pm8xxx_adc_resume_noirq(struct device *dev)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
|
||
|
adc_pmic->msm_suspend_check = 0;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct dev_pm_ops pm8xxx_adc_dev_pm_ops = {
|
||
|
.suspend_noirq = pm8xxx_adc_suspend_noirq,
|
||
|
.resume_noirq = pm8xxx_adc_resume_noirq,
|
||
|
};
|
||
|
|
||
|
#define PM8XXX_ADC_DEV_PM_OPS (&pm8xxx_adc_dev_pm_ops)
|
||
|
#else
|
||
|
#define PM8XXX_ADC_DEV_PM_OPS NULL
|
||
|
#endif
|
||
|
|
||
|
static int __devexit pm8xxx_adc_teardown(struct platform_device *pdev)
|
||
|
{
|
||
|
struct pm8xxx_adc *adc_pmic = pmic_adc;
|
||
|
int i;
|
||
|
|
||
|
msm_xo_put(adc_pmic->adc_voter);
|
||
|
platform_set_drvdata(pdev, NULL);
|
||
|
pmic_adc = NULL;
|
||
|
if (!pa_therm) {
|
||
|
regulator_put(pa_therm);
|
||
|
pa_therm = NULL;
|
||
|
}
|
||
|
for (i = 0; i < adc_pmic->adc_num_board_channel; i++)
|
||
|
device_remove_file(adc_pmic->dev,
|
||
|
&adc_pmic->sens_attr[i].dev_attr);
|
||
|
pm8xxx_adc_initialized = false;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __devinit pm8xxx_adc_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
const struct pm8xxx_adc_platform_data *pdata = pdev->dev.platform_data;
|
||
|
struct pm8xxx_adc *adc_pmic;
|
||
|
struct pm8xxx_adc_amux_properties *adc_amux_prop;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (!pdata) {
|
||
|
dev_err(&pdev->dev, "no platform data?\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
adc_pmic = devm_kzalloc(&pdev->dev, sizeof(struct pm8xxx_adc) +
|
||
|
(sizeof(struct sensor_device_attribute) *
|
||
|
pdata->adc_num_board_channel), GFP_KERNEL);
|
||
|
if (!adc_pmic) {
|
||
|
dev_err(&pdev->dev, "Unable to allocate memory\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
adc_amux_prop = devm_kzalloc(&pdev->dev,
|
||
|
sizeof(struct pm8xxx_adc_amux_properties) +
|
||
|
sizeof(struct pm8xxx_adc_chan_properties)
|
||
|
, GFP_KERNEL);
|
||
|
if (!adc_amux_prop) {
|
||
|
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;
|
||
|
init_completion(&adc_pmic->adc_rslt_completion);
|
||
|
adc_pmic->adc_channel = pdata->adc_channel;
|
||
|
adc_pmic->adc_num_board_channel = pdata->adc_num_board_channel;
|
||
|
adc_pmic->mpp_base = pdata->adc_mpp_base;
|
||
|
|
||
|
mutex_init(&adc_pmic->adc_lock);
|
||
|
mutex_init(&adc_pmic->mpp_adc_lock);
|
||
|
spin_lock_init(&adc_pmic->btm_lock);
|
||
|
|
||
|
adc_pmic->adc_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_0);
|
||
|
if (adc_pmic->adc_irq < 0)
|
||
|
return adc_pmic->adc_irq;
|
||
|
|
||
|
rc = devm_request_irq(&pdev->dev, adc_pmic->adc_irq,
|
||
|
pm8xxx_adc_isr,
|
||
|
IRQF_TRIGGER_RISING, "pm8xxx_adc_interrupt", adc_pmic);
|
||
|
if (rc) {
|
||
|
dev_err(&pdev->dev, "failed to request adc irq "
|
||
|
"with error %d\n", rc);
|
||
|
} else {
|
||
|
enable_irq_wake(adc_pmic->adc_irq);
|
||
|
}
|
||
|
|
||
|
adc_pmic->btm_warm_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_1);
|
||
|
if (adc_pmic->btm_warm_irq < 0)
|
||
|
return adc_pmic->btm_warm_irq;
|
||
|
|
||
|
rc = devm_request_irq(&pdev->dev, adc_pmic->btm_warm_irq,
|
||
|
pm8xxx_btm_warm_isr,
|
||
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||
|
"pm8xxx_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");
|
||
|
}
|
||
|
|
||
|
disable_irq_nosync(adc_pmic->btm_warm_irq);
|
||
|
|
||
|
adc_pmic->btm_cool_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_2);
|
||
|
if (adc_pmic->btm_cool_irq < 0)
|
||
|
return adc_pmic->btm_cool_irq;
|
||
|
|
||
|
rc = devm_request_irq(&pdev->dev, adc_pmic->btm_cool_irq,
|
||
|
pm8xxx_btm_cool_isr,
|
||
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||
|
"pm8xxx_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");
|
||
|
}
|
||
|
|
||
|
disable_irq_nosync(adc_pmic->btm_cool_irq);
|
||
|
platform_set_drvdata(pdev, adc_pmic);
|
||
|
adc_pmic->msm_suspend_check = 0;
|
||
|
pmic_adc = adc_pmic;
|
||
|
|
||
|
INIT_WORK(&adc_pmic->warm_work, pm8xxx_adc_btm_warm_scheduler_fn);
|
||
|
INIT_WORK(&adc_pmic->cool_work, pm8xxx_adc_btm_cool_scheduler_fn);
|
||
|
create_debugfs_entries();
|
||
|
pm8xxx_adc_calib_first_adc = false;
|
||
|
pm8xxx_adc_calib_device_init = false;
|
||
|
pm8xxx_adc_initialized = true;
|
||
|
|
||
|
rc = pm8xxx_adc_init_hwmon(pdev);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx adc init hwmon failed with %d\n", rc);
|
||
|
dev_err(&pdev->dev, "failed to initialize pm8xxx hwmon adc\n");
|
||
|
}
|
||
|
adc_pmic->hwmon = hwmon_device_register(adc_pmic->dev);
|
||
|
|
||
|
if (adc_pmic->adc_voter == NULL) {
|
||
|
adc_pmic->adc_voter = msm_xo_get(MSM_XO_TCXO_D0, "pmic_xoadc");
|
||
|
if (IS_ERR(adc_pmic->adc_voter)) {
|
||
|
dev_err(&pdev->dev, "Failed to get XO vote\n");
|
||
|
return PTR_ERR(adc_pmic->adc_voter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pa_therm = regulator_get(adc_pmic->dev, "pa_therm");
|
||
|
if (IS_ERR(pa_therm)) {
|
||
|
rc = PTR_ERR(pa_therm);
|
||
|
pr_err("failed to request pa_therm vreg with error %d\n", rc);
|
||
|
pa_therm = NULL;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver pm8xxx_adc_driver = {
|
||
|
.probe = pm8xxx_adc_probe,
|
||
|
.remove = __devexit_p(pm8xxx_adc_teardown),
|
||
|
.driver = {
|
||
|
.name = PM8XXX_ADC_DEV_NAME,
|
||
|
.owner = THIS_MODULE,
|
||
|
.pm = PM8XXX_ADC_DEV_PM_OPS,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init pm8xxx_adc_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&pm8xxx_adc_driver);
|
||
|
}
|
||
|
module_init(pm8xxx_adc_init);
|
||
|
|
||
|
static void __exit pm8xxx_adc_exit(void)
|
||
|
{
|
||
|
platform_driver_unregister(&pm8xxx_adc_driver);
|
||
|
}
|
||
|
module_exit(pm8xxx_adc_exit);
|
||
|
|
||
|
MODULE_ALIAS("platform:" PM8XXX_ADC_DEV_NAME);
|
||
|
MODULE_DESCRIPTION("PMIC8921/8018 ADC driver");
|
||
|
MODULE_LICENSE("GPL v2");
|