9e264f3f85
Supporting multi-cs in spi drivers would require the chip_select & cs_gpiod members of struct spi_device to be an array. But changing the type of these members to array would break the spi driver functionality. To make the transition smoother introduced four new APIs to get/set the spi->chip_select & spi->cs_gpiod and replaced all spi->chip_select and spi->cs_gpiod references with get or set API calls. While adding multi-cs support in further patches the chip_select & cs_gpiod members of the spi_device structure would be converted to arrays & the "idx" parameter of the APIs would be used as array index i.e., spi->chip_select[idx] & spi->cs_gpiod[idx] respectively. Signed-off-by: Amit Kumar Mahapatra <amit.kumar-mahapatra@amd.com> Acked-by: Heiko Stuebner <heiko@sntech.de> # Rockchip drivers Reviewed-by: Michal Simek <michal.simek@amd.com> Reviewed-by: Cédric Le Goater <clg@kaod.org> # Aspeed driver Reviewed-by: Dhruva Gole <d-gole@ti.com> # SPI Cadence QSPI Reviewed-by: Patrice Chotard <patrice.chotard@foss.st.com> # spi-stm32-qspi Acked-by: William Zhang <william.zhang@broadcom.com> # bcm63xx-hsspi driver Reviewed-by: Serge Semin <fancer.lancer@gmail.com> # DW SSI part Link: https://lore.kernel.org/r/167847070432.26.15076794204368669839@mailman-core.alsa-project.org Signed-off-by: Mark Brown <broonie@kernel.org>
223 lines
5.1 KiB
C
223 lines
5.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* 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>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spi/altera.h>
|
|
#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
|
|
#define ALTERA_SPI_TARGET_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
|
|
|
|
static int altr_spi_writel(struct altera_spi *hw, unsigned int reg,
|
|
unsigned int val)
|
|
{
|
|
int ret;
|
|
|
|
ret = regmap_write(hw->regmap, hw->regoff + reg, val);
|
|
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;
|
|
|
|
ret = regmap_read(hw->regmap, hw->regoff + reg, val);
|
|
if (ret)
|
|
dev_err(hw->dev, "fail to read reg 0x%x: %d\n", reg, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
|
|
{
|
|
return spi_controller_get_devdata(sdev->controller);
|
|
}
|
|
|
|
static void altera_spi_set_cs(struct spi_device *spi, bool is_high)
|
|
{
|
|
struct altera_spi *hw = altera_spi_to_hw(spi);
|
|
|
|
if (is_high) {
|
|
hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
|
altr_spi_writel(hw, ALTERA_SPI_TARGET_SEL, 0);
|
|
} else {
|
|
altr_spi_writel(hw, ALTERA_SPI_TARGET_SEL,
|
|
BIT(spi_get_chipselect(spi, 0)));
|
|
hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
|
}
|
|
}
|
|
|
|
static void altera_spi_tx_word(struct altera_spi *hw)
|
|
{
|
|
unsigned int txd = 0;
|
|
|
|
if (hw->tx) {
|
|
switch (hw->bytes_per_word) {
|
|
case 1:
|
|
txd = hw->tx[hw->count];
|
|
break;
|
|
case 2:
|
|
txd = (hw->tx[hw->count * 2]
|
|
| (hw->tx[hw->count * 2 + 1] << 8));
|
|
break;
|
|
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;
|
|
|
|
}
|
|
}
|
|
|
|
altr_spi_writel(hw, ALTERA_SPI_TXDATA, txd);
|
|
}
|
|
|
|
static void altera_spi_rx_word(struct altera_spi *hw)
|
|
{
|
|
unsigned int rxd;
|
|
|
|
altr_spi_readl(hw, ALTERA_SPI_RXDATA, &rxd);
|
|
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;
|
|
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;
|
|
|
|
}
|
|
}
|
|
|
|
hw->count++;
|
|
}
|
|
|
|
static int altera_spi_txrx(struct spi_controller *host,
|
|
struct spi_device *spi, struct spi_transfer *t)
|
|
{
|
|
struct altera_spi *hw = spi_controller_get_devdata(host);
|
|
u32 val;
|
|
|
|
hw->tx = t->tx_buf;
|
|
hw->rx = t->rx_buf;
|
|
hw->count = 0;
|
|
hw->bytes_per_word = DIV_ROUND_UP(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;
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
|
|
|
/* send the first byte */
|
|
altera_spi_tx_word(hw);
|
|
|
|
return 1;
|
|
}
|
|
|
|
while (hw->count < hw->len) {
|
|
altera_spi_tx_word(hw);
|
|
|
|
for (;;) {
|
|
altr_spi_readl(hw, ALTERA_SPI_STATUS, &val);
|
|
if (val & ALTERA_SPI_STATUS_RRDY_MSK)
|
|
break;
|
|
|
|
cpu_relax();
|
|
}
|
|
|
|
altera_spi_rx_word(hw);
|
|
}
|
|
spi_finalize_current_transfer(host);
|
|
|
|
return 0;
|
|
}
|
|
|
|
irqreturn_t altera_spi_irq(int irq, void *dev)
|
|
{
|
|
struct spi_controller *host = dev;
|
|
struct altera_spi *hw = spi_controller_get_devdata(host);
|
|
|
|
altera_spi_rx_word(hw);
|
|
|
|
if (hw->count < hw->len) {
|
|
altera_spi_tx_word(hw);
|
|
} else {
|
|
/* disable receive interrupt */
|
|
hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
|
|
altr_spi_writel(hw, ALTERA_SPI_CONTROL, hw->imr);
|
|
|
|
spi_finalize_current_transfer(host);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
EXPORT_SYMBOL_GPL(altera_spi_irq);
|
|
|
|
void altera_spi_init_host(struct spi_controller *host)
|
|
{
|
|
struct altera_spi *hw = spi_controller_get_devdata(host);
|
|
u32 val;
|
|
|
|
host->transfer_one = altera_spi_txrx;
|
|
host->set_cs = altera_spi_set_cs;
|
|
|
|
/* program defaults into the registers */
|
|
hw->imr = 0; /* disable spi interrupts */
|
|
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 */
|
|
}
|
|
EXPORT_SYMBOL_GPL(altera_spi_init_host);
|
|
|
|
MODULE_LICENSE("GPL");
|