| /** |
| * Copyright (c) 2010 Trusted Logic S.A. |
| * 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 <asm/atomic.h> |
| #include <linux/uaccess.h> |
| #include <linux/module.h> |
| #include <linux/errno.h> |
| #include <linux/mm.h> |
| #include <linux/page-flags.h> |
| #include <linux/pm.h> |
| #include <linux/sysdev.h> |
| #include <linux/vmalloc.h> |
| #include <linux/signal.h> |
| #ifdef CONFIG_ANDROID |
| #include <linux/device.h> |
| #endif |
| #include <linux/init.h> |
| #include <linux/bootmem.h> |
| |
| #include "tf_protocol.h" |
| #include "tf_defs.h" |
| #include "tf_util.h" |
| #include "tf_conn.h" |
| #include "tf_comm.h" |
| #include "tf_zebra.h" |
| |
| #include "s_version.h" |
| |
| #define TF_PA_CTRL_START 0x1 |
| #define TF_PA_CTRL_STOP 0x2 |
| |
| #ifdef CONFIG_ANDROID |
| static struct class *tf_ctrl_class; |
| #endif |
| |
| #define TF_DEVICE_CTRL_BASE_NAME "tf_ctrl" |
| |
| struct tf_pa_ctrl { |
| u32 nPACommand; |
| |
| u32 pa_size; |
| u8 *pa_buffer; |
| |
| u32 conf_size; |
| u8 *conf_buffer; |
| }; |
| |
| static int tf_ctrl_check_omap_type(void) |
| { |
| /* No need to do anything on a GP device */ |
| switch (omap_type()) { |
| case OMAP2_DEVICE_TYPE_GP: |
| dprintk(KERN_INFO "SMC: Running on a GP device\n"); |
| return 0; |
| |
| case OMAP2_DEVICE_TYPE_EMU: |
| case OMAP2_DEVICE_TYPE_SEC: |
| /*case OMAP2_DEVICE_TYPE_TEST:*/ |
| dprintk(KERN_INFO "SMC: Running on a EMU or HS device\n"); |
| return 1; |
| |
| default: |
| printk(KERN_ERR "SMC: unknown omap type %x\n", omap_type()); |
| return -EFAULT; |
| } |
| } |
| |
| #define IOCTL_TF_PA_CTRL _IOWR('z', 0xFF, struct tf_pa_ctrl) |
| |
| static long tf_ctrl_device_ioctl(struct file *file, unsigned int ioctl_num, |
| unsigned long ioctl_param) |
| { |
| int result = S_SUCCESS; |
| struct tf_pa_ctrl pa_ctrl; |
| u8 *pa_buffer = NULL; |
| u8 *conf_buffer = NULL; |
| struct tf_device *dev = tf_get_device(); |
| |
| dprintk(KERN_INFO "tf_ctrl_device_ioctl(%p, %u, %p)\n", |
| file, ioctl_num, (void *) ioctl_param); |
| |
| mutex_lock(&dev->dev_mutex); |
| |
| if (ioctl_num != IOCTL_TF_PA_CTRL) { |
| dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " |
| "ioctl number is invalid (%p)\n", |
| file, (void *)ioctl_num); |
| |
| result = -EFAULT; |
| goto exit; |
| } |
| |
| if ((ioctl_param & 0x3) != 0) { |
| dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " |
| "ioctl command message pointer is not word " |
| "aligned (%p)\n", |
| file, (void *)ioctl_param); |
| |
| result = -EFAULT; |
| goto exit; |
| } |
| |
| if (copy_from_user(&pa_ctrl, (struct tf_pa_ctrl *)ioctl_param, |
| sizeof(struct tf_pa_ctrl))) { |
| dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " |
| "cannot access ioctl parameter (%p)\n", |
| file, (void *)ioctl_param); |
| |
| result = -EFAULT; |
| goto exit; |
| } |
| |
| switch (pa_ctrl.nPACommand) { |
| case TF_PA_CTRL_START: |
| dprintk(KERN_INFO "tf_ctrl_device_ioctl(%p): " |
| "Start the SMC PA (%d bytes) with conf (%d bytes)\n", |
| file, pa_ctrl.pa_size, pa_ctrl.conf_size); |
| |
| pa_buffer = (u8 *) internal_kmalloc(pa_ctrl.pa_size, |
| GFP_KERNEL); |
| if (pa_buffer == NULL) { |
| dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " |
| "Out of memory for PA buffer\n", file); |
| |
| result = -ENOMEM; |
| goto exit; |
| } |
| |
| if (copy_from_user( |
| pa_buffer, pa_ctrl.pa_buffer, pa_ctrl.pa_size)) { |
| dprintk(KERN_ERR "tf_ctrl_device_ioctl(%p): " |
| "Cannot access PA buffer (%p)\n", |
| file, (void *) pa_ctrl.pa_buffer); |
| |
| internal_kfree(pa_buffer); |
| |
| result = -EFAULT; |
| goto exit; |
| } |
| |
| if (pa_ctrl.conf_size > 0) { |
| conf_buffer = (u8 *) internal_kmalloc( |
| pa_ctrl.conf_size, GFP_KERNEL); |
| if (conf_buffer == NULL) { |
| internal_kfree(pa_buffer); |
| |
| result = -ENOMEM; |
| goto exit; |
| } |
| |
| if (copy_from_user(conf_buffer, |
| pa_ctrl.conf_buffer, pa_ctrl.conf_size)) { |
| internal_kfree(pa_buffer); |
| internal_kfree(conf_buffer); |
| |
| result = -EFAULT; |
| goto exit; |
| } |
| } |
| |
| if (dev->workspace_addr == 0) { |
| result = -ENOMEM; |
| goto exit; |
| } |
| |
| result = tf_start(&dev->sm, |
| dev->workspace_addr, |
| dev->workspace_size, |
| pa_buffer, |
| pa_ctrl.pa_size, |
| conf_buffer, |
| pa_ctrl.conf_size); |
| if (result) |
| dprintk(KERN_ERR "SMC: start failed\n"); |
| else |
| dprintk(KERN_INFO "SMC: started\n"); |
| |
| internal_kfree(pa_buffer); |
| internal_kfree(conf_buffer); |
| break; |
| |
| case TF_PA_CTRL_STOP: |
| dprintk(KERN_INFO "tf_ctrl_device_ioctl(%p): " |
| "Stop the SMC PA\n", file); |
| |
| result = tf_power_management(&dev->sm, |
| TF_POWER_OPERATION_SHUTDOWN); |
| if (result) |
| dprintk(KERN_WARNING "SMC: stop failed [0x%x]\n", |
| result); |
| else |
| dprintk(KERN_INFO "SMC: stopped\n"); |
| break; |
| |
| default: |
| result = -EOPNOTSUPP; |
| break; |
| } |
| |
| exit: |
| mutex_unlock(&dev->dev_mutex); |
| return result; |
| } |
| |
| /*----------------------------------------------------------------------------*/ |
| |
| static int tf_ctrl_device_open(struct inode *inode, struct file *file) |
| { |
| int error; |
| |
| dprintk(KERN_INFO "tf_ctrl_device_open(%u:%u, %p)\n", |
| imajor(inode), iminor(inode), file); |
| |
| /* Dummy lseek for non-seekable driver */ |
| error = nonseekable_open(inode, file); |
| if (error != 0) { |
| dprintk(KERN_ERR "tf_ctrl_device_open(%p): " |
| "nonseekable_open failed (error %d)!\n", |
| file, error); |
| goto error; |
| } |
| |
| #ifndef CONFIG_ANDROID |
| /* |
| * Check file flags. We only autthorize the O_RDWR access |
| */ |
| if (file->f_flags != O_RDWR) { |
| dprintk(KERN_ERR "tf_ctrl_device_open(%p): " |
| "Invalid access mode %u\n", |
| file, file->f_flags); |
| error = -EACCES; |
| goto error; |
| } |
| #endif |
| |
| error = tf_ctrl_check_omap_type(); |
| if (error <= 0) |
| return error; |
| |
| /* |
| * Successful completion. |
| */ |
| |
| dprintk(KERN_INFO "tf_ctrl_device_open(%p): Success\n", file); |
| return 0; |
| |
| /* |
| * Error handling. |
| */ |
| error: |
| dprintk(KERN_INFO "tf_ctrl_device_open(%p): Failure (error %d)\n", |
| file, error); |
| return error; |
| } |
| |
| static const struct file_operations g_tf_ctrl_device_file_ops = { |
| .owner = THIS_MODULE, |
| .open = tf_ctrl_device_open, |
| .unlocked_ioctl = tf_ctrl_device_ioctl, |
| .llseek = no_llseek, |
| }; |
| |
| int __init tf_ctrl_device_register(void) |
| { |
| int error; |
| struct tf_device *dev = tf_get_device(); |
| |
| cdev_init(&dev->cdev_ctrl, &g_tf_ctrl_device_file_ops); |
| dev->cdev_ctrl.owner = THIS_MODULE; |
| |
| error = register_chrdev_region(dev->dev_number + 1, 1, |
| TF_DEVICE_CTRL_BASE_NAME); |
| if (error) |
| return error; |
| |
| error = cdev_add(&dev->cdev_ctrl, |
| dev->dev_number + 1, 1); |
| if (error) { |
| cdev_del(&(dev->cdev_ctrl)); |
| unregister_chrdev_region(dev->dev_number + 1, 1); |
| return error; |
| } |
| |
| #ifdef CONFIG_ANDROID |
| tf_ctrl_class = class_create(THIS_MODULE, TF_DEVICE_CTRL_BASE_NAME); |
| device_create(tf_ctrl_class, NULL, |
| dev->dev_number + 1, |
| NULL, TF_DEVICE_CTRL_BASE_NAME); |
| #endif |
| |
| mutex_init(&dev->dev_mutex); |
| |
| return error; |
| } |
| |
| static int __initdata smc_mem; |
| static int __initdata smc_address; |
| |
| void __init tf_allocate_workspace(void) |
| { |
| struct tf_device *dev = tf_get_device(); |
| |
| if (tf_ctrl_check_omap_type() <= 0) |
| return; |
| |
| dev->workspace_size = smc_mem; |
| if (dev->workspace_size < 3*SZ_1M) |
| dev->workspace_size = 3*SZ_1M; |
| |
| if (smc_address == 0) |
| #if 0 |
| dev->workspace_addr = (u32) __pa(__alloc_bootmem( |
| dev->workspace_size, SZ_1M, __pa(MAX_DMA_ADDRESS))); |
| #else |
| dev->workspace_addr = (u32) 0xBFD00000; |
| #endif |
| else |
| dev->workspace_addr = smc_address; |
| |
| pr_info("SMC: Allocated workspace of 0x%x Bytes at (0x%x)\n", |
| dev->workspace_size, |
| dev->workspace_addr); |
| } |
| |
| static int __init tf_mem_setup(char *str) |
| { |
| smc_mem = memparse(str, &str); |
| if (*str == '@') { |
| str += 1; |
| get_option(&str, &smc_address); |
| } |
| return 0; |
| } |
| |
| early_param("smc_mem", tf_mem_setup); |