blob: c70ef3a98507c67255a37d6130ad9872056b194e [file] [log] [blame]
/*
* Copyright (c) 2016-2017, The Linux Foundation. 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 and
* only 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.
*/
/*
* Secure-Processor-SubSystem (SPSS) utilities.
*
* This driver provides utilities for the Secure Processor (SP).
*
* The SP daemon needs to load different SPSS images based on:
*
* 1. Test/Production key used to sign the SPSS image (read fuses).
* 2. SPSS HW version (selected via Device Tree).
*
*/
#define pr_fmt(fmt) "spss_utils [%s]: " fmt, __func__
#include <linux/kernel.h> /* min() */
#include <linux/module.h> /* MODULE_LICENSE */
#include <linux/device.h> /* class_create() */
#include <linux/slab.h> /* kzalloc() */
#include <linux/fs.h> /* file_operations */
#include <linux/cdev.h> /* cdev_add() */
#include <linux/errno.h> /* EINVAL, ETIMEDOUT */
#include <linux/printk.h> /* pr_err() */
#include <linux/bitops.h> /* BIT(x) */
#include <linux/platform_device.h> /* platform_driver_register() */
#include <linux/of.h> /* of_property_count_strings() */
#include <linux/io.h> /* ioremap_nocache() */
#include <soc/qcom/subsystem_restart.h>
/* driver name */
#define DEVICE_NAME "spss-utils"
enum spss_firmware_type {
SPSS_FW_TYPE_DEV = 'd',
SPSS_FW_TYPE_TEST = 't',
SPSS_FW_TYPE_PROD = 'p',
};
static enum spss_firmware_type firmware_type = SPSS_FW_TYPE_TEST;
static const char *dev_firmware_name;
static const char *test_firmware_name;
static const char *prod_firmware_name;
static const char *firmware_name = "NA";
static struct device *spss_dev;
static u32 spss_debug_reg_addr; /* SP_SCSR_MBn_SP2CL_GPm(n,m) */
/*==========================================================================*/
/* Device Sysfs */
/*==========================================================================*/
static ssize_t firmware_name_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
if (!dev || !attr || !buf) {
pr_err("invalid param.\n");
return -EINVAL;
}
if (firmware_name == NULL)
ret = snprintf(buf, PAGE_SIZE, "%s\n", "unknown");
else
ret = snprintf(buf, PAGE_SIZE, "%s\n", firmware_name);
return ret;
}
static ssize_t firmware_name_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t size)
{
pr_err("set firmware name is not allowed.\n");
return -EINVAL;
}
static DEVICE_ATTR(firmware_name, 0444,
firmware_name_show, firmware_name_store);
static ssize_t test_fuse_state_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
if (!dev || !attr || !buf) {
pr_err("invalid param.\n");
return -EINVAL;
}
switch (firmware_type) {
case SPSS_FW_TYPE_DEV:
ret = snprintf(buf, PAGE_SIZE, "%s", "dev");
break;
case SPSS_FW_TYPE_TEST:
ret = snprintf(buf, PAGE_SIZE, "%s", "test");
break;
case SPSS_FW_TYPE_PROD:
ret = snprintf(buf, PAGE_SIZE, "%s", "prod");
break;
default:
return -EINVAL;
}
return ret;
}
static ssize_t test_fuse_state_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t size)
{
pr_err("set test fuse state is not allowed.\n");
return -EINVAL;
}
static DEVICE_ATTR(test_fuse_state, 0444,
test_fuse_state_show, test_fuse_state_store);
static ssize_t spss_debug_reg_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int ret;
void __iomem *spss_debug_reg = NULL;
u32 val1, val2;
if (!dev || !attr || !buf) {
pr_err("invalid param.\n");
return -EINVAL;
}
pr_debug("spss_debug_reg_addr [0x%x].\n", spss_debug_reg_addr);
spss_debug_reg = ioremap_nocache(spss_debug_reg_addr, sizeof(u32)*2);
if (!spss_debug_reg) {
pr_err("can't map debug reg addr.\n");
return -EFAULT;
}
val1 = readl_relaxed(spss_debug_reg);
val2 = readl_relaxed(((char *) spss_debug_reg) + sizeof(u32));
ret = snprintf(buf, PAGE_SIZE, "val1 [0x%x] val2 [0x%x]", val1, val2);
iounmap(spss_debug_reg);
return ret;
}
static ssize_t spss_debug_reg_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t size)
{
pr_err("set debug reg is not allowed.\n");
return -EINVAL;
}
static DEVICE_ATTR(spss_debug_reg, 0444,
spss_debug_reg_show, spss_debug_reg_store);
static int spss_create_sysfs(struct device *dev)
{
int ret;
ret = device_create_file(dev, &dev_attr_firmware_name);
if (ret < 0) {
pr_err("failed to create sysfs file for firmware_name.\n");
return ret;
}
ret = device_create_file(dev, &dev_attr_test_fuse_state);
if (ret < 0) {
pr_err("failed to create sysfs file for test_fuse_state.\n");
device_remove_file(dev, &dev_attr_firmware_name);
return ret;
}
ret = device_create_file(dev, &dev_attr_spss_debug_reg);
if (ret < 0) {
pr_err("failed to create sysfs file for spss_debug_reg.\n");
device_remove_file(dev, &dev_attr_firmware_name);
device_remove_file(dev, &dev_attr_test_fuse_state);
return ret;
}
return 0;
}
/*==========================================================================*/
/* Device Tree */
/*==========================================================================*/
/**
* spss_parse_dt() - Parse Device Tree info.
*/
static int spss_parse_dt(struct device_node *node)
{
int ret;
u32 spss_fuse1_addr = 0;
u32 spss_fuse1_bit = 0;
u32 spss_fuse1_mask = 0;
void __iomem *spss_fuse1_reg = NULL;
u32 spss_fuse2_addr = 0;
u32 spss_fuse2_bit = 0;
u32 spss_fuse2_mask = 0;
void __iomem *spss_fuse2_reg = NULL;
u32 val1 = 0;
u32 val2 = 0;
ret = of_property_read_string(node, "qcom,spss-dev-firmware-name",
&dev_firmware_name);
if (ret < 0) {
pr_err("can't get dev fw name.\n");
return -EFAULT;
}
ret = of_property_read_string(node, "qcom,spss-test-firmware-name",
&test_firmware_name);
if (ret < 0) {
pr_err("can't get test fw name.\n");
return -EFAULT;
}
ret = of_property_read_string(node, "qcom,spss-prod-firmware-name",
&prod_firmware_name);
if (ret < 0) {
pr_err("can't get prod fw name.\n");
return -EFAULT;
}
ret = of_property_read_u32(node, "qcom,spss-fuse1-addr",
&spss_fuse1_addr);
if (ret < 0) {
pr_err("can't get fuse1 addr.\n");
return -EFAULT;
}
ret = of_property_read_u32(node, "qcom,spss-fuse2-addr",
&spss_fuse2_addr);
if (ret < 0) {
pr_err("can't get fuse2 addr.\n");
return -EFAULT;
}
ret = of_property_read_u32(node, "qcom,spss-fuse1-bit",
&spss_fuse1_bit);
if (ret < 0) {
pr_err("can't get fuse1 bit.\n");
return -EFAULT;
}
ret = of_property_read_u32(node, "qcom,spss-fuse2-bit",
&spss_fuse2_bit);
if (ret < 0) {
pr_err("can't get fuse2 bit.\n");
return -EFAULT;
}
spss_fuse1_mask = BIT(spss_fuse1_bit);
spss_fuse2_mask = BIT(spss_fuse2_bit);
pr_debug("spss fuse1 addr [0x%x] bit [%d] .\n",
(int) spss_fuse1_addr, (int) spss_fuse1_bit);
pr_debug("spss fuse2 addr [0x%x] bit [%d] .\n",
(int) spss_fuse2_addr, (int) spss_fuse2_bit);
spss_fuse1_reg = ioremap_nocache(spss_fuse1_addr, sizeof(u32));
if (!spss_fuse1_reg) {
pr_err("can't map fuse1 addr.\n");
return -EFAULT;
}
spss_fuse2_reg = ioremap_nocache(spss_fuse2_addr, sizeof(u32));
if (!spss_fuse2_reg) {
iounmap(spss_fuse1_reg);
pr_err("can't map fuse2 addr.\n");
return -EFAULT;
}
val1 = readl_relaxed(spss_fuse1_reg);
val2 = readl_relaxed(spss_fuse2_reg);
pr_debug("spss fuse1 value [0x%08x].\n", (int) val1);
pr_debug("spss fuse2 value [0x%08x].\n", (int) val2);
pr_debug("spss fuse1 mask [0x%08x].\n", (int) spss_fuse1_mask);
pr_debug("spss fuse2 mask [0x%08x].\n", (int) spss_fuse2_mask);
/**
* Set firmware_type based on fuses:
* SPSS_CONFIG_MODE 11: dev
* SPSS_CONFIG_MODE 01 or 10: test
* SPSS_CONFIG_MODE 00: prod
*/
if ((val1 & spss_fuse1_mask) && (val2 & spss_fuse2_mask))
firmware_type = SPSS_FW_TYPE_DEV;
else if ((val1 & spss_fuse1_mask) || (val2 & spss_fuse2_mask))
firmware_type = SPSS_FW_TYPE_TEST;
else
firmware_type = SPSS_FW_TYPE_PROD;
iounmap(spss_fuse1_reg);
iounmap(spss_fuse2_reg);
ret = of_property_read_u32(node, "qcom,spss-debug-reg-addr",
&spss_debug_reg_addr);
if (ret < 0) {
pr_err("can't get debug regs addr.\n");
return ret;
}
return 0;
}
/**
* spss_probe() - initialization sequence
*/
static int spss_probe(struct platform_device *pdev)
{
int ret = 0;
struct device_node *np = NULL;
struct device *dev = NULL;
if (!pdev) {
pr_err("invalid pdev.\n");
return -ENODEV;
}
np = pdev->dev.of_node;
if (!np) {
pr_err("invalid DT node.\n");
return -EINVAL;
}
dev = &pdev->dev;
spss_dev = dev;
if (dev == NULL) {
pr_err("invalid dev.\n");
return -EINVAL;
}
platform_set_drvdata(pdev, dev);
ret = spss_parse_dt(np);
if (ret < 0) {
pr_err("fail to parse device tree.\n");
return -EFAULT;
}
switch (firmware_type) {
case SPSS_FW_TYPE_DEV:
firmware_name = dev_firmware_name;
break;
case SPSS_FW_TYPE_TEST:
firmware_name = test_firmware_name;
break;
case SPSS_FW_TYPE_PROD:
firmware_name = prod_firmware_name;
break;
default:
return -EINVAL;
}
ret = subsystem_set_fwname("spss", firmware_name);
if (ret < 0) {
pr_err("fail to set fw name.\n");
return -EFAULT;
}
ret = spss_create_sysfs(dev);
if (ret < 0) {
pr_err("fail to create sysfs.\n");
return -EFAULT;
}
pr_info("Initialization completed ok, firmware_name [%s].\n",
firmware_name);
return 0;
}
static const struct of_device_id spss_match_table[] = {
{ .compatible = "qcom,spss-utils", },
{ },
};
static struct platform_driver spss_driver = {
.probe = spss_probe,
.driver = {
.name = DEVICE_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(spss_match_table),
},
};
/*==========================================================================*/
/* Driver Init/Exit */
/*==========================================================================*/
static int __init spss_init(void)
{
int ret = 0;
pr_info("spss-utils driver Ver 2.0 30-Mar-2017.\n");
ret = platform_driver_register(&spss_driver);
if (ret)
pr_err("register platform driver failed, ret [%d]\n", ret);
return ret;
}
late_initcall(spss_init); /* start after PIL driver */
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Secure Processor Utilities");