2019-06-04 08:11:33 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2011-02-14 02:10:43 +00:00
|
|
|
/*
|
|
|
|
* 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>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/errno.h>
|
2011-07-03 19:44:29 +00:00
|
|
|
#include <linux/module.h>
|
2011-02-14 02:10:43 +00:00
|
|
|
#include <linux/platform_device.h>
|
2020-06-11 03:25:07 +00:00
|
|
|
#include <linux/spi/altera.h>
|
2011-02-14 02:10:43 +00:00
|
|
|
#include <linux/spi/spi.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
|
2022-12-29 10:38:35 +00:00
|
|
|
#define ALTERA_SPI_TARGET_SEL 20
|
2011-02-14 02:10:43 +00:00
|
|
|
|
|
|
|
#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
|
|
|
|
|
2020-06-19 01:43:39 +00:00
|
|
|
static int altr_spi_writel(struct altera_spi *hw, unsigned int reg,
|
|
|
|
unsigned int val)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2020-06-19 01:43:40 +00:00
|
|
|
ret = regmap_write(hw->regmap, hw->regoff + reg, val);
|
2020-06-19 01:43:39 +00:00
|
|
|
if (ret)
|
|
|
|
dev_err(hw->dev, "fail to write reg 0x%x val 0x%x: %d\n",
|
|
|
|
reg, val, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int altr_spi_readl(struct altera_spi *hw, unsigned int reg,
|
|
|
|
unsigned int *val)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2020-06-19 01:43:40 +00:00
|
|
|
ret = regmap_read(hw->regmap, hw->regoff + reg, val);
|
2020-06-19 01:43:39 +00:00
|
|
|
if (ret)
|
|
|
|
dev_err(hw->dev, "fail to read reg 0x%x: %d\n", reg, ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-02-14 02:10:43 +00:00
|
|
|
static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
|
|
|
|
{
|
2022-12-29 10:38:35 +00:00
|
|
|
return spi_controller_get_devdata(sdev->controller);
|
2011-02-14 02:10:43 +00:00
|
|
|
}
|
|
|
|
|
2017-08-16 09:33:11 +00:00
|
|
|
static void altera_spi_set_cs(struct spi_device *spi, bool is_high)
|
2011-02-14 02:10:43 +00:00
|
|
|
{
|
|
|
|
struct altera_spi *hw = altera_spi_to_hw(spi);
|
|
|
|
|
2017-08-16 09:33:11 +00:00
|
|
|
if (is_high) {
|
|
|
|
hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
|
2020-06-19 01:43:39 +00:00
|
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
2022-12-29 10:38:35 +00:00
|
|
|
altr_spi_writel(hw, ALTERA_SPI_TARGET_SEL, 0);
|
2011-02-14 02:10:43 +00:00
|
|
|
} else {
|
2022-12-29 10:38:35 +00:00
|
|
|
altr_spi_writel(hw, ALTERA_SPI_TARGET_SEL,
|
2023-03-10 17:32:03 +00:00
|
|
|
BIT(spi_get_chipselect(spi, 0)));
|
2017-08-16 09:33:11 +00:00
|
|
|
hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
|
2020-06-19 01:43:39 +00:00
|
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
2011-02-14 02:10:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-16 09:33:12 +00:00
|
|
|
static void altera_spi_tx_word(struct altera_spi *hw)
|
2011-02-14 02:10:43 +00:00
|
|
|
{
|
2017-08-16 09:33:12 +00:00
|
|
|
unsigned int txd = 0;
|
|
|
|
|
2011-02-14 02:10:43 +00:00
|
|
|
if (hw->tx) {
|
|
|
|
switch (hw->bytes_per_word) {
|
|
|
|
case 1:
|
2017-08-16 09:33:12 +00:00
|
|
|
txd = hw->tx[hw->count];
|
|
|
|
break;
|
2011-02-14 02:10:43 +00:00
|
|
|
case 2:
|
2017-08-16 09:33:12 +00:00
|
|
|
txd = (hw->tx[hw->count * 2]
|
|
|
|
| (hw->tx[hw->count * 2 + 1] << 8));
|
|
|
|
break;
|
2020-06-11 03:25:06 +00:00
|
|
|
case 4:
|
|
|
|
txd = (hw->tx[hw->count * 4]
|
|
|
|
| (hw->tx[hw->count * 4 + 1] << 8)
|
|
|
|
| (hw->tx[hw->count * 4 + 2] << 16)
|
|
|
|
| (hw->tx[hw->count * 4 + 3] << 24));
|
|
|
|
break;
|
|
|
|
|
2011-02-14 02:10:43 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-16 09:33:12 +00:00
|
|
|
|
2020-06-19 01:43:39 +00:00
|
|
|
altr_spi_writel(hw, ALTERA_SPI_TXDATA, txd);
|
2017-08-16 09:33:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void altera_spi_rx_word(struct altera_spi *hw)
|
|
|
|
{
|
|
|
|
unsigned int rxd;
|
|
|
|
|
2020-06-19 01:43:39 +00:00
|
|
|
altr_spi_readl(hw, ALTERA_SPI_RXDATA, &rxd);
|
2017-08-16 09:33:12 +00:00
|
|
|
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;
|
2020-06-11 03:25:06 +00:00
|
|
|
case 4:
|
|
|
|
hw->rx[hw->count * 4] = rxd;
|
|
|
|
hw->rx[hw->count * 4 + 1] = rxd >> 8;
|
|
|
|
hw->rx[hw->count * 4 + 2] = rxd >> 16;
|
|
|
|
hw->rx[hw->count * 4 + 3] = rxd >> 24;
|
|
|
|
break;
|
|
|
|
|
2017-08-16 09:33:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hw->count++;
|
2011-02-14 02:10:43 +00:00
|
|
|
}
|
|
|
|
|
2022-12-29 10:38:35 +00:00
|
|
|
static int altera_spi_txrx(struct spi_controller *host,
|
2017-08-16 09:33:11 +00:00
|
|
|
struct spi_device *spi, struct spi_transfer *t)
|
2011-02-14 02:10:43 +00:00
|
|
|
{
|
2022-12-29 10:38:35 +00:00
|
|
|
struct altera_spi *hw = spi_controller_get_devdata(host);
|
2020-06-19 01:43:39 +00:00
|
|
|
u32 val;
|
2011-02-14 02:10:43 +00:00
|
|
|
|
|
|
|
hw->tx = t->tx_buf;
|
|
|
|
hw->rx = t->rx_buf;
|
|
|
|
hw->count = 0;
|
2013-08-29 15:41:20 +00:00
|
|
|
hw->bytes_per_word = DIV_ROUND_UP(t->bits_per_word, 8);
|
2011-02-14 02:10:43 +00:00
|
|
|
hw->len = t->len / hw->bytes_per_word;
|
|
|
|
|
|
|
|
if (hw->irq >= 0) {
|
|
|
|
/* enable receive interrupt */
|
|
|
|
hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
|
2020-06-19 01:43:39 +00:00
|
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
2011-02-14 02:10:43 +00:00
|
|
|
|
|
|
|
/* send the first byte */
|
2017-08-16 09:33:12 +00:00
|
|
|
altera_spi_tx_word(hw);
|
2013-08-15 06:18:46 +00:00
|
|
|
|
2020-12-29 05:27:41 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2020-06-19 01:43:39 +00:00
|
|
|
|
2020-12-29 05:27:41 +00:00
|
|
|
while (hw->count < hw->len) {
|
|
|
|
altera_spi_tx_word(hw);
|
2011-02-14 02:10:43 +00:00
|
|
|
|
2020-12-29 05:27:41 +00:00
|
|
|
for (;;) {
|
|
|
|
altr_spi_readl(hw, ALTERA_SPI_STATUS, &val);
|
|
|
|
if (val & ALTERA_SPI_STATUS_RRDY_MSK)
|
|
|
|
break;
|
|
|
|
|
|
|
|
cpu_relax();
|
2011-02-14 02:10:43 +00:00
|
|
|
}
|
2020-12-29 05:27:41 +00:00
|
|
|
|
|
|
|
altera_spi_rx_word(hw);
|
2011-02-14 02:10:43 +00:00
|
|
|
}
|
2022-12-29 10:38:35 +00:00
|
|
|
spi_finalize_current_transfer(host);
|
2011-02-14 02:10:43 +00:00
|
|
|
|
2020-12-29 05:27:41 +00:00
|
|
|
return 0;
|
2011-02-14 02:10:43 +00:00
|
|
|
}
|
|
|
|
|
2021-04-16 16:57:19 +00:00
|
|
|
irqreturn_t altera_spi_irq(int irq, void *dev)
|
2011-02-14 02:10:43 +00:00
|
|
|
{
|
2022-12-29 10:38:35 +00:00
|
|
|
struct spi_controller *host = dev;
|
|
|
|
struct altera_spi *hw = spi_controller_get_devdata(host);
|
2011-02-14 02:10:43 +00:00
|
|
|
|
2017-08-16 09:33:12 +00:00
|
|
|
altera_spi_rx_word(hw);
|
2011-02-14 02:10:43 +00:00
|
|
|
|
2017-08-16 09:33:11 +00:00
|
|
|
if (hw->count < hw->len) {
|
2017-08-16 09:33:12 +00:00
|
|
|
altera_spi_tx_word(hw);
|
2017-08-16 09:33:11 +00:00
|
|
|
} else {
|
|
|
|
/* disable receive interrupt */
|
|
|
|
hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
|
2020-06-19 01:43:39 +00:00
|
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
2017-08-16 09:33:11 +00:00
|
|
|
|
2022-12-29 10:38:35 +00:00
|
|
|
spi_finalize_current_transfer(host);
|
2017-08-16 09:33:11 +00:00
|
|
|
}
|
2011-02-14 02:10:43 +00:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
2021-04-16 16:57:19 +00:00
|
|
|
EXPORT_SYMBOL_GPL(altera_spi_irq);
|
2011-02-14 02:10:43 +00:00
|
|
|
|
2022-12-29 10:38:35 +00:00
|
|
|
void altera_spi_init_host(struct spi_controller *host)
|
2011-02-14 02:10:43 +00:00
|
|
|
{
|
2022-12-29 10:38:35 +00:00
|
|
|
struct altera_spi *hw = spi_controller_get_devdata(host);
|
2020-06-19 01:43:39 +00:00
|
|
|
u32 val;
|
2020-06-11 03:25:07 +00:00
|
|
|
|
2022-12-29 10:38:35 +00:00
|
|
|
host->transfer_one = altera_spi_txrx;
|
|
|
|
host->set_cs = altera_spi_set_cs;
|
2011-02-14 02:10:43 +00:00
|
|
|
|
|
|
|
/* program defaults into the registers */
|
|
|
|
hw->imr = 0; /* disable spi interrupts */
|
2020-06-19 01:43:39 +00:00
|
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
|
|
|
altr_spi_writel(hw, ALTERA_SPI_STATUS, 0); /* clear status reg */
|
|
|
|
altr_spi_readl(hw, ALTERA_SPI_STATUS, &val);
|
|
|
|
if (val & ALTERA_SPI_STATUS_RRDY_MSK)
|
|
|
|
altr_spi_readl(hw, ALTERA_SPI_RXDATA, &val); /* flush rxdata */
|
2011-02-14 02:10:43 +00:00
|
|
|
}
|
2022-12-29 10:38:35 +00:00
|
|
|
EXPORT_SYMBOL_GPL(altera_spi_init_host);
|
2011-02-14 02:10:43 +00:00
|
|
|
|
2024-06-09 22:18:58 +00:00
|
|
|
MODULE_DESCRIPTION("Altera SPI Controller driver core");
|
2011-02-14 02:10:43 +00:00
|
|
|
MODULE_LICENSE("GPL");
|