2011-02-15 04:30:32 +03:00
/*
* 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 .
*/
# 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 work_struct ws ;
unsigned long cr1 ;
wait_queue_head_t wait ;
spinlock_t lock ;
2012-03-07 09:45:37 +04:00
int width ;
2011-02-15 04:30:32 +03:00
} ;
static void spi_sh_write ( struct spi_sh_data * ss , unsigned long data ,
unsigned long offset )
{
2012-03-07 09:45:37 +04:00
if ( ss - > width = = 8 )
iowrite8 ( data , ss - > addr + ( offset > > 2 ) ) ;
else if ( ss - > width = = 32 )
iowrite32 ( data , ss - > addr + offset ) ;
2011-02-15 04:30:32 +03:00
}
static unsigned long spi_sh_read ( struct spi_sh_data * ss , unsigned long offset )
{
2012-03-07 09:45:37 +04:00
if ( ss - > width = = 8 )
return ioread8 ( ss - > addr + ( offset > > 2 ) ) ;
else if ( ss - > width = = 32 )
return ioread32 ( ss - > addr + offset ) ;
else
return 0 ;
2011-02-15 04:30:32 +03:00
}
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 ;
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 ) ) {
2014-01-13 06:00:44 +04:00
spi_sh_clear_bit ( ss , SPI_SH_SSD | SPI_SH_SSDB , SPI_SH_CR1 ) ;
2011-02-15 04:30:32 +03:00
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 ;
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 ) ;
2014-01-13 06:00:44 +04:00
spi_sh_clear_bit ( ss , SPI_SH_SSD | SPI_SH_SSDB , SPI_SH_CR1 ) ;
2011-02-15 04:30:32 +03:00
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 ;
2014-04-02 18:21:04 +04:00
if ( mesg - > complete )
mesg - > complete ( mesg - > context ) ;
2011-02-15 04:30:32 +03:00
}
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 ;
2014-04-02 18:21:04 +04:00
if ( mesg - > complete )
mesg - > complete ( mesg - > context ) ;
2011-02-15 04:30:32 +03:00
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 ) ;
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 ( " \t mode = %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 ) ;
2016-07-10 17:25:19 +03:00
schedule_work ( & ss - > ws ) ;
2011-02-15 04:30:32 +03:00
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 ;
}
2012-12-07 20:57:14 +04:00
static int spi_sh_remove ( struct platform_device * pdev )
2011-02-15 04:30:32 +03:00
{
2013-05-23 14:20:40 +04:00
struct spi_sh_data * ss = platform_get_drvdata ( pdev ) ;
2011-02-15 04:30:32 +03:00
2011-05-11 17:27:00 +04:00
spi_unregister_master ( ss - > master ) ;
2016-07-10 17:25:19 +03:00
flush_work ( & ss - > ws ) ;
2011-02-15 04:30:32 +03:00
free_irq ( ss - > irq , ss ) ;
return 0 ;
}
2012-12-07 20:57:14 +04:00
static int spi_sh_probe ( struct platform_device * pdev )
2011-02-15 04:30:32 +03:00
{
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 ) {
2017-08-09 18:46:48 +03:00
dev_err ( & pdev - > dev , " platform_get_irq error: %d \n " , irq ) ;
return irq ;
2011-02-15 04:30:32 +03:00
}
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 ) ;
2013-05-23 14:20:40 +04:00
platform_set_drvdata ( pdev , ss ) ;
2011-02-15 04:30:32 +03:00
2012-03-07 09:45:37 +04:00
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 ;
}
2011-02-15 04:30:32 +03:00
ss - > irq = irq ;
ss - > master = master ;
2014-07-06 20:14:50 +04:00
ss - > addr = devm_ioremap ( & pdev - > dev , res - > start , resource_size ( res ) ) ;
2011-02-15 04:30:32 +03:00
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 ) ;
2011-10-22 13:56:55 +04:00
ret = request_irq ( irq , spi_sh_irq , 0 , " spi_sh " , ss ) ;
2011-02-15 04:30:32 +03:00
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " request_irq error \n " ) ;
2016-07-10 17:25:19 +03:00
goto error1 ;
2011-02-15 04:30:32 +03:00
}
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 " ) ;
2014-07-06 20:14:50 +04:00
goto error3 ;
2011-02-15 04:30:32 +03:00
}
return 0 ;
error3 :
2014-07-06 20:14:50 +04:00
free_irq ( irq , ss ) ;
2011-02-15 04:30:32 +03:00
error1 :
spi_master_put ( master ) ;
return ret ;
}
static struct platform_driver spi_sh_driver = {
. probe = spi_sh_probe ,
2012-12-07 20:57:14 +04:00
. remove = spi_sh_remove ,
2011-02-15 04:30:32 +03:00
. driver = {
. name = " sh_spi " ,
} ,
} ;
2011-10-05 21:29:49 +04:00
module_platform_driver ( spi_sh_driver ) ;
2011-02-15 04:30:32 +03:00
MODULE_DESCRIPTION ( " SH SPI bus driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Yoshihiro Shimoda " ) ;
MODULE_ALIAS ( " platform:sh_spi " ) ;