summaryrefslogtreecommitdiff
path: root/drivers/mfd
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
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')
-rw-r--r--drivers/mfd/Kconfig19
-rw-r--r--drivers/mfd/Makefile19
-rw-r--r--drivers/mfd/cpcap-adc.c708
-rw-r--r--drivers/mfd/cpcap-core.c577
-rw-r--r--drivers/mfd/cpcap-irq.c809
-rw-r--r--drivers/mfd/cpcap-key.c146
-rw-r--r--drivers/mfd/cpcap-regacc.c393
-rw-r--r--drivers/mfd/cpcap-uc.c927
-rw-r--r--drivers/mfd/cpcap-usb-det.c948
-rw-r--r--drivers/mfd/m4sensorhub-core.c569
-rw-r--r--drivers/mfd/m4sensorhub-irq.c675
-rw-r--r--drivers/mfd/m4sensorhub-panic.c248
-rw-r--r--drivers/mfd/m4sensorhub-reg.c384
-rw-r--r--drivers/mfd/m4sensorhub-reg.h188
-rw-r--r--drivers/mfd/m4sensorhub-stm32-fw.c419
-rw-r--r--drivers/mfd/tps65912-core.c11
-rw-r--r--drivers/mfd/tps65912-debugfs.c213
17 files changed, 7253 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index d54e985748b..2bed2fd1315 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -356,6 +356,12 @@ config EZX_PCAP
This enables the PCAP ASIC present on EZX Phones. This is
needed for MMC, TouchScreen, Sound, USB, etc..
+config MFD_CPCAP
+ tristate "Support for CPCAP"
+ depends on SPI
+ help
+ Say yes here if you want to include drivers for the CPCAP chip.
+
config MFD_VIPERBOARD
tristate "Nano River Technologies Viperboard"
select MFD_CORE
@@ -602,6 +608,12 @@ config MFD_DB8500_PRCMU
system controller running an XP70 microprocessor, which is accessed
through a register map.
+config MFD_M4SENSORHUB
+ tristate "Support for M4 Sensor Hub"
+ depends on I2C
+ help
+ Say yes here if you want to include drivers for the M4 sensor hub.
+
config MFD_STMPE
bool "STMicroelectronics STMPE"
depends on (I2C=y || SPI_MASTER=y) && GENERIC_HARDIRQS
@@ -841,6 +853,13 @@ config MFD_TPS65912_SPI
If you say yes here you get support for the TPS65912 series of
PM chips with SPI interface.
+config MFD_TPS65912_DEBUGFS
+ bool "TI TPS65912 Power Management chip debugfs support"
+ depends on MFD_TPS65912 && DEBUG_FS
+ help
+ If you say yes here you get support for the TPS65912 series of
+ PM chips debugfs register access.
+
config MFD_TPS80031
bool "TI TPS80031/TPS80032 Power Management chips"
depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 718e94a2a9a..c4aa99058c7 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -64,6 +64,7 @@ tps65912-objs := tps65912-core.o tps65912-irq.o
obj-$(CONFIG_MFD_TPS65912) += tps65912.o
obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o
obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o
+obj-$(CONFIG_MFD_TPS65912_DEBUGFS) += tps65912-debugfs.o
obj-$(CONFIG_MFD_TPS80031) += tps80031.o
obj-$(CONFIG_MENELAUS) += menelaus.o
@@ -155,3 +156,21 @@ obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o
obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o vexpress-sysreg.o
obj-$(CONFIG_MFD_RETU) += retu-mfd.o
obj-$(CONFIG_MFD_AS3711) += as3711.o
+
+cpcap-objs := cpcap-core.o \
+ cpcap-irq.o \
+ cpcap-regacc.o \
+ cpcap-usb-det.o \
+ cpcap-key.o \
+ cpcap-adc.o \
+ cpcap-uc.o
+obj-$(CONFIG_MFD_CPCAP) += cpcap.o
+
+m4sensorhub-objs := m4sensorhub-core.o \
+ m4sensorhub-reg.o \
+ m4sensorhub-irq.o \
+ m4sensorhub-panic.o \
+ m4sensorhub-stm32-fw.o
+
+obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub.o
+
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");
diff --git a/drivers/mfd/cpcap-core.c b/drivers/mfd/cpcap-core.c
new file mode 100644
index 00000000000..b248c9d0d4a
--- /dev/null
+++ b/drivers/mfd/cpcap-core.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) 2007-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/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/machine.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/cpcap.h>
+#include <linux/spi/cpcap-regbits.h>
+#include <linux/uaccess.h>
+#include <linux/reboot.h>
+#include <linux/notifier.h>
+#include <linux/delay.h>
+
+struct cpcap_driver_info {
+ struct list_head list;
+ struct platform_device *pdev;
+};
+
+static long ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static int cpcap_probe(struct spi_device *spi);
+static int cpcap_remove(struct spi_device *spi);
+
+const static struct file_operations cpcap_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = ioctl,
+};
+
+static struct miscdevice cpcap_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = CPCAP_DEV_NAME,
+ .fops = &cpcap_fops,
+};
+
+static struct spi_driver cpcap_driver = {
+ .driver = {
+ .name = "cpcap",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = cpcap_probe,
+ .remove = cpcap_remove,
+};
+
+static struct platform_device cpcap_adc_device = {
+ .name = "cpcap_adc",
+ .id = -1,
+ .dev.platform_data = NULL,
+};
+
+
+static struct platform_device cpcap_key_device = {
+ .name = "cpcap_key",
+ .id = -1,
+ .dev.platform_data = NULL,
+};
+
+static struct platform_device cpcap_batt_device = {
+ .name = "cpcap_battery",
+ .id = -1,
+ .dev.platform_data = NULL,
+};
+
+static struct platform_device cpcap_uc_device = {
+ .name = "cpcap_uc",
+ .id = -1,
+ .dev.platform_data = NULL,
+};
+
+static struct platform_device cpcap_rtc_device = {
+ .name = "cpcap_rtc",
+ .id = -1,
+ .dev.platform_data = NULL,
+};
+
+/* List of required CPCAP devices that will ALWAYS be present.
+ *
+ * DO NOT ADD NEW DEVICES TO THIS LIST! You must use cpcap_driver_register()
+ * for any new drivers for non-core functionality of CPCAP.
+ */
+static struct platform_device *cpcap_devices[] = {
+ &cpcap_uc_device,
+ &cpcap_adc_device,
+ &cpcap_key_device,
+ &cpcap_batt_device,
+ &cpcap_rtc_device,
+};
+
+static struct cpcap_device *misc_cpcap;
+
+static LIST_HEAD(cpcap_device_list);
+static DEFINE_MUTEX(cpcap_driver_lock);
+
+static int cpcap_reboot(struct notifier_block *this, unsigned long code,
+ void *cmd)
+{
+ int ret = -1;
+ int result = NOTIFY_DONE;
+ char *mode = cmd;
+ unsigned short value;
+ unsigned short counter = 0;
+
+ /* Disable the USB transceiver */
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_USBC2, 0,
+ CPCAP_BIT_USBXCVREN);
+
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "Disable Transciever failure.\n");
+ result = NOTIFY_BAD;
+ }
+
+ if (code == SYS_RESTART) {
+ if (mode != NULL && !strncmp("outofcharge", mode, 12)) {
+ /* Set the outofcharge bit in the cpcap */
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1,
+ CPCAP_BIT_OUT_CHARGE_ONLY,
+ CPCAP_BIT_OUT_CHARGE_ONLY);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "outofcharge cpcap set failure.\n");
+ result = NOTIFY_BAD;
+ }
+ /* Set the soft reset bit in the cpcap */
+ cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1,
+ CPCAP_BIT_SOFT_RESET,
+ CPCAP_BIT_SOFT_RESET);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "reset cpcap set failure.\n");
+ result = NOTIFY_BAD;
+ }
+ }
+
+ /* Check if we are starting recovery mode */
+ if (mode != NULL && !strncmp("recovery", mode, 9)) {
+ /* Set the fota (recovery mode) bit in the cpcap */
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1,
+ CPCAP_BIT_FOTA_MODE, CPCAP_BIT_FOTA_MODE);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "Recovery cpcap set failure.\n");
+ result = NOTIFY_BAD;
+ }
+ } else {
+ /* Set the fota (recovery mode) bit in the cpcap */
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0,
+ CPCAP_BIT_FOTA_MODE);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "Recovery cpcap clear failure.\n");
+ result = NOTIFY_BAD;
+ }
+ }
+ /* Check if we are going into fast boot mode */
+ if (mode != NULL && !strncmp("bootloader", mode, 11)) {
+ /* Set the bootmode bit in the cpcap */
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1,
+ CPCAP_BIT_BOOT_MODE, CPCAP_BIT_BOOT_MODE);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "Boot mode cpcap set failure.\n");
+ result = NOTIFY_BAD;
+ }
+ }
+ cpcap_regacc_write(misc_cpcap, CPCAP_REG_MI2, 0, 0xFFFF);
+ } else {
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1,
+ 0,
+ CPCAP_BIT_OUT_CHARGE_ONLY);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "outofcharge cpcap set failure.\n");
+ result = NOTIFY_BAD;
+ }
+
+ /* Clear the soft reset bit in the cpcap */
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0,
+ CPCAP_BIT_SOFT_RESET);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "SW Reset cpcap set failure.\n");
+ result = NOTIFY_BAD;
+ }
+ /* Clear the fota (recovery mode) bit in the cpcap */
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0,
+ CPCAP_BIT_FOTA_MODE);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "Recovery cpcap clear failure.\n");
+ result = NOTIFY_BAD;
+ }
+ }
+
+ /* Always clear the kpanic bit */
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1,
+ 0, CPCAP_BIT_AP_KERNEL_PANIC);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "Clear kernel panic bit failure.\n");
+ result = NOTIFY_BAD;
+ }
+
+ /* Always clear the power cut bit on SW Shutdown*/
+ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_PC1,
+ 0, CPCAP_BIT_PC1_PCEN);
+ if (ret) {
+ dev_err(&(misc_cpcap->spi->dev),
+ "Clear Power Cut bit failure.\n");
+ result = NOTIFY_BAD;
+ }
+
+ cpcap_regacc_write(misc_cpcap, CPCAP_REG_CRM, 0x0300, 0x3FFF);
+
+ (void)cpcap_regacc_read(misc_cpcap, CPCAP_REG_INTS2, &value);
+ if (!(value & CPCAP_BIT_VBUSVLD_S)) {
+ while ((value & CPCAP_BIT_SESSVLD_S) && (counter < 100)) {
+ mdelay(10);
+ counter++;
+ (void)cpcap_regacc_read(misc_cpcap, CPCAP_REG_INTS2,
+ &value);
+ }
+ }
+
+ /* Clear the charger and charge path settings to avoid a false turn on
+ * event in caused by CPCAP. After clearing these settings, 100ms is
+ * needed to before SYSRSTRTB is pulled low to avoid the false turn on
+ * event.
+ */
+ cpcap_regacc_write(misc_cpcap, CPCAP_REG_CRM, 0, 0x3FFF);
+ mdelay(100);
+
+ return result;
+}
+
+static struct notifier_block cpcap_reboot_notifier = {
+ .notifier_call = cpcap_reboot,
+};
+
+static int __init cpcap_init(void)
+{
+ return spi_register_driver(&cpcap_driver);
+}
+
+static void cpcap_vendor_read(struct cpcap_device *cpcap)
+{
+ unsigned short value;
+
+ (void)cpcap_regacc_read(cpcap, CPCAP_REG_VERSC1, &value);
+
+ cpcap->vendor = (enum cpcap_vendor)((value >> 6) & 0x0007);
+ cpcap->revision = (enum cpcap_revision)(((value >> 3) & 0x0007) |
+ ((value << 3) & 0x0038));
+}
+
+
+int cpcap_device_unregister(struct platform_device *pdev)
+{
+ struct cpcap_driver_info *info;
+ struct cpcap_driver_info *tmp;
+ int found;
+
+
+ found = 0;
+ mutex_lock(&cpcap_driver_lock);
+
+ list_for_each_entry_safe(info, tmp, &cpcap_device_list, list) {
+ if (info->pdev == pdev) {
+ list_del(&info->list);
+
+ /*
+ * misc_cpcap != NULL suggests pdev
+ * already registered
+ */
+ if (misc_cpcap) {
+ printk(KERN_INFO "CPCAP: unregister %s\n",
+ pdev->name);
+ platform_device_unregister(pdev);
+ }
+ info->pdev = NULL;
+ kfree(info);
+ found = 1;
+ }
+ }
+
+ mutex_unlock(&cpcap_driver_lock);
+
+ BUG_ON(!found);
+ return 0;
+}
+
+int cpcap_device_register(struct platform_device *pdev)
+{
+ int retval;
+ struct cpcap_driver_info *info;
+
+ retval = 0;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "Cannot save device %s\n", pdev->name);
+ return -ENOMEM;
+ }
+
+ mutex_lock(&cpcap_driver_lock);
+
+ info->pdev = pdev;
+ list_add_tail(&info->list, &cpcap_device_list);
+
+ /* If misc_cpcap is valid, the CPCAP driver has already been probed.
+ * Therefore, call platform_device_register() to probe the device.
+ */
+ if (misc_cpcap) {
+ dev_info(&(misc_cpcap->spi->dev),
+ "Probing CPCAP device %s\n", pdev->name);
+
+ /*
+ * platform_data is non-empty indicates
+ * CPCAP client devices need to pass their own data
+ * In that case we put cpcap data in driver_data
+ */
+ if (pdev->dev.platform_data != NULL)
+ platform_set_drvdata(pdev, misc_cpcap);
+ else
+ pdev->dev.platform_data = misc_cpcap;
+ retval = platform_device_register(pdev);
+ } else
+ printk(KERN_INFO "CPCAP: delaying %s probe\n",
+ pdev->name);
+ mutex_unlock(&cpcap_driver_lock);
+
+ return retval;
+}
+
+static int cpcap_probe(struct spi_device *spi)
+{
+ int retval = -EINVAL;
+ struct cpcap_device *cpcap;
+ struct cpcap_platform_data *data;
+ int i;
+ struct cpcap_driver_info *info;
+
+ cpcap = kzalloc(sizeof(*cpcap), GFP_KERNEL);
+ if (cpcap == NULL)
+ return -ENOMEM;
+
+ cpcap->spi = spi;
+ data = spi->controller_data;
+ spi_set_drvdata(spi, cpcap);
+
+ retval = cpcap_regacc_init(cpcap);
+ if (retval < 0)
+ goto free_mem;
+ retval = cpcap_irq_init(cpcap);
+ if (retval < 0)
+ goto free_cpcap_irq;
+
+ cpcap_vendor_read(cpcap);
+
+ for (i = 0; i < ARRAY_SIZE(cpcap_devices); i++)
+ cpcap_devices[i]->dev.platform_data = cpcap;
+
+ retval = misc_register(&cpcap_dev);
+ if (retval < 0)
+ goto free_cpcap_irq;
+
+ /* loop twice becuase cpcap_regulator_probe may refer to other devices
+ * in this list to handle dependencies between regulators. Create them
+ * all and then add them */
+ for (i = 0; i < CPCAP_NUM_REGULATORS; i++) {
+ struct platform_device *pdev;
+
+ pdev = platform_device_alloc("cpcap-regltr", i);
+ if (!pdev) {
+ dev_err(&(spi->dev), "Cannot create regulator\n");
+ continue;
+ }
+
+ pdev->dev.parent = &(spi->dev);
+ pdev->dev.platform_data = &data->regulator_init[i];
+ platform_set_drvdata(pdev, cpcap);
+ cpcap->regulator_pdev[i] = pdev;
+ }
+
+ for (i = 0; i < CPCAP_NUM_REGULATORS; i++) {
+ /* vusb has to be added after sw5 so skip it for now,
+ * it will be added from probe of sw5 */
+ if (i == CPCAP_VUSB)
+ continue;
+ platform_device_add(cpcap->regulator_pdev[i]);
+ }
+
+ platform_add_devices(cpcap_devices, ARRAY_SIZE(cpcap_devices));
+
+ mutex_lock(&cpcap_driver_lock);
+ misc_cpcap = cpcap; /* kept for misc device */
+
+ list_for_each_entry(info, &cpcap_device_list, list) {
+ int ret = 0;
+ dev_info(&(spi->dev), "Probing CPCAP device %s\n",
+ info->pdev->name);
+ if (info->pdev->dev.platform_data != NULL)
+ platform_set_drvdata(info->pdev, cpcap);
+ else
+ info->pdev->dev.platform_data = cpcap;
+ ret = platform_device_register(info->pdev);
+ }
+ mutex_unlock(&cpcap_driver_lock);
+
+ register_reboot_notifier(&cpcap_reboot_notifier);
+
+ return 0;
+
+free_cpcap_irq:
+ cpcap_irq_shutdown(cpcap);
+free_mem:
+ kfree(cpcap);
+ return retval;
+}
+
+static int cpcap_remove(struct spi_device *spi)
+{
+ struct cpcap_device *cpcap = spi_get_drvdata(spi);
+ struct cpcap_driver_info *info;
+ int i;
+
+ unregister_reboot_notifier(&cpcap_reboot_notifier);
+
+ mutex_lock(&cpcap_driver_lock);
+ list_for_each_entry(info, &cpcap_device_list, list) {
+ dev_info(&(spi->dev), "Removing CPCAP device %s\n",
+ info->pdev->name);
+ platform_device_unregister(info->pdev);
+ }
+ misc_cpcap = NULL;
+ mutex_unlock(&cpcap_driver_lock);
+
+ for (i = ARRAY_SIZE(cpcap_devices); i > 0; i--)
+ platform_device_unregister(cpcap_devices[i-1]);
+
+ for (i = 0; i < CPCAP_NUM_REGULATORS; i++)
+ platform_device_unregister(cpcap->regulator_pdev[i]);
+
+ misc_deregister(&cpcap_dev);
+ cpcap_irq_shutdown(cpcap);
+ kfree(cpcap);
+ return 0;
+}
+
+
+static int test_ioctl(unsigned int cmd, unsigned long arg)
+{
+ int retval = -EINVAL;
+ struct cpcap_regacc read_data;
+ struct cpcap_regacc write_data;
+
+ switch (cmd) {
+ case CPCAP_IOCTL_TEST_READ_REG:
+ if (copy_from_user((void *)&read_data, (void *)arg,
+ sizeof(read_data)))
+ return -EFAULT;
+ retval = cpcap_regacc_read(misc_cpcap, read_data.reg,
+ &read_data.value);
+ if (retval < 0)
+ return retval;
+ if (copy_to_user((void *)arg, (void *)&read_data,
+ sizeof(read_data)))
+ return -EFAULT;
+ return 0;
+ break;
+
+ case CPCAP_IOCTL_TEST_WRITE_REG:
+ if (copy_from_user((void *) &write_data,
+ (void *) arg,
+ sizeof(write_data)))
+ return -EFAULT;
+ retval = cpcap_regacc_write(misc_cpcap, write_data.reg,
+ write_data.value, write_data.mask);
+ break;
+
+ default:
+ retval = -ENOTTY;
+ break;
+ }
+
+ return retval;
+}
+
+static int adc_ioctl(unsigned int cmd, unsigned long arg)
+{
+ int retval = -EINVAL;
+ struct cpcap_adc_phase phase;
+
+ switch (cmd) {
+ case CPCAP_IOCTL_ADC_PHASE:
+ if (copy_from_user((void *) &phase, (void *) arg,
+ sizeof(phase)))
+ return -EFAULT;
+
+ cpcap_adc_phase(misc_cpcap, &phase);
+ retval = 0;
+ break;
+
+ default:
+ retval = -ENOTTY;
+ break;
+ }
+
+ return retval;
+}
+
+#if defined(CONFIG_LEDS_FLASH_RESET)
+int cpcap_direct_misc_write(unsigned short reg, unsigned short value,\
+ unsigned short mask)
+{
+ int retval = -EINVAL;
+
+ retval = cpcap_regacc_write(misc_cpcap, reg, value, mask);
+
+ return retval;
+}
+#endif
+
+static long ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int retval = -ENOTTY;
+ unsigned int cmd_num;
+
+ cmd_num = _IOC_NR(cmd);
+
+ if ((cmd_num > CPCAP_IOCTL_NUM_TEST__START) &&
+ (cmd_num < CPCAP_IOCTL_NUM_TEST__END)) {
+ retval = test_ioctl(cmd, arg);
+ }
+ if ((cmd_num > CPCAP_IOCTL_NUM_ADC__START) &&
+ (cmd_num < CPCAP_IOCTL_NUM_ADC__END)) {
+ retval = adc_ioctl(cmd, arg);
+ }
+
+ return retval;
+}
+
+static void cpcap_shutdown(void)
+{
+ spi_unregister_driver(&cpcap_driver);
+}
+
+int cpcap_disable_offmode_wakeups(bool disable)
+{
+ int retval = 0;
+ return retval;
+}
+
+subsys_initcall(cpcap_init);
+module_exit(cpcap_shutdown);
+
+MODULE_ALIAS("platform:cpcap");
+MODULE_DESCRIPTION("CPCAP driver");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/cpcap-irq.c b/drivers/mfd/cpcap-irq.c
new file mode 100644
index 00000000000..8d253ea2c56
--- /dev/null
+++ b/drivers/mfd/cpcap-irq.c
@@ -0,0 +1,809 @@
+/*
+ * Copyright (C) 2009 - 2010, Motorola, 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 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/gpio.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/wakelock.h>
+
+#include <linux/spi/cpcap.h>
+#include <linux/spi/spi.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#ifdef CONFIG_PM_DEEPSLEEP
+#include <linux/suspend.h>
+#endif
+
+#define LONG_KEYPRESS_DURATION 4 /* in seconds */
+
+#define NUM_INT_REGS 5
+#define NUM_INTS_PER_REG 16
+
+#define CPCAP_INT1_VALID_BITS 0xFFFB
+#define CPCAP_INT2_VALID_BITS 0xFFFF
+#define CPCAP_INT3_VALID_BITS 0xFFFF
+#define CPCAP_INT4_VALID_BITS 0x03FF
+#define CPCAP_INT5_VALID_BITS 0xFFFF
+
+struct cpcap_event_handler {
+ void (*func)(enum cpcap_irqs, void *);
+ void *data;
+};
+
+struct cpcap_irq_info {
+ uint8_t registered;
+ uint8_t enabled;
+ uint32_t count;
+};
+
+struct cpcap_irqdata {
+ struct mutex lock;
+ struct work_struct work;
+ struct workqueue_struct *workqueue;
+ struct cpcap_device *cpcap;
+ struct cpcap_event_handler event_handler[CPCAP_IRQ__NUM];
+ struct cpcap_irq_info irq_info[CPCAP_IRQ__NUM];
+ struct wake_lock wake_lock;
+};
+
+#define EVENT_MASK(event) (1 << ((event) % NUM_INTS_PER_REG))
+
+enum pwrkey_states {
+ PWRKEY_RELEASE, /* Power key released state. */
+ PWRKEY_PRESS, /* Power key pressed state. */
+ PWRKEY_UNKNOWN, /* Unknown power key state. */
+};
+
+static irqreturn_t event_isr(int irq, void *data)
+{
+ struct cpcap_irqdata *irq_data = data;
+ disable_irq_nosync(irq);
+ wake_lock(&irq_data->wake_lock);
+ queue_work(irq_data->workqueue, &irq_data->work);
+
+ return IRQ_HANDLED;
+}
+
+static unsigned short get_int_reg(enum cpcap_irqs event)
+{
+ unsigned short ret;
+
+ if ((event) >= CPCAP_IRQ_INT5_INDEX)
+ ret = CPCAP_REG_MI1;
+ else if ((event) >= CPCAP_IRQ_INT4_INDEX)
+ ret = CPCAP_REG_INT4;
+ else if ((event) >= CPCAP_IRQ_INT3_INDEX)
+ ret = CPCAP_REG_INT3;
+ else if ((event) >= CPCAP_IRQ_INT2_INDEX)
+ ret = CPCAP_REG_INT2;
+ else
+ ret = CPCAP_REG_INT1;
+
+ return ret;
+}
+
+static unsigned short get_mask_reg(enum cpcap_irqs event)
+{
+ unsigned short ret;
+
+ if (event >= CPCAP_IRQ_INT5_INDEX)
+ ret = CPCAP_REG_MIM1;
+ else if (event >= CPCAP_IRQ_INT4_INDEX)
+ ret = CPCAP_REG_INTM4;
+ else if (event >= CPCAP_IRQ_INT3_INDEX)
+ ret = CPCAP_REG_INTM3;
+ else if (event >= CPCAP_IRQ_INT2_INDEX)
+ ret = CPCAP_REG_INTM2;
+ else
+ ret = CPCAP_REG_INTM1;
+
+ return ret;
+}
+
+static unsigned short get_sense_reg(enum cpcap_irqs event)
+{
+ unsigned short ret;
+
+ if (event >= CPCAP_IRQ_INT5_INDEX)
+ ret = CPCAP_REG_MI2;
+ else if (event >= CPCAP_IRQ_INT4_INDEX)
+ ret = CPCAP_REG_INTS4;
+ else if (event >= CPCAP_IRQ_INT3_INDEX)
+ ret = CPCAP_REG_INTS3;
+ else if (event >= CPCAP_IRQ_INT2_INDEX)
+ ret = CPCAP_REG_INTS2;
+ else
+ ret = CPCAP_REG_INTS1;
+
+ return ret;
+}
+
+void cpcap_irq_mask_all(struct cpcap_device *cpcap)
+{
+ int i;
+
+ static const struct {
+ unsigned short mask_reg;
+ unsigned short valid;
+ } int_reg[NUM_INT_REGS] = {
+ {CPCAP_REG_INTM1, CPCAP_INT1_VALID_BITS},
+ {CPCAP_REG_INTM2, CPCAP_INT2_VALID_BITS},
+ {CPCAP_REG_INTM3, CPCAP_INT3_VALID_BITS},
+ {CPCAP_REG_INTM4, CPCAP_INT4_VALID_BITS},
+ {CPCAP_REG_MIM1, CPCAP_INT5_VALID_BITS}
+ };
+
+ for (i = 0; i < NUM_INT_REGS; i++) {
+ cpcap_regacc_write(cpcap, int_reg[i].mask_reg,
+ int_reg[i].valid,
+ int_reg[i].valid);
+ }
+}
+
+struct pwrkey_data {
+ struct cpcap_device *cpcap;
+ enum pwrkey_states state;
+ struct wake_lock wake_lock;
+ struct delayed_work pwrkey_work;
+ int power_double_pressed;
+#ifdef CONFIG_PM_DEEPSLEEP
+ struct hrtimer longPress_timer;
+ int expired;
+#endif
+
+};
+
+#ifdef CONFIG_PM_DBG_DRV
+static struct cpcap_irq_pm_dbg {
+ unsigned short en_ints[NUM_INT_REGS];
+ unsigned char suspend;
+ unsigned char wakeup;
+} pm_dbg_info;
+#endif /* CONFIG_PM_DBG_DRV */
+
+#ifdef CONFIG_PM_DEEPSLEEP
+
+static enum hrtimer_restart longPress_timer_callback(struct hrtimer *timer)
+{
+ struct pwrkey_data *pwrkey_data =
+ container_of(timer, struct pwrkey_data, longPress_timer);
+ struct cpcap_device *cpcap = pwrkey_data->cpcap;
+ enum pwrkey_states new_state = PWRKEY_PRESS;
+
+ if (wake_lock_active(&pwrkey_data->wake_lock))
+ wake_unlock(&pwrkey_data->wake_lock);
+ wake_lock_timeout(&pwrkey_data->wake_lock, 20);
+
+ /* long timer expired without being cancelled so send long press
+ keydown event */
+ pwrkey_data->expired = 1;
+ cpcap_broadcast_key_event(cpcap, KEY_SENDFILE, new_state);
+ pwrkey_data->state = new_state;
+
+ return HRTIMER_NORESTART;
+
+}
+#endif
+
+static void pwrkey_work_func(struct work_struct *work)
+{
+ struct pwrkey_data *pwrkey_data =
+ container_of(work, struct pwrkey_data, pwrkey_work.work);
+ struct cpcap_device *cpcap = pwrkey_data->cpcap;
+
+ if (wake_lock_active(&pwrkey_data->wake_lock))
+ wake_unlock(&pwrkey_data->wake_lock);
+ wake_lock_timeout(&pwrkey_data->wake_lock, 20);
+ if (pwrkey_data->state == PWRKEY_RELEASE) {
+ /* keyup was detected before keydown was sent, so send
+ keydown first */
+ cpcap_broadcast_key_event(cpcap, KEY_END, PWRKEY_PRESS);
+ }
+
+ /* Send detected state (keyup/keydown) */
+ cpcap_broadcast_key_event(cpcap, KEY_END, pwrkey_data->state);
+}
+
+static void pwrkey_handler(enum cpcap_irqs irq, void *data)
+{
+ struct pwrkey_data *pwrkey_data = data;
+ enum pwrkey_states new_state, last_state = pwrkey_data->state;
+ struct cpcap_device *cpcap = pwrkey_data->cpcap;
+
+ new_state = (enum pwrkey_states) cpcap_irq_sense(cpcap, irq, 0);
+
+ /* First do long keypress detection */
+ if (new_state == PWRKEY_RELEASE) {
+#ifdef CONFIG_PM_DEEPSLEEP
+ /* Got a keyup so cancel 2 second timer */
+ hrtimer_cancel(&pwrkey_data->longPress_timer);
+ /* If longpress keydown was previously sent, then send the
+ long press keyup */
+ if (pwrkey_data->expired == 1) {
+ pwrkey_data->expired = 0;
+#endif
+ cpcap_broadcast_key_event(cpcap,
+ KEY_SENDFILE, new_state);
+ pwrkey_data->state = new_state;
+#ifdef CONFIG_PM_DEEPSLEEP
+ }
+#endif
+ } else if (new_state == PWRKEY_PRESS) {
+#ifdef CONFIG_PM_DEEPSLEEP
+ /* Got a keydown so start long keypress timer */
+ pwrkey_data->expired = 0;
+ hrtimer_start(&pwrkey_data->longPress_timer,
+ ktime_set(LONG_KEYPRESS_DURATION, 0),
+ HRTIMER_MODE_REL);
+#endif
+ wake_lock_timeout(&pwrkey_data->wake_lock,
+ (LONG_KEYPRESS_DURATION*HZ)+5);
+ }
+
+ /* Now do normal powerkey detection (in addition to long press) */
+ if ((new_state < PWRKEY_UNKNOWN) && (new_state != last_state)) {
+ if (new_state == PWRKEY_PRESS) {
+ if (wake_lock_active(&pwrkey_data->wake_lock))
+ wake_unlock(&pwrkey_data->wake_lock);
+ if (delayed_work_pending(&pwrkey_data->pwrkey_work)) {
+ /* If 600ms delayed work exists and we got a
+ keydown, then a doublepress has occured */
+ cancel_delayed_work_sync(&pwrkey_data-> \
+ pwrkey_work);
+ wake_lock_timeout(&pwrkey_data->wake_lock, 20);
+ cpcap_broadcast_key_event(cpcap,
+ KEY_POWER_DOUBLE, new_state);
+ pwrkey_data->power_double_pressed = 1;
+ } else {
+ /* If no delayed work was pending and we got a
+ keydown, then start 600ms delayed work */
+ wake_lock(&pwrkey_data->wake_lock);
+ schedule_delayed_work(&pwrkey_data->pwrkey_work,
+ msecs_to_jiffies(600));
+ }
+ } else {
+ /* Got a keyup. If we previously sent a doublepress
+ keydown, then send a doublepress keyup now */
+ if (pwrkey_data->power_double_pressed) {
+ if (wake_lock_active(&pwrkey_data->wake_lock))
+ wake_unlock(&pwrkey_data->wake_lock);
+ wake_lock_timeout(&pwrkey_data->wake_lock, 20);
+ cpcap_broadcast_key_event(cpcap,
+ KEY_POWER_DOUBLE, new_state);
+ pwrkey_data->power_double_pressed = 0;
+ /* If the 600ms delayed work is done and we got a keyup,
+ then send the keyup now */
+ } else if (!delayed_work_pending(&pwrkey_data-> \
+ pwrkey_work)) {
+ if (wake_lock_active(&pwrkey_data->wake_lock))
+ wake_unlock(&pwrkey_data->wake_lock);
+ wake_lock_timeout(&pwrkey_data->wake_lock, 20);
+ cpcap_broadcast_key_event(cpcap, KEY_END,
+ new_state);
+ }
+ /* If we got a keyup while 600ms delayed work is still
+ pending, then do nothing now and let the delayed
+ work handler handle this */
+ }
+ pwrkey_data->state = new_state;
+ }
+ cpcap_irq_unmask(cpcap, CPCAP_IRQ_ON);
+}
+
+static int pwrkey_init(struct cpcap_device *cpcap)
+{
+ struct pwrkey_data *data = kmalloc(sizeof(struct pwrkey_data),
+ GFP_KERNEL);
+ int retval;
+
+ if (!data)
+ return -ENOMEM;
+ data->cpcap = cpcap;
+ data->state = PWRKEY_RELEASE;
+ data->power_double_pressed = 0;
+ retval = cpcap_irq_register(cpcap, CPCAP_IRQ_ON, pwrkey_handler, data);
+ if (retval)
+ kfree(data);
+ wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "pwrkey");
+#ifdef CONFIG_PM_DEEPSLEEP
+
+ hrtimer_init(&(data->longPress_timer),
+ CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+
+ (data->longPress_timer).function = longPress_timer_callback;
+#endif
+ INIT_DELAYED_WORK(&data->pwrkey_work, pwrkey_work_func);
+
+ return retval;
+}
+
+static void pwrkey_remove(struct cpcap_device *cpcap)
+{
+ struct pwrkey_data *data;
+
+ cpcap_irq_get_data(cpcap, CPCAP_IRQ_ON, (void **)&data);
+ if (!data)
+ return;
+ cancel_delayed_work_sync(&data->pwrkey_work);
+ cpcap_irq_free(cpcap, CPCAP_IRQ_ON);
+ wake_lock_destroy(&data->wake_lock);
+ kfree(data);
+}
+
+static int int_read_and_clear(struct cpcap_device *cpcap,
+ unsigned short status_reg,
+ unsigned short mask_reg,
+ unsigned short valid_mask,
+ unsigned short *en)
+{
+ unsigned short ireg_val, mreg_val;
+ int ret;
+ ret = cpcap_regacc_read(cpcap, status_reg, &ireg_val);
+ if (ret)
+ return ret;
+ ret = cpcap_regacc_read(cpcap, mask_reg, &mreg_val);
+ if (ret)
+ return ret;
+ *en |= ireg_val & ~mreg_val;
+ *en &= valid_mask;
+ ret = cpcap_regacc_write(cpcap, mask_reg, *en, *en);
+ if (ret)
+ return ret;
+ ret = cpcap_regacc_write(cpcap, status_reg, *en, *en);
+ if (ret)
+ return ret;
+ return 0;
+}
+
+
+static void irq_work_func(struct work_struct *work)
+{
+ int retval = 0;
+ unsigned short en_ints[NUM_INT_REGS];
+ int i;
+ struct cpcap_irqdata *data;
+ struct cpcap_device *cpcap;
+ struct spi_device *spi;
+ struct cpcap_platform_data *pdata;
+ unsigned int irq_gpio;
+
+ static const struct {
+ unsigned short status_reg;
+ unsigned short mask_reg;
+ unsigned short valid;
+ } int_reg[NUM_INT_REGS] = {
+ {CPCAP_REG_INT1, CPCAP_REG_INTM1, CPCAP_INT1_VALID_BITS},
+ {CPCAP_REG_INT2, CPCAP_REG_INTM2, CPCAP_INT2_VALID_BITS},
+ {CPCAP_REG_INT3, CPCAP_REG_INTM3, CPCAP_INT3_VALID_BITS},
+ {CPCAP_REG_INT4, CPCAP_REG_INTM4, CPCAP_INT4_VALID_BITS},
+ {CPCAP_REG_MI1, CPCAP_REG_MIM1, CPCAP_INT5_VALID_BITS}
+ };
+
+ for (i = 0; i < NUM_INT_REGS; ++i)
+ en_ints[i] = 0;
+
+ data = container_of(work, struct cpcap_irqdata, work);
+ cpcap = data->cpcap;
+ spi = cpcap->spi;
+ pdata = (struct cpcap_platform_data *) spi->controller_data;
+ irq_gpio = pdata->irq_gpio;
+
+ while (gpio_get_value(irq_gpio)) {
+ for (i = 0; i < NUM_INT_REGS; ++i) {
+ retval = int_read_and_clear(cpcap,
+ int_reg[i].status_reg,
+ int_reg[i].mask_reg,
+ int_reg[i].valid,
+ &en_ints[i]);
+ if (retval < 0) {
+ dev_err(&cpcap->spi->dev,
+ "Error reading interrupts\n");
+ break;
+ }
+ }
+ }
+ enable_irq(spi->irq);
+
+#ifdef CONFIG_PM_DBG_DRV
+ if ((pm_dbg_info.suspend != 0) && (pm_dbg_info.wakeup == 0)) {
+ for (i = 0; i < NUM_INT_REGS; ++i)
+ pm_dbg_info.en_ints[i] = en_ints[i];
+ pm_dbg_info.wakeup = 1;
+ }
+#endif /* CONFIG_PM_DBG_DRV */
+
+ /* lock protects event handlers and data */
+ mutex_lock(&data->lock);
+ for (i = 0; i < NUM_INT_REGS; ++i) {
+ unsigned char index;
+
+ while (en_ints[i] > 0) {
+ struct cpcap_event_handler *event_handler;
+
+ /* find the first set bit */
+ index = (unsigned char)(ffs(en_ints[i]) - 1);
+ if (index >= CPCAP_IRQ__NUM)
+ goto error;
+ /* clear the bit */
+ en_ints[i] &= ~(1 << index);
+ /* find the event that occurred */
+ index += CPCAP_IRQ__START + (i * NUM_INTS_PER_REG);
+ if (index >= CPCAP_IRQ__NUM)
+ goto error;
+ event_handler = &data->event_handler[index];
+
+ if (event_handler->func)
+ event_handler->func(index, event_handler->data);
+
+ data->irq_info[index].count++;
+
+ }
+ }
+error:
+ mutex_unlock(&data->lock);
+ wake_unlock(&data->wake_lock);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int cpcap_dbg_irq_show(struct seq_file *s, void *data)
+{
+ static const char *irq_name[] = {
+ [CPCAP_IRQ_HSCLK] = "HSCLK",
+ [CPCAP_IRQ_PRIMAC] = "PRIMAC",
+ [CPCAP_IRQ_SECMAC] = "SECMAC",
+ [CPCAP_IRQ_LOWBPL] = "LOWBPL",
+ [CPCAP_IRQ_SEC2PRI] = "SEC2PRI",
+ [CPCAP_IRQ_LOWBPH] = "LOWBPH",
+ [CPCAP_IRQ_EOL] = "EOL",
+ [CPCAP_IRQ_TS] = "TS",
+ [CPCAP_IRQ_ADCDONE] = "ADCDONE",
+ [CPCAP_IRQ_HS] = "HS",
+ [CPCAP_IRQ_MB2] = "MB2",
+ [CPCAP_IRQ_VBUSOV] = "VBUSOV",
+ [CPCAP_IRQ_RVRS_CHRG] = "RVRS_CHRG",
+ [CPCAP_IRQ_CHRG_DET] = "CHRG_DET",
+ [CPCAP_IRQ_IDFLOAT] = "IDFLOAT",
+ [CPCAP_IRQ_IDGND] = "IDGND",
+
+ [CPCAP_IRQ_SE1] = "SE1",
+ [CPCAP_IRQ_SESSEND] = "SESSEND",
+ [CPCAP_IRQ_SESSVLD] = "SESSVLD",
+ [CPCAP_IRQ_VBUSVLD] = "VBUSVLD",
+ [CPCAP_IRQ_CHRG_CURR1] = "CHRG_CURR1",
+ [CPCAP_IRQ_CHRG_CURR2] = "CHRG_CURR2",
+ [CPCAP_IRQ_RVRS_MODE] = "RVRS_MODE",
+ [CPCAP_IRQ_ON] = "ON",
+ [CPCAP_IRQ_ON2] = "ON2",
+ [CPCAP_IRQ_CLK] = "CLK",
+ [CPCAP_IRQ_1HZ] = "1HZ",
+ [CPCAP_IRQ_PTT] = "PTT",
+ [CPCAP_IRQ_SE0CONN] = "SE0CONN",
+ [CPCAP_IRQ_CHRG_SE1B] = "CHRG_SE1B",
+ [CPCAP_IRQ_UART_ECHO_OVERRUN] = "UART_ECHO_OVERRUN",
+ [CPCAP_IRQ_EXTMEMHD] = "EXTMEMHD",
+
+ [CPCAP_IRQ_WARM] = "WARM",
+ [CPCAP_IRQ_SYSRSTR] = "SYSRSTR",
+ [CPCAP_IRQ_SOFTRST] = "SOFTRST",
+ [CPCAP_IRQ_DIEPWRDWN] = "DIEPWRDWN",
+ [CPCAP_IRQ_DIETEMPH] = "DIETEMPH",
+ [CPCAP_IRQ_PC] = "PC",
+ [CPCAP_IRQ_OFLOWSW] = "OFLOWSW",
+ [CPCAP_IRQ_TODA] = "TODA",
+ [CPCAP_IRQ_OPT_SEL_DTCH] = "OPT_SEL_DTCH",
+ [CPCAP_IRQ_OPT_SEL_STATE] = "OPT_SEL_STATE",
+ [CPCAP_IRQ_ONEWIRE1] = "ONEWIRE1",
+ [CPCAP_IRQ_ONEWIRE2] = "ONEWIRE2",
+ [CPCAP_IRQ_ONEWIRE3] = "ONEWIRE3",
+ [CPCAP_IRQ_UCRESET] = "UCRESET",
+ [CPCAP_IRQ_PWRGOOD] = "PWRGOOD",
+ [CPCAP_IRQ_USBDPLLCLK] = "USBDPLLCLK",
+
+ [CPCAP_IRQ_DPI] = "DPI",
+ [CPCAP_IRQ_DMI] = "DMI",
+ [CPCAP_IRQ_UCBUSY] = "UCBUSY",
+ [CPCAP_IRQ_GCAI_CURR1] = "GCAI_CURR1",
+ [CPCAP_IRQ_GCAI_CURR2] = "GCAI_CURR2",
+ [CPCAP_IRQ_SB_MAX_RETRANSMIT_ERR] = "SB_MAX_RETRANSMIT_ERR",
+ [CPCAP_IRQ_BATTDETB] = "BATTDETB",
+ [CPCAP_IRQ_PRIHALT] = "PRIHALT",
+ [CPCAP_IRQ_SECHALT] = "SECHALT",
+ [CPCAP_IRQ_CC_CAL] = "CC_CAL",
+
+ [CPCAP_IRQ_UC_PRIROMR] = "UC_PRIROMR",
+ [CPCAP_IRQ_UC_PRIRAMW] = "UC_PRIRAMW",
+ [CPCAP_IRQ_UC_PRIRAMR] = "UC_PRIRAMR",
+ [CPCAP_IRQ_UC_USEROFF] = "UC_USEROFF",
+ [CPCAP_IRQ_UC_PRIMACRO_4] = "UC_PRIMACRO_4",
+ [CPCAP_IRQ_UC_PRIMACRO_5] = "UC_PRIMACRO_5",
+ [CPCAP_IRQ_UC_PRIMACRO_6] = "UC_PRIMACRO_6",
+ [CPCAP_IRQ_UC_PRIMACRO_7] = "UC_PRIMACRO_7",
+ [CPCAP_IRQ_UC_PRIMACRO_8] = "UC_PRIMACRO_8",
+ [CPCAP_IRQ_UC_PRIMACRO_9] = "UC_PRIMACRO_9",
+ [CPCAP_IRQ_UC_PRIMACRO_10] = "UC_PRIMACRO_10",
+ [CPCAP_IRQ_UC_PRIMACRO_11] = "UC_PRIMACRO_11",
+ [CPCAP_IRQ_UC_PRIMACRO_12] = "UC_PRIMACRO_12",
+ [CPCAP_IRQ_UC_PRIMACRO_13] = "UC_PRIMACRO_13",
+ [CPCAP_IRQ_UC_PRIMACRO_14] = "UC_PRIMACRO_14",
+ [CPCAP_IRQ_UC_PRIMACRO_15] = "UC_PRIMACRO_15",
+ };
+ unsigned int i;
+ struct cpcap_irqdata *irqdata = s->private;
+
+ seq_printf(s, "%21s%9s%12s%10s\n",
+ "CPCAP IRQ", "Enabled", "Registered", "Count");
+
+ for (i = 0; i < CPCAP_IRQ__NUM; i++) {
+ if ((i <= CPCAP_IRQ_CC_CAL) || (i >= CPCAP_IRQ_UC_PRIROMR)) {
+ seq_printf(s, "%21s%9d%12d%10d\n",
+ irq_name[i],
+ irqdata->irq_info[i].enabled,
+ irqdata->irq_info[i].registered,
+ irqdata->irq_info[i].count);
+ }
+ }
+ return 0;
+}
+
+static int cpcap_dbg_irq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, cpcap_dbg_irq_show, inode->i_private);
+}
+
+static const struct file_operations debug_fops = {
+ .open = cpcap_dbg_irq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+#endif
+
+int cpcap_irq_init(struct cpcap_device *cpcap)
+{
+ int retval;
+ struct spi_device *spi = cpcap->spi;
+ struct cpcap_irqdata *data;
+
+ data = kzalloc(sizeof(struct cpcap_irqdata), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ cpcap_irq_mask_all(cpcap);
+
+ data->workqueue = create_workqueue("cpcap_irq");
+ INIT_WORK(&data->work, irq_work_func);
+ mutex_init(&data->lock);
+ wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "cpcap-irq");
+ data->cpcap = cpcap;
+
+ retval = request_irq(spi->irq, event_isr, IRQF_DISABLED |
+ IRQF_TRIGGER_RISING, "cpcap-irq", data);
+ if (retval) {
+ printk(KERN_ERR "cpcap_irq: Failed requesting irq.\n");
+ goto error;
+ }
+
+ enable_irq_wake(spi->irq);
+
+ cpcap->irqdata = data;
+ retval = pwrkey_init(cpcap);
+ if (retval) {
+ printk(KERN_ERR "cpcap_irq: Failed initializing pwrkey.\n");
+ goto error;
+ }
+#ifdef CONFIG_DEBUG_FS
+ (void)debugfs_create_file("cpcap-irq", S_IRUGO, NULL, data,
+ &debug_fops);
+#endif
+ return 0;
+
+error:
+ free_irq(spi->irq, data);
+ kfree(data);
+ printk(KERN_ERR "cpcap_irq: Error registering cpcap irq.\n");
+ return retval;
+}
+
+void cpcap_irq_shutdown(struct cpcap_device *cpcap)
+{
+ struct spi_device *spi = cpcap->spi;
+ struct cpcap_irqdata *data = cpcap->irqdata;
+
+ pwrkey_remove(cpcap);
+ cancel_work_sync(&data->work);
+ destroy_workqueue(data->workqueue);
+ free_irq(spi->irq, data);
+ kfree(data);
+}
+
+int cpcap_irq_register(struct cpcap_device *cpcap,
+ enum cpcap_irqs irq,
+ void (*cb_func) (enum cpcap_irqs, void *),
+ void *data)
+{
+ struct cpcap_irqdata *irqdata = cpcap->irqdata;
+ int retval = 0;
+
+ if ((irq >= CPCAP_IRQ__NUM) || (!cb_func))
+ return -EINVAL;
+
+ mutex_lock(&irqdata->lock);
+
+ if (irqdata->event_handler[irq].func == NULL) {
+ irqdata->irq_info[irq].registered = 1;
+ cpcap_irq_unmask(cpcap, irq);
+ irqdata->event_handler[irq].func = cb_func;
+ irqdata->event_handler[irq].data = data;
+ } else
+ retval = -EPERM;
+
+ mutex_unlock(&irqdata->lock);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_irq_register);
+
+int cpcap_irq_free(struct cpcap_device *cpcap, enum cpcap_irqs irq)
+{
+ struct cpcap_irqdata *data = cpcap->irqdata;
+ int retval;
+
+ if (irq >= CPCAP_IRQ__NUM)
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+ retval = cpcap_irq_mask(cpcap, irq);
+ data->event_handler[irq].func = NULL;
+ data->event_handler[irq].data = NULL;
+ data->irq_info[irq].registered = 0;
+ mutex_unlock(&data->lock);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_irq_free);
+
+int cpcap_irq_get_data(struct cpcap_device *cpcap,
+ enum cpcap_irqs irq,
+ void **data)
+{
+ struct cpcap_irqdata *irqdata = cpcap->irqdata;
+
+ if (irq >= CPCAP_IRQ__NUM)
+ return -EINVAL;
+
+ mutex_lock(&irqdata->lock);
+ *data = irqdata->event_handler[irq].data;
+ mutex_unlock(&irqdata->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cpcap_irq_get_data);
+
+int cpcap_irq_clear(struct cpcap_device *cpcap,
+ enum cpcap_irqs irq)
+{
+ int retval = -EINVAL;
+
+ if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) {
+ retval = cpcap_regacc_write(cpcap,
+ get_int_reg(irq),
+ EVENT_MASK(irq),
+ EVENT_MASK(irq));
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_irq_clear);
+
+int cpcap_irq_mask(struct cpcap_device *cpcap,
+ enum cpcap_irqs irq)
+{
+ struct cpcap_irqdata *data = cpcap->irqdata;
+ int retval = -EINVAL;
+
+ if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) {
+ data->irq_info[irq].enabled = 0;
+ retval = cpcap_regacc_write(cpcap,
+ get_mask_reg(irq),
+ EVENT_MASK(irq),
+ EVENT_MASK(irq));
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_irq_mask);
+
+int cpcap_irq_unmask(struct cpcap_device *cpcap,
+ enum cpcap_irqs irq)
+{
+ struct cpcap_irqdata *data = cpcap->irqdata;
+ int retval = -EINVAL;
+
+ if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) {
+ data->irq_info[irq].enabled = 1;
+ retval = cpcap_regacc_write(cpcap,
+ get_mask_reg(irq),
+ 0,
+ EVENT_MASK(irq));
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_irq_unmask);
+
+int cpcap_irq_mask_get(struct cpcap_device *cpcap,
+ enum cpcap_irqs irq)
+{
+ struct cpcap_irqdata *data = cpcap->irqdata;
+ int retval = -EINVAL;
+
+ if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC))
+ return data->irq_info[irq].enabled;
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_irq_mask_get);
+
+int cpcap_irq_sense(struct cpcap_device *cpcap,
+ enum cpcap_irqs irq,
+ unsigned char clear)
+{
+ unsigned short val;
+ int retval;
+
+ if (irq >= CPCAP_IRQ__NUM)
+ return -EINVAL;
+
+ retval = cpcap_regacc_read(cpcap, get_sense_reg(irq), &val);
+ if (retval)
+ return retval;
+
+ if (clear)
+ retval = cpcap_irq_clear(cpcap, irq);
+ if (retval)
+ return retval;
+
+ return ((val & EVENT_MASK(irq)) != 0) ? 1 : 0;
+}
+EXPORT_SYMBOL_GPL(cpcap_irq_sense);
+
+#ifdef CONFIG_PM_DBG_DRV
+void cpcap_irq_pm_dbg_suspend(void)
+{
+ pm_dbg_info.suspend = 1;
+ pm_dbg_info.wakeup = 0;
+}
+
+void cpcap_irq_pm_dbg_resume(void)
+{
+ pm_dbg_info.suspend = 0;
+ if (pm_dbg_info.wakeup != 0) {
+ printk(KERN_INFO "PM_DBG WAKEUP CPCAP IRQ = 0x%x.0x%x.0%x.0x%x.0x%x\n",
+ pm_dbg_info.en_ints[0],
+ pm_dbg_info.en_ints[1],
+ pm_dbg_info.en_ints[2],
+ pm_dbg_info.en_ints[3],
+ pm_dbg_info.en_ints[4]);
+ }
+}
+#endif /* CONFIG_PM_DBG_DRV */
diff --git a/drivers/mfd/cpcap-key.c b/drivers/mfd/cpcap-key.c
new file mode 100644
index 00000000000..c9f066391fb
--- /dev/null
+++ b/drivers/mfd/cpcap-key.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2009 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/spi/cpcap.h>
+#include <linux/spi/cpcap-regbits.h>
+
+#if defined(CONFIG_MFD_M4SENSORHUB) || defined(CONFIG_MFD_M4SENSORHUB_MODULE)
+extern int m4sensorhub_stillmode_exit(void);
+#endif
+
+struct cpcap_key_data {
+ struct input_dev *input_dev;
+ struct cpcap_device *cpcap;
+};
+
+static int __init cpcap_key_probe(struct platform_device *pdev)
+{
+ int err;
+ struct cpcap_key_data *key;
+
+ if (pdev->dev.platform_data == NULL) {
+ dev_err(&pdev->dev, "no platform_data\n");
+ return -EINVAL;
+ }
+
+ key = kzalloc(sizeof(*key), GFP_KERNEL);
+ if (!key)
+ return -ENOMEM;
+
+ key->cpcap = pdev->dev.platform_data;
+
+ key->input_dev = input_allocate_device();
+ if (key->input_dev == NULL) {
+ dev_err(&pdev->dev, "can't allocate input device\n");
+ err = -ENOMEM;
+ goto err0;
+ }
+
+ set_bit(EV_KEY, key->input_dev->evbit);
+ set_bit(KEY_MEDIA, key->input_dev->keybit);
+ set_bit(KEY_END, key->input_dev->keybit);
+ set_bit(KEY_POWER_DOUBLE, key->input_dev->keybit);
+ set_bit(KEY_PLAYCD, key->input_dev->keybit);
+ set_bit(KEY_VOLUMEDOWN, key->input_dev->keybit);
+ set_bit(KEY_VOLUMEUP, key->input_dev->keybit);
+ set_bit(KEY_POWER_SONG, key->input_dev->keybit);
+ set_bit(KEY_SENDFILE, key->input_dev->keybit);
+
+ key->input_dev->name = "cpcap-key";
+
+ err = input_register_device(key->input_dev);
+ if (err < 0) {
+ dev_err(&pdev->dev, "could not register input device.\n");
+ goto err1;
+ }
+
+ platform_set_drvdata(pdev, key);
+ cpcap_set_keydata(key->cpcap, key);
+
+ dev_info(&pdev->dev, "CPCAP key device probed\n");
+
+ return 0;
+
+err1:
+ input_free_device(key->input_dev);
+err0:
+ kfree(key);
+ return err;
+}
+
+static int __exit cpcap_key_remove(struct platform_device *pdev)
+{
+ struct cpcap_key_data *key = platform_get_drvdata(pdev);
+
+ input_unregister_device(key->input_dev);
+ input_free_device(key->input_dev);
+ kfree(key);
+
+ return 0;
+}
+
+void cpcap_broadcast_key_event(struct cpcap_device *cpcap,
+ unsigned int code, int value)
+{
+ struct cpcap_key_data *key = cpcap_get_keydata(cpcap);
+
+/* TODO
+#if defined(CONFIG_MFD_M4SENSORHUB) || defined(CONFIG_MFD_M4SENSORHUB_MODULE)
+ //Notify sensorhub driver of power key down event
+ if (key && value)
+ m4sensorhub_stillmode_exit();
+#endif
+*/
+ if (key && key->input_dev) {
+ input_report_key(key->input_dev, code, value);
+ /*sync with input subsystem to solve the key cached problem*/
+ input_sync(key->input_dev);
+ }
+}
+EXPORT_SYMBOL(cpcap_broadcast_key_event);
+
+static struct platform_driver cpcap_key_driver = {
+ .probe = cpcap_key_probe,
+ .remove = __exit_p(cpcap_key_remove),
+ .driver = {
+ .name = "cpcap_key",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init cpcap_key_init(void)
+{
+ return platform_driver_register(&cpcap_key_driver);
+}
+module_init(cpcap_key_init);
+
+static void __exit cpcap_key_exit(void)
+{
+ platform_driver_unregister(&cpcap_key_driver);
+}
+module_exit(cpcap_key_exit);
+
+MODULE_ALIAS("platform:cpcap_key");
+MODULE_DESCRIPTION("CPCAP KEY driver");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/cpcap-regacc.c b/drivers/mfd/cpcap-regacc.c
new file mode 100644
index 00000000000..1a616014188
--- /dev/null
+++ b/drivers/mfd/cpcap-regacc.c
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2007-2009 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/device.h>
+#include <linux/mutex.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/cpcap.h>
+#include <linux/spi/cpcap-regbits.h>
+
+#define IS_CPCAP(reg) ((reg) >= CPCAP_REG_START && (reg) <= CPCAP_REG_END)
+
+static DEFINE_MUTEX(reg_access);
+
+/*
+ * This table contains information about a single register in the power IC.
+ * It is used during register access to information such as the register address
+ * and the modifiability of each bit in the register. Special notes for
+ * particular elements of this structure follows:
+ *
+ * constant_mask: A '1' in this mask indicates that the corresponding bit has a
+ * 'constant' modifiability, and therefore must never be changed by any register
+ * access.
+ *
+ * It is important to note that any bits that are 'constant' must have
+ * synchronized read/write values. That is to say, when a 'constant' bit is
+ * read the value read must be identical to the value that must be written to
+ * that bit in order for that bit to be read with the same value.
+ *
+ * rbw_mask: A '1' in this mask indicates that the corresponding bit (when not
+ * being changed) should be written with the current value of that bit. A '0'
+ * in this mask indicates that the corresponding bit (when not being changed)
+ * should be written with a value of '0'.
+ */
+static const struct {
+ unsigned short address; /* Address of the register */
+ unsigned short constant_mask; /* Constant modifiability mask */
+ unsigned short rbw_mask; /* Read-before-write mask */
+} register_info_tbl[CPCAP_NUM_REG_CPCAP] = {
+ [CPCAP_REG_INT1] = {0, 0x0004, 0x0000},
+ [CPCAP_REG_INT2] = {1, 0x0000, 0x0000},
+ [CPCAP_REG_INT3] = {2, 0x0000, 0x0000},
+ [CPCAP_REG_INT4] = {3, 0xFC00, 0x0000},
+ [CPCAP_REG_INTM1] = {4, 0x0004, 0xFFFF},
+ [CPCAP_REG_INTM2] = {5, 0x0000, 0xFFFF},
+ [CPCAP_REG_INTM3] = {6, 0x0000, 0xFFFF},
+ [CPCAP_REG_INTM4] = {7, 0xFC00, 0xFFFF},
+ [CPCAP_REG_INTS1] = {8, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_INTS2] = {9, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_INTS3] = {10, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_INTS4] = {11, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ASSIGN1] = {12, 0x80F8, 0xFFFF},
+ [CPCAP_REG_ASSIGN2] = {13, 0x0000, 0xFFFF},
+ [CPCAP_REG_ASSIGN3] = {14, 0x0004, 0xFFFF},
+ [CPCAP_REG_ASSIGN4] = {15, 0x0068, 0xFFFF},
+ [CPCAP_REG_ASSIGN5] = {16, 0x0000, 0xFFFF},
+ [CPCAP_REG_ASSIGN6] = {17, 0xFC00, 0xFFFF},
+ [CPCAP_REG_VERSC1] = {18, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_VERSC2] = {19, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_MI1] = {128, 0x0000, 0x0000},
+ [CPCAP_REG_MIM1] = {129, 0x0000, 0xFFFF},
+ [CPCAP_REG_MI2] = {130, 0x0000, 0xFFFF},
+ [CPCAP_REG_MIM2] = {131, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_UCC1] = {132, 0xF000, 0xFFFF},
+ [CPCAP_REG_UCC2] = {133, 0xFC00, 0xFFFF},
+ [CPCAP_REG_PC1] = {135, 0xFC00, 0xFFFF},
+ [CPCAP_REG_PC2] = {136, 0xFC00, 0xFFFF},
+ [CPCAP_REG_BPEOL] = {137, 0xFE00, 0xFFFF},
+ [CPCAP_REG_PGC] = {138, 0xFE00, 0xFFFF},
+ [CPCAP_REG_MT1] = {139, 0x0000, 0x0000},
+ [CPCAP_REG_MT2] = {140, 0x0000, 0x0000},
+ [CPCAP_REG_MT3] = {141, 0x0000, 0x0000},
+ [CPCAP_REG_PF] = {142, 0x0000, 0xFFFF},
+ [CPCAP_REG_SCC] = {256, 0xFF00, 0xFFFF},
+ [CPCAP_REG_SW1] = {257, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_SW2] = {258, 0xFC7F, 0xFFFF},
+ [CPCAP_REG_UCTM] = {259, 0xFFFE, 0xFFFF},
+ [CPCAP_REG_TOD1] = {260, 0xFF00, 0xFFFF},
+ [CPCAP_REG_TOD2] = {261, 0xFE00, 0xFFFF},
+ [CPCAP_REG_TODA1] = {262, 0xFF00, 0xFFFF},
+ [CPCAP_REG_TODA2] = {263, 0xFE00, 0xFFFF},
+ [CPCAP_REG_DAY] = {264, 0x8000, 0xFFFF},
+ [CPCAP_REG_DAYA] = {265, 0x8000, 0xFFFF},
+ [CPCAP_REG_VAL1] = {266, 0x0000, 0xFFFF},
+ [CPCAP_REG_VAL2] = {267, 0x0000, 0xFFFF},
+ [CPCAP_REG_SDVSPLL] = {384, 0x2488, 0xFFFF},
+ [CPCAP_REG_SI2CC1] = {385, 0x8000, 0xFFFF},
+ [CPCAP_REG_Si2CC2] = {386, 0xFF00, 0xFFFF},
+ [CPCAP_REG_S1C1] = {387, 0x9080, 0xFFFF},
+ [CPCAP_REG_S1C2] = {388, 0x8080, 0xFFFF},
+ [CPCAP_REG_S2C1] = {389, 0x9080, 0xFFFF},
+ [CPCAP_REG_S2C2] = {390, 0x8080, 0xFFFF},
+ [CPCAP_REG_S3C] = {391, 0xFA84, 0xFFFF},
+ [CPCAP_REG_S4C1] = {392, 0x9080, 0xFFFF},
+ [CPCAP_REG_S4C2] = {393, 0x8080, 0xFFFF},
+ [CPCAP_REG_S5C] = {394, 0xFFD5, 0xFFFF},
+ [CPCAP_REG_S6C] = {395, 0xFFF4, 0xFFFF},
+ [CPCAP_REG_VCAMC] = {396, 0xFF48, 0xFFFF},
+ [CPCAP_REG_VCSIC] = {397, 0xFFA8, 0xFFFF},
+ [CPCAP_REG_VDACC] = {398, 0xFF48, 0xFFFF},
+ [CPCAP_REG_VDIGC] = {399, 0xFF48, 0xFFFF},
+ [CPCAP_REG_VFUSEC] = {400, 0xFF50, 0xFFFF},
+ [CPCAP_REG_VHVIOC] = {401, 0xFFE8, 0xFFFF},
+ [CPCAP_REG_VSDIOC] = {402, 0xFF40, 0xFFFF},
+ [CPCAP_REG_VPLLC] = {403, 0xFFA4, 0xFFFF},
+ [CPCAP_REG_VRF1C] = {404, 0xFF50, 0xFFFF},
+ [CPCAP_REG_VRF2C] = {405, 0xFFD4, 0xFFFF},
+ [CPCAP_REG_VRFREFC] = {406, 0xFFD4, 0xFFFF},
+ [CPCAP_REG_VWLAN1C] = {407, 0xFFA8, 0xFFFF},
+ [CPCAP_REG_VWLAN2C] = {408, 0xFD32, 0xFFFF},
+ [CPCAP_REG_VSIMC] = {409, 0xE154, 0xFFFF},
+ [CPCAP_REG_VVIBC] = {410, 0xFFF2, 0xFFFF},
+#ifdef CONFIG_EMU_UART_DEBUG
+ [CPCAP_REG_VUSBC] = {411, 0xFFFF, 0xFFFF},
+#else
+ [CPCAP_REG_VUSBC] = {411, 0xFEA2, 0xFFFF},
+#endif
+ [CPCAP_REG_VUSBINT1C] = {412, 0xFFD4, 0xFFFF},
+ [CPCAP_REG_VUSBINT2C] = {413, 0xFFD4, 0xFFFF},
+ [CPCAP_REG_URT] = {414, 0xFFFE, 0xFFFF},
+ [CPCAP_REG_URM1] = {415, 0x0000, 0xFFFF},
+ [CPCAP_REG_URM2] = {416, 0xFC00, 0xFFFF},
+ [CPCAP_REG_VAUDIOC] = {512, 0xFF88, 0xFFFF},
+ [CPCAP_REG_CC] = {513, 0x0000, 0xFEDF},
+ [CPCAP_REG_CDI] = {514, 0x4000, 0xFFFF},
+ [CPCAP_REG_SDAC] = {515, 0xF000, 0xFCFF},
+ [CPCAP_REG_SDACDI] = {516, 0xC000, 0xFFFF},
+ [CPCAP_REG_TXI] = {517, 0x0000, 0xFFFF},
+ [CPCAP_REG_TXMP] = {518, 0xF000, 0xFFFF},
+ [CPCAP_REG_RXOA] = {519, 0xF800, 0xFFFF},
+ [CPCAP_REG_RXVC] = {520, 0x00C3, 0xFFFF},
+ [CPCAP_REG_RXCOA] = {521, 0xF800, 0xFFFF},
+ [CPCAP_REG_RXSDOA] = {522, 0xE000, 0xFFFF},
+ [CPCAP_REG_RXEPOA] = {523, 0x8000, 0xFFFF},
+ [CPCAP_REG_RXLL] = {524, 0x0000, 0xFFFF},
+ [CPCAP_REG_A2LA] = {525, 0xFF00, 0xFFFF},
+ [CPCAP_REG_MIPIS1] = {526, 0x0000, 0xFFFF},
+ [CPCAP_REG_MIPIS2] = {527, 0xFF00, 0xFFFF},
+ [CPCAP_REG_MIPIS3] = {528, 0xFFFC, 0xFFFF},
+ [CPCAP_REG_LVAB] = {529, 0xFFFC, 0xFFFF},
+ [CPCAP_REG_CCC1] = {640, 0xFFF0, 0xFFFF},
+ [CPCAP_REG_CRM] = {641, 0xC000, 0xFFFF},
+ [CPCAP_REG_CCCC2] = {642, 0xFFC0, 0xFFFF},
+ [CPCAP_REG_CCS1] = {643, 0x0000, 0xFFFF},
+ [CPCAP_REG_CCS2] = {644, 0xFF00, 0xFFFF},
+ [CPCAP_REG_CCA1] = {645, 0x0000, 0xFFFF},
+ [CPCAP_REG_CCA2] = {646, 0x0000, 0xFFFF},
+ [CPCAP_REG_CCM] = {647, 0xFC00, 0xFFFF},
+ [CPCAP_REG_CCO] = {648, 0xFC00, 0xFFFF},
+ [CPCAP_REG_CCI] = {649, 0xC000, 0xFFFF},
+ [CPCAP_REG_ADCC1] = {768, 0x0000, 0xFFFF},
+ [CPCAP_REG_ADCC2] = {769, 0x0080, 0xFFFF},
+ [CPCAP_REG_ADCD0] = {770, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCD1] = {771, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCD2] = {772, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCD3] = {773, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCD4] = {774, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCD5] = {775, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCD6] = {776, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCD7] = {777, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCAL1] = {778, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_ADCAL2] = {779, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_USBC1] = {896, 0x0000, 0xFFFF},
+#ifdef CONFIG_EMU_UART_DEBUG
+ [CPCAP_REG_USBC2] = {897, 0x0F07, 0xFFFF},
+#else
+ [CPCAP_REG_USBC2] = {897, 0x0000, 0xFFFF},
+#endif
+ [CPCAP_REG_USBC3] = {898, 0x8200, 0xFFFF},
+ [CPCAP_REG_UVIDL] = {899, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_UVIDH] = {900, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_UPIDL] = {901, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_UPIDH] = {902, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_UFC1] = {903, 0xFF80, 0xFFFF},
+ [CPCAP_REG_UFC2] = {904, 0xFF80, 0xFFFF},
+ [CPCAP_REG_UFC3] = {905, 0xFF80, 0xFFFF},
+ [CPCAP_REG_UIC1] = {906, 0xFF64, 0xFFFF},
+ [CPCAP_REG_UIC2] = {907, 0xFF64, 0xFFFF},
+ [CPCAP_REG_UIC3] = {908, 0xFF64, 0xFFFF},
+ [CPCAP_REG_USBOTG1] = {909, 0xFFC0, 0xFFFF},
+ [CPCAP_REG_USBOTG2] = {910, 0xFFC0, 0xFFFF},
+ [CPCAP_REG_USBOTG3] = {911, 0xFFC0, 0xFFFF},
+ [CPCAP_REG_UIER1] = {912, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_UIER2] = {913, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_UIER3] = {914, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_UIEF1] = {915, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_UIEF2] = {916, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_UIEF3] = {917, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_UIS] = {918, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_UIL] = {919, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_USBD] = {920, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_SCR1] = {921, 0xFF00, 0xFFFF},
+ [CPCAP_REG_SCR2] = {922, 0xFF00, 0xFFFF},
+ [CPCAP_REG_SCR3] = {923, 0xFF00, 0xFFFF},
+ [CPCAP_REG_VMC] = {939, 0xFFFE, 0xFFFF},
+ [CPCAP_REG_OWDC] = {940, 0xFFFC, 0xFFFF},
+ [CPCAP_REG_GPIO0] = {941, 0x0D11, 0x3FFF},
+ [CPCAP_REG_GPIO1] = {943, 0x0D11, 0x3FFF},
+ [CPCAP_REG_GPIO2] = {945, 0x0D11, 0x3FFF},
+ [CPCAP_REG_GPIO3] = {947, 0x0D11, 0x3FFF},
+ [CPCAP_REG_GPIO4] = {949, 0x0D11, 0x3FFF},
+ [CPCAP_REG_GPIO5] = {951, 0x0C11, 0x3FFF},
+ [CPCAP_REG_GPIO6] = {953, 0x0C11, 0x3FFF},
+ [CPCAP_REG_MDLC] = {1024, 0x0000, 0xFFFF},
+ [CPCAP_REG_KLC] = {1025, 0x8000, 0xFFFF},
+ [CPCAP_REG_ADLC] = {1026, 0x8000, 0xFFFF},
+ [CPCAP_REG_REDC] = {1027, 0xFC00, 0xFFFF},
+ [CPCAP_REG_GREENC] = {1028, 0xFC00, 0xFFFF},
+ [CPCAP_REG_BLUEC] = {1029, 0xFC00, 0xFFFF},
+ [CPCAP_REG_CFC] = {1030, 0xF000, 0xFFFF},
+ [CPCAP_REG_ABC] = {1031, 0xFFC3, 0xFFFF},
+ [CPCAP_REG_BLEDC] = {1032, 0xFC00, 0xFFFF},
+ [CPCAP_REG_CLEDC] = {1033, 0xFC00, 0xFFFF},
+ [CPCAP_REG_OW1C] = {1152, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW1D] = {1153, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW1I] = {1154, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_OW1IE] = {1155, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW1] = {1157, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW2C] = {1160, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW2D] = {1161, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW2I] = {1162, 0xFFFF, 0xFFFF},
+ [CPCAP_REG_OW2IE] = {1163, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW2] = {1165, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW3C] = {1168, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW3D] = {1169, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW3I] = {1170, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW3IE] = {1171, 0xFF00, 0xFFFF},
+ [CPCAP_REG_OW3] = {1173, 0xFF00, 0xFFFF},
+ [CPCAP_REG_GCAIC] = {1174, 0xFF00, 0xFFFF},
+ [CPCAP_REG_GCAIM] = {1175, 0xFF00, 0xFFFF},
+ [CPCAP_REG_LGDIR] = {1176, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_LGPU] = {1177, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_LGPIN] = {1178, 0xFF00, 0xFFFF},
+ [CPCAP_REG_LGMASK] = {1179, 0xFFE0, 0xFFFF},
+ [CPCAP_REG_LDEB] = {1180, 0xFF00, 0xFFFF},
+ [CPCAP_REG_LGDET] = {1181, 0xFF00, 0xFFFF},
+ [CPCAP_REG_LMISC] = {1182, 0xFF07, 0xFFFF},
+ [CPCAP_REG_LMACE] = {1183, 0xFFF8, 0xFFFF},
+ [CPCAP_REG_TEST] = {7936, 0x0000, 0xFFFF},
+ [CPCAP_REG_ST_TEST1] = {8002, 0x0000, 0xFFFF},
+};
+
+static int cpcap_spi_access(struct spi_device *spi, u8 *buf,
+ size_t len)
+{
+ struct spi_message m;
+ struct spi_transfer t = {
+ .tx_buf = buf,
+ .len = len,
+ .rx_buf = buf,
+ .bits_per_word = 32,
+ };
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ return spi_sync(spi, &m);
+}
+
+static int cpcap_config_for_read(struct spi_device *spi, unsigned short reg,
+ unsigned short *data)
+{
+ int status = -ENOTTY;
+ u32 buf32; /* force buf to be 32bit aligned */
+ u8 *buf = (u8 *) &buf32;
+
+ if (spi != NULL) {
+ buf[3] = (reg >> 6) & 0x000000FF;
+ buf[2] = (reg << 2) & 0x000000FF;
+ buf[1] = 0;
+ buf[0] = 0;
+
+ status = cpcap_spi_access(spi, buf, 4);
+
+ if (status == 0)
+ *data = buf[0] | (buf[1] << 8);
+ }
+
+ return status;
+}
+
+static int cpcap_config_for_write(struct spi_device *spi, unsigned short reg,
+ unsigned short data)
+{
+ int status = -ENOTTY;
+ u32 buf32; /* force buf to be 32bit aligned */
+ u8 *buf = (u8 *) &buf32;
+
+ if (spi != NULL) {
+ buf[3] = ((reg >> 6) & 0x000000FF) | 0x80;
+ buf[2] = (reg << 2) & 0x000000FF;
+ buf[1] = (data >> 8) & 0x000000FF;
+ buf[0] = data & 0x000000FF;
+
+ status = cpcap_spi_access(spi, buf, 4);
+ }
+
+ return status;
+}
+
+int cpcap_regacc_read(struct cpcap_device *cpcap, enum cpcap_reg reg,
+ unsigned short *value_ptr)
+{
+ int retval = -EINVAL;
+ struct spi_device *spi = cpcap->spi;
+
+ if (IS_CPCAP(reg) && (value_ptr != 0)) {
+ mutex_lock(&reg_access);
+
+ retval = cpcap_config_for_read(spi, register_info_tbl
+ [reg].address, value_ptr);
+
+ mutex_unlock(&reg_access);
+ }
+
+ return retval;
+}
+
+int cpcap_regacc_write(struct cpcap_device *cpcap,
+ enum cpcap_reg reg,
+ unsigned short value,
+ unsigned short mask)
+{
+ int retval = -EINVAL;
+ unsigned short old_value = 0;
+ struct cpcap_platform_data *data;
+ struct spi_device *spi = cpcap->spi;
+
+ data = (struct cpcap_platform_data *)spi->controller_data;
+
+ if (IS_CPCAP(reg) &&
+ (mask & register_info_tbl[reg].constant_mask) == 0) {
+ mutex_lock(&reg_access);
+
+ value &= mask;
+
+ if ((register_info_tbl[reg].rbw_mask) != 0) {
+ retval = cpcap_config_for_read(spi, register_info_tbl
+ [reg].address,
+ &old_value);
+ if (retval != 0)
+ goto error;
+ }
+
+ old_value &= register_info_tbl[reg].rbw_mask;
+ old_value &= ~mask;
+ value |= old_value;
+ retval = cpcap_config_for_write(spi,
+ register_info_tbl[reg].address,
+ value);
+error:
+ mutex_unlock(&reg_access);
+ }
+
+ return retval;
+}
+
+int cpcap_regacc_init(struct cpcap_device *cpcap)
+{
+ unsigned short i;
+ unsigned short mask;
+ int retval = 0;
+ struct cpcap_platform_data *data;
+ struct spi_device *spi = cpcap->spi;
+
+ data = (struct cpcap_platform_data *)spi->controller_data;
+
+ for (i = 0; i < data->init_len; i++) {
+ mask = 0xFFFF;
+ mask &= ~(register_info_tbl[data->init[i].reg].constant_mask);
+
+ retval = cpcap_regacc_write(cpcap, data->init[i].reg,
+ data->init[i].data,
+ mask);
+ if (retval)
+ break;
+ }
+
+ return retval;
+}
diff --git a/drivers/mfd/cpcap-uc.c b/drivers/mfd/cpcap-uc.c
new file mode 100644
index 00000000000..2626bfcb5f7
--- /dev/null
+++ b/drivers/mfd/cpcap-uc.c
@@ -0,0 +1,927 @@
+/*
+ * Copyright (C) 2008-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/completion.h>
+#include <linux/errno.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/ihex.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+
+#include <linux/spi/cpcap.h>
+#include <linux/spi/cpcap-regbits.h>
+#include <linux/spi/spi.h>
+
+#define ERROR_MACRO_TIMEOUT 0x81
+#define ERROR_MACRO_WRITE 0x82
+#define ERROR_MACRO_READ 0x83
+
+#define RAM_START_TI 0x9000
+#define RAM_END_TI 0x9FA0
+#define RAM_START_ST 0x0000
+#define RAM_END_ST 0x0FFF
+
+#define HWCFG_ADDR_ST 0x0148
+#define HWCFG_ADDR_TI 0x90F4 /* Not yet implemented in the TI uC. */
+
+enum {
+ READ_STATE_1, /* Send size and location of RAM read. */
+ READ_STATE_2, /*!< Read MT registers. */
+ READ_STATE_3, /*!< Read data from uC. */
+ READ_STATE_4, /*!< Check for error. */
+};
+
+enum {
+ WRITE_STATE_1, /* Send size and location of RAM write. */
+ WRITE_STATE_2, /* Check for error. */
+ WRITE_STATE_3, /* Write data to uC. */
+ WRITE_STATE_4 /* Check for error. */
+};
+
+struct cpcap_uc_data {
+ struct cpcap_device *cpcap;
+ unsigned char is_supported;
+ unsigned char is_ready;
+ struct completion completion;
+ int cb_status;
+ struct mutex lock;
+ unsigned char uc_reset;
+ unsigned char state;
+ unsigned short state_cntr;
+ struct {
+ unsigned short address;
+ unsigned short *data;
+ unsigned short num_words;
+ } req;
+};
+
+static struct cpcap_uc_data *cpcap_uc_info;
+
+static int fops_open(struct inode *inode, struct file *file);
+static long fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static ssize_t fops_write(struct file *file, const char *buf,
+ size_t count, loff_t *ppos);
+static ssize_t fops_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos);
+
+
+static const struct file_operations fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fops_ioctl,
+ .open = fops_open,
+ .read = fops_read,
+ .write = fops_write,
+};
+
+static struct miscdevice uc_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "cpcap_uc",
+ .fops = &fops,
+};
+
+static int is_valid_address(struct cpcap_device *cpcap, unsigned short address,
+ unsigned short num_words)
+{
+ int vld = 0;
+
+ if (cpcap->vendor == CPCAP_VENDOR_TI) {
+ vld = (address >= RAM_START_TI) &&
+ ((address + num_words) <= RAM_END_TI);
+ } else if (cpcap->vendor == CPCAP_VENDOR_ST) {
+ vld = ((address + num_words) <= RAM_END_ST);
+ }
+
+ return vld;
+}
+
+static void ram_read_state_machine(enum cpcap_irqs irq, void *data)
+{
+ struct cpcap_uc_data *uc_data = data;
+ unsigned short temp;
+
+ if (irq != CPCAP_IRQ_UC_PRIRAMR)
+ return;
+
+ switch (uc_data->state) {
+ case READ_STATE_1:
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1,
+ uc_data->req.address, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2,
+ uc_data->req.num_words, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0, 0xFFFF);
+
+ if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST)
+ uc_data->state = READ_STATE_2;
+ else
+ uc_data->state = READ_STATE_3;
+
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+
+ break;
+
+ case READ_STATE_2:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &temp);
+
+ if (temp == ERROR_MACRO_READ) {
+ uc_data->state = READ_STATE_1;
+ uc_data->state_cntr = 0;
+
+ cpcap_irq_mask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+
+ uc_data->cb_status = -EIO;
+
+ complete(&uc_data->completion);
+ } else {
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2, &temp);
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3, &temp);
+
+ uc_data->state = READ_STATE_3;
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+ }
+ break;
+
+ case READ_STATE_3:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1,
+ uc_data->req.data + uc_data->state_cntr);
+
+ uc_data->state_cntr += 1;
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2, &temp);
+ else {
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2,
+ uc_data->req.data +
+ uc_data->state_cntr);
+
+ uc_data->state_cntr += 1;
+ }
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3, &temp);
+ else {
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3,
+ uc_data->req.data +
+ uc_data->state_cntr);
+
+ uc_data->state_cntr += 1;
+ }
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ uc_data->state = READ_STATE_4;
+
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+ break;
+
+ case READ_STATE_4:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &temp);
+
+ if (temp != ERROR_MACRO_READ)
+ uc_data->cb_status = 0;
+ else
+ uc_data->cb_status = -EIO;
+
+ complete(&uc_data->completion);
+
+ uc_data->state = READ_STATE_1;
+ uc_data->state_cntr = 0;
+ break;
+
+ default:
+ uc_data->state = READ_STATE_1;
+ uc_data->state_cntr = 0;
+ break;
+ }
+}
+
+static void ram_write_state_machine(enum cpcap_irqs irq, void *data)
+{
+ struct cpcap_uc_data *uc_data = data;
+ unsigned short error_check;
+
+ if (irq != CPCAP_IRQ_UC_PRIRAMW)
+ return;
+
+ switch (uc_data->state) {
+ case WRITE_STATE_1:
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1,
+ uc_data->req.address, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2,
+ uc_data->req.num_words, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0, 0xFFFF);
+
+ uc_data->state = WRITE_STATE_2;
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+ break;
+
+ case WRITE_STATE_2:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &error_check);
+
+ if (error_check == ERROR_MACRO_WRITE) {
+ uc_data->state = WRITE_STATE_1;
+ uc_data->state_cntr = 0;
+
+ cpcap_irq_mask(uc_data->cpcap,
+ CPCAP_IRQ_UC_PRIRAMW);
+
+ uc_data->cb_status = -EIO;
+ complete(&uc_data->completion);
+ break;
+ } else
+ uc_data->state = WRITE_STATE_3;
+
+ /* No error has occured, fall through */
+
+ case WRITE_STATE_3:
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1,
+ *(uc_data->req.data + uc_data->state_cntr),
+ 0xFFFF);
+ uc_data->state_cntr += 1;
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2, 0,
+ 0xFFFF);
+ else {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2,
+ *(uc_data->req.data +
+ uc_data->state_cntr), 0xFFFF);
+
+ uc_data->state_cntr += 1;
+ }
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0,
+ 0xFFFF);
+ else {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3,
+ *(uc_data->req.data +
+ uc_data->state_cntr), 0xFFFF);
+
+ uc_data->state_cntr += 1;
+ }
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ uc_data->state = WRITE_STATE_4;
+
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+ break;
+
+ case WRITE_STATE_4:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &error_check);
+
+ if (error_check != ERROR_MACRO_WRITE)
+ uc_data->cb_status = 0;
+ else
+ uc_data->cb_status = -EIO;
+
+ complete(&uc_data->completion);
+
+ uc_data->state = WRITE_STATE_1;
+ uc_data->state_cntr = 0;
+ break;
+
+ default:
+ uc_data->state = WRITE_STATE_1;
+ uc_data->state_cntr = 0;
+ break;
+ }
+}
+
+static void reset_handler(enum cpcap_irqs irq, void *data)
+{
+ int i;
+ unsigned short regval;
+ struct cpcap_uc_data *uc_data = data;
+
+ if (irq != CPCAP_IRQ_UCRESET)
+ return;
+
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCC1,
+ CPCAP_BIT_PRIHALT, CPCAP_BIT_PRIHALT);
+
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_PGC,
+ CPCAP_BIT_PRI_UC_SUSPEND, CPCAP_BIT_PRI_UC_SUSPEND);
+
+ uc_data->uc_reset = 1;
+ uc_data->cb_status = -EIO;
+ complete(&uc_data->completion);
+
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2, 0, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MIM1, 0xFFFF, 0xFFFF);
+ cpcap_irq_mask(uc_data->cpcap, CPCAP_IRQ_PRIMAC);
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UCRESET);
+
+ for (i = 0; i <= CPCAP_REG_END; i++) {
+ cpcap_regacc_read(uc_data->cpcap, i, &regval);
+ dev_err(&uc_data->cpcap->spi->dev,
+ "cpcap reg %d = 0x%04X\n", i, regval);
+ }
+
+ BUG();
+}
+
+static void primac_handler(enum cpcap_irqs irq, void *data)
+{
+ struct cpcap_uc_data *uc_data = data;
+
+ if (irq == CPCAP_IRQ_PRIMAC)
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_PRIMAC);
+}
+
+static int ram_write(struct cpcap_uc_data *uc_data, unsigned short address,
+ unsigned short num_words, unsigned short *data)
+{
+ int retval = -EFAULT;
+
+ mutex_lock(&uc_data->lock);
+
+ if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM,
+ CPCAP_BIT_UCTM, CPCAP_BIT_UCTM);
+ }
+
+ if (uc_data->is_supported && (num_words > 0) &&
+ (data != NULL) &&
+ is_valid_address(uc_data->cpcap, address, num_words) &&
+ !uc_data->uc_reset) {
+ uc_data->req.address = address;
+ uc_data->req.data = data;
+ uc_data->req.num_words = num_words;
+ uc_data->state = WRITE_STATE_1;
+ uc_data->state_cntr = 0;
+ INIT_COMPLETION(uc_data->completion);
+
+ retval = cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2,
+ CPCAP_BIT_PRIRAMW,
+ CPCAP_BIT_PRIRAMW);
+ if (retval)
+ goto err;
+
+ /* Cannot call cpcap_irq_register() here because unregister
+ * cannot be called from the state machine. Doing so causes
+ * a deadlock. */
+ retval = cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+ if (retval)
+ goto err;
+
+ wait_for_completion(&uc_data->completion);
+ retval = uc_data->cb_status;
+ }
+
+err:
+ if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM,
+ 0, CPCAP_BIT_UCTM);
+ }
+
+ mutex_unlock(&uc_data->lock);
+
+ return retval;
+}
+
+static int ram_read(struct cpcap_uc_data *uc_data, unsigned short address,
+ unsigned short num_words, unsigned short *data)
+{
+ int retval = -EFAULT;
+
+ mutex_lock(&uc_data->lock);
+
+ if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM,
+ CPCAP_BIT_UCTM, CPCAP_BIT_UCTM);
+ }
+
+ if (uc_data->is_supported && (num_words > 0) &&
+ is_valid_address(uc_data->cpcap, address, num_words) &&
+ !uc_data->uc_reset) {
+ uc_data->req.address = address;
+ uc_data->req.data = data;
+ uc_data->req.num_words = num_words;
+ uc_data->state = READ_STATE_1;
+ uc_data->state_cntr = 0;
+ INIT_COMPLETION(uc_data->completion);
+
+ retval = cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2,
+ CPCAP_BIT_PRIRAMR,
+ CPCAP_BIT_PRIRAMR);
+ if (retval)
+ goto err;
+
+ /* Cannot call cpcap_irq_register() here because unregister
+ * cannot be called from the state machine. Doing so causes
+ * a deadlock. */
+ retval = cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+ if (retval)
+ goto err;
+
+ wait_for_completion(&uc_data->completion);
+ retval = uc_data->cb_status;
+ }
+
+err:
+ if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM,
+ 0, CPCAP_BIT_UCTM);
+ }
+
+ mutex_unlock(&uc_data->lock);
+
+ return retval;
+}
+
+static int ram_load(struct cpcap_uc_data *uc_data, unsigned int num_words,
+ unsigned short *data)
+{
+ int retval = -EINVAL;
+
+ if ((data != NULL) && (num_words > 0))
+ retval = ram_write(uc_data, data[0], (num_words - 1),
+ (data + 1));
+
+ return retval;
+}
+
+static ssize_t fops_write(struct file *file, const char *buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t retval = -EINVAL;
+ unsigned short address;
+ unsigned short num_words;
+ unsigned short *data;
+ struct cpcap_uc_data *uc_data = file->private_data;
+
+ if ((buf != NULL) && (ppos != NULL) && (count >= 2)) {
+ data = kzalloc(count, GFP_KERNEL);
+
+ if (data != NULL) {
+ num_words = (unsigned short) (count >> 1);
+
+ /* If the position (uC RAM address) is zero then the
+ * data contains the address */
+ if (*ppos == 0) {
+ if (copy_from_user((void *) data, (void *) buf,
+ count) == 0)
+ retval = ram_load(uc_data, num_words,
+ data);
+ else
+ retval = -EFAULT;
+ }
+ /* If the position (uC RAM address) is not zero then the
+ * position holds the address to load the data */
+ else {
+ address = (unsigned short) (*ppos);
+
+ if (copy_from_user((void *) data, (void *) buf,
+ count) == 0)
+ retval = ram_write(uc_data, address,
+ num_words, data);
+ else
+ retval = -EFAULT;
+ }
+
+ kfree(data);
+ } else {
+ retval = -ENOMEM;
+ }
+ }
+
+ if (retval == 0)
+ retval = num_words;
+
+ return retval;
+}
+
+static ssize_t fops_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t retval = -EFAULT;
+ unsigned short address;
+ unsigned short num_words;
+ unsigned short *data;
+ struct cpcap_uc_data *uc_data = file->private_data;
+
+ if ((buf != NULL) && (ppos != NULL) && (count >= 2)) {
+ data = kzalloc(count, GFP_KERNEL);
+
+ if (data != NULL) {
+ address = (unsigned short) (*ppos);
+ num_words = (unsigned short) (count >> 1);
+
+ retval = ram_read(uc_data, address, num_words, data);
+
+ if (retval)
+ goto err;
+
+ if (copy_to_user((void *)buf, (void *)data, count) == 0)
+ retval = count;
+ else
+ retval = -EFAULT;
+
+err:
+ kfree(data);
+ } else {
+ retval = -ENOMEM;
+ }
+ }
+
+ return retval;
+}
+
+static long fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int retval = -ENOTTY;
+ struct cpcap_uc_data *data = file->private_data;
+
+ switch (cmd) {
+ case CPCAP_IOCTL_UC_MACRO_START:
+ /* User space will only attempt to start the init macro if
+ * the ram load requests complete successfully. This is used
+ * as an indication that kernel requests to start macros can
+ * be allowed.
+ */
+ data->is_ready = 1;
+
+ retval = cpcap_uc_start(data->cpcap, (enum cpcap_macro)arg);
+
+ break;
+
+ case CPCAP_IOCTL_UC_MACRO_STOP:
+ retval = cpcap_uc_stop(data->cpcap, (enum cpcap_macro)arg);
+ break;
+
+ case CPCAP_IOCTL_UC_GET_VENDOR:
+ retval = copy_to_user((enum cpcap_vendor *)arg,
+ &(data->cpcap->vendor),
+ sizeof(enum cpcap_vendor));
+ break;
+
+ case CPCAP_IOCTL_UC_SET_TURBO_MODE:
+ if (arg != 0)
+ arg = 1;
+ retval = cpcap_regacc_write(data->cpcap, CPCAP_REG_UCTM,
+ (unsigned short)arg,
+ CPCAP_BIT_UCTM);
+ break;
+
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+static int fops_open(struct inode *inode, struct file *file)
+{
+ int retval = -ENOTTY;
+
+ if (cpcap_uc_info->is_supported)
+ retval = 0;
+
+ file->private_data = cpcap_uc_info;
+ dev_info(&cpcap_uc_info->cpcap->spi->dev, "CPCAP uC: open status:%d\n",
+ retval);
+
+ return retval;
+}
+
+int cpcap_uc_start(struct cpcap_device *cpcap, enum cpcap_macro macro)
+{
+ int retval = -EFAULT;
+ struct cpcap_uc_data *data = cpcap->ucdata;
+
+ if ((data->is_ready) &&
+ (macro > CPCAP_MACRO_USEROFF) && (macro < CPCAP_MACRO__END) &&
+ (data->uc_reset == 0)) {
+ if ((macro == CPCAP_MACRO_4) ||
+ ((cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (macro == CPCAP_MACRO_12)) ||
+ ((cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (macro == CPCAP_MACRO_15))) {
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_MI2,
+ (1 << macro),
+ (1 << macro));
+ } else {
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_MIM1,
+ 0, (1 << macro));
+ }
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_uc_start);
+
+int cpcap_uc_stop(struct cpcap_device *cpcap, enum cpcap_macro macro)
+{
+ int retval = -EFAULT;
+
+ if ((macro > CPCAP_MACRO_4) &&
+ (macro < CPCAP_MACRO__END)) {
+ if ((cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (macro == CPCAP_MACRO_12)) {
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_MI2,
+ 0, (1 << macro));
+ } else {
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_MIM1,
+ (1 << macro), (1 << macro));
+ }
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_uc_stop);
+
+unsigned char cpcap_uc_status(struct cpcap_device *cpcap,
+ enum cpcap_macro macro)
+{
+ unsigned char retval = 0;
+ unsigned short regval;
+
+ if (macro < CPCAP_MACRO__END) {
+ if ((macro <= CPCAP_MACRO_4) ||
+ ((cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (macro == CPCAP_MACRO_12))) {
+ cpcap_regacc_read(cpcap, CPCAP_REG_MI2, &regval);
+
+ if (regval & (1 << macro))
+ retval = 1;
+ } else {
+ cpcap_regacc_read(cpcap, CPCAP_REG_MIM1, &regval);
+
+ if (!(regval & (1 << macro)))
+ retval = 1;
+ }
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_uc_status);
+
+#ifdef CONFIG_PM_DBG_DRV
+int cpcap_uc_ram_write(struct cpcap_device *cpcap, unsigned short address,
+ unsigned short num_words, unsigned short *data)
+{
+ return ram_write(cpcap->ucdata, address, num_words, data);
+}
+
+int cpcap_uc_ram_read(struct cpcap_device *cpcap, unsigned short address,
+ unsigned short num_words, unsigned short *data)
+{
+ return ram_read(cpcap->ucdata, address, num_words, data);
+}
+#endif /* CONFIG_PM_DBG_DRV */
+
+static int fw_load(struct cpcap_uc_data *uc_data, struct device *dev)
+{
+ int err;
+ const struct ihex_binrec *rec;
+ const struct firmware *fw;
+ unsigned short *buf;
+ int i;
+ unsigned short num_bytes;
+ unsigned short num_words;
+ unsigned char odd_bytes;
+ struct cpcap_platform_data *data;
+
+ data = uc_data->cpcap->spi->controller_data;
+
+ if (!uc_data || !dev)
+ return -EINVAL;
+
+ if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST)
+ err = request_ihex_firmware(&fw, "cpcap/firmware_0_2x.fw", dev);
+ else
+ err = request_ihex_firmware(&fw, "cpcap/firmware_1_2x.fw", dev);
+
+ if (err) {
+ dev_err(dev, "Failed to load \"cpcap/firmware_%d_2x.fw\": %d\n",
+ uc_data->cpcap->vendor, err);
+ goto err;
+ }
+
+ for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) {
+ odd_bytes = 0;
+ num_bytes = be16_to_cpu(rec->len);
+
+ /* Since loader requires words, need even number of bytes. */
+ if (be16_to_cpu(rec->len) % 2) {
+ num_bytes++;
+ odd_bytes = 1;
+ }
+
+ num_words = num_bytes >> 1;
+ dev_dbg(dev, "Loading %d word(s) at 0x%04x\n",
+ num_words, be32_to_cpu(rec->addr));
+
+ buf = kzalloc(num_bytes, GFP_KERNEL);
+ if (buf) {
+ for (i = 0; i < num_words; i++) {
+ if (odd_bytes && (i == (num_words - 1)))
+ buf[i] = rec->data[i * 2];
+ else
+ buf[i] = ((uint16_t *)rec->data)[i];
+
+ buf[i] = be16_to_cpu(buf[i]);
+ }
+
+ err = ram_write(uc_data, be32_to_cpu(rec->addr),
+ num_words, buf);
+ kfree(buf);
+
+ if (err) {
+ dev_err(dev, "RAM write failed: %d\n", err);
+ break;
+ }
+ } else {
+ err = -ENOMEM;
+ dev_err(dev, "RAM write failed: %d\n", err);
+ break;
+ }
+ }
+
+ release_firmware(fw);
+
+ if (!err) {
+ uc_data->is_ready = 1;
+
+ if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST)
+ err = ram_write(uc_data, 0x012C, 1, &(data->is_umts));
+ else
+ err = ram_write(uc_data, 0x90F0, 1, &(data->is_umts));
+
+ dev_info(dev, "Loaded Sec SPI Init = %d: %d\n",
+ data->is_umts, err);
+
+ if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST)
+ err = ram_write(uc_data, HWCFG_ADDR_ST,
+ CPCAP_HWCFG_NUM, data->hwcfg);
+ else
+ err = ram_write(uc_data, HWCFG_ADDR_TI,
+ CPCAP_HWCFG_NUM, data->hwcfg);
+
+ dev_info(dev, "Loaded HWCFG data:");
+ for (i = 0; i < CPCAP_HWCFG_NUM; i++)
+ dev_info(dev, " 0x%04x", data->hwcfg[i]);
+ dev_info(dev, "result: %d\n", err);
+
+ err = cpcap_uc_start(uc_data->cpcap, CPCAP_MACRO_4);
+ dev_info(dev, "Started macro 4: %d\n", err);
+
+ err = cpcap_uc_start(uc_data->cpcap, CPCAP_MACRO_15);
+ dev_info(dev, "Started macro 15: %d\n", err);
+ }
+
+err:
+ return err;
+}
+
+static int cpcap_uc_probe(struct platform_device *pdev)
+{
+ int retval = 0;
+ struct cpcap_uc_data *data;
+
+ if (pdev->dev.platform_data == NULL) {
+ dev_err(&pdev->dev, "no platform_data\n");
+ return -EINVAL;
+ }
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->cpcap = pdev->dev.platform_data;
+ data->uc_reset = 0;
+ data->is_supported = 0;
+ data->req.address = 0;
+ data->req.data = NULL;
+ data->req.num_words = 0;
+
+ init_completion(&data->completion);
+ mutex_init(&data->lock);
+ platform_set_drvdata(pdev, data);
+ cpcap_uc_info = data;
+ data->cpcap->ucdata = data;
+
+ if (((data->cpcap->vendor == CPCAP_VENDOR_TI) &&
+ (data->cpcap->revision >= CPCAP_REVISION_2_0)) ||
+ (data->cpcap->vendor == CPCAP_VENDOR_ST)) {
+ retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_PRIMAC,
+ primac_handler, data);
+ if (retval)
+ goto err_free;
+
+ cpcap_irq_clear(data->cpcap, CPCAP_IRQ_UCRESET);
+ retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_UCRESET,
+ reset_handler, data);
+ if (retval)
+ goto err_primac;
+
+ retval = cpcap_irq_register(data->cpcap,
+ CPCAP_IRQ_UC_PRIRAMR,
+ ram_read_state_machine, data);
+ if (retval)
+ goto err_ucreset;
+
+ retval = cpcap_irq_register(data->cpcap,
+ CPCAP_IRQ_UC_PRIRAMW,
+ ram_write_state_machine, data);
+ if (retval)
+ goto err_priramr;
+
+ retval = misc_register(&uc_dev);
+ if (retval)
+ goto err_priramw;
+
+ data->is_supported = 1;
+
+ cpcap_regacc_write(data->cpcap, CPCAP_REG_MIM1, 0xFFFF,
+ 0xFFFF);
+
+ retval = fw_load(data, &pdev->dev);
+ if (retval)
+ goto err_fw;
+ } else
+ retval = -ENODEV;
+
+ return retval;
+
+err_fw:
+ misc_deregister(&uc_dev);
+err_priramw:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+err_priramr:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+err_ucreset:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UCRESET);
+err_primac:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_PRIMAC);
+err_free:
+ kfree(data);
+
+ return retval;
+}
+
+static int __exit cpcap_uc_remove(struct platform_device *pdev)
+{
+ struct cpcap_uc_data *data = platform_get_drvdata(pdev);
+
+ misc_deregister(&uc_dev);
+
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_PRIMAC);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UCRESET);
+
+ kfree(data);
+ return 0;
+}
+
+
+static struct platform_driver cpcap_uc_driver = {
+ .probe = cpcap_uc_probe,
+ .remove = __exit_p(cpcap_uc_remove),
+ .driver = {
+ .name = "cpcap_uc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init cpcap_uc_init(void)
+{
+ return platform_driver_register(&cpcap_uc_driver);
+}
+subsys_initcall(cpcap_uc_init);
+
+static void __exit cpcap_uc_exit(void)
+{
+ platform_driver_unregister(&cpcap_uc_driver);
+}
+module_exit(cpcap_uc_exit);
+
+MODULE_ALIAS("platform:cpcap_uc");
+MODULE_DESCRIPTION("CPCAP uC driver");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("cpcap/firmware_0_2x.fw");
+MODULE_FIRMWARE("cpcap/firmware_1_2x.fw");
diff --git a/drivers/mfd/cpcap-usb-det.c b/drivers/mfd/cpcap-usb-det.c
new file mode 100644
index 00000000000..d594b123098
--- /dev/null
+++ b/drivers/mfd/cpcap-usb-det.c
@@ -0,0 +1,948 @@
+/*
+ * Copyright (C) 2007 - 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/errno.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+#include <linux/gpio.h>
+
+#include <linux/regulator/consumer.h>
+
+#include <linux/spi/cpcap.h>
+#include <linux/spi/cpcap-regbits.h>
+#include <linux/spi/spi.h>
+
+#define MS_TO_NS(x) ((x) * NSEC_PER_MSEC)
+#define CPCAP_SENSE4_LS 8
+#define CPCAP_BIT_DP_S_LS (CPCAP_BIT_DP_S << CPCAP_SENSE4_LS)
+#define CPCAP_BIT_DM_S_LS (CPCAP_BIT_DM_S << CPCAP_SENSE4_LS)
+
+#define SENSE_USB (CPCAP_BIT_ID_FLOAT_S | \
+ CPCAP_BIT_CHRGCURR1_S | \
+ CPCAP_BIT_VBUSVLD_S | \
+ CPCAP_BIT_SESSVLD_S)
+
+#define SENSE_2WIRE (CPCAP_BIT_ID_FLOAT_S | \
+ CPCAP_BIT_CHRGCURR1_S | \
+ CPCAP_BIT_VBUSVLD_S | \
+ CPCAP_BIT_SESSVLD_S | \
+ CPCAP_BIT_DP_S_LS)
+
+#define SENSE_USB_FLASH (CPCAP_BIT_CHRGCURR1_S | \
+ CPCAP_BIT_VBUSVLD_S | \
+ CPCAP_BIT_SESSVLD_S)
+
+#define SENSE_FACTORY (CPCAP_BIT_ID_FLOAT_S | \
+ CPCAP_BIT_ID_GROUND_S | \
+ CPCAP_BIT_CHRGCURR1_S | \
+ CPCAP_BIT_VBUSVLD_S | \
+ CPCAP_BIT_SESSVLD_S)
+
+/* This Sense mask is needed because on TI the CHRGCURR1 interrupt is not always
+ * set. In Factory Mode the comparator follows the Charge current only. */
+#define SENSE_FACTORY_COM (CPCAP_BIT_ID_FLOAT_S | \
+ CPCAP_BIT_ID_GROUND_S | \
+ CPCAP_BIT_VBUSVLD_S | \
+ CPCAP_BIT_SESSVLD_S)
+
+#define SENSE_CHARGER_FLOAT (CPCAP_BIT_ID_FLOAT_S | \
+ CPCAP_BIT_CHRGCURR1_S | \
+ CPCAP_BIT_VBUSVLD_S | \
+ CPCAP_BIT_SESSVLD_S | \
+ CPCAP_BIT_SE1_S | \
+ CPCAP_BIT_DM_S_LS | \
+ CPCAP_BIT_DP_S_LS)
+
+#define SENSE_CHARGER (CPCAP_BIT_CHRGCURR1_S | \
+ CPCAP_BIT_VBUSVLD_S | \
+ CPCAP_BIT_SESSVLD_S | \
+ CPCAP_BIT_SE1_S | \
+ CPCAP_BIT_DM_S_LS | \
+ CPCAP_BIT_DP_S_LS)
+
+#define SENSE_IDLOW_CHARGER (CPCAP_BIT_CHRGCURR1_S | \
+ CPCAP_BIT_VBUSVLD_S | \
+ CPCAP_BIT_SESSVLD_S | \
+ CPCAP_BIT_ID_GROUND_S | \
+ CPCAP_BIT_DP_S_LS)
+
+#define SENSE_CHARGER_MASK (CPCAP_BIT_ID_GROUND_S | \
+ CPCAP_BIT_SESSVLD_S)
+
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+#define TWOWIRE_HANDSHAKE_LEN 6 /* Number of bytes in handshake sequence */
+#define TWOWIRE_DELAY MS_TO_NS(10) /* delay between edges in ns */
+#define BI2BY 8 /* bits per byte */
+#define TWOWIRE_HANDSHAKE_SEQUENCE {0x07, 0xC1, 0xF3, 0xE7, 0xCF, 0x9F}
+#endif
+
+#define UNDETECT_TRIES 5
+
+#define CPCAP_USB_DET_PRINT_STATUS (1U << 0)
+#define CPCAP_USB_DET_PRINT_TRANSITION (1U << 1)
+static int cpcap_usb_det_debug_mask;
+
+module_param_named(cpcap_usb_det_debug_mask, cpcap_usb_det_debug_mask, int,
+ S_IRUGO | S_IWUSR | S_IWGRP);
+
+#define cpcap_usb_det_debug(debug_level_mask, args...) \
+ do { \
+ if (cpcap_usb_det_debug_mask & \
+ CPCAP_USB_DET_PRINT_##debug_level_mask) { \
+ pr_info(args); \
+ } \
+ } while (0)
+
+enum cpcap_det_state {
+ CONFIG,
+ SAMPLE_1,
+ SAMPLE_2,
+ IDENTIFY,
+ USB,
+ FACTORY,
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ START2WIRE,
+ FINISH2WIRE,
+#endif
+};
+
+enum cpcap_accy {
+ CPCAP_ACCY_USB,
+ CPCAP_ACCY_FACTORY,
+ CPCAP_ACCY_CHARGER,
+ CPCAP_ACCY_NONE,
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ CPCAP_ACCY_2WIRE,
+#endif
+ /* Used while debouncing the accessory. */
+ CPCAP_ACCY_UNKNOWN,
+};
+
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+enum cpcap_twowire_state {
+ CPCAP_TWOWIRE_RUNNING,
+ CPCAP_TWOWIRE_DONE,
+};
+
+struct cpcap_usb_det_2wire {
+ int gpio;
+ unsigned short pos;
+ unsigned char data[TWOWIRE_HANDSHAKE_LEN];
+ enum cpcap_twowire_state state;
+};
+#endif
+
+struct cpcap_usb_det_data {
+ struct cpcap_device *cpcap;
+ struct delayed_work work;
+ unsigned short sense;
+ unsigned short prev_sense;
+ enum cpcap_det_state state;
+ enum cpcap_accy usb_accy;
+ struct platform_device *usb_dev;
+ struct platform_device *usb_connected_dev;
+ struct platform_device *charger_connected_dev;
+ struct regulator *regulator;
+ struct wake_lock wake_lock;
+ unsigned char is_vusb_enabled;
+ unsigned char undetect_cnt;
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ struct hrtimer hr_timer;
+ struct cpcap_usb_det_2wire twowire_data;
+#endif
+};
+
+static unsigned char vbus_valid_adc_check(struct cpcap_usb_det_data *data);
+
+static const char *accy_devices[] = {
+ "cpcap_usb_charger",
+ "cpcap_factory",
+ "cpcap_charger",
+};
+
+#ifdef CONFIG_USB_TESTING_POWER
+static int testing_power_enable = -1;
+module_param(testing_power_enable, int, 0644);
+MODULE_PARM_DESC(testing_power_enable, "Enable factory cable power "
+ "supply function for testing");
+#endif
+
+static void vusb_enable(struct cpcap_usb_det_data *data)
+{
+ int ret;
+ if (!data->is_vusb_enabled) {
+ wake_lock(&data->wake_lock);
+ ret = regulator_enable(data->regulator);
+ data->is_vusb_enabled = 1;
+ }
+}
+
+static void vusb_disable(struct cpcap_usb_det_data *data)
+{
+ int ret;
+ if (data->is_vusb_enabled) {
+ wake_unlock(&data->wake_lock);
+ ret = regulator_disable(data->regulator);
+ data->is_vusb_enabled = 0;
+ }
+}
+
+static int get_sense(struct cpcap_usb_det_data *data)
+{
+ int retval = -EFAULT;
+ unsigned short value;
+ struct cpcap_device *cpcap;
+
+ if (!data)
+ return -EFAULT;
+ cpcap = data->cpcap;
+
+ retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS1, &value);
+ if (retval)
+ return retval;
+
+ /* Clear ASAP after read. */
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT1,
+ (CPCAP_BIT_CHRG_DET_I |
+ CPCAP_BIT_ID_FLOAT_I |
+ CPCAP_BIT_ID_GROUND_I),
+ (CPCAP_BIT_CHRG_DET_I |
+ CPCAP_BIT_ID_FLOAT_I |
+ CPCAP_BIT_ID_GROUND_I));
+ if (retval)
+ return retval;
+
+ data->sense = value & (CPCAP_BIT_ID_FLOAT_S |
+ CPCAP_BIT_ID_GROUND_S);
+
+ retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS2, &value);
+ if (retval)
+ return retval;
+
+ /* Clear ASAP after read. */
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT2,
+ (CPCAP_BIT_CHRGCURR1_I |
+ CPCAP_BIT_VBUSVLD_I |
+ CPCAP_BIT_SESSVLD_I |
+ CPCAP_BIT_SE1_I),
+ (CPCAP_BIT_CHRGCURR1_I |
+ CPCAP_BIT_VBUSVLD_I |
+ CPCAP_BIT_SESSVLD_I |
+ CPCAP_BIT_SE1_I));
+ if (retval)
+ return retval;
+
+ data->sense |= value & (CPCAP_BIT_CHRGCURR1_S |
+ CPCAP_BIT_VBUSVLD_S |
+ CPCAP_BIT_SESSVLD_S |
+ CPCAP_BIT_SE1_S);
+
+ retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS4, &value);
+ if (retval)
+ return retval;
+
+ /* Clear ASAP after read. */
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT4,
+ (CPCAP_BIT_DP_I |
+ CPCAP_BIT_DM_I),
+ (CPCAP_BIT_DP_I |
+ CPCAP_BIT_DM_I));
+ if (retval)
+ return retval;
+
+ data->sense |= (value & (CPCAP_BIT_DP_S |
+ CPCAP_BIT_DM_S)) << CPCAP_SENSE4_LS;
+
+ return 0;
+}
+
+static int configure_hardware(struct cpcap_usb_det_data *data,
+ enum cpcap_accy accy)
+{
+ int retval;
+
+ /* Take control of pull up from ULPI. */
+ retval = cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3,
+ CPCAP_BIT_PU_SPI,
+ CPCAP_BIT_PU_SPI);
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1,
+ CPCAP_BIT_DP150KPU,
+ (CPCAP_BIT_DP150KPU | CPCAP_BIT_DP1K5PU |
+ CPCAP_BIT_DM1K5PU | CPCAP_BIT_DPPD |
+ CPCAP_BIT_DMPD));
+
+ switch (accy) {
+ case CPCAP_ACCY_USB:
+ case CPCAP_ACCY_FACTORY:
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, 0,
+ CPCAP_BIT_VBUSPD);
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC2,
+ CPCAP_BIT_USBXCVREN,
+ CPCAP_BIT_USBXCVREN);
+ /* Give USB driver control of pull up via ULPI. */
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3,
+ 0,
+ CPCAP_BIT_PU_SPI |
+ CPCAP_BIT_DMPD_SPI |
+ CPCAP_BIT_DPPD_SPI |
+ CPCAP_BIT_SUSPEND_SPI |
+ CPCAP_BIT_ULPI_SPI_SEL);
+
+ if ((data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (data->cpcap->revision == CPCAP_REVISION_2_0))
+ vusb_enable(data);
+
+ break;
+
+ case CPCAP_ACCY_CHARGER:
+ /* Disable Reverse Mode */
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_CRM,
+ 0, CPCAP_BIT_RVRSMODE);
+ /* Enable VBus PullDown */
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1,
+ CPCAP_BIT_VBUSPD,
+ CPCAP_BIT_VBUSPD);
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, 0,
+ CPCAP_BIT_VBUSSTBY_EN);
+ break;
+
+ case CPCAP_ACCY_UNKNOWN:
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, 0,
+ CPCAP_BIT_VBUSPD);
+ break;
+
+ case CPCAP_ACCY_NONE:
+ default:
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1,
+ CPCAP_BIT_VBUSPD,
+ CPCAP_BIT_VBUSPD);
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC2, 0,
+ CPCAP_BIT_USBXCVREN);
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3,
+ CPCAP_BIT_DMPD_SPI |
+ CPCAP_BIT_DPPD_SPI |
+ CPCAP_BIT_SUSPEND_SPI |
+ CPCAP_BIT_ULPI_SPI_SEL,
+ CPCAP_BIT_DMPD_SPI |
+ CPCAP_BIT_DPPD_SPI |
+ CPCAP_BIT_SUSPEND_SPI |
+ CPCAP_BIT_ULPI_SPI_SEL);
+ break;
+ }
+
+ if (retval != 0)
+ retval = -EFAULT;
+
+ return retval;
+}
+
+static unsigned char vbus_valid_adc_check(struct cpcap_usb_det_data *data)
+{
+ struct cpcap_adc_request req;
+ int ret;
+
+ req.format = CPCAP_ADC_FORMAT_CONVERTED;
+ req.timing = CPCAP_ADC_TIMING_IMM;
+ req.type = CPCAP_ADC_TYPE_BANK_0;
+
+ ret = cpcap_adc_sync_read(data->cpcap, &req);
+ if (ret) {
+ dev_err(&data->cpcap->spi->dev,
+ "%s: ADC Read failed\n", __func__);
+ return false;
+ }
+ return ((req.result[CPCAP_ADC_CHG_ISENSE] < 50) &&
+ (req.result[CPCAP_ADC_VBUS] <
+ (req.result[CPCAP_ADC_BATTP]))) ? false : true;
+}
+
+
+static void notify_accy(struct cpcap_usb_det_data *data, enum cpcap_accy accy)
+{
+ dev_info(&data->cpcap->spi->dev, "notify_accy: accy=%d\n", accy);
+
+ if ((data->usb_accy != CPCAP_ACCY_NONE) && (data->usb_dev != NULL)) {
+ platform_device_del(data->usb_dev);
+ data->usb_dev = NULL;
+ }
+
+ configure_hardware(data, accy);
+ data->usb_accy = accy;
+
+ if (accy != CPCAP_ACCY_NONE) {
+ data->usb_dev = platform_device_alloc(accy_devices[accy], -1);
+ if (data->usb_dev) {
+ data->usb_dev->dev.platform_data = data->cpcap;
+ platform_device_add(data->usb_dev);
+ }
+ } else
+ vusb_disable(data);
+
+ if ((accy == CPCAP_ACCY_USB) || (accy == CPCAP_ACCY_FACTORY)) {
+ if (!data->usb_connected_dev) {
+ data->usb_connected_dev =
+ platform_device_alloc("cpcap_usb_connected", -1);
+ platform_device_add(data->usb_connected_dev);
+ }
+ } else if (data->usb_connected_dev) {
+ platform_device_del(data->usb_connected_dev);
+ data->usb_connected_dev = NULL;
+ }
+
+ if (accy == CPCAP_ACCY_CHARGER) {
+ if (!data->charger_connected_dev) {
+ data->charger_connected_dev =
+ platform_device_alloc("cpcap_charger_connected",
+ -1);
+ platform_device_add(data->charger_connected_dev);
+ }
+ } else if (data->charger_connected_dev) {
+ platform_device_del(data->charger_connected_dev);
+ data->charger_connected_dev = NULL;
+ }
+}
+
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+static enum hrtimer_restart cpcap_send_2wire_sendbit(struct hrtimer *timer)
+{
+ struct cpcap_usb_det_data *usb_det_data =
+ container_of(timer, struct cpcap_usb_det_data, hr_timer);
+ struct cpcap_usb_det_2wire *twd = &(usb_det_data->twowire_data);
+ enum hrtimer_restart ret = HRTIMER_NORESTART;
+ bool value;
+
+ if (gpio_is_valid(twd->gpio) &&
+ (twd->pos < TWOWIRE_HANDSHAKE_LEN * BI2BY)) {
+ value = !!(twd->data[twd->pos/BI2BY] &
+ (1 << (BI2BY - (twd->pos % BI2BY) - 1)));
+ gpio_set_value(twd->gpio, value);
+ ret = HRTIMER_RESTART;
+ }
+
+ if (++twd->pos == TWOWIRE_HANDSHAKE_LEN * BI2BY ||
+ !gpio_is_valid(twd->gpio)) {
+ twd->state = CPCAP_TWOWIRE_DONE;
+ ret = HRTIMER_NORESTART;
+ }
+
+ if (ret == HRTIMER_RESTART)
+ hrtimer_forward(timer, ktime_get(), ns_to_ktime(TWOWIRE_DELAY));
+
+ return ret;
+}
+#endif
+
+static void detection_work(struct work_struct *work)
+{
+ struct cpcap_usb_det_data *data =
+ container_of(work, struct cpcap_usb_det_data, work.work);
+ unsigned char isVBusValid = 0;
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ ktime_t next_time;
+ int sessvalid;
+ unsigned char handshake[TWOWIRE_HANDSHAKE_LEN] =
+ TWOWIRE_HANDSHAKE_SEQUENCE;
+#endif
+
+ switch (data->state) {
+ case CONFIG:
+ vusb_enable(data);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_CURR1);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDGND);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDFLOAT);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_DPI);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_DMI);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SESSVLD);
+
+ configure_hardware(data, CPCAP_ACCY_UNKNOWN);
+
+ data->undetect_cnt = 0;
+ data->state = SAMPLE_1;
+ schedule_delayed_work(&data->work, msecs_to_jiffies(11));
+ break;
+
+ case SAMPLE_1:
+ get_sense(data);
+ data->state = SAMPLE_2;
+ schedule_delayed_work(&data->work, msecs_to_jiffies(100));
+ break;
+
+ case SAMPLE_2:
+ data->prev_sense = data->sense;
+ get_sense(data);
+
+ if (data->prev_sense != data->sense) {
+ /* Stay in this state */
+ data->state = SAMPLE_2;
+ schedule_delayed_work(&data->work,
+ msecs_to_jiffies(100));
+ } else if (!(data->sense & CPCAP_BIT_SE1_S) &&
+ (data->sense & CPCAP_BIT_ID_FLOAT_S) &&
+ !(data->sense & CPCAP_BIT_ID_GROUND_S) &&
+ !(data->sense & CPCAP_BIT_SESSVLD_S)) {
+ data->state = IDENTIFY;
+ schedule_delayed_work(&data->work,
+ msecs_to_jiffies(100));
+ } else {
+ data->state = IDENTIFY;
+ schedule_delayed_work(&data->work, 0);
+ }
+ break;
+
+ case IDENTIFY:
+ get_sense(data);
+ data->state = CONFIG;
+ isVBusValid = vbus_valid_adc_check(data);
+
+ if ((data->sense == SENSE_USB) ||
+ (data->sense == SENSE_USB_FLASH)) {
+ notify_accy(data, CPCAP_ACCY_USB);
+
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND);
+
+ /* Special handling of USB cable undetect. */
+ data->state = USB;
+ } else if ((data->sense == SENSE_FACTORY) ||
+ (data->sense == SENSE_FACTORY_COM)) {
+#ifdef CONFIG_USB_TESTING_POWER
+ if (testing_power_enable > 0) {
+ notify_accy(data, CPCAP_ACCY_NONE);
+ cpcap_irq_unmask(data->cpcap,
+ CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_unmask(data->cpcap,
+ CPCAP_IRQ_CHRG_CURR1);
+ cpcap_irq_unmask(data->cpcap,
+ CPCAP_IRQ_VBUSVLD);
+ break;
+ }
+#endif
+ notify_accy(data, CPCAP_ACCY_FACTORY);
+
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+
+ /* Special handling of factory cable undetect. */
+ data->state = FACTORY;
+ } else if (((data->sense | CPCAP_BIT_VBUSVLD_S) == \
+ SENSE_CHARGER_FLOAT) ||
+ ((data->sense | CPCAP_BIT_VBUSVLD_S) == \
+ SENSE_CHARGER) ||
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ (data->usb_accy == CPCAP_ACCY_2WIRE) ||
+#endif
+ (data->sense == SENSE_IDLOW_CHARGER)) {
+
+ if ((isVBusValid) && ((data->sense == \
+ SENSE_CHARGER_FLOAT) ||
+ (data->sense == SENSE_CHARGER) ||
+ (data->sense == SENSE_IDLOW_CHARGER) ||
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ (data->usb_accy == CPCAP_ACCY_2WIRE) ||
+#endif
+ (data->sense & CPCAP_BIT_SESSVLD_S))) {
+ /* Wakeup device from Suspend especially when
+ * you are coming from dipping voltage[<4.2V]
+ * to higher one [4.6V - VBUS,5V]
+ */
+ if (!(wake_lock_active(&data->wake_lock)))
+ wake_lock(&data->wake_lock);
+
+ notify_accy(data, CPCAP_ACCY_CHARGER);
+ /* VBUS is valid and also session valid bit
+ * is set hence, we notify that charger is
+ * connected
+ */
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND);
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ data->state = FINISH2WIRE;
+ schedule_delayed_work(&data->work,
+ msecs_to_jiffies(500));
+#else
+ data->state = CONFIG;
+#endif
+ } else if ((!isVBusValid) &&
+ ((!(data->sense & CPCAP_BIT_SESSVLD_S) ||
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ (data->usb_accy == CPCAP_ACCY_2WIRE) ||
+#endif
+ (!(data->sense & CPCAP_BIT_VBUSVLD_S))))) {
+ /* Condition when the USB charger is connected &
+ * for some reason Voltage falls below the 4.4V
+ * threshold. Since USB is connected, we reset
+ * the State Machine and wait for the voltage to
+ * reach the high threshold
+ */
+ data->state = CONFIG;
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ data->usb_accy = CPCAP_ACCY_NONE;
+ if (gpio_is_valid(data->twowire_data.gpio))
+ gpio_set_value(data->twowire_data.gpio,
+ 0);
+#endif
+
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_unmask(data->cpcap,
+ CPCAP_IRQ_VBUSVLD);
+ cpcap_irq_unmask(data->cpcap,
+ CPCAP_IRQ_CHRG_DET);
+
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD);
+ schedule_delayed_work(&data->work, 0);
+ }
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ } else if ((data->sense == SENSE_2WIRE) &&
+ (data->usb_accy == CPCAP_ACCY_NONE)) {
+ /* wait 750ms with GPIO low to force idle state */
+ if (gpio_is_valid(data->twowire_data.gpio)) {
+ gpio_set_value(data->twowire_data.gpio, 0);
+ data->state = START2WIRE;
+ schedule_delayed_work(&data->work,
+ msecs_to_jiffies(750));
+ } else {
+ printk(KERN_ERR "Detected 2wire charger but "
+ "GPIO is not configured\n");
+ data->state = CONFIG;
+ cpcap_irq_unmask(data->cpcap,
+ CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND);
+ }
+#endif
+ } else if ((data->sense & CPCAP_BIT_VBUSVLD_S) &&
+ (data->usb_accy == CPCAP_ACCY_NONE)) {
+ data->state = CONFIG;
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND);
+ } else {
+ notify_accy(data, CPCAP_ACCY_NONE);
+
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1);
+
+ /* When a charger is unpowered by unplugging from the
+ * wall, VBUS voltage will drop below CHRG_DET (3.5V)
+ * until the ICHRG bits are cleared. Once ICHRG is
+ * cleared, VBUS will rise above CHRG_DET, but below
+ * VBUSVLD (4.4V) briefly as it decays. If the charger
+ * is re-powered while VBUS is within this window, the
+ * VBUSVLD interrupt is needed to trigger charger
+ * detection.
+ *
+ * VBUSVLD must be masked before going into suspend.
+ * See cpcap_usb_det_suspend() for details.
+ */
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_VBUSVLD);
+ }
+ break;
+
+ case USB:
+ get_sense(data);
+
+ if ((data->sense & CPCAP_BIT_SE1_S) ||
+ (data->sense & CPCAP_BIT_ID_GROUND_S)) {
+ data->state = CONFIG;
+ schedule_delayed_work(&data->work, 0);
+ } else if (!(data->sense & CPCAP_BIT_VBUSVLD_S)) {
+ if (data->undetect_cnt++ < UNDETECT_TRIES) {
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_mask(data->cpcap,
+ CPCAP_IRQ_CHRG_CURR1);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDGND);
+ data->state = USB;
+ schedule_delayed_work(&data->work,
+ msecs_to_jiffies(100));
+ } else {
+ data->state = CONFIG;
+ schedule_delayed_work(&data->work, 0);
+ }
+ } else {
+ data->state = USB;
+ data->undetect_cnt = 0;
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND);
+ }
+ break;
+
+ case FACTORY:
+ get_sense(data);
+
+ /* The removal of a factory cable can only be detected if a
+ * charger is attached.
+ */
+ if (data->sense & CPCAP_BIT_SE1_S) {
+#ifdef CONFIG_TTA_CHARGER
+ enable_tta();
+#endif
+ data->state = CONFIG;
+ schedule_delayed_work(&data->work, 0);
+ } else {
+ data->state = FACTORY;
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+ }
+ break;
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ case START2WIRE:
+ sessvalid = (data->sense & CPCAP_BIT_SESSVLD_S);
+ memcpy(data->twowire_data.data, handshake,
+ TWOWIRE_HANDSHAKE_LEN);
+ data->twowire_data.pos = 5;
+ data->twowire_data.state = CPCAP_TWOWIRE_RUNNING;
+ next_time = ktime_set(0, TWOWIRE_DELAY);
+ hrtimer_start(&data->hr_timer, next_time, HRTIMER_MODE_REL);
+
+ while (sessvalid && data->twowire_data.state !=
+ CPCAP_TWOWIRE_DONE) {
+ msleep(10);
+ get_sense(data);
+ sessvalid = (data->sense & CPCAP_BIT_SESSVLD_S);
+ }
+
+ if (sessvalid && data->twowire_data.state ==
+ CPCAP_TWOWIRE_DONE) {
+ data->usb_accy = CPCAP_ACCY_2WIRE;
+ data->state = IDENTIFY;
+ schedule_delayed_work(&data->work, 0);
+ } else {
+ printk(KERN_ERR "2wire removed durring handshake\n");
+ hrtimer_cancel(&data->hr_timer);
+ if (gpio_is_valid(data->twowire_data.gpio))
+ gpio_set_value(data->twowire_data.gpio, 0);
+ data->state = CONFIG;
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND);
+ }
+ break;
+ case FINISH2WIRE:
+ if (gpio_is_valid(data->twowire_data.gpio))
+ gpio_set_value(data->twowire_data.gpio, 0);
+ data->state = CONFIG;
+ break;
+#endif
+ default:
+ /* This shouldn't happen. Need to reset state machine. */
+ vusb_disable(data);
+ data->state = CONFIG;
+ schedule_delayed_work(&data->work, 0);
+ break;
+ }
+}
+
+static void int_handler(enum cpcap_irqs int_event, void *data)
+{
+ struct cpcap_usb_det_data *usb_det_data = data;
+ schedule_delayed_work(&(usb_det_data->work), 0);
+}
+
+static int cpcap_usb_det_probe(struct platform_device *pdev)
+{
+ int retval;
+ struct cpcap_usb_det_data *data;
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ struct cpcap_platform_data *platform_data;
+#endif
+
+ if (pdev->dev.platform_data == NULL) {
+ dev_err(&pdev->dev, "no platform_data\n");
+ return -EINVAL;
+ }
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->cpcap = pdev->dev.platform_data;
+ data->state = CONFIG;
+ platform_set_drvdata(pdev, data);
+ INIT_DELAYED_WORK(&data->work, detection_work);
+ data->usb_accy = CPCAP_ACCY_NONE;
+ wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "usb");
+ data->undetect_cnt = 0;
+
+ data->regulator = regulator_get(NULL, "vusb");
+ if (IS_ERR(data->regulator)) {
+ dev_err(&pdev->dev, "Could not get regulator for cpcap_usb\n");
+ retval = PTR_ERR(data->regulator);
+ goto free_mem;
+ }
+ regulator_set_voltage(data->regulator, 3300000, 3300000);
+
+ retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_CHRG_DET,
+ int_handler, data);
+ retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_CHRG_CURR1,
+ int_handler, data);
+ retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_SE1,
+ int_handler, data);
+ retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_IDGND,
+ int_handler, data);
+ retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_VBUSVLD,
+ int_handler, data);
+ retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_IDFLOAT,
+ int_handler, data);
+ retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_DPI,
+ int_handler, data);
+ retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_DMI,
+ int_handler, data);
+ retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_SESSVLD,
+ int_handler, data);
+
+ /* Now that HW initialization is done, give USB control via ULPI. */
+ retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3,
+ 0, CPCAP_BIT_ULPI_SPI_SEL);
+
+#ifdef CONFIG_CHARGER_CPCAP_2WIRE
+ hrtimer_init(&(data->hr_timer), CLOCK_REALTIME, HRTIMER_MODE_REL);
+ data->hr_timer.function = &cpcap_send_2wire_sendbit;
+ if (data->cpcap->spi && data->cpcap->spi->controller_data) {
+ platform_data = data->cpcap->spi->controller_data;
+ data->twowire_data.gpio = platform_data->twowire_hndshk_gpio;
+ } else {
+ data->twowire_data.gpio = -1;
+ dev_err(&pdev->dev, "SPI platform_data missing\n");
+ retval = -EINVAL;
+ }
+#endif
+
+ if (retval != 0) {
+ dev_err(&pdev->dev, "Initialization Error\n");
+ retval = -ENODEV;
+ goto free_irqs;
+ }
+
+ dev_info(&pdev->dev, "CPCAP USB detection device probed\n");
+
+ /* Perform initial detection */
+ detection_work(&(data->work.work));
+
+ return 0;
+
+free_irqs:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_VBUSVLD);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDGND);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_CURR1);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDFLOAT);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_DPI);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_DMI);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_SESSVLD);
+ regulator_put(data->regulator);
+free_mem:
+ wake_lock_destroy(&data->wake_lock);
+ kfree(data);
+
+ return retval;
+}
+
+static int cpcap_usb_det_remove(struct platform_device *pdev)
+{
+ struct cpcap_usb_det_data *data = platform_get_drvdata(pdev);
+
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_DET);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_CURR1);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_SE1);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDGND);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_VBUSVLD);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDFLOAT);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_SESSVLD);
+
+ configure_hardware(data, CPCAP_ACCY_NONE);
+ cancel_delayed_work_sync(&data->work);
+
+ if ((data->usb_accy != CPCAP_ACCY_NONE) && (data->usb_dev != NULL))
+ platform_device_del(data->usb_dev);
+
+ vusb_disable(data);
+ regulator_put(data->regulator);
+
+ wake_lock_destroy(&data->wake_lock);
+
+ kfree(data);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int cpcap_usb_det_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct cpcap_usb_det_data *data = platform_get_drvdata(pdev);
+
+ /* VBUSVLD cannot be unmasked when entering suspend. If left
+ * unmasked, a false interrupt will be received, keeping the
+ * device out of suspend. The interrupt does not need to be
+ * unmasked when resuming from suspend since the use case
+ * for having the interrupt unmasked is over.
+ */
+ cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD);
+
+ return 0;
+}
+#else
+#define cpcap_usb_det_suspend NULL
+#endif
+
+static struct platform_driver cpcap_usb_det_driver = {
+ .probe = cpcap_usb_det_probe,
+ .remove = cpcap_usb_det_remove,
+ .suspend = cpcap_usb_det_suspend,
+ .driver = {
+ .name = "cpcap_usb_det",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init cpcap_usb_det_init(void)
+{
+ return cpcap_driver_register(&cpcap_usb_det_driver);
+}
+/* The CPCAP USB detection driver must be started later to give the MUSB
+ * driver time to complete its initialization. */
+late_initcall(cpcap_usb_det_init);
+
+static void __exit cpcap_usb_det_exit(void)
+{
+ platform_driver_unregister(&cpcap_usb_det_driver);
+}
+module_exit(cpcap_usb_det_exit);
+
+MODULE_ALIAS("platform:cpcap_usb_det");
+MODULE_DESCRIPTION("CPCAP USB detection driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/m4sensorhub-core.c b/drivers/mfd/m4sensorhub-core.c
new file mode 100644
index 00000000000..cf1a363921a
--- /dev/null
+++ b/drivers/mfd/m4sensorhub-core.c
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2012 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/err.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/rtc.h>
+#include <linux/gpio.h>
+#include <linux/string.h>
+#include <linux/m4sensorhub/MemMapLog.h>
+#include <linux/m4sensorhub.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+
+
+#define M4SENSORHUB_NUM_GPIOS 6
+
+/* --------------- Global Declarations -------------- */
+char m4sensorhub_debug;
+EXPORT_SYMBOL_GPL(m4sensorhub_debug);
+
+/* ------------ Local Function Prototypes ----------- */
+
+/* -------------- Local Data Structures ------------- */
+static struct miscdevice m4sensorhub_misc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = M4SENSORHUB_DRIVER_NAME,
+};
+
+/* --------------- Local Declarations -------------- */
+static struct m4sensorhub_data *m4sensorhub_misc_data;
+static DEFINE_MUTEX(m4sensorhub_driver_lock);
+
+unsigned short force_upgrade;
+module_param(force_upgrade, short, 0644);
+MODULE_PARM_DESC(force_upgrade, "Force FW download ignoring version check");
+
+unsigned short debug_level;
+module_param(debug_level, short, 0644);
+MODULE_PARM_DESC(debug_level, "Set debug level 1 (CRITICAL) to "
+ "7 (VERBOSE_DEBUG)");
+
+/* -------------- Global Functions ----------------- */
+struct m4sensorhub_data *m4sensorhub_client_get_drvdata(void)
+{
+ return m4sensorhub_misc_data;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_client_get_drvdata);
+
+
+/* -------------- Local Functions ----------------- */
+
+static ssize_t m4sensorhub_get_dbg(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "%d\n", m4sensorhub_debug);
+}
+
+/* BEGIN BOARD FILE */
+/* TODO: replace with request array */
+
+int m4sensorhub_set_bootmode(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_bootmode bootmode)
+{
+ if (!m4sensorhub) {
+ printk(KERN_ERR "set_bootmode: invalid pointer\n");
+ return -EINVAL;
+ }
+
+ switch (bootmode) {
+ case BOOTMODE00:
+ gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 0);
+ gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 0);
+ break;
+ case BOOTMODE01:
+ gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 1);
+ gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 0);
+ break;
+ case BOOTMODE10:
+ gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 0);
+ gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 1);
+ break;
+ case BOOTMODE11:
+ gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 1);
+ gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 1);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void minnow_m4sensorhub_hw_reset(struct m4sensorhub_data *m4sensorhub)
+{
+ if (!m4sensorhub) {
+ printk(KERN_ERR "m4sensorhub_hw_reset: invalid pointer\n");
+ return;
+ }
+
+ m4sensorhub_set_bootmode(m4sensorhub, BOOTMODE00);
+ gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 1);
+ msleep(5);
+ gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 0);
+ msleep(5);
+ gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 1);
+}
+
+/* callback from driver to initialize hardware on probe */
+static int minnow_m4sensorhub_hw_init(struct m4sensorhub_data *m4sensorhub,
+ struct device_node *node)
+{
+ int gpio;
+ int err = -EINVAL;
+
+ if (!m4sensorhub) {
+ printk(KERN_ERR "m4sensorhub_hw_init: invalid pointer\n");
+ err = -EINVAL;
+ goto error;
+ }
+ if (node == NULL) {
+ printk(KERN_ERR "m4sensorhub_hw_init: node null\n");
+ err = -EINVAL;
+ goto error;
+ }
+
+ gpio = of_get_named_gpio_flags(node, "mot,irq-gpio", 0, NULL);
+ err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-intr");
+ if (err) {
+ pr_err("Failed acquiring M4 Sensor Hub IRQ GPIO-%d (%d)\n",
+ gpio, err);
+ goto error;
+ }
+ gpio_direction_input(gpio);
+ m4sensorhub->hwconfig.irq_gpio = gpio;
+
+ gpio = of_get_named_gpio_flags(node, "mot,reset-gpio", 0, NULL);
+ err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-reset");
+ if (err) {
+ pr_err("Failed acquiring M4 Sensor Hub Reset GPIO-%d (%d)\n",
+ gpio, err);
+ goto error_reset;
+ }
+ gpio_direction_output(gpio, 1);
+ m4sensorhub->hwconfig.reset_gpio = gpio;
+
+ gpio = of_get_named_gpio_flags(node, "mot,wake-gpio", 0, NULL);
+ err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-wake");
+ if (err) {
+ pr_err("Failed acquiring M4 Sensor Hub Wake GPIO-%d (%d)\n",
+ gpio, err);
+ goto error_wake;
+ }
+ gpio_direction_output(gpio, 0);
+ m4sensorhub->hwconfig.wake_gpio = gpio;
+
+ gpio = of_get_named_gpio_flags(node, "mot,boot0-gpio", 0, NULL);
+ err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-boot0");
+ if (err) {
+ pr_err("Failed acquiring M4 Sensor Hub Boot0 GPIO-%d (%d)\n",
+ gpio, err);
+ goto error_boot0;
+ }
+ gpio_direction_output(gpio, 0);
+ m4sensorhub->hwconfig.boot0_gpio = gpio;
+
+ gpio = of_get_named_gpio_flags(node, "mot,boot1-gpio", 0, NULL);
+ err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-boot1");
+ if (err) {
+ pr_err("Failed acquiring M4 Sensor Hub Boot1 GPIO-%d (%d)\n",
+ gpio, err);
+ goto error_boot1;
+ }
+ gpio_direction_output(gpio, 0);
+ m4sensorhub->hwconfig.boot1_gpio = gpio;
+
+ gpio = of_get_named_gpio_flags(node, "mot,enable-gpio", 0, NULL);
+ err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-enable");
+ if (err) {
+ pr_err("Failed acquiring M4 Sensor Hub Enable GPIO-%d (%d)\n",
+ gpio, err);
+ goto error_enable;
+ }
+ gpio_direction_output(gpio, 0);
+ m4sensorhub->hwconfig.mpu_9150_en_gpio = gpio;
+
+ minnow_m4sensorhub_hw_reset(m4sensorhub);
+
+ return 0;
+
+error_enable:
+ gpio_free(m4sensorhub->hwconfig.boot1_gpio);
+ m4sensorhub->hwconfig.boot1_gpio = -1;
+error_boot1:
+ gpio_free(m4sensorhub->hwconfig.boot0_gpio);
+ m4sensorhub->hwconfig.boot0_gpio = -1;
+error_boot0:
+ gpio_free(m4sensorhub->hwconfig.wake_gpio);
+ m4sensorhub->hwconfig.wake_gpio = -1;
+error_wake:
+ gpio_free(m4sensorhub->hwconfig.reset_gpio);
+ m4sensorhub->hwconfig.reset_gpio = -1;
+error_reset:
+ gpio_free(m4sensorhub->hwconfig.irq_gpio);
+ m4sensorhub->hwconfig.irq_gpio = -1;
+error:
+ return err;
+}
+
+/* callback from driver to free hardware on shutdown */
+static void minnow_m4sensorhub_hw_free(struct m4sensorhub_data *m4sensorhub)
+{
+
+ if (!m4sensorhub) {
+ printk(KERN_ERR "hw_free: invalid pointer\n");
+ return;
+ }
+
+ if (m4sensorhub->hwconfig.irq_gpio >= 0) {
+ gpio_free(m4sensorhub->hwconfig.irq_gpio);
+ m4sensorhub->hwconfig.irq_gpio = -1;
+ }
+
+ if (m4sensorhub->hwconfig.reset_gpio >= 0) {
+ gpio_free(m4sensorhub->hwconfig.reset_gpio);
+ m4sensorhub->hwconfig.reset_gpio = -1;
+ }
+
+ if (m4sensorhub->hwconfig.wake_gpio >= 0) {
+ gpio_free(m4sensorhub->hwconfig.wake_gpio);
+ m4sensorhub->hwconfig.wake_gpio = -1;
+ }
+
+ if (m4sensorhub->hwconfig.boot0_gpio >= 0) {
+ gpio_free(m4sensorhub->hwconfig.boot0_gpio);
+ m4sensorhub->hwconfig.boot0_gpio = -1;
+ }
+
+ if (m4sensorhub->hwconfig.boot1_gpio >= 0) {
+ gpio_free(m4sensorhub->hwconfig.boot1_gpio);
+ m4sensorhub->hwconfig.boot1_gpio = -1;
+ }
+
+ if (m4sensorhub->hwconfig.mpu_9150_en_gpio >= 0) {
+ gpio_free(m4sensorhub->hwconfig.mpu_9150_en_gpio);
+ m4sensorhub->hwconfig.mpu_9150_en_gpio = -1;
+ }
+}
+
+/* END BOARD FILE FUNCTIONS */
+
+static ssize_t m4sensorhub_set_dbg(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long debug;
+
+ if ((strict_strtol(buf, 10, &debug) < 0) ||
+ (debug < M4SH_NODEBUG) || (debug > M4SH_VERBOSE_DEBUG))
+ return -EINVAL;
+
+ m4sensorhub_debug = debug;
+ KDEBUG(M4SH_CRITICAL, "M4 Sensor Hub debug level = %d\n",
+ m4sensorhub_debug);
+
+ return count;
+}
+
+static DEVICE_ATTR(debug_level, S_IRUGO|S_IWUGO, m4sensorhub_get_dbg,
+ m4sensorhub_set_dbg);
+
+static ssize_t m4sensorhub_get_loglevel(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned long long loglevel;
+
+ m4sensorhub_reg_read(m4sensorhub_misc_data,
+ M4SH_REG_LOG_LOGENABLE, (char *)&loglevel);
+ KDEBUG(M4SH_INFO, "M4 loglevel = %llx", loglevel);
+ return sprintf(buf, "%llu\n", loglevel);
+}
+void ParseAndUpdateLogLevels(char *tag, char *level,
+ unsigned long long *logLevels)
+{
+ int i;
+ int levelindex = -1;
+ int tagindex = -1;
+ unsigned long long mask;
+
+ for (i = 0; i < LOG_LEVELS_MAX; i++) {
+ if (strcmp(acLogLevels[i], level) == 0) {
+ levelindex = i;
+ break;
+ }
+ }
+
+ for (i = 0; i < LOG_MAX; i++) {
+ if (strcmp(acLogTags[i], tag) == 0) {
+ tagindex = i;
+ break;
+ }
+ }
+
+ if ((tagindex == -1) || (levelindex == -1))
+ return;
+
+ /*Clear the revelant bits*/
+ mask = 0x03;
+ *logLevels &= ~(mask << (tagindex * 2));
+ /*set debug level for the relevant bits*/
+ *logLevels |= (levelindex << (tagindex * 2));
+ KDEBUG(M4SH_INFO, "New M4 log levels = 0x%llx \n", *logLevels);
+}
+
+/* Usage: adb shell into the directory of sysinterface log_level and
+ echo LOG_ACCEL=LOG_DEGUB,LOG_POWER=LOG_ERROR > log_level */
+static ssize_t m4sensorhub_set_loglevel(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long long currentLogLevels;
+ char *tag, *level;
+ char **logbuf = (char **) &buf;
+
+ m4sensorhub_reg_read(m4sensorhub_misc_data,
+ M4SH_REG_LOG_LOGENABLE, (char *)&currentLogLevels);
+ while (1) {
+ tag = strsep(logbuf, "=,\n ");
+ if (tag == NULL)
+ break;
+ level = strsep(logbuf, "=,\n ");
+ if (level == NULL)
+ break;
+ ParseAndUpdateLogLevels(tag, level, &currentLogLevels);
+ }
+
+ return m4sensorhub_reg_write(m4sensorhub_misc_data,
+ M4SH_REG_LOG_LOGENABLE, (char *)&currentLogLevels,
+ m4sh_no_mask);
+}
+
+static DEVICE_ATTR(log_level, S_IRUGO|S_IWUGO, m4sensorhub_get_loglevel,
+ m4sensorhub_set_loglevel);
+
+static int m4sensorhub_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct m4sensorhub_data *m4sensorhub;
+ struct device_node *node = client->dev.of_node;
+ int err = -EINVAL;
+
+
+ /* Set debug based on module argument if set, otherwise use
+ default logging rate based on build type */
+ if (debug_level)
+ m4sensorhub_debug = debug_level;
+ else {
+#ifdef CONFIG_DEBUG_FS
+ /* engineering build */
+ m4sensorhub_debug = M4SH_INFO;
+#else
+ /* user/userdebug builds */
+ m4sensorhub_debug = M4SH_ERROR;
+#endif
+ }
+ KDEBUG(M4SH_ERROR, "Initializing M4 Sensor Hub: force_upgrade=%d "
+ "debug=%d\n", force_upgrade, m4sensorhub_debug);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ KDEBUG(M4SH_ERROR, "client not i2c capable\n");
+ err = -ENODEV;
+ goto err_unload;
+ }
+ m4sensorhub = kzalloc(sizeof(*m4sensorhub), GFP_KERNEL);
+ if (m4sensorhub == NULL) {
+ err = -ENOMEM;
+ KDEBUG(M4SH_ERROR,
+ "failed to allocate memory for module data: %d\n", err);
+ goto err_unload;
+ }
+ m4sensorhub_misc_data = m4sensorhub;
+
+ err = minnow_m4sensorhub_hw_init(m4sensorhub, node);
+ if (err)
+ printk(KERN_ERR "%s: hw_init Failed!", __func__);
+
+ /* link i2c_client to m4sensorhub */
+ i2c_set_clientdata(client, m4sensorhub);
+
+ /* link m4sensorhub to i2c_client */
+ m4sensorhub->i2c_client = client;
+
+ err = misc_register(&m4sensorhub_misc_device);
+ if (err < 0) {
+ KDEBUG(M4SH_ERROR, "misc register failed: %d\n", err);
+ goto err_hw_free;
+ }
+
+ err = device_create_file(&client->dev, &dev_attr_debug_level);
+ if (err < 0) {
+ KDEBUG(M4SH_ERROR, "Error creating debug_level file\n");
+ goto err_deregister;
+ }
+
+ err = device_create_file(&client->dev, &dev_attr_log_level);
+ if (err < 0) {
+ KDEBUG(M4SH_ERROR, "Error creating log_level file\n");
+ goto err_del_debug_file;
+ }
+
+ err = m4sensorhub_load_firmware(m4sensorhub, force_upgrade);
+ if (err < 0) {
+ dev_err(&client->dev, "load firmware file failed: %d\n", err);
+ goto err_del_log_file;
+ }
+
+ err = m4sensorhub_reg_init(m4sensorhub);
+ if (err < 0)
+ goto err_set_bootmode;
+
+ if (m4sensorhub->hwconfig.irq_gpio >= 0)
+ client->irq = gpio_to_irq(m4sensorhub->hwconfig.irq_gpio);
+ else {
+ KDEBUG(M4SH_ERROR, "Error: No IRQ configured\n");
+ err = -ENODEV;
+ goto err_reg_shutdown;
+ }
+
+ err = m4sensorhub_panic_init(m4sensorhub);
+ if (err < 0)
+ goto err_reg_shutdown;
+
+ err = m4sensorhub_irq_init(m4sensorhub);
+ if (err < 0)
+ goto err_panic_shutdown;
+
+
+ KDEBUG(M4SH_NOTICE, "Registered M4 Sensor Hub\n");
+
+ goto done;
+
+err_panic_shutdown:
+ m4sensorhub_panic_shutdown(m4sensorhub);
+err_reg_shutdown:
+ m4sensorhub_reg_shutdown(m4sensorhub);
+err_set_bootmode:
+ minnow_m4sensorhub_hw_reset(m4sensorhub);
+err_del_log_file:
+ device_remove_file(&client->dev, &dev_attr_log_level);
+err_del_debug_file:
+ device_remove_file(&client->dev, &dev_attr_debug_level);
+err_deregister:
+ misc_deregister(&m4sensorhub_misc_device);
+err_hw_free:
+ m4sensorhub->i2c_client = NULL;
+ i2c_set_clientdata(client, NULL);
+ minnow_m4sensorhub_hw_free(m4sensorhub);
+ kfree(m4sensorhub);
+ m4sensorhub = NULL;
+ m4sensorhub_misc_data = NULL;
+err_unload:
+done:
+ return err;
+}
+
+static int __exit m4sensorhub_remove(struct i2c_client *client)
+{
+ struct m4sensorhub_data *m4sensorhub = i2c_get_clientdata(client);
+ KDEBUG(M4SH_INFO, "Removing M4 Sensor Hub Driver\n");
+
+ m4sensorhub_irq_shutdown(m4sensorhub);
+ m4sensorhub_panic_shutdown(m4sensorhub);
+ m4sensorhub_reg_shutdown(m4sensorhub);
+ device_remove_file(&client->dev, &dev_attr_log_level);
+ device_remove_file(&client->dev, &dev_attr_debug_level);
+ minnow_m4sensorhub_hw_reset(m4sensorhub);
+ misc_deregister(&m4sensorhub_misc_device);
+ m4sensorhub->i2c_client = NULL;
+ i2c_set_clientdata(client, NULL);
+ minnow_m4sensorhub_hw_free(m4sensorhub);
+ kfree(m4sensorhub);
+ m4sensorhub = NULL;
+ m4sensorhub_misc_data = NULL;
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int m4sensorhub_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ int err = 0;
+ KDEBUG(M4SH_INFO, "%s\n", __func__);
+ m4sensorhub_irq_pm_dbg_suspend();
+ return err;
+}
+
+static int m4sensorhub_resume(struct i2c_client *client)
+{
+
+ int err = 0;
+ KDEBUG(M4SH_INFO, "%s\n", __func__);
+ m4sensorhub_irq_pm_dbg_resume();
+ return err;
+}
+#endif /* CONFIG_PM */
+static const struct of_device_id of_m4sensorhub_match[] = {
+ { .compatible = "mot,m4sensorhub", },
+ {},
+};
+
+static const struct i2c_device_id m4sensorhub_id[] = {
+ {M4SENSORHUB_DRIVER_NAME, 0},
+ { },
+};
+
+MODULE_DEVICE_TABLE(i2c, m4sensorhub_id);
+
+static struct i2c_driver m4sensorhub_driver = {
+ .driver = {
+ .name = M4SENSORHUB_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(of_m4sensorhub_match),
+ },
+ .probe = m4sensorhub_probe,
+ .remove = __exit_p(m4sensorhub_remove),
+#ifdef CONFIG_PM
+ .suspend = m4sensorhub_suspend,
+ .resume = m4sensorhub_resume,
+#endif /* CONFIG_PM */
+ .id_table = m4sensorhub_id,
+};
+
+static int __init m4sensorhub_init(void)
+{
+ return i2c_add_driver(&m4sensorhub_driver);
+}
+
+static void __exit m4sensorhub_exit(void)
+{
+ i2c_del_driver(&m4sensorhub_driver);
+ return;
+}
+
+module_init(m4sensorhub_init);
+module_exit(m4sensorhub_exit);
+
+MODULE_ALIAS("platform:m4sensorhub");
+MODULE_DESCRIPTION("M4 Sensor Hub driver");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/m4sensorhub-irq.c b/drivers/mfd/m4sensorhub-irq.c
new file mode 100644
index 00000000000..56b9504fecd
--- /dev/null
+++ b/drivers/mfd/m4sensorhub-irq.c
@@ -0,0 +1,675 @@
+/*
+ * Copyright (C) 2012 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/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/wakelock.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include <linux/m4sensorhub.h>
+
+#ifdef CONFIG_PM_DEEPSLEEP
+#include <linux/suspend.h>
+#endif
+
+#define NUM_INT_REGS 2
+#define NUM_INTS_PER_REG 8
+#define NUM_INTS_LAST_REG (((M4SH_IRQ__NUM-1)%NUM_INTS_PER_REG)+1)
+#define INTR_VALID_BITS(n) (unsigned char)((1 << (n)) - 1)
+
+#define EVENT_MASK(event) (1 << ((event) % NUM_INTS_PER_REG))
+
+#define DBG_BUF_LINE_LEN 80
+
+/* --------------- Global Declarations -------------- */
+
+/* ------------ Local Function Prototypes ----------- */
+static int m4sensorhub_irq_disable_all(struct m4sensorhub_data *m4sensorhub);
+static unsigned short get_enable_reg(enum m4sensorhub_irqs event);
+static void irq_work_func(struct work_struct *work);
+#ifdef CONFIG_DEBUG_FS
+static int m4sensorhub_dbg_irq_open(struct inode *inode, struct file *file);
+#endif
+static void m4sensorhub_irq_restore(struct m4sensorhub_data *m4sensorhub,\
+ void *data);
+
+/* ---------------- Local Declarations -------------- */
+
+static const char *irq_name[] = {
+ [M4SH_IRQ_TMP_DATA_READY] = "TMP_DATA_READY",
+ [M4SH_IRQ_PRESSURE_DATA_READY] = "PRES_DATA_READY",
+ [M4SH_IRQ_GYRO_DATA_READY] = "GYRO_DATA_READY",
+ [M4SH_IRQ_PEDOMETER_DATA_READY] = "PEDO_DATA_READY",
+ [M4SH_IRQ_COMPASS_DATA_READY] = "COMPASS_DATA_READY",
+ [M4SH_IRQ_FUSION_DATA_READY] = "FUSION_DATA_READY",
+ [M4SH_IRQ_ACCEL_DATA_READY] = "ACCEL_DATA_READY",
+ [M4SH_IRQ_GESTURE_DETECTED] = "GESTURE_DETECTED",
+ [M4SH_IRQ_STILL_DETECTED] = "STILL_DETECTED",
+ [M4SH_IRQ_MOTION_DETECTED] = "MOTION_DETECTED",
+ [M4SH_IRQ_ACTIVITY_CHANGE] = "ACTIVITY_CHANGE",
+ [M4SH_IRQ_DLCMD_RESP_READY] = "DLCMD_RESP_READY",
+ [M4SH_IRQ_MIC_DATA_READY] = "MIC_DATA_READY",
+ [M4SH_IRQ_WRIST_READY] = "WRIST_READY",
+ [M4SH_IRQ_PASSIVE_BUFFER_FULL] = "PASSIVE_BUFFER_FULL",
+};
+
+/* -------------- Local Data Structures ------------- */
+
+struct m4sensorhub_event_handler {
+ void (*func)(enum m4sensorhub_irqs, void *);
+ void *data;
+};
+
+struct m4sensorhub_irq_info {
+ uint8_t registered;
+ uint8_t enabled;
+ uint32_t ena_fired;
+ uint32_t disa_fired;
+};
+
+struct m4sensorhub_irqdata {
+ struct mutex lock; /* lock event handlers and data */
+ struct work_struct work;
+ struct workqueue_struct *workqueue;
+ struct m4sensorhub_data *m4sensorhub;
+ struct m4sensorhub_event_handler event_handler[M4SH_IRQ__NUM];
+ struct m4sensorhub_irq_info irq_info[M4SH_IRQ__NUM];
+ struct wake_lock wake_lock;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs;
+#endif
+};
+
+
+static const struct {
+ enum m4sensorhub_reg status_reg;
+ enum m4sensorhub_reg enable_reg;
+ unsigned char valid_bits;
+} int_registers[NUM_INT_REGS] = {
+ {M4SH_REG_GENERAL_INTERRUPT0STATUS,
+ M4SH_REG_GENERAL_INTERRUPT0ENABLE,
+ INTR_VALID_BITS(NUM_INTS_PER_REG)},
+ {M4SH_REG_GENERAL_INTERRUPT1STATUS,
+ M4SH_REG_GENERAL_INTERRUPT1ENABLE,
+ INTR_VALID_BITS(NUM_INTS_LAST_REG)},
+};
+
+static irqreturn_t event_isr(int irq, void *data)
+{
+ /* Interrupts are left enabled; if multiple interrupts arrive, there
+ * will be multiple jobs in the workqueue. In this case, the first
+ * job in the workqueue may service multple interrupts and
+ * susbsequent jobs will have no interrupts left to service.
+ */
+ struct m4sensorhub_irqdata *irq_data = data;
+ wake_lock(&irq_data->wake_lock);
+ queue_work(irq_data->workqueue, &irq_data->work);
+
+ return IRQ_HANDLED;
+}
+
+static struct mrsensorhub_irq_dbg {
+ unsigned short en_ints[NUM_INT_REGS];
+ unsigned char suspend;
+ unsigned char wakeup;
+} irq_dbg_info;
+
+#ifdef CONFIG_DEBUG_FS
+static const struct file_operations debug_fops = {
+ .open = m4sensorhub_dbg_irq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+#endif
+
+/* -------------- Global Functions ----------------- */
+
+/* m4sensorhub_irq_init()
+
+ Intialize M4 sensor hub IRQ subsystem
+
+ Returns 0 on success. Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+*/
+
+int m4sensorhub_irq_init(struct m4sensorhub_data *m4sensorhub)
+{
+ int retval;
+ struct i2c_client *i2c = m4sensorhub->i2c_client;
+ struct m4sensorhub_irqdata *data;
+
+ data = kzalloc(sizeof(struct m4sensorhub_irqdata), GFP_KERNEL);
+ if (!data) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: Memory error in irq_init\n");
+ retval = -ENOMEM;
+ goto done;
+ }
+
+ KDEBUG(M4SH_INFO, "m4sensorhub: %u IRQs with valid_bits %02X%02X\n",\
+ M4SH_IRQ__NUM, int_registers[1].valid_bits,\
+ int_registers[0].valid_bits);
+ retval = m4sensorhub_irq_disable_all(m4sensorhub);
+ if (retval) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: Failed disable all irqs\n");
+ goto err_free;
+ }
+
+ data->workqueue = create_workqueue("m4sensorhub_irq");
+ if (data->workqueue == NULL) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: IRQ Workqueue init failure\n");
+ retval = -ENOMEM;
+ goto err_free;
+ }
+ INIT_WORK(&data->work, irq_work_func);
+
+ mutex_init(&data->lock);
+
+ wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "m4sensorhub-irq");
+
+ retval = request_irq(i2c->irq, event_isr, IRQF_DISABLED |
+ IRQF_TRIGGER_RISING, "m4sensorhub-irq", data);
+ if (retval) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: Failed requesting irq.\n");
+ goto err_destroy_wq;
+ }
+
+ data->m4sensorhub = m4sensorhub;
+ m4sensorhub->irqdata = data;
+
+ retval = enable_irq_wake(i2c->irq);
+ if (retval) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: Failed enabling irq wake.\n");
+ goto err_free_irq;
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ data->debugfs = debugfs_create_file("m4sensorhub-irq", S_IRUGO, NULL,
+ data, &debug_fops);
+ if (data->debugfs == NULL) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: Error creating debufs\n");
+ retval = -EINVAL;
+ goto err_disa_irq;
+ }
+#endif
+ m4sensorhub_panic_register(m4sensorhub, PANICHDL_IRQ_RESTORE,\
+ m4sensorhub_irq_restore, data);
+ KDEBUG(M4SH_INFO, "m4sensorhub IRQ subsystem initialized\n");
+ retval = 0;
+ goto done;
+
+err_disa_irq:
+ disable_irq_wake(i2c->irq);
+err_free_irq:
+ free_irq(i2c->irq, data);
+ m4sensorhub->irqdata = NULL;
+ data->m4sensorhub = NULL;
+err_destroy_wq:
+ wake_lock_destroy(&data->wake_lock);
+ mutex_destroy(&data->lock);
+ destroy_workqueue(data->workqueue);
+err_free:
+ kfree(data);
+done:
+ return retval;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_init);
+
+/* m4sensorhub_irq_shutdown()
+
+ Shutdown the M4 sensor hub IRQ subsystem
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+*/
+void m4sensorhub_irq_shutdown(struct m4sensorhub_data *m4sensorhub)
+{
+ struct i2c_client *i2c = m4sensorhub->i2c_client;
+ struct m4sensorhub_irqdata *data = m4sensorhub->irqdata;
+
+ KDEBUG(M4SH_INFO, "shutdown m4sensorhub IRQ subsystem\n");
+
+ m4sensorhub_panic_unregister(m4sensorhub, PANICHDL_IRQ_RESTORE);
+
+#ifdef CONFIG_DEBUG_FS
+ debugfs_remove(data->debugfs);
+#endif
+
+ disable_irq_wake(i2c->irq);
+ free_irq(i2c->irq, data);
+
+ m4sensorhub->irqdata = NULL;
+ data->m4sensorhub = NULL;
+
+ if (wake_lock_active(&data->wake_lock))
+ wake_unlock(&data->wake_lock);
+ wake_lock_destroy(&data->wake_lock);
+
+ if (mutex_is_locked(&data->lock))
+ mutex_unlock(&data->lock);
+ mutex_destroy(&data->lock);
+
+ cancel_work_sync(&data->work);
+ destroy_workqueue(data->workqueue);
+
+ kfree(data);
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_shutdown);
+
+/* m4sensorhub_irq_register()
+
+ Register an interupt handler in the M4 Sensor Hub IRQ subsystem.
+ This does not enable the IRQ, that needs to be done by caller
+ with m4sensorhub_irq_enable()
+
+ Returns 0 on success. Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ irq - M4 Sensor Hub interupt to resiter for
+ cb_func - IRQ handler function to execute on inturrupt
+ data - pointer to data for IRQ handler function
+*/
+
+int m4sensorhub_irq_register(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_irqs irq,
+ void (*cb_func) (enum m4sensorhub_irqs, void *),
+ void *data)
+{
+ struct m4sensorhub_irqdata *irqdata;
+ int retval = 0;
+
+ if ((!m4sensorhub) || (irq >= M4SH_IRQ__NUM) || (!cb_func))
+ return -EINVAL;
+
+ irqdata = m4sensorhub->irqdata;
+
+ mutex_lock(&irqdata->lock);
+
+ if (irqdata->event_handler[irq].func == NULL) {
+ irqdata->irq_info[irq].registered = 1;
+ irqdata->event_handler[irq].func = cb_func;
+ irqdata->event_handler[irq].data = data;
+ KDEBUG(M4SH_NOTICE, "m4sensorhub: %s IRQ registered\n",
+ irq_name[irq]);
+ } else {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: %s IRQ registration failed\n",
+ irq_name[irq]);
+ retval = -EPERM;
+ }
+
+ mutex_unlock(&irqdata->lock);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_register);
+
+/* m4sensorhub_irq_unregister()
+
+ Unregister an interupt handler in the M4 Sensor Hub IRQ subsystem
+
+ Returns 0 on success. Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ irq - M4 Sensor Hub interupt to unresiter for
+*/
+int m4sensorhub_irq_unregister(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_irqs irq)
+{
+ struct m4sensorhub_irqdata *data = m4sensorhub->irqdata;
+ int retval;
+
+ if (irq >= M4SH_IRQ__NUM)
+ return -EINVAL;
+
+ retval = m4sensorhub_irq_disable(m4sensorhub, irq);
+
+ mutex_lock(&data->lock);
+ data->event_handler[irq].func = NULL;
+ data->event_handler[irq].data = NULL;
+ data->irq_info[irq].registered = 0;
+ mutex_unlock(&data->lock);
+
+ KDEBUG(M4SH_NOTICE, "m4sensorhub: %s IRQ un-registered\n",
+ irq_name[irq]);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_unregister);
+
+/* m4sensorhub_irq_enable_get()
+
+ Check if an IRQ is enabled
+
+ Returns 1 if enabled, 0 if disabled.
+ Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ irq - M4 Sensor Hub interupt to check
+*/
+
+int m4sensorhub_irq_enable_get(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_irqs irq)
+{
+ struct m4sensorhub_irqdata *data = m4sensorhub->irqdata;
+ int retval = -EINVAL;
+
+ if (irq < M4SH_IRQ__NUM)
+ return data->irq_info[irq].enabled;
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_enable_get);
+
+/* m4sensorhub_irq_disable()
+
+ Disable M4 Sensor Hub subsystem IRQ
+
+ Returns 0 on success. Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ irq - M4 Sensor Hub interupt to disable
+*/
+
+int m4sensorhub_irq_disable(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_irqs irq)
+{
+ struct m4sensorhub_irqdata *data = m4sensorhub->irqdata;
+ int retval = -EINVAL;
+
+ if (irq < M4SH_IRQ__NUM) {
+ mutex_lock(&data->lock);
+ data->irq_info[irq].enabled = 0;
+ mutex_unlock(&data->lock);
+ retval = m4sensorhub_reg_write_1byte(m4sensorhub,
+ get_enable_reg(irq), 0, EVENT_MASK(irq));
+ retval = CHECK_REG_ACCESS_RETVAL(m4sensorhub, retval,
+ get_enable_reg(irq));
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_disable);
+
+/* m4sensorhub_irq_enable()
+
+ Enable M4 Sensor Hub subsystem IRQ
+
+ Returns 0 on success. Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ irq - M4 Sensor Hub interupt to enable
+*/
+
+int m4sensorhub_irq_enable(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_irqs irq)
+{
+ struct m4sensorhub_irqdata *data = m4sensorhub->irqdata;
+ int retval = -EINVAL;
+
+ if (irq < M4SH_IRQ__NUM) {
+ mutex_lock(&data->lock);
+ data->irq_info[irq].enabled = 1;
+ mutex_unlock(&data->lock);
+ retval = m4sensorhub_reg_write_1byte(m4sensorhub,
+ get_enable_reg(irq), EVENT_MASK(irq),
+ EVENT_MASK(irq));
+ retval = CHECK_REG_ACCESS_RETVAL(m4sensorhub, retval,
+ get_enable_reg(irq));
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_enable);
+
+/* m4sensorhub_irq_pm_suspend()
+
+ Called by core to track suspend state and wakeup cause
+
+*/
+
+void m4sensorhub_irq_pm_dbg_suspend(void)
+{
+ irq_dbg_info.suspend = 1;
+ irq_dbg_info.wakeup = 0;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_pm_dbg_suspend);
+
+/* m4sensorhub_irq_pm_resume()
+
+ Called by core to print interupt source on M4SH wakeup
+
+*/
+
+void m4sensorhub_irq_pm_dbg_resume(void)
+{
+ char buffer[DBG_BUF_LINE_LEN];
+ int i;
+
+ irq_dbg_info.suspend = 0;
+ if ((irq_dbg_info.wakeup != 0) && (m4sensorhub_debug >= M4SH_NOTICE)) {
+ strcpy(buffer, "M4 Sensor Hub IRQ registers:");
+ for (i = 0; (i < NUM_INT_REGS) &&
+ (strlen(buffer) < DBG_BUF_LINE_LEN-5); ++i) {
+ sprintf(&buffer[strlen(buffer)], " 0x%02x",
+ irq_dbg_info.en_ints[i]);
+ }
+
+ KDEBUG(M4SH_NOTICE, "newbuf: %s\n", buffer);
+
+ /* Decode the bits */
+ KDEBUG(M4SH_NOTICE, "M4 Sensor Hub IRQ sources:\n");
+ for (i = 0; i < NUM_INT_REGS; ++i) {
+ unsigned char index;
+
+ while (irq_dbg_info.en_ints[i] > 0) {
+ /* find the first set bit */
+ index = (unsigned char)
+ (ffs(irq_dbg_info.en_ints[i]) - 1);
+ if (index >= M4SH_IRQ__NUM)
+ goto error;
+
+ /* clear the bit */
+ irq_dbg_info.en_ints[i] &= ~(1 << index);
+ /* find the event that occurred */
+ index += M4SH_IRQ__START +
+ (i * NUM_INTS_PER_REG);
+ if (index >= M4SH_IRQ__NUM)
+ goto error;
+
+ KDEBUG(M4SH_NOTICE, "\t%s\n", irq_name[index]);
+ }
+ }
+ }
+error:
+ return;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_irq_pm_dbg_resume);
+
+/* --------------- Local Functions ----------------- */
+
+static unsigned short get_enable_reg(enum m4sensorhub_irqs event)
+{
+ unsigned short ret;
+
+ if ((event) >= M4SH_IRQ__NUM)
+ ret = M4SH_REG__INVALID;
+ else if ((event) >= M4SH_IRQ_INT1_INDEX)
+ ret = M4SH_REG_GENERAL_INTERRUPT1ENABLE;
+ else if ((event) >= M4SH_IRQ_INT0_INDEX)
+ ret = M4SH_REG_GENERAL_INTERRUPT0ENABLE;
+ else
+ ret = M4SH_REG__INVALID;
+
+ return ret;
+}
+
+static int m4sensorhub_irq_disable_all(struct m4sensorhub_data *m4sensorhub)
+{
+ int i;
+
+ for (i = 0; i < NUM_INT_REGS; i++) {
+ if (1 != m4sensorhub_reg_write_1byte(m4sensorhub,
+ int_registers[i].enable_reg, 0,
+ int_registers[i].valid_bits)) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub_irq: "
+ "Failed disabling INT%d\n", i);
+ return -EFAULT;
+ }
+ }
+ return 0;
+}
+
+static void irq_work_func(struct work_struct *work)
+{
+ unsigned short en_ints[NUM_INT_REGS] = { 0 };
+ int i;
+ struct m4sensorhub_irqdata *data;
+ struct m4sensorhub_data *m4sensorhub;
+ struct i2c_client *i2c;
+ unsigned char value, is_irq_set = 0;
+
+ data = container_of(work, struct m4sensorhub_irqdata, work);
+ m4sensorhub = data->m4sensorhub;
+ i2c = m4sensorhub->i2c_client;
+
+ for (i = 0; i < NUM_INT_REGS; ++i) {
+ /* M4 is expected to clear these bits when read */
+ if (1 != m4sensorhub_reg_read(m4sensorhub,
+ int_registers[i].status_reg, &value)) {
+ dev_err(&m4sensorhub->i2c_client->dev,
+ "Error reading INT%d\n", i);
+ goto error;
+ }
+ en_ints[i] = value;
+ is_irq_set |= value;
+ }
+
+ if (!is_irq_set) {
+ /* Got the checkpoint to check if M4 panicked */
+ m4sensorhub_panic_process(m4sensorhub);
+ goto error;
+ }
+
+ if ((irq_dbg_info.suspend != 0) && (irq_dbg_info.wakeup == 0)) {
+ for (i = 0; i < NUM_INT_REGS; ++i)
+ irq_dbg_info.en_ints[i] = en_ints[i];
+ irq_dbg_info.wakeup = 1;
+ }
+
+ for (i = 0; i < NUM_INT_REGS; ++i) {
+ unsigned char index;
+
+ while (en_ints[i] > 0) {
+ struct m4sensorhub_event_handler *event_handler;
+
+ /* find the first set bit */
+ index = (unsigned char)(ffs(en_ints[i]) - 1);
+ if (index >= M4SH_IRQ__NUM)
+ goto error;
+ /* clear the bit */
+ en_ints[i] &= ~(1 << index);
+ /* find the event that occurred */
+ index += M4SH_IRQ__START + (i * NUM_INTS_PER_REG);
+ if (index >= M4SH_IRQ__NUM)
+ goto error;
+
+ if (data->irq_info[index].enabled) {
+ event_handler = &data->event_handler[index];
+
+ if (event_handler && event_handler->func)
+ event_handler->func(index,
+ event_handler->data);
+
+ mutex_lock(&data->lock);
+ data->irq_info[index].ena_fired++;
+ mutex_unlock(&data->lock);
+ } else {
+ mutex_lock(&data->lock);
+ data->irq_info[index].disa_fired++;
+ mutex_unlock(&data->lock);
+ }
+ }
+ }
+error:
+ wake_unlock(&data->wake_lock);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int m4sensorhub_dbg_irq_show(struct seq_file *s, void *data)
+{
+ unsigned int i;
+ struct m4sensorhub_irqdata *irqdata = s->private;
+
+ seq_printf(s, "%21s%9s%12s%15s%16s\n",
+ "M4SENSORHUB IRQ", "Enabled", "Registered",
+ "Fired Enabled", "Fired Disabled");
+
+ for (i = 0; i < M4SH_IRQ__NUM; i++) {
+ seq_printf(s, "%21s%9d%12d%15d%16d\n",
+ irq_name[i],
+ irqdata->irq_info[i].enabled,
+ irqdata->irq_info[i].registered,
+ irqdata->irq_info[i].ena_fired,
+ irqdata->irq_info[i].disa_fired);
+ }
+ return 0;
+}
+
+static int m4sensorhub_dbg_irq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, m4sensorhub_dbg_irq_show, inode->i_private);
+}
+#endif
+
+/* m4sensorhub_irq_restore()
+
+ Callback Handler is called by Panic after M4 has been restarted
+
+*/
+static void m4sensorhub_irq_restore(\
+ struct m4sensorhub_data *m4sensorhub, void *data)
+{
+ int i;
+ unsigned short en_ints[NUM_INT_REGS] = {0};
+
+ mutex_lock(&((struct m4sensorhub_irqdata *)data)->lock);
+ for (i = 0; i < M4SH_IRQ__NUM; i++) {
+ if (!((struct m4sensorhub_irqdata *)data)->irq_info[i].enabled)
+ continue;
+ en_ints[i/NUM_INTS_PER_REG] |= EVENT_MASK(i);
+ }
+ mutex_unlock(&((struct m4sensorhub_irqdata *)data)->lock);
+
+ for (i = 0; i < NUM_INT_REGS; i++) {
+ KDEBUG(M4SH_INFO, "m4sensorhub_irq: Reseting INT%d-%02X\n",\
+ i, en_ints[i]);
+ if (1 != m4sensorhub_reg_write_1byte(m4sensorhub,
+ int_registers[i].enable_reg, en_ints[i],
+ int_registers[i].valid_bits)) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub_irq: "
+ "Failed reseting INT%d\n", i);
+ }
+ }
+}
diff --git a/drivers/mfd/m4sensorhub-panic.c b/drivers/mfd/m4sensorhub-panic.c
new file mode 100644
index 00000000000..aaf35f89886
--- /dev/null
+++ b/drivers/mfd/m4sensorhub-panic.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2012 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/module.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/m4sensorhub.h>
+#include <linux/slab.h>
+
+
+
+/* --------------- Global Declarations -------------- */
+#define PANIC_BANK 0xFF /* Reserved for Panic bank */
+#define PANIC_CMD_CHECK 0xCD /* Panic Handoff command */
+#define PANIC_RESP_CHECK 0xDeadBeef /* Panic Handoff Magic code */
+
+/* ------------ Local Function Prototypes ----------- */
+
+/* --------------- Local Declarations -------------- */
+static const char *callback_name[PANICHDL_MAX] = {
+ [PANICHDL_IRQ_RESTORE] = "irq_restore",
+};
+
+struct m4sensorhub_panic_callback {
+ void (*callback)(struct m4sensorhub_data *, void *);
+ void *data;
+};
+
+struct m4sensorhub_panicdata {
+ struct mutex lock; /* lock callback and data */
+ struct m4sensorhub_panic_callback funcs[PANICHDL_MAX];
+};
+
+union panic_buf {
+ struct _in {
+ unsigned char bank;
+ unsigned char cmd;
+ } in;
+ unsigned int data;
+};
+
+/* -------------- Local Data Structures ------------- */
+
+/* -------------- Global Functions ----------------- */
+
+/* m4sensorhub_panic_init()
+
+ Initialized panic private data structures.
+
+ Returns 0 on success or negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+*/
+int m4sensorhub_panic_init(struct m4sensorhub_data *m4sensorhub)
+{
+ int retval = 0;
+ struct m4sensorhub_panicdata *data;
+
+ data = kzalloc(sizeof(struct m4sensorhub_panicdata), GFP_KERNEL);
+ if (data) {
+ mutex_init(&data->lock);
+ m4sensorhub->panicdata = data;
+ } else {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: Memory error in panic_init\n");
+ retval = -ENOMEM;
+ }
+ return retval;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_panic_init);
+
+/* m4sensorhub_panic_shutdown()
+
+ Shutdown the M4 sensor hub Panic subsystem
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+*/
+void m4sensorhub_panic_shutdown(struct m4sensorhub_data *m4sensorhub)
+{
+ if (m4sensorhub && m4sensorhub->panicdata) {
+ struct m4sensorhub_panicdata *data = m4sensorhub->panicdata;
+ m4sensorhub->panicdata = NULL;
+ if (mutex_is_locked(&data->lock))
+ mutex_unlock(&data->lock);
+ mutex_destroy(&data->lock);
+ kfree(data);
+ }
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_panic_shutdown);
+
+/* m4sensorhub_panic_register()
+
+ Register an panic handler to monitor M4 panic reset
+
+ Returns 0 on success or negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ index - M4 Sensor Hub panic handler to resiter for
+ cb_func - panic handler function to execute after M4 reset
+ data - pointer to data for panic handler function
+*/
+
+int m4sensorhub_panic_register(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_panichdl_index index,
+ void (*cb_func) (struct m4sensorhub_data *, void *),
+ void *data)
+{
+ struct m4sensorhub_panicdata *panicdata;
+ int retval = 0;
+
+ if (!m4sensorhub || (index >= PANICHDL_MAX) || !cb_func)
+ return -EINVAL;
+
+ panicdata = (struct m4sensorhub_panicdata *)m4sensorhub->panicdata;
+ mutex_lock(&panicdata->lock);
+ if (panicdata->funcs[index].callback == NULL) {
+ panicdata->funcs[index].callback = cb_func;
+ panicdata->funcs[index].data = data;
+ KDEBUG(M4SH_NOTICE, "m4sensorhub: %s callback registered\n",
+ callback_name[index]);
+ } else {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: %s callback"\
+ " registration failed\n", callback_name[index]);
+ retval = -EPERM;
+ }
+ mutex_unlock(&panicdata->lock);
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_panic_register);
+
+/* m4sensorhub_panic_unregister()
+
+ Unregister an panic handler to monitor M4 panic reset
+
+ Returns 0 on success or negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ index - M4 Sensor Hub panic handler to unresiter for
+*/
+int m4sensorhub_panic_unregister(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_panichdl_index index)
+{
+ struct m4sensorhub_panicdata *panicdata;
+
+ if (!m4sensorhub || (index >= PANICHDL_MAX))
+ return -EINVAL;
+
+ panicdata = (struct m4sensorhub_panicdata *)m4sensorhub->panicdata;
+ mutex_lock(&panicdata->lock);
+ panicdata->funcs[index].callback = NULL;
+ panicdata->funcs[index].data = NULL;
+ mutex_unlock(&panicdata->lock);
+ KDEBUG(M4SH_NOTICE, "m4sensorhub: %s callback un-registered\n",
+ callback_name[index]);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_panic_unregister);
+
+
+/* m4sensorhub_panic_process()
+
+ Check M4 if it's panicked, use I2C to communicate with M4 panic handler
+ OMAP use the same i2c sequences to send command via i2c master, then M4
+ i2c slave program will handle these commands, it may have 2 slave programs
+ 1. Normal i2c slave program handles all vaild banks'(limit on
+ M4SH_TYPE__NUM) command, for invalid bank, it always responses 0xFF
+ 2. Panic i2c slave program handles panic bank(reserved 0xFF for it) command,
+ for others, it always responses 0x00
+
+ To detect whether M4 is panicked, the process should be
+ i. When OMAP got interrupt from M4, OMAP will check which irq is raised, it
+ send normal banks' command to M4, for panic case, it always returns 0x00,
+ so OMAP has a checkpoint as there's interrupt request from M4 without
+ active IRQ
+ ii.Then OMAP will confirm if M4 is panic via send panic bank command, if M4
+ is panicked, it will handle this bank and response panic magic code;
+ Otherwise, if no panic magic code returned from M4, it always means M4
+ isn't panicked.
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ */
+void m4sensorhub_panic_process(struct m4sensorhub_data *m4sensorhub)
+{
+ int i, ret;
+ union panic_buf buf;
+ struct m4sensorhub_panic_callback handler;
+
+ if (!m4sensorhub || !m4sensorhub->panicdata) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub: Invalid parameter in %s!\n",\
+ __func__);
+ return;
+ }
+
+ m4sensorhub_reg_access_lock();
+
+ buf.in.bank = PANIC_BANK;
+ buf.in.cmd = PANIC_CMD_CHECK;
+ ret = m4sensorhub_i2c_write_read(m4sensorhub,\
+ (u8 *)&buf, sizeof(buf.in), sizeof(buf.data));
+ if ((ret != sizeof(buf.data)) || (buf.data != PANIC_RESP_CHECK)) {
+ /* TODO maybe we shall check if M4/OMAP i2c broken */
+ KDEBUG(M4SH_ERROR, "m4sensorhub: Unknown IRQ status! "\
+ "M4 panic handoff ret=%d, data=0x%x\n",\
+ ret, buf.data);
+ m4sensorhub_reg_access_unlock();
+ return;
+ }
+
+ KDEBUG(M4SH_ERROR, "m4sensorhub_panic: Detected M4 panic, reset M4!\n");
+ m4sensorhub->pdev->hw_reset(m4sensorhub);
+ msleep(100);
+ ret = m4sensorhub_load_firmware(m4sensorhub, 0);
+ if (ret < 0) {
+ KDEBUG(M4SH_ERROR, "m4sensorhub_panic: "\
+ "Failed to restart M4, ret = %d\n", ret);
+ BUG();
+ }
+
+ m4sensorhub_reg_access_unlock();
+
+ for (i = 0; i < PANICHDL_MAX; i++) {
+ handler = ((struct m4sensorhub_panicdata *)\
+ (m4sensorhub->panicdata))->funcs[i];
+ if (handler.callback) {
+ KDEBUG(M4SH_NOTICE, "m4sensorhub_panic: "\
+ "Calling %s as M4 restarted!\n",\
+ callback_name[i]);
+ handler.callback(m4sensorhub, handler.data);
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_panic_process);
diff --git a/drivers/mfd/m4sensorhub-reg.c b/drivers/mfd/m4sensorhub-reg.c
new file mode 100644
index 00000000000..cb9e8b1b371
--- /dev/null
+++ b/drivers/mfd/m4sensorhub-reg.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2012 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/module.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/m4sensorhub.h>
+#include <linux/slab.h>
+
+#include "m4sensorhub-reg.h" /* auto-generated header defining registers */
+
+#define I2C_RETRY_DELAY 5
+#define I2C_RETRIES 5
+
+#define DEBUG_LINE_LENGTH 80
+
+/* --------------- Global Declarations -------------- */
+
+/* ------------ Local Function Prototypes ----------- */
+static int m4sensorhub_mapsize(enum m4sensorhub_reg reg);
+
+/* --------------- Local Declarations -------------- */
+static DEFINE_MUTEX(reg_access);
+
+/* -------------- Local Data Structures ------------- */
+
+/* -------------- Global Functions ----------------- */
+
+/* m4sensorhub_reg_init()
+
+ Initialized register access data structures.
+
+ Returns 0 on success or negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+*/
+
+int m4sensorhub_reg_init(struct m4sensorhub_data *m4sensorhub)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_reg_init);
+
+/* m4sensorhub_reg_shutdown()
+
+ Clean up register subsystem on driver removal
+
+ Returns 0 on success or negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+*/
+
+int m4sensorhub_reg_shutdown(struct m4sensorhub_data *m4sensorhub)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_reg_shutdown);
+
+/* m4sensorhub_reg_read_n()
+
+ Read a n bytes from the M4 sensor hub starting at 'register'. Use
+ m4sensorhub_reg_read() instead where possible;
+
+ Returns number of bytes read on success.
+ Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ reg - Register to be read
+ value - array to return data. Needs to be at least register's size
+ num - number of bytes to read
+*/
+
+int m4sensorhub_reg_read_n(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_reg reg, unsigned char *value,
+ short num)
+{
+ int ret = -EINVAL;
+ u8 stack_buf[M4SH_MAX_STACK_BUF_SIZE];
+
+ if (!m4sensorhub || !value || !num) {
+ KDEBUG(M4SH_ERROR, "%s() invalid parameter\n", __func__);
+ return ret;
+ }
+
+ if ((reg < M4SH_REG__NUM) && num <= M4SH_MAX_REG_SIZE &&\
+ register_info_tbl[reg].offset + num <=
+ m4sensorhub_mapsize(reg)) {
+ u8 *buf = (num > (M4SH_MAX_STACK_BUF_SIZE-2))\
+ ? kmalloc(num+2, GFP_KERNEL) : stack_buf;
+ if (!buf) {
+ KDEBUG(M4SH_ERROR, "%s() Failed alloc %d memeory\n"\
+ , __func__, num+2);
+ return -ENOMEM;
+ }
+ buf[0] = register_info_tbl[reg].type;
+ buf[1] = register_info_tbl[reg].offset;
+
+ mutex_lock(&reg_access);
+ ret = m4sensorhub_i2c_write_read(m4sensorhub, buf, 2, num);
+ mutex_unlock(&reg_access);
+
+ if (ret != num)
+ KDEBUG(M4SH_ERROR, "%s() read failure\n", __func__);
+ else
+ memcpy(value, buf, num);
+ if (buf != stack_buf)
+ kfree(buf);
+ } else {
+ KDEBUG(M4SH_ERROR, "%s() invalid register access reg=%d "
+ "maxreg=%d size=%d maxsze=%d mapsize=%d\n", __func__,
+ reg, M4SH_REG__NUM, num, M4SH_MAX_REG_SIZE,
+ m4sensorhub_mapsize(reg));
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_reg_read_n);
+
+/* m4sensorhub_reg_write()
+
+ Write data to a register in the M4 sensor hub. Use
+ m4sensorhub_reg_write() instead where possible;
+
+ Returns number of bytes written on success.
+ Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ reg - Register to be written to
+ value - array of data to write. Needs to be at least register's size
+ mask - mask representing which bits to change in register. If all bits
+ are to be changed, then &m4sh_no_mask can be passed here.
+ num - number of bytes to write
+*/
+
+int m4sensorhub_reg_write_n(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_reg reg, unsigned char *value,
+ unsigned char *mask, short num)
+{
+ int i, ret = -EINVAL;
+ u8 stack_buf[M4SH_MAX_STACK_BUF_SIZE];
+
+ if (!m4sensorhub || !value || !num) {
+ KDEBUG(M4SH_ERROR, "%s() invalid parameter\n", __func__);
+ return ret;
+ }
+
+ if ((reg < M4SH_REG__NUM) && num <= M4SH_MAX_REG_SIZE &&\
+ register_info_tbl[reg].offset + num <=
+ m4sensorhub_mapsize(reg)) {
+ u8 *buf = (num > (M4SH_MAX_STACK_BUF_SIZE-2))\
+ ? kmalloc(num+2, GFP_KERNEL) : stack_buf;
+ if (!buf) {
+ KDEBUG(M4SH_ERROR, "%s() Failed alloc %d memeory\n"\
+ , __func__, num+2);
+ return -ENOMEM;
+ }
+
+ buf[0] = register_info_tbl[reg].type;
+ buf[1] = register_info_tbl[reg].offset;
+
+ mutex_lock(&reg_access);
+ if (mask) {
+ ret = m4sensorhub_i2c_write_read(m4sensorhub, buf,
+ 2, num);
+ if (ret != num) {
+ KDEBUG(M4SH_ERROR, "%s() register read"
+ "failure\n", __func__);
+ goto error;
+ }
+ /* move data right 2 positions and apply mask and
+ new data to prepare for writeback */
+ for (i = num-1; i >= 0; i--) {
+ buf[i+2] = (buf[i] & ~mask[i]) |
+ (value[i] & mask[i]);
+ }
+ buf[0] = register_info_tbl[reg].type;
+ buf[1] = register_info_tbl[reg].offset;
+ } else
+ memcpy(&buf[2], value, num);
+
+ ret = m4sensorhub_i2c_write_read(m4sensorhub, buf,
+ num + 2, 0);
+ if (ret != num + 2) {
+ KDEBUG(M4SH_ERROR, "%s() register write failure\n",
+ __func__);
+ ret = -EINVAL;
+ } else
+ ret -= 2;
+
+error: mutex_unlock(&reg_access);
+ if (buf != stack_buf)
+ kfree(buf);
+ } else {
+ KDEBUG(M4SH_ERROR, "%s() invalid register access reg=%d"
+ " maxreg=%d num=%d maxsze=%d mapsize=%d\n", __func__,
+ reg, M4SH_REG__NUM, num, M4SH_MAX_REG_SIZE,
+ m4sensorhub_mapsize(reg));
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_reg_write_n);
+
+/* m4sensorhub_reg_write_1byte()
+
+ Write data to a 1 byte register in the M4 sensor hub. Avoids need to pass
+ data and mask by reference
+
+ Returns number of bytes written on success.
+ Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ reg - Register to be written to
+ value - byte of data to write
+ mask - mask representing which bits to change in register.
+*/
+
+int m4sensorhub_reg_write_1byte(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_reg reg, unsigned char value,
+ unsigned char mask)
+{
+ if (register_info_tbl[reg].size == 1)
+ return m4sensorhub_reg_write(m4sensorhub, reg, &value, &mask);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_reg_write_1byte);
+
+/* m4sensorhub_reg_getsize()
+
+ Get the size of an M4 register
+
+ Returns size of register on success
+ Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ reg - Register to get size of
+*/
+
+int m4sensorhub_reg_getsize(struct m4sensorhub_data *m4sensorhub,
+ enum m4sensorhub_reg reg)
+{
+ if (reg < M4SH_REG__NUM)
+ return register_info_tbl[reg].size;
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_reg_getsize);
+
+/* m4sensorhub_reg_access_lock()
+
+ Lock reg access to avoid broken I2C transmit process
+
+*/
+
+void m4sensorhub_reg_access_lock(void)
+{
+ mutex_lock(&reg_access);
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_reg_access_lock);
+
+/* m4sensorhub_reg_access_unlock()
+
+ Unlock reg access to wake up blocked I2C transmit process
+
+*/
+
+void m4sensorhub_reg_access_unlock(void)
+{
+ mutex_unlock(&reg_access);
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_reg_access_unlock);
+
+/* m4sensorhub_i2c_write_read()
+
+ Directly I2C access to communicate with the M4 sensor hub.
+ It always read after write if both write and read length are non-zero
+
+ Returns number of bytes write on success if readlen is zero
+ Returns number of bytes read on success if readlen is non-zero
+ Returns negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+ buf - buffer to be used for write to and read from
+ writelen - number of bytes write to
+ readlen - number of bytes read from
+*/
+
+int m4sensorhub_i2c_write_read(struct m4sensorhub_data *m4sensorhub,
+ u8 *buf, int writelen, int readlen)
+{
+ int i, msglen, msgstart, err, tries = 0;
+ char buffer[DEBUG_LINE_LENGTH];
+ struct i2c_msg msgs[] = {
+ {
+ .addr = m4sensorhub->i2c_client->addr,
+ .flags = m4sensorhub->i2c_client->flags,
+ .len = writelen,
+ .buf = buf,
+ },
+ {
+ .addr = m4sensorhub->i2c_client->addr,
+ .flags = m4sensorhub->i2c_client->flags | I2C_M_RD,
+ .len = readlen,
+ .buf = buf,
+ },
+ };
+
+ if (buf == NULL || (writelen == 0 && readlen == 0))
+ return -EFAULT;
+
+ /* Offset and size in msgs array depending on msg type */
+ msglen = (writelen && readlen) ? 2 : 1;
+ msgstart = writelen ? 0 : 1;
+
+ if (m4sensorhub_debug >= M4SH_VERBOSE_DEBUG && writelen) {
+ sprintf(buffer, "Writing to M4:");
+ for (i = 0; i < writelen; i++) {
+ if (strlen(buffer) >= DEBUG_LINE_LENGTH-5) {
+ KDEBUG(M4SH_VERBOSE_DEBUG, "%s\n", buffer);
+ buffer[0] = '\0';
+ }
+ sprintf(&buffer[strlen(buffer)], " 0x%02x", buf[i]);
+ }
+ KDEBUG(M4SH_VERBOSE_DEBUG, "%s\n", buffer);
+ }
+
+ do {
+ err = i2c_transfer(m4sensorhub->i2c_client->adapter,
+ &msgs[msgstart], msglen);
+ if (err != msglen)
+ msleep_interruptible(I2C_RETRY_DELAY);
+ } while ((err != msglen) && (++tries < I2C_RETRIES));
+ if (err != msglen) {
+ dev_err(&m4sensorhub->i2c_client->dev, "i2c transfer error; "
+ "type=%d offset=%d\n", buf[0], buf[1]);
+ err = -EIO;
+ } else {
+ err = (readlen ? readlen : writelen);
+
+ if (m4sensorhub_debug >= M4SH_VERBOSE_DEBUG && readlen) {
+ sprintf(buffer, "Read from M4:");
+ for (i = 0; i < readlen; i++) {
+ if (strlen(buffer) >= DEBUG_LINE_LENGTH-5) {
+ KDEBUG(M4SH_VERBOSE_DEBUG, "%s\n",
+ buffer);
+ buffer[0] = '\0';
+ }
+ sprintf(&buffer[strlen(buffer)], " 0x%02x",
+ buf[i]);
+ }
+ KDEBUG(M4SH_VERBOSE_DEBUG, "%s\n", buffer);
+ }
+ }
+ return err;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_i2c_write_read);
+
+/* -------------- Local Functions ----------------- */
+
+static int m4sensorhub_mapsize(enum m4sensorhub_reg reg)
+{
+ int retval = -EINVAL;
+
+ if (reg < M4SH_REG__NUM)
+ retval = bank_size_tbl[register_info_tbl[reg].type];
+
+ return retval;
+}
diff --git a/drivers/mfd/m4sensorhub-reg.h b/drivers/mfd/m4sensorhub-reg.h
new file mode 100644
index 00000000000..087726c064e
--- /dev/null
+++ b/drivers/mfd/m4sensorhub-reg.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2013, Motorola, Inc. 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 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/***************************** WARNING ******************************
+ * *
+ * DO NOT EDIT THIS FILE *
+ * *
+ * This is an auto-generated file based on register maps in the *
+ * M4 Sensor Hub source tree. *
+ * *
+ ***************************** WARNING *****************************/
+
+#ifndef __M4SENSORHUB_MEM_INTERNAL_H__
+#define __M4SENSORHUB_MEM_INTERNAL_H__
+static const struct {
+ enum m4sensorhub_type type; /* Type of register */
+ unsigned short offset; /* Offset into type's memory */
+ unsigned short size; /* Size of register in bytes */
+} register_info_tbl[M4SH_REG__NUM] = {
+ [M4SH_REG_ACCEL_VERSION] = {M4SH_TYPE_ACCEL, 0x0, 1},
+ [M4SH_REG_ACCEL_DUMMY] = {M4SH_TYPE_ACCEL, 0x1, 1},
+ [M4SH_REG_ACCEL_SAMPLERATE] = {M4SH_TYPE_ACCEL, 0x2, 2},
+ [M4SH_REG_ACCEL_X] = {M4SH_TYPE_ACCEL, 0x4, 4},
+ [M4SH_REG_ACCEL_Y] = {M4SH_TYPE_ACCEL, 0x8, 4},
+ [M4SH_REG_ACCEL_Z] = {M4SH_TYPE_ACCEL, 0xc, 4},
+ [M4SH_REG_ACCEL_SCALEDMAGSQUARED] = {M4SH_TYPE_ACCEL, 0x10, 4},
+ [M4SH_REG_ACCEL_MAGNITUDE] = {M4SH_TYPE_ACCEL, 0x14, 4},
+ [M4SH_REG_ACCEL_TILT] = {M4SH_TYPE_ACCEL, 0x18, 2},
+ [M4SH_REG_ACCEL_ORIENTATION] = {M4SH_TYPE_ACCEL, 0x1a, 2},
+ [M4SH_REG_TEMP_VERSION] = {M4SH_TYPE_TEMP, 0x0, 1},
+ [M4SH_REG_TEMP_DUMMY] = {M4SH_TYPE_TEMP, 0x1, 1},
+ [M4SH_REG_TEMP_SAMPLERATE] = {M4SH_TYPE_TEMP, 0x2, 2},
+ [M4SH_REG_TEMP_EXTRNLTEMP] = {M4SH_TYPE_TEMP, 0x4, 2},
+ [M4SH_REG_TEMP_INTRNLTEMP] = {M4SH_TYPE_TEMP, 0x6, 2},
+ [M4SH_REG_GENERAL_UTC] = {M4SH_TYPE_GENERAL, 0x0, 4},
+ [M4SH_REG_GENERAL_LOCALTIMEZONE] = {M4SH_TYPE_GENERAL, 0x4, 2},
+ [M4SH_REG_GENERAL_VERSION] = {M4SH_TYPE_GENERAL, 0x6, 1},
+ [M4SH_REG_GENERAL_INTERRUPT0ENABLE] = {M4SH_TYPE_GENERAL, 0x7, 1},
+ [M4SH_REG_GENERAL_INTERRUPT1ENABLE] = {M4SH_TYPE_GENERAL, 0x8, 1},
+ [M4SH_REG_GENERAL_INTERRUPT0STATUS] = {M4SH_TYPE_GENERAL, 0x9, 1},
+ [M4SH_REG_GENERAL_INTERRUPT1STATUS] = {M4SH_TYPE_GENERAL, 0xa, 1},
+ [M4SH_REG_PRESSURE_VERSION] = {M4SH_TYPE_PRESSURE, 0x0, 1},
+ [M4SH_REG_PRESSURE_DUMMY] = {M4SH_TYPE_PRESSURE, 0x1, 1},
+ [M4SH_REG_PRESSURE_SAMPLERATE] = {M4SH_TYPE_PRESSURE, 0x2, 2},
+ [M4SH_REG_PRESSURE_PRESSURE] = {M4SH_TYPE_PRESSURE, 0x4, 4},
+ [M4SH_REG_PRESSURE_REFERENCEALTITUDE] = {M4SH_TYPE_PRESSURE, 0x8, 4},
+ [M4SH_REG_PRESSURE_SEALEVELPRESSURE] = {M4SH_TYPE_PRESSURE, 0xc, 4},
+ [M4SH_REG_PRESSURE_ABSOLUTEALTITUDE] = {M4SH_TYPE_PRESSURE, 0x10, 4},
+ [M4SH_REG_PRESSURE_TEMPERATURE] = {M4SH_TYPE_PRESSURE, 0x14, 2},
+ [M4SH_REG_PRESSURE_ISVALID] = {M4SH_TYPE_PRESSURE, 0x16, 1},
+ [M4SH_REG_PEDOMETER_VERSION] = {M4SH_TYPE_PEDOMETER, 0x0, 1},
+ [M4SH_REG_PEDOMETER_TESTCMD] = {M4SH_TYPE_PEDOMETER, 0x1, 1},
+ [M4SH_REG_PEDOMETER_ACTIVITY] = {M4SH_TYPE_PEDOMETER, 0x2, 1},
+ [M4SH_REG_PEDOMETER_EQUIPMENTTYPE] = {M4SH_TYPE_PEDOMETER, 0x3, 1},
+ [M4SH_REG_PEDOMETER_TOTALSTEPS] = {M4SH_TYPE_PEDOMETER, 0x4, 2},
+ [M4SH_REG_PEDOMETER_FLOORSCLIMBED] = {M4SH_TYPE_PEDOMETER, 0x6, 2},
+ [M4SH_REG_PEDOMETER_TOTATDISTANCE] = {M4SH_TYPE_PEDOMETER, 0x8, 4},
+ [M4SH_REG_PEDOMETER_CURRENTSPEED] = {M4SH_TYPE_PEDOMETER, 0xc, 4},
+ [M4SH_REG_PEDOMETER_REPORTEDDISTANCE] = {M4SH_TYPE_PEDOMETER, 0x10, 4},
+ [M4SH_REG_PEDOMETER_USERDISTANCE] = {M4SH_TYPE_PEDOMETER, 0x14, 4},
+ [M4SH_REG_TCMD_OPCODE] = {M4SH_TYPE_TCMD, 0x0, 1},
+ [M4SH_REG_LOG_LOGENABLE] = {M4SH_TYPE_LOG, 0x0, 8},
+ [M4SH_REG_LOG_ISLOGIMMEDIATE] = {M4SH_TYPE_LOG, 0x8, 1},
+ [M4SH_REG_FUSION_VERSION] = {M4SH_TYPE_FUSION, 0x0, 1},
+ [M4SH_REG_FUSION_DUMMY] = {M4SH_TYPE_FUSION, 0x1, 1},
+ [M4SH_REG_FUSION_SAMPLERATE] = {M4SH_TYPE_FUSION, 0x2, 2},
+ [M4SH_REG_FUSION_EULERPITCH] = {M4SH_TYPE_FUSION, 0x4, 4},
+ [M4SH_REG_FUSION_EULERROLL] = {M4SH_TYPE_FUSION, 0x8, 4},
+ [M4SH_REG_FUSION_EULERYAW] = {M4SH_TYPE_FUSION, 0xc, 4},
+ [M4SH_REG_FUSION_LOCALX] = {M4SH_TYPE_FUSION, 0x10, 4},
+ [M4SH_REG_FUSION_LOCALY] = {M4SH_TYPE_FUSION, 0x14, 4},
+ [M4SH_REG_FUSION_LOCALZ] = {M4SH_TYPE_FUSION, 0x18, 4},
+ [M4SH_REG_FUSION_WORLDX] = {M4SH_TYPE_FUSION, 0x1c, 4},
+ [M4SH_REG_FUSION_WORLDY] = {M4SH_TYPE_FUSION, 0x20, 4},
+ [M4SH_REG_FUSION_WORLDZ] = {M4SH_TYPE_FUSION, 0x24, 4},
+ [M4SH_REG_FUSION_HEADING] = {M4SH_TYPE_FUSION, 0x28, 2},
+ [M4SH_REG_FUSION_HEADING_ACCURACY] = {M4SH_TYPE_FUSION, 0x2a, 1},
+ [M4SH_REG_COMPASS_VERSION] = {M4SH_TYPE_COMPASS, 0x0, 1},
+ [M4SH_REG_COMPASS_DUMMY] = {M4SH_TYPE_COMPASS, 0x1, 1},
+ [M4SH_REG_COMPASS_SAMPLERATE] = {M4SH_TYPE_COMPASS, 0x2, 2},
+ [M4SH_REG_COMPASS_X] = {M4SH_TYPE_COMPASS, 0x4, 4},
+ [M4SH_REG_COMPASS_Y] = {M4SH_TYPE_COMPASS, 0x8, 4},
+ [M4SH_REG_COMPASS_Z] = {M4SH_TYPE_COMPASS, 0xc, 4},
+ [M4SH_REG_COMPASS_ACCURACY] = {M4SH_TYPE_COMPASS, 0x10, 1},
+ [M4SH_REG_GYRO_VERSION] = {M4SH_TYPE_GYRO, 0x0, 1},
+ [M4SH_REG_GYRO_DUMMY] = {M4SH_TYPE_GYRO, 0x1, 1},
+ [M4SH_REG_GYRO_SAMPLERATE] = {M4SH_TYPE_GYRO, 0x2, 2},
+ [M4SH_REG_GYRO_X] = {M4SH_TYPE_GYRO, 0x4, 4},
+ [M4SH_REG_GYRO_Y] = {M4SH_TYPE_GYRO, 0x8, 4},
+ [M4SH_REG_GYRO_Z] = {M4SH_TYPE_GYRO, 0xc, 4},
+ [M4SH_REG_METS_VERSION] = {M4SH_TYPE_METS, 0x0, 1},
+ [M4SH_REG_METS_METSACTIVITY] = {M4SH_TYPE_METS, 0x1, 1},
+ [M4SH_REG_METS_MSSAMPLETIME] = {M4SH_TYPE_METS, 0x2, 2},
+ [M4SH_REG_METS_METS] = {M4SH_TYPE_METS, 0x4, 4},
+ [M4SH_REG_METS_CALORIES] = {M4SH_TYPE_METS, 0x8, 4},
+ [M4SH_REG_USERSETTINGS_VERSION] = {M4SH_TYPE_USERSETTINGS, 0x0, 1},
+ [M4SH_REG_USERSETTINGS_USERAGE] = {M4SH_TYPE_USERSETTINGS, 0x1, 1},
+ [M4SH_REG_USERSETTINGS_USERGENDER] = {M4SH_TYPE_USERSETTINGS, 0x2, 1},
+ [M4SH_REG_USERSETTINGS_USERHEIGHT] = {M4SH_TYPE_USERSETTINGS, 0x3, 1},
+ [M4SH_REG_USERSETTINGS_USERWEIGHT] = {M4SH_TYPE_USERSETTINGS, 0x4, 2},
+ [M4SH_REG_USERSETTINGS_SCREENSTATUS] = {M4SH_TYPE_USERSETTINGS, 0x6, 1},
+ [M4SH_REG_USERSETTINGS_RTCRESET] = {M4SH_TYPE_USERSETTINGS, 0x7, 1},
+ [M4SH_REG_POWER_VERSION] = {M4SH_TYPE_POWER, 0x0, 1},
+ [M4SH_REG_POWER_DUMMY] = {M4SH_TYPE_POWER, 0x1, 1},
+ [M4SH_REG_POWER_STILLMODETIMEOUT] = {M4SH_TYPE_POWER, 0x2, 2},
+ [M4SH_REG_POWER_MOTIONDURATION] = {M4SH_TYPE_POWER, 0x4, 1},
+ [M4SH_REG_POWER_MOTIONTHRESHOLD] = {M4SH_TYPE_POWER, 0x5, 1},
+ [M4SH_REG_POWER_NOMOTIONDURATION] = {M4SH_TYPE_POWER, 0x6, 1},
+ [M4SH_REG_POWER_NOMOTIONTHRESHOLD] = {M4SH_TYPE_POWER, 0x7, 1},
+ [M4SH_REG_LOCATION_VERSION] = {M4SH_TYPE_LOCATION, 0x0, 1},
+ [M4SH_REG_LOCATION_SOURCE] = {M4SH_TYPE_LOCATION, 0x1, 1},
+ [M4SH_REG_LOCATION_SPEED] = {M4SH_TYPE_LOCATION, 0x2, 2},
+ [M4SH_REG_LOCATION_LATITUDE] = {M4SH_TYPE_LOCATION, 0x4, 4},
+ [M4SH_REG_LOCATION_LONGITUDE] = {M4SH_TYPE_LOCATION, 0x8, 4},
+ [M4SH_REG_LOCATION_ALTITUDE] = {M4SH_TYPE_LOCATION, 0xc, 2},
+ [M4SH_REG_DOWNLOAD_COMMAND] = {M4SH_TYPE_DOWNLOAD, 0x0, 1},
+ [M4SH_REG_DOWNLOAD_STATUS] = {M4SH_TYPE_DOWNLOAD, 0x1, 1},
+ [M4SH_REG_DOWNLOAD_SIZE] = {M4SH_TYPE_DOWNLOAD, 0x2, 2},
+ [M4SH_REG_DOWNLOAD_CHECKSUM] = {M4SH_TYPE_DOWNLOAD, 0x4, 4},
+ [M4SH_REG_DOWNLOAD_FILENAME] = {M4SH_TYPE_DOWNLOAD, 0x8, 16},
+ [M4SH_REG_DOWNLOAD_PACKET] = {M4SH_TYPE_DOWNLOAD, 0x18, 2048},
+ [M4SH_REG_AUDIO_VERSION] = {M4SH_TYPE_AUDIO, 0x0, 1},
+ [M4SH_REG_AUDIO_ENABLE] = {M4SH_TYPE_AUDIO, 0x1, 1},
+ [M4SH_REG_AUDIO_DUMMY] = {M4SH_TYPE_AUDIO, 0x2, 2},
+ [M4SH_REG_AUDIO_TOTALPACKETS] = {M4SH_TYPE_AUDIO, 0x4, 4},
+ [M4SH_REG_TIMEPIECE_VERSION] = {M4SH_TYPE_TIMEPIECE, 0x0, 1},
+ [M4SH_REG_TIMEPIECE_ENABLE] = {M4SH_TYPE_TIMEPIECE, 0x1, 1},
+ [M4SH_REG_TIMEPIECE_SAMPLERATE] = {M4SH_TYPE_TIMEPIECE, 0x2, 2},
+ [M4SH_REG_TIMEPIECE_OFFSETSTEPS] = {M4SH_TYPE_TIMEPIECE, 0x4, 4},
+ [M4SH_REG_WRIST_VERSION] = {M4SH_TYPE_WRIST, 0x0, 1},
+ [M4SH_REG_WRIST_ENABLE] = {M4SH_TYPE_WRIST, 0x1, 1},
+ [M4SH_REG_WRIST_INTERRUPTREASON] = {M4SH_TYPE_WRIST, 0x2, 1},
+ [M4SH_REG_WRIST_HOSTRESPONSE] = {M4SH_TYPE_WRIST, 0x3, 1},
+ [M4SH_REG_WRIST_FMONCHIP] = {M4SH_TYPE_WRIST, 0x4, 2},
+ [M4SH_REG_WRIST_FMONFILE] = {M4SH_TYPE_WRIST, 0x6, 2},
+ [M4SH_REG_GESTURE_VERSION] = {M4SH_TYPE_GESTURE, 0x0, 1},
+ [M4SH_REG_GESTURE_GESTURE1] = {M4SH_TYPE_GESTURE, 0x1, 1},
+ [M4SH_REG_GESTURE_CONFIDENCE1] = {M4SH_TYPE_GESTURE, 0x2, 1},
+ [M4SH_REG_GESTURE_VALUE1] = {M4SH_TYPE_GESTURE, 0x3, 1},
+ [M4SH_REG_GESTURE_GESTURE2] = {M4SH_TYPE_GESTURE, 0x4, 1},
+ [M4SH_REG_GESTURE_CONFIDENCE2] = {M4SH_TYPE_GESTURE, 0x5, 1},
+ [M4SH_REG_GESTURE_VALUE2] = {M4SH_TYPE_GESTURE, 0x6, 1},
+ [M4SH_REG_GESTURE_GESTURE3] = {M4SH_TYPE_GESTURE, 0x7, 1},
+ [M4SH_REG_GESTURE_CONFIDENCE3] = {M4SH_TYPE_GESTURE, 0x8, 1},
+ [M4SH_REG_GESTURE_VALUE3] = {M4SH_TYPE_GESTURE, 0x9, 1},
+ [M4SH_REG_PASSIVE_TIMESTAMP] = {M4SH_TYPE_PASSIVE, 0x0, 48},
+ [M4SH_REG_PASSIVE_STEPS] = {M4SH_TYPE_PASSIVE, 0x30, 48},
+ [M4SH_REG_PASSIVE_METS] = {M4SH_TYPE_PASSIVE, 0x60, 48},
+ [M4SH_REG_PASSIVE_FLOORSCLIMBED] = {M4SH_TYPE_PASSIVE, 0x90, 48},
+};
+
+static const unsigned int bank_size_tbl[M4SH_TYPE__NUM] = {
+ [M4SH_TYPE_ACCEL] = 28,
+ [M4SH_TYPE_TEMP] = 8,
+ [M4SH_TYPE_GENERAL] = 11,
+ [M4SH_TYPE_PRESSURE] = 23,
+ [M4SH_TYPE_PEDOMETER] = 24,
+ [M4SH_TYPE_TCMD] = 1,
+ [M4SH_TYPE_LOG] = 9,
+ [M4SH_TYPE_FUSION] = 43,
+ [M4SH_TYPE_COMPASS] = 17,
+ [M4SH_TYPE_GYRO] = 16,
+ [M4SH_TYPE_METS] = 12,
+ [M4SH_TYPE_USERSETTINGS] = 8,
+ [M4SH_TYPE_POWER] = 8,
+ [M4SH_TYPE_LOCATION] = 14,
+ [M4SH_TYPE_DOWNLOAD] = 2072,
+ [M4SH_TYPE_AUDIO] = 8,
+ [M4SH_TYPE_TIMEPIECE] = 8,
+ [M4SH_TYPE_WRIST] = 8,
+ [M4SH_TYPE_GESTURE] = 10,
+ [M4SH_TYPE_PASSIVE] = 192,
+};
+#endif /*__M4SENSORHUB_MEM_INTERNAL_H__ */
diff --git a/drivers/mfd/m4sensorhub-stm32-fw.c b/drivers/mfd/m4sensorhub-stm32-fw.c
new file mode 100644
index 00000000000..55aca3ebb05
--- /dev/null
+++ b/drivers/mfd/m4sensorhub-stm32-fw.c
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2012 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/delay.h>
+#include <linux/firmware.h>
+#include <linux/m4sensorhub.h>
+#include <linux/slab.h>
+
+/* --------------- Global Declarations -------------- */
+
+/* ------------ Local Function Prototypes ----------- */
+static int m4sensorhub_jump_to_user(struct m4sensorhub_data *m4sensorhub);
+
+/* --------------- Local Declarations -------------- */
+/* Firmware */
+#define FIRMWARE_NAME "m4sensorhub.bin"
+/* The M4 Flash Memory Map
+ * From the Flash Programming Manual:
+ Sector Start Address Size comments
+ ------ ------------- ---------- ----------------------------
+ 0 0x08000000 16 Kbytes reserved for M4 bootload
+ 1 0x08004000 16 Kbytes first M4 firmware code page
+ 2 0x08008000 16 Kbytes
+ 3 0x0800C000 16 Kbytes
+ 4 0x08010000 64 Kbytes
+ 5 0x08020000 128 Kbytes last M4 firmware code page
+ 6 0x08040000 128 Kbytes first M4 file system page
+ 7 0x08060000 128 Kbytes
+ 8 0x08080000 128 Kbytes
+ 9 0x080A0000 128 Kbytes
+ 10 0x080C0000 128 Kbytes
+ 11 0x080E0000 128 Kbytes last M4 file system page
+ */
+enum {
+ M4_FLASH_SECTOR0 = 0x08000000,
+ M4_FLASH_SECTOR1 = 0x08004000,
+ M4_FLASH_SECTOR2 = 0x08008000,
+ M4_FLASH_SECTOR3 = 0x0800C000,
+ M4_FLASH_SECTOR4 = 0x08010000,
+ M4_FLASH_SECTOR5 = 0x08020000,
+ M4_FLASH_SECTOR6 = 0x08040000,
+ M4_FLASH_SECTOR7 = 0x08060000,
+ M4_FLASH_SECTOR8 = 0x08080000,
+ M4_FLASH_SECTOR9 = 0x080A0000,
+ M4_FLASH_SECTORA = 0x080C0000,
+ M4_FLASH_SECTORB = 0x080E0000,
+ M4_FLASH_END = 0x08100000
+};
+#define USER_FLASH_FIRST_PAGE_ADDRESS M4_FLASH_SECTOR1
+#define USER_FLASH_FIRST_FILE_ADDRESS M4_FLASH_SECTOR6
+#define VERSION_OFFSET 0x200
+#define VERSION_ADDRESS (USER_FLASH_FIRST_PAGE_ADDRESS + VERSION_OFFSET)
+#define BARKER_SIZE 4
+#define BARKER_ADDRESS (USER_FLASH_FIRST_FILE_ADDRESS - BARKER_SIZE)
+#define BARKER_NUMBER 0xACEC0DE
+/* The MAX_FILE_SIZE is the size of sectors 1-5 where the firmware code
+ * will reside (minus the barker size).
+ */
+#define MAX_FILE_SIZE (USER_FLASH_FIRST_FILE_ADDRESS \
+ - USER_FLASH_FIRST_PAGE_ADDRESS \
+ - BARKER_SIZE) /* bytes */
+#define MAX_TRANSFER_SIZE 1024 /* bytes */
+#define MAX_RETRIES 5
+#define OPC_READ (uint8_t)(0x03)
+#define OPC_WREN (uint8_t)(0x06)
+#define OPC_ERPG (uint8_t)(0x20)
+#define OPC_ERUSM (uint8_t)(0x60)
+#define OPC_USRCD (uint8_t)(0x77)
+
+/* -------------- Local Data Structures ------------- */
+#define NUM_FLASH_TO_ERASE 5
+int flash_address[NUM_FLASH_TO_ERASE] = {
+ M4_FLASH_SECTOR1,
+ M4_FLASH_SECTOR2,
+ M4_FLASH_SECTOR3,
+ M4_FLASH_SECTOR4,
+ M4_FLASH_SECTOR5
+};
+int flash_delay[NUM_FLASH_TO_ERASE] = {
+ 440, 440, 440, 1320, 4000
+};
+
+/* -------------- Global Functions ----------------- */
+
+/* m4sensorhub_load_firmware()
+
+ Check firmware and load if different from what's already on the M4.
+ Then jump to user code on M4.
+
+ Returns 0 on success or negative error code on failure
+
+ m4sensorhub - pointer to the main m4sensorhub data struct
+*/
+
+int m4sensorhub_load_firmware(struct m4sensorhub_data *m4sensorhub,
+ unsigned short force_upgrade)
+{
+ const struct firmware *firmware;
+ int i = MAX_RETRIES;
+ int ret = 0;
+ int bytes_left, bytes_to_write;
+ int address_to_write;
+ u8 *buf_to_read, *buf = NULL;
+ u16 fw_version_file, fw_version_device;
+ u32 barker_read_from_device;
+ int j = 0;
+
+ buf = kzalloc(MAX_TRANSFER_SIZE+8, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ ret = request_firmware(&firmware, FIRMWARE_NAME,
+ &m4sensorhub->i2c_client->dev);
+ if (ret < 0) {
+ KDEBUG(M4SH_ERROR, "%s: request_firmware failed for %s\n",
+ __func__, FIRMWARE_NAME);
+ KDEBUG(M4SH_ERROR, "Trying to run firmware already on hw.\n");
+ ret = 0;
+ goto done;
+ }
+
+ if (firmware->size > MAX_FILE_SIZE) {
+ KDEBUG(M4SH_ERROR, "%s: firmware file size is too big.\n",
+ __func__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ fw_version_file = *(u16 *) (firmware->data +
+ VERSION_ADDRESS - USER_FLASH_FIRST_PAGE_ADDRESS);
+
+ if (!force_upgrade) {
+ /* Verify Barker number from device */
+ buf[0] = OPC_READ;
+ buf[1] = (BARKER_ADDRESS >> 24) & 0xFF;
+ buf[2] = (BARKER_ADDRESS >> 16) & 0xFF;
+ buf[3] = (BARKER_ADDRESS >> 8) & 0xFF;
+ buf[4] = BARKER_ADDRESS & 0xFF;
+ buf[5] = 0x00;
+ buf[6] = 0x04;
+ if (m4sensorhub_i2c_write_read(m4sensorhub,
+ buf, 7, 0) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ msleep(100);
+
+ if (m4sensorhub_i2c_write_read(m4sensorhub,
+ (u8 *) &barker_read_from_device, 0, BARKER_SIZE) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (barker_read_from_device != BARKER_NUMBER) {
+ KDEBUG(M4SH_NOTICE,
+ "Barker Number read 0x%8x does not match 0x%8x\n",
+ barker_read_from_device, BARKER_NUMBER);
+ KDEBUG(M4SH_NOTICE,
+ "forcing firmware update from file\n");
+ } else {
+
+ /* Read firmware version from device */
+ buf[0] = OPC_READ;
+ buf[1] = (VERSION_ADDRESS >> 24) & 0xFF;
+ buf[2] = (VERSION_ADDRESS >> 16) & 0xFF;
+ buf[3] = (VERSION_ADDRESS >> 8) & 0xFF;
+ buf[4] = VERSION_ADDRESS & 0xFF;
+ buf[5] = 0x00;
+ buf[6] = 0x02;
+ if (m4sensorhub_i2c_write_read(
+ m4sensorhub, buf, 7, 0) < 0) {
+ KDEBUG(M4SH_ERROR,
+ "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ msleep(100);
+
+ if (m4sensorhub_i2c_write_read(m4sensorhub,
+ (u8 *) &fw_version_device, 0, 2) < 0) {
+ KDEBUG(M4SH_ERROR,
+ "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (fw_version_file == fw_version_device) {
+ KDEBUG(M4SH_NOTICE,
+ "Version of firmware on device is 0x%04x\n",
+ fw_version_device);
+ KDEBUG(M4SH_NOTICE,
+ "Firmware on device same as file, not loading firmware.\n");
+ goto done;
+ }
+ /* Print statement below isn't really an ERROR, but
+ * this ensures it is always printed */
+ KDEBUG(M4SH_ERROR,
+ "Version of firmware on device is 0x%04x\n",
+ fw_version_device);
+ KDEBUG(M4SH_ERROR,
+ "Version of firmware on file is 0x%04x\n",
+ fw_version_file);
+ KDEBUG(M4SH_ERROR,
+ "Firmware on device different from file, updating...\n");
+ }
+ } else {
+ KDEBUG(M4SH_NOTICE, "Version of firmware on file is 0x%04x\n",
+ fw_version_file);
+ }
+
+ /* The flash memory to update has to be erased before updating */
+ for (j = 0; j < NUM_FLASH_TO_ERASE; j++) {
+ buf[0] = OPC_ERPG;
+ buf[1] = (flash_address[j] >> 24) & 0xFF;
+ buf[2] = (flash_address[j] >> 16) & 0xFF;
+ buf[3] = (flash_address[j] >> 8) & 0xFF;
+ buf[4] = flash_address[j] & 0xFF;
+ buf[5] = 0x00;
+ buf[6] = 0x01;
+ if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 7, 0) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ KDEBUG(M4SH_ERROR, "Erasing %8x address failed\n",
+ flash_address[j]);
+ ret = -EINVAL;
+ goto done;
+ }
+ msleep(flash_delay[j]);
+ }
+
+ bytes_left = firmware->size;
+ address_to_write = USER_FLASH_FIRST_PAGE_ADDRESS;
+ buf_to_read = (u8 *) firmware->data;
+
+ KDEBUG(M4SH_DEBUG, "%s: %d bytes to be written\n", __func__,
+ firmware->size);
+
+ while (bytes_left && i) {
+ if (bytes_left > MAX_TRANSFER_SIZE)
+ bytes_to_write = MAX_TRANSFER_SIZE;
+ else
+ bytes_to_write = bytes_left;
+
+ buf[0] = OPC_WREN;
+ buf[1] = (address_to_write >> 24) & 0xFF;
+ buf[2] = (address_to_write >> 16) & 0xFF;
+ buf[3] = (address_to_write >> 8) & 0xFF;
+ buf[4] = address_to_write & 0xFF;
+ buf[5] = (bytes_to_write >> 8) & 0xFF;
+ buf[6] = bytes_to_write & 0xFF;
+ buf[7] = 0xFF;
+ memcpy(&buf[8], buf_to_read, bytes_to_write);
+ if (m4sensorhub_i2c_write_read(m4sensorhub, buf,
+ bytes_to_write+8, 0) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ msleep(20);
+
+ /* Read back the code that was written and validate it */
+ buf[0] = OPC_READ;
+ buf[1] = (address_to_write >> 24) & 0xFF;
+ buf[2] = (address_to_write >> 16) & 0xFF;
+ buf[3] = (address_to_write >> 8) & 0xFF;
+ buf[4] = address_to_write & 0xFF;
+ buf[5] = (bytes_to_write >> 8) & 0xFF;
+ buf[6] = bytes_to_write & 0xFF;
+ if (m4sensorhub_i2c_write_read(
+ m4sensorhub, buf, 7, 0) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 0,
+ bytes_to_write) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (memcmp(buf, buf_to_read, bytes_to_write) != 0) {
+ /* If memory write fails, try again */
+ KDEBUG(M4SH_ERROR,
+ "memory write to 0x%x of %d bytes failed, try again\n",
+ address_to_write, bytes_to_write);
+ i--;
+ } else {
+ address_to_write += bytes_to_write;
+ buf_to_read += bytes_to_write;
+ bytes_left -= bytes_to_write;
+ /* Reset reter of retries */
+ i = MAX_RETRIES;
+ }
+ }
+
+ if (!i) {
+ KDEBUG(M4SH_ERROR, "%s: firmware transfer failed\n", __func__);
+ ret = -EINVAL;
+ } else {
+ /* Write barker number when firmware successfully written */
+ buf[0] = OPC_WREN;
+ buf[1] = (BARKER_ADDRESS >> 24) & 0xFF;
+ buf[2] = (BARKER_ADDRESS >> 16) & 0xFF;
+ buf[3] = (BARKER_ADDRESS >> 8) & 0xFF;
+ buf[4] = BARKER_ADDRESS & 0xFF;
+ buf[5] = 0x00;
+ buf[6] = 0x04;
+ buf[7] = 0xFF;
+ buf[8] = BARKER_NUMBER & 0xFF;
+ buf[9] = (BARKER_NUMBER >> 8) & 0xFF;
+ buf[10] = (BARKER_NUMBER >> 16) & 0xFF;
+ buf[11] = (BARKER_NUMBER >> 24) & 0xFF;
+ if (m4sensorhub_i2c_write_read(m4sensorhub,
+ buf, 12, 0) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ KDEBUG(M4SH_NOTICE, "%s: %d bytes written successfully\n",
+ __func__, firmware->size);
+ }
+
+done:
+ release_firmware(firmware);
+
+ /* If ret is invalid, then we don't try to jump to user code */
+ if (ret >= 0 && m4sensorhub_jump_to_user(m4sensorhub) < 0)
+ /* If jump to user code fails, return failure */
+ ret = -EINVAL;
+
+ kfree(buf);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(m4sensorhub_load_firmware);
+
+
+/* -------------- Local Functions ----------------- */
+
+static int m4sensorhub_jump_to_user(struct m4sensorhub_data *m4sensorhub)
+{
+ int ret = -1;
+ u8 buf[7] = {0};
+ u32 barker_read_from_device = 0;
+
+ buf[0] = OPC_READ;
+ buf[1] = (BARKER_ADDRESS >> 24) & 0xFF;
+ buf[2] = (BARKER_ADDRESS >> 16) & 0xFF;
+ buf[3] = (BARKER_ADDRESS >> 8) & 0xFF;
+ buf[4] = BARKER_ADDRESS & 0xFF;
+ buf[5] = 0x00;
+ buf[6] = 0x04;
+ if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 7, 0) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ msleep(100);
+
+ if (m4sensorhub_i2c_write_read(m4sensorhub,
+ (u8 *) &barker_read_from_device, 0, BARKER_SIZE) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ if (barker_read_from_device == BARKER_NUMBER) {
+ buf[0] = OPC_USRCD;
+ if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 1, 0) < 0) {
+ KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n",
+ __func__, __LINE__);
+ return ret;
+ }
+ KDEBUG(M4SH_NOTICE, "Executing M4 code \n");
+ msleep(5000); /* 5 secs delay */
+ ret = 0;
+ } else {
+ KDEBUG(M4SH_ERROR,
+ "Barker Number read 0x%8x does not match 0x%8x\n",
+ barker_read_from_device, BARKER_NUMBER);
+ KDEBUG(M4SH_ERROR, "*** Not executing M4 code ***\n");
+ }
+
+ return ret;
+}
diff --git a/drivers/mfd/tps65912-core.c b/drivers/mfd/tps65912-core.c
index aeb8e40ab42..dfd832d0aef 100644
--- a/drivers/mfd/tps65912-core.c
+++ b/drivers/mfd/tps65912-core.c
@@ -156,9 +156,17 @@ int tps65912_device_init(struct tps65912 *tps65912)
if (ret < 0)
goto err;
+#ifdef CONFIG_MFD_TPS65912_DEBUGFS
+ ret = tps65912_debugfs_create(tps65912);
+ if (ret < 0)
+ goto err_debugfs;
+#endif
+
kfree(init_data);
return ret;
+err_debugfs:
+ tps65912_irq_exit(tps65912);
err:
kfree(init_data);
mfd_remove_devices(tps65912->dev);
@@ -168,6 +176,9 @@ err:
void tps65912_device_exit(struct tps65912 *tps65912)
{
+#ifdef CONFIG_MFD_TPS65912_DEBUGFS
+ tps65912_debugfs_remove(tps65912);
+#endif
mfd_remove_devices(tps65912->dev);
tps65912_irq_exit(tps65912);
kfree(tps65912);
diff --git a/drivers/mfd/tps65912-debugfs.c b/drivers/mfd/tps65912-debugfs.c
new file mode 100644
index 00000000000..2a7d870aa8e
--- /dev/null
+++ b/drivers/mfd/tps65912-debugfs.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2013 Motorola Mobility LLC
+ *
+ * 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/debugfs.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/mfd/tps65912.h>
+
+
+struct tps65912_debugfs_reg_descr {
+ char *name; /* Debugfs file name */
+ u8 reg; /* Register address */
+};
+
+/* Registers exposed in debugfs */
+static const struct tps65912_debugfs_reg_descr debugfs_regs[] = {
+ { "DCDC1_CTRL", 0x00 },
+ { "DCDC2_CTRL", 0x01 },
+ { "DCDC3_CTRL", 0x02 },
+ { "DCDC4_CTRL", 0x03 },
+ { "DCDC1_OP", 0x04 },
+ { "DCDC1_AVS", 0x05 },
+ { "DCDC1_LIMIT", 0x06 },
+ { "DCDC2_OP", 0x07 },
+ { "DCDC2_AVS", 0x08 },
+ { "DCDC2_LIMIT", 0x09 },
+ { "DCDC3_OP", 0x0A },
+ { "DCDC3_AVS", 0x0B },
+ { "DCDC3_LIMIT", 0x0C },
+ { "DCDC4_OP", 0x0D },
+ { "DCDC4_AVS", 0x0E },
+ { "DCDC4_LIMIT", 0x0F },
+ { "LDO1_OP", 0x10 },
+ { "LDO1_AVS", 0x11 },
+ { "LDO1_LIMIT", 0x12 },
+ { "LDO2_OP", 0x13 },
+ { "LDO2_AVS", 0x14 },
+ { "LDO2_LIMIT", 0x15 },
+ { "LDO3_OP", 0x16 },
+ { "LDO3_AVS", 0x17 },
+ { "LDO3_LIMIT", 0x18 },
+ { "LDO4_OP", 0x19 },
+ { "LDO4_AVS", 0x1A },
+ { "LDO4_LIMIT", 0x1B },
+ { "LDO5", 0x1C },
+ { "LDO6", 0x1D },
+ { "LDO7", 0x1E },
+ { "LDO8", 0x1F },
+ { "LDO9", 0x20 },
+ { "LDO10", 0x21 },
+ { "THRM", 0x22 },
+ { "CLK32OUT", 0x23 },
+ { "DEVCTRL", 0x24 },
+ { "DEVCTRL2", 0x25 },
+ { "I2C_SPI_CFG", 0x26 },
+ { "KEEP_ON", 0x27 },
+ { "KEEP_ON2", 0x28 },
+ { "SET_OFF1", 0x29 },
+ { "SET_OFF2", 0x2A },
+ { "DEF_VOLT", 0x2B },
+ { "DEF_VOLT_MAPPING", 0x2C },
+ { "DISCHARGE", 0x2D },
+ { "DISCHARGE2", 0x2E },
+ { "EN1_SET1", 0x2F },
+ { "EN1_SET2", 0x30 },
+ { "EN2_SET1", 0x31 },
+ { "EN2_SET2", 0x32 },
+ { "EN3_SET1", 0x33 },
+ { "EN3_SET2", 0x34 },
+ { "EN4_SET1", 0x35 },
+ { "EN4_SET2", 0x36 },
+ { "PGOOD", 0x37 },
+ { "PGOOD2", 0x38 },
+ { "INT_STS", 0x39 },
+ { "INT_MSK", 0x3A },
+ { "INT_STS2", 0x3B },
+ { "INT_MSK2", 0x3C },
+ { "INT_STS3", 0x3D },
+ { "INT_MSK3", 0x3E },
+ { "INT_STS4", 0x3F },
+ { "INT_MSK4", 0x40 },
+ { "GPIO1", 0x41 },
+ { "GPIO2", 0x42 },
+ { "GPIO3", 0x43 },
+ { "GPIO4", 0x44 },
+ { "GPIO5", 0x45 },
+ { "VMON", 0x46 },
+ { "LEDA_CTRL1", 0x47 },
+ { "LEDA_CTRL2", 0x48 },
+ { "LEDA_CTRL3", 0x49 },
+ { "LEDA_CTRL4", 0x4A },
+ { "LEDA_CTRL5", 0x4B },
+ { "LEDA_CTRL6", 0x4C },
+ { "LEDA_CTRL7", 0x4D },
+ { "LEDA_CTRL8", 0x4E },
+ { "LEDB_CTRL1", 0x4F },
+ { "LEDB_CTRL2", 0x50 },
+ { "LEDB_CTRL3", 0x51 },
+ { "LEDB_CTRL4", 0x52 },
+ { "LEDB_CTRL5", 0x53 },
+ { "LEDB_CTRL6", 0x54 },
+ { "LEDB_CTRL7", 0x55 },
+ { "LEDB_CTRL8", 0x56 },
+ { "LEDC_CTRL1", 0x57 },
+ { "LEDC_CTRL2", 0x58 },
+ { "LEDC_CTRL3", 0x59 },
+ { "LEDC_CTRL4", 0x5A },
+ { "LEDC_CTRL5", 0x5B },
+ { "LEDC_CTRL6", 0x5C },
+ { "LEDC_CTRL7", 0x5D },
+ { "LEDC_CTRL8", 0x5E },
+ { "LED_RAMP_UP_TIME", 0x5F },
+ { "LED_RAMP_DOWN_TIME", 0x60 },
+ { "LED_SEQ_EN", 0x61 },
+ { "LOADSWITCH", 0x62 },
+ { "SPARE", 0x63 },
+ { "VERNUM", 0x64 },
+};
+
+struct tps65912_debugfs_reg_data {
+ struct tps65912 *tps65912; /* Device */
+ u8 reg; /* Register address */
+};
+
+struct tps65912_debugfs_data {
+ struct dentry *root; /* Debugfs directory of the device */
+ struct tps65912_debugfs_reg_data reg_data[ARRAY_SIZE(debugfs_regs)];
+};
+
+static int reg_read(void *data, u64 *val)
+{
+ struct tps65912_debugfs_reg_data *reg_data;
+
+ reg_data = (struct tps65912_debugfs_reg_data *) data;
+ *val = tps65912_reg_read(reg_data->tps65912, reg_data->reg);
+ return 0;
+}
+
+static int reg_write(void *data, u64 val)
+{
+ struct tps65912_debugfs_reg_data *reg_data;
+
+ reg_data = (struct tps65912_debugfs_reg_data *) data;
+ return tps65912_reg_write(reg_data->tps65912, reg_data->reg, val);
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, reg_read, reg_write, "0x%02llx\n");
+
+int tps65912_debugfs_create(struct tps65912 *tps65912)
+{
+ int i;
+ struct tps65912_debugfs_data *debugfs_data;
+
+ debugfs_data = kzalloc(sizeof(struct tps65912_debugfs_data),
+ GFP_KERNEL);
+ if (!debugfs_data)
+ goto err_data;
+
+ debugfs_data->root = debugfs_create_dir(dev_name(tps65912->dev), NULL);
+ if (!debugfs_data->root)
+ goto err_root;
+
+ for (i = 0; i < ARRAY_SIZE(debugfs_regs); i++) {
+ debugfs_data->reg_data[i].tps65912 = tps65912;
+ debugfs_data->reg_data[i].reg = debugfs_regs[i].reg;
+ if (!debugfs_create_file(debugfs_regs[i].name,
+ S_IRUGO | S_IWUSR, debugfs_data->root,
+ debugfs_data->reg_data + i, &reg_fops))
+ goto err_file;
+ }
+
+ tps65912->debugfs_data = debugfs_data;
+ return 0;
+
+err_file:
+ debugfs_remove_recursive(debugfs_data->root);
+err_root:
+ kfree(debugfs_data);
+err_data:
+ tps65912->debugfs_data = NULL;
+ return -ENOMEM;
+}
+
+void tps65912_debugfs_remove(struct tps65912 *tps65912)
+{
+ struct tps65912_debugfs_data *debugfs_data;
+
+ debugfs_data = (struct tps65912_debugfs_data *) tps65912->debugfs_data;
+ if (debugfs_data) {
+ debugfs_remove_recursive(debugfs_data->root);
+ kfree(debugfs_data);
+ }
+}