kernel_samsung_a53x/drivers/soc/samsung/exynos-ssp.c
2024-06-15 16:02:09 -03:00

1005 lines
23 KiB
C
Executable file

/*
* exynos-ssp.c - Samsung Secure Platform driver for the Exynos
*
* Copyright (C) 2019 Samsung Electronics
* Keunyoung Park <keun0.park@samsung.com>
*
* 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/pm_wakeup.h>
#include <linux/smc.h>
#include <linux/miscdevice.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/debugfs.h>
#include <linux/timer.h>
#include <linux/mm.h>
#include <linux/device.h>
#include <soc/samsung/exynos-smc.h>
#include <soc/samsung/exynos-pd.h>
#include <linux/soc/samsung/exynos-soc.h>
#include <linux/regulator/consumer.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#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(&reg0, &reg1, &reg2, &reg3);
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(&reg0, &reg1, &reg2, &reg3);
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(&reg0, &reg1, &reg2, &reg3);
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(&reg0, &reg1, &reg2, &reg3);
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(&reg0, &reg1, &reg2, &reg3);
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(&reg0, &reg1, &reg2, &reg3);
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 <keun0.park@samsung.com>");
MODULE_LICENSE("GPL");