e26aa015dd
PBX 2 SPI devices need the nonstandard "cs change per word" mechanism. This patch is one of three updating this driver to make the last data bits get sent before advancing the transfer ... in this case, before the chipselect gets deactivated. Signed-off-by: Bryan Wu <bryan.wu@analog.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1462 lines
37 KiB
C
1462 lines
37 KiB
C
/*
|
|
* File: drivers/spi/bfin5xx_spi.c
|
|
* Maintainer:
|
|
* Bryan Wu <bryan.wu@analog.com>
|
|
* Original Author:
|
|
* Luke Yang (Analog Devices Inc.)
|
|
*
|
|
* Created: March. 10th 2006
|
|
* Description: SPI controller driver for Blackfin BF5xx
|
|
* Bugs: Enter bugs at http://blackfin.uclinux.org/
|
|
*
|
|
* Modified:
|
|
* March 10, 2006 bfin5xx_spi.c Created. (Luke Yang)
|
|
* August 7, 2006 added full duplex mode (Axel Weiss & Luke Yang)
|
|
* July 17, 2007 add support for BF54x SPI0 controller (Bryan Wu)
|
|
* July 30, 2007 add platfrom_resource interface to support multi-port
|
|
* SPI controller (Bryan Wu)
|
|
*
|
|
* Copyright 2004-2007 Analog Devices Inc.
|
|
*
|
|
* 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, 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 ; see the file COPYING.
|
|
* If not, write to the Free Software Foundation,
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/io.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <asm/dma.h>
|
|
#include <asm/portmux.h>
|
|
#include <asm/bfin5xx_spi.h>
|
|
|
|
#define DRV_NAME "bfin-spi"
|
|
#define DRV_AUTHOR "Bryan Wu, Luke Yang"
|
|
#define DRV_DESC "Blackfin BF5xx on-chip SPI Controller Driver"
|
|
#define DRV_VERSION "1.0"
|
|
|
|
MODULE_AUTHOR(DRV_AUTHOR);
|
|
MODULE_DESCRIPTION(DRV_DESC);
|
|
MODULE_LICENSE("GPL");
|
|
|
|
#define IS_DMA_ALIGNED(x) (((u32)(x)&0x07) == 0)
|
|
|
|
#define START_STATE ((void *)0)
|
|
#define RUNNING_STATE ((void *)1)
|
|
#define DONE_STATE ((void *)2)
|
|
#define ERROR_STATE ((void *)-1)
|
|
#define QUEUE_RUNNING 0
|
|
#define QUEUE_STOPPED 1
|
|
|
|
struct driver_data {
|
|
/* Driver model hookup */
|
|
struct platform_device *pdev;
|
|
|
|
/* SPI framework hookup */
|
|
struct spi_master *master;
|
|
|
|
/* Regs base of SPI controller */
|
|
void __iomem *regs_base;
|
|
|
|
/* Pin request list */
|
|
u16 *pin_req;
|
|
|
|
/* BFIN hookup */
|
|
struct bfin5xx_spi_master *master_info;
|
|
|
|
/* Driver message queue */
|
|
struct workqueue_struct *workqueue;
|
|
struct work_struct pump_messages;
|
|
spinlock_t lock;
|
|
struct list_head queue;
|
|
int busy;
|
|
int run;
|
|
|
|
/* Message Transfer pump */
|
|
struct tasklet_struct pump_transfers;
|
|
|
|
/* Current message transfer state info */
|
|
struct spi_message *cur_msg;
|
|
struct spi_transfer *cur_transfer;
|
|
struct chip_data *cur_chip;
|
|
size_t len_in_bytes;
|
|
size_t len;
|
|
void *tx;
|
|
void *tx_end;
|
|
void *rx;
|
|
void *rx_end;
|
|
|
|
/* DMA stuffs */
|
|
int dma_channel;
|
|
int dma_mapped;
|
|
int dma_requested;
|
|
dma_addr_t rx_dma;
|
|
dma_addr_t tx_dma;
|
|
|
|
size_t rx_map_len;
|
|
size_t tx_map_len;
|
|
u8 n_bytes;
|
|
int cs_change;
|
|
void (*write) (struct driver_data *);
|
|
void (*read) (struct driver_data *);
|
|
void (*duplex) (struct driver_data *);
|
|
};
|
|
|
|
struct chip_data {
|
|
u16 ctl_reg;
|
|
u16 baud;
|
|
u16 flag;
|
|
|
|
u8 chip_select_num;
|
|
u8 n_bytes;
|
|
u8 width; /* 0 or 1 */
|
|
u8 enable_dma;
|
|
u8 bits_per_word; /* 8 or 16 */
|
|
u8 cs_change_per_word;
|
|
u16 cs_chg_udelay; /* Some devices require > 255usec delay */
|
|
void (*write) (struct driver_data *);
|
|
void (*read) (struct driver_data *);
|
|
void (*duplex) (struct driver_data *);
|
|
};
|
|
|
|
#define DEFINE_SPI_REG(reg, off) \
|
|
static inline u16 read_##reg(struct driver_data *drv_data) \
|
|
{ return bfin_read16(drv_data->regs_base + off); } \
|
|
static inline void write_##reg(struct driver_data *drv_data, u16 v) \
|
|
{ bfin_write16(drv_data->regs_base + off, v); }
|
|
|
|
DEFINE_SPI_REG(CTRL, 0x00)
|
|
DEFINE_SPI_REG(FLAG, 0x04)
|
|
DEFINE_SPI_REG(STAT, 0x08)
|
|
DEFINE_SPI_REG(TDBR, 0x0C)
|
|
DEFINE_SPI_REG(RDBR, 0x10)
|
|
DEFINE_SPI_REG(BAUD, 0x14)
|
|
DEFINE_SPI_REG(SHAW, 0x18)
|
|
|
|
static void bfin_spi_enable(struct driver_data *drv_data)
|
|
{
|
|
u16 cr;
|
|
|
|
cr = read_CTRL(drv_data);
|
|
write_CTRL(drv_data, (cr | BIT_CTL_ENABLE));
|
|
}
|
|
|
|
static void bfin_spi_disable(struct driver_data *drv_data)
|
|
{
|
|
u16 cr;
|
|
|
|
cr = read_CTRL(drv_data);
|
|
write_CTRL(drv_data, (cr & (~BIT_CTL_ENABLE)));
|
|
}
|
|
|
|
/* Caculate the SPI_BAUD register value based on input HZ */
|
|
static u16 hz_to_spi_baud(u32 speed_hz)
|
|
{
|
|
u_long sclk = get_sclk();
|
|
u16 spi_baud = (sclk / (2 * speed_hz));
|
|
|
|
if ((sclk % (2 * speed_hz)) > 0)
|
|
spi_baud++;
|
|
|
|
return spi_baud;
|
|
}
|
|
|
|
static int flush(struct driver_data *drv_data)
|
|
{
|
|
unsigned long limit = loops_per_jiffy << 1;
|
|
|
|
/* wait for stop and clear stat */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF) && limit--)
|
|
cpu_relax();
|
|
|
|
write_STAT(drv_data, BIT_STAT_CLR);
|
|
|
|
return limit;
|
|
}
|
|
|
|
/* Chip select operation functions for cs_change flag */
|
|
static void cs_active(struct driver_data *drv_data, struct chip_data *chip)
|
|
{
|
|
u16 flag = read_FLAG(drv_data);
|
|
|
|
flag |= chip->flag;
|
|
flag &= ~(chip->flag << 8);
|
|
|
|
write_FLAG(drv_data, flag);
|
|
}
|
|
|
|
static void cs_deactive(struct driver_data *drv_data, struct chip_data *chip)
|
|
{
|
|
u16 flag = read_FLAG(drv_data);
|
|
|
|
flag |= (chip->flag << 8);
|
|
|
|
write_FLAG(drv_data, flag);
|
|
|
|
/* Move delay here for consistency */
|
|
if (chip->cs_chg_udelay)
|
|
udelay(chip->cs_chg_udelay);
|
|
}
|
|
|
|
#define MAX_SPI_SSEL 7
|
|
|
|
/* stop controller and re-config current chip*/
|
|
static void restore_state(struct driver_data *drv_data)
|
|
{
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
|
|
/* Clear status and disable clock */
|
|
write_STAT(drv_data, BIT_STAT_CLR);
|
|
bfin_spi_disable(drv_data);
|
|
dev_dbg(&drv_data->pdev->dev, "restoring spi ctl state\n");
|
|
|
|
/* Load the registers */
|
|
write_CTRL(drv_data, chip->ctl_reg);
|
|
write_BAUD(drv_data, chip->baud);
|
|
|
|
bfin_spi_enable(drv_data);
|
|
cs_active(drv_data, chip);
|
|
}
|
|
|
|
/* used to kick off transfer in rx mode */
|
|
static unsigned short dummy_read(struct driver_data *drv_data)
|
|
{
|
|
unsigned short tmp;
|
|
tmp = read_RDBR(drv_data);
|
|
return tmp;
|
|
}
|
|
|
|
static void null_writer(struct driver_data *drv_data)
|
|
{
|
|
u8 n_bytes = drv_data->n_bytes;
|
|
|
|
while (drv_data->tx < drv_data->tx_end) {
|
|
write_TDBR(drv_data, 0);
|
|
while ((read_STAT(drv_data) & BIT_STAT_TXS))
|
|
cpu_relax();
|
|
drv_data->tx += n_bytes;
|
|
}
|
|
}
|
|
|
|
static void null_reader(struct driver_data *drv_data)
|
|
{
|
|
u8 n_bytes = drv_data->n_bytes;
|
|
dummy_read(drv_data);
|
|
|
|
while (drv_data->rx < drv_data->rx_end) {
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
dummy_read(drv_data);
|
|
drv_data->rx += n_bytes;
|
|
}
|
|
}
|
|
|
|
static void u8_writer(struct driver_data *drv_data)
|
|
{
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"cr8-s is 0x%x\n", read_STAT(drv_data));
|
|
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
while (drv_data->tx < drv_data->tx_end) {
|
|
write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
|
|
while (read_STAT(drv_data) & BIT_STAT_TXS)
|
|
cpu_relax();
|
|
++drv_data->tx;
|
|
}
|
|
}
|
|
|
|
static void u8_cs_chg_writer(struct driver_data *drv_data)
|
|
{
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
|
|
while (drv_data->tx < drv_data->tx_end) {
|
|
cs_active(drv_data, chip);
|
|
|
|
write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
|
|
while (read_STAT(drv_data) & BIT_STAT_TXS)
|
|
cpu_relax();
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
cs_deactive(drv_data, chip);
|
|
|
|
++drv_data->tx;
|
|
}
|
|
}
|
|
|
|
static void u8_reader(struct driver_data *drv_data)
|
|
{
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"cr-8 is 0x%x\n", read_STAT(drv_data));
|
|
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
/* clear TDBR buffer before read(else it will be shifted out) */
|
|
write_TDBR(drv_data, 0xFFFF);
|
|
|
|
dummy_read(drv_data);
|
|
|
|
while (drv_data->rx < drv_data->rx_end - 1) {
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u8 *) (drv_data->rx) = read_RDBR(drv_data);
|
|
++drv_data->rx;
|
|
}
|
|
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u8 *) (drv_data->rx) = read_SHAW(drv_data);
|
|
++drv_data->rx;
|
|
}
|
|
|
|
static void u8_cs_chg_reader(struct driver_data *drv_data)
|
|
{
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
|
|
while (drv_data->rx < drv_data->rx_end) {
|
|
cs_active(drv_data, chip);
|
|
read_RDBR(drv_data); /* kick off */
|
|
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
*(u8 *) (drv_data->rx) = read_SHAW(drv_data);
|
|
cs_deactive(drv_data, chip);
|
|
|
|
++drv_data->rx;
|
|
}
|
|
}
|
|
|
|
static void u8_duplex(struct driver_data *drv_data)
|
|
{
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
/* in duplex mode, clk is triggered by writing of TDBR */
|
|
while (drv_data->rx < drv_data->rx_end) {
|
|
write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
|
|
while (read_STAT(drv_data) & BIT_STAT_TXS)
|
|
cpu_relax();
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u8 *) (drv_data->rx) = read_RDBR(drv_data);
|
|
++drv_data->rx;
|
|
++drv_data->tx;
|
|
}
|
|
}
|
|
|
|
static void u8_cs_chg_duplex(struct driver_data *drv_data)
|
|
{
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
|
|
while (drv_data->rx < drv_data->rx_end) {
|
|
cs_active(drv_data, chip);
|
|
|
|
write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
|
|
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u8 *) (drv_data->rx) = read_RDBR(drv_data);
|
|
|
|
cs_deactive(drv_data, chip);
|
|
|
|
++drv_data->rx;
|
|
++drv_data->tx;
|
|
}
|
|
}
|
|
|
|
static void u16_writer(struct driver_data *drv_data)
|
|
{
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"cr16 is 0x%x\n", read_STAT(drv_data));
|
|
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
while (drv_data->tx < drv_data->tx_end) {
|
|
write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
|
|
while ((read_STAT(drv_data) & BIT_STAT_TXS))
|
|
cpu_relax();
|
|
drv_data->tx += 2;
|
|
}
|
|
}
|
|
|
|
static void u16_cs_chg_writer(struct driver_data *drv_data)
|
|
{
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
while (drv_data->tx < drv_data->tx_end) {
|
|
cs_active(drv_data, chip);
|
|
|
|
write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
|
|
while ((read_STAT(drv_data) & BIT_STAT_TXS))
|
|
cpu_relax();
|
|
|
|
cs_deactive(drv_data, chip);
|
|
|
|
drv_data->tx += 2;
|
|
}
|
|
}
|
|
|
|
static void u16_reader(struct driver_data *drv_data)
|
|
{
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"cr-16 is 0x%x\n", read_STAT(drv_data));
|
|
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
/* clear TDBR buffer before read(else it will be shifted out) */
|
|
write_TDBR(drv_data, 0xFFFF);
|
|
|
|
dummy_read(drv_data);
|
|
|
|
while (drv_data->rx < (drv_data->rx_end - 2)) {
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u16 *) (drv_data->rx) = read_RDBR(drv_data);
|
|
drv_data->rx += 2;
|
|
}
|
|
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u16 *) (drv_data->rx) = read_SHAW(drv_data);
|
|
drv_data->rx += 2;
|
|
}
|
|
|
|
static void u16_cs_chg_reader(struct driver_data *drv_data)
|
|
{
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
/* clear TDBR buffer before read(else it will be shifted out) */
|
|
write_TDBR(drv_data, 0xFFFF);
|
|
|
|
cs_active(drv_data, chip);
|
|
dummy_read(drv_data);
|
|
|
|
while (drv_data->rx < drv_data->rx_end - 2) {
|
|
cs_deactive(drv_data, chip);
|
|
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
cs_active(drv_data, chip);
|
|
*(u16 *) (drv_data->rx) = read_RDBR(drv_data);
|
|
drv_data->rx += 2;
|
|
}
|
|
cs_deactive(drv_data, chip);
|
|
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u16 *) (drv_data->rx) = read_SHAW(drv_data);
|
|
drv_data->rx += 2;
|
|
}
|
|
|
|
static void u16_duplex(struct driver_data *drv_data)
|
|
{
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
/* in duplex mode, clk is triggered by writing of TDBR */
|
|
while (drv_data->tx < drv_data->tx_end) {
|
|
write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
|
|
while (read_STAT(drv_data) & BIT_STAT_TXS)
|
|
cpu_relax();
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u16 *) (drv_data->rx) = read_RDBR(drv_data);
|
|
drv_data->rx += 2;
|
|
drv_data->tx += 2;
|
|
}
|
|
}
|
|
|
|
static void u16_cs_chg_duplex(struct driver_data *drv_data)
|
|
{
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
while (drv_data->tx < drv_data->tx_end) {
|
|
cs_active(drv_data, chip);
|
|
|
|
write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
|
|
while (read_STAT(drv_data) & BIT_STAT_TXS)
|
|
cpu_relax();
|
|
while (!(read_STAT(drv_data) & BIT_STAT_RXS))
|
|
cpu_relax();
|
|
*(u16 *) (drv_data->rx) = read_RDBR(drv_data);
|
|
|
|
cs_deactive(drv_data, chip);
|
|
|
|
drv_data->rx += 2;
|
|
drv_data->tx += 2;
|
|
}
|
|
}
|
|
|
|
/* test if ther is more transfer to be done */
|
|
static void *next_transfer(struct driver_data *drv_data)
|
|
{
|
|
struct spi_message *msg = drv_data->cur_msg;
|
|
struct spi_transfer *trans = drv_data->cur_transfer;
|
|
|
|
/* Move to next transfer */
|
|
if (trans->transfer_list.next != &msg->transfers) {
|
|
drv_data->cur_transfer =
|
|
list_entry(trans->transfer_list.next,
|
|
struct spi_transfer, transfer_list);
|
|
return RUNNING_STATE;
|
|
} else
|
|
return DONE_STATE;
|
|
}
|
|
|
|
/*
|
|
* caller already set message->status;
|
|
* dma and pio irqs are blocked give finished message back
|
|
*/
|
|
static void giveback(struct driver_data *drv_data)
|
|
{
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
struct spi_transfer *last_transfer;
|
|
unsigned long flags;
|
|
struct spi_message *msg;
|
|
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
msg = drv_data->cur_msg;
|
|
drv_data->cur_msg = NULL;
|
|
drv_data->cur_transfer = NULL;
|
|
drv_data->cur_chip = NULL;
|
|
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
|
|
last_transfer = list_entry(msg->transfers.prev,
|
|
struct spi_transfer, transfer_list);
|
|
|
|
msg->state = NULL;
|
|
|
|
/* disable chip select signal. And not stop spi in autobuffer mode */
|
|
if (drv_data->tx_dma != 0xFFFF) {
|
|
cs_deactive(drv_data, chip);
|
|
bfin_spi_disable(drv_data);
|
|
}
|
|
|
|
if (!drv_data->cs_change)
|
|
cs_deactive(drv_data, chip);
|
|
|
|
if (msg->complete)
|
|
msg->complete(msg->context);
|
|
}
|
|
|
|
static irqreturn_t dma_irq_handler(int irq, void *dev_id)
|
|
{
|
|
struct driver_data *drv_data = dev_id;
|
|
struct chip_data *chip = drv_data->cur_chip;
|
|
struct spi_message *msg = drv_data->cur_msg;
|
|
|
|
dev_dbg(&drv_data->pdev->dev, "in dma_irq_handler\n");
|
|
clear_dma_irqstat(drv_data->dma_channel);
|
|
|
|
/* Wait for DMA to complete */
|
|
while (get_dma_curr_irqstat(drv_data->dma_channel) & DMA_RUN)
|
|
cpu_relax();
|
|
|
|
/*
|
|
* wait for the last transaction shifted out. HRM states:
|
|
* at this point there may still be data in the SPI DMA FIFO waiting
|
|
* to be transmitted ... software needs to poll TXS in the SPI_STAT
|
|
* register until it goes low for 2 successive reads
|
|
*/
|
|
if (drv_data->tx != NULL) {
|
|
while ((read_STAT(drv_data) & TXS) ||
|
|
(read_STAT(drv_data) & TXS))
|
|
cpu_relax();
|
|
}
|
|
|
|
while (!(read_STAT(drv_data) & SPIF))
|
|
cpu_relax();
|
|
|
|
msg->actual_length += drv_data->len_in_bytes;
|
|
|
|
if (drv_data->cs_change)
|
|
cs_deactive(drv_data, chip);
|
|
|
|
/* Move to next transfer */
|
|
msg->state = next_transfer(drv_data);
|
|
|
|
/* Schedule transfer tasklet */
|
|
tasklet_schedule(&drv_data->pump_transfers);
|
|
|
|
/* free the irq handler before next transfer */
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"disable dma channel irq%d\n",
|
|
drv_data->dma_channel);
|
|
dma_disable_irq(drv_data->dma_channel);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void pump_transfers(unsigned long data)
|
|
{
|
|
struct driver_data *drv_data = (struct driver_data *)data;
|
|
struct spi_message *message = NULL;
|
|
struct spi_transfer *transfer = NULL;
|
|
struct spi_transfer *previous = NULL;
|
|
struct chip_data *chip = NULL;
|
|
u8 width;
|
|
u16 cr, dma_width, dma_config;
|
|
u32 tranf_success = 1;
|
|
|
|
/* Get current state information */
|
|
message = drv_data->cur_msg;
|
|
transfer = drv_data->cur_transfer;
|
|
chip = drv_data->cur_chip;
|
|
|
|
/*
|
|
* if msg is error or done, report it back using complete() callback
|
|
*/
|
|
|
|
/* Handle for abort */
|
|
if (message->state == ERROR_STATE) {
|
|
message->status = -EIO;
|
|
giveback(drv_data);
|
|
return;
|
|
}
|
|
|
|
/* Handle end of message */
|
|
if (message->state == DONE_STATE) {
|
|
message->status = 0;
|
|
giveback(drv_data);
|
|
return;
|
|
}
|
|
|
|
/* Delay if requested at end of transfer */
|
|
if (message->state == RUNNING_STATE) {
|
|
previous = list_entry(transfer->transfer_list.prev,
|
|
struct spi_transfer, transfer_list);
|
|
if (previous->delay_usecs)
|
|
udelay(previous->delay_usecs);
|
|
}
|
|
|
|
/* Setup the transfer state based on the type of transfer */
|
|
if (flush(drv_data) == 0) {
|
|
dev_err(&drv_data->pdev->dev, "pump_transfers: flush failed\n");
|
|
message->status = -EIO;
|
|
giveback(drv_data);
|
|
return;
|
|
}
|
|
|
|
if (transfer->tx_buf != NULL) {
|
|
drv_data->tx = (void *)transfer->tx_buf;
|
|
drv_data->tx_end = drv_data->tx + transfer->len;
|
|
dev_dbg(&drv_data->pdev->dev, "tx_buf is %p, tx_end is %p\n",
|
|
transfer->tx_buf, drv_data->tx_end);
|
|
} else {
|
|
drv_data->tx = NULL;
|
|
}
|
|
|
|
if (transfer->rx_buf != NULL) {
|
|
drv_data->rx = transfer->rx_buf;
|
|
drv_data->rx_end = drv_data->rx + transfer->len;
|
|
dev_dbg(&drv_data->pdev->dev, "rx_buf is %p, rx_end is %p\n",
|
|
transfer->rx_buf, drv_data->rx_end);
|
|
} else {
|
|
drv_data->rx = NULL;
|
|
}
|
|
|
|
drv_data->rx_dma = transfer->rx_dma;
|
|
drv_data->tx_dma = transfer->tx_dma;
|
|
drv_data->len_in_bytes = transfer->len;
|
|
drv_data->cs_change = transfer->cs_change;
|
|
|
|
/* Bits per word setup */
|
|
switch (transfer->bits_per_word) {
|
|
case 8:
|
|
drv_data->n_bytes = 1;
|
|
width = CFG_SPI_WORDSIZE8;
|
|
drv_data->read = chip->cs_change_per_word ?
|
|
u8_cs_chg_reader : u8_reader;
|
|
drv_data->write = chip->cs_change_per_word ?
|
|
u8_cs_chg_writer : u8_writer;
|
|
drv_data->duplex = chip->cs_change_per_word ?
|
|
u8_cs_chg_duplex : u8_duplex;
|
|
break;
|
|
|
|
case 16:
|
|
drv_data->n_bytes = 2;
|
|
width = CFG_SPI_WORDSIZE16;
|
|
drv_data->read = chip->cs_change_per_word ?
|
|
u16_cs_chg_reader : u16_reader;
|
|
drv_data->write = chip->cs_change_per_word ?
|
|
u16_cs_chg_writer : u16_writer;
|
|
drv_data->duplex = chip->cs_change_per_word ?
|
|
u16_cs_chg_duplex : u16_duplex;
|
|
break;
|
|
|
|
default:
|
|
/* No change, the same as default setting */
|
|
drv_data->n_bytes = chip->n_bytes;
|
|
width = chip->width;
|
|
drv_data->write = drv_data->tx ? chip->write : null_writer;
|
|
drv_data->read = drv_data->rx ? chip->read : null_reader;
|
|
drv_data->duplex = chip->duplex ? chip->duplex : null_writer;
|
|
break;
|
|
}
|
|
cr = (read_CTRL(drv_data) & (~BIT_CTL_TIMOD));
|
|
cr |= (width << 8);
|
|
write_CTRL(drv_data, cr);
|
|
|
|
if (width == CFG_SPI_WORDSIZE16) {
|
|
drv_data->len = (transfer->len) >> 1;
|
|
} else {
|
|
drv_data->len = transfer->len;
|
|
}
|
|
dev_dbg(&drv_data->pdev->dev, "transfer: ",
|
|
"drv_data->write is %p, chip->write is %p, null_wr is %p\n",
|
|
drv_data->write, chip->write, null_writer);
|
|
|
|
/* speed and width has been set on per message */
|
|
message->state = RUNNING_STATE;
|
|
dma_config = 0;
|
|
|
|
/* Speed setup (surely valid because already checked) */
|
|
if (transfer->speed_hz)
|
|
write_BAUD(drv_data, hz_to_spi_baud(transfer->speed_hz));
|
|
else
|
|
write_BAUD(drv_data, chip->baud);
|
|
|
|
write_STAT(drv_data, BIT_STAT_CLR);
|
|
cr = (read_CTRL(drv_data) & (~BIT_CTL_TIMOD));
|
|
cs_active(drv_data, chip);
|
|
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"now pumping a transfer: width is %d, len is %d\n",
|
|
width, transfer->len);
|
|
|
|
/*
|
|
* Try to map dma buffer and do a dma transfer if
|
|
* successful use different way to r/w according to
|
|
* drv_data->cur_chip->enable_dma
|
|
*/
|
|
if (drv_data->cur_chip->enable_dma && drv_data->len > 6) {
|
|
|
|
disable_dma(drv_data->dma_channel);
|
|
clear_dma_irqstat(drv_data->dma_channel);
|
|
bfin_spi_disable(drv_data);
|
|
|
|
/* config dma channel */
|
|
dev_dbg(&drv_data->pdev->dev, "doing dma transfer\n");
|
|
if (width == CFG_SPI_WORDSIZE16) {
|
|
set_dma_x_count(drv_data->dma_channel, drv_data->len);
|
|
set_dma_x_modify(drv_data->dma_channel, 2);
|
|
dma_width = WDSIZE_16;
|
|
} else {
|
|
set_dma_x_count(drv_data->dma_channel, drv_data->len);
|
|
set_dma_x_modify(drv_data->dma_channel, 1);
|
|
dma_width = WDSIZE_8;
|
|
}
|
|
|
|
/* poll for SPI completion before start */
|
|
while (!(read_STAT(drv_data) & BIT_STAT_SPIF))
|
|
cpu_relax();
|
|
|
|
/* dirty hack for autobuffer DMA mode */
|
|
if (drv_data->tx_dma == 0xFFFF) {
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"doing autobuffer DMA out.\n");
|
|
|
|
/* no irq in autobuffer mode */
|
|
dma_config =
|
|
(DMAFLOW_AUTO | RESTART | dma_width | DI_EN);
|
|
set_dma_config(drv_data->dma_channel, dma_config);
|
|
set_dma_start_addr(drv_data->dma_channel,
|
|
(unsigned long)drv_data->tx);
|
|
enable_dma(drv_data->dma_channel);
|
|
|
|
/* start SPI transfer */
|
|
write_CTRL(drv_data,
|
|
(cr | CFG_SPI_DMAWRITE | BIT_CTL_ENABLE));
|
|
|
|
/* just return here, there can only be one transfer
|
|
* in this mode
|
|
*/
|
|
message->status = 0;
|
|
giveback(drv_data);
|
|
return;
|
|
}
|
|
|
|
/* In dma mode, rx or tx must be NULL in one transfer */
|
|
if (drv_data->rx != NULL) {
|
|
/* set transfer mode, and enable SPI */
|
|
dev_dbg(&drv_data->pdev->dev, "doing DMA in.\n");
|
|
|
|
/* clear tx reg soformer data is not shifted out */
|
|
write_TDBR(drv_data, 0xFFFF);
|
|
|
|
set_dma_x_count(drv_data->dma_channel, drv_data->len);
|
|
|
|
/* start dma */
|
|
dma_enable_irq(drv_data->dma_channel);
|
|
dma_config = (WNR | RESTART | dma_width | DI_EN);
|
|
set_dma_config(drv_data->dma_channel, dma_config);
|
|
set_dma_start_addr(drv_data->dma_channel,
|
|
(unsigned long)drv_data->rx);
|
|
enable_dma(drv_data->dma_channel);
|
|
|
|
/* start SPI transfer */
|
|
write_CTRL(drv_data,
|
|
(cr | CFG_SPI_DMAREAD | BIT_CTL_ENABLE));
|
|
|
|
} else if (drv_data->tx != NULL) {
|
|
dev_dbg(&drv_data->pdev->dev, "doing DMA out.\n");
|
|
|
|
/* start dma */
|
|
dma_enable_irq(drv_data->dma_channel);
|
|
dma_config = (RESTART | dma_width | DI_EN);
|
|
set_dma_config(drv_data->dma_channel, dma_config);
|
|
set_dma_start_addr(drv_data->dma_channel,
|
|
(unsigned long)drv_data->tx);
|
|
enable_dma(drv_data->dma_channel);
|
|
|
|
/* start SPI transfer */
|
|
write_CTRL(drv_data,
|
|
(cr | CFG_SPI_DMAWRITE | BIT_CTL_ENABLE));
|
|
}
|
|
} else {
|
|
/* IO mode write then read */
|
|
dev_dbg(&drv_data->pdev->dev, "doing IO transfer\n");
|
|
|
|
if (drv_data->tx != NULL && drv_data->rx != NULL) {
|
|
/* full duplex mode */
|
|
BUG_ON((drv_data->tx_end - drv_data->tx) !=
|
|
(drv_data->rx_end - drv_data->rx));
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"IO duplex: cr is 0x%x\n", cr);
|
|
|
|
/* set SPI transfer mode */
|
|
write_CTRL(drv_data, (cr | CFG_SPI_WRITE));
|
|
|
|
drv_data->duplex(drv_data);
|
|
|
|
if (drv_data->tx != drv_data->tx_end)
|
|
tranf_success = 0;
|
|
} else if (drv_data->tx != NULL) {
|
|
/* write only half duplex */
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"IO write: cr is 0x%x\n", cr);
|
|
|
|
/* set SPI transfer mode */
|
|
write_CTRL(drv_data, (cr | CFG_SPI_WRITE));
|
|
|
|
drv_data->write(drv_data);
|
|
|
|
if (drv_data->tx != drv_data->tx_end)
|
|
tranf_success = 0;
|
|
} else if (drv_data->rx != NULL) {
|
|
/* read only half duplex */
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"IO read: cr is 0x%x\n", cr);
|
|
|
|
/* set SPI transfer mode */
|
|
write_CTRL(drv_data, (cr | CFG_SPI_READ));
|
|
|
|
drv_data->read(drv_data);
|
|
if (drv_data->rx != drv_data->rx_end)
|
|
tranf_success = 0;
|
|
}
|
|
|
|
if (!tranf_success) {
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"IO write error!\n");
|
|
message->state = ERROR_STATE;
|
|
} else {
|
|
/* Update total byte transfered */
|
|
message->actual_length += drv_data->len;
|
|
|
|
/* Move to next transfer of this msg */
|
|
message->state = next_transfer(drv_data);
|
|
}
|
|
|
|
/* Schedule next transfer tasklet */
|
|
tasklet_schedule(&drv_data->pump_transfers);
|
|
|
|
}
|
|
}
|
|
|
|
/* pop a msg from queue and kick off real transfer */
|
|
static void pump_messages(struct work_struct *work)
|
|
{
|
|
struct driver_data *drv_data;
|
|
unsigned long flags;
|
|
|
|
drv_data = container_of(work, struct driver_data, pump_messages);
|
|
|
|
/* Lock queue and check for queue work */
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
if (list_empty(&drv_data->queue) || drv_data->run == QUEUE_STOPPED) {
|
|
/* pumper kicked off but no work to do */
|
|
drv_data->busy = 0;
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
return;
|
|
}
|
|
|
|
/* Make sure we are not already running a message */
|
|
if (drv_data->cur_msg) {
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
return;
|
|
}
|
|
|
|
/* Extract head of queue */
|
|
drv_data->cur_msg = list_entry(drv_data->queue.next,
|
|
struct spi_message, queue);
|
|
|
|
/* Setup the SSP using the per chip configuration */
|
|
drv_data->cur_chip = spi_get_ctldata(drv_data->cur_msg->spi);
|
|
restore_state(drv_data);
|
|
|
|
list_del_init(&drv_data->cur_msg->queue);
|
|
|
|
/* Initial message state */
|
|
drv_data->cur_msg->state = START_STATE;
|
|
drv_data->cur_transfer = list_entry(drv_data->cur_msg->transfers.next,
|
|
struct spi_transfer, transfer_list);
|
|
|
|
dev_dbg(&drv_data->pdev->dev, "got a message to pump, "
|
|
"state is set to: baud %d, flag 0x%x, ctl 0x%x\n",
|
|
drv_data->cur_chip->baud, drv_data->cur_chip->flag,
|
|
drv_data->cur_chip->ctl_reg);
|
|
|
|
dev_dbg(&drv_data->pdev->dev,
|
|
"the first transfer len is %d\n",
|
|
drv_data->cur_transfer->len);
|
|
|
|
/* Mark as busy and launch transfers */
|
|
tasklet_schedule(&drv_data->pump_transfers);
|
|
|
|
drv_data->busy = 1;
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
}
|
|
|
|
/*
|
|
* got a msg to transfer, queue it in drv_data->queue.
|
|
* And kick off message pumper
|
|
*/
|
|
static int transfer(struct spi_device *spi, struct spi_message *msg)
|
|
{
|
|
struct driver_data *drv_data = spi_master_get_devdata(spi->master);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
|
|
if (drv_data->run == QUEUE_STOPPED) {
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
return -ESHUTDOWN;
|
|
}
|
|
|
|
msg->actual_length = 0;
|
|
msg->status = -EINPROGRESS;
|
|
msg->state = START_STATE;
|
|
|
|
dev_dbg(&spi->dev, "adding an msg in transfer() \n");
|
|
list_add_tail(&msg->queue, &drv_data->queue);
|
|
|
|
if (drv_data->run == QUEUE_RUNNING && !drv_data->busy)
|
|
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
|
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define MAX_SPI_SSEL 7
|
|
|
|
static u16 ssel[3][MAX_SPI_SSEL] = {
|
|
{P_SPI0_SSEL1, P_SPI0_SSEL2, P_SPI0_SSEL3,
|
|
P_SPI0_SSEL4, P_SPI0_SSEL5,
|
|
P_SPI0_SSEL6, P_SPI0_SSEL7},
|
|
|
|
{P_SPI1_SSEL1, P_SPI1_SSEL2, P_SPI1_SSEL3,
|
|
P_SPI1_SSEL4, P_SPI1_SSEL5,
|
|
P_SPI1_SSEL6, P_SPI1_SSEL7},
|
|
|
|
{P_SPI2_SSEL1, P_SPI2_SSEL2, P_SPI2_SSEL3,
|
|
P_SPI2_SSEL4, P_SPI2_SSEL5,
|
|
P_SPI2_SSEL6, P_SPI2_SSEL7},
|
|
};
|
|
|
|
/* first setup for new devices */
|
|
static int setup(struct spi_device *spi)
|
|
{
|
|
struct bfin5xx_spi_chip *chip_info = NULL;
|
|
struct chip_data *chip;
|
|
struct driver_data *drv_data = spi_master_get_devdata(spi->master);
|
|
u8 spi_flg;
|
|
|
|
/* Abort device setup if requested features are not supported */
|
|
if (spi->mode & ~(SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST)) {
|
|
dev_err(&spi->dev, "requested mode not fully supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Zero (the default) here means 8 bits */
|
|
if (!spi->bits_per_word)
|
|
spi->bits_per_word = 8;
|
|
|
|
if (spi->bits_per_word != 8 && spi->bits_per_word != 16)
|
|
return -EINVAL;
|
|
|
|
/* Only alloc (or use chip_info) on first setup */
|
|
chip = spi_get_ctldata(spi);
|
|
if (chip == NULL) {
|
|
chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
chip->enable_dma = 0;
|
|
chip_info = spi->controller_data;
|
|
}
|
|
|
|
/* chip_info isn't always needed */
|
|
if (chip_info) {
|
|
/* Make sure people stop trying to set fields via ctl_reg
|
|
* when they should actually be using common SPI framework.
|
|
* Currently we let through: WOM EMISO PSSE GM SZ TIMOD.
|
|
* Not sure if a user actually needs/uses any of these,
|
|
* but let's assume (for now) they do.
|
|
*/
|
|
if (chip_info->ctl_reg & (SPE|MSTR|CPOL|CPHA|LSBF|SIZE)) {
|
|
dev_err(&spi->dev, "do not set bits in ctl_reg "
|
|
"that the SPI framework manages\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->enable_dma = chip_info->enable_dma != 0
|
|
&& drv_data->master_info->enable_dma;
|
|
chip->ctl_reg = chip_info->ctl_reg;
|
|
chip->bits_per_word = chip_info->bits_per_word;
|
|
chip->cs_change_per_word = chip_info->cs_change_per_word;
|
|
chip->cs_chg_udelay = chip_info->cs_chg_udelay;
|
|
}
|
|
|
|
/* translate common spi framework into our register */
|
|
if (spi->mode & SPI_CPOL)
|
|
chip->ctl_reg |= CPOL;
|
|
if (spi->mode & SPI_CPHA)
|
|
chip->ctl_reg |= CPHA;
|
|
if (spi->mode & SPI_LSB_FIRST)
|
|
chip->ctl_reg |= LSBF;
|
|
/* we dont support running in slave mode (yet?) */
|
|
chip->ctl_reg |= MSTR;
|
|
|
|
/*
|
|
* if any one SPI chip is registered and wants DMA, request the
|
|
* DMA channel for it
|
|
*/
|
|
if (chip->enable_dma && !drv_data->dma_requested) {
|
|
/* register dma irq handler */
|
|
if (request_dma(drv_data->dma_channel, "BF53x_SPI_DMA") < 0) {
|
|
dev_dbg(&spi->dev,
|
|
"Unable to request BlackFin SPI DMA channel\n");
|
|
return -ENODEV;
|
|
}
|
|
if (set_dma_callback(drv_data->dma_channel,
|
|
(void *)dma_irq_handler, drv_data) < 0) {
|
|
dev_dbg(&spi->dev, "Unable to set dma callback\n");
|
|
return -EPERM;
|
|
}
|
|
dma_disable_irq(drv_data->dma_channel);
|
|
drv_data->dma_requested = 1;
|
|
}
|
|
|
|
/*
|
|
* Notice: for blackfin, the speed_hz is the value of register
|
|
* SPI_BAUD, not the real baudrate
|
|
*/
|
|
chip->baud = hz_to_spi_baud(spi->max_speed_hz);
|
|
spi_flg = ~(1 << (spi->chip_select));
|
|
chip->flag = ((u16) spi_flg << 8) | (1 << (spi->chip_select));
|
|
chip->chip_select_num = spi->chip_select;
|
|
|
|
switch (chip->bits_per_word) {
|
|
case 8:
|
|
chip->n_bytes = 1;
|
|
chip->width = CFG_SPI_WORDSIZE8;
|
|
chip->read = chip->cs_change_per_word ?
|
|
u8_cs_chg_reader : u8_reader;
|
|
chip->write = chip->cs_change_per_word ?
|
|
u8_cs_chg_writer : u8_writer;
|
|
chip->duplex = chip->cs_change_per_word ?
|
|
u8_cs_chg_duplex : u8_duplex;
|
|
break;
|
|
|
|
case 16:
|
|
chip->n_bytes = 2;
|
|
chip->width = CFG_SPI_WORDSIZE16;
|
|
chip->read = chip->cs_change_per_word ?
|
|
u16_cs_chg_reader : u16_reader;
|
|
chip->write = chip->cs_change_per_word ?
|
|
u16_cs_chg_writer : u16_writer;
|
|
chip->duplex = chip->cs_change_per_word ?
|
|
u16_cs_chg_duplex : u16_duplex;
|
|
break;
|
|
|
|
default:
|
|
dev_err(&spi->dev, "%d bits_per_word is not supported\n",
|
|
chip->bits_per_word);
|
|
kfree(chip);
|
|
return -ENODEV;
|
|
}
|
|
|
|
dev_dbg(&spi->dev, "setup spi chip %s, width is %d, dma is %d\n",
|
|
spi->modalias, chip->width, chip->enable_dma);
|
|
dev_dbg(&spi->dev, "ctl_reg is 0x%x, flag_reg is 0x%x\n",
|
|
chip->ctl_reg, chip->flag);
|
|
|
|
spi_set_ctldata(spi, chip);
|
|
|
|
dev_dbg(&spi->dev, "chip select number is %d\n", chip->chip_select_num);
|
|
if ((chip->chip_select_num > 0)
|
|
&& (chip->chip_select_num <= spi->master->num_chipselect))
|
|
peripheral_request(ssel[spi->master->bus_num]
|
|
[chip->chip_select_num-1], spi->modalias);
|
|
|
|
cs_deactive(drv_data, chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* callback for spi framework.
|
|
* clean driver specific data
|
|
*/
|
|
static void cleanup(struct spi_device *spi)
|
|
{
|
|
struct chip_data *chip = spi_get_ctldata(spi);
|
|
|
|
if ((chip->chip_select_num > 0)
|
|
&& (chip->chip_select_num <= spi->master->num_chipselect))
|
|
peripheral_free(ssel[spi->master->bus_num]
|
|
[chip->chip_select_num-1]);
|
|
|
|
kfree(chip);
|
|
}
|
|
|
|
static inline int init_queue(struct driver_data *drv_data)
|
|
{
|
|
INIT_LIST_HEAD(&drv_data->queue);
|
|
spin_lock_init(&drv_data->lock);
|
|
|
|
drv_data->run = QUEUE_STOPPED;
|
|
drv_data->busy = 0;
|
|
|
|
/* init transfer tasklet */
|
|
tasklet_init(&drv_data->pump_transfers,
|
|
pump_transfers, (unsigned long)drv_data);
|
|
|
|
/* init messages workqueue */
|
|
INIT_WORK(&drv_data->pump_messages, pump_messages);
|
|
drv_data->workqueue =
|
|
create_singlethread_workqueue(drv_data->master->dev.parent->bus_id);
|
|
if (drv_data->workqueue == NULL)
|
|
return -EBUSY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int start_queue(struct driver_data *drv_data)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
|
|
if (drv_data->run == QUEUE_RUNNING || drv_data->busy) {
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
return -EBUSY;
|
|
}
|
|
|
|
drv_data->run = QUEUE_RUNNING;
|
|
drv_data->cur_msg = NULL;
|
|
drv_data->cur_transfer = NULL;
|
|
drv_data->cur_chip = NULL;
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
|
|
queue_work(drv_data->workqueue, &drv_data->pump_messages);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int stop_queue(struct driver_data *drv_data)
|
|
{
|
|
unsigned long flags;
|
|
unsigned limit = 500;
|
|
int status = 0;
|
|
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
|
|
/*
|
|
* This is a bit lame, but is optimized for the common execution path.
|
|
* A wait_queue on the drv_data->busy could be used, but then the common
|
|
* execution path (pump_messages) would be required to call wake_up or
|
|
* friends on every SPI message. Do this instead
|
|
*/
|
|
drv_data->run = QUEUE_STOPPED;
|
|
while (!list_empty(&drv_data->queue) && drv_data->busy && limit--) {
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
msleep(10);
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
}
|
|
|
|
if (!list_empty(&drv_data->queue) || drv_data->busy)
|
|
status = -EBUSY;
|
|
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
|
|
return status;
|
|
}
|
|
|
|
static inline int destroy_queue(struct driver_data *drv_data)
|
|
{
|
|
int status;
|
|
|
|
status = stop_queue(drv_data);
|
|
if (status != 0)
|
|
return status;
|
|
|
|
destroy_workqueue(drv_data->workqueue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init bfin5xx_spi_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct bfin5xx_spi_master *platform_info;
|
|
struct spi_master *master;
|
|
struct driver_data *drv_data = 0;
|
|
struct resource *res;
|
|
int status = 0;
|
|
|
|
platform_info = dev->platform_data;
|
|
|
|
/* Allocate master with space for drv_data */
|
|
master = spi_alloc_master(dev, sizeof(struct driver_data) + 16);
|
|
if (!master) {
|
|
dev_err(&pdev->dev, "can not alloc spi_master\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
drv_data = spi_master_get_devdata(master);
|
|
drv_data->master = master;
|
|
drv_data->master_info = platform_info;
|
|
drv_data->pdev = pdev;
|
|
drv_data->pin_req = platform_info->pin_req;
|
|
|
|
master->bus_num = pdev->id;
|
|
master->num_chipselect = platform_info->num_chipselect;
|
|
master->cleanup = cleanup;
|
|
master->setup = setup;
|
|
master->transfer = transfer;
|
|
|
|
/* Find and map our resources */
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (res == NULL) {
|
|
dev_err(dev, "Cannot get IORESOURCE_MEM\n");
|
|
status = -ENOENT;
|
|
goto out_error_get_res;
|
|
}
|
|
|
|
drv_data->regs_base = ioremap(res->start, (res->end - res->start + 1));
|
|
if (drv_data->regs_base == NULL) {
|
|
dev_err(dev, "Cannot map IO\n");
|
|
status = -ENXIO;
|
|
goto out_error_ioremap;
|
|
}
|
|
|
|
drv_data->dma_channel = platform_get_irq(pdev, 0);
|
|
if (drv_data->dma_channel < 0) {
|
|
dev_err(dev, "No DMA channel specified\n");
|
|
status = -ENOENT;
|
|
goto out_error_no_dma_ch;
|
|
}
|
|
|
|
/* Initial and start queue */
|
|
status = init_queue(drv_data);
|
|
if (status != 0) {
|
|
dev_err(dev, "problem initializing queue\n");
|
|
goto out_error_queue_alloc;
|
|
}
|
|
|
|
status = start_queue(drv_data);
|
|
if (status != 0) {
|
|
dev_err(dev, "problem starting queue\n");
|
|
goto out_error_queue_alloc;
|
|
}
|
|
|
|
/* Register with the SPI framework */
|
|
platform_set_drvdata(pdev, drv_data);
|
|
status = spi_register_master(master);
|
|
if (status != 0) {
|
|
dev_err(dev, "problem registering spi master\n");
|
|
goto out_error_queue_alloc;
|
|
}
|
|
|
|
status = peripheral_request_list(drv_data->pin_req, DRV_NAME);
|
|
if (status != 0) {
|
|
dev_err(&pdev->dev, ": Requesting Peripherals failed\n");
|
|
goto out_error;
|
|
}
|
|
|
|
dev_info(dev, "%s, Version %s, regs_base@%p, dma channel@%d\n",
|
|
DRV_DESC, DRV_VERSION, drv_data->regs_base,
|
|
drv_data->dma_channel);
|
|
return status;
|
|
|
|
out_error_queue_alloc:
|
|
destroy_queue(drv_data);
|
|
out_error_no_dma_ch:
|
|
iounmap((void *) drv_data->regs_base);
|
|
out_error_ioremap:
|
|
out_error_get_res:
|
|
out_error:
|
|
spi_master_put(master);
|
|
|
|
return status;
|
|
}
|
|
|
|
/* stop hardware and remove the driver */
|
|
static int __devexit bfin5xx_spi_remove(struct platform_device *pdev)
|
|
{
|
|
struct driver_data *drv_data = platform_get_drvdata(pdev);
|
|
int status = 0;
|
|
|
|
if (!drv_data)
|
|
return 0;
|
|
|
|
/* Remove the queue */
|
|
status = destroy_queue(drv_data);
|
|
if (status != 0)
|
|
return status;
|
|
|
|
/* Disable the SSP at the peripheral and SOC level */
|
|
bfin_spi_disable(drv_data);
|
|
|
|
/* Release DMA */
|
|
if (drv_data->master_info->enable_dma) {
|
|
if (dma_channel_active(drv_data->dma_channel))
|
|
free_dma(drv_data->dma_channel);
|
|
}
|
|
|
|
/* Disconnect from the SPI framework */
|
|
spi_unregister_master(drv_data->master);
|
|
|
|
peripheral_free_list(drv_data->pin_req);
|
|
|
|
/* Prevent double remove */
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int bfin5xx_spi_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
struct driver_data *drv_data = platform_get_drvdata(pdev);
|
|
int status = 0;
|
|
|
|
status = stop_queue(drv_data);
|
|
if (status != 0)
|
|
return status;
|
|
|
|
/* stop hardware */
|
|
bfin_spi_disable(drv_data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bfin5xx_spi_resume(struct platform_device *pdev)
|
|
{
|
|
struct driver_data *drv_data = platform_get_drvdata(pdev);
|
|
int status = 0;
|
|
|
|
/* Enable the SPI interface */
|
|
bfin_spi_enable(drv_data);
|
|
|
|
/* Start the queue running */
|
|
status = start_queue(drv_data);
|
|
if (status != 0) {
|
|
dev_err(&pdev->dev, "problem starting queue (%d)\n", status);
|
|
return status;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
#define bfin5xx_spi_suspend NULL
|
|
#define bfin5xx_spi_resume NULL
|
|
#endif /* CONFIG_PM */
|
|
|
|
MODULE_ALIAS("bfin-spi-master"); /* for platform bus hotplug */
|
|
static struct platform_driver bfin5xx_spi_driver = {
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.suspend = bfin5xx_spi_suspend,
|
|
.resume = bfin5xx_spi_resume,
|
|
.remove = __devexit_p(bfin5xx_spi_remove),
|
|
};
|
|
|
|
static int __init bfin5xx_spi_init(void)
|
|
{
|
|
return platform_driver_probe(&bfin5xx_spi_driver, bfin5xx_spi_probe);
|
|
}
|
|
module_init(bfin5xx_spi_init);
|
|
|
|
static void __exit bfin5xx_spi_exit(void)
|
|
{
|
|
platform_driver_unregister(&bfin5xx_spi_driver);
|
|
}
|
|
module_exit(bfin5xx_spi_exit);
|