summaryrefslogtreecommitdiffstats
path: root/drivers/remoteproc/stm32_rproc.c
blob: 4dd1634a4759a327acc600d4b0815ed1d7aef888 (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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
 * Authors: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
 *          Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
 * Copyright (C) 2019 Ahmad Fatoum, Pengutronix
 */

#include <common.h>
#include <driver.h>
#include <init.h>
#include <io.h>
#include <mach/stm32mp/smc.h>
#include <mfd/syscon.h>
#include <of_address.h>
#include <linux/regmap.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>

#include "remoteproc_internal.h"

#define HOLD_BOOT		0
#define RELEASE_BOOT		1

struct stm32_syscon {
	struct regmap *map;
	u32 reg;
	u32 mask;
};

struct stm32_rproc {
	struct reset_control *rst;
	struct reset_control *hold_boot_rst;
	struct stm32_syscon hold_boot;
	bool hold_boot_smc;
};

static void *stm32_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
{
	__be32 in_addr = cpu_to_be32(da);
	struct device *dev = &rproc->dev;
	u64 paddr;

	paddr = of_translate_dma_address(dev->parent->of_node, &in_addr);
	if (paddr == OF_BAD_ADDR)
		return NULL;

	return phys_to_virt(paddr);
}

static int stm32_rproc_set_hold_boot(struct rproc *rproc, bool hold)
{
	struct stm32_rproc *ddata = rproc->priv;
	struct stm32_syscon *hold_boot = &ddata->hold_boot;
	struct arm_smccc_res smc_res;
	int val, err;

	/*
	 * Three ways to manage the hold boot
	 * - using SCMI: the hold boot is managed as a reset,
	 * - using Linux(no SCMI): the hold boot is managed as a syscon register
	 * - using SMC call (deprecated): use SMC reset interface
	 */

	val = hold ? HOLD_BOOT : RELEASE_BOOT;

	if (ddata->hold_boot_rst) {
		/* Use the SCMI reset controller */
		if (!hold)
			err = reset_control_deassert(ddata->hold_boot_rst);
		else
			err =  reset_control_assert(ddata->hold_boot_rst);
	} else if (IS_ENABLED(CONFIG_HAVE_ARM_SMCCC) && ddata->hold_boot_smc) {
		/* Use the SMC call */
		arm_smccc_smc(STM32_SMC_RCC, STM32_SMC_REG_WRITE,
			      hold_boot->reg, val, 0, 0, 0, 0, &smc_res);
		err = smc_res.a0;
	} else {
		/* Use syscon */
		err = regmap_update_bits(hold_boot->map, hold_boot->reg,
					 hold_boot->mask, val);
	}

	if (err)
		dev_err(&rproc->dev, "failed to set hold boot\n");

	return err;
}

static int stm32_rproc_start(struct rproc *rproc)
{
	int err;

	err = stm32_rproc_set_hold_boot(rproc, false);
	if (err)
		return err;

	return stm32_rproc_set_hold_boot(rproc, true);
}

static int stm32_rproc_stop(struct rproc *rproc)
{
	struct stm32_rproc *ddata = rproc->priv;
	int err;

	err = stm32_rproc_set_hold_boot(rproc, true);
	if (err)
		return err;

	err = reset_control_assert(ddata->rst);
	if (err) {
		dev_err(&rproc->dev, "failed to assert the reset\n");
		return err;
	}

	return 0;
}

static struct rproc_ops st_rproc_ops = {
	.start		= stm32_rproc_start,
	.stop		= stm32_rproc_stop,
	.da_to_va	= stm32_rproc_da_to_va,
};

static int stm32_rproc_get_syscon(struct device_node *np, const char *prop,
				  struct stm32_syscon *syscon)
{
	int err = 0;

	syscon->map = syscon_regmap_lookup_by_phandle(np, prop);
	if (IS_ERR(syscon->map)) {
		err = PTR_ERR(syscon->map);
		syscon->map = NULL;
		goto out;
	}

	err = of_property_read_u32_index(np, prop, 1, &syscon->reg);
	if (err)
		goto out;

	err = of_property_read_u32_index(np, prop, 2, &syscon->mask);

out:
	return err;
}

static int stm32_rproc_parse_dt(struct device *dev, struct stm32_rproc *ddata)
{
	struct device_node *np = dev->of_node;
	struct stm32_syscon tz;
	unsigned int tzen;
	int err;

	ddata->rst = reset_control_get(dev, NULL);
	if (IS_ERR(ddata->rst)) {
		dev_err(dev, "failed to get mcu reset\n");
		return PTR_ERR(ddata->rst);
	}

	/*
	 * Three ways to manage the hold boot
	 * - using SCMI: the hold boot is managed as a reset
	 *    The DT "reset-mames" property should be defined with 2 items:
	 *        reset-names = "mcu_rst", "hold_boot";
	 * - using SMC call (deprecated): use SMC reset interface
	 *    The DT "reset-mames" property is optional, "st,syscfg-tz" is required
	 * - default(no SCMI, no SMC): the hold boot is managed as a syscon register
	 *    The DT "reset-mames" property is optional, "st,syscfg-holdboot" is required
	 */

	ddata->hold_boot_rst = reset_control_get_optional(dev, "hold_boot");
	if (IS_ERR(ddata->hold_boot_rst))
		return dev_err_probe(dev, PTR_ERR(ddata->hold_boot_rst),
				     "failed to get hold_boot reset\n");

	if (!ddata->hold_boot_rst && IS_ENABLED(CONFIG_HAVE_ARM_SMCCC)) {
		/* Manage the MCU_BOOT using SMC call */
		err = stm32_rproc_get_syscon(np, "st,syscfg-tz", &tz);
		if (!err) {
			err = regmap_read(tz.map, tz.reg, &tzen);
			if (err) {
				dev_err(dev, "failed to read tzen\n");
				return err;
			}
			ddata->hold_boot_smc = tzen & tz.mask;
		}
	}

	if (!ddata->hold_boot_rst && !ddata->hold_boot_smc) {
		/* Default: hold boot manage it through the syscon controller */
		err = stm32_rproc_get_syscon(np, "st,syscfg-holdboot",
					     &ddata->hold_boot);
		if (err) {
			dev_err(dev, "failed to get hold boot\n");
			return err;
		}
	}

	return 0;
}

static int stm32_rproc_probe(struct device *dev)
{
	struct rproc *rproc;
	int ret;

	rproc = rproc_alloc(dev, dev_name(dev), &st_rproc_ops,
			    sizeof(struct stm32_rproc));
	if (!rproc)
		return -ENOMEM;

	ret = stm32_rproc_parse_dt(dev, rproc->priv);
	if (ret)
		return ret;

	return rproc_add(rproc);
}

static const struct of_device_id stm32_rproc_of_match[] = {
	{ .compatible = "st,stm32mp1-m4" },
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, stm32_rproc_of_match);

static struct driver stm32_rproc_driver = {
	.name = "stm32-rproc",
	.probe = stm32_rproc_probe,
	.of_compatible = DRV_OF_COMPAT(stm32_rproc_of_match),
};
device_platform_driver(stm32_rproc_driver);