162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Amlogic Secure Monitor driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2016 Endless Mobile, Inc.
662306a36Sopenharmony_ci * Author: Carlo Caione <carlo@endlessm.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#define pr_fmt(fmt) "meson-sm: " fmt
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/arm-smccc.h>
1262306a36Sopenharmony_ci#include <linux/bug.h>
1362306a36Sopenharmony_ci#include <linux/io.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/of.h>
1662306a36Sopenharmony_ci#include <linux/of_device.h>
1762306a36Sopenharmony_ci#include <linux/platform_device.h>
1862306a36Sopenharmony_ci#include <linux/printk.h>
1962306a36Sopenharmony_ci#include <linux/types.h>
2062306a36Sopenharmony_ci#include <linux/sizes.h>
2162306a36Sopenharmony_ci #include <linux/slab.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <linux/firmware/meson/meson_sm.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistruct meson_sm_cmd {
2662306a36Sopenharmony_ci	unsigned int index;
2762306a36Sopenharmony_ci	u32 smc_id;
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci#define CMD(d, s) { .index = (d), .smc_id = (s), }
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct meson_sm_chip {
3262306a36Sopenharmony_ci	unsigned int shmem_size;
3362306a36Sopenharmony_ci	u32 cmd_shmem_in_base;
3462306a36Sopenharmony_ci	u32 cmd_shmem_out_base;
3562306a36Sopenharmony_ci	struct meson_sm_cmd cmd[];
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic const struct meson_sm_chip gxbb_chip = {
3962306a36Sopenharmony_ci	.shmem_size		= SZ_4K,
4062306a36Sopenharmony_ci	.cmd_shmem_in_base	= 0x82000020,
4162306a36Sopenharmony_ci	.cmd_shmem_out_base	= 0x82000021,
4262306a36Sopenharmony_ci	.cmd = {
4362306a36Sopenharmony_ci		CMD(SM_EFUSE_READ,	0x82000030),
4462306a36Sopenharmony_ci		CMD(SM_EFUSE_WRITE,	0x82000031),
4562306a36Sopenharmony_ci		CMD(SM_EFUSE_USER_MAX,	0x82000033),
4662306a36Sopenharmony_ci		CMD(SM_GET_CHIP_ID,	0x82000044),
4762306a36Sopenharmony_ci		CMD(SM_A1_PWRC_SET,	0x82000093),
4862306a36Sopenharmony_ci		CMD(SM_A1_PWRC_GET,	0x82000095),
4962306a36Sopenharmony_ci		{ /* sentinel */ },
5062306a36Sopenharmony_ci	},
5162306a36Sopenharmony_ci};
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistruct meson_sm_firmware {
5462306a36Sopenharmony_ci	const struct meson_sm_chip *chip;
5562306a36Sopenharmony_ci	void __iomem *sm_shmem_in_base;
5662306a36Sopenharmony_ci	void __iomem *sm_shmem_out_base;
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic u32 meson_sm_get_cmd(const struct meson_sm_chip *chip,
6062306a36Sopenharmony_ci			    unsigned int cmd_index)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	const struct meson_sm_cmd *cmd = chip->cmd;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	while (cmd->smc_id && cmd->index != cmd_index)
6562306a36Sopenharmony_ci		cmd++;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return cmd->smc_id;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic u32 __meson_sm_call(u32 cmd, u32 arg0, u32 arg1, u32 arg2,
7162306a36Sopenharmony_ci			   u32 arg3, u32 arg4)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct arm_smccc_res res;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	arm_smccc_smc(cmd, arg0, arg1, arg2, arg3, arg4, 0, 0, &res);
7662306a36Sopenharmony_ci	return res.a0;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic void __iomem *meson_sm_map_shmem(u32 cmd_shmem, unsigned int size)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	u32 sm_phy_base;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	sm_phy_base = __meson_sm_call(cmd_shmem, 0, 0, 0, 0, 0);
8462306a36Sopenharmony_ci	if (!sm_phy_base)
8562306a36Sopenharmony_ci		return NULL;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return ioremap_cache(sm_phy_base, size);
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci/**
9162306a36Sopenharmony_ci * meson_sm_call - generic SMC32 call to the secure-monitor
9262306a36Sopenharmony_ci *
9362306a36Sopenharmony_ci * @fw:		Pointer to secure-monitor firmware
9462306a36Sopenharmony_ci * @cmd_index:	Index of the SMC32 function ID
9562306a36Sopenharmony_ci * @ret:	Returned value
9662306a36Sopenharmony_ci * @arg0:	SMC32 Argument 0
9762306a36Sopenharmony_ci * @arg1:	SMC32 Argument 1
9862306a36Sopenharmony_ci * @arg2:	SMC32 Argument 2
9962306a36Sopenharmony_ci * @arg3:	SMC32 Argument 3
10062306a36Sopenharmony_ci * @arg4:	SMC32 Argument 4
10162306a36Sopenharmony_ci *
10262306a36Sopenharmony_ci * Return:	0 on success, a negative value on error
10362306a36Sopenharmony_ci */
10462306a36Sopenharmony_ciint meson_sm_call(struct meson_sm_firmware *fw, unsigned int cmd_index,
10562306a36Sopenharmony_ci		  u32 *ret, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	u32 cmd, lret;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (!fw->chip)
11062306a36Sopenharmony_ci		return -ENOENT;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	cmd = meson_sm_get_cmd(fw->chip, cmd_index);
11362306a36Sopenharmony_ci	if (!cmd)
11462306a36Sopenharmony_ci		return -EINVAL;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	lret = __meson_sm_call(cmd, arg0, arg1, arg2, arg3, arg4);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (ret)
11962306a36Sopenharmony_ci		*ret = lret;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	return 0;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ciEXPORT_SYMBOL(meson_sm_call);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci/**
12662306a36Sopenharmony_ci * meson_sm_call_read - retrieve data from secure-monitor
12762306a36Sopenharmony_ci *
12862306a36Sopenharmony_ci * @fw:		Pointer to secure-monitor firmware
12962306a36Sopenharmony_ci * @buffer:	Buffer to store the retrieved data
13062306a36Sopenharmony_ci * @bsize:	Size of the buffer
13162306a36Sopenharmony_ci * @cmd_index:	Index of the SMC32 function ID
13262306a36Sopenharmony_ci * @arg0:	SMC32 Argument 0
13362306a36Sopenharmony_ci * @arg1:	SMC32 Argument 1
13462306a36Sopenharmony_ci * @arg2:	SMC32 Argument 2
13562306a36Sopenharmony_ci * @arg3:	SMC32 Argument 3
13662306a36Sopenharmony_ci * @arg4:	SMC32 Argument 4
13762306a36Sopenharmony_ci *
13862306a36Sopenharmony_ci * Return:	size of read data on success, a negative value on error
13962306a36Sopenharmony_ci *		When 0 is returned there is no guarantee about the amount of
14062306a36Sopenharmony_ci *		data read and bsize bytes are copied in buffer.
14162306a36Sopenharmony_ci */
14262306a36Sopenharmony_ciint meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer,
14362306a36Sopenharmony_ci		       unsigned int bsize, unsigned int cmd_index, u32 arg0,
14462306a36Sopenharmony_ci		       u32 arg1, u32 arg2, u32 arg3, u32 arg4)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	u32 size;
14762306a36Sopenharmony_ci	int ret;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	if (!fw->chip)
15062306a36Sopenharmony_ci		return -ENOENT;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (!fw->chip->cmd_shmem_out_base)
15362306a36Sopenharmony_ci		return -EINVAL;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (bsize > fw->chip->shmem_size)
15662306a36Sopenharmony_ci		return -EINVAL;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (meson_sm_call(fw, cmd_index, &size, arg0, arg1, arg2, arg3, arg4) < 0)
15962306a36Sopenharmony_ci		return -EINVAL;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (size > bsize)
16262306a36Sopenharmony_ci		return -EINVAL;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	ret = size;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (!size)
16762306a36Sopenharmony_ci		size = bsize;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (buffer)
17062306a36Sopenharmony_ci		memcpy(buffer, fw->sm_shmem_out_base, size);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	return ret;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ciEXPORT_SYMBOL(meson_sm_call_read);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci/**
17762306a36Sopenharmony_ci * meson_sm_call_write - send data to secure-monitor
17862306a36Sopenharmony_ci *
17962306a36Sopenharmony_ci * @fw:		Pointer to secure-monitor firmware
18062306a36Sopenharmony_ci * @buffer:	Buffer containing data to send
18162306a36Sopenharmony_ci * @size:	Size of the data to send
18262306a36Sopenharmony_ci * @cmd_index:	Index of the SMC32 function ID
18362306a36Sopenharmony_ci * @arg0:	SMC32 Argument 0
18462306a36Sopenharmony_ci * @arg1:	SMC32 Argument 1
18562306a36Sopenharmony_ci * @arg2:	SMC32 Argument 2
18662306a36Sopenharmony_ci * @arg3:	SMC32 Argument 3
18762306a36Sopenharmony_ci * @arg4:	SMC32 Argument 4
18862306a36Sopenharmony_ci *
18962306a36Sopenharmony_ci * Return:	size of sent data on success, a negative value on error
19062306a36Sopenharmony_ci */
19162306a36Sopenharmony_ciint meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer,
19262306a36Sopenharmony_ci			unsigned int size, unsigned int cmd_index, u32 arg0,
19362306a36Sopenharmony_ci			u32 arg1, u32 arg2, u32 arg3, u32 arg4)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	u32 written;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	if (!fw->chip)
19862306a36Sopenharmony_ci		return -ENOENT;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	if (size > fw->chip->shmem_size)
20162306a36Sopenharmony_ci		return -EINVAL;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	if (!fw->chip->cmd_shmem_in_base)
20462306a36Sopenharmony_ci		return -EINVAL;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	memcpy(fw->sm_shmem_in_base, buffer, size);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (meson_sm_call(fw, cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0)
20962306a36Sopenharmony_ci		return -EINVAL;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	if (!written)
21262306a36Sopenharmony_ci		return -EINVAL;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	return written;
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ciEXPORT_SYMBOL(meson_sm_call_write);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci/**
21962306a36Sopenharmony_ci * meson_sm_get - get pointer to meson_sm_firmware structure.
22062306a36Sopenharmony_ci *
22162306a36Sopenharmony_ci * @sm_node:		Pointer to the secure-monitor Device Tree node.
22262306a36Sopenharmony_ci *
22362306a36Sopenharmony_ci * Return:		NULL is the secure-monitor device is not ready.
22462306a36Sopenharmony_ci */
22562306a36Sopenharmony_cistruct meson_sm_firmware *meson_sm_get(struct device_node *sm_node)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	struct platform_device *pdev = of_find_device_by_node(sm_node);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	if (!pdev)
23062306a36Sopenharmony_ci		return NULL;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	return platform_get_drvdata(pdev);
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_sm_get);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci#define SM_CHIP_ID_LENGTH	119
23762306a36Sopenharmony_ci#define SM_CHIP_ID_OFFSET	4
23862306a36Sopenharmony_ci#define SM_CHIP_ID_SIZE		12
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic ssize_t serial_show(struct device *dev, struct device_attribute *attr,
24162306a36Sopenharmony_ci			 char *buf)
24262306a36Sopenharmony_ci{
24362306a36Sopenharmony_ci	struct platform_device *pdev = to_platform_device(dev);
24462306a36Sopenharmony_ci	struct meson_sm_firmware *fw;
24562306a36Sopenharmony_ci	uint8_t *id_buf;
24662306a36Sopenharmony_ci	int ret;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	fw = platform_get_drvdata(pdev);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	id_buf = kmalloc(SM_CHIP_ID_LENGTH, GFP_KERNEL);
25162306a36Sopenharmony_ci	if (!id_buf)
25262306a36Sopenharmony_ci		return -ENOMEM;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	ret = meson_sm_call_read(fw, id_buf, SM_CHIP_ID_LENGTH, SM_GET_CHIP_ID,
25562306a36Sopenharmony_ci				 0, 0, 0, 0, 0);
25662306a36Sopenharmony_ci	if (ret < 0) {
25762306a36Sopenharmony_ci		kfree(id_buf);
25862306a36Sopenharmony_ci		return ret;
25962306a36Sopenharmony_ci	}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	ret = sprintf(buf, "%12phN\n", &id_buf[SM_CHIP_ID_OFFSET]);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	kfree(id_buf);
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	return ret;
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic DEVICE_ATTR_RO(serial);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic struct attribute *meson_sm_sysfs_attributes[] = {
27162306a36Sopenharmony_ci	&dev_attr_serial.attr,
27262306a36Sopenharmony_ci	NULL,
27362306a36Sopenharmony_ci};
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cistatic const struct attribute_group meson_sm_sysfs_attr_group = {
27662306a36Sopenharmony_ci	.attrs = meson_sm_sysfs_attributes,
27762306a36Sopenharmony_ci};
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_cistatic const struct of_device_id meson_sm_ids[] = {
28062306a36Sopenharmony_ci	{ .compatible = "amlogic,meson-gxbb-sm", .data = &gxbb_chip },
28162306a36Sopenharmony_ci	{ /* sentinel */ },
28262306a36Sopenharmony_ci};
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_cistatic int __init meson_sm_probe(struct platform_device *pdev)
28562306a36Sopenharmony_ci{
28662306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
28762306a36Sopenharmony_ci	const struct meson_sm_chip *chip;
28862306a36Sopenharmony_ci	struct meson_sm_firmware *fw;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL);
29162306a36Sopenharmony_ci	if (!fw)
29262306a36Sopenharmony_ci		return -ENOMEM;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	chip = of_match_device(meson_sm_ids, dev)->data;
29562306a36Sopenharmony_ci	if (!chip)
29662306a36Sopenharmony_ci		return -EINVAL;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	if (chip->cmd_shmem_in_base) {
29962306a36Sopenharmony_ci		fw->sm_shmem_in_base = meson_sm_map_shmem(chip->cmd_shmem_in_base,
30062306a36Sopenharmony_ci							  chip->shmem_size);
30162306a36Sopenharmony_ci		if (WARN_ON(!fw->sm_shmem_in_base))
30262306a36Sopenharmony_ci			goto out;
30362306a36Sopenharmony_ci	}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	if (chip->cmd_shmem_out_base) {
30662306a36Sopenharmony_ci		fw->sm_shmem_out_base = meson_sm_map_shmem(chip->cmd_shmem_out_base,
30762306a36Sopenharmony_ci							   chip->shmem_size);
30862306a36Sopenharmony_ci		if (WARN_ON(!fw->sm_shmem_out_base))
30962306a36Sopenharmony_ci			goto out_in_base;
31062306a36Sopenharmony_ci	}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	fw->chip = chip;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	platform_set_drvdata(pdev, fw);
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	if (devm_of_platform_populate(dev))
31762306a36Sopenharmony_ci		goto out_in_base;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	if (sysfs_create_group(&pdev->dev.kobj, &meson_sm_sysfs_attr_group))
32062306a36Sopenharmony_ci		goto out_in_base;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	pr_info("secure-monitor enabled\n");
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	return 0;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ciout_in_base:
32762306a36Sopenharmony_ci	iounmap(fw->sm_shmem_in_base);
32862306a36Sopenharmony_ciout:
32962306a36Sopenharmony_ci	return -EINVAL;
33062306a36Sopenharmony_ci}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_cistatic struct platform_driver meson_sm_driver = {
33362306a36Sopenharmony_ci	.driver = {
33462306a36Sopenharmony_ci		.name = "meson-sm",
33562306a36Sopenharmony_ci		.of_match_table = of_match_ptr(meson_sm_ids),
33662306a36Sopenharmony_ci	},
33762306a36Sopenharmony_ci};
33862306a36Sopenharmony_cimodule_platform_driver_probe(meson_sm_driver, meson_sm_probe);
33962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
340