2006-05-20 15:00:18 -07:00
/* linux/drivers/spi/spi_s3c24xx.c
*
* Copyright ( c ) 2006 Ben Dooks
* Copyright ( c ) 2006 Simtec Electronics
* Ben Dooks < ben @ simtec . co . uk >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*/
//#define DEBUG
# include <linux/config.h>
# include <linux/init.h>
# include <linux/spinlock.h>
# include <linux/workqueue.h>
# include <linux/interrupt.h>
# include <linux/delay.h>
# include <linux/errno.h>
# include <linux/err.h>
# include <linux/clk.h>
# include <linux/platform_device.h>
# include <linux/spi/spi.h>
# include <linux/spi/spi_bitbang.h>
# include <asm/io.h>
# include <asm/dma.h>
# include <asm/hardware.h>
# include <asm/arch/regs-gpio.h>
# include <asm/arch/regs-spi.h>
# include <asm/arch/spi.h>
struct s3c24xx_spi {
/* bitbang has to be first */
struct spi_bitbang bitbang ;
struct completion done ;
void __iomem * regs ;
int irq ;
int len ;
int count ;
/* data buffers */
const unsigned char * tx ;
unsigned char * rx ;
struct clk * clk ;
struct resource * ioarea ;
struct spi_master * master ;
struct spi_device * curdev ;
struct device * dev ;
struct s3c2410_spi_info * pdata ;
} ;
# define SPCON_DEFAULT (S3C2410_SPCON_MSTR | S3C2410_SPCON_SMOD_INT)
# define SPPIN_DEFAULT (S3C2410_SPPIN_KEEP)
static inline struct s3c24xx_spi * to_hw ( struct spi_device * sdev )
{
return spi_master_get_devdata ( sdev - > master ) ;
}
static void s3c24xx_spi_chipsel ( struct spi_device * spi , int value )
{
struct s3c24xx_spi * hw = to_hw ( spi ) ;
unsigned int cspol = spi - > mode & SPI_CS_HIGH ? 1 : 0 ;
unsigned int spcon ;
switch ( value ) {
case BITBANG_CS_INACTIVE :
if ( hw - > pdata - > set_cs )
hw - > pdata - > set_cs ( hw - > pdata , value , cspol ) ;
else
s3c2410_gpio_setpin ( hw - > pdata - > pin_cs , cspol ^ 1 ) ;
break ;
case BITBANG_CS_ACTIVE :
spcon = readb ( hw - > regs + S3C2410_SPCON ) ;
if ( spi - > mode & SPI_CPHA )
spcon | = S3C2410_SPCON_CPHA_FMTB ;
else
spcon & = ~ S3C2410_SPCON_CPHA_FMTB ;
if ( spi - > mode & SPI_CPOL )
spcon | = S3C2410_SPCON_CPOL_HIGH ;
else
spcon & = ~ S3C2410_SPCON_CPOL_HIGH ;
spcon | = S3C2410_SPCON_ENSCK ;
/* write new configration */
writeb ( spcon , hw - > regs + S3C2410_SPCON ) ;
if ( hw - > pdata - > set_cs )
hw - > pdata - > set_cs ( hw - > pdata , value , cspol ) ;
else
s3c2410_gpio_setpin ( hw - > pdata - > pin_cs , cspol ) ;
break ;
}
}
static int s3c24xx_spi_setupxfer ( struct spi_device * spi ,
struct spi_transfer * t )
{
struct s3c24xx_spi * hw = to_hw ( spi ) ;
unsigned int bpw ;
unsigned int hz ;
unsigned int div ;
bpw = t ? t - > bits_per_word : spi - > bits_per_word ;
hz = t ? t - > speed_hz : spi - > max_speed_hz ;
if ( bpw ! = 8 ) {
dev_err ( & spi - > dev , " invalid bits-per-word (%d) \n " , bpw ) ;
return - EINVAL ;
}
div = clk_get_rate ( hw - > clk ) / hz ;
/* is clk = pclk / (2 * (pre+1)), or is it
* clk = ( pclk * 2 ) / ( pre + 1 ) */
div = ( div / 2 ) - 1 ;
if ( div < 0 )
div = 1 ;
if ( div > 255 )
div = 255 ;
dev_dbg ( & spi - > dev , " setting pre-scaler to %d (hz %d) \n " , div , hz ) ;
writeb ( div , hw - > regs + S3C2410_SPPRE ) ;
spin_lock ( & hw - > bitbang . lock ) ;
if ( ! hw - > bitbang . busy ) {
hw - > bitbang . chipselect ( spi , BITBANG_CS_INACTIVE ) ;
/* need to ndelay for 0.5 clocktick ? */
}
spin_unlock ( & hw - > bitbang . lock ) ;
return 0 ;
}
static int s3c24xx_spi_setup ( struct spi_device * spi )
{
int ret ;
if ( ! spi - > bits_per_word )
spi - > bits_per_word = 8 ;
if ( ( spi - > mode & SPI_LSB_FIRST ) ! = 0 )
return - EINVAL ;
ret = s3c24xx_spi_setupxfer ( spi , NULL ) ;
if ( ret < 0 ) {
dev_err ( & spi - > dev , " setupxfer returned %d \n " , ret ) ;
return ret ;
}
dev_dbg ( & spi - > dev , " %s: mode %d, %u bpw, %d hz \n " ,
__FUNCTION__ , spi - > mode , spi - > bits_per_word ,
spi - > max_speed_hz ) ;
return 0 ;
}
static inline unsigned int hw_txbyte ( struct s3c24xx_spi * hw , int count )
{
return hw - > tx ? hw - > tx [ count ] : 0xff ;
}
static int s3c24xx_spi_txrx ( struct spi_device * spi , struct spi_transfer * t )
{
struct s3c24xx_spi * hw = to_hw ( spi ) ;
dev_dbg ( & spi - > dev , " txrx: tx %p, rx %p, len %d \n " ,
t - > tx_buf , t - > rx_buf , t - > len ) ;
hw - > tx = t - > tx_buf ;
hw - > rx = t - > rx_buf ;
hw - > len = t - > len ;
hw - > count = 0 ;
/* send the first byte */
writeb ( hw_txbyte ( hw , 0 ) , hw - > regs + S3C2410_SPTDAT ) ;
wait_for_completion ( & hw - > done ) ;
return hw - > count ;
}
static irqreturn_t s3c24xx_spi_irq ( int irq , void * dev , struct pt_regs * regs )
{
struct s3c24xx_spi * hw = dev ;
unsigned int spsta = readb ( hw - > regs + S3C2410_SPSTA ) ;
unsigned int count = hw - > count ;
if ( spsta & S3C2410_SPSTA_DCOL ) {
dev_dbg ( hw - > dev , " data-collision \n " ) ;
complete ( & hw - > done ) ;
goto irq_done ;
}
if ( ! ( spsta & S3C2410_SPSTA_READY ) ) {
dev_dbg ( hw - > dev , " spi not ready for tx? \n " ) ;
complete ( & hw - > done ) ;
goto irq_done ;
}
hw - > count + + ;
if ( hw - > rx )
hw - > rx [ count ] = readb ( hw - > regs + S3C2410_SPRDAT ) ;
count + + ;
if ( count < hw - > len )
writeb ( hw_txbyte ( hw , count ) , hw - > regs + S3C2410_SPTDAT ) ;
else
complete ( & hw - > done ) ;
irq_done :
return IRQ_HANDLED ;
}
static int s3c24xx_spi_probe ( struct platform_device * pdev )
{
struct s3c24xx_spi * hw ;
struct spi_master * master ;
struct spi_board_info * bi ;
struct resource * res ;
int err = 0 ;
int i ;
master = spi_alloc_master ( & pdev - > dev , sizeof ( struct s3c24xx_spi ) ) ;
if ( master = = NULL ) {
dev_err ( & pdev - > dev , " No memory for spi_master \n " ) ;
err = - ENOMEM ;
goto err_nomem ;
}
hw = spi_master_get_devdata ( master ) ;
memset ( hw , 0 , sizeof ( struct s3c24xx_spi ) ) ;
hw - > master = spi_master_get ( master ) ;
hw - > pdata = pdev - > dev . platform_data ;
hw - > dev = & pdev - > dev ;
if ( hw - > pdata = = NULL ) {
dev_err ( & pdev - > dev , " No platform data supplied \n " ) ;
err = - ENOENT ;
goto err_no_pdata ;
}
platform_set_drvdata ( pdev , hw ) ;
init_completion ( & hw - > done ) ;
/* setup the state for the bitbang driver */
hw - > bitbang . master = hw - > master ;
hw - > bitbang . setup_transfer = s3c24xx_spi_setupxfer ;
hw - > bitbang . chipselect = s3c24xx_spi_chipsel ;
hw - > bitbang . txrx_bufs = s3c24xx_spi_txrx ;
hw - > bitbang . master - > setup = s3c24xx_spi_setup ;
dev_dbg ( hw - > dev , " bitbang at %p \n " , & hw - > bitbang ) ;
/* find and map our resources */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( res = = NULL ) {
dev_err ( & pdev - > dev , " Cannot get IORESOURCE_MEM \n " ) ;
err = - ENOENT ;
goto err_no_iores ;
}
hw - > ioarea = request_mem_region ( res - > start , ( res - > end - res - > start ) + 1 ,
pdev - > name ) ;
if ( hw - > ioarea = = NULL ) {
dev_err ( & pdev - > dev , " Cannot reserve region \n " ) ;
err = - ENXIO ;
goto err_no_iores ;
}
hw - > regs = ioremap ( res - > start , ( res - > end - res - > start ) + 1 ) ;
if ( hw - > regs = = NULL ) {
dev_err ( & pdev - > dev , " Cannot map IO \n " ) ;
err = - ENXIO ;
goto err_no_iomap ;
}
hw - > irq = platform_get_irq ( pdev , 0 ) ;
if ( hw - > irq < 0 ) {
dev_err ( & pdev - > dev , " No IRQ specified \n " ) ;
err = - ENOENT ;
goto err_no_irq ;
}
err = request_irq ( hw - > irq , s3c24xx_spi_irq , 0 , pdev - > name , hw ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Cannot claim IRQ \n " ) ;
goto err_no_irq ;
}
hw - > clk = clk_get ( & pdev - > dev , " spi " ) ;
if ( IS_ERR ( hw - > clk ) ) {
dev_err ( & pdev - > dev , " No clock for device \n " ) ;
err = PTR_ERR ( hw - > clk ) ;
goto err_no_clk ;
}
/* for the moment, permanently enable the clock */
clk_enable ( hw - > clk ) ;
/* program defaults into the registers */
writeb ( 0xff , hw - > regs + S3C2410_SPPRE ) ;
writeb ( SPPIN_DEFAULT , hw - > regs + S3C2410_SPPIN ) ;
writeb ( SPCON_DEFAULT , hw - > regs + S3C2410_SPCON ) ;
/* setup any gpio we can */
if ( ! hw - > pdata - > set_cs ) {
s3c2410_gpio_setpin ( hw - > pdata - > pin_cs , 1 ) ;
s3c2410_gpio_cfgpin ( hw - > pdata - > pin_cs , S3C2410_GPIO_OUTPUT ) ;
}
/* register our spi controller */
err = spi_bitbang_start ( & hw - > bitbang ) ;
if ( err ) {
dev_err ( & pdev - > dev , " Failed to register SPI master \n " ) ;
goto err_register ;
}
dev_dbg ( hw - > dev , " shutdown=%d \n " , hw - > bitbang . shutdown ) ;
/* register all the devices associated */
bi = & hw - > pdata - > board_info [ 0 ] ;
for ( i = 0 ; i < hw - > pdata - > board_size ; i + + , bi + + ) {
dev_info ( hw - > dev , " registering %s \n " , bi - > modalias ) ;
bi - > controller_data = hw ;
spi_new_device ( master , bi ) ;
}
return 0 ;
err_register :
clk_disable ( hw - > clk ) ;
clk_put ( hw - > clk ) ;
err_no_clk :
free_irq ( hw - > irq , hw ) ;
err_no_irq :
iounmap ( hw - > regs ) ;
err_no_iomap :
release_resource ( hw - > ioarea ) ;
kfree ( hw - > ioarea ) ;
err_no_iores :
err_no_pdata :
spi_master_put ( hw - > master ) ; ;
err_nomem :
return err ;
}
static int s3c24xx_spi_remove ( struct platform_device * dev )
{
struct s3c24xx_spi * hw = platform_get_drvdata ( dev ) ;
platform_set_drvdata ( dev , NULL ) ;
spi_unregister_master ( hw - > master ) ;
clk_disable ( hw - > clk ) ;
clk_put ( hw - > clk ) ;
free_irq ( hw - > irq , hw ) ;
iounmap ( hw - > regs ) ;
release_resource ( hw - > ioarea ) ;
kfree ( hw - > ioarea ) ;
spi_master_put ( hw - > master ) ;
return 0 ;
}
# ifdef CONFIG_PM
static int s3c24xx_spi_suspend ( struct platform_device * pdev , pm_message_t msg )
{
2006-05-25 18:44:25 -07:00
struct s3c24xx_spi * hw = platform_get_drvdata ( pdev ) ;
2006-05-20 15:00:18 -07:00
clk_disable ( hw - > clk ) ;
return 0 ;
}
static int s3c24xx_spi_resume ( struct platform_device * pdev )
{
2006-05-25 18:44:25 -07:00
struct s3c24xx_spi * hw = platform_get_drvdata ( pdev ) ;
2006-05-20 15:00:18 -07:00
clk_enable ( hw - > clk ) ;
return 0 ;
}
# else
# define s3c24xx_spi_suspend NULL
# define s3c24xx_spi_resume NULL
# endif
static struct platform_driver s3c24xx_spidrv = {
. probe = s3c24xx_spi_probe ,
. remove = s3c24xx_spi_remove ,
. suspend = s3c24xx_spi_suspend ,
. resume = s3c24xx_spi_resume ,
. driver = {
. name = " s3c2410-spi " ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init s3c24xx_spi_init ( void )
{
return platform_driver_register ( & s3c24xx_spidrv ) ;
}
static void __exit s3c24xx_spi_exit ( void )
{
platform_driver_unregister ( & s3c24xx_spidrv ) ;
}
module_init ( s3c24xx_spi_init ) ;
module_exit ( s3c24xx_spi_exit ) ;
MODULE_DESCRIPTION ( " S3C24XX SPI Driver " ) ;
MODULE_AUTHOR ( " Ben Dooks, <ben@simtec.co.uk> " ) ;
MODULE_LICENSE ( " GPL " ) ;