/*
* SPI bus via the Blackfin SPORT peripheral
*
* Enter bugs at http://blackfin.uclinux.org/
*
* Copyright 2009-2011 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio.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/spi/spi.h>
#include <linux/workqueue.h>
#include <asm/portmux.h>
#include <asm/bfin5xx_spi.h>
#include <asm/blackfin.h>
#include <asm/bfin_sport.h>
#include <asm/cacheflush.h>
#define DRV_NAME "bfin-sport-spi"
#define DRV_DESC "SPI bus via the Blackfin SPORT"
MODULE_AUTHOR("Cliff Cai");
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:bfin-sport-spi");
enum bfin_sport_spi_state {
START_STATE,
RUNNING_STATE,
DONE_STATE,
ERROR_STATE,
};
struct bfin_sport_spi_master_data;
struct bfin_sport_transfer_ops {
void (*write) (struct bfin_sport_spi_master_data *);
void (*read) (struct bfin_sport_spi_master_data *);
void (*duplex) (struct bfin_sport_spi_master_data *);
};
struct bfin_sport_spi_master_data {
/* Driver model hookup */
struct device *dev;
/* SPI framework hookup */
struct spi_master *master;
/* Regs base of SPI controller */
struct sport_register __iomem *regs;
int err_irq;
/* Pin request list */
u16 *pin_req;
/* Driver message queue */
struct workqueue_struct *workqueue;
struct work_struct pump_messages;
spinlock_t lock;
struct list_head queue;
int busy;
bool run;
/* Message Transfer pump */
struct tasklet_struct pump_transfers;
/* Current message transfer state info */
enum bfin_sport_spi_state state;
struct spi_message *cur_msg;
struct spi_transfer *cur_transfer;
struct bfin_sport_spi_slave_data *cur_chip;
union {
void *tx;
u8 *tx8;
u16 *tx16;
};
void *tx_end;
union {
void *rx;
u8 *rx8;
u16 *rx16;
};
void *rx_end;
int cs_change;
struct bfin_sport_transfer_ops *ops;
};
struct bfin_sport_spi_slave_data {
u16 ctl_reg;
u16 baud;
u16 cs_chg_udelay; /* Some devices require > 255usec delay */
u32 cs_gpio;
u16 idle_tx_val;
struct bfin_sport_transfer_ops *ops;
};
static void
bfin_sport_spi_enable(struct bfin_sport_spi_master_data *drv_data)
{
bfin_write_or(&drv_data->regs->tcr1, TSPEN);
bfin_write_or(&drv_data->regs->rcr1, TSPEN);
SSYNC();
}
static void
bfin_sport_spi_disable(struct bfin_sport_spi_master_data *drv_data)
{
bfin_write_and(&drv_data->regs->tcr1, ~TSPEN);
bfin_write_and(&drv_data->regs->rcr1, ~TSPEN);
SSYNC();
}
/* Caculate the SPI_BAUD register value based on input HZ */
static u16
bfin_sport_hz_to_spi_baud(u32 speed_hz)
{
u_long clk, sclk = get_sclk();
int div = (sclk / (2 * speed_hz)) - 1;
if (div < 0)
div = 0;
clk = sclk / (2 * (div + 1));
if (clk > speed_hz)
div++;
return div;
}
/* Chip select operation functions for cs_change flag */
static void
bfin_sport_spi_cs_active(struct bfin_sport_spi_slave_data *chip)
{
gpio_direction_output(chip->cs_gpio, 0);
}
static void
bfin_sport_spi_cs_deactive(struct bfin_sport_spi_slave_data *chip)
{
gpio_direction_output(chip->cs_gpio, 1);
/* Move delay here for consistency */
if (chip->cs_chg_udelay)
udelay(chip->cs_chg_udelay);
}
static void
bfin_sport_spi_stat_poll_complete(struct bfin_sport_spi_master_data *drv_data)
{
unsigned long timeout = jiffies + HZ;
while (!(bfin_read(&drv_data->regs->stat) & RXNE)) {
if (!time_before(jiffies, timeout))
break;
}
}
static void
bfin_sport_spi_u8_writer(struct bfin_sport_spi_master_data *drv_data)
{
u16 dummy;
while (drv_data->tx < drv_data->tx_end) {
bfin_write(&drv_data->regs->tx16, *drv_data->tx8++);
bfin_sport_spi_stat_poll_complete(drv_data);
dummy = bfin_read(&drv_data->regs->rx16);
}
}
static void
bfin_sport_spi_u8_reader(struct bfin_sport_spi_master_data *drv_data)
{
u16 tx_val = drv_data->cur_chip->idle_tx_val;
while (drv_data->rx < drv_data->rx_end) {
bfin_write(&drv_data->regs->tx16, tx_val);
bfin_sport_spi_stat_poll_complete(drv_data);
*drv_data->rx8++ = bfin_read(&drv_data->regs->rx16);
}
}
static void
bfin_sport_spi_u8_duplex(struct bfin_sport_spi_master_data *drv_data)
{
while (drv_data->rx < drv_data->rx_end) {
bfin_write(&drv_data