mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-27 22:53:55 +08:00
eac9837f87
The workqueue "workqueue" serves as a driver message queue. It has a single work item(&drv_data->pump_messages) and hence doesn't require ordering. Also, it is not being used on a memory reclaim path. Hence, the singlethreaded workqueue has been replaced with the use of system_wq. System workqueues have been able to handle high level of concurrency for a long time now and hence it's not required to have a singlethreaded workqueue just to gain concurrency. Unlike a dedicated per-cpu workqueue created with create_singlethread_workqueue(), system_wq allows multiple work items to overlap executions even on the same CPU; however, a per-cpu workqueue doesn't have any CPU locality or global ordering guarantee unless the target CPU is explicitly specified and thus the increase of local concurrency shouldn't make any difference. Work item has been flushed in bfin_sport_spi_destroy_queue() to ensure that there are no pending tasks while disconnecting the driver. Signed-off-by: Bhaktipriya Shridhar <bhaktipriya96@gmail.com> Acked-by: Tejun Heo <tj@kernel.org> Signed-off-by: Mark Brown <broonie@kernel.org>
920 lines
22 KiB
C
920 lines
22 KiB
C
/*
|
|
* 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;
|
|
|
|
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->regs->tx16, *drv_data->tx8++);
|
|
bfin_sport_spi_stat_poll_complete(drv_data);
|
|
*drv_data->rx8++ = bfin_read(&drv_data->regs->rx16);
|
|
}
|
|
}
|
|
|
|
static struct bfin_sport_transfer_ops bfin_sport_transfer_ops_u8 = {
|
|
.write = bfin_sport_spi_u8_writer,
|
|
.read = bfin_sport_spi_u8_reader,
|
|
.duplex = bfin_sport_spi_u8_duplex,
|
|
};
|
|
|
|
static void
|
|
bfin_sport_spi_u16_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->tx16++);
|
|
bfin_sport_spi_stat_poll_complete(drv_data);
|
|
dummy = bfin_read(&drv_data->regs->rx16);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bfin_sport_spi_u16_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->rx16++ = bfin_read(&drv_data->regs->rx16);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bfin_sport_spi_u16_duplex(struct bfin_sport_spi_master_data *drv_data)
|
|
{
|
|
while (drv_data->rx < drv_data->rx_end) {
|
|
bfin_write(&drv_data->regs->tx16, *drv_data->tx16++);
|
|
bfin_sport_spi_stat_poll_complete(drv_data);
|
|
*drv_data->rx16++ = bfin_read(&drv_data->regs->rx16);
|
|
}
|
|
}
|
|
|
|
static struct bfin_sport_transfer_ops bfin_sport_transfer_ops_u16 = {
|
|
.write = bfin_sport_spi_u16_writer,
|
|
.read = bfin_sport_spi_u16_reader,
|
|
.duplex = bfin_sport_spi_u16_duplex,
|
|
};
|
|
|
|
/* stop controller and re-config current chip */
|
|
static void
|
|
bfin_sport_spi_restore_state(struct bfin_sport_spi_master_data *drv_data)
|
|
{
|
|
struct bfin_sport_spi_slave_data *chip = drv_data->cur_chip;
|
|
|
|
bfin_sport_spi_disable(drv_data);
|
|
dev_dbg(drv_data->dev, "restoring spi ctl state\n");
|
|
|
|
bfin_write(&drv_data->regs->tcr1, chip->ctl_reg);
|
|
bfin_write(&drv_data->regs->tclkdiv, chip->baud);
|
|
SSYNC();
|
|
|
|
bfin_write(&drv_data->regs->rcr1, chip->ctl_reg & ~(ITCLK | ITFS));
|
|
SSYNC();
|
|
|
|
bfin_sport_spi_cs_active(chip);
|
|
}
|
|
|
|
/* test if there is more transfer to be done */
|
|
static enum bfin_sport_spi_state
|
|
bfin_sport_spi_next_transfer(struct bfin_sport_spi_master_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;
|
|
}
|
|
|
|
return DONE_STATE;
|
|
}
|
|
|
|
/*
|
|
* caller already set message->status;
|
|
* dma and pio irqs are blocked give finished message back
|
|
*/
|
|
static void
|
|
bfin_sport_spi_giveback(struct bfin_sport_spi_master_data *drv_data)
|
|
{
|
|
struct bfin_sport_spi_slave_data *chip = drv_data->cur_chip;
|
|
unsigned long flags;
|
|
struct spi_message *msg;
|
|
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
msg = drv_data->cur_msg;
|
|
drv_data->state = START_STATE;
|
|
drv_data->cur_msg = NULL;
|
|
drv_data->cur_transfer = NULL;
|
|
drv_data->cur_chip = NULL;
|
|
schedule_work(&drv_data->pump_messages);
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
|
|
if (!drv_data->cs_change)
|
|
bfin_sport_spi_cs_deactive(chip);
|
|
|
|
if (msg->complete)
|
|
msg->complete(msg->context);
|
|
}
|
|
|
|
static irqreturn_t
|
|
sport_err_handler(int irq, void *dev_id)
|
|
{
|
|
struct bfin_sport_spi_master_data *drv_data = dev_id;
|
|
u16 status;
|
|
|
|
dev_dbg(drv_data->dev, "%s enter\n", __func__);
|
|
status = bfin_read(&drv_data->regs->stat) & (TOVF | TUVF | ROVF | RUVF);
|
|
|
|
if (status) {
|
|
bfin_write(&drv_data->regs->stat, status);
|
|
SSYNC();
|
|
|
|
bfin_sport_spi_disable(drv_data);
|
|
dev_err(drv_data->dev, "status error:%s%s%s%s\n",
|
|
status & TOVF ? " TOVF" : "",
|
|
status & TUVF ? " TUVF" : "",
|
|
status & ROVF ? " ROVF" : "",
|
|
status & RUVF ? " RUVF" : "");
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void
|
|
bfin_sport_spi_pump_transfers(unsigned long data)
|
|
{
|
|
struct bfin_sport_spi_master_data *drv_data = (void *)data;
|
|
struct spi_message *message = NULL;
|
|
struct spi_transfer *transfer = NULL;
|
|
struct spi_transfer *previous = NULL;
|
|
struct bfin_sport_spi_slave_data *chip = NULL;
|
|
unsigned int bits_per_word;
|
|
u32 tranf_success = 1;
|
|
u32 transfer_speed;
|
|
u8 full_duplex = 0;
|
|
|
|
/* Get current state information */
|
|
message = drv_data->cur_msg;
|
|
transfer = drv_data->cur_transfer;
|
|
chip = drv_data->cur_chip;
|
|
|
|
transfer_speed = bfin_sport_hz_to_spi_baud(transfer->speed_hz);
|
|
bfin_write(&drv_data->regs->tclkdiv, transfer_speed);
|
|
SSYNC();
|
|
|
|
/*
|
|
* if msg is error or done, report it back using complete() callback
|
|
*/
|
|
|
|
/* Handle for abort */
|
|
if (drv_data->state == ERROR_STATE) {
|
|
dev_dbg(drv_data->dev, "transfer: we've hit an error\n");
|
|
message->status = -EIO;
|
|
bfin_sport_spi_giveback(drv_data);
|
|
return;
|
|
}
|
|
|
|
/* Handle end of message */
|
|
if (drv_data->state == DONE_STATE) {
|
|
dev_dbg(drv_data->dev, "transfer: all done!\n");
|
|
message->status = 0;
|
|
bfin_sport_spi_giveback(drv_data);
|
|
return;
|
|
}
|
|
|
|
/* Delay if requested at end of transfer */
|
|
if (drv_data->state == RUNNING_STATE) {
|
|
dev_dbg(drv_data->dev, "transfer: still running ...\n");
|
|
previous = list_entry(transfer->transfer_list.prev,
|
|
struct spi_transfer, transfer_list);
|
|
if (previous->delay_usecs)
|
|
udelay(previous->delay_usecs);
|
|
}
|
|
|
|
if (transfer->len == 0) {
|
|
/* Move to next transfer of this msg */
|
|
drv_data->state = bfin_sport_spi_next_transfer(drv_data);
|
|
/* Schedule next transfer tasklet */
|
|
tasklet_schedule(&drv_data->pump_transfers);
|
|
}
|
|
|
|
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->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) {
|
|
full_duplex = transfer->tx_buf != NULL;
|
|
drv_data->rx = transfer->rx_buf;
|
|
drv_data->rx_end = drv_data->rx + transfer->len;
|
|
dev_dbg(drv_data->dev, "rx_buf is %p, rx_end is %p\n",
|
|
transfer->rx_buf, drv_data->rx_end);
|
|
} else
|
|
drv_data->rx = NULL;
|
|
|
|
drv_data->cs_change = transfer->cs_change;
|
|
|
|
/* Bits per word setup */
|
|
bits_per_word = transfer->bits_per_word;
|
|
if (bits_per_word == 16)
|
|
drv_data->ops = &bfin_sport_transfer_ops_u16;
|
|
else
|
|
drv_data->ops = &bfin_sport_transfer_ops_u8;
|
|
bfin_write(&drv_data->regs->tcr2, bits_per_word - 1);
|
|
bfin_write(&drv_data->regs->tfsdiv, bits_per_word - 1);
|
|
bfin_write(&drv_data->regs->rcr2, bits_per_word - 1);
|
|
|
|
drv_data->state = RUNNING_STATE;
|
|
|
|
if (drv_data->cs_change)
|
|
bfin_sport_spi_cs_active(chip);
|
|
|
|
dev_dbg(drv_data->dev,
|
|
"now pumping a transfer: width is %d, len is %d\n",
|
|
bits_per_word, transfer->len);
|
|
|
|
/* PIO mode write then read */
|
|
dev_dbg(drv_data->dev, "doing IO transfer\n");
|
|
|
|
bfin_sport_spi_enable(drv_data);
|
|
if (full_duplex) {
|
|
/* full duplex mode */
|
|
BUG_ON((drv_data->tx_end - drv_data->tx) !=
|
|
(drv_data->rx_end - drv_data->rx));
|
|
drv_data->ops->duplex(drv_data);
|
|
|
|
if (drv_data->tx != drv_data->tx_end)
|
|
tranf_success = 0;
|
|
} else if (drv_data->tx != NULL) {
|
|
/* write only half duplex */
|
|
|
|
drv_data->ops->write(drv_data);
|
|
|
|
if (drv_data->tx != drv_data->tx_end)
|
|
tranf_success = 0;
|
|
} else if (drv_data->rx != NULL) {
|
|
/* read only half duplex */
|
|
|
|
drv_data->ops->read(drv_data);
|
|
if (drv_data->rx != drv_data->rx_end)
|
|
tranf_success = 0;
|
|
}
|
|
bfin_sport_spi_disable(drv_data);
|
|
|
|
if (!tranf_success) {
|
|
dev_dbg(drv_data->dev, "IO write error!\n");
|
|
drv_data->state = ERROR_STATE;
|
|
} else {
|
|
/* Update total byte transferred */
|
|
message->actual_length += transfer->len;
|
|
/* Move to next transfer of this msg */
|
|
drv_data->state = bfin_sport_spi_next_transfer(drv_data);
|
|
if (drv_data->cs_change)
|
|
bfin_sport_spi_cs_deactive(chip);
|
|
}
|
|
|
|
/* Schedule next transfer tasklet */
|
|
tasklet_schedule(&drv_data->pump_transfers);
|
|
}
|
|
|
|
/* pop a msg from queue and kick off real transfer */
|
|
static void
|
|
bfin_sport_spi_pump_messages(struct work_struct *work)
|
|
{
|
|
struct bfin_sport_spi_master_data *drv_data;
|
|
unsigned long flags;
|
|
struct spi_message *next_msg;
|
|
|
|
drv_data = container_of(work, struct bfin_sport_spi_master_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) {
|
|
/* 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 */
|
|
next_msg = list_entry(drv_data->queue.next,
|
|
struct spi_message, queue);
|
|
|
|
drv_data->cur_msg = next_msg;
|
|
|
|
/* Setup the SSP using the per chip configuration */
|
|
drv_data->cur_chip = spi_get_ctldata(drv_data->cur_msg->spi);
|
|
|
|
list_del_init(&drv_data->cur_msg->queue);
|
|
|
|
/* Initialize 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);
|
|
bfin_sport_spi_restore_state(drv_data);
|
|
dev_dbg(drv_data->dev, "got a message to pump, "
|
|
"state is set to: baud %d, cs_gpio %i, ctl 0x%x\n",
|
|
drv_data->cur_chip->baud, drv_data->cur_chip->cs_gpio,
|
|
drv_data->cur_chip->ctl_reg);
|
|
|
|
dev_dbg(drv_data->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
|
|
bfin_sport_spi_transfer(struct spi_device *spi, struct spi_message *msg)
|
|
{
|
|
struct bfin_sport_spi_master_data *drv_data = spi_master_get_devdata(spi->master);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
|
|
if (!drv_data->run) {
|
|
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 && !drv_data->busy)
|
|
schedule_work(&drv_data->pump_messages);
|
|
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Called every time common spi devices change state */
|
|
static int
|
|
bfin_sport_spi_setup(struct spi_device *spi)
|
|
{
|
|
struct bfin_sport_spi_slave_data *chip, *first = NULL;
|
|
int ret;
|
|
|
|
/* Only alloc (or use chip_info) on first setup */
|
|
chip = spi_get_ctldata(spi);
|
|
if (chip == NULL) {
|
|
struct bfin5xx_spi_chip *chip_info;
|
|
|
|
chip = first = kzalloc(sizeof(*chip), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
/* platform chip_info isn't required */
|
|
chip_info = spi->controller_data;
|
|
if (chip_info) {
|
|
/*
|
|
* DITFS and TDTYPE are only thing we don't set, but
|
|
* they probably shouldn't be changed by people.
|
|
*/
|
|
if (chip_info->ctl_reg || chip_info->enable_dma) {
|
|
ret = -EINVAL;
|
|
dev_err(&spi->dev, "don't set ctl_reg/enable_dma fields\n");
|
|
goto error;
|
|
}
|
|
chip->cs_chg_udelay = chip_info->cs_chg_udelay;
|
|
chip->idle_tx_val = chip_info->idle_tx_val;
|
|
}
|
|
}
|
|
|
|
/* translate common spi framework into our register
|
|
* following configure contents are same for tx and rx.
|
|
*/
|
|
|
|
if (spi->mode & SPI_CPHA)
|
|
chip->ctl_reg &= ~TCKFE;
|
|
else
|
|
chip->ctl_reg |= TCKFE;
|
|
|
|
if (spi->mode & SPI_LSB_FIRST)
|
|
chip->ctl_reg |= TLSBIT;
|
|
else
|
|
chip->ctl_reg &= ~TLSBIT;
|
|
|
|
/* Sport in master mode */
|
|
chip->ctl_reg |= ITCLK | ITFS | TFSR | LATFS | LTFS;
|
|
|
|
chip->baud = bfin_sport_hz_to_spi_baud(spi->max_speed_hz);
|
|
|
|
chip->cs_gpio = spi->chip_select;
|
|
ret = gpio_request(chip->cs_gpio, spi->modalias);
|
|
if (ret)
|
|
goto error;
|
|
|
|
dev_dbg(&spi->dev, "setup spi chip %s, width is %d\n",
|
|
spi->modalias, spi->bits_per_word);
|
|
dev_dbg(&spi->dev, "ctl_reg is 0x%x, GPIO is %i\n",
|
|
chip->ctl_reg, spi->chip_select);
|
|
|
|
spi_set_ctldata(spi, chip);
|
|
|
|
bfin_sport_spi_cs_deactive(chip);
|
|
|
|
return ret;
|
|
|
|
error:
|
|
kfree(first);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* callback for spi framework.
|
|
* clean driver specific data
|
|
*/
|
|
static void
|
|
bfin_sport_spi_cleanup(struct spi_device *spi)
|
|
{
|
|
struct bfin_sport_spi_slave_data *chip = spi_get_ctldata(spi);
|
|
|
|
if (!chip)
|
|
return;
|
|
|
|
gpio_free(chip->cs_gpio);
|
|
|
|
kfree(chip);
|
|
}
|
|
|
|
static int
|
|
bfin_sport_spi_init_queue(struct bfin_sport_spi_master_data *drv_data)
|
|
{
|
|
INIT_LIST_HEAD(&drv_data->queue);
|
|
spin_lock_init(&drv_data->lock);
|
|
|
|
drv_data->run = false;
|
|
drv_data->busy = 0;
|
|
|
|
/* init transfer tasklet */
|
|
tasklet_init(&drv_data->pump_transfers,
|
|
bfin_sport_spi_pump_transfers, (unsigned long)drv_data);
|
|
|
|
INIT_WORK(&drv_data->pump_messages, bfin_sport_spi_pump_messages);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bfin_sport_spi_start_queue(struct bfin_sport_spi_master_data *drv_data)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&drv_data->lock, flags);
|
|
|
|
if (drv_data->run || drv_data->busy) {
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
return -EBUSY;
|
|
}
|
|
|
|
drv_data->run = true;
|
|
drv_data->cur_msg = NULL;
|
|
drv_data->cur_transfer = NULL;
|
|
drv_data->cur_chip = NULL;
|
|
spin_unlock_irqrestore(&drv_data->lock, flags);
|
|
|
|
schedule_work(&drv_data->pump_messages);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int
|
|
bfin_sport_spi_stop_queue(struct bfin_sport_spi_master_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 = false;
|
|
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
|
|
bfin_sport_spi_destroy_queue(struct bfin_sport_spi_master_data *drv_data)
|
|
{
|
|
int status;
|
|
|
|
status = bfin_sport_spi_stop_queue(drv_data);
|
|
if (status)
|
|
return status;
|
|
|
|
flush_work(&drv_data->pump_messages);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bfin_sport_spi_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct bfin5xx_spi_master *platform_info;
|
|
struct spi_master *master;
|
|
struct resource *res, *ires;
|
|
struct bfin_sport_spi_master_data *drv_data;
|
|
int status;
|
|
|
|
platform_info = dev_get_platdata(dev);
|
|
|
|
/* Allocate master with space for drv_data */
|
|
master = spi_alloc_master(dev, sizeof(*master) + 16);
|
|
if (!master) {
|
|
dev_err(dev, "cannot alloc spi_master\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
drv_data = spi_master_get_devdata(master);
|
|
drv_data->master = master;
|
|
drv_data->dev = dev;
|
|
drv_data->pin_req = platform_info->pin_req;
|
|
|
|
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST;
|
|
master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
|
|
master->bus_num = pdev->id;
|
|
master->num_chipselect = platform_info->num_chipselect;
|
|
master->cleanup = bfin_sport_spi_cleanup;
|
|
master->setup = bfin_sport_spi_setup;
|
|
master->transfer = bfin_sport_spi_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 = ioremap(res->start, resource_size(res));
|
|
if (drv_data->regs == NULL) {
|
|
dev_err(dev, "cannot map registers\n");
|
|
status = -ENXIO;
|
|
goto out_error_ioremap;
|
|
}
|
|
|
|
ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!ires) {
|
|
dev_err(dev, "cannot get IORESOURCE_IRQ\n");
|
|
status = -ENODEV;
|
|
goto out_error_get_ires;
|
|
}
|
|
drv_data->err_irq = ires->start;
|
|
|
|
/* Initial and start queue */
|
|
status = bfin_sport_spi_init_queue(drv_data);
|
|
if (status) {
|
|
dev_err(dev, "problem initializing queue\n");
|
|
goto out_error_queue_alloc;
|
|
}
|
|
|
|
status = bfin_sport_spi_start_queue(drv_data);
|
|
if (status) {
|
|
dev_err(dev, "problem starting queue\n");
|
|
goto out_error_queue_alloc;
|
|
}
|
|
|
|
status = request_irq(drv_data->err_irq, sport_err_handler,
|
|
0, "sport_spi_err", drv_data);
|
|
if (status) {
|
|
dev_err(dev, "unable to request sport err irq\n");
|
|
goto out_error_irq;
|
|
}
|
|
|
|
status = peripheral_request_list(drv_data->pin_req, DRV_NAME);
|
|
if (status) {
|
|
dev_err(dev, "requesting peripherals failed\n");
|
|
goto out_error_peripheral;
|
|
}
|
|
|
|
/* Register with the SPI framework */
|
|
platform_set_drvdata(pdev, drv_data);
|
|
status = spi_register_master(master);
|
|
if (status) {
|
|
dev_err(dev, "problem registering spi master\n");
|
|
goto out_error_master;
|
|
}
|
|
|
|
dev_info(dev, "%s, regs_base@%p\n", DRV_DESC, drv_data->regs);
|
|
return 0;
|
|
|
|
out_error_master:
|
|
peripheral_free_list(drv_data->pin_req);
|
|
out_error_peripheral:
|
|
free_irq(drv_data->err_irq, drv_data);
|
|
out_error_irq:
|
|
out_error_queue_alloc:
|
|
bfin_sport_spi_destroy_queue(drv_data);
|
|
out_error_get_ires:
|
|
iounmap(drv_data->regs);
|
|
out_error_ioremap:
|
|
out_error_get_res:
|
|
spi_master_put(master);
|
|
|
|
return status;
|
|
}
|
|
|
|
/* stop hardware and remove the driver */
|
|
static int bfin_sport_spi_remove(struct platform_device *pdev)
|
|
{
|
|
struct bfin_sport_spi_master_data *drv_data = platform_get_drvdata(pdev);
|
|
int status = 0;
|
|
|
|
if (!drv_data)
|
|
return 0;
|
|
|
|
/* Remove the queue */
|
|
status = bfin_sport_spi_destroy_queue(drv_data);
|
|
if (status)
|
|
return status;
|
|
|
|
/* Disable the SSP at the peripheral and SOC level */
|
|
bfin_sport_spi_disable(drv_data);
|
|
|
|
/* Disconnect from the SPI framework */
|
|
spi_unregister_master(drv_data->master);
|
|
|
|
peripheral_free_list(drv_data->pin_req);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int bfin_sport_spi_suspend(struct device *dev)
|
|
{
|
|
struct bfin_sport_spi_master_data *drv_data = dev_get_drvdata(dev);
|
|
int status;
|
|
|
|
status = bfin_sport_spi_stop_queue(drv_data);
|
|
if (status)
|
|
return status;
|
|
|
|
/* stop hardware */
|
|
bfin_sport_spi_disable(drv_data);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int bfin_sport_spi_resume(struct device *dev)
|
|
{
|
|
struct bfin_sport_spi_master_data *drv_data = dev_get_drvdata(dev);
|
|
int status;
|
|
|
|
/* Enable the SPI interface */
|
|
bfin_sport_spi_enable(drv_data);
|
|
|
|
/* Start the queue running */
|
|
status = bfin_sport_spi_start_queue(drv_data);
|
|
if (status)
|
|
dev_err(drv_data->dev, "problem resuming queue\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(bfin_sport_spi_pm_ops, bfin_sport_spi_suspend,
|
|
bfin_sport_spi_resume);
|
|
|
|
#define BFIN_SPORT_SPI_PM_OPS (&bfin_sport_spi_pm_ops)
|
|
#else
|
|
#define BFIN_SPORT_SPI_PM_OPS NULL
|
|
#endif
|
|
|
|
static struct platform_driver bfin_sport_spi_driver = {
|
|
.driver = {
|
|
.name = DRV_NAME,
|
|
.pm = BFIN_SPORT_SPI_PM_OPS,
|
|
},
|
|
.probe = bfin_sport_spi_probe,
|
|
.remove = bfin_sport_spi_remove,
|
|
};
|
|
module_platform_driver(bfin_sport_spi_driver);
|