linux/drivers/spi/spi-altera.c
<<
>>
Prefs
   1/*
   2 * Altera SPI driver
   3 *
   4 * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
   5 *
   6 * Based on spi_s3c24xx.c, which is:
   7 * Copyright (c) 2006 Ben Dooks
   8 * Copyright (c) 2006 Simtec Electronics
   9 *      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 */
  15
  16#include <linux/interrupt.h>
  17#include <linux/errno.h>
  18#include <linux/module.h>
  19#include <linux/platform_device.h>
  20#include <linux/spi/spi.h>
  21#include <linux/io.h>
  22#include <linux/of.h>
  23
  24#define DRV_NAME "spi_altera"
  25
  26#define ALTERA_SPI_RXDATA       0
  27#define ALTERA_SPI_TXDATA       4
  28#define ALTERA_SPI_STATUS       8
  29#define ALTERA_SPI_CONTROL      12
  30#define ALTERA_SPI_SLAVE_SEL    20
  31
  32#define ALTERA_SPI_STATUS_ROE_MSK       0x8
  33#define ALTERA_SPI_STATUS_TOE_MSK       0x10
  34#define ALTERA_SPI_STATUS_TMT_MSK       0x20
  35#define ALTERA_SPI_STATUS_TRDY_MSK      0x40
  36#define ALTERA_SPI_STATUS_RRDY_MSK      0x80
  37#define ALTERA_SPI_STATUS_E_MSK         0x100
  38
  39#define ALTERA_SPI_CONTROL_IROE_MSK     0x8
  40#define ALTERA_SPI_CONTROL_ITOE_MSK     0x10
  41#define ALTERA_SPI_CONTROL_ITRDY_MSK    0x40
  42#define ALTERA_SPI_CONTROL_IRRDY_MSK    0x80
  43#define ALTERA_SPI_CONTROL_IE_MSK       0x100
  44#define ALTERA_SPI_CONTROL_SSO_MSK      0x400
  45
  46struct altera_spi {
  47        void __iomem *base;
  48        int irq;
  49        int len;
  50        int count;
  51        int bytes_per_word;
  52        unsigned long imr;
  53
  54        /* data buffers */
  55        const unsigned char *tx;
  56        unsigned char *rx;
  57};
  58
  59static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
  60{
  61        return spi_master_get_devdata(sdev->master);
  62}
  63
  64static void altera_spi_set_cs(struct spi_device *spi, bool is_high)
  65{
  66        struct altera_spi *hw = altera_spi_to_hw(spi);
  67
  68        if (is_high) {
  69                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);
  72        } else {
  73                writel(BIT(spi->chip_select), hw->base + ALTERA_SPI_SLAVE_SEL);
  74                hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
  75                writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
  76        }
  77}
  78
  79static void altera_spi_tx_word(struct altera_spi *hw)
  80{
  81        unsigned int txd = 0;
  82
  83        if (hw->tx) {
  84                switch (hw->bytes_per_word) {
  85                case 1:
  86                        txd = hw->tx[hw->count];
  87                        break;
  88                case 2:
  89                        txd = (hw->tx[hw->count * 2]
  90                                | (hw->tx[hw->count * 2 + 1] << 8));
  91                        break;
  92                }
  93        }
  94
  95        writel(txd, hw->base + ALTERA_SPI_TXDATA);
  96}
  97
  98static void altera_spi_rx_word(struct altera_spi *hw)
  99{
 100        unsigned int rxd;
 101
 102        rxd = readl(hw->base + ALTERA_SPI_RXDATA);
 103        if (hw->rx) {
 104                switch (hw->bytes_per_word) {
 105                case 1:
 106                        hw->rx[hw->count] = rxd;
 107                        break;
 108                case 2:
 109                        hw->rx[hw->count * 2] = rxd;
 110                        hw->rx[hw->count * 2 + 1] = rxd >> 8;
 111                        break;
 112                }
 113        }
 114
 115        hw->count++;
 116}
 117
 118static int altera_spi_txrx(struct spi_master *master,
 119        struct spi_device *spi, struct spi_transfer *t)
 120{
 121        struct altera_spi *hw = spi_master_get_devdata(master);
 122
 123        hw->tx = t->tx_buf;
 124        hw->rx = t->rx_buf;
 125        hw->count = 0;
 126        hw->bytes_per_word = DIV_ROUND_UP(t->bits_per_word, 8);
 127        hw->len = t->len / hw->bytes_per_word;
 128
 129        if (hw->irq >= 0) {
 130                /* enable receive interrupt */
 131                hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
 132                writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
 133
 134                /* send the first byte */
 135                altera_spi_tx_word(hw);
 136        } else {
 137                while (hw->count < hw->len) {
 138                        altera_spi_tx_word(hw);
 139
 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);
 147        }
 148
 149        return t->len;
 150}
 151
 152static irqreturn_t altera_spi_irq(int irq, void *dev)
 153{
 154        struct spi_master *master = dev;
 155        struct altera_spi *hw = spi_master_get_devdata(master);
 156
 157        altera_spi_rx_word(hw);
 158
 159        if (hw->count < hw->len) {
 160                altera_spi_tx_word(hw);
 161        } else {
 162                /* disable receive interrupt */
 163                hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
 164                writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
 165
 166                spi_finalize_current_transfer(master);
 167        }
 168
 169        return IRQ_HANDLED;
 170}
 171
 172static int altera_spi_probe(struct platform_device *pdev)
 173{
 174        struct altera_spi *hw;
 175        struct spi_master *master;
 176        struct resource *res;
 177        int err = -ENODEV;
 178
 179        master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
 180        if (!master)
 181                return err;
 182
 183        /* setup the master state. */
 184        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);
 188        master->dev.of_node = pdev->dev.of_node;
 189        master->transfer_one = altera_spi_txrx;
 190        master->set_cs = altera_spi_set_cs;
 191
 192        hw = spi_master_get_devdata(master);
 193
 194        /* 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;
 200        }
 201        /* program defaults into the registers */
 202        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 */
 207        /* irq is optional */
 208        hw->irq = platform_get_irq(pdev, 0);
 209        if (hw->irq >= 0) {
 210                err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
 211                                       pdev->name, master);
 212                if (err)
 213                        goto exit;
 214        }
 215
 216        err = devm_spi_register_master(&pdev->dev, master);
 217        if (err)
 218                goto exit;
 219        dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
 220
 221        return 0;
 222exit:
 223        spi_master_put(master);
 224        return err;
 225}
 226
 227#ifdef CONFIG_OF
 228static const struct of_device_id altera_spi_match[] = {
 229        { .compatible = "ALTR,spi-1.0", },
 230        { .compatible = "altr,spi-1.0", },
 231        {},
 232};
 233MODULE_DEVICE_TABLE(of, altera_spi_match);
 234#endif /* CONFIG_OF */
 235
 236static struct platform_driver altera_spi_driver = {
 237        .probe = altera_spi_probe,
 238        .driver = {
 239                .name = DRV_NAME,
 240                .pm = NULL,
 241                .of_match_table = of_match_ptr(altera_spi_match),
 242        },
 243};
 244module_platform_driver(altera_spi_driver);
 245
 246MODULE_DESCRIPTION("Altera SPI driver");
 247MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
 248MODULE_LICENSE("GPL");
 249MODULE_ALIAS("platform:" DRV_NAME);
 250