/**
 *	scu_ipc_ioctl		-	control ioctls for the SCU
 *	@fp: file handle of the SCU device
 *	@cmd: ioctl coce
 *	@arg: pointer to user passed structure
 *
 *	Support the I/O and firmware flashing interfaces of the SCU
 */
static long scu_ipc_ioctl(struct file *fp, unsigned int cmd,
							unsigned long arg)
{
	int ret;
	struct scu_ipc_data  data;
	void __user *argp = (void __user *)arg;

	if (!capable(CAP_SYS_RAWIO))
		return -EPERM;

	if (cmd == INTE_SCU_IPC_FW_UPDATE) {
			u8 *fwbuf = kmalloc(MAX_FW_SIZE, GFP_KERNEL);
			if (fwbuf == NULL)
				return -ENOMEM;
			if (copy_from_user(fwbuf, (u8 *)arg, MAX_FW_SIZE)) {
				kfree(fwbuf);
				return -EFAULT;
			}
			ret = intel_scu_ipc_fw_update(fwbuf, MAX_FW_SIZE);
			kfree(fwbuf);
			return ret;
	} else {
		if (copy_from_user(&data, argp, sizeof(struct scu_ipc_data)))
			return -EFAULT;
		ret = scu_reg_access(cmd, &data);
		if (ret < 0)
			return ret;
		if (copy_to_user(argp, &data, sizeof(struct scu_ipc_data)))
			return -EFAULT;
		return 0;
	}
}
/**
 *	scu_ipc_ioctl		-	control ioctls for the SCU
 *	@fp: file handle of the SCU device
 *	@cmd: ioctl coce
 *	@arg: pointer to user passed structure
 *
 *	Support the I/O and firmware flashing interfaces of the SCU
 */
static long scu_ipc_ioctl(struct file *fp, unsigned int cmd,
                          unsigned long arg)
{
    int ret;
    struct scu_ipc_data  data;
    void __user *argp = (void __user *)arg;

    if (!capable(CAP_SYS_RAWIO))
        return -EPERM;

    if (copy_from_user(&data, argp, sizeof(struct scu_ipc_data)))
        return -EFAULT;
    ret = scu_reg_access(cmd, &data);
    if (ret < 0)
        return ret;
    if (copy_to_user(argp, &data, sizeof(struct scu_ipc_data)))
        return -EFAULT;
    return 0;
}
/**
 *	scu_ipc_ioctl		-	control ioctls for the SCU
 *	@fp: file handle of the SCU device
 *	@cmd: ioctl coce
 *	@arg: pointer to user passed structure
 *
 *	Support the I/O and firmware flashing interfaces of the SCU
 */
static long scu_ipc_ioctl(struct file *fp, unsigned int cmd,
							unsigned long arg)
{
	int ret = -EINVAL;
	struct scu_ipc_data  data;
	void __user *argp = (void __user *)arg;
	int platform;

	/* Only IOCTL cmd allowed to pass through without capability check */
	/* is getting fw version info, all others need to check to prevent */
	/* arbitrary access to all sort of bit of the hardware exposed here*/

	if (cmd != INTEL_SCU_IPC_FW_REVISION_GET && !capable(CAP_SYS_RAWIO))
		return -EPERM;

	platform = intel_mid_identify_cpu();
	switch (cmd) {
	case INTEL_SCU_IPC_READ_FBMODE_FROM_OSNIB:
	{
		u8 fb_mode;
		ret = intel_scu_ipc_read_osnib_fb_mode(&fb_mode);
		if (ret < 0) {
			pr_err("read fb_mode from ipc failed!!\n");
			return ret;
		}
		ret = copy_to_user(argp, &fb_mode, 1);
		break;
	}
	case INTEL_SCU_IPC_WRITE_FBMODE_TO_OSNIB:
	{
		u8 fb_mode;

		ret = copy_from_user(&fb_mode, (u8 *)arg, 1);
		if (ret < 0) {
			pr_err("copy fb_mode from user failed!!\n");
			return ret;
		}
		ret = intel_scu_ipc_write_osnib_fb_mode(fb_mode);
		break;
	}
	case INTEL_SCU_IPC_READ_BOS_FROM_OSNIB:
	{
		u8 resync_reason;
		ret = intel_scu_ipc_read_osnib_bos(&resync_reason);
		if (ret < 0)
			return ret;
		ret = copy_to_user(argp, &resync_reason, 1);
		break;
	}
	case INTEL_SCU_IPC_WRITE_BOS_TO_OSNIB:
	{
		u8 data;

		ret = copy_from_user(&data, (u8 *)arg, 1);
		if (ret < 0) {
			pr_err("copy from user failed!!\n");
			return ret;
		}
		ret = intel_scu_ipc_write_osnib_bos(data);
		break;
	}
	case INTEL_SCU_IPC_READ_RR_FROM_OSNIB:
	{
		u8 reboot_reason;
		ret = intel_scu_ipc_read_osnib_rr(&reboot_reason);
		if (ret < 0)
			return ret;
		ret = copy_to_user(argp, &reboot_reason, 1);
		break;
	}
	case INTEL_SCU_IPC_WRITE_RR_TO_OSNIB:
	{
		u8 data;

		ret = copy_from_user(&data, (u8 *)arg, 1);
		if (ret < 0) {
			pr_err("copy from user failed!!\n");
			return ret;
		}
		ret = intel_scu_ipc_write_osnib_rr(data);
		break;
	}
	case INTEL_SCU_IPC_WRITE_ALARM_FLAG_TO_OSNIB:
	{
		u8 flag, data;
		ret = copy_from_user(&flag, (u8 *)arg, 1);
		if (ret < 0) {
			pr_err("copy from user failed!!\n");
			return ret;
		}
		ret = intel_scu_ipc_read_osnib(&data, 1, OSNIB_ALARM_OFFSET);
		if (ret < 0)
			return ret;
		if (flag)
			data = data | 0x1; /* set alarm flag */
		else
			data = data & 0xFE; /* clear alarm flag */
		ret = intel_scu_ipc_write_osnib(&data, 1, OSNIB_ALARM_OFFSET);
		break;
	}
	case INTEL_SCU_IPC_READ_VBATTCRIT:
	{
		u32 value;

		pr_debug("cmd = INTEL_SCU_IPC_READ_VBATTCRIT");
		ret = intel_scu_ipc_read_mip((u8 *)&value, 4, 0x318, 1);
		if (ret < 0)
			return ret;
		pr_debug("VBATTCRIT VALUE = %x\n", value);
		ret = copy_to_user(argp, &value, 4);
		break;
	}
	case INTEL_SCU_IPC_FW_REVISION_GET:
	{
		struct scu_ipc_version version;

		if (copy_from_user(&version, argp, sizeof(u32)))
			return -EFAULT;

		if (version.count > 16)
			return -EINVAL;

		ret = intel_scu_ipc_command(IPCMSG_FW_REVISION, 0,
					NULL, 0, (u32 *)version.data, 4);
		if (ret < 0)
			return ret;

		if (copy_to_user(argp + sizeof(u32),
					version.data, version.count))
			ret = -EFAULT;
		break;
	}
	case INTEL_SCU_IPC_OSC_CLK_CNTL:
	{
		struct osc_clk_t osc_clk;

		if (copy_from_user(&osc_clk, argp, sizeof(struct osc_clk_t)))
			return -EFAULT;

		ret = intel_scu_ipc_osc_clk(osc_clk.id, osc_clk.khz);
		if (ret)
			pr_err("%s: failed to set osc clk\n", __func__);

		break;
	}
	case INTEL_SCU_IPC_READ_BYTE_IN_OSNIB:
	{
		struct osnib_data read_data;

		ret = copy_from_user(&read_data, (u8 *)arg,
					sizeof(struct scu_ipc_data));
		if (ret < 0) {
			pr_err("copy from user failed!!\n");
			return ret;
		}
		if ((read_data.offset-OSNIB_OFFSET) >= 32)
			return -EINVAL;
		ret = intel_scu_ipc_read_osnib_byte(read_data.offset,
							&read_data.data);
		if (ret < 0)
			return ret;
		ret = copy_to_user(argp, &read_data,
					sizeof(struct scu_ipc_data));
		break;
	}
	case INTEL_SCU_IPC_WRITE_BYTE_IN_OSNIB:
	{
		struct osnib_data write_data;

		ret = copy_from_user(&write_data, (u8 *)arg,
					sizeof(struct scu_ipc_data));
		if (ret < 0) {
			pr_err("copy from user failed!!\n");
			return ret;
		}
		if ((write_data.offset-OSNIB_OFFSET) >= 32)
			return -EINVAL;
		ret = intel_scu_ipc_write_osnib_byte(write_data.offset,
							write_data.data);
		if (ret < 0)
			return ret;
		break;
	}
	default:
		if (copy_from_user(&data, argp, sizeof(struct scu_ipc_data)))
			return -EFAULT;
		ret = scu_reg_access(cmd, &data);
		if (ret < 0)
			return ret;
		if (copy_to_user(argp, &data, sizeof(struct scu_ipc_data)))
			return -EFAULT;
		return 0;
	}

	return ret;
}