1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Memory-mapped interface driver for DW SPI Core |
4 | * |
5 | * Copyright (c) 2010, Octasic semiconductor. |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/err.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/pm_runtime.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/spi/spi.h> |
14 | #include <linux/scatterlist.h> |
15 | #include <linux/mfd/syscon.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_platform.h> |
19 | #include <linux/acpi.h> |
20 | #include <linux/property.h> |
21 | #include <linux/regmap.h> |
22 | #include <linux/reset.h> |
23 | |
24 | #include "spi-dw.h" |
25 | |
26 | #define DRIVER_NAME "dw_spi_mmio" |
27 | |
28 | struct dw_spi_mmio { |
29 | struct dw_spi dws; |
30 | struct clk *clk; |
31 | struct clk *pclk; |
32 | void *priv; |
33 | struct reset_control *rstc; |
34 | }; |
35 | |
36 | #define MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24 |
37 | #define OCELOT_IF_SI_OWNER_OFFSET 4 |
38 | #define JAGUAR2_IF_SI_OWNER_OFFSET 6 |
39 | #define MSCC_IF_SI_OWNER_MASK GENMASK(1, 0) |
40 | #define MSCC_IF_SI_OWNER_SISL 0 |
41 | #define MSCC_IF_SI_OWNER_SIBM 1 |
42 | #define MSCC_IF_SI_OWNER_SIMC 2 |
43 | |
44 | #define MSCC_SPI_MST_SW_MODE 0x14 |
45 | #define MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE BIT(13) |
46 | #define MSCC_SPI_MST_SW_MODE_SW_SPI_CS(x) (x << 5) |
47 | |
48 | #define SPARX5_FORCE_ENA 0xa4 |
49 | #define SPARX5_FORCE_VAL 0xa8 |
50 | |
51 | struct dw_spi_mscc { |
52 | struct regmap *syscon; |
53 | void __iomem *spi_mst; /* Not sparx5 */ |
54 | }; |
55 | |
56 | /* |
57 | * Elba SoC does not use ssi, pin override is used for cs 0,1 and |
58 | * gpios for cs 2,3 as defined in the device tree. |
59 | * |
60 | * cs: | 1 0 |
61 | * bit: |---3-------2-------1-------0 |
62 | * | cs1 cs1_ovr cs0 cs0_ovr |
63 | */ |
64 | #define ELBA_SPICS_REG 0x2468 |
65 | #define ELBA_SPICS_OFFSET(cs) ((cs) << 1) |
66 | #define ELBA_SPICS_MASK(cs) (GENMASK(1, 0) << ELBA_SPICS_OFFSET(cs)) |
67 | #define ELBA_SPICS_SET(cs, val) \ |
68 | ((((val) << 1) | BIT(0)) << ELBA_SPICS_OFFSET(cs)) |
69 | |
70 | /* |
71 | * The Designware SPI controller (referred to as master in the documentation) |
72 | * automatically deasserts chip select when the tx fifo is empty. The chip |
73 | * selects then needs to be either driven as GPIOs or, for the first 4 using |
74 | * the SPI boot controller registers. the final chip select is an OR gate |
75 | * between the Designware SPI controller and the SPI boot controller. |
76 | */ |
77 | static void dw_spi_mscc_set_cs(struct spi_device *spi, bool enable) |
78 | { |
79 | struct dw_spi *dws = spi_controller_get_devdata(ctlr: spi->controller); |
80 | struct dw_spi_mmio *dwsmmio = container_of(dws, struct dw_spi_mmio, dws); |
81 | struct dw_spi_mscc *dwsmscc = dwsmmio->priv; |
82 | u32 cs = spi_get_chipselect(spi, idx: 0); |
83 | |
84 | if (cs < 4) { |
85 | u32 sw_mode = MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE; |
86 | |
87 | if (!enable) |
88 | sw_mode |= MSCC_SPI_MST_SW_MODE_SW_SPI_CS(BIT(cs)); |
89 | |
90 | writel(val: sw_mode, addr: dwsmscc->spi_mst + MSCC_SPI_MST_SW_MODE); |
91 | } |
92 | |
93 | dw_spi_set_cs(spi, enable); |
94 | } |
95 | |
96 | static int dw_spi_mscc_init(struct platform_device *pdev, |
97 | struct dw_spi_mmio *dwsmmio, |
98 | const char *cpu_syscon, u32 if_si_owner_offset) |
99 | { |
100 | struct dw_spi_mscc *dwsmscc; |
101 | |
102 | dwsmscc = devm_kzalloc(dev: &pdev->dev, size: sizeof(*dwsmscc), GFP_KERNEL); |
103 | if (!dwsmscc) |
104 | return -ENOMEM; |
105 | |
106 | dwsmscc->spi_mst = devm_platform_ioremap_resource(pdev, index: 1); |
107 | if (IS_ERR(ptr: dwsmscc->spi_mst)) { |
108 | dev_err(&pdev->dev, "SPI_MST region map failed\n" ); |
109 | return PTR_ERR(ptr: dwsmscc->spi_mst); |
110 | } |
111 | |
112 | dwsmscc->syscon = syscon_regmap_lookup_by_compatible(s: cpu_syscon); |
113 | if (IS_ERR(ptr: dwsmscc->syscon)) |
114 | return PTR_ERR(ptr: dwsmscc->syscon); |
115 | |
116 | /* Deassert all CS */ |
117 | writel(val: 0, addr: dwsmscc->spi_mst + MSCC_SPI_MST_SW_MODE); |
118 | |
119 | /* Select the owner of the SI interface */ |
120 | regmap_update_bits(map: dwsmscc->syscon, MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL, |
121 | MSCC_IF_SI_OWNER_MASK << if_si_owner_offset, |
122 | MSCC_IF_SI_OWNER_SIMC << if_si_owner_offset); |
123 | |
124 | dwsmmio->dws.set_cs = dw_spi_mscc_set_cs; |
125 | dwsmmio->priv = dwsmscc; |
126 | |
127 | return 0; |
128 | } |
129 | |
130 | static int dw_spi_mscc_ocelot_init(struct platform_device *pdev, |
131 | struct dw_spi_mmio *dwsmmio) |
132 | { |
133 | return dw_spi_mscc_init(pdev, dwsmmio, cpu_syscon: "mscc,ocelot-cpu-syscon" , |
134 | OCELOT_IF_SI_OWNER_OFFSET); |
135 | } |
136 | |
137 | static int dw_spi_mscc_jaguar2_init(struct platform_device *pdev, |
138 | struct dw_spi_mmio *dwsmmio) |
139 | { |
140 | return dw_spi_mscc_init(pdev, dwsmmio, cpu_syscon: "mscc,jaguar2-cpu-syscon" , |
141 | JAGUAR2_IF_SI_OWNER_OFFSET); |
142 | } |
143 | |
144 | /* |
145 | * The Designware SPI controller (referred to as master in the |
146 | * documentation) automatically deasserts chip select when the tx fifo |
147 | * is empty. The chip selects then needs to be driven by a CS override |
148 | * register. enable is an active low signal. |
149 | */ |
150 | static void dw_spi_sparx5_set_cs(struct spi_device *spi, bool enable) |
151 | { |
152 | struct dw_spi *dws = spi_controller_get_devdata(ctlr: spi->controller); |
153 | struct dw_spi_mmio *dwsmmio = container_of(dws, struct dw_spi_mmio, dws); |
154 | struct dw_spi_mscc *dwsmscc = dwsmmio->priv; |
155 | u8 cs = spi_get_chipselect(spi, idx: 0); |
156 | |
157 | if (!enable) { |
158 | /* CS override drive enable */ |
159 | regmap_write(map: dwsmscc->syscon, SPARX5_FORCE_ENA, val: 1); |
160 | /* Now set CSx enabled */ |
161 | regmap_write(map: dwsmscc->syscon, SPARX5_FORCE_VAL, val: ~BIT(cs)); |
162 | /* Allow settle */ |
163 | usleep_range(min: 1, max: 5); |
164 | } else { |
165 | /* CS value */ |
166 | regmap_write(map: dwsmscc->syscon, SPARX5_FORCE_VAL, val: ~0); |
167 | /* Allow settle */ |
168 | usleep_range(min: 1, max: 5); |
169 | /* CS override drive disable */ |
170 | regmap_write(map: dwsmscc->syscon, SPARX5_FORCE_ENA, val: 0); |
171 | } |
172 | |
173 | dw_spi_set_cs(spi, enable); |
174 | } |
175 | |
176 | static int dw_spi_mscc_sparx5_init(struct platform_device *pdev, |
177 | struct dw_spi_mmio *dwsmmio) |
178 | { |
179 | const char *syscon_name = "microchip,sparx5-cpu-syscon" ; |
180 | struct device *dev = &pdev->dev; |
181 | struct dw_spi_mscc *dwsmscc; |
182 | |
183 | if (!IS_ENABLED(CONFIG_SPI_MUX)) { |
184 | dev_err(dev, "This driver needs CONFIG_SPI_MUX\n" ); |
185 | return -EOPNOTSUPP; |
186 | } |
187 | |
188 | dwsmscc = devm_kzalloc(dev, size: sizeof(*dwsmscc), GFP_KERNEL); |
189 | if (!dwsmscc) |
190 | return -ENOMEM; |
191 | |
192 | dwsmscc->syscon = |
193 | syscon_regmap_lookup_by_compatible(s: syscon_name); |
194 | if (IS_ERR(ptr: dwsmscc->syscon)) { |
195 | dev_err(dev, "No syscon map %s\n" , syscon_name); |
196 | return PTR_ERR(ptr: dwsmscc->syscon); |
197 | } |
198 | |
199 | dwsmmio->dws.set_cs = dw_spi_sparx5_set_cs; |
200 | dwsmmio->priv = dwsmscc; |
201 | |
202 | return 0; |
203 | } |
204 | |
205 | static int dw_spi_alpine_init(struct platform_device *pdev, |
206 | struct dw_spi_mmio *dwsmmio) |
207 | { |
208 | dwsmmio->dws.caps = DW_SPI_CAP_CS_OVERRIDE; |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static int dw_spi_pssi_init(struct platform_device *pdev, |
214 | struct dw_spi_mmio *dwsmmio) |
215 | { |
216 | dw_spi_dma_setup_generic(dws: &dwsmmio->dws); |
217 | |
218 | return 0; |
219 | } |
220 | |
221 | static int dw_spi_hssi_init(struct platform_device *pdev, |
222 | struct dw_spi_mmio *dwsmmio) |
223 | { |
224 | dwsmmio->dws.ip = DW_HSSI_ID; |
225 | |
226 | dw_spi_dma_setup_generic(dws: &dwsmmio->dws); |
227 | |
228 | return 0; |
229 | } |
230 | |
231 | static int dw_spi_intel_init(struct platform_device *pdev, |
232 | struct dw_spi_mmio *dwsmmio) |
233 | { |
234 | dwsmmio->dws.ip = DW_HSSI_ID; |
235 | |
236 | return 0; |
237 | } |
238 | |
239 | /* |
240 | * DMA-based mem ops are not configured for this device and are not tested. |
241 | */ |
242 | static int dw_spi_mountevans_imc_init(struct platform_device *pdev, |
243 | struct dw_spi_mmio *dwsmmio) |
244 | { |
245 | /* |
246 | * The Intel Mount Evans SoC's Integrated Management Complex DW |
247 | * apb_ssi_v4.02a controller has an errata where a full TX FIFO can |
248 | * result in data corruption. The suggested workaround is to never |
249 | * completely fill the FIFO. The TX FIFO has a size of 32 so the |
250 | * fifo_len is set to 31. |
251 | */ |
252 | dwsmmio->dws.fifo_len = 31; |
253 | |
254 | return 0; |
255 | } |
256 | |
257 | static int dw_spi_canaan_k210_init(struct platform_device *pdev, |
258 | struct dw_spi_mmio *dwsmmio) |
259 | { |
260 | /* |
261 | * The Canaan Kendryte K210 SoC DW apb_ssi v4 spi controller is |
262 | * documented to have a 32 word deep TX and RX FIFO, which |
263 | * spi_hw_init() detects. However, when the RX FIFO is filled up to |
264 | * 32 entries (RXFLR = 32), an RX FIFO overrun error occurs. Avoid this |
265 | * problem by force setting fifo_len to 31. |
266 | */ |
267 | dwsmmio->dws.fifo_len = 31; |
268 | |
269 | return 0; |
270 | } |
271 | |
272 | static void dw_spi_elba_override_cs(struct regmap *syscon, int cs, int enable) |
273 | { |
274 | regmap_update_bits(map: syscon, ELBA_SPICS_REG, ELBA_SPICS_MASK(cs), |
275 | ELBA_SPICS_SET(cs, enable)); |
276 | } |
277 | |
278 | static void dw_spi_elba_set_cs(struct spi_device *spi, bool enable) |
279 | { |
280 | struct dw_spi *dws = spi_controller_get_devdata(ctlr: spi->controller); |
281 | struct dw_spi_mmio *dwsmmio = container_of(dws, struct dw_spi_mmio, dws); |
282 | struct regmap *syscon = dwsmmio->priv; |
283 | u8 cs; |
284 | |
285 | cs = spi_get_chipselect(spi, idx: 0); |
286 | if (cs < 2) |
287 | dw_spi_elba_override_cs(syscon, cs: spi_get_chipselect(spi, idx: 0), enable); |
288 | |
289 | /* |
290 | * The DW SPI controller needs a native CS bit selected to start |
291 | * the serial engine. |
292 | */ |
293 | spi_set_chipselect(spi, idx: 0, chipselect: 0); |
294 | dw_spi_set_cs(spi, enable); |
295 | spi_set_chipselect(spi, idx: 0, chipselect: cs); |
296 | } |
297 | |
298 | static int dw_spi_elba_init(struct platform_device *pdev, |
299 | struct dw_spi_mmio *dwsmmio) |
300 | { |
301 | struct regmap *syscon; |
302 | |
303 | syscon = syscon_regmap_lookup_by_phandle(np: dev_of_node(dev: &pdev->dev), |
304 | property: "amd,pensando-elba-syscon" ); |
305 | if (IS_ERR(ptr: syscon)) |
306 | return dev_err_probe(dev: &pdev->dev, err: PTR_ERR(ptr: syscon), |
307 | fmt: "syscon regmap lookup failed\n" ); |
308 | |
309 | dwsmmio->priv = syscon; |
310 | dwsmmio->dws.set_cs = dw_spi_elba_set_cs; |
311 | |
312 | return 0; |
313 | } |
314 | |
315 | static int dw_spi_mmio_probe(struct platform_device *pdev) |
316 | { |
317 | int (*init_func)(struct platform_device *pdev, |
318 | struct dw_spi_mmio *dwsmmio); |
319 | struct dw_spi_mmio *dwsmmio; |
320 | struct resource *mem; |
321 | struct dw_spi *dws; |
322 | int ret; |
323 | int num_cs; |
324 | |
325 | dwsmmio = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct dw_spi_mmio), |
326 | GFP_KERNEL); |
327 | if (!dwsmmio) |
328 | return -ENOMEM; |
329 | |
330 | dws = &dwsmmio->dws; |
331 | |
332 | /* Get basic io resource and map it */ |
333 | dws->regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &mem); |
334 | if (IS_ERR(ptr: dws->regs)) |
335 | return PTR_ERR(ptr: dws->regs); |
336 | |
337 | dws->paddr = mem->start; |
338 | |
339 | dws->irq = platform_get_irq(pdev, 0); |
340 | if (dws->irq < 0) |
341 | return dws->irq; /* -ENXIO */ |
342 | |
343 | dwsmmio->clk = devm_clk_get_enabled(dev: &pdev->dev, NULL); |
344 | if (IS_ERR(ptr: dwsmmio->clk)) |
345 | return PTR_ERR(ptr: dwsmmio->clk); |
346 | |
347 | /* Optional clock needed to access the registers */ |
348 | dwsmmio->pclk = devm_clk_get_optional_enabled(dev: &pdev->dev, id: "pclk" ); |
349 | if (IS_ERR(ptr: dwsmmio->pclk)) |
350 | return PTR_ERR(ptr: dwsmmio->pclk); |
351 | |
352 | /* find an optional reset controller */ |
353 | dwsmmio->rstc = devm_reset_control_get_optional_exclusive(dev: &pdev->dev, id: "spi" ); |
354 | if (IS_ERR(ptr: dwsmmio->rstc)) |
355 | return PTR_ERR(ptr: dwsmmio->rstc); |
356 | |
357 | reset_control_deassert(rstc: dwsmmio->rstc); |
358 | |
359 | dws->bus_num = pdev->id; |
360 | |
361 | dws->max_freq = clk_get_rate(clk: dwsmmio->clk); |
362 | |
363 | if (device_property_read_u32(dev: &pdev->dev, propname: "reg-io-width" , |
364 | val: &dws->reg_io_width)) |
365 | dws->reg_io_width = 4; |
366 | |
367 | num_cs = 4; |
368 | |
369 | device_property_read_u32(dev: &pdev->dev, propname: "num-cs" , val: &num_cs); |
370 | |
371 | dws->num_cs = num_cs; |
372 | |
373 | init_func = device_get_match_data(dev: &pdev->dev); |
374 | if (init_func) { |
375 | ret = init_func(pdev, dwsmmio); |
376 | if (ret) |
377 | goto out_reset; |
378 | } |
379 | |
380 | pm_runtime_enable(dev: &pdev->dev); |
381 | |
382 | ret = dw_spi_add_host(dev: &pdev->dev, dws); |
383 | if (ret) |
384 | goto out; |
385 | |
386 | platform_set_drvdata(pdev, data: dwsmmio); |
387 | return 0; |
388 | |
389 | out: |
390 | pm_runtime_disable(dev: &pdev->dev); |
391 | out_reset: |
392 | reset_control_assert(rstc: dwsmmio->rstc); |
393 | |
394 | return ret; |
395 | } |
396 | |
397 | static void dw_spi_mmio_remove(struct platform_device *pdev) |
398 | { |
399 | struct dw_spi_mmio *dwsmmio = platform_get_drvdata(pdev); |
400 | |
401 | dw_spi_remove_host(dws: &dwsmmio->dws); |
402 | pm_runtime_disable(dev: &pdev->dev); |
403 | reset_control_assert(rstc: dwsmmio->rstc); |
404 | } |
405 | |
406 | static const struct of_device_id dw_spi_mmio_of_match[] = { |
407 | { .compatible = "snps,dw-apb-ssi" , .data = dw_spi_pssi_init}, |
408 | { .compatible = "mscc,ocelot-spi" , .data = dw_spi_mscc_ocelot_init}, |
409 | { .compatible = "mscc,jaguar2-spi" , .data = dw_spi_mscc_jaguar2_init}, |
410 | { .compatible = "amazon,alpine-dw-apb-ssi" , .data = dw_spi_alpine_init}, |
411 | { .compatible = "renesas,rzn1-spi" , .data = dw_spi_pssi_init}, |
412 | { .compatible = "snps,dwc-ssi-1.01a" , .data = dw_spi_hssi_init}, |
413 | { .compatible = "intel,keembay-ssi" , .data = dw_spi_intel_init}, |
414 | { |
415 | .compatible = "intel,mountevans-imc-ssi" , |
416 | .data = dw_spi_mountevans_imc_init, |
417 | }, |
418 | { .compatible = "microchip,sparx5-spi" , dw_spi_mscc_sparx5_init}, |
419 | { .compatible = "canaan,k210-spi" , dw_spi_canaan_k210_init}, |
420 | { .compatible = "amd,pensando-elba-spi" , .data = dw_spi_elba_init}, |
421 | { /* end of table */} |
422 | }; |
423 | MODULE_DEVICE_TABLE(of, dw_spi_mmio_of_match); |
424 | |
425 | #ifdef CONFIG_ACPI |
426 | static const struct acpi_device_id dw_spi_mmio_acpi_match[] = { |
427 | {"HISI0173" , (kernel_ulong_t)dw_spi_pssi_init}, |
428 | {}, |
429 | }; |
430 | MODULE_DEVICE_TABLE(acpi, dw_spi_mmio_acpi_match); |
431 | #endif |
432 | |
433 | static struct platform_driver dw_spi_mmio_driver = { |
434 | .probe = dw_spi_mmio_probe, |
435 | .remove_new = dw_spi_mmio_remove, |
436 | .driver = { |
437 | .name = DRIVER_NAME, |
438 | .of_match_table = dw_spi_mmio_of_match, |
439 | .acpi_match_table = ACPI_PTR(dw_spi_mmio_acpi_match), |
440 | }, |
441 | }; |
442 | module_platform_driver(dw_spi_mmio_driver); |
443 | |
444 | MODULE_AUTHOR("Jean-Hugues Deschenes <jean-hugues.deschenes@octasic.com>" ); |
445 | MODULE_DESCRIPTION("Memory-mapped I/O interface driver for DW SPI Core" ); |
446 | MODULE_LICENSE("GPL v2" ); |
447 | MODULE_IMPORT_NS(SPI_DW_CORE); |
448 | |