/* * exynos-ssp.c - Samsung Secure Platform driver for the Exynos * * Copyright (C) 2019 Samsung Electronics * Keunyoung Park * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SSP_RET_OK 0 #define SSP_RET_FAIL -1 #define SSP_RET_BUSY 0x20010 /* defined at LDFW */ #define SSP_MAX_USER_CNT 100 #define SSP_MAX_USER_PATH_LEN 128 #define SSP_K250_STABLE_TIME_MS 20 #define SSP_V9RR_STABLE_TIME_MS 10 /* smc to call ldfw functions */ #define SMC_CMD_SSP (0xC2001040) #define SSP_CMD_BOOT (0x1) #define SSP_CMD_BACKUP (0x2) #define SSP_CMD_RESTORE (0x3) #define SSP_CMD_SELF_TEST (0x10) #define SSP_CMD_BOOT_INIT (0x11) #define SSP_CMD_BOOT_CHECK (0x12) #define SSP_CMD_RESTORE_INIT (0x31) #define SSP_CMD_RESTORE_CHECK (0x32) /* ioctl command for TAC */ #define SSP_IOCTL_MAGIC 'c' #define SSP_IOCTL_INIT _IO(SSP_IOCTL_MAGIC, 1) #define SSP_IOCTL_EXIT _IOWR(SSP_IOCTL_MAGIC, 2, uint64_t) #define SSP_IOCTL_TEST _IOWR(SSP_IOCTL_MAGIC, 3, uint64_t) #define SSP_IOCTL_DEBUG _IOWR(SSP_IOCTL_MAGIC, 4, uint64_t) #define ssp_err(dev, fmt, arg...) printk("[EXYNOS][CMSSP][ERROR] " fmt, ##arg) #define ssp_info(dev, fmt, arg...) printk("[EXYNOS][CMSSP][ INFO] " fmt, ##arg) /* SFR for ssp power control */ #define PMU_ALIVE_PA_BASE (0x15860000 + 0x2000) #define PMU_SSP_STATUS_VA_OFFSET (0x304) #define PMU_SSP_STATUS_MASK (1 << 0) void __iomem *pmu_va_base; spinlock_t ssp_lock; struct mutex ssp_ioctl_lock; static int ssp_power_count; static int ssp_idle_ip_index; extern struct exynos_chipid_info exynos_soc_info; struct ssp_device { struct device *dev; struct miscdevice misc_device; struct regulator *secure_nvm_ldo; unsigned long snvm_init_time; int gpio_se_sel; int gpio_snvm_reset; int is_k250; }; struct ssp_user { char path[SSP_MAX_USER_PATH_LEN]; unsigned long init_count; unsigned long init_time; unsigned long exit_count; unsigned long exit_time; unsigned long init_fail_count; unsigned long init_fail_time; unsigned long exit_fail_count; unsigned long exit_fail_time; struct ssp_user *next; }; struct ssp_user *head = NULL; static void exynos_ssp_print_user_list(struct device *dev) { struct ssp_user *ptr = head; int i = 0; ssp_info(dev, "====== Power control status =================" "=============================================\n"); ssp_info(dev, "No: %8s:\t%8s\t%8s\t%16s\t%16s\n", "Caller", "ON", "OFF", "ON-TIME", "OFF-TIME"); ssp_info(dev, "---------------------------------------------" "---------------------------------------------\n"); while (ptr != NULL) { i++; ssp_info(dev, "%2u:\t%8s: %s\n", i, "Path", ptr->path); ssp_info(dev, "%2u:\t%8s:\t%8u\t%8u\t%16u\t%16u\n", i, "Success", ptr->init_count, ptr->exit_count, ptr->init_time, ptr->exit_time); ssp_info(dev, "%2u:\t%8s:\t%8u\t%8u\t%16u\t%16u\n", i, "Fail", ptr->init_fail_count, ptr->exit_fail_count, ptr->init_fail_time, ptr->exit_fail_time); ptr = ptr->next; } ssp_info(dev, "---------------------------------------------" "---------------------------------------------\n"); } static int exynos_ssp_get_path(struct device *dev, struct task_struct *task, char *task_path) { int ret = SSP_RET_OK; char *buf; char *path; struct file *exe_file; buf = (char *)__get_free_page(GFP_KERNEL); if (!buf) { ssp_err(dev, "%s: fail to __get_free_page.\n", __func__); return -ENOMEM; } exe_file = get_task_exe_file(task); if (!exe_file) { ret = -ENOENT; ssp_err(dev, "%s: fail to get_task_exe_file.\n", __func__); goto end; } path = d_path(&exe_file->f_path, buf, PAGE_SIZE); if (IS_ERR(path)) { ret = PTR_ERR(path); ssp_err(dev, "%s: fail to d_path. ret = 0x%x\n", __func__, ret); goto end; } memset(task_path, 0, SSP_MAX_USER_PATH_LEN); strncpy(task_path, path, SSP_MAX_USER_PATH_LEN - 1); end: free_page((unsigned long)buf); return ret; } static struct ssp_user *exynos_ssp_find_user(char *path) { struct ssp_user *now = head; if (head == NULL) return NULL; while (strncmp(now->path, path, SSP_MAX_USER_PATH_LEN)) { if (now->next == NULL) return NULL; now = now->next; } return now; } static int exynos_ssp_powerctl_update(struct device *dev, bool power_on, bool pass) { int ret = SSP_RET_OK; char path[SSP_MAX_USER_PATH_LEN]; static int ssp_user_count; struct ssp_user *link = NULL; ret = exynos_ssp_get_path(dev, current, path); if (ret) { ssp_err(dev, "%s: fail to get path. ret = 0x%x\n", __func__, ret); return ret; } link = exynos_ssp_find_user(path); if (link == NULL) { if (++ssp_user_count >= SSP_MAX_USER_CNT) { ssp_err(dev, "%s: exceed max user count.\n", __func__); return -ENOMEM; } link = (struct ssp_user *)kzalloc(sizeof(struct ssp_user), GFP_KERNEL); if (!link) { ssp_err(dev, "%s: fail to kzalloc.\n", __func__); return -ENOMEM; } memcpy(link->path, path, sizeof(path)); link->init_count = 0; link->exit_count = 0; link->next = head; head = link; } if (power_on == 1 && pass == 1) { link->init_count++; link->init_time = ktime_get_boottime_ns() / NSEC_PER_USEC; } else if (power_on == 1 && pass == 0) { link->init_fail_count++; link->init_fail_time = ktime_get_boottime_ns() / NSEC_PER_USEC; } else if (power_on == 0 && pass == 1) { link->exit_count++; link->exit_time = ktime_get_boottime_ns() / NSEC_PER_USEC; } else if (power_on == 0 && pass == 0) { link->exit_fail_count++; link->exit_fail_time = ktime_get_boottime_ns() / NSEC_PER_USEC; } return ret; } static int exynos_cm_smc(uint64_t *arg0, uint64_t *arg1, uint64_t *arg2, uint64_t *arg3) { struct arm_smccc_res res; arm_smccc_smc(*arg0, *arg1, *arg2, *arg3, 0, 0, 0, 0, &res); *arg0 = res.a0; *arg1 = res.a1; *arg2 = res.a2; *arg3 = res.a3; return *arg0; } static int exynos_ssp_map_sfr(struct ssp_device *sspdev) { int ret = SSP_RET_OK; pmu_va_base = ioremap(PMU_ALIVE_PA_BASE, SZ_4K); if (!pmu_va_base) { ssp_err(sspdev->dev, "%s: fail to ioremap\n", __func__); ret = SSP_RET_FAIL; } return ret; } static bool exynos_ssp_check_power_status(struct device *dev) { unsigned int reg; if (!pmu_va_base) { ssp_err(dev, "%s: invalid pmu_va_base\n", __func__); return false; } reg = readl(pmu_va_base + PMU_SSP_STATUS_VA_OFFSET); if (reg & PMU_SSP_STATUS_MASK) return true; reg = readl(pmu_va_base + PMU_SSP_STATUS_VA_OFFSET); if (reg & PMU_SSP_STATUS_MASK) return true; ssp_err(dev, "%s: power hasn't been enabled: 0x%x\n", __func__, reg); ssp_err(dev, "%s: pmu address: 0x%x\n", __func__, pmu_va_base + PMU_SSP_STATUS_VA_OFFSET); return false; } static void exynos_ssp_pm_enable(struct ssp_device *sspdev) { pm_runtime_enable(sspdev->dev); ssp_info(sspdev->dev, "pm_runtime_enable\n"); } static int exynos_ssp_power_on(struct ssp_device *sspdev) { int ret = SSP_RET_OK; ret = pm_runtime_get_sync(sspdev->dev); if (ret != SSP_RET_OK) ssp_err(sspdev->dev, "%s: fail to pm_runtime_get_sync. ret = 0x%x\n", __func__, ret); else ssp_info(sspdev->dev, "pm_runtime_get_sync done\n"); if (exynos_ssp_check_power_status(sspdev->dev) == false) { ssp_err(sspdev->dev, "%s: ssp power status\n", __func__); return SSP_RET_FAIL; } return ret; } static int exynos_ssp_power_off(struct ssp_device *sspdev) { int ret = SSP_RET_OK; ret = pm_runtime_put_sync(sspdev->dev); if (ret != SSP_RET_OK) ssp_err(sspdev->dev, "%s: fail to pm_runtime_put_sync. ret = 0x%x\n", __func__, ret); else ssp_info(sspdev->dev, "pm_runtime_put_sync done\n"); return ret; } static int exynos_ssp_boot_init(struct device *dev) { int ret = SSP_RET_OK; uint64_t reg0; uint64_t reg1; uint64_t reg2; uint64_t reg3; uint64_t count; unsigned long flag; ssp_info(dev, "ssp boot start\n"); exynos_update_ip_idle_status(ssp_idle_ip_index, 0); count = 0; do { count++; reg0 = SMC_CMD_SSP; reg1 = SSP_CMD_BOOT_INIT; reg2 = 0; reg3 = 0; spin_lock_irqsave(&ssp_lock, flag); ret = exynos_cm_smc(®0, ®1, ®2, ®3); spin_unlock_irqrestore(&ssp_lock, flag); if (ret == SSP_RET_BUSY) usleep_range(500, 1000); } while (ret == SSP_RET_BUSY); if (ret != SSP_RET_OK) ssp_err(dev, "%s: fail to boot at ldfw. ret = 0x%x\n", __func__, ret); else ssp_info(dev, "ssp boot done: %d\n", count); return ret; } static int exynos_ssp_boot_check(struct device *dev) { int ret = SSP_RET_OK; uint64_t reg0; uint64_t reg1; uint64_t reg2; uint64_t reg3; uint64_t count; unsigned long flag; ssp_info(dev, "ssp boot check start\n"); count = 0; do { count++; reg0 = SMC_CMD_SSP; reg1 = SSP_CMD_BOOT_CHECK; reg2 = 0; reg3 = 0; spin_lock_irqsave(&ssp_lock, flag); ret = exynos_cm_smc(®0, ®1, ®2, ®3); spin_unlock_irqrestore(&ssp_lock, flag); if (ret == SSP_RET_BUSY) usleep_range(500, 1000); } while (ret == SSP_RET_BUSY); exynos_update_ip_idle_status(ssp_idle_ip_index, 1); if (ret != SSP_RET_OK) ssp_err(dev, "%s: fail to boot check at ldfw. ret = 0x%x\n", __func__, ret); else ssp_info(dev, "ssp boot check done: %d\n", count); return ret; } static int exynos_ssp_backup(struct device *dev) { int ret = SSP_RET_OK; uint64_t reg0; uint64_t reg1; uint64_t reg2; uint64_t reg3; unsigned long flag; ssp_info(dev, "ssp backup start\n"); reg0 = SMC_CMD_SSP; reg1 = SSP_CMD_BACKUP; reg2 = 0; reg3 = 0; exynos_update_ip_idle_status(ssp_idle_ip_index, 0); spin_lock_irqsave(&ssp_lock, flag); ret = exynos_cm_smc(®0, ®1, ®2, ®3); spin_unlock_irqrestore(&ssp_lock, flag); exynos_update_ip_idle_status(ssp_idle_ip_index, 1); if (ret != SSP_RET_OK) ssp_err(dev, "%s: fail to backup at ldfw. ret = 0x%x\n", __func__, ret); else ssp_info(dev, "ssp backup done\n"); return ret; } static int exynos_ssp_restore_init(struct device *dev) { int ret = SSP_RET_OK; uint64_t reg0; uint64_t reg1; uint64_t reg2; uint64_t reg3; unsigned long flag; ssp_info(dev, "ssp restore init start\n"); reg0 = SMC_CMD_SSP; reg1 = SSP_CMD_RESTORE_INIT; reg2 = 0; reg3 = 0; exynos_update_ip_idle_status(ssp_idle_ip_index, 0); spin_lock_irqsave(&ssp_lock, flag); ret = exynos_cm_smc(®0, ®1, ®2, ®3); spin_unlock_irqrestore(&ssp_lock, flag); if (ret != SSP_RET_OK) ssp_err(dev, "%s: fail to restore check at ldfw. ret = 0x%x\n", __func__, ret); else ssp_info(dev, "ssp restore init done\n"); return ret; } static int exynos_ssp_restore_check(struct device *dev) { int ret = SSP_RET_OK; uint64_t reg0; uint64_t reg1; uint64_t reg2; uint64_t reg3; unsigned long flag; ssp_info(dev, "ssp restore check start\n"); reg0 = SMC_CMD_SSP; reg1 = SSP_CMD_RESTORE_CHECK; reg2 = 0; reg3 = 0; spin_lock_irqsave(&ssp_lock, flag); ret = exynos_cm_smc(®0, ®1, ®2, ®3); spin_unlock_irqrestore(&ssp_lock, flag); exynos_update_ip_idle_status(ssp_idle_ip_index, 1); if (ret != SSP_RET_OK) ssp_err(dev, "%s: fail to restore check at ldfw. ret = 0x%x\n", __func__, ret); else ssp_info(dev, "ssp restore check done\n"); return ret; } static int exynos_ssp_self_test(struct device *dev, uint64_t test_mode) { int ret = SSP_RET_OK; uint64_t reg0; uint64_t reg1; uint64_t reg2; uint64_t reg3; unsigned long flag; ssp_info(dev, "call ssp function: %d\n", (int)test_mode); reg0 = SMC_CMD_SSP; reg1 = SSP_CMD_SELF_TEST; reg2 = test_mode; reg3 = 0; spin_lock_irqsave(&ssp_lock, flag); ret = exynos_cm_smc(®0, ®1, ®2, ®3); spin_unlock_irqrestore(&ssp_lock, flag); ssp_info(dev, "return from ldfw: 0x%x\n", ret); return ret; } static int exynos_se_power_on_phase1(struct ssp_device *dev) { int ret = SSP_RET_OK; ssp_info(dev, "Secure NVM on\n"); if (IS_ERR(dev->secure_nvm_ldo) || regulator_enable(dev->secure_nvm_ldo) < 0) { ssp_err(dev, "%s - failed to enable LDO for SE\n", __func__); return -ENODEV; } if (dev->is_k250) { if (dev->gpio_snvm_reset >= 0) { /* GPC5.1 set to 1 if controllable */ gpio_set_value(dev->gpio_snvm_reset, 1); } } dev->snvm_init_time = ktime_get_boottime_ns(); return ret; } static int exynos_se_power_on_phase2(struct ssp_device *dev) { int ret = SSP_RET_OK; unsigned long delay; /* delay for chip stabilization */ delay = (ktime_get_boottime_ns() - dev->snvm_init_time) / NSEC_PER_MSEC; if (dev->is_k250) { if (delay < SSP_K250_STABLE_TIME_MS) { mdelay(SSP_K250_STABLE_TIME_MS - delay); } if (dev->gpio_se_sel >= 0) { /* GPC1.0 set to 1 if controllable */ gpio_set_value(dev->gpio_se_sel, 1); } } else { if (delay < SSP_V9RR_STABLE_TIME_MS) { mdelay(SSP_V9RR_STABLE_TIME_MS - delay); } if (dev->gpio_se_sel >= 0) { /* GPC1.0 set to 0 if controllable */ gpio_set_value(dev->gpio_se_sel, 0); } } ssp_info(dev, "Secure NVM on\n"); return ret; } static int exynos_se_power_off(struct ssp_device *dev) { int ret = SSP_RET_OK; ssp_info(dev, "Secure NVM off\n"); if (dev->is_k250) { if (dev->gpio_se_sel >= 0) { /* GPC1.0 set to 1 if controllable */ gpio_set_value(dev->gpio_se_sel, 1); } } if (IS_ERR(dev->secure_nvm_ldo) || regulator_disable(dev->secure_nvm_ldo) < 0) { ssp_err(dev, "%s - failed to disable LDO for SE\n", __func__); return -ENODEV; } if (dev->is_k250) { mdelay(SSP_K250_STABLE_TIME_MS); } else { mdelay(SSP_V9RR_STABLE_TIME_MS); } ssp_info(dev, "Secure NVM off done\n"); return ret; } static int exynos_ssp_enable(struct ssp_device *sspdev) { int ret = SSP_RET_OK; static int ssp_boot_flag; ++ssp_power_count; if (ssp_power_count == 1) { pm_stay_awake(sspdev->dev); if (!ssp_boot_flag) { ret = exynos_se_power_on_phase1(sspdev); if (unlikely(ret)) goto ERR_OUT1; /* step1: write FW base & size to mailbox for boot */ ret = exynos_ssp_boot_init(sspdev->dev); if (unlikely(ret)) goto ERR_OUT2; /* step2: power on */ ret = exynos_ssp_power_on(sspdev); if (unlikely(ret)) goto ERR_OUT2; /* step3: check booting status */ ret = exynos_ssp_boot_check(sspdev->dev); if (unlikely(ret)) goto ERR_OUT3; ret = exynos_se_power_on_phase2(sspdev); if (unlikely(ret)) goto ERR_OUT3; ssp_boot_flag = 1; } else { ret = exynos_se_power_on_phase1(sspdev); if (unlikely(ret)) goto ERR_OUT1; /* step1: write FW base & size to mailbox for boot */ ret = exynos_ssp_restore_init(sspdev->dev); if (unlikely(ret)) goto ERR_OUT2; /* step2: power on */ ret = exynos_ssp_power_on(sspdev); if (unlikely(ret)) goto ERR_OUT2; /* step3: check booting status */ ret = exynos_ssp_restore_check(sspdev->dev); if (unlikely(ret)) goto ERR_OUT3; ret = exynos_se_power_on_phase2(sspdev); if (unlikely(ret)) goto ERR_OUT3; } } exynos_ssp_powerctl_update(sspdev->dev, 1, 1); ssp_info(sspdev->dev, "ssp enable: count: %d\n", ssp_power_count); return ret; ERR_OUT3: exynos_ssp_power_off(sspdev); ERR_OUT2: exynos_se_power_off(sspdev); ERR_OUT1: exynos_ssp_powerctl_update(sspdev->dev, 1, 0); pm_relax(sspdev->dev); --ssp_power_count; return ret; } static int exynos_ssp_disable(struct ssp_device *sspdev) { int ret = SSP_RET_OK; if (ssp_power_count <= 0) { ssp_err(sspdev->dev, "%s ssp has already been disabled\n", __func__); ssp_err(sspdev->dev, "ssp disable: count: %d\n", ssp_power_count); return ret; } --ssp_power_count; if (ssp_power_count == 0) { ret = exynos_ssp_backup(sspdev->dev); if (unlikely(ret)) goto ERR_OUT1; ret = exynos_ssp_power_off(sspdev); if (unlikely(ret)) goto ERR_OUT2; ret = exynos_se_power_off(sspdev); if (unlikely(ret)) goto ERR_OUT3; /* keep the wake-up lock when above two functions fail */ /* for debugging purpose */ pm_relax(sspdev->dev); } exynos_ssp_powerctl_update(sspdev->dev, 0, 1); ssp_info(sspdev->dev, "ssp disable: count: %d\n", ssp_power_count); return ret; ERR_OUT1: exynos_ssp_power_off(sspdev); ERR_OUT2: exynos_se_power_off(sspdev); ERR_OUT3: exynos_ssp_powerctl_update(sspdev->dev, 0, 0); pm_relax(sspdev->dev); return ret; } static long ssp_ioctl(struct file *filp, unsigned int cmd, unsigned long __arg) { int ret = SSP_RET_OK; uint64_t test_mode; char path[SSP_MAX_USER_PATH_LEN]; struct ssp_device *sspdev = filp->private_data; void __user *arg = (void __user *)__arg; ret = exynos_ssp_get_path(sspdev->dev, current, path); if (ret) { ssp_err(sspdev->dev, "%s: fail to get user path. ret = 0x%x\n", __func__, ret); return ret; } ssp_info(sspdev->dev, "requested by %s\n", path); mutex_lock(&ssp_ioctl_lock); switch (cmd) { case SSP_IOCTL_INIT: ret = exynos_ssp_enable(sspdev); if (ret != SSP_RET_OK) exynos_ssp_print_user_list(sspdev->dev); break; case SSP_IOCTL_EXIT: ret = exynos_ssp_disable(sspdev); if (ret != SSP_RET_OK) exynos_ssp_print_user_list(sspdev->dev); break; case SSP_IOCTL_TEST: ret = get_user(test_mode, (uint64_t __user *)arg); if (unlikely(ret)) { ssp_err(sspdev->dev, "%s: fail to get_user. ret = 0x%x\n", __func__, ret); break; } ret = exynos_ssp_self_test(sspdev->dev, test_mode); break; case SSP_IOCTL_DEBUG: ssp_info(sspdev->dev, "power-on count: %d\n", ssp_power_count); exynos_ssp_print_user_list(sspdev->dev); break; default: ssp_err(sspdev->dev, "%s: invalid ioctl cmd: 0x%x\n", __func__, cmd); ret = -EPERM; break; } mutex_unlock(&ssp_ioctl_lock); return ret; } static int ssp_open(struct inode *inode, struct file *file) { struct miscdevice *misc = file->private_data; struct ssp_device *sspdev = container_of(misc, struct ssp_device, misc_device); file->private_data = sspdev; ssp_info(sspdev->dev, "driver open is done\n"); return 0; } static const struct file_operations ssp_fops = { .owner = THIS_MODULE, .open = ssp_open, .unlocked_ioctl = ssp_ioctl, .compat_ioctl = ssp_ioctl, }; static int exynos_ssp_probe(struct platform_device *pdev) { int ret = SSP_RET_OK; struct ssp_device *sspdev = NULL; sspdev = kzalloc(sizeof(struct ssp_device), GFP_KERNEL); if (!sspdev) { ssp_err(&pdev->dev, "%s: fail to kzalloc.\n", __func__); return -ENOMEM; } platform_set_drvdata(pdev, sspdev); sspdev->dev = &pdev->dev; spin_lock_init(&ssp_lock); mutex_init(&ssp_ioctl_lock); exynos_ssp_map_sfr(sspdev); /* get regulator */ sspdev->secure_nvm_ldo = devm_regulator_get_optional(&(pdev->dev), "vdd_se"); if (IS_ERR(sspdev->secure_nvm_ldo)) { ssp_err(sspdev->dev, "%s: failed to get regulator", __func__); ret = -ENODEV; goto err; } /* get SE_SEL GPIO number */ sspdev->gpio_se_sel = of_get_named_gpio(pdev->dev.of_node, "se_sel-gpios", 0); /* get SNVM_RESET GPIO number */ sspdev->gpio_snvm_reset = of_get_named_gpio(pdev->dev.of_node, "snvm_reset-gpios", 0); if (sspdev->gpio_se_sel < 0 || sspdev->gpio_snvm_reset < 0) { /* If GPIO cannot be controlled, default is K250AF. */ ssp_info(sspdev->dev, "Default SE is K250AF.", __func__); sspdev->is_k250 = 1; } else { /* request GPIO */ ssp_info(sspdev->dev, "se_sel gpio : %d\n", sspdev->gpio_se_sel); if (devm_gpio_request(&pdev->dev, sspdev->gpio_se_sel, "se_sel") < 0) { ssp_err(sspdev->dev, "%s: failed to request se_sel gpio", __func__); ret = -ENODEV; goto err; } /* change GPIO direction to output */ if (gpio_direction_output(sspdev->gpio_se_sel, 0) < 0) { ssp_err(sspdev->dev, "%s: failed to set gpio_output for SE_SEL\n", __func__); ret = -ENODEV; goto err; } /* request GPIO */ ssp_info(sspdev->dev, "snvm_reset gpio : %d\n", sspdev->gpio_snvm_reset); if (devm_gpio_request(&pdev->dev, sspdev->gpio_snvm_reset, "snvm_reset") < 0) { ssp_err(sspdev->dev, "%s: failed to request snvm_reset gpio", __func__); ret = -ENODEV; goto err; } /* change GPIO direction to output */ if (gpio_direction_output(sspdev->gpio_snvm_reset, 0) < 0) { ssp_err(sspdev->dev, "%s: failed to set gpio_output for reset\n", __func__); ret = -ENODEV; goto err; } /* set SE to K250 */ sspdev->is_k250 = 1; } /* enable runtime PM */ exynos_ssp_pm_enable(sspdev); ssp_idle_ip_index = exynos_get_idle_ip_index(dev_name(sspdev->dev), 1); exynos_update_ip_idle_status(ssp_idle_ip_index, 1); /* set misc driver */ memset((void *)&sspdev->misc_device, 0, sizeof(struct miscdevice)); sspdev->misc_device.minor = MISC_DYNAMIC_MINOR; sspdev->misc_device.name = "ssp"; sspdev->misc_device.fops = &ssp_fops; ret = misc_register(&sspdev->misc_device); if (ret) { ssp_err(sspdev->dev, "%s: fail to misc_register. ret = %d\n", __func__, ret); ret = -ENOMEM; goto err; } ret = device_init_wakeup(sspdev->dev, true); if (ret) { ssp_err(sspdev->dev, "%s: fail to init wakeup. ret = %d\n", __func__, ret); goto err; } return SSP_RET_OK; err: kfree(sspdev); return ret; } static int exynos_ssp_remove(struct platform_device *pdev) { struct ssp_device *sspdev = platform_get_drvdata(pdev); misc_deregister(&sspdev->misc_device); kfree(sspdev); return 0; } #if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME) static int exynos_ssp_suspend(struct device *dev) { int ret = SSP_RET_OK; /* Do nothing */ return ret; } static int exynos_ssp_resume(struct device *dev) { int ret = SSP_RET_OK; /* Do nothing */ return ret; } #endif static struct dev_pm_ops exynos_ssp_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(exynos_ssp_suspend, exynos_ssp_resume) }; #ifdef CONFIG_OF static const struct of_device_id exynos_ssp_match[] = { { .compatible = "samsung,exynos-ssp", }, {}, }; #endif static struct platform_driver exynos_ssp_driver = { .probe = exynos_ssp_probe, .remove = exynos_ssp_remove, .driver = { .name = "ssp", .owner = THIS_MODULE, .pm = &exynos_ssp_pm_ops, #ifdef CONFIG_OF .of_match_table = exynos_ssp_match, #endif }, }; static int __init exynos_ssp_init(void) { int ret = SSP_RET_OK; ret = platform_driver_register(&exynos_ssp_driver); if (ret) { pr_err("[Exynos][CMSSP] %s: fail to register driver. ret = 0x%x\n", __func__, ret); return ret; } pr_info("[Exynos][CMSSP] driver, (c) 2019 Samsung Electronics\n"); return 0; } static void __exit exynos_ssp_exit(void) { platform_driver_unregister(&exynos_ssp_driver); } module_init(exynos_ssp_init); module_exit(exynos_ssp_exit); MODULE_DESCRIPTION("EXYNOS Samsung Secure Platform driver"); MODULE_AUTHOR("Keunyoung Park "); MODULE_LICENSE("GPL");