linux/drivers/spi/spi-axi-spi-engine.c
<<
>>
Prefs
   1/*
   2 * SPI-Engine SPI controller driver
   3 * Copyright 2015 Analog Devices Inc.
   4 *  Author: Lars-Peter Clausen <lars@metafoo.de>
   5 *
   6 * Licensed under the GPL-2.
   7 */
   8
   9#include <linux/clk.h>
  10#include <linux/interrupt.h>
  11#include <linux/io.h>
  12#include <linux/of.h>
  13#include <linux/module.h>
  14#include <linux/platform_device.h>
  15#include <linux/spi/spi.h>
  16
  17#define SPI_ENGINE_VERSION_MAJOR(x)     ((x >> 16) & 0xff)
  18#define SPI_ENGINE_VERSION_MINOR(x)     ((x >> 8) & 0xff)
  19#define SPI_ENGINE_VERSION_PATCH(x)     (x & 0xff)
  20
  21#define SPI_ENGINE_REG_VERSION                  0x00
  22
  23#define SPI_ENGINE_REG_RESET                    0x40
  24
  25#define SPI_ENGINE_REG_INT_ENABLE               0x80
  26#define SPI_ENGINE_REG_INT_PENDING              0x84
  27#define SPI_ENGINE_REG_INT_SOURCE               0x88
  28
  29#define SPI_ENGINE_REG_SYNC_ID                  0xc0
  30
  31#define SPI_ENGINE_REG_CMD_FIFO_ROOM            0xd0
  32#define SPI_ENGINE_REG_SDO_FIFO_ROOM            0xd4
  33#define SPI_ENGINE_REG_SDI_FIFO_LEVEL           0xd8
  34
  35#define SPI_ENGINE_REG_CMD_FIFO                 0xe0
  36#define SPI_ENGINE_REG_SDO_DATA_FIFO            0xe4
  37#define SPI_ENGINE_REG_SDI_DATA_FIFO            0xe8
  38#define SPI_ENGINE_REG_SDI_DATA_FIFO_PEEK       0xec
  39
  40#define SPI_ENGINE_INT_CMD_ALMOST_EMPTY         BIT(0)
  41#define SPI_ENGINE_INT_SDO_ALMOST_EMPTY         BIT(1)
  42#define SPI_ENGINE_INT_SDI_ALMOST_FULL          BIT(2)
  43#define SPI_ENGINE_INT_SYNC                     BIT(3)
  44
  45#define SPI_ENGINE_CONFIG_CPHA                  BIT(0)
  46#define SPI_ENGINE_CONFIG_CPOL                  BIT(1)
  47#define SPI_ENGINE_CONFIG_3WIRE                 BIT(2)
  48
  49#define SPI_ENGINE_INST_TRANSFER                0x0
  50#define SPI_ENGINE_INST_ASSERT                  0x1
  51#define SPI_ENGINE_INST_WRITE                   0x2
  52#define SPI_ENGINE_INST_MISC                    0x3
  53
  54#define SPI_ENGINE_CMD_REG_CLK_DIV              0x0
  55#define SPI_ENGINE_CMD_REG_CONFIG               0x1
  56
  57#define SPI_ENGINE_MISC_SYNC                    0x0
  58#define SPI_ENGINE_MISC_SLEEP                   0x1
  59
  60#define SPI_ENGINE_TRANSFER_WRITE               0x1
  61#define SPI_ENGINE_TRANSFER_READ                0x2
  62
  63#define SPI_ENGINE_CMD(inst, arg1, arg2) \
  64        (((inst) << 12) | ((arg1) << 8) | (arg2))
  65
  66#define SPI_ENGINE_CMD_TRANSFER(flags, n) \
  67        SPI_ENGINE_CMD(SPI_ENGINE_INST_TRANSFER, (flags), (n))
  68#define SPI_ENGINE_CMD_ASSERT(delay, cs) \
  69        SPI_ENGINE_CMD(SPI_ENGINE_INST_ASSERT, (delay), (cs))
  70#define SPI_ENGINE_CMD_WRITE(reg, val) \
  71        SPI_ENGINE_CMD(SPI_ENGINE_INST_WRITE, (reg), (val))
  72#define SPI_ENGINE_CMD_SLEEP(delay) \
  73        SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SLEEP, (delay))
  74#define SPI_ENGINE_CMD_SYNC(id) \
  75        SPI_ENGINE_CMD(SPI_ENGINE_INST_MISC, SPI_ENGINE_MISC_SYNC, (id))
  76
  77struct spi_engine_program {
  78        unsigned int length;
  79        uint16_t instructions[];
  80};
  81
  82struct spi_engine {
  83        struct clk *clk;
  84        struct clk *ref_clk;
  85
  86        spinlock_t lock;
  87
  88        void __iomem *base;
  89
  90        struct spi_message *msg;
  91        struct spi_engine_program *p;
  92        unsigned cmd_length;
  93        const uint16_t *cmd_buf;
  94
  95        struct spi_transfer *tx_xfer;
  96        unsigned int tx_length;
  97        const uint8_t *tx_buf;
  98
  99        struct spi_transfer *rx_xfer;
 100        unsigned int rx_length;
 101        uint8_t *rx_buf;
 102
 103        unsigned int sync_id;
 104        unsigned int completed_id;
 105
 106        unsigned int int_enable;
 107};
 108
 109static void spi_engine_program_add_cmd(struct spi_engine_program *p,
 110        bool dry, uint16_t cmd)
 111{
 112        if (!dry)
 113                p->instructions[p->length] = cmd;
 114        p->length++;
 115}
 116
 117static unsigned int spi_engine_get_config(struct spi_device *spi)
 118{
 119        unsigned int config = 0;
 120
 121        if (spi->mode & SPI_CPOL)
 122                config |= SPI_ENGINE_CONFIG_CPOL;
 123        if (spi->mode & SPI_CPHA)
 124                config |= SPI_ENGINE_CONFIG_CPHA;
 125        if (spi->mode & SPI_3WIRE)
 126                config |= SPI_ENGINE_CONFIG_3WIRE;
 127
 128        return config;
 129}
 130
 131static unsigned int spi_engine_get_clk_div(struct spi_engine *spi_engine,
 132        struct spi_device *spi, struct spi_transfer *xfer)
 133{
 134        unsigned int clk_div;
 135
 136        clk_div = DIV_ROUND_UP(clk_get_rate(spi_engine->ref_clk),
 137                xfer->speed_hz * 2);
 138        if (clk_div > 255)
 139                clk_div = 255;
 140        else if (clk_div > 0)
 141                clk_div -= 1;
 142
 143        return clk_div;
 144}
 145
 146static void spi_engine_gen_xfer(struct spi_engine_program *p, bool dry,
 147        struct spi_transfer *xfer)
 148{
 149        unsigned int len = xfer->len;
 150
 151        while (len) {
 152                unsigned int n = min(len, 256U);
 153                unsigned int flags = 0;
 154
 155                if (xfer->tx_buf)
 156                        flags |= SPI_ENGINE_TRANSFER_WRITE;
 157                if (xfer->rx_buf)
 158                        flags |= SPI_ENGINE_TRANSFER_READ;
 159
 160                spi_engine_program_add_cmd(p, dry,
 161                        SPI_ENGINE_CMD_TRANSFER(flags, n - 1));
 162                len -= n;
 163        }
 164}
 165
 166static void spi_engine_gen_sleep(struct spi_engine_program *p, bool dry,
 167        struct spi_engine *spi_engine, unsigned int clk_div, unsigned int delay)
 168{
 169        unsigned int spi_clk = clk_get_rate(spi_engine->ref_clk);
 170        unsigned int t;
 171
 172        if (delay == 0)
 173                return;
 174
 175        t = DIV_ROUND_UP(delay * spi_clk, (clk_div + 1) * 2);
 176        while (t) {
 177                unsigned int n = min(t, 256U);
 178
 179                spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_SLEEP(n - 1));
 180                t -= n;
 181        }
 182}
 183
 184static void spi_engine_gen_cs(struct spi_engine_program *p, bool dry,
 185                struct spi_device *spi, bool assert)
 186{
 187        unsigned int mask = 0xff;
 188
 189        if (assert)
 190                mask ^= BIT(spi->chip_select);
 191
 192        spi_engine_program_add_cmd(p, dry, SPI_ENGINE_CMD_ASSERT(1, mask));
 193}
 194
 195static int spi_engine_compile_message(struct spi_engine *spi_engine,
 196        struct spi_message *msg, bool dry, struct spi_engine_program *p)
 197{
 198        struct spi_device *spi = msg->spi;
 199        struct spi_transfer *xfer;
 200        int clk_div, new_clk_div;
 201        bool cs_change = true;
 202
 203        clk_div = -1;
 204
 205        spi_engine_program_add_cmd(p, dry,
 206                SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CONFIG,
 207                        spi_engine_get_config(spi)));
 208
 209        list_for_each_entry(xfer, &msg->transfers, transfer_list) {
 210                new_clk_div = spi_engine_get_clk_div(spi_engine, spi, xfer);
 211                if (new_clk_div != clk_div) {
 212                        clk_div = new_clk_div;
 213                        spi_engine_program_add_cmd(p, dry,
 214                                SPI_ENGINE_CMD_WRITE(SPI_ENGINE_CMD_REG_CLK_DIV,
 215                                        clk_div));
 216                }
 217
 218                if (cs_change)
 219                        spi_engine_gen_cs(p, dry, spi, true);
 220
 221                spi_engine_gen_xfer(p, dry, xfer);
 222                spi_engine_gen_sleep(p, dry, spi_engine, clk_div,
 223                        xfer->delay_usecs);
 224
 225                cs_change = xfer->cs_change;
 226                if (list_is_last(&xfer->transfer_list, &msg->transfers))
 227                        cs_change = !cs_change;
 228
 229                if (cs_change)
 230                        spi_engine_gen_cs(p, dry, spi, false);
 231        }
 232
 233        return 0;
 234}
 235
 236static void spi_engine_xfer_next(struct spi_engine *spi_engine,
 237        struct spi_transfer **_xfer)
 238{
 239        struct spi_message *msg = spi_engine->msg;
 240        struct spi_transfer *xfer = *_xfer;
 241
 242        if (!xfer) {
 243                xfer = list_first_entry(&msg->transfers,
 244                        struct spi_transfer, transfer_list);
 245        } else if (list_is_last(&xfer->transfer_list, &msg->transfers)) {
 246                xfer = NULL;
 247        } else {
 248                xfer = list_next_entry(xfer, transfer_list);
 249        }
 250
 251        *_xfer = xfer;
 252}
 253
 254static void spi_engine_tx_next(struct spi_engine *spi_engine)
 255{
 256        struct spi_transfer *xfer = spi_engine->tx_xfer;
 257
 258        do {
 259                spi_engine_xfer_next(spi_engine, &xfer);
 260        } while (xfer && !xfer->tx_buf);
 261
 262        spi_engine->tx_xfer = xfer;
 263        if (xfer) {
 264                spi_engine->tx_length = xfer->len;
 265                spi_engine->tx_buf = xfer->tx_buf;
 266        } else {
 267                spi_engine->tx_buf = NULL;
 268        }
 269}
 270
 271static void spi_engine_rx_next(struct spi_engine *spi_engine)
 272{
 273        struct spi_transfer *xfer = spi_engine->rx_xfer;
 274
 275        do {
 276                spi_engine_xfer_next(spi_engine, &xfer);
 277        } while (xfer && !xfer->rx_buf);
 278
 279        spi_engine->rx_xfer = xfer;
 280        if (xfer) {
 281                spi_engine->rx_length = xfer->len;
 282                spi_engine->rx_buf = xfer->rx_buf;
 283        } else {
 284                spi_engine->rx_buf = NULL;
 285        }
 286}
 287
 288static bool spi_engine_write_cmd_fifo(struct spi_engine *spi_engine)
 289{
 290        void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_CMD_FIFO;
 291        unsigned int n, m, i;
 292        const uint16_t *buf;
 293
 294        n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_CMD_FIFO_ROOM);
 295        while (n && spi_engine->cmd_length) {
 296                m = min(n, spi_engine->cmd_length);
 297                buf = spi_engine->cmd_buf;
 298                for (i = 0; i < m; i++)
 299                        writel_relaxed(buf[i], addr);
 300                spi_engine->cmd_buf += m;
 301                spi_engine->cmd_length -= m;
 302                n -= m;
 303        }
 304
 305        return spi_engine->cmd_length != 0;
 306}
 307
 308static bool spi_engine_write_tx_fifo(struct spi_engine *spi_engine)
 309{
 310        void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDO_DATA_FIFO;
 311        unsigned int n, m, i;
 312        const uint8_t *buf;
 313
 314        n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDO_FIFO_ROOM);
 315        while (n && spi_engine->tx_length) {
 316                m = min(n, spi_engine->tx_length);
 317                buf = spi_engine->tx_buf;
 318                for (i = 0; i < m; i++)
 319                        writel_relaxed(buf[i], addr);
 320                spi_engine->tx_buf += m;
 321                spi_engine->tx_length -= m;
 322                n -= m;
 323                if (spi_engine->tx_length == 0)
 324                        spi_engine_tx_next(spi_engine);
 325        }
 326
 327        return spi_engine->tx_length != 0;
 328}
 329
 330static bool spi_engine_read_rx_fifo(struct spi_engine *spi_engine)
 331{
 332        void __iomem *addr = spi_engine->base + SPI_ENGINE_REG_SDI_DATA_FIFO;
 333        unsigned int n, m, i;
 334        uint8_t *buf;
 335
 336        n = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_SDI_FIFO_LEVEL);
 337        while (n && spi_engine->rx_length) {
 338                m = min(n, spi_engine->rx_length);
 339                buf = spi_engine->rx_buf;
 340                for (i = 0; i < m; i++)
 341                        buf[i] = readl_relaxed(addr);
 342                spi_engine->rx_buf += m;
 343                spi_engine->rx_length -= m;
 344                n -= m;
 345                if (spi_engine->rx_length == 0)
 346                        spi_engine_rx_next(spi_engine);
 347        }
 348
 349        return spi_engine->rx_length != 0;
 350}
 351
 352static irqreturn_t spi_engine_irq(int irq, void *devid)
 353{
 354        struct spi_master *master = devid;
 355        struct spi_engine *spi_engine = spi_master_get_devdata(master);
 356        unsigned int disable_int = 0;
 357        unsigned int pending;
 358
 359        pending = readl_relaxed(spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
 360
 361        if (pending & SPI_ENGINE_INT_SYNC) {
 362                writel_relaxed(SPI_ENGINE_INT_SYNC,
 363                        spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
 364                spi_engine->completed_id = readl_relaxed(
 365                        spi_engine->base + SPI_ENGINE_REG_SYNC_ID);
 366        }
 367
 368        spin_lock(&spi_engine->lock);
 369
 370        if (pending & SPI_ENGINE_INT_CMD_ALMOST_EMPTY) {
 371                if (!spi_engine_write_cmd_fifo(spi_engine))
 372                        disable_int |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY;
 373        }
 374
 375        if (pending & SPI_ENGINE_INT_SDO_ALMOST_EMPTY) {
 376                if (!spi_engine_write_tx_fifo(spi_engine))
 377                        disable_int |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY;
 378        }
 379
 380        if (pending & (SPI_ENGINE_INT_SDI_ALMOST_FULL | SPI_ENGINE_INT_SYNC)) {
 381                if (!spi_engine_read_rx_fifo(spi_engine))
 382                        disable_int |= SPI_ENGINE_INT_SDI_ALMOST_FULL;
 383        }
 384
 385        if (pending & SPI_ENGINE_INT_SYNC) {
 386                if (spi_engine->msg &&
 387                    spi_engine->completed_id == spi_engine->sync_id) {
 388                        struct spi_message *msg = spi_engine->msg;
 389
 390                        kfree(spi_engine->p);
 391                        msg->status = 0;
 392                        msg->actual_length = msg->frame_length;
 393                        spi_engine->msg = NULL;
 394                        spi_finalize_current_message(master);
 395                        disable_int |= SPI_ENGINE_INT_SYNC;
 396                }
 397        }
 398
 399        if (disable_int) {
 400                spi_engine->int_enable &= ~disable_int;
 401                writel_relaxed(spi_engine->int_enable,
 402                        spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
 403        }
 404
 405        spin_unlock(&spi_engine->lock);
 406
 407        return IRQ_HANDLED;
 408}
 409
 410static int spi_engine_transfer_one_message(struct spi_master *master,
 411        struct spi_message *msg)
 412{
 413        struct spi_engine_program p_dry, *p;
 414        struct spi_engine *spi_engine = spi_master_get_devdata(master);
 415        unsigned int int_enable = 0;
 416        unsigned long flags;
 417        size_t size;
 418
 419        p_dry.length = 0;
 420        spi_engine_compile_message(spi_engine, msg, true, &p_dry);
 421
 422        size = sizeof(*p->instructions) * (p_dry.length + 1);
 423        p = kzalloc(sizeof(*p) + size, GFP_KERNEL);
 424        if (!p)
 425                return -ENOMEM;
 426        spi_engine_compile_message(spi_engine, msg, false, p);
 427
 428        spin_lock_irqsave(&spi_engine->lock, flags);
 429        spi_engine->sync_id = (spi_engine->sync_id + 1) & 0xff;
 430        spi_engine_program_add_cmd(p, false,
 431                SPI_ENGINE_CMD_SYNC(spi_engine->sync_id));
 432
 433        spi_engine->msg = msg;
 434        spi_engine->p = p;
 435
 436        spi_engine->cmd_buf = p->instructions;
 437        spi_engine->cmd_length = p->length;
 438        if (spi_engine_write_cmd_fifo(spi_engine))
 439                int_enable |= SPI_ENGINE_INT_CMD_ALMOST_EMPTY;
 440
 441        spi_engine_tx_next(spi_engine);
 442        if (spi_engine_write_tx_fifo(spi_engine))
 443                int_enable |= SPI_ENGINE_INT_SDO_ALMOST_EMPTY;
 444
 445        spi_engine_rx_next(spi_engine);
 446        if (spi_engine->rx_length != 0)
 447                int_enable |= SPI_ENGINE_INT_SDI_ALMOST_FULL;
 448
 449        int_enable |= SPI_ENGINE_INT_SYNC;
 450
 451        writel_relaxed(int_enable,
 452                spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
 453        spi_engine->int_enable = int_enable;
 454        spin_unlock_irqrestore(&spi_engine->lock, flags);
 455
 456        return 0;
 457}
 458
 459static int spi_engine_probe(struct platform_device *pdev)
 460{
 461        struct spi_engine *spi_engine;
 462        struct spi_master *master;
 463        unsigned int version;
 464        struct resource *res;
 465        int irq;
 466        int ret;
 467
 468        irq = platform_get_irq(pdev, 0);
 469        if (irq <= 0)
 470                return -ENXIO;
 471
 472        spi_engine = devm_kzalloc(&pdev->dev, sizeof(*spi_engine), GFP_KERNEL);
 473        if (!spi_engine)
 474                return -ENOMEM;
 475
 476        master = spi_alloc_master(&pdev->dev, 0);
 477        if (!master)
 478                return -ENOMEM;
 479
 480        spi_master_set_devdata(master, spi_engine);
 481
 482        spin_lock_init(&spi_engine->lock);
 483
 484        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 485        spi_engine->base = devm_ioremap_resource(&pdev->dev, res);
 486        if (IS_ERR(spi_engine->base)) {
 487                ret = PTR_ERR(spi_engine->base);
 488                goto err_put_master;
 489        }
 490
 491        version = readl(spi_engine->base + SPI_ENGINE_REG_VERSION);
 492        if (SPI_ENGINE_VERSION_MAJOR(version) != 1) {
 493                dev_err(&pdev->dev, "Unsupported peripheral version %u.%u.%c\n",
 494                        SPI_ENGINE_VERSION_MAJOR(version),
 495                        SPI_ENGINE_VERSION_MINOR(version),
 496                        SPI_ENGINE_VERSION_PATCH(version));
 497                ret = -ENODEV;
 498                goto err_put_master;
 499        }
 500
 501        spi_engine->clk = devm_clk_get(&pdev->dev, "s_axi_aclk");
 502        if (IS_ERR(spi_engine->clk)) {
 503                ret = PTR_ERR(spi_engine->clk);
 504                goto err_put_master;
 505        }
 506
 507        spi_engine->ref_clk = devm_clk_get(&pdev->dev, "spi_clk");
 508        if (IS_ERR(spi_engine->ref_clk)) {
 509                ret = PTR_ERR(spi_engine->ref_clk);
 510                goto err_put_master;
 511        }
 512
 513        ret = clk_prepare_enable(spi_engine->clk);
 514        if (ret)
 515                goto err_put_master;
 516
 517        ret = clk_prepare_enable(spi_engine->ref_clk);
 518        if (ret)
 519                goto err_clk_disable;
 520
 521        writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_RESET);
 522        writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
 523        writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
 524
 525        ret = request_irq(irq, spi_engine_irq, 0, pdev->name, master);
 526        if (ret)
 527                goto err_ref_clk_disable;
 528
 529        master->dev.of_node = pdev->dev.of_node;
 530        master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_3WIRE;
 531        master->bits_per_word_mask = SPI_BPW_MASK(8);
 532        master->max_speed_hz = clk_get_rate(spi_engine->ref_clk) / 2;
 533        master->transfer_one_message = spi_engine_transfer_one_message;
 534        master->num_chipselect = 8;
 535
 536        ret = spi_register_master(master);
 537        if (ret)
 538                goto err_free_irq;
 539
 540        platform_set_drvdata(pdev, master);
 541
 542        return 0;
 543err_free_irq:
 544        free_irq(irq, master);
 545err_ref_clk_disable:
 546        clk_disable_unprepare(spi_engine->ref_clk);
 547err_clk_disable:
 548        clk_disable_unprepare(spi_engine->clk);
 549err_put_master:
 550        spi_master_put(master);
 551        return ret;
 552}
 553
 554static int spi_engine_remove(struct platform_device *pdev)
 555{
 556        struct spi_master *master = spi_master_get(platform_get_drvdata(pdev));
 557        struct spi_engine *spi_engine = spi_master_get_devdata(master);
 558        int irq = platform_get_irq(pdev, 0);
 559
 560        spi_unregister_master(master);
 561
 562        free_irq(irq, master);
 563
 564        spi_master_put(master);
 565
 566        writel_relaxed(0xff, spi_engine->base + SPI_ENGINE_REG_INT_PENDING);
 567        writel_relaxed(0x00, spi_engine->base + SPI_ENGINE_REG_INT_ENABLE);
 568        writel_relaxed(0x01, spi_engine->base + SPI_ENGINE_REG_RESET);
 569
 570        clk_disable_unprepare(spi_engine->ref_clk);
 571        clk_disable_unprepare(spi_engine->clk);
 572
 573        return 0;
 574}
 575
 576static const struct of_device_id spi_engine_match_table[] = {
 577        { .compatible = "adi,axi-spi-engine-1.00.a" },
 578        { },
 579};
 580MODULE_DEVICE_TABLE(of, spi_engine_match_table);
 581
 582static struct platform_driver spi_engine_driver = {
 583        .probe = spi_engine_probe,
 584        .remove = spi_engine_remove,
 585        .driver = {
 586                .name = "spi-engine",
 587                .of_match_table = spi_engine_match_table,
 588        },
 589};
 590module_platform_driver(spi_engine_driver);
 591
 592MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
 593MODULE_DESCRIPTION("Analog Devices SPI engine peripheral driver");
 594MODULE_LICENSE("GPL");
 595