]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
bb416465 FB |
2 | /* |
3 | * Copyright (c) 2017 Intel Corporation | |
4 | * | |
5 | * Intel Mobile Internet Devices (MID) based on Intel Atom SoCs have few | |
6 | * microcontrollers inside to do some auxiliary tasks. One of such | |
7 | * microcontroller is System Controller Unit (SCU) which, in particular, | |
8 | * is servicing watchdog and controlling system reset function. | |
9 | * | |
10 | * This driver enables IPC channel to SCU. | |
bb416465 | 11 | */ |
d678a59d | 12 | #include <common.h> |
bb416465 FB |
13 | #include <dm.h> |
14 | #include <regmap.h> | |
15 | #include <syscon.h> | |
16 | #include <asm/cpu.h> | |
17 | #include <asm/scu.h> | |
cd93d625 | 18 | #include <linux/bitops.h> |
c05ed00a | 19 | #include <linux/delay.h> |
bb416465 FB |
20 | #include <linux/errno.h> |
21 | #include <linux/io.h> | |
22 | #include <linux/kernel.h> | |
23 | ||
24 | /* SCU register map */ | |
25 | struct ipc_regs { | |
26 | u32 cmd; | |
27 | u32 status; | |
28 | u32 sptr; | |
29 | u32 dptr; | |
30 | u32 reserved[28]; | |
31 | u32 wbuf[4]; | |
32 | u32 rbuf[4]; | |
33 | }; | |
34 | ||
35 | struct scu { | |
36 | struct ipc_regs *regs; | |
37 | }; | |
38 | ||
39 | /** | |
40 | * scu_ipc_send_command() - send command to SCU | |
41 | * @regs: register map of SCU | |
42 | * @cmd: command | |
43 | * | |
44 | * Command Register (Write Only): | |
45 | * A write to this register results in an interrupt to the SCU core processor | |
46 | * Format: | |
47 | * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)| | |
48 | */ | |
49 | static void scu_ipc_send_command(struct ipc_regs *regs, u32 cmd) | |
50 | { | |
51 | writel(cmd, ®s->cmd); | |
52 | } | |
53 | ||
54 | /** | |
55 | * scu_ipc_check_status() - check status of last command | |
56 | * @regs: register map of SCU | |
57 | * | |
58 | * Status Register (Read Only): | |
59 | * Driver will read this register to get the ready/busy status of the IPC | |
60 | * block and error status of the IPC command that was just processed by SCU | |
61 | * Format: | |
62 | * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)| | |
63 | */ | |
64 | static int scu_ipc_check_status(struct ipc_regs *regs) | |
65 | { | |
66 | int loop_count = 100000; | |
67 | int status; | |
68 | ||
69 | do { | |
70 | status = readl(®s->status); | |
71 | if (!(status & BIT(0))) | |
72 | break; | |
73 | ||
74 | udelay(1); | |
75 | } while (--loop_count); | |
76 | if (!loop_count) | |
77 | return -ETIMEDOUT; | |
78 | ||
79 | if (status & BIT(1)) { | |
80 | printf("%s() status=0x%08x\n", __func__, status); | |
81 | return -EIO; | |
82 | } | |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
87 | static int scu_ipc_cmd(struct ipc_regs *regs, u32 cmd, u32 sub, | |
88 | u32 *in, int inlen, u32 *out, int outlen) | |
89 | { | |
90 | int i, err; | |
91 | ||
92 | for (i = 0; i < inlen; i++) | |
93 | writel(*in++, ®s->wbuf[i]); | |
94 | ||
95 | scu_ipc_send_command(regs, (inlen << 16) | (sub << 12) | cmd); | |
96 | err = scu_ipc_check_status(regs); | |
97 | ||
98 | if (!err) { | |
99 | for (i = 0; i < outlen; i++) | |
100 | *out++ = readl(®s->rbuf[i]); | |
101 | } | |
102 | ||
103 | return err; | |
104 | } | |
105 | ||
224742a3 GS |
106 | /** |
107 | * scu_ipc_raw_command() - IPC command with data and pointers | |
108 | * @cmd: IPC command code | |
109 | * @sub: IPC command sub type | |
110 | * @in: input data of this IPC command | |
111 | * @inlen: input data length in dwords | |
112 | * @out: output data of this IPC command | |
113 | * @outlen: output data length in dwords | |
114 | * @dptr: data writing to SPTR register | |
115 | * @sptr: data writing to DPTR register | |
116 | * | |
117 | * Send an IPC command to SCU with input/output data and source/dest pointers. | |
118 | * | |
119 | * Return: an IPC error code or 0 on success. | |
120 | */ | |
121 | int scu_ipc_raw_command(u32 cmd, u32 sub, u32 *in, int inlen, u32 *out, | |
122 | int outlen, u32 dptr, u32 sptr) | |
123 | { | |
124 | int inbuflen = DIV_ROUND_UP(inlen, 4); | |
125 | struct udevice *dev; | |
126 | struct scu *scu; | |
127 | int ret; | |
128 | ||
129 | ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); | |
130 | if (ret) | |
131 | return ret; | |
132 | ||
133 | scu = dev_get_priv(dev); | |
134 | ||
135 | /* Up to 16 bytes */ | |
136 | if (inbuflen > 4) | |
137 | return -EINVAL; | |
138 | ||
139 | writel(dptr, &scu->regs->dptr); | |
140 | writel(sptr, &scu->regs->sptr); | |
141 | ||
142 | /* | |
143 | * SRAM controller doesn't support 8-bit writes, it only | |
144 | * supports 32-bit writes, so we have to copy input data into | |
145 | * the temporary buffer, and SCU FW will use the inlen to | |
146 | * determine the actual input data length in the temporary | |
147 | * buffer. | |
148 | */ | |
149 | ||
150 | u32 inbuf[4] = {0}; | |
151 | ||
152 | memcpy(inbuf, in, inlen); | |
153 | ||
154 | return scu_ipc_cmd(scu->regs, cmd, sub, inbuf, inlen, out, outlen); | |
155 | } | |
156 | ||
bb416465 FB |
157 | /** |
158 | * scu_ipc_simple_command() - send a simple command | |
159 | * @cmd: command | |
160 | * @sub: sub type | |
161 | * | |
162 | * Issue a simple command to the SCU. Do not use this interface if | |
163 | * you must then access data as any data values may be overwritten | |
164 | * by another SCU access by the time this function returns. | |
165 | * | |
166 | * This function may sleep. Locking for SCU accesses is handled for | |
167 | * the caller. | |
168 | */ | |
169 | int scu_ipc_simple_command(u32 cmd, u32 sub) | |
170 | { | |
171 | struct scu *scu; | |
172 | struct udevice *dev; | |
173 | int ret; | |
174 | ||
175 | ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); | |
176 | if (ret) | |
177 | return ret; | |
178 | ||
179 | scu = dev_get_priv(dev); | |
180 | ||
181 | scu_ipc_send_command(scu->regs, sub << 12 | cmd); | |
182 | return scu_ipc_check_status(scu->regs); | |
183 | } | |
184 | ||
6321da52 GS |
185 | /** |
186 | * scu_ipc_command - command with data | |
187 | * @cmd: command | |
188 | * @sub: sub type | |
189 | * @in: input data | |
190 | * @inlen: input length in dwords | |
191 | * @out: output data | |
192 | * @outlen: output length in dwords | |
193 | * | |
194 | * Issue a command to the SCU which involves data transfers. | |
195 | */ | |
bb416465 FB |
196 | int scu_ipc_command(u32 cmd, u32 sub, u32 *in, int inlen, u32 *out, int outlen) |
197 | { | |
198 | struct scu *scu; | |
199 | struct udevice *dev; | |
200 | int ret; | |
201 | ||
202 | ret = syscon_get_by_driver_data(X86_SYSCON_SCU, &dev); | |
203 | if (ret) | |
204 | return ret; | |
205 | ||
206 | scu = dev_get_priv(dev); | |
207 | ||
208 | return scu_ipc_cmd(scu->regs, cmd, sub, in, inlen, out, outlen); | |
209 | } | |
210 | ||
211 | static int scu_ipc_probe(struct udevice *dev) | |
212 | { | |
213 | struct scu *scu = dev_get_priv(dev); | |
214 | ||
215 | scu->regs = syscon_get_first_range(X86_SYSCON_SCU); | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | static const struct udevice_id scu_ipc_match[] = { | |
221 | { .compatible = "intel,scu-ipc", .data = X86_SYSCON_SCU }, | |
222 | { /* sentinel */ } | |
223 | }; | |
224 | ||
225 | U_BOOT_DRIVER(scu_ipc) = { | |
226 | .name = "scu_ipc", | |
227 | .id = UCLASS_SYSCON, | |
228 | .of_match = scu_ipc_match, | |
229 | .probe = scu_ipc_probe, | |
41575d8e | 230 | .priv_auto = sizeof(struct scu), |
bb416465 | 231 | }; |