2011-06-06 11:16:30 +04:00
/*
2009-11-30 10:39:42 +03:00
* Copyright ( C ) 2009 Samsung Electronics Ltd .
* Jaswinder Singh < jassi . brar @ samsung . com >
*
* 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 ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/workqueue.h>
2011-11-10 14:57:32 +04:00
# include <linux/interrupt.h>
2009-11-30 10:39:42 +03:00
# include <linux/delay.h>
# include <linux/clk.h>
# include <linux/dma-mapping.h>
# include <linux/platform_device.h>
2011-12-04 04:58:06 +04:00
# include <linux/pm_runtime.h>
2009-11-30 10:39:42 +03:00
# include <linux/spi/spi.h>
2012-07-13 02:15:14 +04:00
# include <linux/gpio.h>
2012-07-13 02:15:15 +04:00
# include <linux/of.h>
# include <linux/of_gpio.h>
2009-11-30 10:39:42 +03:00
# include <mach/dma.h>
2010-01-20 23:49:45 +03:00
# include <plat/s3c64xx-spi.h>
2009-11-30 10:39:42 +03:00
2012-07-13 02:15:14 +04:00
# define MAX_SPI_PORTS 3
2009-11-30 10:39:42 +03:00
/* Registers and bit-fields */
# define S3C64XX_SPI_CH_CFG 0x00
# define S3C64XX_SPI_CLK_CFG 0x04
# define S3C64XX_SPI_MODE_CFG 0x08
# define S3C64XX_SPI_SLAVE_SEL 0x0C
# define S3C64XX_SPI_INT_EN 0x10
# define S3C64XX_SPI_STATUS 0x14
# define S3C64XX_SPI_TX_DATA 0x18
# define S3C64XX_SPI_RX_DATA 0x1C
# define S3C64XX_SPI_PACKET_CNT 0x20
# define S3C64XX_SPI_PENDING_CLR 0x24
# define S3C64XX_SPI_SWAP_CFG 0x28
# define S3C64XX_SPI_FB_CLK 0x2C
# define S3C64XX_SPI_CH_HS_EN (1<<6) /* High Speed Enable */
# define S3C64XX_SPI_CH_SW_RST (1<<5)
# define S3C64XX_SPI_CH_SLAVE (1<<4)
# define S3C64XX_SPI_CPOL_L (1<<3)
# define S3C64XX_SPI_CPHA_B (1<<2)
# define S3C64XX_SPI_CH_RXCH_ON (1<<1)
# define S3C64XX_SPI_CH_TXCH_ON (1<<0)
# define S3C64XX_SPI_CLKSEL_SRCMSK (3<<9)
# define S3C64XX_SPI_CLKSEL_SRCSHFT 9
# define S3C64XX_SPI_ENCLK_ENABLE (1<<8)
# define S3C64XX_SPI_PSR_MASK 0xff
# define S3C64XX_SPI_MODE_CH_TSZ_BYTE (0<<29)
# define S3C64XX_SPI_MODE_CH_TSZ_HALFWORD (1<<29)
# define S3C64XX_SPI_MODE_CH_TSZ_WORD (2<<29)
# define S3C64XX_SPI_MODE_CH_TSZ_MASK (3<<29)
# define S3C64XX_SPI_MODE_BUS_TSZ_BYTE (0<<17)
# define S3C64XX_SPI_MODE_BUS_TSZ_HALFWORD (1<<17)
# define S3C64XX_SPI_MODE_BUS_TSZ_WORD (2<<17)
# define S3C64XX_SPI_MODE_BUS_TSZ_MASK (3<<17)
# define S3C64XX_SPI_MODE_RXDMA_ON (1<<2)
# define S3C64XX_SPI_MODE_TXDMA_ON (1<<1)
# define S3C64XX_SPI_MODE_4BURST (1<<0)
# define S3C64XX_SPI_SLAVE_AUTO (1<<1)
# define S3C64XX_SPI_SLAVE_SIG_INACT (1<<0)
# define S3C64XX_SPI_ACT(c) writel(0, (c)->regs + S3C64XX_SPI_SLAVE_SEL)
# define S3C64XX_SPI_DEACT(c) writel(S3C64XX_SPI_SLAVE_SIG_INACT, \
( c ) - > regs + S3C64XX_SPI_SLAVE_SEL )
# define S3C64XX_SPI_INT_TRAILING_EN (1<<6)
# define S3C64XX_SPI_INT_RX_OVERRUN_EN (1<<5)
# define S3C64XX_SPI_INT_RX_UNDERRUN_EN (1<<4)
# define S3C64XX_SPI_INT_TX_OVERRUN_EN (1<<3)
# define S3C64XX_SPI_INT_TX_UNDERRUN_EN (1<<2)
# define S3C64XX_SPI_INT_RX_FIFORDY_EN (1<<1)
# define S3C64XX_SPI_INT_TX_FIFORDY_EN (1<<0)
# define S3C64XX_SPI_ST_RX_OVERRUN_ERR (1<<5)
# define S3C64XX_SPI_ST_RX_UNDERRUN_ERR (1<<4)
# define S3C64XX_SPI_ST_TX_OVERRUN_ERR (1<<3)
# define S3C64XX_SPI_ST_TX_UNDERRUN_ERR (1<<2)
# define S3C64XX_SPI_ST_RX_FIFORDY (1<<1)
# define S3C64XX_SPI_ST_TX_FIFORDY (1<<0)
# define S3C64XX_SPI_PACKET_CNT_EN (1<<16)
# define S3C64XX_SPI_PND_TX_UNDERRUN_CLR (1<<4)
# define S3C64XX_SPI_PND_TX_OVERRUN_CLR (1<<3)
# define S3C64XX_SPI_PND_RX_UNDERRUN_CLR (1<<2)
# define S3C64XX_SPI_PND_RX_OVERRUN_CLR (1<<1)
# define S3C64XX_SPI_PND_TRAILING_CLR (1<<0)
# define S3C64XX_SPI_SWAP_RX_HALF_WORD (1<<7)
# define S3C64XX_SPI_SWAP_RX_BYTE (1<<6)
# define S3C64XX_SPI_SWAP_RX_BIT (1<<5)
# define S3C64XX_SPI_SWAP_RX_EN (1<<4)
# define S3C64XX_SPI_SWAP_TX_HALF_WORD (1<<3)
# define S3C64XX_SPI_SWAP_TX_BYTE (1<<2)
# define S3C64XX_SPI_SWAP_TX_BIT (1<<1)
# define S3C64XX_SPI_SWAP_TX_EN (1<<0)
# define S3C64XX_SPI_FBCLK_MSK (3<<0)
2012-07-13 02:15:14 +04:00
# define FIFO_LVL_MASK(i) ((i)->port_conf->fifo_lvl_mask[i->port_id])
# define S3C64XX_SPI_ST_TX_DONE(v, i) (((v) & \
( 1 < < ( i ) - > port_conf - > tx_st_done ) ) ? 1 : 0 )
# define TX_FIFO_LVL(v, i) (((v) >> 6) & FIFO_LVL_MASK(i))
# define RX_FIFO_LVL(v, i) (((v) >> (i)->port_conf->rx_lvl_offset) & \
FIFO_LVL_MASK ( i ) )
2009-11-30 10:39:42 +03:00
# define S3C64XX_SPI_MAX_TRAILCNT 0x3ff
# define S3C64XX_SPI_TRAILCNT_OFF 19
# define S3C64XX_SPI_TRAILCNT S3C64XX_SPI_MAX_TRAILCNT
# define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
# define RXBUSY (1<<2)
# define TXBUSY (1<<3)
2011-09-02 04:44:42 +04:00
struct s3c64xx_spi_dma_data {
unsigned ch ;
enum dma_data_direction direction ;
enum dma_ch dmach ;
2012-07-13 02:15:15 +04:00
struct property * dma_prop ;
2011-09-02 04:44:42 +04:00
} ;
2012-07-13 02:15:14 +04:00
/**
* struct s3c64xx_spi_info - SPI Controller hardware info
* @ fifo_lvl_mask : Bit - mask for { TX | RX } _FIFO_LVL bits in SPI_STATUS register .
* @ rx_lvl_offset : Bit offset of RX_FIFO_LVL bits in SPI_STATUS regiter .
* @ tx_st_done : Bit offset of TX_DONE bit in SPI_STATUS regiter .
* @ high_speed : True , if the controller supports HIGH_SPEED_EN bit .
* @ clk_from_cmu : True , if the controller does not include a clock mux and
* prescaler unit .
*
* The Samsung s3c64xx SPI controller are used on various Samsung SoC ' s but
* differ in some aspects such as the size of the fifo and spi bus clock
* setup . Such differences are specified to the driver using this structure
* which is provided as driver data to the driver .
*/
struct s3c64xx_spi_port_config {
int fifo_lvl_mask [ MAX_SPI_PORTS ] ;
int rx_lvl_offset ;
int tx_st_done ;
bool high_speed ;
bool clk_from_cmu ;
} ;
2009-11-30 10:39:42 +03:00
/**
* struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver .
* @ clk : Pointer to the spi clock .
2010-01-20 23:49:44 +03:00
* @ src_clk : Pointer to the clock used to generate SPI signals .
2009-11-30 10:39:42 +03:00
* @ master : Pointer to the SPI Protocol master .
* @ cntrlr_info : Platform specific data for the controller this driver manages .
* @ tgl_spi : Pointer to the last CS left untoggled by the cs_change hint .
* @ queue : To log SPI xfer requests .
* @ lock : Controller specific lock .
* @ state : Set of FLAGS to indicate status .
* @ rx_dmach : Controller ' s DMA channel for Rx .
* @ tx_dmach : Controller ' s DMA channel for Tx .
* @ sfr_start : BUS address of SPI controller regs .
* @ regs : Pointer to ioremap ' ed controller registers .
2011-11-10 14:57:32 +04:00
* @ irq : interrupt
2009-11-30 10:39:42 +03:00
* @ xfer_completion : To indicate completion of xfer task .
* @ cur_mode : Stores the active configuration of the controller .
* @ cur_bpw : Stores the active bits per word settings .
* @ cur_speed : Stores the active xfer clock speed .
*/
struct s3c64xx_spi_driver_data {
void __iomem * regs ;
struct clk * clk ;
2010-01-20 23:49:44 +03:00
struct clk * src_clk ;
2009-11-30 10:39:42 +03:00
struct platform_device * pdev ;
struct spi_master * master ;
2010-01-20 23:49:44 +03:00
struct s3c64xx_spi_info * cntrlr_info ;
2009-11-30 10:39:42 +03:00
struct spi_device * tgl_spi ;
struct list_head queue ;
spinlock_t lock ;
unsigned long sfr_start ;
struct completion xfer_completion ;
unsigned state ;
unsigned cur_mode , cur_bpw ;
unsigned cur_speed ;
2011-09-02 04:44:42 +04:00
struct s3c64xx_spi_dma_data rx_dma ;
struct s3c64xx_spi_dma_data tx_dma ;
2011-09-02 04:44:41 +04:00
struct samsung_dma_ops * ops ;
2012-07-13 02:15:14 +04:00
struct s3c64xx_spi_port_config * port_conf ;
unsigned int port_id ;
2012-07-13 02:15:15 +04:00
unsigned long gpios [ 4 ] ;
2009-11-30 10:39:42 +03:00
} ;
static struct s3c2410_dma_client s3c64xx_spi_dma_client = {
. name = " samsung-spi-dma " ,
} ;
static void flush_fifo ( struct s3c64xx_spi_driver_data * sdd )
{
void __iomem * regs = sdd - > regs ;
unsigned long loops ;
u32 val ;
writel ( 0 , regs + S3C64XX_SPI_PACKET_CNT ) ;
val = readl ( regs + S3C64XX_SPI_CH_CFG ) ;
val | = S3C64XX_SPI_CH_SW_RST ;
val & = ~ S3C64XX_SPI_CH_HS_EN ;
writel ( val , regs + S3C64XX_SPI_CH_CFG ) ;
/* Flush TxFIFO*/
loops = msecs_to_loops ( 1 ) ;
do {
val = readl ( regs + S3C64XX_SPI_STATUS ) ;
2012-07-13 02:15:14 +04:00
} while ( TX_FIFO_LVL ( val , sdd ) & & loops - - ) ;
2009-11-30 10:39:42 +03:00
2010-08-23 20:40:56 +04:00
if ( loops = = 0 )
dev_warn ( & sdd - > pdev - > dev , " Timed out flushing TX FIFO \n " ) ;
2009-11-30 10:39:42 +03:00
/* Flush RxFIFO*/
loops = msecs_to_loops ( 1 ) ;
do {
val = readl ( regs + S3C64XX_SPI_STATUS ) ;
2012-07-13 02:15:14 +04:00
if ( RX_FIFO_LVL ( val , sdd ) )
2009-11-30 10:39:42 +03:00
readl ( regs + S3C64XX_SPI_RX_DATA ) ;
else
break ;
} while ( loops - - ) ;
2010-08-23 20:40:56 +04:00
if ( loops = = 0 )
dev_warn ( & sdd - > pdev - > dev , " Timed out flushing RX FIFO \n " ) ;
2009-11-30 10:39:42 +03:00
val = readl ( regs + S3C64XX_SPI_CH_CFG ) ;
val & = ~ S3C64XX_SPI_CH_SW_RST ;
writel ( val , regs + S3C64XX_SPI_CH_CFG ) ;
val = readl ( regs + S3C64XX_SPI_MODE_CFG ) ;
val & = ~ ( S3C64XX_SPI_MODE_TXDMA_ON | S3C64XX_SPI_MODE_RXDMA_ON ) ;
writel ( val , regs + S3C64XX_SPI_MODE_CFG ) ;
val = readl ( regs + S3C64XX_SPI_CH_CFG ) ;
val & = ~ ( S3C64XX_SPI_CH_RXCH_ON | S3C64XX_SPI_CH_TXCH_ON ) ;
writel ( val , regs + S3C64XX_SPI_CH_CFG ) ;
}
2011-09-02 04:44:42 +04:00
static void s3c64xx_spi_dmacb ( void * data )
2011-09-02 04:44:41 +04:00
{
2011-09-02 04:44:42 +04:00
struct s3c64xx_spi_driver_data * sdd ;
struct s3c64xx_spi_dma_data * dma = data ;
2011-09-02 04:44:41 +04:00
unsigned long flags ;
2012-03-10 04:48:46 +04:00
if ( dma - > direction = = DMA_DEV_TO_MEM )
2011-09-02 04:44:42 +04:00
sdd = container_of ( data ,
struct s3c64xx_spi_driver_data , rx_dma ) ;
else
sdd = container_of ( data ,
struct s3c64xx_spi_driver_data , tx_dma ) ;
2011-09-02 04:44:41 +04:00
spin_lock_irqsave ( & sdd - > lock , flags ) ;
2012-03-10 04:48:46 +04:00
if ( dma - > direction = = DMA_DEV_TO_MEM ) {
2011-09-02 04:44:42 +04:00
sdd - > state & = ~ RXBUSY ;
if ( ! ( sdd - > state & TXBUSY ) )
complete ( & sdd - > xfer_completion ) ;
} else {
sdd - > state & = ~ TXBUSY ;
if ( ! ( sdd - > state & RXBUSY ) )
complete ( & sdd - > xfer_completion ) ;
}
2011-09-02 04:44:41 +04:00
spin_unlock_irqrestore ( & sdd - > lock , flags ) ;
}
2011-09-02 04:44:42 +04:00
static void prepare_dma ( struct s3c64xx_spi_dma_data * dma ,
unsigned len , dma_addr_t buf )
2011-09-02 04:44:41 +04:00
{
2011-09-02 04:44:42 +04:00
struct s3c64xx_spi_driver_data * sdd ;
2012-06-19 08:27:03 +04:00
struct samsung_dma_prep info ;
struct samsung_dma_config config ;
2011-09-02 04:44:41 +04:00
2012-06-19 08:27:03 +04:00
if ( dma - > direction = = DMA_DEV_TO_MEM ) {
2011-09-02 04:44:42 +04:00
sdd = container_of ( ( void * ) dma ,
struct s3c64xx_spi_driver_data , rx_dma ) ;
2012-06-19 08:27:03 +04:00
config . direction = sdd - > rx_dma . direction ;
config . fifo = sdd - > sfr_start + S3C64XX_SPI_RX_DATA ;
config . width = sdd - > cur_bpw / 8 ;
sdd - > ops - > config ( sdd - > rx_dma . ch , & config ) ;
} else {
2011-09-02 04:44:42 +04:00
sdd = container_of ( ( void * ) dma ,
struct s3c64xx_spi_driver_data , tx_dma ) ;
2012-06-19 08:27:03 +04:00
config . direction = sdd - > tx_dma . direction ;
config . fifo = sdd - > sfr_start + S3C64XX_SPI_TX_DATA ;
config . width = sdd - > cur_bpw / 8 ;
sdd - > ops - > config ( sdd - > tx_dma . ch , & config ) ;
}
2011-09-02 04:44:41 +04:00
2011-09-02 04:44:42 +04:00
info . cap = DMA_SLAVE ;
info . len = len ;
info . fp = s3c64xx_spi_dmacb ;
info . fp_param = dma ;
info . direction = dma - > direction ;
info . buf = buf ;
sdd - > ops - > prepare ( dma - > ch , & info ) ;
sdd - > ops - > trigger ( dma - > ch ) ;
}
2011-09-02 04:44:41 +04:00
2011-09-02 04:44:42 +04:00
static int acquire_dma ( struct s3c64xx_spi_driver_data * sdd )
{
2012-06-19 08:27:03 +04:00
struct samsung_dma_req req ;
2011-09-02 04:44:42 +04:00
sdd - > ops = samsung_dma_get_ops ( ) ;
2012-06-19 08:27:03 +04:00
req . cap = DMA_SLAVE ;
req . client = & s3c64xx_spi_dma_client ;
2012-07-13 02:15:15 +04:00
req . dt_dmach_prop = sdd - > rx_dma . dma_prop ;
2012-06-19 08:27:03 +04:00
sdd - > rx_dma . ch = sdd - > ops - > request ( sdd - > rx_dma . dmach , & req ) ;
2012-07-13 02:15:15 +04:00
req . dt_dmach_prop = sdd - > tx_dma . dma_prop ;
2012-06-19 08:27:03 +04:00
sdd - > tx_dma . ch = sdd - > ops - > request ( sdd - > tx_dma . dmach , & req ) ;
2011-09-02 04:44:42 +04:00
return 1 ;
2011-09-02 04:44:41 +04:00
}
2009-11-30 10:39:42 +03:00
static void enable_datapath ( struct s3c64xx_spi_driver_data * sdd ,
struct spi_device * spi ,
struct spi_transfer * xfer , int dma_mode )
{
void __iomem * regs = sdd - > regs ;
u32 modecfg , chcfg ;
modecfg = readl ( regs + S3C64XX_SPI_MODE_CFG ) ;
modecfg & = ~ ( S3C64XX_SPI_MODE_TXDMA_ON | S3C64XX_SPI_MODE_RXDMA_ON ) ;
chcfg = readl ( regs + S3C64XX_SPI_CH_CFG ) ;
chcfg & = ~ S3C64XX_SPI_CH_TXCH_ON ;
if ( dma_mode ) {
chcfg & = ~ S3C64XX_SPI_CH_RXCH_ON ;
} else {
/* Always shift in data in FIFO, even if xfer is Tx only,
* this helps setting PCKT_CNT value for generating clocks
* as exactly needed .
*/
chcfg | = S3C64XX_SPI_CH_RXCH_ON ;
writel ( ( ( xfer - > len * 8 / sdd - > cur_bpw ) & 0xffff )
| S3C64XX_SPI_PACKET_CNT_EN ,
regs + S3C64XX_SPI_PACKET_CNT ) ;
}
if ( xfer - > tx_buf ! = NULL ) {
sdd - > state | = TXBUSY ;
chcfg | = S3C64XX_SPI_CH_TXCH_ON ;
if ( dma_mode ) {
modecfg | = S3C64XX_SPI_MODE_TXDMA_ON ;
2011-09-02 04:44:42 +04:00
prepare_dma ( & sdd - > tx_dma , xfer - > len , xfer - > tx_dma ) ;
2009-11-30 10:39:42 +03:00
} else {
2010-09-29 12:31:33 +04:00
switch ( sdd - > cur_bpw ) {
case 32 :
iowrite32_rep ( regs + S3C64XX_SPI_TX_DATA ,
xfer - > tx_buf , xfer - > len / 4 ) ;
break ;
case 16 :
iowrite16_rep ( regs + S3C64XX_SPI_TX_DATA ,
xfer - > tx_buf , xfer - > len / 2 ) ;
break ;
default :
iowrite8_rep ( regs + S3C64XX_SPI_TX_DATA ,
xfer - > tx_buf , xfer - > len ) ;
break ;
}
2009-11-30 10:39:42 +03:00
}
}
if ( xfer - > rx_buf ! = NULL ) {
sdd - > state | = RXBUSY ;
2012-07-13 02:15:14 +04:00
if ( sdd - > port_conf - > high_speed & & sdd - > cur_speed > = 30000000UL
2009-11-30 10:39:42 +03:00
& & ! ( sdd - > cur_mode & SPI_CPHA ) )
chcfg | = S3C64XX_SPI_CH_HS_EN ;
if ( dma_mode ) {
modecfg | = S3C64XX_SPI_MODE_RXDMA_ON ;
chcfg | = S3C64XX_SPI_CH_RXCH_ON ;
writel ( ( ( xfer - > len * 8 / sdd - > cur_bpw ) & 0xffff )
| S3C64XX_SPI_PACKET_CNT_EN ,
regs + S3C64XX_SPI_PACKET_CNT ) ;
2011-09-02 04:44:42 +04:00
prepare_dma ( & sdd - > rx_dma , xfer - > len , xfer - > rx_dma ) ;
2009-11-30 10:39:42 +03:00
}
}
writel ( modecfg , regs + S3C64XX_SPI_MODE_CFG ) ;
writel ( chcfg , regs + S3C64XX_SPI_CH_CFG ) ;
}
static inline void enable_cs ( struct s3c64xx_spi_driver_data * sdd ,
struct spi_device * spi )
{
struct s3c64xx_spi_csinfo * cs ;
if ( sdd - > tgl_spi ! = NULL ) { /* If last device toggled after mssg */
if ( sdd - > tgl_spi ! = spi ) { /* if last mssg on diff device */
/* Deselect the last toggled device */
cs = sdd - > tgl_spi - > controller_data ;
2012-07-13 02:15:14 +04:00
gpio_set_value ( cs - > line ,
spi - > mode & SPI_CS_HIGH ? 0 : 1 ) ;
2009-11-30 10:39:42 +03:00
}
sdd - > tgl_spi = NULL ;
}
cs = spi - > controller_data ;
2012-07-13 02:15:14 +04:00
gpio_set_value ( cs - > line , spi - > mode & SPI_CS_HIGH ? 1 : 0 ) ;
2009-11-30 10:39:42 +03:00
}
static int wait_for_xfer ( struct s3c64xx_spi_driver_data * sdd ,
struct spi_transfer * xfer , int dma_mode )
{
void __iomem * regs = sdd - > regs ;
unsigned long val ;
int ms ;
/* millisecs to xfer 'len' bytes @ 'cur_speed' */
ms = xfer - > len * 8 * 1000 / sdd - > cur_speed ;
2010-09-07 19:37:52 +04:00
ms + = 10 ; /* some tolerance */
2009-11-30 10:39:42 +03:00
if ( dma_mode ) {
val = msecs_to_jiffies ( ms ) + 10 ;
val = wait_for_completion_timeout ( & sdd - > xfer_completion , val ) ;
} else {
2010-09-03 05:36:46 +04:00
u32 status ;
2009-11-30 10:39:42 +03:00
val = msecs_to_loops ( ms ) ;
do {
2010-09-03 05:36:46 +04:00
status = readl ( regs + S3C64XX_SPI_STATUS ) ;
2012-07-13 02:15:14 +04:00
} while ( RX_FIFO_LVL ( status , sdd ) < xfer - > len & & - - val ) ;
2009-11-30 10:39:42 +03:00
}
if ( ! val )
return - EIO ;
if ( dma_mode ) {
u32 status ;
/*
* DmaTx returns after simply writing data in the FIFO ,
* w / o waiting for real transmission on the bus to finish .
* DmaRx returns only after Dma read data from FIFO which
* needs bus transmission to finish , so we don ' t worry if
* Xfer involved Rx ( with or without Tx ) .
*/
if ( xfer - > rx_buf = = NULL ) {
val = msecs_to_loops ( 10 ) ;
status = readl ( regs + S3C64XX_SPI_STATUS ) ;
2012-07-13 02:15:14 +04:00
while ( ( TX_FIFO_LVL ( status , sdd )
| | ! S3C64XX_SPI_ST_TX_DONE ( status , sdd ) )
2009-11-30 10:39:42 +03:00
& & - - val ) {
cpu_relax ( ) ;
status = readl ( regs + S3C64XX_SPI_STATUS ) ;
}
if ( ! val )
return - EIO ;
}
} else {
/* If it was only Tx */
if ( xfer - > rx_buf = = NULL ) {
sdd - > state & = ~ TXBUSY ;
return 0 ;
}
2010-09-29 12:31:33 +04:00
switch ( sdd - > cur_bpw ) {
case 32 :
ioread32_rep ( regs + S3C64XX_SPI_RX_DATA ,
xfer - > rx_buf , xfer - > len / 4 ) ;
break ;
case 16 :
ioread16_rep ( regs + S3C64XX_SPI_RX_DATA ,
xfer - > rx_buf , xfer - > len / 2 ) ;
break ;
default :
ioread8_rep ( regs + S3C64XX_SPI_RX_DATA ,
xfer - > rx_buf , xfer - > len ) ;
break ;
}
2009-11-30 10:39:42 +03:00
sdd - > state & = ~ RXBUSY ;
}
return 0 ;
}
static inline void disable_cs ( struct s3c64xx_spi_driver_data * sdd ,
struct spi_device * spi )
{
struct s3c64xx_spi_csinfo * cs = spi - > controller_data ;
if ( sdd - > tgl_spi = = spi )
sdd - > tgl_spi = NULL ;
2012-07-13 02:15:14 +04:00
gpio_set_value ( cs - > line , spi - > mode & SPI_CS_HIGH ? 0 : 1 ) ;
2009-11-30 10:39:42 +03:00
}
static void s3c64xx_spi_config ( struct s3c64xx_spi_driver_data * sdd )
{
void __iomem * regs = sdd - > regs ;
u32 val ;
/* Disable Clock */
2012-07-13 02:15:14 +04:00
if ( sdd - > port_conf - > clk_from_cmu ) {
2010-09-29 12:31:33 +04:00
clk_disable ( sdd - > src_clk ) ;
} else {
val = readl ( regs + S3C64XX_SPI_CLK_CFG ) ;
val & = ~ S3C64XX_SPI_ENCLK_ENABLE ;
writel ( val , regs + S3C64XX_SPI_CLK_CFG ) ;
}
2009-11-30 10:39:42 +03:00
/* Set Polarity and Phase */
val = readl ( regs + S3C64XX_SPI_CH_CFG ) ;
val & = ~ ( S3C64XX_SPI_CH_SLAVE |
S3C64XX_SPI_CPOL_L |
S3C64XX_SPI_CPHA_B ) ;
if ( sdd - > cur_mode & SPI_CPOL )
val | = S3C64XX_SPI_CPOL_L ;
if ( sdd - > cur_mode & SPI_CPHA )
val | = S3C64XX_SPI_CPHA_B ;
writel ( val , regs + S3C64XX_SPI_CH_CFG ) ;
/* Set Channel & DMA Mode */
val = readl ( regs + S3C64XX_SPI_MODE_CFG ) ;
val & = ~ ( S3C64XX_SPI_MODE_BUS_TSZ_MASK
| S3C64XX_SPI_MODE_CH_TSZ_MASK ) ;
switch ( sdd - > cur_bpw ) {
case 32 :
val | = S3C64XX_SPI_MODE_BUS_TSZ_WORD ;
2010-09-29 12:31:33 +04:00
val | = S3C64XX_SPI_MODE_CH_TSZ_WORD ;
2009-11-30 10:39:42 +03:00
break ;
case 16 :
val | = S3C64XX_SPI_MODE_BUS_TSZ_HALFWORD ;
2010-09-29 12:31:33 +04:00
val | = S3C64XX_SPI_MODE_CH_TSZ_HALFWORD ;
2009-11-30 10:39:42 +03:00
break ;
default :
val | = S3C64XX_SPI_MODE_BUS_TSZ_BYTE ;
2010-09-29 12:31:33 +04:00
val | = S3C64XX_SPI_MODE_CH_TSZ_BYTE ;
2009-11-30 10:39:42 +03:00
break ;
}
writel ( val , regs + S3C64XX_SPI_MODE_CFG ) ;
2012-07-13 02:15:14 +04:00
if ( sdd - > port_conf - > clk_from_cmu ) {
2010-09-29 12:31:33 +04:00
/* Configure Clock */
/* There is half-multiplier before the SPI */
clk_set_rate ( sdd - > src_clk , sdd - > cur_speed * 2 ) ;
/* Enable Clock */
clk_enable ( sdd - > src_clk ) ;
} else {
/* Configure Clock */
val = readl ( regs + S3C64XX_SPI_CLK_CFG ) ;
val & = ~ S3C64XX_SPI_PSR_MASK ;
val | = ( ( clk_get_rate ( sdd - > src_clk ) / sdd - > cur_speed / 2 - 1 )
& S3C64XX_SPI_PSR_MASK ) ;
writel ( val , regs + S3C64XX_SPI_CLK_CFG ) ;
/* Enable Clock */
val = readl ( regs + S3C64XX_SPI_CLK_CFG ) ;
val | = S3C64XX_SPI_ENCLK_ENABLE ;
writel ( val , regs + S3C64XX_SPI_CLK_CFG ) ;
}
2009-11-30 10:39:42 +03:00
}
# define XFER_DMAADDR_INVALID DMA_BIT_MASK(32)
static int s3c64xx_spi_map_mssg ( struct s3c64xx_spi_driver_data * sdd ,
struct spi_message * msg )
{
struct device * dev = & sdd - > pdev - > dev ;
struct spi_transfer * xfer ;
if ( msg - > is_dma_mapped )
return 0 ;
/* First mark all xfer unmapped */
list_for_each_entry ( xfer , & msg - > transfers , transfer_list ) {
xfer - > rx_dma = XFER_DMAADDR_INVALID ;
xfer - > tx_dma = XFER_DMAADDR_INVALID ;
}
/* Map until end or first fail */
list_for_each_entry ( xfer , & msg - > transfers , transfer_list ) {
2012-07-13 02:15:14 +04:00
if ( xfer - > len < = ( ( FIFO_LVL_MASK ( sdd ) > > 1 ) + 1 ) )
2010-09-29 12:31:31 +04:00
continue ;
2009-11-30 10:39:42 +03:00
if ( xfer - > tx_buf ! = NULL ) {
2010-09-03 05:36:26 +04:00
xfer - > tx_dma = dma_map_single ( dev ,
( void * ) xfer - > tx_buf , xfer - > len ,
DMA_TO_DEVICE ) ;
2009-11-30 10:39:42 +03:00
if ( dma_mapping_error ( dev , xfer - > tx_dma ) ) {
dev_err ( dev , " dma_map_single Tx failed \n " ) ;
xfer - > tx_dma = XFER_DMAADDR_INVALID ;
return - ENOMEM ;
}
}
if ( xfer - > rx_buf ! = NULL ) {
xfer - > rx_dma = dma_map_single ( dev , xfer - > rx_buf ,
xfer - > len , DMA_FROM_DEVICE ) ;
if ( dma_mapping_error ( dev , xfer - > rx_dma ) ) {
dev_err ( dev , " dma_map_single Rx failed \n " ) ;
dma_unmap_single ( dev , xfer - > tx_dma ,
xfer - > len , DMA_TO_DEVICE ) ;
xfer - > tx_dma = XFER_DMAADDR_INVALID ;
xfer - > rx_dma = XFER_DMAADDR_INVALID ;
return - ENOMEM ;
}
}
}
return 0 ;
}
static void s3c64xx_spi_unmap_mssg ( struct s3c64xx_spi_driver_data * sdd ,
struct spi_message * msg )
{
struct device * dev = & sdd - > pdev - > dev ;
struct spi_transfer * xfer ;
if ( msg - > is_dma_mapped )
return ;
list_for_each_entry ( xfer , & msg - > transfers , transfer_list ) {
2012-07-13 02:15:14 +04:00
if ( xfer - > len < = ( ( FIFO_LVL_MASK ( sdd ) > > 1 ) + 1 ) )
2010-09-29 12:31:31 +04:00
continue ;
2009-11-30 10:39:42 +03:00
if ( xfer - > rx_buf ! = NULL
& & xfer - > rx_dma ! = XFER_DMAADDR_INVALID )
dma_unmap_single ( dev , xfer - > rx_dma ,
xfer - > len , DMA_FROM_DEVICE ) ;
if ( xfer - > tx_buf ! = NULL
& & xfer - > tx_dma ! = XFER_DMAADDR_INVALID )
dma_unmap_single ( dev , xfer - > tx_dma ,
xfer - > len , DMA_TO_DEVICE ) ;
}
}
2012-02-16 02:48:32 +04:00
static int s3c64xx_spi_transfer_one_message ( struct spi_master * master ,
struct spi_message * msg )
2009-11-30 10:39:42 +03:00
{
2012-02-16 02:48:32 +04:00
struct s3c64xx_spi_driver_data * sdd = spi_master_get_devdata ( master ) ;
2009-11-30 10:39:42 +03:00
struct spi_device * spi = msg - > spi ;
struct s3c64xx_spi_csinfo * cs = spi - > controller_data ;
struct spi_transfer * xfer ;
int status = 0 , cs_toggle = 0 ;
u32 speed ;
u8 bpw ;
/* If Master's(controller) state differs from that needed by Slave */
if ( sdd - > cur_speed ! = spi - > max_speed_hz
| | sdd - > cur_mode ! = spi - > mode
| | sdd - > cur_bpw ! = spi - > bits_per_word ) {
sdd - > cur_bpw = spi - > bits_per_word ;
sdd - > cur_speed = spi - > max_speed_hz ;
sdd - > cur_mode = spi - > mode ;
s3c64xx_spi_config ( sdd ) ;
}
/* Map all the transfers if needed */
if ( s3c64xx_spi_map_mssg ( sdd , msg ) ) {
dev_err ( & spi - > dev ,
" Xfer: Unable to map message buffers! \n " ) ;
status = - ENOMEM ;
goto out ;
}
/* Configure feedback delay */
writel ( cs - > fb_delay & 0x3 , sdd - > regs + S3C64XX_SPI_FB_CLK ) ;
list_for_each_entry ( xfer , & msg - > transfers , transfer_list ) {
unsigned long flags ;
int use_dma ;
INIT_COMPLETION ( sdd - > xfer_completion ) ;
/* Only BPW and Speed may change across transfers */
bpw = xfer - > bits_per_word ? : spi - > bits_per_word ;
speed = xfer - > speed_hz ? : spi - > max_speed_hz ;
2010-09-29 12:31:33 +04:00
if ( xfer - > len % ( bpw / 8 ) ) {
dev_err ( & spi - > dev ,
" Xfer length(%u) not a multiple of word size(%u) \n " ,
xfer - > len , bpw / 8 ) ;
status = - EIO ;
goto out ;
}
2009-11-30 10:39:42 +03:00
if ( bpw ! = sdd - > cur_bpw | | speed ! = sdd - > cur_speed ) {
sdd - > cur_bpw = bpw ;
sdd - > cur_speed = speed ;
s3c64xx_spi_config ( sdd ) ;
}
/* Polling method for xfers not bigger than FIFO capacity */
2012-07-13 02:15:14 +04:00
if ( xfer - > len < = ( ( FIFO_LVL_MASK ( sdd ) > > 1 ) + 1 ) )
2009-11-30 10:39:42 +03:00
use_dma = 0 ;
else
use_dma = 1 ;
spin_lock_irqsave ( & sdd - > lock , flags ) ;
/* Pending only which is to be done */
sdd - > state & = ~ RXBUSY ;
sdd - > state & = ~ TXBUSY ;
enable_datapath ( sdd , spi , xfer , use_dma ) ;
/* Slave Select */
enable_cs ( sdd , spi ) ;
/* Start the signals */
S3C64XX_SPI_ACT ( sdd ) ;
spin_unlock_irqrestore ( & sdd - > lock , flags ) ;
status = wait_for_xfer ( sdd , xfer , use_dma ) ;
/* Quiese the signals */
S3C64XX_SPI_DEACT ( sdd ) ;
if ( status ) {
2010-02-02 10:22:13 +03:00
dev_err ( & spi - > dev , " I/O Error: "
" rx-%d tx-%d res:rx-%c tx-%c len-%d \n " ,
2009-11-30 10:39:42 +03:00
xfer - > rx_buf ? 1 : 0 , xfer - > tx_buf ? 1 : 0 ,
( sdd - > state & RXBUSY ) ? ' f ' : ' p ' ,
( sdd - > state & TXBUSY ) ? ' f ' : ' p ' ,
xfer - > len ) ;
if ( use_dma ) {
if ( xfer - > tx_buf ! = NULL
& & ( sdd - > state & TXBUSY ) )
2011-09-02 04:44:42 +04:00
sdd - > ops - > stop ( sdd - > tx_dma . ch ) ;
2009-11-30 10:39:42 +03:00
if ( xfer - > rx_buf ! = NULL
& & ( sdd - > state & RXBUSY ) )
2011-09-02 04:44:42 +04:00
sdd - > ops - > stop ( sdd - > rx_dma . ch ) ;
2009-11-30 10:39:42 +03:00
}
goto out ;
}
if ( xfer - > delay_usecs )
udelay ( xfer - > delay_usecs ) ;
if ( xfer - > cs_change ) {
/* Hint that the next mssg is gonna be
for the same device */
if ( list_is_last ( & xfer - > transfer_list ,
& msg - > transfers ) )
cs_toggle = 1 ;
else
disable_cs ( sdd , spi ) ;
}
msg - > actual_length + = xfer - > len ;
flush_fifo ( sdd ) ;
}
out :
if ( ! cs_toggle | | status )
disable_cs ( sdd , spi ) ;
else
sdd - > tgl_spi = spi ;
s3c64xx_spi_unmap_mssg ( sdd , msg ) ;
msg - > status = status ;
2012-02-16 02:48:32 +04:00
spi_finalize_current_message ( master ) ;
return 0 ;
2009-11-30 10:39:42 +03:00
}
2012-02-16 02:48:32 +04:00
static int s3c64xx_spi_prepare_transfer ( struct spi_master * spi )
2009-11-30 10:39:42 +03:00
{
2012-02-16 02:48:32 +04:00
struct s3c64xx_spi_driver_data * sdd = spi_master_get_devdata ( spi ) ;
2009-11-30 10:39:42 +03:00
/* Acquire DMA channels */
while ( ! acquire_dma ( sdd ) )
msleep ( 10 ) ;
2011-12-04 04:58:06 +04:00
pm_runtime_get_sync ( & sdd - > pdev - > dev ) ;
2012-02-16 02:48:32 +04:00
return 0 ;
}
2009-11-30 10:39:42 +03:00
2012-02-16 02:48:32 +04:00
static int s3c64xx_spi_unprepare_transfer ( struct spi_master * spi )
{
struct s3c64xx_spi_driver_data * sdd = spi_master_get_devdata ( spi ) ;
2009-11-30 10:39:42 +03:00
/* Free DMA channels */
2011-09-02 04:44:42 +04:00
sdd - > ops - > release ( sdd - > rx_dma . ch , & s3c64xx_spi_dma_client ) ;
sdd - > ops - > release ( sdd - > tx_dma . ch , & s3c64xx_spi_dma_client ) ;
2011-12-04 04:58:06 +04:00
pm_runtime_put ( & sdd - > pdev - > dev ) ;
2009-11-30 10:39:42 +03:00
return 0 ;
}
2012-07-13 02:15:15 +04:00
static struct s3c64xx_spi_csinfo * s3c64xx_get_slave_ctrldata (
struct s3c64xx_spi_driver_data * sdd ,
struct spi_device * spi )
{
struct s3c64xx_spi_csinfo * cs ;
struct device_node * slave_np , * data_np ;
u32 fb_delay = 0 ;
slave_np = spi - > dev . of_node ;
if ( ! slave_np ) {
dev_err ( & spi - > dev , " device node not found \n " ) ;
return ERR_PTR ( - EINVAL ) ;
}
for_each_child_of_node ( slave_np , data_np )
if ( ! strcmp ( data_np - > name , " controller-data " ) )
break ;
if ( ! data_np ) {
dev_err ( & spi - > dev , " child node 'controller-data' not found \n " ) ;
return ERR_PTR ( - EINVAL ) ;
}
cs = kzalloc ( sizeof ( * cs ) , GFP_KERNEL ) ;
if ( ! cs ) {
dev_err ( & spi - > dev , " could not allocate memory for controller "
" data \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
cs - > line = of_get_named_gpio ( data_np , " cs-gpio " , 0 ) ;
if ( ! gpio_is_valid ( cs - > line ) ) {
dev_err ( & spi - > dev , " chip select gpio is not specified or "
" invalid \n " ) ;
kfree ( cs ) ;
return ERR_PTR ( - EINVAL ) ;
}
of_property_read_u32 ( data_np , " samsung,spi-feedback-delay " , & fb_delay ) ;
cs - > fb_delay = fb_delay ;
return cs ;
}
2009-11-30 10:39:42 +03:00
/*
* Here we only check the validity of requested configuration
* and save the configuration in a local data - structure .
* The controller is actually configured only just before we
* get a message to transfer .
*/
static int s3c64xx_spi_setup ( struct spi_device * spi )
{
struct s3c64xx_spi_csinfo * cs = spi - > controller_data ;
struct s3c64xx_spi_driver_data * sdd ;
2010-01-20 23:49:44 +03:00
struct s3c64xx_spi_info * sci ;
2009-11-30 10:39:42 +03:00
struct spi_message * msg ;
unsigned long flags ;
2012-07-13 02:15:15 +04:00
int err ;
2009-11-30 10:39:42 +03:00
2012-07-13 02:15:15 +04:00
sdd = spi_master_get_devdata ( spi - > master ) ;
if ( ! cs & & spi - > dev . of_node ) {
cs = s3c64xx_get_slave_ctrldata ( sdd , spi ) ;
spi - > controller_data = cs ;
}
if ( IS_ERR_OR_NULL ( cs ) ) {
2009-11-30 10:39:42 +03:00
dev_err ( & spi - > dev , " No CS for SPI(%d) \n " , spi - > chip_select ) ;
return - ENODEV ;
}
2012-07-13 02:15:14 +04:00
if ( ! spi_get_ctldata ( spi ) ) {
2012-07-19 09:36:16 +04:00
err = gpio_request_one ( cs - > line , GPIOF_OUT_INIT_HIGH ,
dev_name ( & spi - > dev ) ) ;
2012-07-13 02:15:14 +04:00
if ( err ) {
2012-07-19 09:36:13 +04:00
dev_err ( & spi - > dev ,
" Failed to get /CS gpio [%d]: %d \n " ,
cs - > line , err ) ;
2012-07-13 02:15:15 +04:00
goto err_gpio_req ;
2012-07-13 02:15:14 +04:00
}
spi_set_ctldata ( spi , cs ) ;
}
2009-11-30 10:39:42 +03:00
sci = sdd - > cntrlr_info ;
spin_lock_irqsave ( & sdd - > lock , flags ) ;
list_for_each_entry ( msg , & sdd - > queue , queue ) {
/* Is some mssg is already queued for this device */
if ( msg - > spi = = spi ) {
dev_err ( & spi - > dev ,
" setup: attempt while mssg in queue! \n " ) ;
spin_unlock_irqrestore ( & sdd - > lock , flags ) ;
2012-07-13 02:15:15 +04:00
err = - EBUSY ;
goto err_msgq ;
2009-11-30 10:39:42 +03:00
}
}
spin_unlock_irqrestore ( & sdd - > lock , flags ) ;
if ( spi - > bits_per_word ! = 8
& & spi - > bits_per_word ! = 16
& & spi - > bits_per_word ! = 32 ) {
dev_err ( & spi - > dev , " setup: %dbits/wrd not supported! \n " ,
spi - > bits_per_word ) ;
err = - EINVAL ;
goto setup_exit ;
}
2011-12-04 04:58:06 +04:00
pm_runtime_get_sync ( & sdd - > pdev - > dev ) ;
2009-11-30 10:39:42 +03:00
/* Check if we can provide the requested rate */
2012-07-13 02:15:14 +04:00
if ( ! sdd - > port_conf - > clk_from_cmu ) {
2010-09-29 12:31:33 +04:00
u32 psr , speed ;
/* Max possible */
speed = clk_get_rate ( sdd - > src_clk ) / 2 / ( 0 + 1 ) ;
if ( spi - > max_speed_hz > speed )
spi - > max_speed_hz = speed ;
psr = clk_get_rate ( sdd - > src_clk ) / 2 / spi - > max_speed_hz - 1 ;
psr & = S3C64XX_SPI_PSR_MASK ;
if ( psr = = S3C64XX_SPI_PSR_MASK )
psr - - ;
speed = clk_get_rate ( sdd - > src_clk ) / 2 / ( psr + 1 ) ;
if ( spi - > max_speed_hz < speed ) {
if ( psr + 1 < S3C64XX_SPI_PSR_MASK ) {
psr + + ;
} else {
err = - EINVAL ;
goto setup_exit ;
}
}
2009-11-30 10:39:42 +03:00
2010-09-29 12:31:33 +04:00
speed = clk_get_rate ( sdd - > src_clk ) / 2 / ( psr + 1 ) ;
2012-07-13 02:15:15 +04:00
if ( spi - > max_speed_hz > = speed ) {
2010-09-29 12:31:33 +04:00
spi - > max_speed_hz = speed ;
2012-07-13 02:15:15 +04:00
} else {
2009-11-30 10:39:42 +03:00
err = - EINVAL ;
2012-07-13 02:15:15 +04:00
goto setup_exit ;
}
2009-11-30 10:39:42 +03:00
}
2011-12-04 04:58:06 +04:00
pm_runtime_put ( & sdd - > pdev - > dev ) ;
2012-07-13 02:15:15 +04:00
disable_cs ( sdd , spi ) ;
return 0 ;
2011-12-04 04:58:06 +04:00
2009-11-30 10:39:42 +03:00
setup_exit :
/* setup() returns with device de-selected */
disable_cs ( sdd , spi ) ;
2012-07-13 02:15:15 +04:00
err_msgq :
gpio_free ( cs - > line ) ;
spi_set_ctldata ( spi , NULL ) ;
err_gpio_req :
kfree ( cs ) ;
2009-11-30 10:39:42 +03:00
return err ;
}
2012-07-13 02:15:14 +04:00
static void s3c64xx_spi_cleanup ( struct spi_device * spi )
{
struct s3c64xx_spi_csinfo * cs = spi_get_ctldata ( spi ) ;
2012-07-13 02:15:15 +04:00
if ( cs ) {
2012-07-13 02:15:14 +04:00
gpio_free ( cs - > line ) ;
2012-07-13 02:15:15 +04:00
if ( spi - > dev . of_node )
kfree ( cs ) ;
}
2012-07-13 02:15:14 +04:00
spi_set_ctldata ( spi , NULL ) ;
}
2011-11-10 14:57:32 +04:00
static irqreturn_t s3c64xx_spi_irq ( int irq , void * data )
{
struct s3c64xx_spi_driver_data * sdd = data ;
struct spi_master * spi = sdd - > master ;
unsigned int val ;
val = readl ( sdd - > regs + S3C64XX_SPI_PENDING_CLR ) ;
val & = S3C64XX_SPI_PND_RX_OVERRUN_CLR |
S3C64XX_SPI_PND_RX_UNDERRUN_CLR |
S3C64XX_SPI_PND_TX_OVERRUN_CLR |
S3C64XX_SPI_PND_TX_UNDERRUN_CLR ;
writel ( val , sdd - > regs + S3C64XX_SPI_PENDING_CLR ) ;
if ( val & S3C64XX_SPI_PND_RX_OVERRUN_CLR )
dev_err ( & spi - > dev , " RX overrun \n " ) ;
if ( val & S3C64XX_SPI_PND_RX_UNDERRUN_CLR )
dev_err ( & spi - > dev , " RX underrun \n " ) ;
if ( val & S3C64XX_SPI_PND_TX_OVERRUN_CLR )
dev_err ( & spi - > dev , " TX overrun \n " ) ;
if ( val & S3C64XX_SPI_PND_TX_UNDERRUN_CLR )
dev_err ( & spi - > dev , " TX underrun \n " ) ;
return IRQ_HANDLED ;
}
2009-11-30 10:39:42 +03:00
static void s3c64xx_spi_hwinit ( struct s3c64xx_spi_driver_data * sdd , int channel )
{
2010-01-20 23:49:44 +03:00
struct s3c64xx_spi_info * sci = sdd - > cntrlr_info ;
2009-11-30 10:39:42 +03:00
void __iomem * regs = sdd - > regs ;
unsigned int val ;
sdd - > cur_speed = 0 ;
S3C64XX_SPI_DEACT ( sdd ) ;
/* Disable Interrupts - we use Polling if not DMA mode */
writel ( 0 , regs + S3C64XX_SPI_INT_EN ) ;
2012-07-13 02:15:14 +04:00
if ( ! sdd - > port_conf - > clk_from_cmu )
2010-09-29 12:31:33 +04:00
writel ( sci - > src_clk_nr < < S3C64XX_SPI_CLKSEL_SRCSHFT ,
2009-11-30 10:39:42 +03:00
regs + S3C64XX_SPI_CLK_CFG ) ;
writel ( 0 , regs + S3C64XX_SPI_MODE_CFG ) ;
writel ( 0 , regs + S3C64XX_SPI_PACKET_CNT ) ;
/* Clear any irq pending bits */
writel ( readl ( regs + S3C64XX_SPI_PENDING_CLR ) ,
regs + S3C64XX_SPI_PENDING_CLR ) ;
writel ( 0 , regs + S3C64XX_SPI_SWAP_CFG ) ;
val = readl ( regs + S3C64XX_SPI_MODE_CFG ) ;
val & = ~ S3C64XX_SPI_MODE_4BURST ;
val & = ~ ( S3C64XX_SPI_MAX_TRAILCNT < < S3C64XX_SPI_TRAILCNT_OFF ) ;
val | = ( S3C64XX_SPI_TRAILCNT < < S3C64XX_SPI_TRAILCNT_OFF ) ;
writel ( val , regs + S3C64XX_SPI_MODE_CFG ) ;
flush_fifo ( sdd ) ;
}
2012-07-13 02:15:15 +04:00
static int __devinit s3c64xx_spi_get_dmares (
struct s3c64xx_spi_driver_data * sdd , bool tx )
{
struct platform_device * pdev = sdd - > pdev ;
struct s3c64xx_spi_dma_data * dma_data ;
struct property * prop ;
struct resource * res ;
char prop_name [ 15 ] , * chan_str ;
if ( tx ) {
dma_data = & sdd - > tx_dma ;
dma_data - > direction = DMA_TO_DEVICE ;
chan_str = " tx " ;
} else {
dma_data = & sdd - > rx_dma ;
dma_data - > direction = DMA_FROM_DEVICE ;
chan_str = " rx " ;
}
if ( ! sdd - > pdev - > dev . of_node ) {
res = platform_get_resource ( pdev , IORESOURCE_DMA , tx ? 0 : 1 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " Unable to get SPI-%s dma "
" resource \n " , chan_str ) ;
return - ENXIO ;
}
dma_data - > dmach = res - > start ;
return 0 ;
}
sprintf ( prop_name , " %s-dma-channel " , chan_str ) ;
prop = of_find_property ( pdev - > dev . of_node , prop_name , NULL ) ;
if ( ! prop ) {
dev_err ( & pdev - > dev , " %s dma channel property not specified \n " ,
chan_str ) ;
return - ENXIO ;
}
dma_data - > dmach = DMACH_DT_PROP ;
dma_data - > dma_prop = prop ;
return 0 ;
}
# ifdef CONFIG_OF
static int s3c64xx_spi_parse_dt_gpio ( struct s3c64xx_spi_driver_data * sdd )
{
struct device * dev = & sdd - > pdev - > dev ;
int idx , gpio , ret ;
/* find gpios for mosi, miso and clock lines */
for ( idx = 0 ; idx < 3 ; idx + + ) {
gpio = of_get_gpio ( dev - > of_node , idx ) ;
if ( ! gpio_is_valid ( gpio ) ) {
dev_err ( dev , " invalid gpio[%d]: %d \n " , idx , gpio ) ;
goto free_gpio ;
}
ret = gpio_request ( gpio , " spi-bus " ) ;
if ( ret ) {
2012-07-19 09:36:13 +04:00
dev_err ( dev , " gpio [%d] request failed: %d \n " ,
gpio , ret ) ;
2012-07-13 02:15:15 +04:00
goto free_gpio ;
}
}
return 0 ;
free_gpio :
while ( - - idx > = 0 )
gpio_free ( sdd - > gpios [ idx ] ) ;
return - EINVAL ;
}
static void s3c64xx_spi_dt_gpio_free ( struct s3c64xx_spi_driver_data * sdd )
{
unsigned int idx ;
for ( idx = 0 ; idx < 3 ; idx + + )
gpio_free ( sdd - > gpios [ idx ] ) ;
}
static struct __devinit s3c64xx_spi_info * s3c64xx_spi_parse_dt (
struct device * dev )
{
struct s3c64xx_spi_info * sci ;
u32 temp ;
sci = devm_kzalloc ( dev , sizeof ( * sci ) , GFP_KERNEL ) ;
if ( ! sci ) {
dev_err ( dev , " memory allocation for spi_info failed \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
if ( of_property_read_u32 ( dev - > of_node , " samsung,spi-src-clk " , & temp ) ) {
dev_warn ( dev , " spi bus clock parent not specified, using "
" clock at index 0 as parent \n " ) ;
sci - > src_clk_nr = 0 ;
} else {
sci - > src_clk_nr = temp ;
}
if ( of_property_read_u32 ( dev - > of_node , " num-cs " , & temp ) ) {
dev_warn ( dev , " number of chip select lines not specified, "
" assuming 1 chip select line \n " ) ;
sci - > num_cs = 1 ;
} else {
sci - > num_cs = temp ;
}
return sci ;
}
# else
static struct s3c64xx_spi_info * s3c64xx_spi_parse_dt ( struct device * dev )
{
return dev - > platform_data ;
}
static int s3c64xx_spi_parse_dt_gpio ( struct s3c64xx_spi_driver_data * sdd )
{
return - EINVAL ;
}
static void s3c64xx_spi_dt_gpio_free ( struct s3c64xx_spi_driver_data * sdd )
{
}
# endif
static const struct of_device_id s3c64xx_spi_dt_match [ ] ;
2012-07-13 02:15:14 +04:00
static inline struct s3c64xx_spi_port_config * s3c64xx_spi_get_port_config (
struct platform_device * pdev )
{
2012-07-13 02:15:15 +04:00
# ifdef CONFIG_OF
if ( pdev - > dev . of_node ) {
const struct of_device_id * match ;
match = of_match_node ( s3c64xx_spi_dt_match , pdev - > dev . of_node ) ;
return ( struct s3c64xx_spi_port_config * ) match - > data ;
}
# endif
2012-07-13 02:15:14 +04:00
return ( struct s3c64xx_spi_port_config * )
platform_get_device_id ( pdev ) - > driver_data ;
}
2009-11-30 10:39:42 +03:00
static int __init s3c64xx_spi_probe ( struct platform_device * pdev )
{
2012-07-13 02:15:15 +04:00
struct resource * mem_res ;
2009-11-30 10:39:42 +03:00
struct s3c64xx_spi_driver_data * sdd ;
2012-07-13 02:15:15 +04:00
struct s3c64xx_spi_info * sci = pdev - > dev . platform_data ;
2009-11-30 10:39:42 +03:00
struct spi_master * master ;
2011-11-10 14:57:32 +04:00
int ret , irq ;
2011-11-02 15:04:19 +04:00
char clk_name [ 16 ] ;
2009-11-30 10:39:42 +03:00
2012-07-13 02:15:15 +04:00
if ( ! sci & & pdev - > dev . of_node ) {
sci = s3c64xx_spi_parse_dt ( & pdev - > dev ) ;
if ( IS_ERR ( sci ) )
return PTR_ERR ( sci ) ;
2009-11-30 10:39:42 +03:00
}
2012-07-13 02:15:15 +04:00
if ( ! sci ) {
2009-11-30 10:39:42 +03:00
dev_err ( & pdev - > dev , " platform_data missing! \n " ) ;
return - ENODEV ;
}
mem_res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( mem_res = = NULL ) {
dev_err ( & pdev - > dev , " Unable to get SPI MEM resource \n " ) ;
return - ENXIO ;
}
2011-11-10 14:57:32 +04:00
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
dev_warn ( & pdev - > dev , " Failed to get IRQ: %d \n " , irq ) ;
return irq ;
}
2009-11-30 10:39:42 +03:00
master = spi_alloc_master ( & pdev - > dev ,
sizeof ( struct s3c64xx_spi_driver_data ) ) ;
if ( master = = NULL ) {
dev_err ( & pdev - > dev , " Unable to allocate SPI Master \n " ) ;
return - ENOMEM ;
}
platform_set_drvdata ( pdev , master ) ;
sdd = spi_master_get_devdata ( master ) ;
2012-07-13 02:15:14 +04:00
sdd - > port_conf = s3c64xx_spi_get_port_config ( pdev ) ;
2009-11-30 10:39:42 +03:00
sdd - > master = master ;
sdd - > cntrlr_info = sci ;
sdd - > pdev = pdev ;
sdd - > sfr_start = mem_res - > start ;
2012-07-13 02:15:15 +04:00
if ( pdev - > dev . of_node ) {
ret = of_alias_get_id ( pdev - > dev . of_node , " spi " ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to get alias id, "
" errno %d \n " , ret ) ;
goto err0 ;
}
sdd - > port_id = ret ;
} else {
sdd - > port_id = pdev - > id ;
}
2009-11-30 10:39:42 +03:00
sdd - > cur_bpw = 8 ;
2012-07-13 02:15:15 +04:00
ret = s3c64xx_spi_get_dmares ( sdd , true ) ;
if ( ret )
goto err0 ;
ret = s3c64xx_spi_get_dmares ( sdd , false ) ;
if ( ret )
goto err0 ;
master - > dev . of_node = pdev - > dev . of_node ;
2012-07-13 02:15:14 +04:00
master - > bus_num = sdd - > port_id ;
2009-11-30 10:39:42 +03:00
master - > setup = s3c64xx_spi_setup ;
2012-07-13 02:15:14 +04:00
master - > cleanup = s3c64xx_spi_cleanup ;
2012-02-16 02:48:32 +04:00
master - > prepare_transfer_hardware = s3c64xx_spi_prepare_transfer ;
master - > transfer_one_message = s3c64xx_spi_transfer_one_message ;
master - > unprepare_transfer_hardware = s3c64xx_spi_unprepare_transfer ;
2009-11-30 10:39:42 +03:00
master - > num_chipselect = sci - > num_cs ;
master - > dma_alignment = 8 ;
/* the spi->mode bits understood by this driver: */
master - > mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH ;
2012-07-19 09:36:19 +04:00
sdd - > regs = devm_request_and_ioremap ( & pdev - > dev , mem_res ) ;
2009-11-30 10:39:42 +03:00
if ( sdd - > regs = = NULL ) {
dev_err ( & pdev - > dev , " Unable to remap IO \n " ) ;
ret = - ENXIO ;
goto err1 ;
}
2012-07-13 02:15:15 +04:00
if ( ! sci - > cfg_gpio & & pdev - > dev . of_node ) {
if ( s3c64xx_spi_parse_dt_gpio ( sdd ) )
return - EBUSY ;
} else if ( sci - > cfg_gpio = = NULL | | sci - > cfg_gpio ( ) ) {
2009-11-30 10:39:42 +03:00
dev_err ( & pdev - > dev , " Unable to config gpio \n " ) ;
ret = - EBUSY ;
goto err2 ;
}
/* Setup clocks */
sdd - > clk = clk_get ( & pdev - > dev , " spi " ) ;
if ( IS_ERR ( sdd - > clk ) ) {
dev_err ( & pdev - > dev , " Unable to acquire clock 'spi' \n " ) ;
ret = PTR_ERR ( sdd - > clk ) ;
goto err3 ;
}
if ( clk_enable ( sdd - > clk ) ) {
dev_err ( & pdev - > dev , " Couldn't enable clock 'spi' \n " ) ;
ret = - EBUSY ;
goto err4 ;
}
2011-11-02 15:04:19 +04:00
sprintf ( clk_name , " spi_busclk%d " , sci - > src_clk_nr ) ;
sdd - > src_clk = clk_get ( & pdev - > dev , clk_name ) ;
2010-01-20 23:49:44 +03:00
if ( IS_ERR ( sdd - > src_clk ) ) {
2009-11-30 10:39:42 +03:00
dev_err ( & pdev - > dev ,
2011-11-02 15:04:19 +04:00
" Unable to acquire clock '%s' \n " , clk_name ) ;
2010-01-20 23:49:44 +03:00
ret = PTR_ERR ( sdd - > src_clk ) ;
2009-11-30 10:39:42 +03:00
goto err5 ;
}
2010-01-20 23:49:44 +03:00
if ( clk_enable ( sdd - > src_clk ) ) {
2011-11-02 15:04:19 +04:00
dev_err ( & pdev - > dev , " Couldn't enable clock '%s' \n " , clk_name ) ;
2009-11-30 10:39:42 +03:00
ret = - EBUSY ;
goto err6 ;
}
/* Setup Deufult Mode */
2012-07-13 02:15:14 +04:00
s3c64xx_spi_hwinit ( sdd , sdd - > port_id ) ;
2009-11-30 10:39:42 +03:00
spin_lock_init ( & sdd - > lock ) ;
init_completion ( & sdd - > xfer_completion ) ;
INIT_LIST_HEAD ( & sdd - > queue ) ;
2011-11-10 14:57:32 +04:00
ret = request_irq ( irq , s3c64xx_spi_irq , 0 , " spi-s3c64xx " , sdd ) ;
if ( ret ! = 0 ) {
dev_err ( & pdev - > dev , " Failed to request IRQ %d: %d \n " ,
irq , ret ) ;
2012-02-16 02:48:32 +04:00
goto err7 ;
2011-11-10 14:57:32 +04:00
}
writel ( S3C64XX_SPI_INT_RX_OVERRUN_EN | S3C64XX_SPI_INT_RX_UNDERRUN_EN |
S3C64XX_SPI_INT_TX_OVERRUN_EN | S3C64XX_SPI_INT_TX_UNDERRUN_EN ,
sdd - > regs + S3C64XX_SPI_INT_EN ) ;
2009-11-30 10:39:42 +03:00
if ( spi_register_master ( master ) ) {
dev_err ( & pdev - > dev , " cannot register SPI master \n " ) ;
ret = - EBUSY ;
2012-02-16 02:48:32 +04:00
goto err8 ;
2009-11-30 10:39:42 +03:00
}
2010-02-02 10:22:13 +03:00
dev_dbg ( & pdev - > dev , " Samsung SoC SPI Driver loaded for Bus SPI-%d "
" with %d Slaves attached \n " ,
2012-07-13 02:15:14 +04:00
sdd - > port_id , master - > num_chipselect ) ;
2010-02-02 10:22:13 +03:00
dev_dbg ( & pdev - > dev , " \t IOmem=[0x%x-0x%x] \t DMA=[Rx-%d, Tx-%d] \n " ,
2009-11-30 10:39:42 +03:00
mem_res - > end , mem_res - > start ,
2011-09-02 04:44:42 +04:00
sdd - > rx_dma . dmach , sdd - > tx_dma . dmach ) ;
2009-11-30 10:39:42 +03:00
2011-12-04 04:58:06 +04:00
pm_runtime_enable ( & pdev - > dev ) ;
2009-11-30 10:39:42 +03:00
return 0 ;
err8 :
2012-02-16 02:48:32 +04:00
free_irq ( irq , sdd ) ;
2009-11-30 10:39:42 +03:00
err7 :
2010-01-20 23:49:44 +03:00
clk_disable ( sdd - > src_clk ) ;
2009-11-30 10:39:42 +03:00
err6 :
2010-01-20 23:49:44 +03:00
clk_put ( sdd - > src_clk ) ;
2009-11-30 10:39:42 +03:00
err5 :
clk_disable ( sdd - > clk ) ;
err4 :
clk_put ( sdd - > clk ) ;
err3 :
2012-07-13 02:15:15 +04:00
if ( ! sdd - > cntrlr_info - > cfg_gpio & & pdev - > dev . of_node )
s3c64xx_spi_dt_gpio_free ( sdd ) ;
2009-11-30 10:39:42 +03:00
err2 :
err1 :
err0 :
platform_set_drvdata ( pdev , NULL ) ;
spi_master_put ( master ) ;
return ret ;
}
static int s3c64xx_spi_remove ( struct platform_device * pdev )
{
struct spi_master * master = spi_master_get ( platform_get_drvdata ( pdev ) ) ;
struct s3c64xx_spi_driver_data * sdd = spi_master_get_devdata ( master ) ;
2011-12-04 04:58:06 +04:00
pm_runtime_disable ( & pdev - > dev ) ;
2009-11-30 10:39:42 +03:00
spi_unregister_master ( master ) ;
2011-11-10 14:57:32 +04:00
writel ( 0 , sdd - > regs + S3C64XX_SPI_INT_EN ) ;
free_irq ( platform_get_irq ( pdev , 0 ) , sdd ) ;
2010-01-20 23:49:44 +03:00
clk_disable ( sdd - > src_clk ) ;
clk_put ( sdd - > src_clk ) ;
2009-11-30 10:39:42 +03:00
clk_disable ( sdd - > clk ) ;
clk_put ( sdd - > clk ) ;
2012-07-13 02:15:15 +04:00
if ( ! sdd - > cntrlr_info - > cfg_gpio & & pdev - > dev . of_node )
s3c64xx_spi_dt_gpio_free ( sdd ) ;
2009-11-30 10:39:42 +03:00
platform_set_drvdata ( pdev , NULL ) ;
spi_master_put ( master ) ;
return 0 ;
}
# ifdef CONFIG_PM
2011-12-04 04:36:18 +04:00
static int s3c64xx_spi_suspend ( struct device * dev )
2009-11-30 10:39:42 +03:00
{
2011-12-04 04:36:18 +04:00
struct spi_master * master = spi_master_get ( dev_get_drvdata ( dev ) ) ;
2009-11-30 10:39:42 +03:00
struct s3c64xx_spi_driver_data * sdd = spi_master_get_devdata ( master ) ;
2012-02-16 02:48:32 +04:00
spi_master_suspend ( master ) ;
2009-11-30 10:39:42 +03:00
/* Disable the clock */
2010-01-20 23:49:44 +03:00
clk_disable ( sdd - > src_clk ) ;
2009-11-30 10:39:42 +03:00
clk_disable ( sdd - > clk ) ;
2012-07-13 02:15:15 +04:00
if ( ! sdd - > cntrlr_info - > cfg_gpio & & dev - > of_node )
s3c64xx_spi_dt_gpio_free ( sdd ) ;
2009-11-30 10:39:42 +03:00
sdd - > cur_speed = 0 ; /* Output Clock is stopped */
return 0 ;
}
2011-12-04 04:36:18 +04:00
static int s3c64xx_spi_resume ( struct device * dev )
2009-11-30 10:39:42 +03:00
{
2011-12-04 04:36:18 +04:00
struct spi_master * master = spi_master_get ( dev_get_drvdata ( dev ) ) ;
2009-11-30 10:39:42 +03:00
struct s3c64xx_spi_driver_data * sdd = spi_master_get_devdata ( master ) ;
2010-01-20 23:49:44 +03:00
struct s3c64xx_spi_info * sci = sdd - > cntrlr_info ;
2009-11-30 10:39:42 +03:00
2012-07-13 02:15:15 +04:00
if ( ! sci - > cfg_gpio & & dev - > of_node )
s3c64xx_spi_parse_dt_gpio ( sdd ) ;
else
sci - > cfg_gpio ( ) ;
2009-11-30 10:39:42 +03:00
/* Enable the clock */
2010-01-20 23:49:44 +03:00
clk_enable ( sdd - > src_clk ) ;
2009-11-30 10:39:42 +03:00
clk_enable ( sdd - > clk ) ;
2012-07-13 02:15:14 +04:00
s3c64xx_spi_hwinit ( sdd , sdd - > port_id ) ;
2009-11-30 10:39:42 +03:00
2012-02-16 02:48:32 +04:00
spi_master_resume ( master ) ;
2009-11-30 10:39:42 +03:00
return 0 ;
}
# endif /* CONFIG_PM */
2011-12-04 04:58:06 +04:00
# ifdef CONFIG_PM_RUNTIME
static int s3c64xx_spi_runtime_suspend ( struct device * dev )
{
struct spi_master * master = spi_master_get ( dev_get_drvdata ( dev ) ) ;
struct s3c64xx_spi_driver_data * sdd = spi_master_get_devdata ( master ) ;
clk_disable ( sdd - > clk ) ;
clk_disable ( sdd - > src_clk ) ;
return 0 ;
}
static int s3c64xx_spi_runtime_resume ( struct device * dev )
{
struct spi_master * master = spi_master_get ( dev_get_drvdata ( dev ) ) ;
struct s3c64xx_spi_driver_data * sdd = spi_master_get_devdata ( master ) ;
clk_enable ( sdd - > src_clk ) ;
clk_enable ( sdd - > clk ) ;
return 0 ;
}
# endif /* CONFIG_PM_RUNTIME */
2011-12-04 04:36:18 +04:00
static const struct dev_pm_ops s3c64xx_spi_pm = {
SET_SYSTEM_SLEEP_PM_OPS ( s3c64xx_spi_suspend , s3c64xx_spi_resume )
2011-12-04 04:58:06 +04:00
SET_RUNTIME_PM_OPS ( s3c64xx_spi_runtime_suspend ,
s3c64xx_spi_runtime_resume , NULL )
2011-12-04 04:36:18 +04:00
} ;
2012-07-13 02:15:14 +04:00
struct s3c64xx_spi_port_config s3c2443_spi_port_config = {
. fifo_lvl_mask = { 0x7f } ,
. rx_lvl_offset = 13 ,
. tx_st_done = 21 ,
. high_speed = true ,
} ;
struct s3c64xx_spi_port_config s3c6410_spi_port_config = {
. fifo_lvl_mask = { 0x7f , 0x7F } ,
. rx_lvl_offset = 13 ,
. tx_st_done = 21 ,
} ;
struct s3c64xx_spi_port_config s5p64x0_spi_port_config = {
. fifo_lvl_mask = { 0x1ff , 0x7F } ,
. rx_lvl_offset = 15 ,
. tx_st_done = 25 ,
} ;
struct s3c64xx_spi_port_config s5pc100_spi_port_config = {
. fifo_lvl_mask = { 0x7f , 0x7F } ,
. rx_lvl_offset = 13 ,
. tx_st_done = 21 ,
. high_speed = true ,
} ;
struct s3c64xx_spi_port_config s5pv210_spi_port_config = {
. fifo_lvl_mask = { 0x1ff , 0x7F } ,
. rx_lvl_offset = 15 ,
. tx_st_done = 25 ,
. high_speed = true ,
} ;
struct s3c64xx_spi_port_config exynos4_spi_port_config = {
. fifo_lvl_mask = { 0x1ff , 0x7F , 0x7F } ,
. rx_lvl_offset = 15 ,
. tx_st_done = 25 ,
. high_speed = true ,
. clk_from_cmu = true ,
} ;
static struct platform_device_id s3c64xx_spi_driver_ids [ ] = {
{
. name = " s3c2443-spi " ,
. driver_data = ( kernel_ulong_t ) & s3c2443_spi_port_config ,
} , {
. name = " s3c6410-spi " ,
. driver_data = ( kernel_ulong_t ) & s3c6410_spi_port_config ,
} , {
. name = " s5p64x0-spi " ,
. driver_data = ( kernel_ulong_t ) & s5p64x0_spi_port_config ,
} , {
. name = " s5pc100-spi " ,
. driver_data = ( kernel_ulong_t ) & s5pc100_spi_port_config ,
} , {
. name = " s5pv210-spi " ,
. driver_data = ( kernel_ulong_t ) & s5pv210_spi_port_config ,
} , {
. name = " exynos4210-spi " ,
. driver_data = ( kernel_ulong_t ) & exynos4_spi_port_config ,
} ,
{ } ,
} ;
2012-07-13 02:15:15 +04:00
# ifdef CONFIG_OF
static const struct of_device_id s3c64xx_spi_dt_match [ ] = {
{ . compatible = " samsung,exynos4210-spi " ,
. data = ( void * ) & exynos4_spi_port_config ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , s3c64xx_spi_dt_match ) ;
# endif /* CONFIG_OF */
2009-11-30 10:39:42 +03:00
static struct platform_driver s3c64xx_spi_driver = {
. driver = {
. name = " s3c64xx-spi " ,
. owner = THIS_MODULE ,
2011-12-04 04:36:18 +04:00
. pm = & s3c64xx_spi_pm ,
2012-07-13 02:15:15 +04:00
. of_match_table = of_match_ptr ( s3c64xx_spi_dt_match ) ,
2009-11-30 10:39:42 +03:00
} ,
. remove = s3c64xx_spi_remove ,
2012-07-13 02:15:14 +04:00
. id_table = s3c64xx_spi_driver_ids ,
2009-11-30 10:39:42 +03:00
} ;
MODULE_ALIAS ( " platform:s3c64xx-spi " ) ;
static int __init s3c64xx_spi_init ( void )
{
return platform_driver_probe ( & s3c64xx_spi_driver , s3c64xx_spi_probe ) ;
}
2010-09-07 14:29:17 +04:00
subsys_initcall ( s3c64xx_spi_init ) ;
2009-11-30 10:39:42 +03:00
static void __exit s3c64xx_spi_exit ( void )
{
platform_driver_unregister ( & s3c64xx_spi_driver ) ;
}
module_exit ( s3c64xx_spi_exit ) ;
MODULE_AUTHOR ( " Jaswinder Singh <jassi.brar@samsung.com> " ) ;
MODULE_DESCRIPTION ( " S3C64XX SPI Controller Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;