summaryrefslogtreecommitdiff
path: root/drivers/mfd/cpcap-adc.c
diff options
context:
space:
mode:
authorDoug Zobel <dzobel1@motorola.com>2013-11-15 14:29:07 -0600
committerJames Wylder <jwylder@motorola.com>2014-03-05 17:46:52 -0600
commitd2a782003a6047da120a33e6f8ee6fd33bb825d6 (patch)
tree8d20bd4ecda62a06e98993c4108456bc1acb0d0b /drivers/mfd/cpcap-adc.c
parent32fd2d36d2464056d4522a9c02797b7c2b2e884f (diff)
downloadolio-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.c708
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");