.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Memory-mapped interface driver for DW SPI Core |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (c) 2010, Octasic semiconductor. |
---|
5 | | - * |
---|
6 | | - * This program is free software; you can redistribute it and/or modify it |
---|
7 | | - * under the terms and conditions of the GNU General Public License, |
---|
8 | | - * version 2, as published by the Free Software Foundation. |
---|
9 | 6 | */ |
---|
10 | 7 | |
---|
11 | 8 | #include <linux/clk.h> |
---|
12 | 9 | #include <linux/err.h> |
---|
13 | | -#include <linux/interrupt.h> |
---|
14 | 10 | #include <linux/platform_device.h> |
---|
| 11 | +#include <linux/pm_runtime.h> |
---|
15 | 12 | #include <linux/slab.h> |
---|
16 | 13 | #include <linux/spi/spi.h> |
---|
17 | 14 | #include <linux/scatterlist.h> |
---|
18 | 15 | #include <linux/mfd/syscon.h> |
---|
19 | 16 | #include <linux/module.h> |
---|
20 | 17 | #include <linux/of.h> |
---|
21 | | -#include <linux/of_gpio.h> |
---|
22 | 18 | #include <linux/of_platform.h> |
---|
| 19 | +#include <linux/acpi.h> |
---|
23 | 20 | #include <linux/property.h> |
---|
24 | 21 | #include <linux/regmap.h> |
---|
| 22 | +#include <linux/reset.h> |
---|
25 | 23 | |
---|
26 | 24 | #include "spi-dw.h" |
---|
27 | 25 | |
---|
.. | .. |
---|
30 | 28 | struct dw_spi_mmio { |
---|
31 | 29 | struct dw_spi dws; |
---|
32 | 30 | struct clk *clk; |
---|
| 31 | + struct clk *pclk; |
---|
33 | 32 | void *priv; |
---|
| 33 | + struct reset_control *rstc; |
---|
34 | 34 | }; |
---|
35 | 35 | |
---|
36 | 36 | #define MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24 |
---|
37 | | -#define OCELOT_IF_SI_OWNER_MASK GENMASK(5, 4) |
---|
38 | 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) |
---|
39 | 40 | #define MSCC_IF_SI_OWNER_SISL 0 |
---|
40 | 41 | #define MSCC_IF_SI_OWNER_SIBM 1 |
---|
41 | 42 | #define MSCC_IF_SI_OWNER_SIMC 2 |
---|
.. | .. |
---|
44 | 45 | #define MSCC_SPI_MST_SW_MODE_SW_PIN_CTRL_MODE BIT(13) |
---|
45 | 46 | #define MSCC_SPI_MST_SW_MODE_SW_SPI_CS(x) (x << 5) |
---|
46 | 47 | |
---|
| 48 | +#define SPARX5_FORCE_ENA 0xa4 |
---|
| 49 | +#define SPARX5_FORCE_VAL 0xa8 |
---|
| 50 | + |
---|
47 | 51 | struct dw_spi_mscc { |
---|
48 | 52 | struct regmap *syscon; |
---|
49 | | - void __iomem *spi_mst; |
---|
| 53 | + void __iomem *spi_mst; /* Not sparx5 */ |
---|
50 | 54 | }; |
---|
51 | 55 | |
---|
52 | 56 | /* |
---|
.. | .. |
---|
76 | 80 | } |
---|
77 | 81 | |
---|
78 | 82 | static int dw_spi_mscc_init(struct platform_device *pdev, |
---|
79 | | - struct dw_spi_mmio *dwsmmio) |
---|
| 83 | + struct dw_spi_mmio *dwsmmio, |
---|
| 84 | + const char *cpu_syscon, u32 if_si_owner_offset) |
---|
80 | 85 | { |
---|
81 | 86 | struct dw_spi_mscc *dwsmscc; |
---|
82 | | - struct resource *res; |
---|
83 | 87 | |
---|
84 | 88 | dwsmscc = devm_kzalloc(&pdev->dev, sizeof(*dwsmscc), GFP_KERNEL); |
---|
85 | 89 | if (!dwsmscc) |
---|
86 | 90 | return -ENOMEM; |
---|
87 | 91 | |
---|
88 | | - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
---|
89 | | - dwsmscc->spi_mst = devm_ioremap_resource(&pdev->dev, res); |
---|
| 92 | + dwsmscc->spi_mst = devm_platform_ioremap_resource(pdev, 1); |
---|
90 | 93 | if (IS_ERR(dwsmscc->spi_mst)) { |
---|
91 | 94 | dev_err(&pdev->dev, "SPI_MST region map failed\n"); |
---|
92 | 95 | return PTR_ERR(dwsmscc->spi_mst); |
---|
93 | 96 | } |
---|
94 | 97 | |
---|
95 | | - dwsmscc->syscon = syscon_regmap_lookup_by_compatible("mscc,ocelot-cpu-syscon"); |
---|
| 98 | + dwsmscc->syscon = syscon_regmap_lookup_by_compatible(cpu_syscon); |
---|
96 | 99 | if (IS_ERR(dwsmscc->syscon)) |
---|
97 | 100 | return PTR_ERR(dwsmscc->syscon); |
---|
98 | 101 | |
---|
.. | .. |
---|
101 | 104 | |
---|
102 | 105 | /* Select the owner of the SI interface */ |
---|
103 | 106 | regmap_update_bits(dwsmscc->syscon, MSCC_CPU_SYSTEM_CTRL_GENERAL_CTRL, |
---|
104 | | - OCELOT_IF_SI_OWNER_MASK, |
---|
105 | | - MSCC_IF_SI_OWNER_SIMC << OCELOT_IF_SI_OWNER_OFFSET); |
---|
| 107 | + MSCC_IF_SI_OWNER_MASK << if_si_owner_offset, |
---|
| 108 | + MSCC_IF_SI_OWNER_SIMC << if_si_owner_offset); |
---|
106 | 109 | |
---|
107 | 110 | dwsmmio->dws.set_cs = dw_spi_mscc_set_cs; |
---|
108 | 111 | dwsmmio->priv = dwsmscc; |
---|
| 112 | + |
---|
| 113 | + return 0; |
---|
| 114 | +} |
---|
| 115 | + |
---|
| 116 | +static int dw_spi_mscc_ocelot_init(struct platform_device *pdev, |
---|
| 117 | + struct dw_spi_mmio *dwsmmio) |
---|
| 118 | +{ |
---|
| 119 | + return dw_spi_mscc_init(pdev, dwsmmio, "mscc,ocelot-cpu-syscon", |
---|
| 120 | + OCELOT_IF_SI_OWNER_OFFSET); |
---|
| 121 | +} |
---|
| 122 | + |
---|
| 123 | +static int dw_spi_mscc_jaguar2_init(struct platform_device *pdev, |
---|
| 124 | + struct dw_spi_mmio *dwsmmio) |
---|
| 125 | +{ |
---|
| 126 | + return dw_spi_mscc_init(pdev, dwsmmio, "mscc,jaguar2-cpu-syscon", |
---|
| 127 | + JAGUAR2_IF_SI_OWNER_OFFSET); |
---|
| 128 | +} |
---|
| 129 | + |
---|
| 130 | +/* |
---|
| 131 | + * The Designware SPI controller (referred to as master in the |
---|
| 132 | + * documentation) automatically deasserts chip select when the tx fifo |
---|
| 133 | + * is empty. The chip selects then needs to be driven by a CS override |
---|
| 134 | + * register. enable is an active low signal. |
---|
| 135 | + */ |
---|
| 136 | +static void dw_spi_sparx5_set_cs(struct spi_device *spi, bool enable) |
---|
| 137 | +{ |
---|
| 138 | + struct dw_spi *dws = spi_master_get_devdata(spi->master); |
---|
| 139 | + struct dw_spi_mmio *dwsmmio = container_of(dws, struct dw_spi_mmio, dws); |
---|
| 140 | + struct dw_spi_mscc *dwsmscc = dwsmmio->priv; |
---|
| 141 | + u8 cs = spi->chip_select; |
---|
| 142 | + |
---|
| 143 | + if (!enable) { |
---|
| 144 | + /* CS override drive enable */ |
---|
| 145 | + regmap_write(dwsmscc->syscon, SPARX5_FORCE_ENA, 1); |
---|
| 146 | + /* Now set CSx enabled */ |
---|
| 147 | + regmap_write(dwsmscc->syscon, SPARX5_FORCE_VAL, ~BIT(cs)); |
---|
| 148 | + /* Allow settle */ |
---|
| 149 | + usleep_range(1, 5); |
---|
| 150 | + } else { |
---|
| 151 | + /* CS value */ |
---|
| 152 | + regmap_write(dwsmscc->syscon, SPARX5_FORCE_VAL, ~0); |
---|
| 153 | + /* Allow settle */ |
---|
| 154 | + usleep_range(1, 5); |
---|
| 155 | + /* CS override drive disable */ |
---|
| 156 | + regmap_write(dwsmscc->syscon, SPARX5_FORCE_ENA, 0); |
---|
| 157 | + } |
---|
| 158 | + |
---|
| 159 | + dw_spi_set_cs(spi, enable); |
---|
| 160 | +} |
---|
| 161 | + |
---|
| 162 | +static int dw_spi_mscc_sparx5_init(struct platform_device *pdev, |
---|
| 163 | + struct dw_spi_mmio *dwsmmio) |
---|
| 164 | +{ |
---|
| 165 | + const char *syscon_name = "microchip,sparx5-cpu-syscon"; |
---|
| 166 | + struct device *dev = &pdev->dev; |
---|
| 167 | + struct dw_spi_mscc *dwsmscc; |
---|
| 168 | + |
---|
| 169 | + if (!IS_ENABLED(CONFIG_SPI_MUX)) { |
---|
| 170 | + dev_err(dev, "This driver needs CONFIG_SPI_MUX\n"); |
---|
| 171 | + return -EOPNOTSUPP; |
---|
| 172 | + } |
---|
| 173 | + |
---|
| 174 | + dwsmscc = devm_kzalloc(dev, sizeof(*dwsmscc), GFP_KERNEL); |
---|
| 175 | + if (!dwsmscc) |
---|
| 176 | + return -ENOMEM; |
---|
| 177 | + |
---|
| 178 | + dwsmscc->syscon = |
---|
| 179 | + syscon_regmap_lookup_by_compatible(syscon_name); |
---|
| 180 | + if (IS_ERR(dwsmscc->syscon)) { |
---|
| 181 | + dev_err(dev, "No syscon map %s\n", syscon_name); |
---|
| 182 | + return PTR_ERR(dwsmscc->syscon); |
---|
| 183 | + } |
---|
| 184 | + |
---|
| 185 | + dwsmmio->dws.set_cs = dw_spi_sparx5_set_cs; |
---|
| 186 | + dwsmmio->priv = dwsmscc; |
---|
| 187 | + |
---|
| 188 | + return 0; |
---|
| 189 | +} |
---|
| 190 | + |
---|
| 191 | +static int dw_spi_alpine_init(struct platform_device *pdev, |
---|
| 192 | + struct dw_spi_mmio *dwsmmio) |
---|
| 193 | +{ |
---|
| 194 | + dwsmmio->dws.caps = DW_SPI_CAP_CS_OVERRIDE; |
---|
| 195 | + |
---|
| 196 | + return 0; |
---|
| 197 | +} |
---|
| 198 | + |
---|
| 199 | +static int dw_spi_dw_apb_init(struct platform_device *pdev, |
---|
| 200 | + struct dw_spi_mmio *dwsmmio) |
---|
| 201 | +{ |
---|
| 202 | + dw_spi_dma_setup_generic(&dwsmmio->dws); |
---|
| 203 | + |
---|
| 204 | + return 0; |
---|
| 205 | +} |
---|
| 206 | + |
---|
| 207 | +static int dw_spi_dwc_ssi_init(struct platform_device *pdev, |
---|
| 208 | + struct dw_spi_mmio *dwsmmio) |
---|
| 209 | +{ |
---|
| 210 | + dwsmmio->dws.caps = DW_SPI_CAP_DWC_SSI; |
---|
| 211 | + |
---|
| 212 | + dw_spi_dma_setup_generic(&dwsmmio->dws); |
---|
| 213 | + |
---|
| 214 | + return 0; |
---|
| 215 | +} |
---|
| 216 | + |
---|
| 217 | +static int dw_spi_keembay_init(struct platform_device *pdev, |
---|
| 218 | + struct dw_spi_mmio *dwsmmio) |
---|
| 219 | +{ |
---|
| 220 | + dwsmmio->dws.caps = DW_SPI_CAP_KEEMBAY_MST | DW_SPI_CAP_DWC_SSI; |
---|
109 | 221 | |
---|
110 | 222 | return 0; |
---|
111 | 223 | } |
---|
.. | .. |
---|
115 | 227 | int (*init_func)(struct platform_device *pdev, |
---|
116 | 228 | struct dw_spi_mmio *dwsmmio); |
---|
117 | 229 | struct dw_spi_mmio *dwsmmio; |
---|
118 | | - struct dw_spi *dws; |
---|
119 | 230 | struct resource *mem; |
---|
| 231 | + struct dw_spi *dws; |
---|
120 | 232 | int ret; |
---|
121 | 233 | int num_cs; |
---|
122 | 234 | |
---|
.. | .. |
---|
128 | 240 | dws = &dwsmmio->dws; |
---|
129 | 241 | |
---|
130 | 242 | /* Get basic io resource and map it */ |
---|
131 | | - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
---|
132 | | - dws->regs = devm_ioremap_resource(&pdev->dev, mem); |
---|
133 | | - if (IS_ERR(dws->regs)) { |
---|
134 | | - dev_err(&pdev->dev, "SPI region map failed\n"); |
---|
| 243 | + dws->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); |
---|
| 244 | + if (IS_ERR(dws->regs)) |
---|
135 | 245 | return PTR_ERR(dws->regs); |
---|
136 | | - } |
---|
| 246 | + |
---|
| 247 | + dws->paddr = mem->start; |
---|
137 | 248 | |
---|
138 | 249 | dws->irq = platform_get_irq(pdev, 0); |
---|
139 | | - if (dws->irq < 0) { |
---|
140 | | - dev_err(&pdev->dev, "no irq resource?\n"); |
---|
| 250 | + if (dws->irq < 0) |
---|
141 | 251 | return dws->irq; /* -ENXIO */ |
---|
142 | | - } |
---|
143 | 252 | |
---|
144 | 253 | dwsmmio->clk = devm_clk_get(&pdev->dev, NULL); |
---|
145 | 254 | if (IS_ERR(dwsmmio->clk)) |
---|
.. | .. |
---|
147 | 256 | ret = clk_prepare_enable(dwsmmio->clk); |
---|
148 | 257 | if (ret) |
---|
149 | 258 | return ret; |
---|
| 259 | + |
---|
| 260 | + /* Optional clock needed to access the registers */ |
---|
| 261 | + dwsmmio->pclk = devm_clk_get_optional(&pdev->dev, "pclk"); |
---|
| 262 | + if (IS_ERR(dwsmmio->pclk)) { |
---|
| 263 | + ret = PTR_ERR(dwsmmio->pclk); |
---|
| 264 | + goto out_clk; |
---|
| 265 | + } |
---|
| 266 | + ret = clk_prepare_enable(dwsmmio->pclk); |
---|
| 267 | + if (ret) |
---|
| 268 | + goto out_clk; |
---|
| 269 | + |
---|
| 270 | + /* find an optional reset controller */ |
---|
| 271 | + dwsmmio->rstc = devm_reset_control_get_optional_exclusive(&pdev->dev, "spi"); |
---|
| 272 | + if (IS_ERR(dwsmmio->rstc)) { |
---|
| 273 | + ret = PTR_ERR(dwsmmio->rstc); |
---|
| 274 | + goto out_clk; |
---|
| 275 | + } |
---|
| 276 | + reset_control_deassert(dwsmmio->rstc); |
---|
150 | 277 | |
---|
151 | 278 | dws->bus_num = pdev->id; |
---|
152 | 279 | |
---|
.. | .. |
---|
160 | 287 | |
---|
161 | 288 | dws->num_cs = num_cs; |
---|
162 | 289 | |
---|
163 | | - if (pdev->dev.of_node) { |
---|
164 | | - int i; |
---|
165 | | - |
---|
166 | | - for (i = 0; i < dws->num_cs; i++) { |
---|
167 | | - int cs_gpio = of_get_named_gpio(pdev->dev.of_node, |
---|
168 | | - "cs-gpios", i); |
---|
169 | | - |
---|
170 | | - if (cs_gpio == -EPROBE_DEFER) { |
---|
171 | | - ret = cs_gpio; |
---|
172 | | - goto out; |
---|
173 | | - } |
---|
174 | | - |
---|
175 | | - if (gpio_is_valid(cs_gpio)) { |
---|
176 | | - ret = devm_gpio_request(&pdev->dev, cs_gpio, |
---|
177 | | - dev_name(&pdev->dev)); |
---|
178 | | - if (ret) |
---|
179 | | - goto out; |
---|
180 | | - } |
---|
181 | | - } |
---|
182 | | - } |
---|
183 | | - |
---|
184 | 290 | init_func = device_get_match_data(&pdev->dev); |
---|
185 | 291 | if (init_func) { |
---|
186 | 292 | ret = init_func(pdev, dwsmmio); |
---|
187 | 293 | if (ret) |
---|
188 | 294 | goto out; |
---|
189 | 295 | } |
---|
| 296 | + |
---|
| 297 | + pm_runtime_enable(&pdev->dev); |
---|
190 | 298 | |
---|
191 | 299 | ret = dw_spi_add_host(&pdev->dev, dws); |
---|
192 | 300 | if (ret) |
---|
.. | .. |
---|
196 | 304 | return 0; |
---|
197 | 305 | |
---|
198 | 306 | out: |
---|
| 307 | + pm_runtime_disable(&pdev->dev); |
---|
| 308 | + clk_disable_unprepare(dwsmmio->pclk); |
---|
| 309 | +out_clk: |
---|
199 | 310 | clk_disable_unprepare(dwsmmio->clk); |
---|
| 311 | + reset_control_assert(dwsmmio->rstc); |
---|
| 312 | + |
---|
200 | 313 | return ret; |
---|
201 | 314 | } |
---|
202 | 315 | |
---|
.. | .. |
---|
205 | 318 | struct dw_spi_mmio *dwsmmio = platform_get_drvdata(pdev); |
---|
206 | 319 | |
---|
207 | 320 | dw_spi_remove_host(&dwsmmio->dws); |
---|
| 321 | + pm_runtime_disable(&pdev->dev); |
---|
| 322 | + clk_disable_unprepare(dwsmmio->pclk); |
---|
208 | 323 | clk_disable_unprepare(dwsmmio->clk); |
---|
| 324 | + reset_control_assert(dwsmmio->rstc); |
---|
209 | 325 | |
---|
210 | 326 | return 0; |
---|
211 | 327 | } |
---|
212 | 328 | |
---|
213 | 329 | static const struct of_device_id dw_spi_mmio_of_match[] = { |
---|
214 | | - { .compatible = "snps,dw-apb-ssi", }, |
---|
215 | | - { .compatible = "mscc,ocelot-spi", .data = dw_spi_mscc_init}, |
---|
| 330 | + { .compatible = "snps,dw-apb-ssi", .data = dw_spi_dw_apb_init}, |
---|
| 331 | + { .compatible = "mscc,ocelot-spi", .data = dw_spi_mscc_ocelot_init}, |
---|
| 332 | + { .compatible = "mscc,jaguar2-spi", .data = dw_spi_mscc_jaguar2_init}, |
---|
| 333 | + { .compatible = "amazon,alpine-dw-apb-ssi", .data = dw_spi_alpine_init}, |
---|
| 334 | + { .compatible = "renesas,rzn1-spi", .data = dw_spi_dw_apb_init}, |
---|
| 335 | + { .compatible = "snps,dwc-ssi-1.01a", .data = dw_spi_dwc_ssi_init}, |
---|
| 336 | + { .compatible = "intel,keembay-ssi", .data = dw_spi_keembay_init}, |
---|
| 337 | + { .compatible = "microchip,sparx5-spi", dw_spi_mscc_sparx5_init}, |
---|
216 | 338 | { /* end of table */} |
---|
217 | 339 | }; |
---|
218 | 340 | MODULE_DEVICE_TABLE(of, dw_spi_mmio_of_match); |
---|
| 341 | + |
---|
| 342 | +#ifdef CONFIG_ACPI |
---|
| 343 | +static const struct acpi_device_id dw_spi_mmio_acpi_match[] = { |
---|
| 344 | + {"HISI0173", (kernel_ulong_t)dw_spi_dw_apb_init}, |
---|
| 345 | + {}, |
---|
| 346 | +}; |
---|
| 347 | +MODULE_DEVICE_TABLE(acpi, dw_spi_mmio_acpi_match); |
---|
| 348 | +#endif |
---|
219 | 349 | |
---|
220 | 350 | static struct platform_driver dw_spi_mmio_driver = { |
---|
221 | 351 | .probe = dw_spi_mmio_probe, |
---|
.. | .. |
---|
223 | 353 | .driver = { |
---|
224 | 354 | .name = DRIVER_NAME, |
---|
225 | 355 | .of_match_table = dw_spi_mmio_of_match, |
---|
| 356 | + .acpi_match_table = ACPI_PTR(dw_spi_mmio_acpi_match), |
---|
226 | 357 | }, |
---|
227 | 358 | }; |
---|
228 | 359 | module_platform_driver(dw_spi_mmio_driver); |
---|