2012-02-01 14:14:09 +04:00
/*
* Broadcom BCM63xx SPI controller support
*
2012-04-20 17:37:33 +04:00
* Copyright ( C ) 2009 - 2012 Florian Fainelli < florian @ openwrt . org >
2012-02-01 14:14:09 +04:00
* Copyright ( C ) 2010 Tanguy Bouzeloc < tanguy . bouzeloc @ efixo . 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 . , 51 Franklin Street , Fifth Floor ,
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/spi/spi.h>
# include <linux/completion.h>
# include <linux/err.h>
2012-04-20 17:37:33 +04:00
# include <linux/workqueue.h>
# include <linux/pm_runtime.h>
2012-02-01 14:14:09 +04:00
# include <bcm63xx_dev_spi.h>
# define PFX KBUILD_MODNAME
struct bcm63xx_spi {
struct completion done ;
void __iomem * regs ;
int irq ;
/* Platform data */
u32 speed_hz ;
unsigned fifo_size ;
2012-06-18 14:07:51 +04:00
unsigned int msg_type_shift ;
unsigned int msg_ctl_width ;
2012-02-01 14:14:09 +04:00
/* Data buffers */
const unsigned char * tx_ptr ;
unsigned char * rx_ptr ;
/* data iomem */
u8 __iomem * tx_io ;
const u8 __iomem * rx_io ;
int remaining_bytes ;
struct clk * clk ;
struct platform_device * pdev ;
} ;
static inline u8 bcm_spi_readb ( struct bcm63xx_spi * bs ,
unsigned int offset )
{
return bcm_readb ( bs - > regs + bcm63xx_spireg ( offset ) ) ;
}
static inline u16 bcm_spi_readw ( struct bcm63xx_spi * bs ,
unsigned int offset )
{
return bcm_readw ( bs - > regs + bcm63xx_spireg ( offset ) ) ;
}
static inline void bcm_spi_writeb ( struct bcm63xx_spi * bs ,
u8 value , unsigned int offset )
{
bcm_writeb ( value , bs - > regs + bcm63xx_spireg ( offset ) ) ;
}
static inline void bcm_spi_writew ( struct bcm63xx_spi * bs ,
u16 value , unsigned int offset )
{
bcm_writew ( value , bs - > regs + bcm63xx_spireg ( offset ) ) ;
}
static const unsigned bcm63xx_spi_freq_table [ SPI_CLK_MASK ] [ 2 ] = {
{ 20000000 , SPI_CLK_20MHZ } ,
{ 12500000 , SPI_CLK_12_50MHZ } ,
{ 6250000 , SPI_CLK_6_250MHZ } ,
{ 3125000 , SPI_CLK_3_125MHZ } ,
{ 1563000 , SPI_CLK_1_563MHZ } ,
{ 781000 , SPI_CLK_0_781MHZ } ,
{ 391000 , SPI_CLK_0_391MHZ }
} ;
2012-04-20 17:37:33 +04:00
static int bcm63xx_spi_check_transfer ( struct spi_device * spi ,
struct spi_transfer * t )
2012-02-01 14:14:09 +04:00
{
u8 bits_per_word ;
bits_per_word = ( t ) ? t - > bits_per_word : spi - > bits_per_word ;
if ( bits_per_word ! = 8 ) {
dev_err ( & spi - > dev , " %s, unsupported bits_per_word=%d \n " ,
__func__ , bits_per_word ) ;
return - EINVAL ;
}
if ( spi - > chip_select > spi - > master - > num_chipselect ) {
dev_err ( & spi - > dev , " %s, unsupported slave %d \n " ,
__func__ , spi - > chip_select ) ;
return - EINVAL ;
}
2012-04-20 17:37:33 +04:00
return 0 ;
}
static void bcm63xx_spi_setup_transfer ( struct spi_device * spi ,
struct spi_transfer * t )
{
struct bcm63xx_spi * bs = spi_master_get_devdata ( spi - > master ) ;
u32 hz ;
u8 clk_cfg , reg ;
int i ;
hz = ( t ) ? t - > speed_hz : spi - > max_speed_hz ;
2012-02-01 14:14:09 +04:00
/* Find the closest clock configuration */
for ( i = 0 ; i < SPI_CLK_MASK ; i + + ) {
2012-07-23 16:44:36 +04:00
if ( hz > = bcm63xx_spi_freq_table [ i ] [ 0 ] ) {
2012-02-01 14:14:09 +04:00
clk_cfg = bcm63xx_spi_freq_table [ i ] [ 1 ] ;
break ;
}
}
/* No matching configuration found, default to lowest */
if ( i = = SPI_CLK_MASK )
clk_cfg = SPI_CLK_0_391MHZ ;
/* clear existing clock configuration bits of the register */
reg = bcm_spi_readb ( bs , SPI_CLK_CFG ) ;
reg & = ~ SPI_CLK_MASK ;
reg | = clk_cfg ;
bcm_spi_writeb ( bs , reg , SPI_CLK_CFG ) ;
dev_dbg ( & spi - > dev , " Setting clock register to %02x (hz %d) \n " ,
clk_cfg , hz ) ;
}
/* the spi->mode bits understood by this driver: */
# define MODEBITS (SPI_CPOL | SPI_CPHA)
static int bcm63xx_spi_setup ( struct spi_device * spi )
{
struct bcm63xx_spi * bs ;
int ret ;
bs = spi_master_get_devdata ( spi - > master ) ;
if ( ! spi - > bits_per_word )
spi - > bits_per_word = 8 ;
if ( spi - > mode & ~ MODEBITS ) {
dev_err ( & spi - > dev , " %s, unsupported mode bits %x \n " ,
__func__ , spi - > mode & ~ MODEBITS ) ;
return - EINVAL ;
}
dev_dbg ( & spi - > dev , " %s, mode %d, %u bits/w, %u nsec/bit \n " ,
__func__ , spi - > mode & MODEBITS , spi - > bits_per_word , 0 ) ;
return 0 ;
}
/* Fill the TX FIFO with as many bytes as possible */
static void bcm63xx_spi_fill_tx_fifo ( struct bcm63xx_spi * bs )
{
u8 size ;
/* Fill the Tx FIFO with as many bytes as possible */
size = bs - > remaining_bytes < bs - > fifo_size ? bs - > remaining_bytes :
bs - > fifo_size ;
memcpy_toio ( bs - > tx_io , bs - > tx_ptr , size ) ;
bs - > remaining_bytes - = size ;
}
2012-04-20 17:37:33 +04:00
static unsigned int bcm63xx_txrx_bufs ( struct spi_device * spi ,
struct spi_transfer * t )
2012-02-01 14:14:09 +04:00
{
struct bcm63xx_spi * bs = spi_master_get_devdata ( spi - > master ) ;
u16 msg_ctl ;
u16 cmd ;
2012-04-20 17:37:33 +04:00
/* Disable the CMD_DONE interrupt */
bcm_spi_writeb ( bs , 0 , SPI_INT_MASK ) ;
2012-02-01 14:14:09 +04:00
dev_dbg ( & spi - > dev , " txrx: tx %p, rx %p, len %d \n " ,
t - > tx_buf , t - > rx_buf , t - > len ) ;
/* Transmitter is inhibited */
bs - > tx_ptr = t - > tx_buf ;
bs - > rx_ptr = t - > rx_buf ;
if ( t - > tx_buf ) {
bs - > remaining_bytes = t - > len ;
bcm63xx_spi_fill_tx_fifo ( bs ) ;
}
2012-04-20 17:37:33 +04:00
init_completion ( & bs - > done ) ;
2012-02-01 14:14:09 +04:00
/* Fill in the Message control register */
msg_ctl = ( t - > len < < SPI_BYTE_CNT_SHIFT ) ;
if ( t - > rx_buf & & t - > tx_buf )
2012-06-18 14:07:51 +04:00
msg_ctl | = ( SPI_FD_RW < < bs - > msg_type_shift ) ;
2012-02-01 14:14:09 +04:00
else if ( t - > rx_buf )
2012-06-18 14:07:51 +04:00
msg_ctl | = ( SPI_HD_R < < bs - > msg_type_shift ) ;
2012-02-01 14:14:09 +04:00
else if ( t - > tx_buf )
2012-06-18 14:07:51 +04:00
msg_ctl | = ( SPI_HD_W < < bs - > msg_type_shift ) ;
switch ( bs - > msg_ctl_width ) {
case 8 :
bcm_spi_writeb ( bs , msg_ctl , SPI_MSG_CTL ) ;
break ;
case 16 :
bcm_spi_writew ( bs , msg_ctl , SPI_MSG_CTL ) ;
break ;
}
2012-02-01 14:14:09 +04:00
/* Issue the transfer */
cmd = SPI_CMD_START_IMMEDIATE ;
cmd | = ( 0 < < SPI_CMD_PREPEND_BYTE_CNT_SHIFT ) ;
cmd | = ( spi - > chip_select < < SPI_CMD_DEVICE_ID_SHIFT ) ;
bcm_spi_writew ( bs , cmd , SPI_CMD ) ;
2012-04-20 17:37:33 +04:00
/* Enable the CMD_DONE interrupt */
bcm_spi_writeb ( bs , SPI_INTR_CMD_DONE , SPI_INT_MASK ) ;
2012-02-01 14:14:09 +04:00
return t - > len - bs - > remaining_bytes ;
}
2012-04-20 17:37:33 +04:00
static int bcm63xx_spi_prepare_transfer ( struct spi_master * master )
2012-02-01 14:14:09 +04:00
{
2012-04-20 17:37:33 +04:00
struct bcm63xx_spi * bs = spi_master_get_devdata ( master ) ;
2012-02-01 14:14:09 +04:00
2012-04-20 17:37:33 +04:00
pm_runtime_get_sync ( & bs - > pdev - > dev ) ;
2012-02-01 14:14:09 +04:00
2012-04-20 17:37:33 +04:00
return 0 ;
}
static int bcm63xx_spi_unprepare_transfer ( struct spi_master * master )
{
struct bcm63xx_spi * bs = spi_master_get_devdata ( master ) ;
pm_runtime_put ( & bs - > pdev - > dev ) ;
return 0 ;
}
static int bcm63xx_spi_transfer_one ( struct spi_master * master ,
struct spi_message * m )
{
struct bcm63xx_spi * bs = spi_master_get_devdata ( master ) ;
struct spi_transfer * t ;
struct spi_device * spi = m - > spi ;
int status = 0 ;
unsigned int timeout = 0 ;
2012-02-01 14:14:09 +04:00
list_for_each_entry ( t , & m - > transfers , transfer_list ) {
2012-04-20 17:37:33 +04:00
unsigned int len = t - > len ;
u8 rx_tail ;
status = bcm63xx_spi_check_transfer ( spi , t ) ;
if ( status < 0 )
goto exit ;
/* configure adapter for a new transfer */
bcm63xx_spi_setup_transfer ( spi , t ) ;
while ( len ) {
/* send the data */
len - = bcm63xx_txrx_bufs ( spi , t ) ;
timeout = wait_for_completion_timeout ( & bs - > done , HZ ) ;
if ( ! timeout ) {
status = - ETIMEDOUT ;
goto exit ;
}
/* read out all data */
rx_tail = bcm_spi_readb ( bs , SPI_RX_TAIL ) ;
/* Read out all the data */
if ( rx_tail )
memcpy_fromio ( bs - > rx_ptr , bs - > rx_io , rx_tail ) ;
}
2012-02-01 14:14:09 +04:00
2012-04-20 17:37:33 +04:00
m - > actual_length + = t - > len ;
}
exit :
m - > status = status ;
spi_finalize_current_message ( master ) ;
2012-02-01 14:14:09 +04:00
2012-04-20 17:37:33 +04:00
return 0 ;
2012-02-01 14:14:09 +04:00
}
/* This driver supports single master mode only. Hence
* CMD_DONE is the only interrupt we care about
*/
static irqreturn_t bcm63xx_spi_interrupt ( int irq , void * dev_id )
{
struct spi_master * master = ( struct spi_master * ) dev_id ;
struct bcm63xx_spi * bs = spi_master_get_devdata ( master ) ;
u8 intr ;
/* Read interupts and clear them immediately */
intr = bcm_spi_readb ( bs , SPI_INT_STATUS ) ;
bcm_spi_writeb ( bs , SPI_INTR_CLEAR_ALL , SPI_INT_STATUS ) ;
bcm_spi_writeb ( bs , 0 , SPI_INT_MASK ) ;
2012-04-20 17:37:33 +04:00
/* A transfer completed */
if ( intr & SPI_INTR_CMD_DONE )
complete ( & bs - > done ) ;
2012-02-01 14:14:09 +04:00
return IRQ_HANDLED ;
}
2012-12-07 20:57:14 +04:00
static int bcm63xx_spi_probe ( struct platform_device * pdev )
2012-02-01 14:14:09 +04:00
{
struct resource * r ;
struct device * dev = & pdev - > dev ;
struct bcm63xx_spi_pdata * pdata = pdev - > dev . platform_data ;
int irq ;
struct spi_master * master ;
struct clk * clk ;
struct bcm63xx_spi * bs ;
int ret ;
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! r ) {
dev_err ( dev , " no iomem \n " ) ;
ret = - ENXIO ;
goto out ;
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
dev_err ( dev , " no irq \n " ) ;
ret = - ENXIO ;
goto out ;
}
clk = clk_get ( dev , " spi " ) ;
if ( IS_ERR ( clk ) ) {
dev_err ( dev , " no clock for device \n " ) ;
ret = PTR_ERR ( clk ) ;
goto out ;
}
master = spi_alloc_master ( dev , sizeof ( * bs ) ) ;
if ( ! master ) {
dev_err ( dev , " out of memory \n " ) ;
ret = - ENOMEM ;
goto out_clk ;
}
bs = spi_master_get_devdata ( master ) ;
platform_set_drvdata ( pdev , master ) ;
bs - > pdev = pdev ;
if ( ! devm_request_mem_region ( & pdev - > dev , r - > start ,
resource_size ( r ) , PFX ) ) {
dev_err ( dev , " iomem request failed \n " ) ;
ret = - ENXIO ;
goto out_err ;
}
bs - > regs = devm_ioremap_nocache ( & pdev - > dev , r - > start ,
resource_size ( r ) ) ;
if ( ! bs - > regs ) {
dev_err ( dev , " unable to ioremap regs \n " ) ;
ret = - ENOMEM ;
goto out_err ;
}
bs - > irq = irq ;
bs - > clk = clk ;
bs - > fifo_size = pdata - > fifo_size ;
ret = devm_request_irq ( & pdev - > dev , irq , bcm63xx_spi_interrupt , 0 ,
pdev - > name , master ) ;
if ( ret ) {
dev_err ( dev , " unable to request irq \n " ) ;
goto out_err ;
}
master - > bus_num = pdata - > bus_num ;
master - > num_chipselect = pdata - > num_chipselect ;
master - > setup = bcm63xx_spi_setup ;
2012-04-20 17:37:33 +04:00
master - > prepare_transfer_hardware = bcm63xx_spi_prepare_transfer ;
master - > unprepare_transfer_hardware = bcm63xx_spi_unprepare_transfer ;
master - > transfer_one_message = bcm63xx_spi_transfer_one ;
2012-04-20 17:37:35 +04:00
master - > mode_bits = MODEBITS ;
2012-02-01 14:14:09 +04:00
bs - > speed_hz = pdata - > speed_hz ;
2012-06-18 14:07:51 +04:00
bs - > msg_type_shift = pdata - > msg_type_shift ;
bs - > msg_ctl_width = pdata - > msg_ctl_width ;
2012-02-01 14:14:09 +04:00
bs - > tx_io = ( u8 * ) ( bs - > regs + bcm63xx_spireg ( SPI_MSG_DATA ) ) ;
bs - > rx_io = ( const u8 * ) ( bs - > regs + bcm63xx_spireg ( SPI_RX_DATA ) ) ;
2012-06-18 14:07:51 +04:00
switch ( bs - > msg_ctl_width ) {
case 8 :
case 16 :
break ;
default :
dev_err ( dev , " unsupported MSG_CTL width: %d \n " ,
bs - > msg_ctl_width ) ;
goto out_clk_disable ;
}
2012-02-01 14:14:09 +04:00
/* Initialize hardware */
clk_enable ( bs - > clk ) ;
bcm_spi_writeb ( bs , SPI_INTR_CLEAR_ALL , SPI_INT_STATUS ) ;
/* register and we are done */
ret = spi_register_master ( master ) ;
if ( ret ) {
dev_err ( dev , " spi register failed \n " ) ;
goto out_clk_disable ;
}
2012-10-03 13:56:53 +04:00
dev_info ( dev , " at 0x%08x (irq %d, FIFOs size %d) \n " ,
r - > start , irq , bs - > fifo_size ) ;
2012-02-01 14:14:09 +04:00
return 0 ;
out_clk_disable :
clk_disable ( clk ) ;
out_err :
platform_set_drvdata ( pdev , NULL ) ;
spi_master_put ( master ) ;
out_clk :
clk_put ( clk ) ;
out :
return ret ;
}
2012-12-07 20:57:14 +04:00
static int bcm63xx_spi_remove ( struct platform_device * pdev )
2012-02-01 14:14:09 +04:00
{
2012-08-11 00:56:27 +04:00
struct spi_master * master = spi_master_get ( platform_get_drvdata ( pdev ) ) ;
2012-02-01 14:14:09 +04:00
struct bcm63xx_spi * bs = spi_master_get_devdata ( master ) ;
2012-04-20 17:37:34 +04:00
spi_unregister_master ( master ) ;
2012-02-01 14:14:09 +04:00
/* reset spi block */
bcm_spi_writeb ( bs , 0 , SPI_INT_MASK ) ;
/* HW shutdown */
clk_disable ( bs - > clk ) ;
clk_put ( bs - > clk ) ;
platform_set_drvdata ( pdev , 0 ) ;
2012-08-11 00:56:27 +04:00
spi_master_put ( master ) ;
2012-02-01 14:14:09 +04:00
return 0 ;
}
# ifdef CONFIG_PM
static int bcm63xx_spi_suspend ( struct device * dev )
{
struct spi_master * master =
platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct bcm63xx_spi * bs = spi_master_get_devdata ( master ) ;
2012-10-03 13:56:54 +04:00
spi_master_suspend ( master ) ;
2012-02-01 14:14:09 +04:00
clk_disable ( bs - > clk ) ;
return 0 ;
}
static int bcm63xx_spi_resume ( struct device * dev )
{
struct spi_master * master =
platform_get_drvdata ( to_platform_device ( dev ) ) ;
struct bcm63xx_spi * bs = spi_master_get_devdata ( master ) ;
clk_enable ( bs - > clk ) ;
2012-10-03 13:56:54 +04:00
spi_master_resume ( master ) ;
2012-02-01 14:14:09 +04:00
return 0 ;
}
static const struct dev_pm_ops bcm63xx_spi_pm_ops = {
. suspend = bcm63xx_spi_suspend ,
. resume = bcm63xx_spi_resume ,
} ;
# define BCM63XX_SPI_PM_OPS (&bcm63xx_spi_pm_ops)
# else
# define BCM63XX_SPI_PM_OPS NULL
# endif
static struct platform_driver bcm63xx_spi_driver = {
. driver = {
. name = " bcm63xx-spi " ,
. owner = THIS_MODULE ,
. pm = BCM63XX_SPI_PM_OPS ,
} ,
. probe = bcm63xx_spi_probe ,
2012-12-07 20:57:14 +04:00
. remove = bcm63xx_spi_remove ,
2012-02-01 14:14:09 +04:00
} ;
module_platform_driver ( bcm63xx_spi_driver ) ;
MODULE_ALIAS ( " platform:bcm63xx_spi " ) ;
MODULE_AUTHOR ( " Florian Fainelli <florian@openwrt.org> " ) ;
MODULE_AUTHOR ( " Tanguy Bouzeloc <tanguy.bouzeloc@efixo.com> " ) ;
MODULE_DESCRIPTION ( " Broadcom BCM63xx SPI Controller driver " ) ;
MODULE_LICENSE ( " GPL " ) ;