| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Altera SPI driver |
|---|
| 3 | 4 | * |
|---|
| .. | .. |
|---|
| 7 | 8 | * Copyright (c) 2006 Ben Dooks |
|---|
| 8 | 9 | * Copyright (c) 2006 Simtec Electronics |
|---|
| 9 | 10 | * Ben Dooks <ben@simtec.co.uk> |
|---|
| 10 | | - * |
|---|
| 11 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 12 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 13 | | - * published by the Free Software Foundation. |
|---|
| 14 | 11 | */ |
|---|
| 15 | 12 | |
|---|
| 16 | 13 | #include <linux/interrupt.h> |
|---|
| 17 | 14 | #include <linux/errno.h> |
|---|
| 18 | 15 | #include <linux/module.h> |
|---|
| 19 | 16 | #include <linux/platform_device.h> |
|---|
| 17 | +#include <linux/spi/altera.h> |
|---|
| 20 | 18 | #include <linux/spi/spi.h> |
|---|
| 21 | 19 | #include <linux/io.h> |
|---|
| 22 | 20 | #include <linux/of.h> |
|---|
| .. | .. |
|---|
| 43 | 41 | #define ALTERA_SPI_CONTROL_IE_MSK 0x100 |
|---|
| 44 | 42 | #define ALTERA_SPI_CONTROL_SSO_MSK 0x400 |
|---|
| 45 | 43 | |
|---|
| 44 | +#define ALTERA_SPI_MAX_CS 32 |
|---|
| 45 | + |
|---|
| 46 | +enum altera_spi_type { |
|---|
| 47 | + ALTERA_SPI_TYPE_UNKNOWN, |
|---|
| 48 | + ALTERA_SPI_TYPE_SUBDEV, |
|---|
| 49 | +}; |
|---|
| 50 | + |
|---|
| 46 | 51 | struct altera_spi { |
|---|
| 47 | | - void __iomem *base; |
|---|
| 48 | 52 | int irq; |
|---|
| 49 | 53 | int len; |
|---|
| 50 | 54 | int count; |
|---|
| 51 | 55 | int bytes_per_word; |
|---|
| 52 | | - unsigned long imr; |
|---|
| 56 | + u32 imr; |
|---|
| 53 | 57 | |
|---|
| 54 | 58 | /* data buffers */ |
|---|
| 55 | 59 | const unsigned char *tx; |
|---|
| 56 | 60 | unsigned char *rx; |
|---|
| 61 | + |
|---|
| 62 | + struct regmap *regmap; |
|---|
| 63 | + u32 regoff; |
|---|
| 64 | + struct device *dev; |
|---|
| 57 | 65 | }; |
|---|
| 66 | + |
|---|
| 67 | +static const struct regmap_config spi_altera_config = { |
|---|
| 68 | + .reg_bits = 32, |
|---|
| 69 | + .reg_stride = 4, |
|---|
| 70 | + .val_bits = 32, |
|---|
| 71 | + .fast_io = true, |
|---|
| 72 | +}; |
|---|
| 73 | + |
|---|
| 74 | +static int altr_spi_writel(struct altera_spi *hw, unsigned int reg, |
|---|
| 75 | + unsigned int val) |
|---|
| 76 | +{ |
|---|
| 77 | + int ret; |
|---|
| 78 | + |
|---|
| 79 | + ret = regmap_write(hw->regmap, hw->regoff + reg, val); |
|---|
| 80 | + if (ret) |
|---|
| 81 | + dev_err(hw->dev, "fail to write reg 0x%x val 0x%x: %d\n", |
|---|
| 82 | + reg, val, ret); |
|---|
| 83 | + |
|---|
| 84 | + return ret; |
|---|
| 85 | +} |
|---|
| 86 | + |
|---|
| 87 | +static int altr_spi_readl(struct altera_spi *hw, unsigned int reg, |
|---|
| 88 | + unsigned int *val) |
|---|
| 89 | +{ |
|---|
| 90 | + int ret; |
|---|
| 91 | + |
|---|
| 92 | + ret = regmap_read(hw->regmap, hw->regoff + reg, val); |
|---|
| 93 | + if (ret) |
|---|
| 94 | + dev_err(hw->dev, "fail to read reg 0x%x: %d\n", reg, ret); |
|---|
| 95 | + |
|---|
| 96 | + return ret; |
|---|
| 97 | +} |
|---|
| 58 | 98 | |
|---|
| 59 | 99 | static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev) |
|---|
| 60 | 100 | { |
|---|
| .. | .. |
|---|
| 67 | 107 | |
|---|
| 68 | 108 | if (is_high) { |
|---|
| 69 | 109 | hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK; |
|---|
| 70 | | - writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); |
|---|
| 71 | | - writel(0, hw->base + ALTERA_SPI_SLAVE_SEL); |
|---|
| 110 | + altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr); |
|---|
| 111 | + altr_spi_writel(hw, ALTERA_SPI_SLAVE_SEL, 0); |
|---|
| 72 | 112 | } else { |
|---|
| 73 | | - writel(BIT(spi->chip_select), hw->base + ALTERA_SPI_SLAVE_SEL); |
|---|
| 113 | + altr_spi_writel(hw, ALTERA_SPI_SLAVE_SEL, |
|---|
| 114 | + BIT(spi->chip_select)); |
|---|
| 74 | 115 | hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK; |
|---|
| 75 | | - writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); |
|---|
| 116 | + altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr); |
|---|
| 76 | 117 | } |
|---|
| 77 | 118 | } |
|---|
| 78 | 119 | |
|---|
| .. | .. |
|---|
| 89 | 130 | txd = (hw->tx[hw->count * 2] |
|---|
| 90 | 131 | | (hw->tx[hw->count * 2 + 1] << 8)); |
|---|
| 91 | 132 | break; |
|---|
| 133 | + case 4: |
|---|
| 134 | + txd = (hw->tx[hw->count * 4] |
|---|
| 135 | + | (hw->tx[hw->count * 4 + 1] << 8) |
|---|
| 136 | + | (hw->tx[hw->count * 4 + 2] << 16) |
|---|
| 137 | + | (hw->tx[hw->count * 4 + 3] << 24)); |
|---|
| 138 | + break; |
|---|
| 139 | + |
|---|
| 92 | 140 | } |
|---|
| 93 | 141 | } |
|---|
| 94 | 142 | |
|---|
| 95 | | - writel(txd, hw->base + ALTERA_SPI_TXDATA); |
|---|
| 143 | + altr_spi_writel(hw, ALTERA_SPI_TXDATA, txd); |
|---|
| 96 | 144 | } |
|---|
| 97 | 145 | |
|---|
| 98 | 146 | static void altera_spi_rx_word(struct altera_spi *hw) |
|---|
| 99 | 147 | { |
|---|
| 100 | 148 | unsigned int rxd; |
|---|
| 101 | 149 | |
|---|
| 102 | | - rxd = readl(hw->base + ALTERA_SPI_RXDATA); |
|---|
| 150 | + altr_spi_readl(hw, ALTERA_SPI_RXDATA, &rxd); |
|---|
| 103 | 151 | if (hw->rx) { |
|---|
| 104 | 152 | switch (hw->bytes_per_word) { |
|---|
| 105 | 153 | case 1: |
|---|
| .. | .. |
|---|
| 109 | 157 | hw->rx[hw->count * 2] = rxd; |
|---|
| 110 | 158 | hw->rx[hw->count * 2 + 1] = rxd >> 8; |
|---|
| 111 | 159 | break; |
|---|
| 160 | + case 4: |
|---|
| 161 | + hw->rx[hw->count * 4] = rxd; |
|---|
| 162 | + hw->rx[hw->count * 4 + 1] = rxd >> 8; |
|---|
| 163 | + hw->rx[hw->count * 4 + 2] = rxd >> 16; |
|---|
| 164 | + hw->rx[hw->count * 4 + 3] = rxd >> 24; |
|---|
| 165 | + break; |
|---|
| 166 | + |
|---|
| 112 | 167 | } |
|---|
| 113 | 168 | } |
|---|
| 114 | 169 | |
|---|
| .. | .. |
|---|
| 119 | 174 | struct spi_device *spi, struct spi_transfer *t) |
|---|
| 120 | 175 | { |
|---|
| 121 | 176 | struct altera_spi *hw = spi_master_get_devdata(master); |
|---|
| 177 | + u32 val; |
|---|
| 122 | 178 | |
|---|
| 123 | 179 | hw->tx = t->tx_buf; |
|---|
| 124 | 180 | hw->rx = t->rx_buf; |
|---|
| .. | .. |
|---|
| 129 | 185 | if (hw->irq >= 0) { |
|---|
| 130 | 186 | /* enable receive interrupt */ |
|---|
| 131 | 187 | hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK; |
|---|
| 132 | | - writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); |
|---|
| 188 | + altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr); |
|---|
| 133 | 189 | |
|---|
| 134 | 190 | /* send the first byte */ |
|---|
| 135 | 191 | altera_spi_tx_word(hw); |
|---|
| 136 | | - } else { |
|---|
| 137 | | - while (hw->count < hw->len) { |
|---|
| 138 | | - altera_spi_tx_word(hw); |
|---|
| 139 | 192 | |
|---|
| 140 | | - while (!(readl(hw->base + ALTERA_SPI_STATUS) & |
|---|
| 141 | | - ALTERA_SPI_STATUS_RRDY_MSK)) |
|---|
| 142 | | - cpu_relax(); |
|---|
| 143 | | - |
|---|
| 144 | | - altera_spi_rx_word(hw); |
|---|
| 145 | | - } |
|---|
| 146 | | - spi_finalize_current_transfer(master); |
|---|
| 193 | + return 1; |
|---|
| 147 | 194 | } |
|---|
| 148 | 195 | |
|---|
| 149 | | - return t->len; |
|---|
| 196 | + while (hw->count < hw->len) { |
|---|
| 197 | + altera_spi_tx_word(hw); |
|---|
| 198 | + |
|---|
| 199 | + for (;;) { |
|---|
| 200 | + altr_spi_readl(hw, ALTERA_SPI_STATUS, &val); |
|---|
| 201 | + if (val & ALTERA_SPI_STATUS_RRDY_MSK) |
|---|
| 202 | + break; |
|---|
| 203 | + |
|---|
| 204 | + cpu_relax(); |
|---|
| 205 | + } |
|---|
| 206 | + |
|---|
| 207 | + altera_spi_rx_word(hw); |
|---|
| 208 | + } |
|---|
| 209 | + spi_finalize_current_transfer(master); |
|---|
| 210 | + |
|---|
| 211 | + return 0; |
|---|
| 150 | 212 | } |
|---|
| 151 | 213 | |
|---|
| 152 | 214 | static irqreturn_t altera_spi_irq(int irq, void *dev) |
|---|
| .. | .. |
|---|
| 161 | 223 | } else { |
|---|
| 162 | 224 | /* disable receive interrupt */ |
|---|
| 163 | 225 | hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK; |
|---|
| 164 | | - writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); |
|---|
| 226 | + altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr); |
|---|
| 165 | 227 | |
|---|
| 166 | 228 | spi_finalize_current_transfer(master); |
|---|
| 167 | 229 | } |
|---|
| .. | .. |
|---|
| 171 | 233 | |
|---|
| 172 | 234 | static int altera_spi_probe(struct platform_device *pdev) |
|---|
| 173 | 235 | { |
|---|
| 236 | + const struct platform_device_id *platid = platform_get_device_id(pdev); |
|---|
| 237 | + struct altera_spi_platform_data *pdata = dev_get_platdata(&pdev->dev); |
|---|
| 238 | + enum altera_spi_type type = ALTERA_SPI_TYPE_UNKNOWN; |
|---|
| 174 | 239 | struct altera_spi *hw; |
|---|
| 175 | 240 | struct spi_master *master; |
|---|
| 176 | | - struct resource *res; |
|---|
| 177 | 241 | int err = -ENODEV; |
|---|
| 242 | + u32 val; |
|---|
| 243 | + u16 i; |
|---|
| 178 | 244 | |
|---|
| 179 | 245 | master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi)); |
|---|
| 180 | 246 | if (!master) |
|---|
| .. | .. |
|---|
| 182 | 248 | |
|---|
| 183 | 249 | /* setup the master state. */ |
|---|
| 184 | 250 | master->bus_num = pdev->id; |
|---|
| 185 | | - master->num_chipselect = 16; |
|---|
| 186 | | - master->mode_bits = SPI_CS_HIGH; |
|---|
| 187 | | - master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 16); |
|---|
| 251 | + |
|---|
| 252 | + if (pdata) { |
|---|
| 253 | + if (pdata->num_chipselect > ALTERA_SPI_MAX_CS) { |
|---|
| 254 | + dev_err(&pdev->dev, |
|---|
| 255 | + "Invalid number of chipselect: %hu\n", |
|---|
| 256 | + pdata->num_chipselect); |
|---|
| 257 | + err = -EINVAL; |
|---|
| 258 | + goto exit; |
|---|
| 259 | + } |
|---|
| 260 | + |
|---|
| 261 | + master->num_chipselect = pdata->num_chipselect; |
|---|
| 262 | + master->mode_bits = pdata->mode_bits; |
|---|
| 263 | + master->bits_per_word_mask = pdata->bits_per_word_mask; |
|---|
| 264 | + } else { |
|---|
| 265 | + master->num_chipselect = 16; |
|---|
| 266 | + master->mode_bits = SPI_CS_HIGH; |
|---|
| 267 | + master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 16); |
|---|
| 268 | + } |
|---|
| 269 | + |
|---|
| 188 | 270 | master->dev.of_node = pdev->dev.of_node; |
|---|
| 189 | 271 | master->transfer_one = altera_spi_txrx; |
|---|
| 190 | 272 | master->set_cs = altera_spi_set_cs; |
|---|
| 191 | 273 | |
|---|
| 192 | 274 | hw = spi_master_get_devdata(master); |
|---|
| 275 | + hw->dev = &pdev->dev; |
|---|
| 276 | + |
|---|
| 277 | + if (platid) |
|---|
| 278 | + type = platid->driver_data; |
|---|
| 193 | 279 | |
|---|
| 194 | 280 | /* find and map our resources */ |
|---|
| 195 | | - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|---|
| 196 | | - hw->base = devm_ioremap_resource(&pdev->dev, res); |
|---|
| 197 | | - if (IS_ERR(hw->base)) { |
|---|
| 198 | | - err = PTR_ERR(hw->base); |
|---|
| 199 | | - goto exit; |
|---|
| 281 | + if (type == ALTERA_SPI_TYPE_SUBDEV) { |
|---|
| 282 | + struct resource *regoff; |
|---|
| 283 | + |
|---|
| 284 | + hw->regmap = dev_get_regmap(pdev->dev.parent, NULL); |
|---|
| 285 | + if (!hw->regmap) { |
|---|
| 286 | + dev_err(&pdev->dev, "get regmap failed\n"); |
|---|
| 287 | + goto exit; |
|---|
| 288 | + } |
|---|
| 289 | + |
|---|
| 290 | + regoff = platform_get_resource(pdev, IORESOURCE_REG, 0); |
|---|
| 291 | + if (regoff) |
|---|
| 292 | + hw->regoff = regoff->start; |
|---|
| 293 | + } else { |
|---|
| 294 | + void __iomem *res; |
|---|
| 295 | + |
|---|
| 296 | + res = devm_platform_ioremap_resource(pdev, 0); |
|---|
| 297 | + if (IS_ERR(res)) { |
|---|
| 298 | + err = PTR_ERR(res); |
|---|
| 299 | + goto exit; |
|---|
| 300 | + } |
|---|
| 301 | + |
|---|
| 302 | + hw->regmap = devm_regmap_init_mmio(&pdev->dev, res, |
|---|
| 303 | + &spi_altera_config); |
|---|
| 304 | + if (IS_ERR(hw->regmap)) { |
|---|
| 305 | + dev_err(&pdev->dev, "regmap mmio init failed\n"); |
|---|
| 306 | + err = PTR_ERR(hw->regmap); |
|---|
| 307 | + goto exit; |
|---|
| 308 | + } |
|---|
| 200 | 309 | } |
|---|
| 310 | + |
|---|
| 201 | 311 | /* program defaults into the registers */ |
|---|
| 202 | 312 | hw->imr = 0; /* disable spi interrupts */ |
|---|
| 203 | | - writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); |
|---|
| 204 | | - writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */ |
|---|
| 205 | | - if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK) |
|---|
| 206 | | - readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */ |
|---|
| 313 | + altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr); |
|---|
| 314 | + altr_spi_writel(hw, ALTERA_SPI_STATUS, 0); /* clear status reg */ |
|---|
| 315 | + altr_spi_readl(hw, ALTERA_SPI_STATUS, &val); |
|---|
| 316 | + if (val & ALTERA_SPI_STATUS_RRDY_MSK) |
|---|
| 317 | + altr_spi_readl(hw, ALTERA_SPI_RXDATA, &val); /* flush rxdata */ |
|---|
| 207 | 318 | /* irq is optional */ |
|---|
| 208 | 319 | hw->irq = platform_get_irq(pdev, 0); |
|---|
| 209 | 320 | if (hw->irq >= 0) { |
|---|
| .. | .. |
|---|
| 216 | 327 | err = devm_spi_register_master(&pdev->dev, master); |
|---|
| 217 | 328 | if (err) |
|---|
| 218 | 329 | goto exit; |
|---|
| 219 | | - dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq); |
|---|
| 330 | + |
|---|
| 331 | + if (pdata) { |
|---|
| 332 | + for (i = 0; i < pdata->num_devices; i++) { |
|---|
| 333 | + if (!spi_new_device(master, pdata->devices + i)) |
|---|
| 334 | + dev_warn(&pdev->dev, |
|---|
| 335 | + "unable to create SPI device: %s\n", |
|---|
| 336 | + pdata->devices[i].modalias); |
|---|
| 337 | + } |
|---|
| 338 | + } |
|---|
| 339 | + |
|---|
| 340 | + dev_info(&pdev->dev, "regoff %u, irq %d\n", hw->regoff, hw->irq); |
|---|
| 220 | 341 | |
|---|
| 221 | 342 | return 0; |
|---|
| 222 | 343 | exit: |
|---|
| .. | .. |
|---|
| 233 | 354 | MODULE_DEVICE_TABLE(of, altera_spi_match); |
|---|
| 234 | 355 | #endif /* CONFIG_OF */ |
|---|
| 235 | 356 | |
|---|
| 357 | +static const struct platform_device_id altera_spi_ids[] = { |
|---|
| 358 | + { DRV_NAME, ALTERA_SPI_TYPE_UNKNOWN }, |
|---|
| 359 | + { "subdev_spi_altera", ALTERA_SPI_TYPE_SUBDEV }, |
|---|
| 360 | + { } |
|---|
| 361 | +}; |
|---|
| 362 | +MODULE_DEVICE_TABLE(platform, altera_spi_ids); |
|---|
| 363 | + |
|---|
| 236 | 364 | static struct platform_driver altera_spi_driver = { |
|---|
| 237 | 365 | .probe = altera_spi_probe, |
|---|
| 238 | 366 | .driver = { |
|---|
| .. | .. |
|---|
| 240 | 368 | .pm = NULL, |
|---|
| 241 | 369 | .of_match_table = of_match_ptr(altera_spi_match), |
|---|
| 242 | 370 | }, |
|---|
| 371 | + .id_table = altera_spi_ids, |
|---|
| 243 | 372 | }; |
|---|
| 244 | 373 | module_platform_driver(altera_spi_driver); |
|---|
| 245 | 374 | |
|---|