aboutsummaryrefslogtreecommitdiff
path: root/drivers/sm/meson-sm.c
blob: 15b3b0e2672f6e714474df8ac3b7fc43faf3948c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (c) 2023 SberDevices, Inc.
 *
 * Author: Alexey Romanov <avromanov@salutedevices.com>
 */

#include <common.h>
#include <dm.h>
#include <regmap.h>
#include <sm.h>
#include <sm-uclass.h>
#include <stdlib.h>
#include <syscon.h>
#include <asm/ptrace.h>
#include <asm/system.h>
#include <meson/sm.h>
#include <linux/bitfield.h>
#include <linux/err.h>
#include <linux/sizes.h>

struct meson_sm_cmd {
	u32 smc_id;
};

#define SET_CMD(index, id)	\
	[index] = {		\
		.smc_id = (id),	\
	}

struct meson_sm_data {
	u32 cmd_get_shmem_in;
	u32 cmd_get_shmem_out;
	unsigned int shmem_size;
	struct meson_sm_cmd cmd[];
};

struct meson_sm_priv {
	void *sm_shmem_in;
	void *sm_shmem_out;
	const struct meson_sm_data *data;
};

static unsigned long __meson_sm_call(u32 cmd, const struct pt_regs *args)
{
	struct pt_regs r = *args;

	r.regs[0] = cmd;
	smc_call(&r);

	return r.regs[0];
};

static u32 meson_sm_get_cmd(const struct meson_sm_data *data,
			    u32 cmd_index)
{
	struct meson_sm_cmd cmd;

	if (cmd_index >= MESON_SMC_CMD_COUNT)
		return 0;

	cmd = data->cmd[cmd_index];
	return cmd.smc_id;
}

static int meson_sm_call(struct udevice *dev, u32 cmd_index, s32 *retval,
			 struct pt_regs *args)
{
	struct meson_sm_priv *priv = dev_get_priv(dev);
	u32 cmd, ret;

	cmd = meson_sm_get_cmd(priv->data, cmd_index);
	if (!cmd)
		return -ENOENT;

	ret = __meson_sm_call(cmd, args);
	if (retval)
		*retval = ret;

	return 0;
}

static int meson_sm_call_read(struct udevice *dev, void *buffer, size_t size,
			      u32 cmd_index, struct pt_regs *args)
{
	struct meson_sm_priv *priv = dev_get_priv(dev);
	s32 nbytes;
	int ret;

	if (!buffer || size > priv->data->shmem_size)
		return -EINVAL;

	ret = meson_sm_call(dev, cmd_index, &nbytes, args);
	if (ret)
		return ret;

	if (nbytes < 0 || nbytes > size)
		return -ENOBUFS;

	/* In some cases (for example GET_CHIP_ID command),
	 * SMC doesn't return the number of bytes read, even
	 * though the bytes were actually read into sm_shmem_out.
	 * So this check is needed.
	 */
	ret = nbytes;
	if (!nbytes)
		nbytes = size;

	memcpy(buffer, priv->sm_shmem_out, nbytes);

	return ret;
}

static int meson_sm_call_write(struct udevice *dev, void *buffer, size_t size,
			       u32 cmd_index, struct pt_regs *args)
{
	struct meson_sm_priv *priv = dev_get_priv(dev);
	s32 nbytes;
	int ret;

	if (!buffer || size > priv->data->shmem_size)
		return -EINVAL;

	memcpy(priv->sm_shmem_in, buffer, size);

	ret = meson_sm_call(dev, cmd_index, &nbytes, args);
	if (ret)
		return ret;

	if (nbytes <= 0 || nbytes > size)
		return -EIO;

	return nbytes;
}

static int meson_sm_probe(struct udevice *dev)
{
	struct meson_sm_priv *priv = dev_get_priv(dev);
	struct pt_regs regs = { 0 };

	priv->data = (struct meson_sm_data *)dev_get_driver_data(dev);
	if (!priv->data)
		return -EINVAL;

	priv->sm_shmem_in =
		(void *)__meson_sm_call(priv->data->cmd_get_shmem_in, &regs);

	if (!priv->sm_shmem_in)
		return -ENOMEM;

	priv->sm_shmem_out =
		(void *)__meson_sm_call(priv->data->cmd_get_shmem_out, &regs);

	if (!priv->sm_shmem_out)
		return -ENOMEM;

	pr_debug("meson sm driver probed\n"
		 "shmem_in addr: 0x%p, shmem_out addr: 0x%p\n",
		 priv->sm_shmem_in,
		 priv->sm_shmem_out);

	return 0;
}

static const struct meson_sm_data meson_sm_gxbb_data = {
	.cmd_get_shmem_in  = 0x82000020,
	.cmd_get_shmem_out = 0x82000021,
	.shmem_size = SZ_4K,
	.cmd = {
		SET_CMD(MESON_SMC_CMD_EFUSE_READ,  0x82000030),
		SET_CMD(MESON_SMC_CMD_EFUSE_WRITE, 0x82000031),
		SET_CMD(MESON_SMC_CMD_CHIP_ID_GET, 0x82000044),
		SET_CMD(MESON_SMC_CMD_PWRDM_SET,   0x82000093),
	},
};

static const struct udevice_id meson_sm_ids[] = {
	{
		.compatible = "amlogic,meson-gxbb-sm",
		.data = (ulong)&meson_sm_gxbb_data,
	},
	{ }
};

static const struct sm_ops sm_ops = {
	.sm_call = meson_sm_call,
	.sm_call_read = meson_sm_call_read,
	.sm_call_write = meson_sm_call_write,
};

U_BOOT_DRIVER(meson_sm) = {
	.name = "meson_sm",
	.id = UCLASS_SM,
	.of_match = meson_sm_ids,
	.probe = meson_sm_probe,
	.bind = dm_scan_fdt_dev,
	.priv_auto = sizeof(struct meson_sm_priv),
	.ops = &sm_ops,
};