]>
Commit | Line | Data |
---|---|---|
603fcd16 WG |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * Copyright (C) 2019 MediaTek Inc. All Rights Reserved. | |
4 | * | |
5 | * Author: Weijie Gao <[email protected]> | |
6 | */ | |
7 | ||
8 | #include <common.h> | |
9 | #include <clk.h> | |
10 | #include <dm.h> | |
11 | #include <errno.h> | |
12 | #include <spi.h> | |
13 | #include <spi-mem.h> | |
14 | #include <stdbool.h> | |
15 | #include <watchdog.h> | |
16 | #include <dm/pinctrl.h> | |
17 | #include <linux/bitops.h> | |
18 | #include <linux/io.h> | |
19 | #include <linux/iopoll.h> | |
20 | ||
21 | #define SNFI_MAC_CTL 0x500 | |
22 | #define MAC_XIO_SEL BIT(4) | |
23 | #define SF_MAC_EN BIT(3) | |
24 | #define SF_TRIG BIT(2) | |
25 | #define WIP_READY BIT(1) | |
26 | #define WIP BIT(0) | |
27 | ||
28 | #define SNFI_MAC_OUTL 0x504 | |
29 | #define SNFI_MAC_INL 0x508 | |
30 | ||
31 | #define SNFI_MISC_CTL 0x538 | |
32 | #define SW_RST BIT(28) | |
33 | #define FIFO_RD_LTC_SHIFT 25 | |
34 | #define FIFO_RD_LTC GENMASK(26, 25) | |
35 | #define LATCH_LAT_SHIFT 8 | |
36 | #define LATCH_LAT GENMASK(9, 8) | |
37 | #define CS_DESELECT_CYC_SHIFT 0 | |
38 | #define CS_DESELECT_CYC GENMASK(4, 0) | |
39 | ||
40 | #define SNF_STA_CTL1 0x550 | |
41 | #define SPI_STATE GENMASK(3, 0) | |
42 | ||
43 | #define SNFI_GPRAM_OFFSET 0x800 | |
44 | #define SNFI_GPRAM_SIZE 0x80 | |
45 | ||
46 | #define SNFI_POLL_INTERVAL 500000 | |
47 | #define SNFI_RST_POLL_INTERVAL 1000000 | |
48 | ||
49 | struct mtk_snfi_priv { | |
50 | void __iomem *base; | |
51 | ||
52 | struct clk nfi_clk; | |
53 | struct clk pad_clk; | |
54 | }; | |
55 | ||
56 | static int mtk_snfi_adjust_op_size(struct spi_slave *slave, | |
57 | struct spi_mem_op *op) | |
58 | { | |
59 | u32 nbytes; | |
60 | ||
61 | /* | |
62 | * When there is input data, it will be appended after the output | |
63 | * data in the GPRAM. So the total size of either pure output data | |
64 | * or the output+input data must not exceed the GPRAM size. | |
65 | */ | |
66 | ||
67 | nbytes = sizeof(op->cmd.opcode) + op->addr.nbytes + | |
68 | op->dummy.nbytes; | |
69 | ||
70 | if (nbytes + op->data.nbytes <= SNFI_GPRAM_SIZE) | |
71 | return 0; | |
72 | ||
73 | if (nbytes >= SNFI_GPRAM_SIZE) | |
74 | return -ENOTSUPP; | |
75 | ||
76 | op->data.nbytes = SNFI_GPRAM_SIZE - nbytes; | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
81 | static bool mtk_snfi_supports_op(struct spi_slave *slave, | |
82 | const struct spi_mem_op *op) | |
83 | { | |
84 | if (op->cmd.buswidth > 1 || op->addr.buswidth > 1 || | |
85 | op->dummy.buswidth > 1 || op->data.buswidth > 1) | |
86 | return false; | |
87 | ||
88 | return true; | |
89 | } | |
90 | ||
91 | static int mtk_snfi_mac_trigger(struct mtk_snfi_priv *priv, | |
92 | struct udevice *bus, u32 outlen, u32 inlen) | |
93 | { | |
94 | int ret; | |
95 | u32 val; | |
96 | ||
97 | #ifdef CONFIG_PINCTRL | |
98 | pinctrl_select_state(bus, "snfi"); | |
99 | #endif | |
100 | ||
101 | writel(SF_MAC_EN, priv->base + SNFI_MAC_CTL); | |
102 | writel(outlen, priv->base + SNFI_MAC_OUTL); | |
103 | writel(inlen, priv->base + SNFI_MAC_INL); | |
104 | ||
105 | writel(SF_MAC_EN | SF_TRIG, priv->base + SNFI_MAC_CTL); | |
106 | ||
107 | ret = readl_poll_timeout(priv->base + SNFI_MAC_CTL, val, | |
108 | val & WIP_READY, SNFI_POLL_INTERVAL); | |
109 | if (ret) { | |
110 | printf("%s: timed out waiting for WIP_READY\n", __func__); | |
111 | goto cleanup; | |
112 | } | |
113 | ||
114 | ret = readl_poll_timeout(priv->base + SNFI_MAC_CTL, val, | |
115 | !(val & WIP), SNFI_POLL_INTERVAL); | |
116 | if (ret) | |
117 | printf("%s: timed out waiting for WIP cleared\n", __func__); | |
118 | ||
119 | writel(0, priv->base + SNFI_MAC_CTL); | |
120 | ||
121 | cleanup: | |
122 | #ifdef CONFIG_PINCTRL | |
123 | pinctrl_select_state(bus, "default"); | |
124 | #endif | |
125 | ||
126 | return ret; | |
127 | } | |
128 | ||
129 | static int mtk_snfi_mac_reset(struct mtk_snfi_priv *priv) | |
130 | { | |
131 | int ret; | |
132 | u32 val; | |
133 | ||
134 | setbits_32(priv->base + SNFI_MISC_CTL, SW_RST); | |
135 | ||
136 | ret = readl_poll_timeout(priv->base + SNF_STA_CTL1, val, | |
137 | !(val & SPI_STATE), SNFI_POLL_INTERVAL); | |
138 | if (ret) | |
139 | printf("%s: failed to reset snfi mac\n", __func__); | |
140 | ||
141 | writel((2 << FIFO_RD_LTC_SHIFT) | | |
142 | (10 << CS_DESELECT_CYC_SHIFT), | |
143 | priv->base + SNFI_MISC_CTL); | |
144 | ||
145 | return ret; | |
146 | } | |
147 | ||
148 | static void mtk_snfi_copy_to_gpram(struct mtk_snfi_priv *priv, | |
149 | const void *data, size_t len) | |
150 | { | |
151 | void __iomem *gpram = priv->base + SNFI_GPRAM_OFFSET; | |
152 | size_t i, n = (len + sizeof(u32) - 1) / sizeof(u32); | |
153 | const u32 *buff = data; | |
154 | ||
155 | /* | |
156 | * The output data will always be copied to the beginning of | |
157 | * the GPRAM. Uses word write for better performace. | |
158 | * | |
159 | * Trailing bytes in the last word are not cared. | |
160 | */ | |
161 | ||
162 | for (i = 0; i < n; i++) | |
163 | writel(buff[i], gpram + i * sizeof(u32)); | |
164 | } | |
165 | ||
166 | static void mtk_snfi_copy_from_gpram(struct mtk_snfi_priv *priv, u8 *cache, | |
167 | void *data, size_t pos, size_t len) | |
168 | { | |
169 | void __iomem *gpram = priv->base + SNFI_GPRAM_OFFSET; | |
170 | u32 *buff = (u32 *)cache; | |
171 | size_t i, off, end; | |
172 | ||
173 | /* Start position in the buffer */ | |
174 | off = pos & (sizeof(u32) - 1); | |
175 | ||
176 | /* End position for copy */ | |
177 | end = (len + pos + sizeof(u32) - 1) & (~(sizeof(u32) - 1)); | |
178 | ||
179 | /* Start position for copy */ | |
180 | pos &= ~(sizeof(u32) - 1); | |
181 | ||
182 | /* | |
183 | * Read aligned data from GPRAM to buffer first. | |
184 | * Uses word read for better performace. | |
185 | */ | |
186 | i = 0; | |
187 | while (pos < end) { | |
188 | buff[i++] = readl(gpram + pos); | |
189 | pos += sizeof(u32); | |
190 | } | |
191 | ||
192 | /* Copy rx data */ | |
193 | memcpy(data, cache + off, len); | |
194 | } | |
195 | ||
196 | static int mtk_snfi_exec_op(struct spi_slave *slave, | |
197 | const struct spi_mem_op *op) | |
198 | { | |
199 | struct udevice *bus = dev_get_parent(slave->dev); | |
200 | struct mtk_snfi_priv *priv = dev_get_priv(bus); | |
201 | u8 gpram_cache[SNFI_GPRAM_SIZE]; | |
202 | u32 i, len = 0, inlen = 0; | |
203 | int addr_sh; | |
204 | int ret; | |
205 | ||
206 | WATCHDOG_RESET(); | |
207 | ||
208 | ret = mtk_snfi_mac_reset(priv); | |
209 | if (ret) | |
210 | return ret; | |
211 | ||
212 | /* Put opcode */ | |
213 | gpram_cache[len++] = op->cmd.opcode; | |
214 | ||
215 | /* Put address */ | |
216 | addr_sh = (op->addr.nbytes - 1) * 8; | |
217 | while (addr_sh >= 0) { | |
218 | gpram_cache[len++] = (op->addr.val >> addr_sh) & 0xff; | |
219 | addr_sh -= 8; | |
220 | } | |
221 | ||
222 | /* Put dummy bytes */ | |
223 | for (i = 0; i < op->dummy.nbytes; i++) | |
224 | gpram_cache[len++] = 0; | |
225 | ||
226 | /* Put output data */ | |
227 | if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_OUT) { | |
228 | memcpy(gpram_cache + len, op->data.buf.out, op->data.nbytes); | |
229 | len += op->data.nbytes; | |
230 | } | |
231 | ||
232 | /* Copy final output data to GPRAM */ | |
233 | mtk_snfi_copy_to_gpram(priv, gpram_cache, len); | |
234 | ||
235 | /* Start one SPI transaction */ | |
236 | if (op->data.nbytes && op->data.dir == SPI_MEM_DATA_IN) | |
237 | inlen = op->data.nbytes; | |
238 | ||
239 | ret = mtk_snfi_mac_trigger(priv, bus, len, inlen); | |
240 | if (ret) | |
241 | return ret; | |
242 | ||
243 | /* Copy input data from GPRAM */ | |
244 | if (inlen) | |
245 | mtk_snfi_copy_from_gpram(priv, gpram_cache, op->data.buf.in, | |
246 | len, inlen); | |
247 | ||
248 | return 0; | |
249 | } | |
250 | ||
251 | static int mtk_snfi_spi_probe(struct udevice *bus) | |
252 | { | |
253 | struct mtk_snfi_priv *priv = dev_get_priv(bus); | |
254 | int ret; | |
255 | ||
8613c8d8 | 256 | priv->base = dev_read_addr_ptr(bus); |
603fcd16 WG |
257 | if (!priv->base) |
258 | return -EINVAL; | |
259 | ||
260 | ret = clk_get_by_name(bus, "nfi_clk", &priv->nfi_clk); | |
261 | if (ret < 0) | |
262 | return ret; | |
263 | ||
264 | ret = clk_get_by_name(bus, "pad_clk", &priv->pad_clk); | |
265 | if (ret < 0) | |
266 | return ret; | |
267 | ||
268 | clk_enable(&priv->nfi_clk); | |
269 | clk_enable(&priv->pad_clk); | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
274 | static int mtk_snfi_set_speed(struct udevice *bus, uint speed) | |
275 | { | |
276 | /* | |
277 | * The SNFI does not have a bus clock divider. | |
278 | * The bus clock is set in dts (pad_clk, UNIVPLL2_D8 = 50MHz). | |
279 | */ | |
280 | ||
281 | return 0; | |
282 | } | |
283 | ||
284 | static int mtk_snfi_set_mode(struct udevice *bus, uint mode) | |
285 | { | |
286 | /* The SNFI supports only mode 0 */ | |
287 | ||
288 | if (mode) | |
289 | return -EINVAL; | |
290 | ||
291 | return 0; | |
292 | } | |
293 | ||
294 | static const struct spi_controller_mem_ops mtk_snfi_mem_ops = { | |
295 | .adjust_op_size = mtk_snfi_adjust_op_size, | |
296 | .supports_op = mtk_snfi_supports_op, | |
297 | .exec_op = mtk_snfi_exec_op, | |
298 | }; | |
299 | ||
300 | static const struct dm_spi_ops mtk_snfi_spi_ops = { | |
301 | .mem_ops = &mtk_snfi_mem_ops, | |
302 | .set_speed = mtk_snfi_set_speed, | |
303 | .set_mode = mtk_snfi_set_mode, | |
304 | }; | |
305 | ||
306 | static const struct udevice_id mtk_snfi_spi_ids[] = { | |
307 | { .compatible = "mediatek,mtk-snfi-spi" }, | |
308 | { } | |
309 | }; | |
310 | ||
311 | U_BOOT_DRIVER(mtk_snfi_spi) = { | |
312 | .name = "mtk_snfi_spi", | |
313 | .id = UCLASS_SPI, | |
314 | .of_match = mtk_snfi_spi_ids, | |
315 | .ops = &mtk_snfi_spi_ops, | |
41575d8e | 316 | .priv_auto = sizeof(struct mtk_snfi_priv), |
603fcd16 WG |
317 | .probe = mtk_snfi_spi_probe, |
318 | }; |