|  | /* | 
|  | * SH SPI bus driver | 
|  | * | 
|  | * Copyright (C) 2011  Renesas Solutions Corp. | 
|  | * | 
|  | * Based on pxa2xx_spi.c: | 
|  | * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs | 
|  | * | 
|  | * 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/timer.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/list.h> | 
|  | #include <linux/workqueue.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/spi/spi.h> | 
|  |  | 
|  | #define SPI_SH_TBR		0x00 | 
|  | #define SPI_SH_RBR		0x00 | 
|  | #define SPI_SH_CR1		0x08 | 
|  | #define SPI_SH_CR2		0x10 | 
|  | #define SPI_SH_CR3		0x18 | 
|  | #define SPI_SH_CR4		0x20 | 
|  | #define SPI_SH_CR5		0x28 | 
|  |  | 
|  | /* CR1 */ | 
|  | #define SPI_SH_TBE		0x80 | 
|  | #define SPI_SH_TBF		0x40 | 
|  | #define SPI_SH_RBE		0x20 | 
|  | #define SPI_SH_RBF		0x10 | 
|  | #define SPI_SH_PFONRD		0x08 | 
|  | #define SPI_SH_SSDB		0x04 | 
|  | #define SPI_SH_SSD		0x02 | 
|  | #define SPI_SH_SSA		0x01 | 
|  |  | 
|  | /* CR2 */ | 
|  | #define SPI_SH_RSTF		0x80 | 
|  | #define SPI_SH_LOOPBK		0x40 | 
|  | #define SPI_SH_CPOL		0x20 | 
|  | #define SPI_SH_CPHA		0x10 | 
|  | #define SPI_SH_L1M0		0x08 | 
|  |  | 
|  | /* CR3 */ | 
|  | #define SPI_SH_MAX_BYTE		0xFF | 
|  |  | 
|  | /* CR4 */ | 
|  | #define SPI_SH_TBEI		0x80 | 
|  | #define SPI_SH_TBFI		0x40 | 
|  | #define SPI_SH_RBEI		0x20 | 
|  | #define SPI_SH_RBFI		0x10 | 
|  | #define SPI_SH_WPABRT		0x04 | 
|  | #define SPI_SH_SSS		0x01 | 
|  |  | 
|  | /* CR8 */ | 
|  | #define SPI_SH_P1L0		0x80 | 
|  | #define SPI_SH_PP1L0		0x40 | 
|  | #define SPI_SH_MUXI		0x20 | 
|  | #define SPI_SH_MUXIRQ		0x10 | 
|  |  | 
|  | #define SPI_SH_FIFO_SIZE	32 | 
|  | #define SPI_SH_SEND_TIMEOUT	(3 * HZ) | 
|  | #define SPI_SH_RECEIVE_TIMEOUT	(HZ >> 3) | 
|  |  | 
|  | #undef DEBUG | 
|  |  | 
|  | struct spi_sh_data { | 
|  | void __iomem *addr; | 
|  | int irq; | 
|  | struct spi_master *master; | 
|  | struct list_head queue; | 
|  | struct workqueue_struct *workqueue; | 
|  | struct work_struct ws; | 
|  | unsigned long cr1; | 
|  | wait_queue_head_t wait; | 
|  | spinlock_t lock; | 
|  | int width; | 
|  | }; | 
|  |  | 
|  | static void spi_sh_write(struct spi_sh_data *ss, unsigned long data, | 
|  | unsigned long offset) | 
|  | { | 
|  | if (ss->width == 8) | 
|  | iowrite8(data, ss->addr + (offset >> 2)); | 
|  | else if (ss->width == 32) | 
|  | iowrite32(data, ss->addr + offset); | 
|  | } | 
|  |  | 
|  | static unsigned long spi_sh_read(struct spi_sh_data *ss, unsigned long offset) | 
|  | { | 
|  | if (ss->width == 8) | 
|  | return ioread8(ss->addr + (offset >> 2)); | 
|  | else if (ss->width == 32) | 
|  | return ioread32(ss->addr + offset); | 
|  | else | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void spi_sh_set_bit(struct spi_sh_data *ss, unsigned long val, | 
|  | unsigned long offset) | 
|  | { | 
|  | unsigned long tmp; | 
|  |  | 
|  | tmp = spi_sh_read(ss, offset); | 
|  | tmp |= val; | 
|  | spi_sh_write(ss, tmp, offset); | 
|  | } | 
|  |  | 
|  | static void spi_sh_clear_bit(struct spi_sh_data *ss, unsigned long val, | 
|  | unsigned long offset) | 
|  | { | 
|  | unsigned long tmp; | 
|  |  | 
|  | tmp = spi_sh_read(ss, offset); | 
|  | tmp &= ~val; | 
|  | spi_sh_write(ss, tmp, offset); | 
|  | } | 
|  |  | 
|  | static void clear_fifo(struct spi_sh_data *ss) | 
|  | { | 
|  | spi_sh_set_bit(ss, SPI_SH_RSTF, SPI_SH_CR2); | 
|  | spi_sh_clear_bit(ss, SPI_SH_RSTF, SPI_SH_CR2); | 
|  | } | 
|  |  | 
|  | static int spi_sh_wait_receive_buffer(struct spi_sh_data *ss) | 
|  | { | 
|  | int timeout = 100000; | 
|  |  | 
|  | while (spi_sh_read(ss, SPI_SH_CR1) & SPI_SH_RBE) { | 
|  | udelay(10); | 
|  | if (timeout-- < 0) | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int spi_sh_wait_write_buffer_empty(struct spi_sh_data *ss) | 
|  | { | 
|  | int timeout = 100000; | 
|  |  | 
|  | while (!(spi_sh_read(ss, SPI_SH_CR1) & SPI_SH_TBE)) { | 
|  | udelay(10); | 
|  | if (timeout-- < 0) | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int spi_sh_send(struct spi_sh_data *ss, struct spi_message *mesg, | 
|  | struct spi_transfer *t) | 
|  | { | 
|  | int i, retval = 0; | 
|  | int remain = t->len; | 
|  | int cur_len; | 
|  | unsigned char *data; | 
|  | unsigned long tmp; | 
|  | long ret; | 
|  |  | 
|  | if (t->len) | 
|  | spi_sh_set_bit(ss, SPI_SH_SSA, SPI_SH_CR1); | 
|  |  | 
|  | data = (unsigned char *)t->tx_buf; | 
|  | while (remain > 0) { | 
|  | cur_len = min(SPI_SH_FIFO_SIZE, remain); | 
|  | for (i = 0; i < cur_len && | 
|  | !(spi_sh_read(ss, SPI_SH_CR4) & | 
|  | SPI_SH_WPABRT) && | 
|  | !(spi_sh_read(ss, SPI_SH_CR1) & SPI_SH_TBF); | 
|  | i++) | 
|  | spi_sh_write(ss, (unsigned long)data[i], SPI_SH_TBR); | 
|  |  | 
|  | if (spi_sh_read(ss, SPI_SH_CR4) & SPI_SH_WPABRT) { | 
|  | /* Abort SPI operation */ | 
|  | spi_sh_set_bit(ss, SPI_SH_WPABRT, SPI_SH_CR4); | 
|  | retval = -EIO; | 
|  | break; | 
|  | } | 
|  |  | 
|  | cur_len = i; | 
|  |  | 
|  | remain -= cur_len; | 
|  | data += cur_len; | 
|  |  | 
|  | if (remain > 0) { | 
|  | ss->cr1 &= ~SPI_SH_TBE; | 
|  | spi_sh_set_bit(ss, SPI_SH_TBE, SPI_SH_CR4); | 
|  | ret = wait_event_interruptible_timeout(ss->wait, | 
|  | ss->cr1 & SPI_SH_TBE, | 
|  | SPI_SH_SEND_TIMEOUT); | 
|  | if (ret == 0 && !(ss->cr1 & SPI_SH_TBE)) { | 
|  | printk(KERN_ERR "%s: timeout\n", __func__); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (list_is_last(&t->transfer_list, &mesg->transfers)) { | 
|  | tmp = spi_sh_read(ss, SPI_SH_CR1); | 
|  | tmp = tmp & ~(SPI_SH_SSD | SPI_SH_SSDB); | 
|  | spi_sh_write(ss, tmp, SPI_SH_CR1); | 
|  | spi_sh_set_bit(ss, SPI_SH_SSA, SPI_SH_CR1); | 
|  |  | 
|  | ss->cr1 &= ~SPI_SH_TBE; | 
|  | spi_sh_set_bit(ss, SPI_SH_TBE, SPI_SH_CR4); | 
|  | ret = wait_event_interruptible_timeout(ss->wait, | 
|  | ss->cr1 & SPI_SH_TBE, | 
|  | SPI_SH_SEND_TIMEOUT); | 
|  | if (ret == 0 && (ss->cr1 & SPI_SH_TBE)) { | 
|  | printk(KERN_ERR "%s: timeout\n", __func__); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | } | 
|  |  | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | static int spi_sh_receive(struct spi_sh_data *ss, struct spi_message *mesg, | 
|  | struct spi_transfer *t) | 
|  | { | 
|  | int i; | 
|  | int remain = t->len; | 
|  | int cur_len; | 
|  | unsigned char *data; | 
|  | unsigned long tmp; | 
|  | long ret; | 
|  |  | 
|  | if (t->len > SPI_SH_MAX_BYTE) | 
|  | spi_sh_write(ss, SPI_SH_MAX_BYTE, SPI_SH_CR3); | 
|  | else | 
|  | spi_sh_write(ss, t->len, SPI_SH_CR3); | 
|  |  | 
|  | tmp = spi_sh_read(ss, SPI_SH_CR1); | 
|  | tmp = tmp & ~(SPI_SH_SSD | SPI_SH_SSDB); | 
|  | spi_sh_write(ss, tmp, SPI_SH_CR1); | 
|  | spi_sh_set_bit(ss, SPI_SH_SSA, SPI_SH_CR1); | 
|  |  | 
|  | spi_sh_wait_write_buffer_empty(ss); | 
|  |  | 
|  | data = (unsigned char *)t->rx_buf; | 
|  | while (remain > 0) { | 
|  | if (remain >= SPI_SH_FIFO_SIZE) { | 
|  | ss->cr1 &= ~SPI_SH_RBF; | 
|  | spi_sh_set_bit(ss, SPI_SH_RBF, SPI_SH_CR4); | 
|  | ret = wait_event_interruptible_timeout(ss->wait, | 
|  | ss->cr1 & SPI_SH_RBF, | 
|  | SPI_SH_RECEIVE_TIMEOUT); | 
|  | if (ret == 0 && | 
|  | spi_sh_read(ss, SPI_SH_CR1) & SPI_SH_RBE) { | 
|  | printk(KERN_ERR "%s: timeout\n", __func__); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  | } | 
|  |  | 
|  | cur_len = min(SPI_SH_FIFO_SIZE, remain); | 
|  | for (i = 0; i < cur_len; i++) { | 
|  | if (spi_sh_wait_receive_buffer(ss)) | 
|  | break; | 
|  | data[i] = (unsigned char)spi_sh_read(ss, SPI_SH_RBR); | 
|  | } | 
|  |  | 
|  | remain -= cur_len; | 
|  | data += cur_len; | 
|  | } | 
|  |  | 
|  | /* deassert CS when SPI is receiving. */ | 
|  | if (t->len > SPI_SH_MAX_BYTE) { | 
|  | clear_fifo(ss); | 
|  | spi_sh_write(ss, 1, SPI_SH_CR3); | 
|  | } else { | 
|  | spi_sh_write(ss, 0, SPI_SH_CR3); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void spi_sh_work(struct work_struct *work) | 
|  | { | 
|  | struct spi_sh_data *ss = container_of(work, struct spi_sh_data, ws); | 
|  | struct spi_message *mesg; | 
|  | struct spi_transfer *t; | 
|  | unsigned long flags; | 
|  | int ret; | 
|  |  | 
|  | pr_debug("%s: enter\n", __func__); | 
|  |  | 
|  | spin_lock_irqsave(&ss->lock, flags); | 
|  | while (!list_empty(&ss->queue)) { | 
|  | mesg = list_entry(ss->queue.next, struct spi_message, queue); | 
|  | list_del_init(&mesg->queue); | 
|  |  | 
|  | spin_unlock_irqrestore(&ss->lock, flags); | 
|  | list_for_each_entry(t, &mesg->transfers, transfer_list) { | 
|  | pr_debug("tx_buf = %p, rx_buf = %p\n", | 
|  | t->tx_buf, t->rx_buf); | 
|  | pr_debug("len = %d, delay_usecs = %d\n", | 
|  | t->len, t->delay_usecs); | 
|  |  | 
|  | if (t->tx_buf) { | 
|  | ret = spi_sh_send(ss, mesg, t); | 
|  | if (ret < 0) | 
|  | goto error; | 
|  | } | 
|  | if (t->rx_buf) { | 
|  | ret = spi_sh_receive(ss, mesg, t); | 
|  | if (ret < 0) | 
|  | goto error; | 
|  | } | 
|  | mesg->actual_length += t->len; | 
|  | } | 
|  | spin_lock_irqsave(&ss->lock, flags); | 
|  |  | 
|  | mesg->status = 0; | 
|  | mesg->complete(mesg->context); | 
|  | } | 
|  |  | 
|  | clear_fifo(ss); | 
|  | spi_sh_set_bit(ss, SPI_SH_SSD, SPI_SH_CR1); | 
|  | udelay(100); | 
|  |  | 
|  | spi_sh_clear_bit(ss, SPI_SH_SSA | SPI_SH_SSDB | SPI_SH_SSD, | 
|  | SPI_SH_CR1); | 
|  |  | 
|  | clear_fifo(ss); | 
|  |  | 
|  | spin_unlock_irqrestore(&ss->lock, flags); | 
|  |  | 
|  | return; | 
|  |  | 
|  | error: | 
|  | mesg->status = ret; | 
|  | mesg->complete(mesg->context); | 
|  |  | 
|  | spi_sh_clear_bit(ss, SPI_SH_SSA | SPI_SH_SSDB | SPI_SH_SSD, | 
|  | SPI_SH_CR1); | 
|  | clear_fifo(ss); | 
|  |  | 
|  | } | 
|  |  | 
|  | static int spi_sh_setup(struct spi_device *spi) | 
|  | { | 
|  | struct spi_sh_data *ss = spi_master_get_devdata(spi->master); | 
|  |  | 
|  | if (!spi->bits_per_word) | 
|  | spi->bits_per_word = 8; | 
|  |  | 
|  | pr_debug("%s: enter\n", __func__); | 
|  |  | 
|  | spi_sh_write(ss, 0xfe, SPI_SH_CR1);	/* SPI sycle stop */ | 
|  | spi_sh_write(ss, 0x00, SPI_SH_CR1);	/* CR1 init */ | 
|  | spi_sh_write(ss, 0x00, SPI_SH_CR3);	/* CR3 init */ | 
|  |  | 
|  | clear_fifo(ss); | 
|  |  | 
|  | /* 1/8 clock */ | 
|  | spi_sh_write(ss, spi_sh_read(ss, SPI_SH_CR2) | 0x07, SPI_SH_CR2); | 
|  | udelay(10); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int spi_sh_transfer(struct spi_device *spi, struct spi_message *mesg) | 
|  | { | 
|  | struct spi_sh_data *ss = spi_master_get_devdata(spi->master); | 
|  | unsigned long flags; | 
|  |  | 
|  | pr_debug("%s: enter\n", __func__); | 
|  | pr_debug("\tmode = %02x\n", spi->mode); | 
|  |  | 
|  | spin_lock_irqsave(&ss->lock, flags); | 
|  |  | 
|  | mesg->actual_length = 0; | 
|  | mesg->status = -EINPROGRESS; | 
|  |  | 
|  | spi_sh_clear_bit(ss, SPI_SH_SSA, SPI_SH_CR1); | 
|  |  | 
|  | list_add_tail(&mesg->queue, &ss->queue); | 
|  | queue_work(ss->workqueue, &ss->ws); | 
|  |  | 
|  | spin_unlock_irqrestore(&ss->lock, flags); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void spi_sh_cleanup(struct spi_device *spi) | 
|  | { | 
|  | struct spi_sh_data *ss = spi_master_get_devdata(spi->master); | 
|  |  | 
|  | pr_debug("%s: enter\n", __func__); | 
|  |  | 
|  | spi_sh_clear_bit(ss, SPI_SH_SSA | SPI_SH_SSDB | SPI_SH_SSD, | 
|  | SPI_SH_CR1); | 
|  | } | 
|  |  | 
|  | static irqreturn_t spi_sh_irq(int irq, void *_ss) | 
|  | { | 
|  | struct spi_sh_data *ss = (struct spi_sh_data *)_ss; | 
|  | unsigned long cr1; | 
|  |  | 
|  | cr1 = spi_sh_read(ss, SPI_SH_CR1); | 
|  | if (cr1 & SPI_SH_TBE) | 
|  | ss->cr1 |= SPI_SH_TBE; | 
|  | if (cr1 & SPI_SH_TBF) | 
|  | ss->cr1 |= SPI_SH_TBF; | 
|  | if (cr1 & SPI_SH_RBE) | 
|  | ss->cr1 |= SPI_SH_RBE; | 
|  | if (cr1 & SPI_SH_RBF) | 
|  | ss->cr1 |= SPI_SH_RBF; | 
|  |  | 
|  | if (ss->cr1) { | 
|  | spi_sh_clear_bit(ss, ss->cr1, SPI_SH_CR4); | 
|  | wake_up(&ss->wait); | 
|  | } | 
|  |  | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static int spi_sh_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct spi_sh_data *ss = platform_get_drvdata(pdev); | 
|  |  | 
|  | spi_unregister_master(ss->master); | 
|  | destroy_workqueue(ss->workqueue); | 
|  | free_irq(ss->irq, ss); | 
|  | iounmap(ss->addr); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int spi_sh_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct resource *res; | 
|  | struct spi_master *master; | 
|  | struct spi_sh_data *ss; | 
|  | int ret, irq; | 
|  |  | 
|  | /* 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 spi_sh_data)); | 
|  | if (master == NULL) { | 
|  | dev_err(&pdev->dev, "spi_alloc_master error.\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | ss = spi_master_get_devdata(master); | 
|  | platform_set_drvdata(pdev, ss); | 
|  |  | 
|  | switch (res->flags & IORESOURCE_MEM_TYPE_MASK) { | 
|  | case IORESOURCE_MEM_8BIT: | 
|  | ss->width = 8; | 
|  | break; | 
|  | case IORESOURCE_MEM_32BIT: | 
|  | ss->width = 32; | 
|  | break; | 
|  | default: | 
|  | dev_err(&pdev->dev, "No support width\n"); | 
|  | ret = -ENODEV; | 
|  | goto error1; | 
|  | } | 
|  | ss->irq = irq; | 
|  | ss->master = master; | 
|  | ss->addr = ioremap(res->start, resource_size(res)); | 
|  | if (ss->addr == NULL) { | 
|  | dev_err(&pdev->dev, "ioremap error.\n"); | 
|  | ret = -ENOMEM; | 
|  | goto error1; | 
|  | } | 
|  | INIT_LIST_HEAD(&ss->queue); | 
|  | spin_lock_init(&ss->lock); | 
|  | INIT_WORK(&ss->ws, spi_sh_work); | 
|  | init_waitqueue_head(&ss->wait); | 
|  | ss->workqueue = create_singlethread_workqueue( | 
|  | dev_name(master->dev.parent)); | 
|  | if (ss->workqueue == NULL) { | 
|  | dev_err(&pdev->dev, "create workqueue error\n"); | 
|  | ret = -EBUSY; | 
|  | goto error2; | 
|  | } | 
|  |  | 
|  | ret = request_irq(irq, spi_sh_irq, 0, "spi_sh", ss); | 
|  | if (ret < 0) { | 
|  | dev_err(&pdev->dev, "request_irq error\n"); | 
|  | goto error3; | 
|  | } | 
|  |  | 
|  | master->num_chipselect = 2; | 
|  | master->bus_num = pdev->id; | 
|  | master->setup = spi_sh_setup; | 
|  | master->transfer = spi_sh_transfer; | 
|  | master->cleanup = spi_sh_cleanup; | 
|  |  | 
|  | ret = spi_register_master(master); | 
|  | if (ret < 0) { | 
|  | printk(KERN_ERR "spi_register_master error.\n"); | 
|  | goto error4; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | error4: | 
|  | free_irq(irq, ss); | 
|  | error3: | 
|  | destroy_workqueue(ss->workqueue); | 
|  | error2: | 
|  | iounmap(ss->addr); | 
|  | error1: | 
|  | spi_master_put(master); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static struct platform_driver spi_sh_driver = { | 
|  | .probe = spi_sh_probe, | 
|  | .remove = spi_sh_remove, | 
|  | .driver = { | 
|  | .name = "sh_spi", | 
|  | .owner = THIS_MODULE, | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(spi_sh_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("SH SPI bus driver"); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_AUTHOR("Yoshihiro Shimoda"); | 
|  | MODULE_ALIAS("platform:sh_spi"); |