summaryrefslogtreecommitdiff
path: root/drivers/mfd/cpcap-uc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/cpcap-uc.c')
-rw-r--r--drivers/mfd/cpcap-uc.c927
1 files changed, 927 insertions, 0 deletions
diff --git a/drivers/mfd/cpcap-uc.c b/drivers/mfd/cpcap-uc.c
new file mode 100644
index 00000000000..2626bfcb5f7
--- /dev/null
+++ b/drivers/mfd/cpcap-uc.c
@@ -0,0 +1,927 @@
+/*
+ * Copyright (C) 2008-2010 Motorola, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307, USA
+ */
+
+#include <linux/completion.h>
+#include <linux/errno.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/ihex.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+
+#include <linux/spi/cpcap.h>
+#include <linux/spi/cpcap-regbits.h>
+#include <linux/spi/spi.h>
+
+#define ERROR_MACRO_TIMEOUT 0x81
+#define ERROR_MACRO_WRITE 0x82
+#define ERROR_MACRO_READ 0x83
+
+#define RAM_START_TI 0x9000
+#define RAM_END_TI 0x9FA0
+#define RAM_START_ST 0x0000
+#define RAM_END_ST 0x0FFF
+
+#define HWCFG_ADDR_ST 0x0148
+#define HWCFG_ADDR_TI 0x90F4 /* Not yet implemented in the TI uC. */
+
+enum {
+ READ_STATE_1, /* Send size and location of RAM read. */
+ READ_STATE_2, /*!< Read MT registers. */
+ READ_STATE_3, /*!< Read data from uC. */
+ READ_STATE_4, /*!< Check for error. */
+};
+
+enum {
+ WRITE_STATE_1, /* Send size and location of RAM write. */
+ WRITE_STATE_2, /* Check for error. */
+ WRITE_STATE_3, /* Write data to uC. */
+ WRITE_STATE_4 /* Check for error. */
+};
+
+struct cpcap_uc_data {
+ struct cpcap_device *cpcap;
+ unsigned char is_supported;
+ unsigned char is_ready;
+ struct completion completion;
+ int cb_status;
+ struct mutex lock;
+ unsigned char uc_reset;
+ unsigned char state;
+ unsigned short state_cntr;
+ struct {
+ unsigned short address;
+ unsigned short *data;
+ unsigned short num_words;
+ } req;
+};
+
+static struct cpcap_uc_data *cpcap_uc_info;
+
+static int fops_open(struct inode *inode, struct file *file);
+static long fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+static ssize_t fops_write(struct file *file, const char *buf,
+ size_t count, loff_t *ppos);
+static ssize_t fops_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos);
+
+
+static const struct file_operations fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fops_ioctl,
+ .open = fops_open,
+ .read = fops_read,
+ .write = fops_write,
+};
+
+static struct miscdevice uc_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "cpcap_uc",
+ .fops = &fops,
+};
+
+static int is_valid_address(struct cpcap_device *cpcap, unsigned short address,
+ unsigned short num_words)
+{
+ int vld = 0;
+
+ if (cpcap->vendor == CPCAP_VENDOR_TI) {
+ vld = (address >= RAM_START_TI) &&
+ ((address + num_words) <= RAM_END_TI);
+ } else if (cpcap->vendor == CPCAP_VENDOR_ST) {
+ vld = ((address + num_words) <= RAM_END_ST);
+ }
+
+ return vld;
+}
+
+static void ram_read_state_machine(enum cpcap_irqs irq, void *data)
+{
+ struct cpcap_uc_data *uc_data = data;
+ unsigned short temp;
+
+ if (irq != CPCAP_IRQ_UC_PRIRAMR)
+ return;
+
+ switch (uc_data->state) {
+ case READ_STATE_1:
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1,
+ uc_data->req.address, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2,
+ uc_data->req.num_words, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0, 0xFFFF);
+
+ if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST)
+ uc_data->state = READ_STATE_2;
+ else
+ uc_data->state = READ_STATE_3;
+
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+
+ break;
+
+ case READ_STATE_2:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &temp);
+
+ if (temp == ERROR_MACRO_READ) {
+ uc_data->state = READ_STATE_1;
+ uc_data->state_cntr = 0;
+
+ cpcap_irq_mask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+
+ uc_data->cb_status = -EIO;
+
+ complete(&uc_data->completion);
+ } else {
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2, &temp);
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3, &temp);
+
+ uc_data->state = READ_STATE_3;
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+ }
+ break;
+
+ case READ_STATE_3:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1,
+ uc_data->req.data + uc_data->state_cntr);
+
+ uc_data->state_cntr += 1;
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2, &temp);
+ else {
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2,
+ uc_data->req.data +
+ uc_data->state_cntr);
+
+ uc_data->state_cntr += 1;
+ }
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3, &temp);
+ else {
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3,
+ uc_data->req.data +
+ uc_data->state_cntr);
+
+ uc_data->state_cntr += 1;
+ }
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ uc_data->state = READ_STATE_4;
+
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+ break;
+
+ case READ_STATE_4:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &temp);
+
+ if (temp != ERROR_MACRO_READ)
+ uc_data->cb_status = 0;
+ else
+ uc_data->cb_status = -EIO;
+
+ complete(&uc_data->completion);
+
+ uc_data->state = READ_STATE_1;
+ uc_data->state_cntr = 0;
+ break;
+
+ default:
+ uc_data->state = READ_STATE_1;
+ uc_data->state_cntr = 0;
+ break;
+ }
+}
+
+static void ram_write_state_machine(enum cpcap_irqs irq, void *data)
+{
+ struct cpcap_uc_data *uc_data = data;
+ unsigned short error_check;
+
+ if (irq != CPCAP_IRQ_UC_PRIRAMW)
+ return;
+
+ switch (uc_data->state) {
+ case WRITE_STATE_1:
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1,
+ uc_data->req.address, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2,
+ uc_data->req.num_words, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0, 0xFFFF);
+
+ uc_data->state = WRITE_STATE_2;
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+ break;
+
+ case WRITE_STATE_2:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &error_check);
+
+ if (error_check == ERROR_MACRO_WRITE) {
+ uc_data->state = WRITE_STATE_1;
+ uc_data->state_cntr = 0;
+
+ cpcap_irq_mask(uc_data->cpcap,
+ CPCAP_IRQ_UC_PRIRAMW);
+
+ uc_data->cb_status = -EIO;
+ complete(&uc_data->completion);
+ break;
+ } else
+ uc_data->state = WRITE_STATE_3;
+
+ /* No error has occured, fall through */
+
+ case WRITE_STATE_3:
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1,
+ *(uc_data->req.data + uc_data->state_cntr),
+ 0xFFFF);
+ uc_data->state_cntr += 1;
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2, 0,
+ 0xFFFF);
+ else {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2,
+ *(uc_data->req.data +
+ uc_data->state_cntr), 0xFFFF);
+
+ uc_data->state_cntr += 1;
+ }
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0,
+ 0xFFFF);
+ else {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3,
+ *(uc_data->req.data +
+ uc_data->state_cntr), 0xFFFF);
+
+ uc_data->state_cntr += 1;
+ }
+
+ if (uc_data->state_cntr == uc_data->req.num_words)
+ uc_data->state = WRITE_STATE_4;
+
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+ break;
+
+ case WRITE_STATE_4:
+ cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &error_check);
+
+ if (error_check != ERROR_MACRO_WRITE)
+ uc_data->cb_status = 0;
+ else
+ uc_data->cb_status = -EIO;
+
+ complete(&uc_data->completion);
+
+ uc_data->state = WRITE_STATE_1;
+ uc_data->state_cntr = 0;
+ break;
+
+ default:
+ uc_data->state = WRITE_STATE_1;
+ uc_data->state_cntr = 0;
+ break;
+ }
+}
+
+static void reset_handler(enum cpcap_irqs irq, void *data)
+{
+ int i;
+ unsigned short regval;
+ struct cpcap_uc_data *uc_data = data;
+
+ if (irq != CPCAP_IRQ_UCRESET)
+ return;
+
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCC1,
+ CPCAP_BIT_PRIHALT, CPCAP_BIT_PRIHALT);
+
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_PGC,
+ CPCAP_BIT_PRI_UC_SUSPEND, CPCAP_BIT_PRI_UC_SUSPEND);
+
+ uc_data->uc_reset = 1;
+ uc_data->cb_status = -EIO;
+ complete(&uc_data->completion);
+
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2, 0, 0xFFFF);
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MIM1, 0xFFFF, 0xFFFF);
+ cpcap_irq_mask(uc_data->cpcap, CPCAP_IRQ_PRIMAC);
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UCRESET);
+
+ for (i = 0; i <= CPCAP_REG_END; i++) {
+ cpcap_regacc_read(uc_data->cpcap, i, &regval);
+ dev_err(&uc_data->cpcap->spi->dev,
+ "cpcap reg %d = 0x%04X\n", i, regval);
+ }
+
+ BUG();
+}
+
+static void primac_handler(enum cpcap_irqs irq, void *data)
+{
+ struct cpcap_uc_data *uc_data = data;
+
+ if (irq == CPCAP_IRQ_PRIMAC)
+ cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_PRIMAC);
+}
+
+static int ram_write(struct cpcap_uc_data *uc_data, unsigned short address,
+ unsigned short num_words, unsigned short *data)
+{
+ int retval = -EFAULT;
+
+ mutex_lock(&uc_data->lock);
+
+ if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM,
+ CPCAP_BIT_UCTM, CPCAP_BIT_UCTM);
+ }
+
+ if (uc_data->is_supported && (num_words > 0) &&
+ (data != NULL) &&
+ is_valid_address(uc_data->cpcap, address, num_words) &&
+ !uc_data->uc_reset) {
+ uc_data->req.address = address;
+ uc_data->req.data = data;
+ uc_data->req.num_words = num_words;
+ uc_data->state = WRITE_STATE_1;
+ uc_data->state_cntr = 0;
+ INIT_COMPLETION(uc_data->completion);
+
+ retval = cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2,
+ CPCAP_BIT_PRIRAMW,
+ CPCAP_BIT_PRIRAMW);
+ if (retval)
+ goto err;
+
+ /* Cannot call cpcap_irq_register() here because unregister
+ * cannot be called from the state machine. Doing so causes
+ * a deadlock. */
+ retval = cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+ if (retval)
+ goto err;
+
+ wait_for_completion(&uc_data->completion);
+ retval = uc_data->cb_status;
+ }
+
+err:
+ if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM,
+ 0, CPCAP_BIT_UCTM);
+ }
+
+ mutex_unlock(&uc_data->lock);
+
+ return retval;
+}
+
+static int ram_read(struct cpcap_uc_data *uc_data, unsigned short address,
+ unsigned short num_words, unsigned short *data)
+{
+ int retval = -EFAULT;
+
+ mutex_lock(&uc_data->lock);
+
+ if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM,
+ CPCAP_BIT_UCTM, CPCAP_BIT_UCTM);
+ }
+
+ if (uc_data->is_supported && (num_words > 0) &&
+ is_valid_address(uc_data->cpcap, address, num_words) &&
+ !uc_data->uc_reset) {
+ uc_data->req.address = address;
+ uc_data->req.data = data;
+ uc_data->req.num_words = num_words;
+ uc_data->state = READ_STATE_1;
+ uc_data->state_cntr = 0;
+ INIT_COMPLETION(uc_data->completion);
+
+ retval = cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2,
+ CPCAP_BIT_PRIRAMR,
+ CPCAP_BIT_PRIRAMR);
+ if (retval)
+ goto err;
+
+ /* Cannot call cpcap_irq_register() here because unregister
+ * cannot be called from the state machine. Doing so causes
+ * a deadlock. */
+ retval = cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+ if (retval)
+ goto err;
+
+ wait_for_completion(&uc_data->completion);
+ retval = uc_data->cb_status;
+ }
+
+err:
+ if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) {
+ cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM,
+ 0, CPCAP_BIT_UCTM);
+ }
+
+ mutex_unlock(&uc_data->lock);
+
+ return retval;
+}
+
+static int ram_load(struct cpcap_uc_data *uc_data, unsigned int num_words,
+ unsigned short *data)
+{
+ int retval = -EINVAL;
+
+ if ((data != NULL) && (num_words > 0))
+ retval = ram_write(uc_data, data[0], (num_words - 1),
+ (data + 1));
+
+ return retval;
+}
+
+static ssize_t fops_write(struct file *file, const char *buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t retval = -EINVAL;
+ unsigned short address;
+ unsigned short num_words;
+ unsigned short *data;
+ struct cpcap_uc_data *uc_data = file->private_data;
+
+ if ((buf != NULL) && (ppos != NULL) && (count >= 2)) {
+ data = kzalloc(count, GFP_KERNEL);
+
+ if (data != NULL) {
+ num_words = (unsigned short) (count >> 1);
+
+ /* If the position (uC RAM address) is zero then the
+ * data contains the address */
+ if (*ppos == 0) {
+ if (copy_from_user((void *) data, (void *) buf,
+ count) == 0)
+ retval = ram_load(uc_data, num_words,
+ data);
+ else
+ retval = -EFAULT;
+ }
+ /* If the position (uC RAM address) is not zero then the
+ * position holds the address to load the data */
+ else {
+ address = (unsigned short) (*ppos);
+
+ if (copy_from_user((void *) data, (void *) buf,
+ count) == 0)
+ retval = ram_write(uc_data, address,
+ num_words, data);
+ else
+ retval = -EFAULT;
+ }
+
+ kfree(data);
+ } else {
+ retval = -ENOMEM;
+ }
+ }
+
+ if (retval == 0)
+ retval = num_words;
+
+ return retval;
+}
+
+static ssize_t fops_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t retval = -EFAULT;
+ unsigned short address;
+ unsigned short num_words;
+ unsigned short *data;
+ struct cpcap_uc_data *uc_data = file->private_data;
+
+ if ((buf != NULL) && (ppos != NULL) && (count >= 2)) {
+ data = kzalloc(count, GFP_KERNEL);
+
+ if (data != NULL) {
+ address = (unsigned short) (*ppos);
+ num_words = (unsigned short) (count >> 1);
+
+ retval = ram_read(uc_data, address, num_words, data);
+
+ if (retval)
+ goto err;
+
+ if (copy_to_user((void *)buf, (void *)data, count) == 0)
+ retval = count;
+ else
+ retval = -EFAULT;
+
+err:
+ kfree(data);
+ } else {
+ retval = -ENOMEM;
+ }
+ }
+
+ return retval;
+}
+
+static long fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int retval = -ENOTTY;
+ struct cpcap_uc_data *data = file->private_data;
+
+ switch (cmd) {
+ case CPCAP_IOCTL_UC_MACRO_START:
+ /* User space will only attempt to start the init macro if
+ * the ram load requests complete successfully. This is used
+ * as an indication that kernel requests to start macros can
+ * be allowed.
+ */
+ data->is_ready = 1;
+
+ retval = cpcap_uc_start(data->cpcap, (enum cpcap_macro)arg);
+
+ break;
+
+ case CPCAP_IOCTL_UC_MACRO_STOP:
+ retval = cpcap_uc_stop(data->cpcap, (enum cpcap_macro)arg);
+ break;
+
+ case CPCAP_IOCTL_UC_GET_VENDOR:
+ retval = copy_to_user((enum cpcap_vendor *)arg,
+ &(data->cpcap->vendor),
+ sizeof(enum cpcap_vendor));
+ break;
+
+ case CPCAP_IOCTL_UC_SET_TURBO_MODE:
+ if (arg != 0)
+ arg = 1;
+ retval = cpcap_regacc_write(data->cpcap, CPCAP_REG_UCTM,
+ (unsigned short)arg,
+ CPCAP_BIT_UCTM);
+ break;
+
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+static int fops_open(struct inode *inode, struct file *file)
+{
+ int retval = -ENOTTY;
+
+ if (cpcap_uc_info->is_supported)
+ retval = 0;
+
+ file->private_data = cpcap_uc_info;
+ dev_info(&cpcap_uc_info->cpcap->spi->dev, "CPCAP uC: open status:%d\n",
+ retval);
+
+ return retval;
+}
+
+int cpcap_uc_start(struct cpcap_device *cpcap, enum cpcap_macro macro)
+{
+ int retval = -EFAULT;
+ struct cpcap_uc_data *data = cpcap->ucdata;
+
+ if ((data->is_ready) &&
+ (macro > CPCAP_MACRO_USEROFF) && (macro < CPCAP_MACRO__END) &&
+ (data->uc_reset == 0)) {
+ if ((macro == CPCAP_MACRO_4) ||
+ ((cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (macro == CPCAP_MACRO_12)) ||
+ ((cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (macro == CPCAP_MACRO_15))) {
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_MI2,
+ (1 << macro),
+ (1 << macro));
+ } else {
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_MIM1,
+ 0, (1 << macro));
+ }
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_uc_start);
+
+int cpcap_uc_stop(struct cpcap_device *cpcap, enum cpcap_macro macro)
+{
+ int retval = -EFAULT;
+
+ if ((macro > CPCAP_MACRO_4) &&
+ (macro < CPCAP_MACRO__END)) {
+ if ((cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (macro == CPCAP_MACRO_12)) {
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_MI2,
+ 0, (1 << macro));
+ } else {
+ retval = cpcap_regacc_write(cpcap, CPCAP_REG_MIM1,
+ (1 << macro), (1 << macro));
+ }
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_uc_stop);
+
+unsigned char cpcap_uc_status(struct cpcap_device *cpcap,
+ enum cpcap_macro macro)
+{
+ unsigned char retval = 0;
+ unsigned short regval;
+
+ if (macro < CPCAP_MACRO__END) {
+ if ((macro <= CPCAP_MACRO_4) ||
+ ((cpcap->vendor == CPCAP_VENDOR_ST) &&
+ (macro == CPCAP_MACRO_12))) {
+ cpcap_regacc_read(cpcap, CPCAP_REG_MI2, &regval);
+
+ if (regval & (1 << macro))
+ retval = 1;
+ } else {
+ cpcap_regacc_read(cpcap, CPCAP_REG_MIM1, &regval);
+
+ if (!(regval & (1 << macro)))
+ retval = 1;
+ }
+ }
+
+ return retval;
+}
+EXPORT_SYMBOL_GPL(cpcap_uc_status);
+
+#ifdef CONFIG_PM_DBG_DRV
+int cpcap_uc_ram_write(struct cpcap_device *cpcap, unsigned short address,
+ unsigned short num_words, unsigned short *data)
+{
+ return ram_write(cpcap->ucdata, address, num_words, data);
+}
+
+int cpcap_uc_ram_read(struct cpcap_device *cpcap, unsigned short address,
+ unsigned short num_words, unsigned short *data)
+{
+ return ram_read(cpcap->ucdata, address, num_words, data);
+}
+#endif /* CONFIG_PM_DBG_DRV */
+
+static int fw_load(struct cpcap_uc_data *uc_data, struct device *dev)
+{
+ int err;
+ const struct ihex_binrec *rec;
+ const struct firmware *fw;
+ unsigned short *buf;
+ int i;
+ unsigned short num_bytes;
+ unsigned short num_words;
+ unsigned char odd_bytes;
+ struct cpcap_platform_data *data;
+
+ data = uc_data->cpcap->spi->controller_data;
+
+ if (!uc_data || !dev)
+ return -EINVAL;
+
+ if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST)
+ err = request_ihex_firmware(&fw, "cpcap/firmware_0_2x.fw", dev);
+ else
+ err = request_ihex_firmware(&fw, "cpcap/firmware_1_2x.fw", dev);
+
+ if (err) {
+ dev_err(dev, "Failed to load \"cpcap/firmware_%d_2x.fw\": %d\n",
+ uc_data->cpcap->vendor, err);
+ goto err;
+ }
+
+ for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) {
+ odd_bytes = 0;
+ num_bytes = be16_to_cpu(rec->len);
+
+ /* Since loader requires words, need even number of bytes. */
+ if (be16_to_cpu(rec->len) % 2) {
+ num_bytes++;
+ odd_bytes = 1;
+ }
+
+ num_words = num_bytes >> 1;
+ dev_dbg(dev, "Loading %d word(s) at 0x%04x\n",
+ num_words, be32_to_cpu(rec->addr));
+
+ buf = kzalloc(num_bytes, GFP_KERNEL);
+ if (buf) {
+ for (i = 0; i < num_words; i++) {
+ if (odd_bytes && (i == (num_words - 1)))
+ buf[i] = rec->data[i * 2];
+ else
+ buf[i] = ((uint16_t *)rec->data)[i];
+
+ buf[i] = be16_to_cpu(buf[i]);
+ }
+
+ err = ram_write(uc_data, be32_to_cpu(rec->addr),
+ num_words, buf);
+ kfree(buf);
+
+ if (err) {
+ dev_err(dev, "RAM write failed: %d\n", err);
+ break;
+ }
+ } else {
+ err = -ENOMEM;
+ dev_err(dev, "RAM write failed: %d\n", err);
+ break;
+ }
+ }
+
+ release_firmware(fw);
+
+ if (!err) {
+ uc_data->is_ready = 1;
+
+ if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST)
+ err = ram_write(uc_data, 0x012C, 1, &(data->is_umts));
+ else
+ err = ram_write(uc_data, 0x90F0, 1, &(data->is_umts));
+
+ dev_info(dev, "Loaded Sec SPI Init = %d: %d\n",
+ data->is_umts, err);
+
+ if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST)
+ err = ram_write(uc_data, HWCFG_ADDR_ST,
+ CPCAP_HWCFG_NUM, data->hwcfg);
+ else
+ err = ram_write(uc_data, HWCFG_ADDR_TI,
+ CPCAP_HWCFG_NUM, data->hwcfg);
+
+ dev_info(dev, "Loaded HWCFG data:");
+ for (i = 0; i < CPCAP_HWCFG_NUM; i++)
+ dev_info(dev, " 0x%04x", data->hwcfg[i]);
+ dev_info(dev, "result: %d\n", err);
+
+ err = cpcap_uc_start(uc_data->cpcap, CPCAP_MACRO_4);
+ dev_info(dev, "Started macro 4: %d\n", err);
+
+ err = cpcap_uc_start(uc_data->cpcap, CPCAP_MACRO_15);
+ dev_info(dev, "Started macro 15: %d\n", err);
+ }
+
+err:
+ return err;
+}
+
+static int cpcap_uc_probe(struct platform_device *pdev)
+{
+ int retval = 0;
+ struct cpcap_uc_data *data;
+
+ if (pdev->dev.platform_data == NULL) {
+ dev_err(&pdev->dev, "no platform_data\n");
+ return -EINVAL;
+ }
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->cpcap = pdev->dev.platform_data;
+ data->uc_reset = 0;
+ data->is_supported = 0;
+ data->req.address = 0;
+ data->req.data = NULL;
+ data->req.num_words = 0;
+
+ init_completion(&data->completion);
+ mutex_init(&data->lock);
+ platform_set_drvdata(pdev, data);
+ cpcap_uc_info = data;
+ data->cpcap->ucdata = data;
+
+ if (((data->cpcap->vendor == CPCAP_VENDOR_TI) &&
+ (data->cpcap->revision >= CPCAP_REVISION_2_0)) ||
+ (data->cpcap->vendor == CPCAP_VENDOR_ST)) {
+ retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_PRIMAC,
+ primac_handler, data);
+ if (retval)
+ goto err_free;
+
+ cpcap_irq_clear(data->cpcap, CPCAP_IRQ_UCRESET);
+ retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_UCRESET,
+ reset_handler, data);
+ if (retval)
+ goto err_primac;
+
+ retval = cpcap_irq_register(data->cpcap,
+ CPCAP_IRQ_UC_PRIRAMR,
+ ram_read_state_machine, data);
+ if (retval)
+ goto err_ucreset;
+
+ retval = cpcap_irq_register(data->cpcap,
+ CPCAP_IRQ_UC_PRIRAMW,
+ ram_write_state_machine, data);
+ if (retval)
+ goto err_priramr;
+
+ retval = misc_register(&uc_dev);
+ if (retval)
+ goto err_priramw;
+
+ data->is_supported = 1;
+
+ cpcap_regacc_write(data->cpcap, CPCAP_REG_MIM1, 0xFFFF,
+ 0xFFFF);
+
+ retval = fw_load(data, &pdev->dev);
+ if (retval)
+ goto err_fw;
+ } else
+ retval = -ENODEV;
+
+ return retval;
+
+err_fw:
+ misc_deregister(&uc_dev);
+err_priramw:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+err_priramr:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+err_ucreset:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UCRESET);
+err_primac:
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_PRIMAC);
+err_free:
+ kfree(data);
+
+ return retval;
+}
+
+static int __exit cpcap_uc_remove(struct platform_device *pdev)
+{
+ struct cpcap_uc_data *data = platform_get_drvdata(pdev);
+
+ misc_deregister(&uc_dev);
+
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_PRIMAC);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMW);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMR);
+ cpcap_irq_free(data->cpcap, CPCAP_IRQ_UCRESET);
+
+ kfree(data);
+ return 0;
+}
+
+
+static struct platform_driver cpcap_uc_driver = {
+ .probe = cpcap_uc_probe,
+ .remove = __exit_p(cpcap_uc_remove),
+ .driver = {
+ .name = "cpcap_uc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init cpcap_uc_init(void)
+{
+ return platform_driver_register(&cpcap_uc_driver);
+}
+subsys_initcall(cpcap_uc_init);
+
+static void __exit cpcap_uc_exit(void)
+{
+ platform_driver_unregister(&cpcap_uc_driver);
+}
+module_exit(cpcap_uc_exit);
+
+MODULE_ALIAS("platform:cpcap_uc");
+MODULE_DESCRIPTION("CPCAP uC driver");
+MODULE_AUTHOR("Motorola");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("cpcap/firmware_0_2x.fw");
+MODULE_FIRMWARE("cpcap/firmware_1_2x.fw");