diff options
Diffstat (limited to 'drivers/spi/spi-bfin-sport.c')
| -rw-r--r-- | drivers/spi/spi-bfin-sport.c | 930 | 
1 files changed, 930 insertions, 0 deletions
diff --git a/drivers/spi/spi-bfin-sport.c b/drivers/spi/spi-bfin-sport.c new file mode 100644 index 00000000000..f515c5e9db5 --- /dev/null +++ b/drivers/spi/spi-bfin-sport.c @@ -0,0 +1,930 @@ +/* + * 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->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; +	queue_work(drv_data->workqueue, &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; + +	if (transfer->speed_hz) +		transfer_speed = bfin_sport_hz_to_spi_baud(transfer->speed_hz); +	else +		transfer_speed = chip->baud; +	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) +		queue_work(drv_data->workqueue, &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 messages workqueue */ +	INIT_WORK(&drv_data->pump_messages, bfin_sport_spi_pump_messages); +	drv_data->workqueue = +	    create_singlethread_workqueue(dev_name(drv_data->master->dev.parent)); +	if (drv_data->workqueue == NULL) +		return -EBUSY; + +	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); + +	queue_work(drv_data->workqueue, &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; + +	destroy_workqueue(drv_data->workqueue); + +	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, +		.owner	= THIS_MODULE, +		.pm	= BFIN_SPORT_SPI_PM_OPS, +	}, +	.probe   = bfin_sport_spi_probe, +	.remove  = bfin_sport_spi_remove, +}; +module_platform_driver(bfin_sport_spi_driver);  | 
