Commit 3d8d59f9 authored by Laurentiu-Cristian Duca's avatar Laurentiu-Cristian Duca Committed by Jan Kiszka

am335x (beaglebone black) omap4 spi real-time driver.

spi-omap2-mcspi-rt.c:
I/O handling is lifted from  omap2/4 mcspi linux driver.
The code follows the model in spi-bcm2835.c.
The theoretical maximum spi frequency of am335x omap4 is 48MHz,
and the driver practical maximum frequency is limited to 40MHz.
The omap4 maximum fifo queue size is 64 bytes,
but when the transfer implies both sending and receiving
it is limited to 32, so, in the driver things are kept simple:
max 32 for tx and max 32 for rx.
When the driver must transfer N bytes
then ((int)(N/32))*32 bytes are transfered with fifo queue size 32
and (N mod 32) bytes are transfered with fifo queue size (N mod 32);
between fifo setup, the slave chip select line is kept active
by using the FORCE bit in the channel configuration register.
Signed-off-by: default avatarLaurentiu-Cristian Duca <laurentiu.duca@gmail.com>
Signed-off-by: Jan Kiszka's avatarJan Kiszka <jan.kiszka@siemens.com>
parent bb21224f
/**
* I/O handling lifted from drivers/spi/spi-omap2-mcspi.c:
* Copyright (C) 2019 Laurentiu-Cristian Duca
* <laurentiu [dot] duca [at] gmail [dot] com>
* RTDM integration by:
* Copyright (C) 2016 Philippe Gerum <rpm@xenomai.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/spi/spi.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/gcd.h>
#include "spi-master.h"
#define RTDM_SUBCLASS_OMAP2_MCSPI 3
#define OMAP4_MCSPI_REG_OFFSET 0x100
#define OMAP2_MCSPI_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)
#define OMAP2_MCSPI_MAX_FREQ 48000000
#define OMAP2_MCSPI_DRIVER_MAX_FREQ 40000000
#define OMAP2_MCSPI_MAX_DIVIDER 4096
#define OMAP2_MCSPI_MAX_FIFODEPTH 64
#define OMAP2_MCSPI_MAX_FIFOWCNT 0xFFFF
#define SPI_AUTOSUSPEND_TIMEOUT 2000
#define PM_NEGATIVE_DELAY -2000
#define OMAP2_MCSPI_REVISION 0x00
#define OMAP2_MCSPI_SYSCONFIG 0x10
#define OMAP2_MCSPI_SYSSTATUS 0x14
#define OMAP2_MCSPI_IRQSTATUS 0x18
#define OMAP2_MCSPI_IRQENABLE 0x1c
#define OMAP2_MCSPI_WAKEUPENABLE 0x20
#define OMAP2_MCSPI_SYST 0x24
#define OMAP2_MCSPI_MODULCTRL 0x28
#define OMAP2_MCSPI_XFERLEVEL 0x7c
/* per-channel (chip select) banks, 0x14 bytes each, first is: */
#define OMAP2_MCSPI_CHANNELBANK_SIZE 0x14
#define OMAP2_MCSPI_CHCONF0 0x2c
#define OMAP2_MCSPI_CHSTAT0 0x30
#define OMAP2_MCSPI_CHCTRL0 0x34
#define OMAP2_MCSPI_TX0 0x38
#define OMAP2_MCSPI_RX0 0x3c
/* per-register bitmasks: */
#define OMAP2_MCSPI_IRQSTATUS_EOW BIT(17)
#define OMAP2_MCSPI_IRQSTATUS_RX1_FULL BIT(6)
#define OMAP2_MCSPI_IRQSTATUS_TX1_EMPTY BIT(4)
#define OMAP2_MCSPI_IRQSTATUS_RX0_FULL BIT(2)
#define OMAP2_MCSPI_IRQSTATUS_TX0_EMPTY BIT(0)
#define OMAP2_MCSPI_IRQENABLE_EOW BIT(17)
#define OMAP2_MCSPI_IRQENABLE_RX1_FULL BIT(6)
#define OMAP2_MCSPI_IRQENABLE_TX1_EMPTY BIT(4)
#define OMAP2_MCSPI_IRQENABLE_RX0_FULL BIT(2)
#define OMAP2_MCSPI_IRQENABLE_TX0_EMPTY BIT(0)
#define OMAP2_MCSPI_MODULCTRL_SINGLE BIT(0)
#define OMAP2_MCSPI_MODULCTRL_MS BIT(2)
#define OMAP2_MCSPI_MODULCTRL_STEST BIT(3)
#define OMAP2_MCSPI_CHCONF_PHA BIT(0)
#define OMAP2_MCSPI_CHCONF_POL BIT(1)
#define OMAP2_MCSPI_CHCONF_CLKD_MASK (0x0f << 2)
#define OMAP2_MCSPI_CHCONF_EPOL BIT(6)
#define OMAP2_MCSPI_CHCONF_WL_MASK (0x1f << 7)
#define OMAP2_MCSPI_CHCONF_TRM_RX_ONLY BIT(12)
#define OMAP2_MCSPI_CHCONF_TRM_TX_ONLY BIT(13)
#define OMAP2_MCSPI_CHCONF_TRM_MASK (0x03 << 12)
#define OMAP2_MCSPI_CHCONF_DMAW BIT(14)
#define OMAP2_MCSPI_CHCONF_DMAR BIT(15)
#define OMAP2_MCSPI_CHCONF_DPE0 BIT(16)
#define OMAP2_MCSPI_CHCONF_DPE1 BIT(17)
#define OMAP2_MCSPI_CHCONF_IS BIT(18)
#define OMAP2_MCSPI_CHCONF_TURBO BIT(19)
#define OMAP2_MCSPI_CHCONF_FORCE BIT(20)
#define OMAP2_MCSPI_CHCONF_FFET BIT(27)
#define OMAP2_MCSPI_CHCONF_FFER BIT(28)
#define OMAP2_MCSPI_CHCONF_CLKG BIT(29)
#define OMAP2_MCSPI_CHSTAT_RXS BIT(0)
#define OMAP2_MCSPI_CHSTAT_TXS BIT(1)
#define OMAP2_MCSPI_CHSTAT_EOT BIT(2)
#define OMAP2_MCSPI_CHSTAT_TXFFE BIT(3)
#define OMAP2_MCSPI_CHCTRL_EN BIT(0)
#define OMAP2_MCSPI_CHCTRL_EXTCLK_MASK (0xff << 8)
#define OMAP2_MCSPI_WAKEUPENABLE_WKEN BIT(0)
#define OMAP2_MCSPI_SYSCONFIG_CLOCKACTIVITY_MASK (0x3 << 8)
#define OMAP2_MCSPI_SYSCONFIG_SIDLEMODE_MASK (0x3 << 3)
#define OMAP2_MCSPI_SYSCONFIG_SOFTRESET BIT(1)
#define OMAP2_MCSPI_SYSCONFIG_AUTOIDLE BIT(0)
#define OMAP2_MCSPI_SYSSTATUS_RESETDONE BIT(0)
/* current version supports max 2 CS per module */
#define OMAP2_MCSPI_CS_N 2
#define MCSPI_PINDIR_D0_IN_D1_OUT 0
#define MCSPI_PINDIR_D0_OUT_D1_IN 1
struct omap2_mcspi_platform_config {
unsigned short num_cs;
unsigned int regs_offset;
unsigned int pin_dir:1;
};
struct omap2_mcspi_cs {
/* CS channel */
void __iomem *regs;
unsigned long phys;
u8 chosen;
};
struct spi_master_omap2_mcspi {
struct rtdm_spi_master master;
void __iomem *regs;
unsigned long phys;
rtdm_irq_t irqh;
const u8 *tx_buf;
u8 *rx_buf;
int tx_len;
int rx_len;
int fifo_depth;
rtdm_event_t transfer_done;
unsigned int pin_dir:1;
struct omap2_mcspi_cs cs[OMAP2_MCSPI_CS_N];
/* logging */
int n_rx_full;
int n_tx_empty;
int n_interrupts;
};
struct spi_slave_omap2_mcspi {
struct rtdm_spi_remote_slave slave;
void *io_virt;
dma_addr_t io_dma;
size_t io_len;
};
static inline struct spi_slave_omap2_mcspi *
to_slave_omap2_mcspi(struct rtdm_spi_remote_slave *slave)
{
return container_of(slave, struct spi_slave_omap2_mcspi, slave);
}
static inline struct spi_master_omap2_mcspi *
to_master_omap2_mcspi(struct rtdm_spi_remote_slave *slave)
{
return container_of(slave->master,
struct spi_master_omap2_mcspi, master);
}
static inline struct device *
master_to_kdev(struct rtdm_spi_master *master)
{
return &master->kmaster->dev;
}
static inline u32 mcspi_rd_reg(struct spi_master_omap2_mcspi *spim,
unsigned int reg)
{
return readl(spim->regs + reg);
}
static inline void mcspi_wr_reg(struct spi_master_omap2_mcspi *spim,
unsigned int reg, u32 val)
{
writel(val, spim->regs + reg);
}
static inline u32
mcspi_rd_cs_reg(struct spi_master_omap2_mcspi *spim,
int cs_id, unsigned int reg)
{
return readl(spim->cs[cs_id].regs + reg);
}
static inline void
mcspi_wr_cs_reg(struct spi_master_omap2_mcspi *spim, int cs_id,
unsigned int reg, u32 val)
{
writel(val, spim->cs[cs_id].regs + reg);
}
static void omap2_mcspi_init_hw(struct spi_master_omap2_mcspi *spim)
{
u32 l;
l = mcspi_rd_reg(spim, OMAP2_MCSPI_SYSCONFIG);
/* CLOCKACTIVITY = 3h: OCP and Functional clocks are maintained */
l |= OMAP2_MCSPI_SYSCONFIG_CLOCKACTIVITY_MASK;
/* SIDLEMODE = 1h: ignore idle requests */
l &= ~OMAP2_MCSPI_SYSCONFIG_SIDLEMODE_MASK;
l |= 0x1 << 3;
/* AUTOIDLE=0: OCP clock is free-running */
l &= ~OMAP2_MCSPI_SYSCONFIG_AUTOIDLE;
mcspi_wr_reg(spim, OMAP2_MCSPI_SYSCONFIG, l);
/* Initialise the hardware with the default polarities (only omap2) */
mcspi_wr_reg(spim, OMAP2_MCSPI_WAKEUPENABLE,
OMAP2_MCSPI_WAKEUPENABLE_WKEN);
/* Setup single-channel master mode */
l = mcspi_rd_reg(spim, OMAP2_MCSPI_MODULCTRL);
/* MS=0 => spi master */
l &= ~(OMAP2_MCSPI_MODULCTRL_STEST | OMAP2_MCSPI_MODULCTRL_MS);
l |= OMAP2_MCSPI_MODULCTRL_SINGLE;
mcspi_wr_reg(spim, OMAP2_MCSPI_MODULCTRL, l);
}
static void omap2_mcspi_reset_hw(struct spi_master_omap2_mcspi *spim)
{
u32 l;
l = mcspi_rd_reg(spim, OMAP2_MCSPI_SYSCONFIG);
l |= OMAP2_MCSPI_SYSCONFIG_SOFTRESET;
mcspi_wr_reg(spim, OMAP2_MCSPI_SYSCONFIG, l);
/* wait until reset is done */
do {
l = mcspi_rd_reg(spim, OMAP2_MCSPI_SYSSTATUS);
cpu_relax();
} while (!(l & OMAP2_MCSPI_SYSSTATUS_RESETDONE));
}
static void
omap2_mcspi_chip_select(struct rtdm_spi_remote_slave *slave, bool active)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
u32 l;
/* FORCE: manual SPIEN assertion to keep SPIEN active */
l = mcspi_rd_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0);
/* "active" is the logical state, not the impedance level. */
if (active)
l |= OMAP2_MCSPI_CHCONF_FORCE;
else
l &= ~OMAP2_MCSPI_CHCONF_FORCE;
mcspi_wr_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0, l);
/* Flash post-writes */
l = mcspi_rd_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0);
}
static u32 omap2_mcspi_calc_divisor(u32 speed_hz)
{
u32 div;
for (div = 0; div < 15; div++)
if (speed_hz >= (OMAP2_MCSPI_MAX_FREQ >> div))
return div;
return 15;
}
/* channel 0 enable/disable */
static void
omap2_mcspi_channel_enable(struct rtdm_spi_remote_slave *slave, int enable)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
u32 l;
l = mcspi_rd_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCTRL0);
if (enable)
l |= OMAP2_MCSPI_CHCTRL_EN;
else
l &= ~OMAP2_MCSPI_CHCTRL_EN;
mcspi_wr_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCTRL0, l);
/* Flash post-writes */
l = mcspi_rd_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCTRL0);
}
/* called only when no transfer is active to this device */
static int omap2_mcspi_configure(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
struct rtdm_spi_config *config = &slave->config;
u32 l = 0, clkd = 0, div = 1, extclk = 0, clkg = 0, word_len;
u32 speed_hz = OMAP2_MCSPI_MAX_FREQ;
u32 chctrl0;
/* The configuration parameters can be loaded in MCSPI_CH(i)CONF
* only when the channel is disabled
*/
omap2_mcspi_channel_enable(slave, 0);
l = mcspi_rd_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0);
/* Set clock frequency. */
speed_hz = (u32) config->speed_hz;
if (speed_hz > OMAP2_MCSPI_DRIVER_MAX_FREQ) {
dev_warn(slave_to_kdev(slave),
"maximum clock frequency is %d",
OMAP2_MCSPI_DRIVER_MAX_FREQ);
}
speed_hz = min_t(u32, speed_hz, OMAP2_MCSPI_DRIVER_MAX_FREQ);
if (speed_hz < (OMAP2_MCSPI_MAX_FREQ / OMAP2_MCSPI_MAX_DIVIDER)) {
clkd = omap2_mcspi_calc_divisor(speed_hz);
speed_hz = OMAP2_MCSPI_MAX_FREQ >> clkd;
clkg = 0;
} else {
div = (OMAP2_MCSPI_MAX_FREQ + speed_hz - 1) / speed_hz;
speed_hz = OMAP2_MCSPI_MAX_FREQ / div;
clkd = (div - 1) & 0xf;
extclk = (div - 1) >> 4;
clkg = OMAP2_MCSPI_CHCONF_CLKG;
}
/* set clock divisor */
l &= ~OMAP2_MCSPI_CHCONF_CLKD_MASK;
l |= clkd << 2;
/* set clock granularity */
l &= ~OMAP2_MCSPI_CHCONF_CLKG;
l |= clkg;
if (clkg) {
chctrl0 = mcspi_rd_cs_reg(spim,
slave->chip_select, OMAP2_MCSPI_CHCTRL0);
chctrl0 &= ~OMAP2_MCSPI_CHCTRL_EXTCLK_MASK;
chctrl0 |= extclk << 8;
mcspi_wr_cs_reg(spim,
slave->chip_select, OMAP2_MCSPI_CHCTRL0, chctrl0);
}
if (spim->pin_dir == MCSPI_PINDIR_D0_IN_D1_OUT) {
l &= ~OMAP2_MCSPI_CHCONF_IS;
l &= ~OMAP2_MCSPI_CHCONF_DPE1;
l |= OMAP2_MCSPI_CHCONF_DPE0;
} else {
l |= OMAP2_MCSPI_CHCONF_IS;
l |= OMAP2_MCSPI_CHCONF_DPE1;
l &= ~OMAP2_MCSPI_CHCONF_DPE0;
}
/* wordlength */
word_len = config->bits_per_word;
/* TODO: allow word_len != 8 */
if (word_len != 8) {
dev_err(slave_to_kdev(slave), "word_len(%d) != 8.\n",
word_len);
return -EIO;
}
l &= ~OMAP2_MCSPI_CHCONF_WL_MASK;
l |= (word_len - 1) << 7;
/* set chipselect polarity; manage with FORCE */
if (!(config->mode & SPI_CS_HIGH))
/* CS active-low */
l |= OMAP2_MCSPI_CHCONF_EPOL;
else
l &= ~OMAP2_MCSPI_CHCONF_EPOL;
/* set SPI mode 0..3 */
if (config->mode & SPI_CPOL)
l |= OMAP2_MCSPI_CHCONF_POL;
else
l &= ~OMAP2_MCSPI_CHCONF_POL;
if (config->mode & SPI_CPHA)
l |= OMAP2_MCSPI_CHCONF_PHA;
else
l &= ~OMAP2_MCSPI_CHCONF_PHA;
mcspi_wr_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0, l);
l = mcspi_rd_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0);
omap2_mcspi_chip_select(slave, 0);
return 0;
}
static void mcspi_rd_fifo(struct spi_master_omap2_mcspi *spim, int cs_id)
{
u8 byte;
int i;
/* Receiver register must be read to remove source of interrupt */
for (i = 0; i < spim->fifo_depth; i++) {
byte = mcspi_rd_cs_reg(spim, cs_id, OMAP2_MCSPI_RX0);
if (spim->rx_buf && (spim->rx_len > 0))
*spim->rx_buf++ = byte;
spim->rx_len--;
}
}
static void mcspi_wr_fifo(struct spi_master_omap2_mcspi *spim, int cs_id)
{
u8 byte;
int i;
/* load transmitter register to remove the source of the interrupt */
for (i = 0; i < spim->fifo_depth; i++) {
if (spim->tx_len <= 0)
byte = 0;
else
byte = spim->tx_buf ? *spim->tx_buf++ : 0;
mcspi_wr_cs_reg(spim, cs_id, OMAP2_MCSPI_TX0, byte);
spim->tx_len--;
}
}
static int omap2_mcspi_interrupt(rtdm_irq_t *irqh)
{
struct spi_master_omap2_mcspi *spim;
u32 l;
int i, cs_id = 0;
spim = rtdm_irq_get_arg(irqh, struct spi_master_omap2_mcspi);
for (i = 0; i < OMAP2_MCSPI_CS_N; i++)
if (spim->cs[i].chosen) {
cs_id = i;
break;
}
spim->n_interrupts++;
l = mcspi_rd_reg(spim, OMAP2_MCSPI_IRQSTATUS);
if ((l & OMAP2_MCSPI_IRQSTATUS_RX0_FULL) ||
(l & OMAP2_MCSPI_IRQSTATUS_RX1_FULL)) {
mcspi_rd_fifo(spim, cs_id);
spim->n_rx_full++;
}
if ((l & OMAP2_MCSPI_IRQSTATUS_TX0_EMPTY) ||
(l & OMAP2_MCSPI_IRQSTATUS_TX1_EMPTY)) {
if (spim->tx_len > 0)
mcspi_wr_fifo(spim, cs_id);
spim->n_tx_empty++;
}
/* write 1 to OMAP2_MCSPI_IRQSTATUS field to reset it */
mcspi_wr_reg(spim, OMAP2_MCSPI_IRQSTATUS, l);
if ((spim->tx_len <= 0) && (spim->rx_len <= 0)) {
/* disable interrupts */
mcspi_wr_reg(spim, OMAP2_MCSPI_IRQENABLE, 0);
rtdm_event_signal(&spim->transfer_done);
}
return RTDM_IRQ_HANDLED;
}
static int omap2_mcspi_disable_fifo(struct rtdm_spi_remote_slave *slave,
int cs_id)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
u32 chconf;
chconf = mcspi_rd_cs_reg(spim, cs_id, OMAP2_MCSPI_CHCONF0);
chconf &= ~(OMAP2_MCSPI_CHCONF_FFER | OMAP2_MCSPI_CHCONF_FFET);
mcspi_wr_cs_reg(spim, cs_id, OMAP2_MCSPI_CHCONF0, chconf);
return 0;
}
static int omap2_mcspi_set_fifo(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
unsigned int wcnt;
int max_fifo_depth, fifo_depth, bytes_per_word;
u32 chconf, xferlevel;
chconf = mcspi_rd_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0);
bytes_per_word = 1;
max_fifo_depth = OMAP2_MCSPI_MAX_FIFODEPTH / 2;
if (spim->tx_len < max_fifo_depth) {
fifo_depth = spim->tx_len;
wcnt = spim->tx_len / bytes_per_word;
} else {
fifo_depth = max_fifo_depth;
wcnt = max_fifo_depth * (spim->tx_len / max_fifo_depth)
/ bytes_per_word;
}
if (wcnt > OMAP2_MCSPI_MAX_FIFOWCNT) {
dev_err(slave_to_kdev(slave),
"%s: wcnt=%d: too many bytes in a transfer.\n",
__func__, wcnt);
return -EINVAL;
}
chconf |= OMAP2_MCSPI_CHCONF_FFER;
chconf |= OMAP2_MCSPI_CHCONF_FFET;
mcspi_wr_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0, chconf);
spim->fifo_depth = fifo_depth;
xferlevel = wcnt << 16;
xferlevel |= (fifo_depth - 1) << 8;
xferlevel |= fifo_depth - 1;
mcspi_wr_reg(spim, OMAP2_MCSPI_XFERLEVEL, xferlevel);
return 0;
}
static int do_transfer_irq_bh(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
u32 chconf, l;
int ret;
int i;
/* configure to send and receive */
chconf = mcspi_rd_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0);
chconf &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;
chconf &= ~OMAP2_MCSPI_CHCONF_TURBO;
mcspi_wr_cs_reg(spim, slave->chip_select, OMAP2_MCSPI_CHCONF0, chconf);
/* fifo can be enabled on a single channel */
if (slave->chip_select == 0) {
if (spim->cs[1].chosen)
omap2_mcspi_disable_fifo(slave, 1);
} else {
if (spim->cs[0].chosen)
omap2_mcspi_disable_fifo(slave, 0);
}
ret = omap2_mcspi_set_fifo(slave);
if (ret)
return ret;
omap2_mcspi_channel_enable(slave, 1);
/* Set slave->chip_select as chosen */
for (i = 0; i < OMAP2_MCSPI_CS_N; i++)
if (i == slave->chip_select)
spim->cs[i].chosen = 1;
else
spim->cs[i].chosen = 0;
/* The interrupt status bit should always be reset
* after the channel is enabled
* and before the event is enabled as an interrupt source.
*/
/* write 1 to OMAP2_MCSPI_IRQSTATUS field to reset it */
l = mcspi_rd_reg(spim, OMAP2_MCSPI_IRQSTATUS);
mcspi_wr_reg(spim, OMAP2_MCSPI_IRQSTATUS, l);
spim->n_interrupts = 0;
spim->n_rx_full = 0;
spim->n_tx_empty = 0;
/* Enable interrupts last. */
/* support only two channels */
if (slave->chip_select == 0)
l = OMAP2_MCSPI_IRQENABLE_TX0_EMPTY |
OMAP2_MCSPI_IRQENABLE_RX0_FULL;
else
l = OMAP2_MCSPI_IRQENABLE_TX1_EMPTY |
OMAP2_MCSPI_IRQENABLE_RX1_FULL;
mcspi_wr_reg(spim, OMAP2_MCSPI_IRQENABLE, l);
/* TX_EMPTY will be raised only after data is transfered */
mcspi_wr_fifo(spim, slave->chip_select);
/* wait for transfer completion */
ret = rtdm_event_wait(&spim->transfer_done);
omap2_mcspi_channel_enable(slave, 0);
if (ret)
return ret;
/* spim->tx_len and spim->rx_len should be 0 */
if (spim->tx_len || spim->rx_len)
return -EIO;
return 0;
}
static int do_transfer_irq(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
int len, first_size, last_size, ret;
len = spim->tx_len;
if (len < (OMAP2_MCSPI_MAX_FIFODEPTH / 2))
goto label_last;
first_size = (OMAP2_MCSPI_MAX_FIFODEPTH / 2) *
(len / (OMAP2_MCSPI_MAX_FIFODEPTH / 2));
spim->tx_len = first_size;
spim->rx_len = first_size;
ret = do_transfer_irq_bh(slave);
if (ret)
return ret;
label_last:
last_size = len % (OMAP2_MCSPI_MAX_FIFODEPTH / 2);
if (last_size == 0)
return ret;
spim->tx_len = last_size;
spim->rx_len = last_size;
ret = do_transfer_irq_bh(slave);
return ret;
}
static int omap2_mcspi_transfer_iobufs(struct rtdm_spi_remote_slave *slave)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
struct spi_slave_omap2_mcspi *mapped_data = to_slave_omap2_mcspi(slave);
int ret;
if (mapped_data->io_len == 0)
return -EINVAL; /* No I/O buffers set. */
spim->tx_len = mapped_data->io_len / 2;
spim->rx_len = spim->tx_len;
spim->tx_buf = mapped_data->io_virt + spim->rx_len;
spim->rx_buf = mapped_data->io_virt;
ret = do_transfer_irq(slave);
return ret ? : 0;
}
static int omap2_mcspi_transfer_iobufs_n(struct rtdm_spi_remote_slave *slave,
int len)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
struct spi_slave_omap2_mcspi *mapped_data = to_slave_omap2_mcspi(slave);
int ret;
if ((mapped_data->io_len == 0) ||
(len <= 0) || (len > (mapped_data->io_len / 2)))
return -EINVAL;
spim->tx_len = len;
spim->rx_len = len;
spim->tx_buf = mapped_data->io_virt + mapped_data->io_len / 2;
spim->rx_buf = mapped_data->io_virt;
ret = do_transfer_irq(slave);
return ret ? : 0;
}
static ssize_t omap2_mcspi_read(struct rtdm_spi_remote_slave *slave,
void *rx, size_t len)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
int ret;
spim->tx_len = len;
spim->rx_len = len;
spim->tx_buf = NULL;
spim->rx_buf = rx;
ret = do_transfer_irq(slave);
return ret ? : len;
}
static ssize_t omap2_mcspi_write(struct rtdm_spi_remote_slave *slave,
const void *tx, size_t len)
{
struct spi_master_omap2_mcspi *spim = to_master_omap2_mcspi(slave);
int ret;