1
0
Fork 0
mirror of https://github.com/followmsi/android_kernel_google_msm.git synced 2024-11-06 23:17:41 +00:00
android_kernel_google_msm/drivers/hwmon/pm8xxx-adc.c
Siddartha Mohanadoss 73eebbf41a hwmon: pm8xxx-adc: Vote for a PMIC clock buffer.:
Incorrect PMIC_THERM/XO_THERM readings are seen when
the XO clock is disabled.

The internal PMIC therm relies on the PMIC band gap
reference which in turn is enabled when any of the
PMIC clock buffers are enabled. The XO_THERM relies
on the VREF_XO as its reference source which gets
enabled when any of the PMIC clock buffers are
switched on. Therefore vote for a clock buffer when
a PMIC_THERM/XO_THERM is requested.

CRs-Fixed: 392602
Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
(cherry picked from commit f9dfe1b7b70b634422fbbbf65756aa997536bd33)

Change-Id: I09e132dbc4e635b96cd08ece245a766d68ed27b4
Signed-off-by: Sridhar Gujje <sgujje@codeaurora.org>

Conflicts:

	drivers/hwmon/pm8xxx-adc.c

Signed-off-by: Sridhar Gujje <sgujje@codeaurora.org>
Signed-off-by: Sivasri Kumar Vanka <sivasri@codeaurora.org>
(cherry picked from commit 8ab75c713cef66317f9507b35cc08a5d7b49ae44)

Conflicts:

	drivers/hwmon/pm8xxx-adc.c

Signed-off-by: Sridhar Gujje <sgujje@codeaurora.org>
2013-03-15 17:08:44 -07:00

1332 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, "%llu\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,
&reg_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");