diff options
| author | Doug Zobel <dzobel1@motorola.com> | 2013-11-15 14:29:07 -0600 | 
|---|---|---|
| committer | James Wylder <jwylder@motorola.com> | 2014-03-05 17:46:52 -0600 | 
| commit | d2a782003a6047da120a33e6f8ee6fd33bb825d6 (patch) | |
| tree | 8d20bd4ecda62a06e98993c4108456bc1acb0d0b /drivers/mfd/cpcap-adc.c | |
| parent | 32fd2d36d2464056d4522a9c02797b7c2b2e884f (diff) | |
| download | olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.tar.xz olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.zip | |
CW integration and minnow bringup
  * create minnow machine type
  * create Android makefile
  * add pre-commit syntax check
  * enable -Werror
  * Add drivers: CPCAP, TPS65xxx, m4sensorhub, atmxt, lm3535,
                 usb gadget, minnow display, TI 12xx wireless
Change-Id: I7962f5e1256715f2452aed5a62a4f2f2383d5046
Diffstat (limited to 'drivers/mfd/cpcap-adc.c')
| -rw-r--r-- | drivers/mfd/cpcap-adc.c | 708 | 
1 files changed, 708 insertions, 0 deletions
| diff --git a/drivers/mfd/cpcap-adc.c b/drivers/mfd/cpcap-adc.c new file mode 100644 index 00000000000..6389c23ea61 --- /dev/null +++ b/drivers/mfd/cpcap-adc.c @@ -0,0 +1,708 @@ +/* + * Copyright (C) 2009-2010 Motorola, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/completion.h> +#include <linux/sched.h> + +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> +#include <linux/spi/spi.h> + + +#define MAX_ADC_FIFO_DEPTH 8 /* this must be a power of 2 */ +#define MAX_TEMP_LVL 27 +#define FOUR_POINT_TWO_ADC 801 + +#define DEFAULT_ICHARGE_SENSE_RES 100 +#define DEFAULT_ISENSE_RANGE 5000 + +struct cpcap_adc { +	struct cpcap_device *cpcap; + +	/* Private stuff */ +	struct cpcap_adc_request *queue[MAX_ADC_FIFO_DEPTH]; +	int queue_head; +	int queue_tail; +	struct mutex queue_mutex; +	struct delayed_work work; +}; + +struct phasing_tbl { +	short offset; +	unsigned short multiplier; +	unsigned short divider; +	short min; +	short max; +}; + +static struct phasing_tbl bank0_phasing[CPCAP_ADC_BANK0_NUM] = { +	[CPCAP_ADC_AD0_BATTDETB] = {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_BATTP] =        {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_VBUS] =         {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_AD3] =          {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_BPLUS_AD4] =    {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_CHG_ISENSE] =   {0, 0x80, 0x80, -512,  511}, +	[CPCAP_ADC_BATTI_ADC] =    {0, 0x80, 0x80, -512,  511}, +	[CPCAP_ADC_USB_ID] =       {0, 0x80, 0x80,    0, 1023}, +}; + +static struct phasing_tbl bank1_phasing[CPCAP_ADC_BANK1_NUM] = { +	[CPCAP_ADC_AD8] =          {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_AD9] =          {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_LICELL] =       {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_HV_BATTP] =     {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_TSX1_AD12] =    {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_TSX2_AD13] =    {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_TSY1_AD14] =    {0, 0x80, 0x80,    0, 1023}, +	[CPCAP_ADC_TSY2_AD15] =    {0, 0x80, 0x80,    0, 1023}, +}; + +enum conv_type { +	CONV_TYPE_NONE, +	CONV_TYPE_DIRECT, +	CONV_TYPE_MAPPING, +}; + +struct conversion_tbl { +	enum conv_type conv_type; +	int align_offset; +	int conv_offset; +	int cal_offset; +	int multiplier; +	int divider; +}; + +static struct conversion_tbl bank0_conversion[CPCAP_ADC_BANK0_NUM] = { +	[CPCAP_ADC_AD0_BATTDETB] = { +		CONV_TYPE_MAPPING,   0,    0, 0,     1,    1}, +	[CPCAP_ADC_BATTP] = { +		CONV_TYPE_DIRECT,    0, 2400, 0,  2300, 1023}, +	[CPCAP_ADC_VBUS] = { +		CONV_TYPE_DIRECT,    0,    0, 0, 10000, 1023}, +	[CPCAP_ADC_AD3] = { +		CONV_TYPE_MAPPING,   0,    0, 0,     1,    1}, +	[CPCAP_ADC_BPLUS_AD4] = { +		CONV_TYPE_DIRECT,    0, 2400, 0,  2300, 1023}, +	/* ISENSE multiplier is updated in probe function below */ +	[CPCAP_ADC_CHG_ISENSE] = { +		CONV_TYPE_DIRECT, -512,    2, 0,  5000, 1023}, +	[CPCAP_ADC_BATTI_ADC] =	{ +		CONV_TYPE_DIRECT, -512,    2, 0,  5000, 1023}, +	[CPCAP_ADC_USB_ID] = { +		CONV_TYPE_NONE,      0,    0, 0,     1,    1}, +}; + +static struct conversion_tbl bank1_conversion[CPCAP_ADC_BANK1_NUM] = { +	[CPCAP_ADC_AD8] =          {CONV_TYPE_NONE,   0,  0,  0,    1,    1}, +	[CPCAP_ADC_AD9] =          {CONV_TYPE_NONE,   0,  0,  0,    1,    1}, +	[CPCAP_ADC_LICELL] =       {CONV_TYPE_DIRECT, 0,  0,  0, 3400, 1023}, +	[CPCAP_ADC_HV_BATTP] =     {CONV_TYPE_NONE,   0,  0,  0,    1,    1}, +	[CPCAP_ADC_TSX1_AD12] =    {CONV_TYPE_NONE,   0,  0,  0,    1,    1}, +	[CPCAP_ADC_TSX2_AD13] =    {CONV_TYPE_NONE,   0,  0,  0,    1,    1}, +	[CPCAP_ADC_TSY1_AD14] =    {CONV_TYPE_NONE,   0,  0,  0,    1,    1}, +	[CPCAP_ADC_TSY2_AD15] =    {CONV_TYPE_NONE,   0,  0,  0,    1,    1}, +}; + +static const unsigned short temp_map[MAX_TEMP_LVL][2] = { +    {0x03ff, 233}, /* -40C */ +    {0x03ff, 238}, /* -35C */ +    {0x03ef, 243}, /* -30C */ +    {0x03b2, 248}, /* -25C */ +    {0x036c, 253}, /* -20C */ +    {0x0320, 258}, /* -15C */ +    {0x02d0, 263}, /* -10C */ +    {0x027f, 268}, /*  -5C */ +    {0x022f, 273}, /*   0C */ +    {0x01e4, 278}, /*   5C */ +    {0x019f, 283}, /*  10C */ +    {0x0161, 288}, /*  15C */ +    {0x012b, 293}, /*  20C */ +    {0x00fc, 298}, /*  25C */ +    {0x00d4, 303}, /*  30C */ +    {0x00b2, 308}, /*  35C */ +    {0x0095, 313}, /*  40C */ +    {0x007d, 318}, /*  45C */ +    {0x0069, 323}, /*  50C */ +    {0x0059, 328}, /*  55C */ +    {0x004b, 333}, /*  60C */ +    {0x003f, 338}, /*  65C */ +    {0x0036, 343}, /*  70C */ +    {0x002e, 348}, /*  75C */ +    {0x0027, 353}, /*  80C */ +    {0x0022, 358}, /*  85C */ +    {0x001d, 363}, /*  90C */ +}; + +static unsigned short convert_to_kelvins(unsigned short value) +{ +	int i; +	unsigned short result = 0; +	signed short alpha = 0; + +	if (value <= temp_map[MAX_TEMP_LVL - 1][0]) +		return temp_map[MAX_TEMP_LVL - 1][1]; + +	if (value >= temp_map[0][0]) +		return temp_map[0][1]; + +	for (i = 0; i < MAX_TEMP_LVL - 1; i++) { +		if ((value <= temp_map[i][0]) && +		    (value >= temp_map[i+1][0])) { +			if (value == temp_map[i][0]) +				result = temp_map[i][1]; +			else if (value == temp_map[i+1][0]) +				result = temp_map[i+1][1]; +			else { +				alpha = ((value - temp_map[i][0])*1000)/ +					(temp_map[i+1][0] - temp_map[i][0]); + +				result = temp_map[i][1] + +					((alpha*(temp_map[i+1][1] - +						 temp_map[i][1]))/1000); +			} +			break; +		} +	} +	return result; +} + +static void adc_setup(struct cpcap_device *cpcap, +		      struct cpcap_adc_request *req) +{ +	struct cpcap_adc_ato *ato; +	struct cpcap_platform_data *data; +	unsigned short value1 = 0; +	unsigned short value2 = 0; + +	data = cpcap->spi->controller_data; +	ato = data->adc_ato; + +	if (req->type == CPCAP_ADC_TYPE_BANK_1) +		value1 |= CPCAP_BIT_AD_SEL1; +	else if (req->type == CPCAP_ADC_TYPE_BATT_PI) +		value1 |= CPCAP_BIT_RAND1; + +	switch (req->timing) { +	case CPCAP_ADC_TIMING_IN: +		value1 |= ato->ato_in; +		value1 |= ato->atox_in; +		value2 |= ato->adc_ps_factor_in; +		value2 |= ato->atox_ps_factor_in; +		break; + +	case CPCAP_ADC_TIMING_OUT: +		value1 |= ato->ato_out; +		value1 |= ato->atox_out; +		value2 |= ato->adc_ps_factor_out; +		value2 |= ato->atox_ps_factor_out; +		break; + +	case CPCAP_ADC_TIMING_IMM: +	default: +		break; +	} + +	cpcap_regacc_write(cpcap, CPCAP_REG_ADCC1, value1, +			   (CPCAP_BIT_CAL_MODE | CPCAP_BIT_ATOX | +			    CPCAP_BIT_ATO3 | CPCAP_BIT_ATO2 | +			    CPCAP_BIT_ATO1 | CPCAP_BIT_ATO0 | +			    CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | +			    CPCAP_BIT_ADA0 | CPCAP_BIT_AD_SEL1 | +			    CPCAP_BIT_RAND1 | CPCAP_BIT_RAND0)); + +	cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, value2, +			   (CPCAP_BIT_ATOX_PS_FACTOR | +			    CPCAP_BIT_ADC_PS_FACTOR1 | +			    CPCAP_BIT_ADC_PS_FACTOR0)); + +	if (req->timing == CPCAP_ADC_TIMING_IMM) { +		cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, +				   CPCAP_BIT_ADTRIG_DIS, +				   CPCAP_BIT_ADTRIG_DIS); +		cpcap_irq_clear(cpcap, CPCAP_IRQ_ADCDONE); +		cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, +				   CPCAP_BIT_ASC, +				   CPCAP_BIT_ASC); +	} else { +		cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, +				   CPCAP_BIT_ADTRIG_ONESHOT, +				   CPCAP_BIT_ADTRIG_ONESHOT); +		cpcap_irq_clear(cpcap, CPCAP_IRQ_ADCDONE); +		cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, +				   0, +				   CPCAP_BIT_ADTRIG_DIS); +	} + +	schedule_delayed_work(&((struct cpcap_adc *)(cpcap->adcdata))->work, +			      msecs_to_jiffies(500)); + +	cpcap_irq_unmask(cpcap, CPCAP_IRQ_ADCDONE); +} + +static void adc_setup_calibrate(struct cpcap_device *cpcap, +				enum cpcap_adc_bank0 chan) +{ +	unsigned short value = 0; +	unsigned long timeout = jiffies + msecs_to_jiffies(11); + +	if ((chan != CPCAP_ADC_CHG_ISENSE) && +	    (chan != CPCAP_ADC_BATTI_ADC)) +		return; + +	value |= CPCAP_BIT_CAL_MODE | CPCAP_BIT_RAND0; +	value |= ((chan << 4) & +		   (CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | CPCAP_BIT_ADA0)); + +	cpcap_regacc_write(cpcap, CPCAP_REG_ADCC1, value, +			   (CPCAP_BIT_CAL_MODE | CPCAP_BIT_ATOX | +			    CPCAP_BIT_ATO3 | CPCAP_BIT_ATO2 | +			    CPCAP_BIT_ATO1 | CPCAP_BIT_ATO0 | +			    CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | +			    CPCAP_BIT_ADA0 | CPCAP_BIT_AD_SEL1 | +			    CPCAP_BIT_RAND1 | CPCAP_BIT_RAND0)); + +	cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, 0, +			   (CPCAP_BIT_ATOX_PS_FACTOR | +			    CPCAP_BIT_ADC_PS_FACTOR1 | +			    CPCAP_BIT_ADC_PS_FACTOR0)); + +	cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, +			   CPCAP_BIT_ADTRIG_DIS, +			   CPCAP_BIT_ADTRIG_DIS); + +	cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, +			   CPCAP_BIT_ASC, +			   CPCAP_BIT_ASC); + +	do { +		schedule_timeout_uninterruptible(1); +		cpcap_regacc_read(cpcap, CPCAP_REG_ADCC2, &value); +	} while ((value & CPCAP_BIT_ASC) && time_before(jiffies, timeout)); + +	if (value & CPCAP_BIT_ASC) +		dev_err(&(cpcap->spi->dev), +			"Timeout waiting for calibration to complete\n"); + +	cpcap_irq_clear(cpcap, CPCAP_IRQ_ADCDONE); + +	cpcap_regacc_write(cpcap, CPCAP_REG_ADCC1, 0, CPCAP_BIT_CAL_MODE); +} + +static void trigger_next_adc_job_if_any(struct cpcap_device *cpcap) +{ +	struct cpcap_adc *adc = cpcap->adcdata; +	int head; + +	mutex_lock(&adc->queue_mutex); + +	head = adc->queue_head; + +	if (!adc->queue[head]) { +		mutex_unlock(&adc->queue_mutex); +		return; +	} +	mutex_unlock(&adc->queue_mutex); + +	adc_setup(cpcap, adc->queue[head]); +} + +static int +adc_enqueue_request(struct cpcap_device *cpcap, struct cpcap_adc_request *req) +{ +	struct cpcap_adc *adc = cpcap->adcdata; +	int head; +	int tail; +	int running; + +	mutex_lock(&adc->queue_mutex); + +	head = adc->queue_head; +	tail = adc->queue_tail; +	running = (head != tail); + +	if (adc->queue[tail]) { +		mutex_unlock(&adc->queue_mutex); +		return -EBUSY; +	} + +	adc->queue[tail] = req; +	adc->queue_tail = (tail + 1) & (MAX_ADC_FIFO_DEPTH - 1); + +	mutex_unlock(&adc->queue_mutex); + +	if (!running) +		trigger_next_adc_job_if_any(cpcap); + +	return 0; +} + +static void +cpcap_adc_sync_read_callback(struct cpcap_device *cpcap, void *param) +{ +	struct cpcap_adc_request *req = param; + +	complete(&req->completion); +} + +int cpcap_adc_sync_read(struct cpcap_device *cpcap, +			struct cpcap_adc_request *request) +{ +	int ret; + +	request->callback = cpcap_adc_sync_read_callback; +	request->callback_param = request; +	init_completion(&request->completion); +	ret = adc_enqueue_request(cpcap, request); +	if (ret) +		return ret; +	wait_for_completion(&request->completion); + +	return 0; +} +EXPORT_SYMBOL_GPL(cpcap_adc_sync_read); + +int cpcap_adc_async_read(struct cpcap_device *cpcap, +			 struct cpcap_adc_request *request) +{ +	return adc_enqueue_request(cpcap, request); +} +EXPORT_SYMBOL_GPL(cpcap_adc_async_read); + +void cpcap_adc_phase(struct cpcap_device *cpcap, struct cpcap_adc_phase *phase) +{ +	bank0_phasing[CPCAP_ADC_BATTI_ADC].offset = phase->offset_batti; +	bank0_phasing[CPCAP_ADC_BATTI_ADC].multiplier = phase->slope_batti; + +	bank0_phasing[CPCAP_ADC_CHG_ISENSE].offset = phase->offset_chrgi; +	bank0_phasing[CPCAP_ADC_CHG_ISENSE].multiplier = phase->slope_chrgi; + +	bank0_phasing[CPCAP_ADC_BATTP].offset = phase->offset_battp; +	bank0_phasing[CPCAP_ADC_BATTP].multiplier = phase->slope_battp; + +	bank0_phasing[CPCAP_ADC_BPLUS_AD4].offset = phase->offset_bp; +	bank0_phasing[CPCAP_ADC_BPLUS_AD4].multiplier = phase->slope_bp; + +	bank0_phasing[CPCAP_ADC_AD0_BATTDETB].offset = phase->offset_battt; +	bank0_phasing[CPCAP_ADC_AD0_BATTDETB].multiplier = phase->slope_battt; + +	bank0_phasing[CPCAP_ADC_VBUS].offset = phase->offset_chrgv; +	bank0_phasing[CPCAP_ADC_VBUS].multiplier = phase->slope_chrgv; +} +EXPORT_SYMBOL_GPL(cpcap_adc_phase); + +static void adc_phase(struct cpcap_adc_request *req, int index) +{ +	struct conversion_tbl *conv_tbl = bank0_conversion; +	struct phasing_tbl *phase_tbl = bank0_phasing; +	int tbl_index = index; + +	if (req->type == CPCAP_ADC_TYPE_BANK_1) { +		conv_tbl = bank1_conversion; +		phase_tbl = bank1_phasing; +	} + +	if (req->type == CPCAP_ADC_TYPE_BATT_PI) +		tbl_index = (tbl_index % 2) ? CPCAP_ADC_BATTI_ADC : +			    CPCAP_ADC_BATTP; + +	if (((req->type == CPCAP_ADC_TYPE_BANK_0) || +	    (req->type == CPCAP_ADC_TYPE_BATT_PI)) && +	    (tbl_index == CPCAP_ADC_BATTP)) { +		req->result[index] -= phase_tbl[tbl_index].offset; +		req->result[index] -= FOUR_POINT_TWO_ADC; +		req->result[index] *= phase_tbl[tbl_index].multiplier; +		req->result[index] /= phase_tbl[tbl_index].divider; +		req->result[index] += FOUR_POINT_TWO_ADC; +	} else { +		req->result[index] += conv_tbl[tbl_index].cal_offset; +		req->result[index] += conv_tbl[tbl_index].align_offset; +		req->result[index] *= phase_tbl[tbl_index].multiplier; +		req->result[index] /= phase_tbl[tbl_index].divider; +		req->result[index] += phase_tbl[tbl_index].offset; +	} + +	if (req->result[index] < phase_tbl[tbl_index].min) +		req->result[index] = phase_tbl[tbl_index].min; +	else if (req->result[index] > phase_tbl[tbl_index].max) +		req->result[index] = phase_tbl[tbl_index].max; +} + +static void adc_convert(struct cpcap_adc_request *req, int index) +{ +	struct conversion_tbl *conv_tbl = bank0_conversion; +	int tbl_index = index; + +	if (req->type == CPCAP_ADC_TYPE_BANK_1) +		conv_tbl = bank1_conversion; + +	if (req->type == CPCAP_ADC_TYPE_BATT_PI) +		tbl_index = (tbl_index % 2) ? CPCAP_ADC_BATTI_ADC : +			    CPCAP_ADC_BATTP; + +	if (conv_tbl[tbl_index].conv_type == CONV_TYPE_DIRECT) { +		req->result[index] *= conv_tbl[tbl_index].multiplier; +		req->result[index] /= conv_tbl[tbl_index].divider; +		req->result[index] += conv_tbl[tbl_index].conv_offset; +	} else if (conv_tbl[tbl_index].conv_type == CONV_TYPE_MAPPING) +		req->result[index] = convert_to_kelvins(req->result[tbl_index]); +} + +static void adc_raw(struct cpcap_adc_request *req, int index) +{ +	struct conversion_tbl *conv_tbl = bank0_conversion; +	struct phasing_tbl *phase_tbl = bank0_phasing; +	int tbl_index = index; + +	if (req->type == CPCAP_ADC_TYPE_BANK_1) +		return; + +	if (req->type == CPCAP_ADC_TYPE_BATT_PI) +		tbl_index = (tbl_index % 2) ? CPCAP_ADC_BATTI_ADC : +			    CPCAP_ADC_BATTP; + +	req->result[index] += conv_tbl[tbl_index].cal_offset; + +	if (req->result[index] < +	    (phase_tbl[tbl_index].min - conv_tbl[tbl_index].align_offset)) { +		req->result[index] = (phase_tbl[tbl_index].min - +				      conv_tbl[tbl_index].align_offset); +	} else if (req->result[index] > +		   (phase_tbl[tbl_index].max - +		    conv_tbl[tbl_index].align_offset)) { +		req->result[index] = (phase_tbl[tbl_index].max - +				      conv_tbl[tbl_index].align_offset); +	} +} + +static void adc_result(struct cpcap_device *cpcap, +		       struct cpcap_adc_request *req) +{ +	int i; +	int j; +	unsigned short cal_data; + +	cal_data = 0; +	cpcap_regacc_read(cpcap, CPCAP_REG_ADCAL1, &cal_data); +	bank0_conversion[CPCAP_ADC_CHG_ISENSE].cal_offset = +		((short)cal_data * -1) + 512; + +	cal_data = 0; +	cpcap_regacc_read(cpcap, CPCAP_REG_ADCAL2, &cal_data); +	bank0_conversion[CPCAP_ADC_BATTI_ADC].cal_offset = +		((short)cal_data * -1) + 512; + + +	for (i = CPCAP_REG_ADCD0; i <= CPCAP_REG_ADCD7; i++) { +		j = i - CPCAP_REG_ADCD0; +		cpcap_regacc_read(cpcap, i, (unsigned short *)&req->result[j]); +		req->result[j] &= 0x3FF; + +		switch (req->format) { +		case CPCAP_ADC_FORMAT_PHASED: +			adc_phase(req, j); +			break; + +		case CPCAP_ADC_FORMAT_CONVERTED: +			adc_phase(req, j); +			adc_convert(req, j); +			break; + +		case CPCAP_ADC_FORMAT_RAW: +			adc_raw(req, j); +			break; + +		default: +			break; +		} +	} +} + +static void cpcap_adc_irq(enum cpcap_irqs irq, void *data) +{ +	struct cpcap_adc *adc = data; +	struct cpcap_device *cpcap = adc->cpcap; +	struct cpcap_adc_request *req; +	int head; + +	cancel_delayed_work_sync(&adc->work); + +	cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, +			   CPCAP_BIT_ADTRIG_DIS, +			   CPCAP_BIT_ADTRIG_DIS); + +	mutex_lock(&adc->queue_mutex); +	head = adc->queue_head; + +	req = adc->queue[head]; +	if (!req) { +		dev_info(&(cpcap->spi->dev), +			"cpcap_adc_irq: ADC queue empty!\n"); +		mutex_unlock(&adc->queue_mutex); +		return; +	} +	adc->queue[head] = NULL; +	adc->queue_head = (head + 1) & (MAX_ADC_FIFO_DEPTH - 1); + +	mutex_unlock(&adc->queue_mutex); + +	adc_result(cpcap, req); + +	trigger_next_adc_job_if_any(cpcap); + +	req->status = 0; + +	req->callback(cpcap, req->callback_param); +} + +static void cpcap_adc_cancel(struct work_struct *work) +{ +	int head; +	struct cpcap_adc_request *req; +	struct cpcap_adc *adc = +		container_of(work, struct cpcap_adc, work.work); + +	cpcap_irq_mask(adc->cpcap, CPCAP_IRQ_ADCDONE); + +	cpcap_regacc_write(adc->cpcap, CPCAP_REG_ADCC2, +			   CPCAP_BIT_ADTRIG_DIS, +			   CPCAP_BIT_ADTRIG_DIS); + +	mutex_lock(&adc->queue_mutex); +	head = adc->queue_head; + +	req = adc->queue[head]; +	if (!req) { +		dev_info(&(adc->cpcap->spi->dev), +			"cpcap_adc_cancel: ADC queue empty!\n"); +		mutex_unlock(&adc->queue_mutex); +		return; +	} +	adc->queue[head] = NULL; +	adc->queue_head = (head + 1) & (MAX_ADC_FIFO_DEPTH - 1); + +	mutex_unlock(&adc->queue_mutex); + +	req->status = -ETIMEDOUT; + +	req->callback(adc->cpcap, req->callback_param); + +	trigger_next_adc_job_if_any(adc->cpcap); +} + +static int cpcap_adc_probe(struct platform_device *pdev) +{ +	struct cpcap_adc *adc; +	unsigned short cal_data; +	struct cpcap_platform_data *data; + +	if (pdev->dev.platform_data == NULL) { +		dev_err(&pdev->dev, "no platform_data\n"); +		return -EINVAL; +	} + +	adc = kzalloc(sizeof(*adc), GFP_KERNEL); +	if (!adc) +		return -ENOMEM; + +	adc->cpcap = pdev->dev.platform_data; + +	platform_set_drvdata(pdev, adc); +	adc->cpcap->adcdata = adc; + +	data = adc->cpcap->spi->controller_data; + +	/* Scale ICHRG sense ADCs for non-standard sense resistor */ +	bank0_conversion[CPCAP_ADC_CHG_ISENSE].multiplier = +		(DEFAULT_ICHARGE_SENSE_RES * DEFAULT_ISENSE_RANGE) / +		data->adc_ato->ichrg_sense_res; + +	mutex_init(&adc->queue_mutex); + +	adc_setup_calibrate(adc->cpcap, CPCAP_ADC_CHG_ISENSE); +	adc_setup_calibrate(adc->cpcap, CPCAP_ADC_BATTI_ADC); + +	cal_data = 0; +	cpcap_regacc_read(adc->cpcap, CPCAP_REG_ADCAL1, &cal_data); +	bank0_conversion[CPCAP_ADC_CHG_ISENSE].cal_offset = +		((short)cal_data * -1) + 512; + +	cal_data = 0; +	cpcap_regacc_read(adc->cpcap, CPCAP_REG_ADCAL2, &cal_data); +	bank0_conversion[CPCAP_ADC_BATTI_ADC].cal_offset = +		((short)cal_data * -1) + 512; + +	INIT_DELAYED_WORK(&adc->work, cpcap_adc_cancel); + +	cpcap_irq_register(adc->cpcap, CPCAP_IRQ_ADCDONE, +			   cpcap_adc_irq, adc); + +	dev_info(&pdev->dev, "CPCAP ADC device probed\n"); + +	return 0; +} + +static int cpcap_adc_remove(struct platform_device *pdev) +{ +	struct cpcap_adc *adc = platform_get_drvdata(pdev); +	int head; + +	cancel_delayed_work_sync(&adc->work); + +	cpcap_irq_free(adc->cpcap, CPCAP_IRQ_ADCDONE); + +	mutex_lock(&adc->queue_mutex); +	head = adc->queue_head; + +	if (WARN_ON(adc->queue[head])) +		dev_err(&pdev->dev, +			"adc driver removed with request pending\n"); + +	mutex_unlock(&adc->queue_mutex); +	kfree(adc); + +	return 0; +} + +static struct platform_driver cpcap_adc_driver = { +	.driver = { +		.name = "cpcap_adc", +	}, +	.probe = cpcap_adc_probe, +	.remove = cpcap_adc_remove, +}; + +static int __init cpcap_adc_init(void) +{ +	return platform_driver_register(&cpcap_adc_driver); +} +subsys_initcall(cpcap_adc_init); + +static void __exit cpcap_adc_exit(void) +{ +	platform_driver_unregister(&cpcap_adc_driver); +} +module_exit(cpcap_adc_exit); + +MODULE_ALIAS("platform:cpcap_adc"); +MODULE_DESCRIPTION("CPCAP ADC driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); |