766ed70447
The spi core make sure that each transfer structure have the proper setting for bits_per_word before calling low level transfer APIs. Hence it is no more require to check again in low level driver for this field whether this is set correct or not. Removing such code from low level driver. The txx9 change also removes a test for bits_per_word set to 0, and forcing it to 8 in that case. This can also be removed now since spi_setup() ensures spi->bits_per_word is not zero. if (!spi->bits_per_word) spi->bits_per_word = 8; Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com> Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
328 lines
7.8 KiB
C
328 lines
7.8 KiB
C
/*
|
|
* Altera SPI driver
|
|
*
|
|
* Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
|
|
*
|
|
* Based on spi_s3c24xx.c, which is:
|
|
* Copyright (c) 2006 Ben Dooks
|
|
* Copyright (c) 2006 Simtec Electronics
|
|
* Ben Dooks <ben@simtec.co.uk>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/spi/spi_bitbang.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of.h>
|
|
|
|
#define DRV_NAME "spi_altera"
|
|
|
|
#define ALTERA_SPI_RXDATA 0
|
|
#define ALTERA_SPI_TXDATA 4
|
|
#define ALTERA_SPI_STATUS 8
|
|
#define ALTERA_SPI_CONTROL 12
|
|
#define ALTERA_SPI_SLAVE_SEL 20
|
|
|
|
#define ALTERA_SPI_STATUS_ROE_MSK 0x8
|
|
#define ALTERA_SPI_STATUS_TOE_MSK 0x10
|
|
#define ALTERA_SPI_STATUS_TMT_MSK 0x20
|
|
#define ALTERA_SPI_STATUS_TRDY_MSK 0x40
|
|
#define ALTERA_SPI_STATUS_RRDY_MSK 0x80
|
|
#define ALTERA_SPI_STATUS_E_MSK 0x100
|
|
|
|
#define ALTERA_SPI_CONTROL_IROE_MSK 0x8
|
|
#define ALTERA_SPI_CONTROL_ITOE_MSK 0x10
|
|
#define ALTERA_SPI_CONTROL_ITRDY_MSK 0x40
|
|
#define ALTERA_SPI_CONTROL_IRRDY_MSK 0x80
|
|
#define ALTERA_SPI_CONTROL_IE_MSK 0x100
|
|
#define ALTERA_SPI_CONTROL_SSO_MSK 0x400
|
|
|
|
struct altera_spi {
|
|
/* bitbang has to be first */
|
|
struct spi_bitbang bitbang;
|
|
struct completion done;
|
|
|
|
void __iomem *base;
|
|
int irq;
|
|
int len;
|
|
int count;
|
|
int bytes_per_word;
|
|
unsigned long imr;
|
|
|
|
/* data buffers */
|
|
const unsigned char *tx;
|
|
unsigned char *rx;
|
|
};
|
|
|
|
static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
|
|
{
|
|
return spi_master_get_devdata(sdev->master);
|
|
}
|
|
|
|
static void altera_spi_chipsel(struct spi_device *spi, int value)
|
|
{
|
|
struct altera_spi *hw = altera_spi_to_hw(spi);
|
|
|
|
if (spi->mode & SPI_CS_HIGH) {
|
|
switch (value) {
|
|
case BITBANG_CS_INACTIVE:
|
|
writel(1 << spi->chip_select,
|
|
hw->base + ALTERA_SPI_SLAVE_SEL);
|
|
hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
|
|
writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
|
|
break;
|
|
|
|
case BITBANG_CS_ACTIVE:
|
|
hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
|
|
writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
|
|
writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
|
|
break;
|
|
}
|
|
} else {
|
|
switch (value) {
|
|
case BITBANG_CS_INACTIVE:
|
|
hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
|
|
writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
|
|
break;
|
|
|
|
case BITBANG_CS_ACTIVE:
|
|
writel(1 << spi->chip_select,
|
|
hw->base + ALTERA_SPI_SLAVE_SEL);
|
|
hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
|
|
writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int altera_spi_setup(struct spi_device *spi)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline unsigned int hw_txbyte(struct altera_spi *hw, int count)
|
|
{
|
|
if (hw->tx) {
|
|
switch (hw->bytes_per_word) {
|
|
case 1:
|
|
return hw->tx[count];
|
|
case 2:
|
|
return (hw->tx[count * 2]
|
|
| (hw->tx[count * 2 + 1] << 8));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
|
|
{
|
|
struct altera_spi *hw = altera_spi_to_hw(spi);
|
|
|
|
hw->tx = t->tx_buf;
|
|
hw->rx = t->rx_buf;
|
|
hw->count = 0;
|
|
hw->bytes_per_word = t->bits_per_word / 8;
|
|
hw->len = t->len / hw->bytes_per_word;
|
|
|
|
if (hw->irq >= 0) {
|
|
/* enable receive interrupt */
|
|
hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
|
|
writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
|
|
|
|
/* send the first byte */
|
|
writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
|
|
|
|
wait_for_completion(&hw->done);
|
|
/* disable receive interrupt */
|
|
hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
|
|
writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
|
|
} else {
|
|
/* send the first byte */
|
|
writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
|
|
|
|
while (1) {
|
|
unsigned int rxd;
|
|
|
|
while (!(readl(hw->base + ALTERA_SPI_STATUS) &
|
|
ALTERA_SPI_STATUS_RRDY_MSK))
|
|
cpu_relax();
|
|
|
|
rxd = readl(hw->base + ALTERA_SPI_RXDATA);
|
|
if (hw->rx) {
|
|
switch (hw->bytes_per_word) {
|
|
case 1:
|
|
hw->rx[hw->count] = rxd;
|
|
break;
|
|
case 2:
|
|
hw->rx[hw->count * 2] = rxd;
|
|
hw->rx[hw->count * 2 + 1] = rxd >> 8;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hw->count++;
|
|
|
|
if (hw->count < hw->len)
|
|
writel(hw_txbyte(hw, hw->count),
|
|
hw->base + ALTERA_SPI_TXDATA);
|
|
else
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return hw->count * hw->bytes_per_word;
|
|
}
|
|
|
|
static irqreturn_t altera_spi_irq(int irq, void *dev)
|
|
{
|
|
struct altera_spi *hw = dev;
|
|
unsigned int rxd;
|
|
|
|
rxd = readl(hw->base + ALTERA_SPI_RXDATA);
|
|
if (hw->rx) {
|
|
switch (hw->bytes_per_word) {
|
|
case 1:
|
|
hw->rx[hw->count] = rxd;
|
|
break;
|
|
case 2:
|
|
hw->rx[hw->count * 2] = rxd;
|
|
hw->rx[hw->count * 2 + 1] = rxd >> 8;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hw->count++;
|
|
|
|
if (hw->count < hw->len)
|
|
writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA);
|
|
else
|
|
complete(&hw->done);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int altera_spi_probe(struct platform_device *pdev)
|
|
{
|
|
struct altera_spi_platform_data *platp = pdev->dev.platform_data;
|
|
struct altera_spi *hw;
|
|
struct spi_master *master;
|
|
struct resource *res;
|
|
int err = -ENODEV;
|
|
|
|
master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
|
|
if (!master)
|
|
return err;
|
|
|
|
/* setup the master state. */
|
|
master->bus_num = pdev->id;
|
|
master->num_chipselect = 16;
|
|
master->mode_bits = SPI_CS_HIGH;
|
|
master->setup = altera_spi_setup;
|
|
|
|
hw = spi_master_get_devdata(master);
|
|
platform_set_drvdata(pdev, hw);
|
|
|
|
/* setup the state for the bitbang driver */
|
|
hw->bitbang.master = spi_master_get(master);
|
|
if (!hw->bitbang.master)
|
|
return err;
|
|
hw->bitbang.setup_transfer = altera_spi_setupxfer;
|
|
hw->bitbang.chipselect = altera_spi_chipsel;
|
|
hw->bitbang.txrx_bufs = altera_spi_txrx;
|
|
|
|
/* find and map our resources */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res)
|
|
goto exit_busy;
|
|
if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
|
|
pdev->name))
|
|
goto exit_busy;
|
|
hw->base = devm_ioremap_nocache(&pdev->dev, res->start,
|
|
resource_size(res));
|
|
if (!hw->base)
|
|
goto exit_busy;
|
|
/* program defaults into the registers */
|
|
hw->imr = 0; /* disable spi interrupts */
|
|
writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
|
|
writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */
|
|
if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
|
|
readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */
|
|
/* irq is optional */
|
|
hw->irq = platform_get_irq(pdev, 0);
|
|
if (hw->irq >= 0) {
|
|
init_completion(&hw->done);
|
|
err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
|
|
pdev->name, hw);
|
|
if (err)
|
|
goto exit;
|
|
}
|
|
/* find platform data */
|
|
if (!platp)
|
|
hw->bitbang.master->dev.of_node = pdev->dev.of_node;
|
|
|
|
/* register our spi controller */
|
|
err = spi_bitbang_start(&hw->bitbang);
|
|
if (err)
|
|
goto exit;
|
|
dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
|
|
|
|
return 0;
|
|
|
|
exit_busy:
|
|
err = -EBUSY;
|
|
exit:
|
|
platform_set_drvdata(pdev, NULL);
|
|
spi_master_put(master);
|
|
return err;
|
|
}
|
|
|
|
static int altera_spi_remove(struct platform_device *dev)
|
|
{
|
|
struct altera_spi *hw = platform_get_drvdata(dev);
|
|
struct spi_master *master = hw->bitbang.master;
|
|
|
|
spi_bitbang_stop(&hw->bitbang);
|
|
platform_set_drvdata(dev, NULL);
|
|
spi_master_put(master);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id altera_spi_match[] = {
|
|
{ .compatible = "ALTR,spi-1.0", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, altera_spi_match);
|
|
#endif /* CONFIG_OF */
|
|
|
|
static struct platform_driver altera_spi_driver = {
|
|
.probe = altera_spi_probe,
|
|
.remove = altera_spi_remove,
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.owner = THIS_MODULE,
|
|
.pm = NULL,
|
|
.of_match_table = of_match_ptr(altera_spi_match),
|
|
},
|
|
};
|
|
module_platform_driver(altera_spi_driver);
|
|
|
|
MODULE_DESCRIPTION("Altera SPI driver");
|
|
MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:" DRV_NAME);
|