| /* |
| * Copyright (C) 2016 Samsung Electronics Co., Ltd. |
| * |
| * 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/module.h> |
| #include <linux/of.h> |
| #include <crypto/smu.h> |
| |
| #include "ufshcd.h" |
| #include "ufs-exynos.h" |
| #include "ufs-exynos-smu.h" |
| |
| #define EXYNOS_UFS_SMU_LABEL "ufs-exynos-smu" |
| |
| static struct exynos_smu_variant_ops *exynos_ufs_smu_get_vops(struct device *dev) |
| { |
| struct exynos_smu_variant_ops *smu_vops = NULL; |
| struct device_node *node; |
| |
| node = of_parse_phandle(dev->of_node, EXYNOS_UFS_SMU_LABEL, 0); |
| if (!node) { |
| dev_err(dev, "%s: exynos-ufs-smu property not specified\n", |
| __func__); |
| goto err; |
| } |
| |
| smu_vops = exynos_smu_get_variant_ops(node); |
| if (!smu_vops) |
| dev_err(dev, "%s: Fail to get smu_vops\n", __func__); |
| |
| of_node_put(node); |
| err: |
| return smu_vops; |
| } |
| |
| static struct platform_device *exynos_ufs_smu_get_pdevice(struct device *dev) |
| { |
| struct device_node *node; |
| struct platform_device *smu_pdev = NULL; |
| |
| node = of_parse_phandle(dev->of_node, EXYNOS_UFS_SMU_LABEL, 0); |
| if (!node) { |
| dev_err(dev, "%s: exynos-ufs-smu property not specified\n", |
| __func__); |
| goto err; |
| } |
| |
| smu_pdev = exynos_smu_get_pdevice(node); |
| err: |
| return smu_pdev; |
| } |
| |
| int exynos_ufs_smu_get_dev(struct exynos_ufs *ufs) |
| { |
| int ret; |
| struct device *dev; |
| |
| if (!ufs || !ufs->dev) { |
| pr_err("%s: invalid ufs host, host->dev\n", __func__); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| dev = ufs->dev; |
| ufs->smu.vops = exynos_ufs_smu_get_vops(dev); |
| ufs->smu.pdev = exynos_ufs_smu_get_pdevice(dev); |
| |
| if (ufs->smu.pdev == ERR_PTR(-EPROBE_DEFER)) { |
| dev_err(dev, "%s: SMU device not probed yet\n", __func__); |
| ret = -EPROBE_DEFER; |
| goto err; |
| } |
| |
| if (!ufs->smu.pdev || !ufs->smu.vops) { |
| dev_err(dev, "%s: Host device doesn't have SMU or fail to get SMU", |
| __func__); |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| return 0; |
| err: |
| ufs->smu.pdev = NULL; |
| ufs->smu.vops = NULL; |
| |
| return ret; |
| } |
| |
| int exynos_ufs_smu_init(struct exynos_ufs *ufs) |
| { |
| struct smu_data_setting smu_set; |
| |
| if (!ufs->smu.vops || !ufs->smu.pdev) |
| return 0; |
| |
| smu_set.id = SMU_EMBEDDED; |
| smu_set.command = SMU_INIT; |
| return ufs->smu.vops->init(ufs->smu.pdev, &smu_set); |
| } |
| |
| int exynos_ufs_smu_sec_cfg(struct exynos_ufs *ufs) |
| { |
| struct smu_data_setting smu_set; |
| |
| if (!ufs->smu.vops || !ufs->smu.pdev) |
| return 0; |
| |
| smu_set.id = SMU_EMBEDDED; |
| smu_set.desc_type = CFG_DESCTYPE; |
| |
| return ufs->smu.vops->sec_config(ufs->smu.pdev, &smu_set); |
| } |
| |
| int exynos_ufs_smu_resume(struct exynos_ufs *ufs) |
| { |
| struct smu_data_setting smu_set; |
| |
| if (!ufs->smu.vops || !ufs->smu.pdev) |
| return 0; |
| |
| smu_set.id = SMU_EMBEDDED; |
| return ufs->smu.vops->resume(ufs->smu.pdev, &smu_set); |
| } |
| |
| int exynos_ufs_smu_abort(struct exynos_ufs *ufs) |
| { |
| struct smu_data_setting smu_set; |
| |
| if (!ufs->smu.vops || !ufs->smu.pdev) |
| return 0; |
| |
| smu_set.id = SMU_EMBEDDED; |
| smu_set.command = SMU_ABORT; |
| return ufs->smu.vops->abort(ufs->smu.pdev, &smu_set); |
| } |