diff options
| author | Doug Zobel <dzobel1@motorola.com> | 2013-11-15 14:29:07 -0600 |
|---|---|---|
| committer | James Wylder <jwylder@motorola.com> | 2014-03-05 17:46:52 -0600 |
| commit | d2a782003a6047da120a33e6f8ee6fd33bb825d6 (patch) | |
| tree | 8d20bd4ecda62a06e98993c4108456bc1acb0d0b /drivers/mfd | |
| parent | 32fd2d36d2464056d4522a9c02797b7c2b2e884f (diff) | |
| download | olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.tar.xz olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.zip | |
CW integration and minnow bringup
* create minnow machine type
* create Android makefile
* add pre-commit syntax check
* enable -Werror
* Add drivers: CPCAP, TPS65xxx, m4sensorhub, atmxt, lm3535,
usb gadget, minnow display, TI 12xx wireless
Change-Id: I7962f5e1256715f2452aed5a62a4f2f2383d5046
Diffstat (limited to 'drivers/mfd')
| -rw-r--r-- | drivers/mfd/Kconfig | 19 | ||||
| -rw-r--r-- | drivers/mfd/Makefile | 19 | ||||
| -rw-r--r-- | drivers/mfd/cpcap-adc.c | 708 | ||||
| -rw-r--r-- | drivers/mfd/cpcap-core.c | 577 | ||||
| -rw-r--r-- | drivers/mfd/cpcap-irq.c | 809 | ||||
| -rw-r--r-- | drivers/mfd/cpcap-key.c | 146 | ||||
| -rw-r--r-- | drivers/mfd/cpcap-regacc.c | 393 | ||||
| -rw-r--r-- | drivers/mfd/cpcap-uc.c | 927 | ||||
| -rw-r--r-- | drivers/mfd/cpcap-usb-det.c | 948 | ||||
| -rw-r--r-- | drivers/mfd/m4sensorhub-core.c | 569 | ||||
| -rw-r--r-- | drivers/mfd/m4sensorhub-irq.c | 675 | ||||
| -rw-r--r-- | drivers/mfd/m4sensorhub-panic.c | 248 | ||||
| -rw-r--r-- | drivers/mfd/m4sensorhub-reg.c | 384 | ||||
| -rw-r--r-- | drivers/mfd/m4sensorhub-reg.h | 188 | ||||
| -rw-r--r-- | drivers/mfd/m4sensorhub-stm32-fw.c | 419 | ||||
| -rw-r--r-- | drivers/mfd/tps65912-core.c | 11 | ||||
| -rw-r--r-- | drivers/mfd/tps65912-debugfs.c | 213 |
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(®_access); + + retval = cpcap_config_for_read(spi, register_info_tbl + [reg].address, value_ptr); + + mutex_unlock(®_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(®_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(®_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, ®val); + 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, ®val); + + if (regval & (1 << macro)) + retval = 1; + } else { + cpcap_regacc_read(cpcap, CPCAP_REG_MIM1, ®val); + + 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 *)¤tLogLevels); + while (1) { + tag = strsep(logbuf, "=,\n "); + if (tag == NULL) + break; + level = strsep(logbuf, "=,\n "); + if (level == NULL) + break; + ParseAndUpdateLogLevels(tag, level, ¤tLogLevels); + } + + return m4sensorhub_reg_write(m4sensorhub_misc_data, + M4SH_REG_LOG_LOGENABLE, (char *)¤tLogLevels, + 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(®_access); + ret = m4sensorhub_i2c_write_read(m4sensorhub, buf, 2, num); + mutex_unlock(®_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(®_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(®_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(®_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(®_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, ®_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); + } +} |