2012-08-22 23:25:07 +04:00
/*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
* Copyright ( C ) 2011 , 2012 Cavium , Inc .
*/
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/spi/spi.h>
# include <linux/module.h>
# include <linux/delay.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/of.h>
# include <asm/octeon/octeon.h>
# include <asm/octeon/cvmx-mpi-defs.h>
# define OCTEON_SPI_CFG 0
# define OCTEON_SPI_STS 0x08
# define OCTEON_SPI_TX 0x10
# define OCTEON_SPI_DAT0 0x80
# define OCTEON_SPI_MAX_BYTES 9
# define OCTEON_SPI_MAX_CLOCK_HZ 16000000
struct octeon_spi {
u64 register_base ;
u64 last_cfg ;
u64 cs_enax ;
} ;
struct octeon_spi_setup {
u32 max_speed_hz ;
u8 chip_select ;
u8 mode ;
u8 bits_per_word ;
} ;
static void octeon_spi_wait_ready ( struct octeon_spi * p )
{
union cvmx_mpi_sts mpi_sts ;
unsigned int loops = 0 ;
do {
if ( loops + + )
__delay ( 500 ) ;
mpi_sts . u64 = cvmx_read_csr ( p - > register_base + OCTEON_SPI_STS ) ;
} while ( mpi_sts . s . busy ) ;
}
static int octeon_spi_do_transfer ( struct octeon_spi * p ,
struct spi_message * msg ,
struct spi_transfer * xfer ,
bool last_xfer )
{
union cvmx_mpi_cfg mpi_cfg ;
union cvmx_mpi_tx mpi_tx ;
unsigned int clkdiv ;
unsigned int speed_hz ;
int mode ;
bool cpha , cpol ;
const u8 * tx_buf ;
u8 * rx_buf ;
int len ;
int i ;
struct octeon_spi_setup * msg_setup = spi_get_ctldata ( msg - > spi ) ;
speed_hz = msg_setup - > max_speed_hz ;
mode = msg_setup - > mode ;
cpha = mode & SPI_CPHA ;
cpol = mode & SPI_CPOL ;
if ( xfer - > speed_hz )
speed_hz = xfer - > speed_hz ;
if ( speed_hz > OCTEON_SPI_MAX_CLOCK_HZ )
speed_hz = OCTEON_SPI_MAX_CLOCK_HZ ;
clkdiv = octeon_get_io_clock_rate ( ) / ( 2 * speed_hz ) ;
mpi_cfg . u64 = 0 ;
mpi_cfg . s . clkdiv = clkdiv ;
mpi_cfg . s . cshi = ( mode & SPI_CS_HIGH ) ? 1 : 0 ;
mpi_cfg . s . lsbfirst = ( mode & SPI_LSB_FIRST ) ? 1 : 0 ;
mpi_cfg . s . wireor = ( mode & SPI_3WIRE ) ? 1 : 0 ;
mpi_cfg . s . idlelo = cpha ! = cpol ;
mpi_cfg . s . cslate = cpha ? 1 : 0 ;
mpi_cfg . s . enable = 1 ;
if ( msg_setup - > chip_select < 4 )
p - > cs_enax | = 1ull < < ( 12 + msg_setup - > chip_select ) ;
mpi_cfg . u64 | = p - > cs_enax ;
if ( mpi_cfg . u64 ! = p - > last_cfg ) {
p - > last_cfg = mpi_cfg . u64 ;
cvmx_write_csr ( p - > register_base + OCTEON_SPI_CFG , mpi_cfg . u64 ) ;
}
tx_buf = xfer - > tx_buf ;
rx_buf = xfer - > rx_buf ;
len = xfer - > len ;
while ( len > OCTEON_SPI_MAX_BYTES ) {
for ( i = 0 ; i < OCTEON_SPI_MAX_BYTES ; i + + ) {
u8 d ;
if ( tx_buf )
d = * tx_buf + + ;
else
d = 0 ;
cvmx_write_csr ( p - > register_base + OCTEON_SPI_DAT0 + ( 8 * i ) , d ) ;
}
mpi_tx . u64 = 0 ;
mpi_tx . s . csid = msg_setup - > chip_select ;
mpi_tx . s . leavecs = 1 ;
mpi_tx . s . txnum = tx_buf ? OCTEON_SPI_MAX_BYTES : 0 ;
mpi_tx . s . totnum = OCTEON_SPI_MAX_BYTES ;
cvmx_write_csr ( p - > register_base + OCTEON_SPI_TX , mpi_tx . u64 ) ;
octeon_spi_wait_ready ( p ) ;
if ( rx_buf )
for ( i = 0 ; i < OCTEON_SPI_MAX_BYTES ; i + + ) {
u64 v = cvmx_read_csr ( p - > register_base + OCTEON_SPI_DAT0 + ( 8 * i ) ) ;
* rx_buf + + = ( u8 ) v ;
}
len - = OCTEON_SPI_MAX_BYTES ;
}
for ( i = 0 ; i < len ; i + + ) {
u8 d ;
if ( tx_buf )
d = * tx_buf + + ;
else
d = 0 ;
cvmx_write_csr ( p - > register_base + OCTEON_SPI_DAT0 + ( 8 * i ) , d ) ;
}
mpi_tx . u64 = 0 ;
mpi_tx . s . csid = msg_setup - > chip_select ;
if ( last_xfer )
mpi_tx . s . leavecs = xfer - > cs_change ;
else
mpi_tx . s . leavecs = ! xfer - > cs_change ;
mpi_tx . s . txnum = tx_buf ? len : 0 ;
mpi_tx . s . totnum = len ;
cvmx_write_csr ( p - > register_base + OCTEON_SPI_TX , mpi_tx . u64 ) ;
octeon_spi_wait_ready ( p ) ;
if ( rx_buf )
for ( i = 0 ; i < len ; i + + ) {
u64 v = cvmx_read_csr ( p - > register_base + OCTEON_SPI_DAT0 + ( 8 * i ) ) ;
* rx_buf + + = ( u8 ) v ;
}
if ( xfer - > delay_usecs )
udelay ( xfer - > delay_usecs ) ;
return xfer - > len ;
}
static int octeon_spi_transfer_one_message ( struct spi_master * master ,
struct spi_message * msg )
{
struct octeon_spi * p = spi_master_get_devdata ( master ) ;
unsigned int total_len = 0 ;
int status = 0 ;
struct spi_transfer * xfer ;
/*
* We better have set the configuration via a call to . setup
* before we get here .
*/
if ( spi_get_ctldata ( msg - > spi ) = = NULL ) {
status = - EINVAL ;
goto err ;
}
list_for_each_entry ( xfer , & msg - > transfers , transfer_list ) {
bool last_xfer = & xfer - > transfer_list = = msg - > transfers . prev ;
int r = octeon_spi_do_transfer ( p , msg , xfer , last_xfer ) ;
if ( r < 0 ) {
status = r ;
goto err ;
}
total_len + = r ;
}
err :
msg - > status = status ;
msg - > actual_length = total_len ;
spi_finalize_current_message ( master ) ;
return status ;
}
static struct octeon_spi_setup * octeon_spi_new_setup ( struct spi_device * spi )
{
struct octeon_spi_setup * setup = kzalloc ( sizeof ( * setup ) , GFP_KERNEL ) ;
if ( ! setup )
return NULL ;
setup - > max_speed_hz = spi - > max_speed_hz ;
setup - > chip_select = spi - > chip_select ;
setup - > mode = spi - > mode ;
setup - > bits_per_word = spi - > bits_per_word ;
return setup ;
}
static int octeon_spi_setup ( struct spi_device * spi )
{
struct octeon_spi_setup * new_setup ;
struct octeon_spi_setup * old_setup = spi_get_ctldata ( spi ) ;
new_setup = octeon_spi_new_setup ( spi ) ;
if ( ! new_setup )
return - ENOMEM ;
spi_set_ctldata ( spi , new_setup ) ;
kfree ( old_setup ) ;
return 0 ;
}
static void octeon_spi_cleanup ( struct spi_device * spi )
{
struct octeon_spi_setup * old_setup = spi_get_ctldata ( spi ) ;
spi_set_ctldata ( spi , NULL ) ;
kfree ( old_setup ) ;
}
2012-12-07 20:57:14 +04:00
static int octeon_spi_probe ( struct platform_device * pdev )
2012-08-22 23:25:07 +04:00
{
struct resource * res_mem ;
struct spi_master * master ;
struct octeon_spi * p ;
int err = - ENOENT ;
master = spi_alloc_master ( & pdev - > dev , sizeof ( struct octeon_spi ) ) ;
if ( ! master )
return - ENOMEM ;
p = spi_master_get_devdata ( master ) ;
2013-08-05 11:53:32 +04:00
platform_set_drvdata ( pdev , master ) ;
2012-08-22 23:25:07 +04:00
res_mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( res_mem = = NULL ) {
dev_err ( & pdev - > dev , " found no memory resource \n " ) ;
err = - ENXIO ;
goto fail ;
}
if ( ! devm_request_mem_region ( & pdev - > dev , res_mem - > start ,
resource_size ( res_mem ) , res_mem - > name ) ) {
dev_err ( & pdev - > dev , " request_mem_region failed \n " ) ;
goto fail ;
}
p - > register_base = ( u64 ) devm_ioremap ( & pdev - > dev , res_mem - > start ,
resource_size ( res_mem ) ) ;
/* Dynamic bus numbering */
master - > bus_num = - 1 ;
master - > num_chipselect = 4 ;
master - > mode_bits = SPI_CPHA |
SPI_CPOL |
SPI_CS_HIGH |
SPI_LSB_FIRST |
SPI_3WIRE ;
master - > setup = octeon_spi_setup ;
master - > cleanup = octeon_spi_cleanup ;
master - > transfer_one_message = octeon_spi_transfer_one_message ;
2013-08-11 19:09:43 +04:00
master - > bits_per_word_mask = SPI_BPW_MASK ( 8 ) ;
2012-08-22 23:25:07 +04:00
master - > dev . of_node = pdev - > dev . of_node ;
2013-09-24 08:34:24 +04:00
err = devm_spi_register_master ( & pdev - > dev , master ) ;
2012-08-22 23:25:07 +04:00
if ( err ) {
dev_err ( & pdev - > dev , " register master failed: %d \n " , err ) ;
goto fail ;
}
dev_info ( & pdev - > dev , " OCTEON SPI bus driver \n " ) ;
return 0 ;
fail :
spi_master_put ( master ) ;
return err ;
}
2012-12-07 20:57:14 +04:00
static int octeon_spi_remove ( struct platform_device * pdev )
2012-08-22 23:25:07 +04:00
{
2013-08-05 11:53:32 +04:00
struct spi_master * master = platform_get_drvdata ( pdev ) ;
struct octeon_spi * p = spi_master_get_devdata ( master ) ;
2012-08-22 23:25:07 +04:00
u64 register_base = p - > register_base ;
/* Clear the CSENA* and put everything in a known state. */
cvmx_write_csr ( register_base + OCTEON_SPI_CFG , 0 ) ;
return 0 ;
}
static struct of_device_id octeon_spi_match [ ] = {
{ . compatible = " cavium,octeon-3010-spi " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , octeon_spi_match ) ;
static struct platform_driver octeon_spi_driver = {
. driver = {
. name = " spi-octeon " ,
. owner = THIS_MODULE ,
. of_match_table = octeon_spi_match ,
} ,
. probe = octeon_spi_probe ,
2012-12-07 20:57:14 +04:00
. remove = octeon_spi_remove ,
2012-08-22 23:25:07 +04:00
} ;
module_platform_driver ( octeon_spi_driver ) ;
MODULE_DESCRIPTION ( " Cavium, Inc. OCTEON SPI bus driver " ) ;
MODULE_AUTHOR ( " David Daney " ) ;
MODULE_LICENSE ( " GPL " ) ;