| /* |
| * 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"); |