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