diff options
Diffstat (limited to 'drivers/spi/spi-rspi.c')
| -rw-r--r-- | drivers/spi/spi-rspi.c | 521 | 
1 files changed, 521 insertions, 0 deletions
| diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c new file mode 100644 index 000000000000..354f170eab95 --- /dev/null +++ b/drivers/spi/spi-rspi.c @@ -0,0 +1,521 @@ +/* + * SH RSPI driver + * + * Copyright (C) 2012  Renesas Solutions Corp. + * + * Based on spi-sh.c: + * Copyright (C) 2011 Renesas Solutions Corp. + * + * 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; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/spi/spi.h> + +#define RSPI_SPCR		0x00 +#define RSPI_SSLP		0x01 +#define RSPI_SPPCR		0x02 +#define RSPI_SPSR		0x03 +#define RSPI_SPDR		0x04 +#define RSPI_SPSCR		0x08 +#define RSPI_SPSSR		0x09 +#define RSPI_SPBR		0x0a +#define RSPI_SPDCR		0x0b +#define RSPI_SPCKD		0x0c +#define RSPI_SSLND		0x0d +#define RSPI_SPND		0x0e +#define RSPI_SPCR2		0x0f +#define RSPI_SPCMD0		0x10 +#define RSPI_SPCMD1		0x12 +#define RSPI_SPCMD2		0x14 +#define RSPI_SPCMD3		0x16 +#define RSPI_SPCMD4		0x18 +#define RSPI_SPCMD5		0x1a +#define RSPI_SPCMD6		0x1c +#define RSPI_SPCMD7		0x1e + +/* SPCR */ +#define SPCR_SPRIE		0x80 +#define SPCR_SPE		0x40 +#define SPCR_SPTIE		0x20 +#define SPCR_SPEIE		0x10 +#define SPCR_MSTR		0x08 +#define SPCR_MODFEN		0x04 +#define SPCR_TXMD		0x02 +#define SPCR_SPMS		0x01 + +/* SSLP */ +#define SSLP_SSL1P		0x02 +#define SSLP_SSL0P		0x01 + +/* SPPCR */ +#define SPPCR_MOIFE		0x20 +#define SPPCR_MOIFV		0x10 +#define SPPCR_SPOM		0x04 +#define SPPCR_SPLP2		0x02 +#define SPPCR_SPLP		0x01 + +/* SPSR */ +#define SPSR_SPRF		0x80 +#define SPSR_SPTEF		0x20 +#define SPSR_PERF		0x08 +#define SPSR_MODF		0x04 +#define SPSR_IDLNF		0x02 +#define SPSR_OVRF		0x01 + +/* SPSCR */ +#define SPSCR_SPSLN_MASK	0x07 + +/* SPSSR */ +#define SPSSR_SPECM_MASK	0x70 +#define SPSSR_SPCP_MASK		0x07 + +/* SPDCR */ +#define SPDCR_SPLW		0x20 +#define SPDCR_SPRDTD		0x10 +#define SPDCR_SLSEL1		0x08 +#define SPDCR_SLSEL0		0x04 +#define SPDCR_SLSEL_MASK	0x0c +#define SPDCR_SPFC1		0x02 +#define SPDCR_SPFC0		0x01 + +/* SPCKD */ +#define SPCKD_SCKDL_MASK	0x07 + +/* SSLND */ +#define SSLND_SLNDL_MASK	0x07 + +/* SPND */ +#define SPND_SPNDL_MASK		0x07 + +/* SPCR2 */ +#define SPCR2_PTE		0x08 +#define SPCR2_SPIE		0x04 +#define SPCR2_SPOE		0x02 +#define SPCR2_SPPE		0x01 + +/* SPCMDn */ +#define SPCMD_SCKDEN		0x8000 +#define SPCMD_SLNDEN		0x4000 +#define SPCMD_SPNDEN		0x2000 +#define SPCMD_LSBF		0x1000 +#define SPCMD_SPB_MASK		0x0f00 +#define SPCMD_SPB_8_TO_16(bit)	(((bit - 1) << 8) & SPCMD_SPB_MASK) +#define SPCMD_SPB_20BIT		0x0000 +#define SPCMD_SPB_24BIT		0x0100 +#define SPCMD_SPB_32BIT		0x0200 +#define SPCMD_SSLKP		0x0080 +#define SPCMD_SSLA_MASK		0x0030 +#define SPCMD_BRDV_MASK		0x000c +#define SPCMD_CPOL		0x0002 +#define SPCMD_CPHA		0x0001 + +struct rspi_data { +	void __iomem *addr; +	u32 max_speed_hz; +	struct spi_master *master; +	struct list_head queue; +	struct work_struct ws; +	wait_queue_head_t wait; +	spinlock_t lock; +	struct clk *clk; +	unsigned char spsr; +}; + +static void rspi_write8(struct rspi_data *rspi, u8 data, u16 offset) +{ +	iowrite8(data, rspi->addr + offset); +} + +static void rspi_write16(struct rspi_data *rspi, u16 data, u16 offset) +{ +	iowrite16(data, rspi->addr + offset); +} + +static u8 rspi_read8(struct rspi_data *rspi, u16 offset) +{ +	return ioread8(rspi->addr + offset); +} + +static u16 rspi_read16(struct rspi_data *rspi, u16 offset) +{ +	return ioread16(rspi->addr + offset); +} + +static unsigned char rspi_calc_spbr(struct rspi_data *rspi) +{ +	int tmp; +	unsigned char spbr; + +	tmp = clk_get_rate(rspi->clk) / (2 * rspi->max_speed_hz) - 1; +	spbr = clamp(tmp, 0, 255); + +	return spbr; +} + +static void rspi_enable_irq(struct rspi_data *rspi, u8 enable) +{ +	rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) | enable, RSPI_SPCR); +} + +static void rspi_disable_irq(struct rspi_data *rspi, u8 disable) +{ +	rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) & ~disable, RSPI_SPCR); +} + +static int rspi_wait_for_interrupt(struct rspi_data *rspi, u8 wait_mask, +				   u8 enable_bit) +{ +	int ret; + +	rspi->spsr = rspi_read8(rspi, RSPI_SPSR); +	rspi_enable_irq(rspi, enable_bit); +	ret = wait_event_timeout(rspi->wait, rspi->spsr & wait_mask, HZ); +	if (ret == 0 && !(rspi->spsr & wait_mask)) +		return -ETIMEDOUT; + +	return 0; +} + +static void rspi_assert_ssl(struct rspi_data *rspi) +{ +	rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) | SPCR_SPE, RSPI_SPCR); +} + +static void rspi_negate_ssl(struct rspi_data *rspi) +{ +	rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) & ~SPCR_SPE, RSPI_SPCR); +} + +static int rspi_set_config_register(struct rspi_data *rspi, int access_size) +{ +	/* Sets output mode(CMOS) and MOSI signal(from previous transfer) */ +	rspi_write8(rspi, 0x00, RSPI_SPPCR); + +	/* Sets transfer bit rate */ +	rspi_write8(rspi, rspi_calc_spbr(rspi), RSPI_SPBR); + +	/* Sets number of frames to be used: 1 frame */ +	rspi_write8(rspi, 0x00, RSPI_SPDCR); + +	/* Sets RSPCK, SSL, next-access delay value */ +	rspi_write8(rspi, 0x00, RSPI_SPCKD); +	rspi_write8(rspi, 0x00, RSPI_SSLND); +	rspi_write8(rspi, 0x00, RSPI_SPND); + +	/* Sets parity, interrupt mask */ +	rspi_write8(rspi, 0x00, RSPI_SPCR2); + +	/* Sets SPCMD */ +	rspi_write16(rspi, SPCMD_SPB_8_TO_16(access_size) | SPCMD_SSLKP, +		     RSPI_SPCMD0); + +	/* Sets RSPI mode */ +	rspi_write8(rspi, SPCR_MSTR, RSPI_SPCR); + +	return 0; +} + +static int rspi_send_pio(struct rspi_data *rspi, struct spi_message *mesg, +			 struct spi_transfer *t) +{ +	int remain = t->len; +	u8 *data; + +	data = (u8 *)t->tx_buf; +	while (remain > 0) { +		rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) | SPCR_TXMD, +			    RSPI_SPCR); + +		if (rspi_wait_for_interrupt(rspi, SPSR_SPTEF, SPCR_SPTIE) < 0) { +			dev_err(&rspi->master->dev, +				"%s: tx empty timeout\n", __func__); +			return -ETIMEDOUT; +		} + +		rspi_write16(rspi, *data, RSPI_SPDR); +		data++; +		remain--; +	} + +	/* Waiting for the last transmition */ +	rspi_wait_for_interrupt(rspi, SPSR_SPTEF, SPCR_SPTIE); + +	return 0; +} + +static int rspi_receive_pio(struct rspi_data *rspi, struct spi_message *mesg, +			    struct spi_transfer *t) +{ +	int remain = t->len; +	u8 *data; +	unsigned char spsr; + +	spsr = rspi_read8(rspi, RSPI_SPSR); +	if (spsr & SPSR_SPRF) +		rspi_read16(rspi, RSPI_SPDR);	/* dummy read */ +	if (spsr & SPSR_OVRF) +		rspi_write8(rspi, rspi_read8(rspi, RSPI_SPSR) & ~SPSR_OVRF, +			    RSPI_SPCR); + +	data = (u8 *)t->rx_buf; +	while (remain > 0) { +		rspi_write8(rspi, rspi_read8(rspi, RSPI_SPCR) & ~SPCR_TXMD, +			    RSPI_SPCR); + +		if (rspi_wait_for_interrupt(rspi, SPSR_SPTEF, SPCR_SPTIE) < 0) { +			dev_err(&rspi->master->dev, +				"%s: tx empty timeout\n", __func__); +			return -ETIMEDOUT; +		} +		/* dummy write for generate clock */ +		rspi_write16(rspi, 0x00, RSPI_SPDR); + +		if (rspi_wait_for_interrupt(rspi, SPSR_SPRF, SPCR_SPRIE) < 0) { +			dev_err(&rspi->master->dev, +				"%s: receive timeout\n", __func__); +			return -ETIMEDOUT; +		} +		/* SPDR allows 16 or 32-bit access only */ +		*data = (u8)rspi_read16(rspi, RSPI_SPDR); + +		data++; +		remain--; +	} + +	return 0; +} + +static void rspi_work(struct work_struct *work) +{ +	struct rspi_data *rspi = container_of(work, struct rspi_data, ws); +	struct spi_message *mesg; +	struct spi_transfer *t; +	unsigned long flags; +	int ret; + +	spin_lock_irqsave(&rspi->lock, flags); +	while (!list_empty(&rspi->queue)) { +		mesg = list_entry(rspi->queue.next, struct spi_message, queue); +		list_del_init(&mesg->queue); +		spin_unlock_irqrestore(&rspi->lock, flags); + +		rspi_assert_ssl(rspi); + +		list_for_each_entry(t, &mesg->transfers, transfer_list) { +			if (t->tx_buf) { +				ret = rspi_send_pio(rspi, mesg, t); +				if (ret < 0) +					goto error; +			} +			if (t->rx_buf) { +				ret = rspi_receive_pio(rspi, mesg, t); +				if (ret < 0) +					goto error; +			} +			mesg->actual_length += t->len; +		} +		rspi_negate_ssl(rspi); + +		mesg->status = 0; +		mesg->complete(mesg->context); + +		spin_lock_irqsave(&rspi->lock, flags); +	} + +	return; + +error: +	mesg->status = ret; +	mesg->complete(mesg->context); +} + +static int rspi_setup(struct spi_device *spi) +{ +	struct rspi_data *rspi = spi_master_get_devdata(spi->master); + +	if (!spi->bits_per_word) +		spi->bits_per_word = 8; +	rspi->max_speed_hz = spi->max_speed_hz; + +	rspi_set_config_register(rspi, 8); + +	return 0; +} + +static int rspi_transfer(struct spi_device *spi, struct spi_message *mesg) +{ +	struct rspi_data *rspi = spi_master_get_devdata(spi->master); +	unsigned long flags; + +	mesg->actual_length = 0; +	mesg->status = -EINPROGRESS; + +	spin_lock_irqsave(&rspi->lock, flags); +	list_add_tail(&mesg->queue, &rspi->queue); +	schedule_work(&rspi->ws); +	spin_unlock_irqrestore(&rspi->lock, flags); + +	return 0; +} + +static void rspi_cleanup(struct spi_device *spi) +{ +} + +static irqreturn_t rspi_irq(int irq, void *_sr) +{ +	struct rspi_data *rspi = (struct rspi_data *)_sr; +	unsigned long spsr; +	irqreturn_t ret = IRQ_NONE; +	unsigned char disable_irq = 0; + +	rspi->spsr = spsr = rspi_read8(rspi, RSPI_SPSR); +	if (spsr & SPSR_SPRF) +		disable_irq |= SPCR_SPRIE; +	if (spsr & SPSR_SPTEF) +		disable_irq |= SPCR_SPTIE; + +	if (disable_irq) { +		ret = IRQ_HANDLED; +		rspi_disable_irq(rspi, disable_irq); +		wake_up(&rspi->wait); +	} + +	return ret; +} + +static int __devexit rspi_remove(struct platform_device *pdev) +{ +	struct rspi_data *rspi = dev_get_drvdata(&pdev->dev); + +	spi_unregister_master(rspi->master); +	free_irq(platform_get_irq(pdev, 0), rspi); +	clk_put(rspi->clk); +	iounmap(rspi->addr); +	spi_master_put(rspi->master); + +	return 0; +} + +static int __devinit rspi_probe(struct platform_device *pdev) +{ +	struct resource *res; +	struct spi_master *master; +	struct rspi_data *rspi; +	int ret, irq; +	char clk_name[16]; + +	/* get base addr */ +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (unlikely(res == NULL)) { +		dev_err(&pdev->dev, "invalid resource\n"); +		return -EINVAL; +	} + +	irq = platform_get_irq(pdev, 0); +	if (irq < 0) { +		dev_err(&pdev->dev, "platform_get_irq error\n"); +		return -ENODEV; +	} + +	master = spi_alloc_master(&pdev->dev, sizeof(struct rspi_data)); +	if (master == NULL) { +		dev_err(&pdev->dev, "spi_alloc_master error.\n"); +		return -ENOMEM; +	} + +	rspi = spi_master_get_devdata(master); +	dev_set_drvdata(&pdev->dev, rspi); + +	rspi->master = master; +	rspi->addr = ioremap(res->start, resource_size(res)); +	if (rspi->addr == NULL) { +		dev_err(&pdev->dev, "ioremap error.\n"); +		ret = -ENOMEM; +		goto error1; +	} + +	snprintf(clk_name, sizeof(clk_name), "rspi%d", pdev->id); +	rspi->clk = clk_get(&pdev->dev, clk_name); +	if (IS_ERR(rspi->clk)) { +		dev_err(&pdev->dev, "cannot get clock\n"); +		ret = PTR_ERR(rspi->clk); +		goto error2; +	} +	clk_enable(rspi->clk); + +	INIT_LIST_HEAD(&rspi->queue); +	spin_lock_init(&rspi->lock); +	INIT_WORK(&rspi->ws, rspi_work); +	init_waitqueue_head(&rspi->wait); + +	master->num_chipselect = 2; +	master->bus_num = pdev->id; +	master->setup = rspi_setup; +	master->transfer = rspi_transfer; +	master->cleanup = rspi_cleanup; + +	ret = request_irq(irq, rspi_irq, 0, dev_name(&pdev->dev), rspi); +	if (ret < 0) { +		dev_err(&pdev->dev, "request_irq error\n"); +		goto error3; +	} + +	ret = spi_register_master(master); +	if (ret < 0) { +		dev_err(&pdev->dev, "spi_register_master error.\n"); +		goto error4; +	} + +	dev_info(&pdev->dev, "probed\n"); + +	return 0; + +error4: +	free_irq(irq, rspi); +error3: +	clk_put(rspi->clk); +error2: +	iounmap(rspi->addr); +error1: +	spi_master_put(master); + +	return ret; +} + +static struct platform_driver rspi_driver = { +	.probe =	rspi_probe, +	.remove =	__devexit_p(rspi_remove), +	.driver		= { +		.name = "rspi", +		.owner	= THIS_MODULE, +	}, +}; +module_platform_driver(rspi_driver); + +MODULE_DESCRIPTION("Renesas RSPI bus driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_ALIAS("platform:rspi"); | 
