2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-03-12 21:55:24 +04:00
/*
* Xtensa xtfpga SPI controller driver
*
* Copyright ( c ) 2014 Cadence Design Systems Inc .
*/
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/spi/spi.h>
# include <linux/spi/spi_bitbang.h>
# define XTFPGA_SPI_NAME "xtfpga_spi"
# define XTFPGA_SPI_START 0x0
# define XTFPGA_SPI_BUSY 0x4
# define XTFPGA_SPI_DATA 0x8
# define BUSY_WAIT_US 100
struct xtfpga_spi {
struct spi_bitbang bitbang ;
void __iomem * regs ;
u32 data ;
unsigned data_sz ;
} ;
static inline void xtfpga_spi_write32 ( const struct xtfpga_spi * spi ,
unsigned addr , u32 val )
{
2015-09-22 14:32:03 +03:00
__raw_writel ( val , spi - > regs + addr ) ;
2014-03-12 21:55:24 +04:00
}
static inline unsigned int xtfpga_spi_read32 ( const struct xtfpga_spi * spi ,
unsigned addr )
{
2015-09-22 14:32:03 +03:00
return __raw_readl ( spi - > regs + addr ) ;
2014-03-12 21:55:24 +04:00
}
static inline void xtfpga_spi_wait_busy ( struct xtfpga_spi * xspi )
{
unsigned i ;
2014-09-02 06:54:37 +04:00
2014-03-12 21:55:24 +04:00
for ( i = 0 ; xtfpga_spi_read32 ( xspi , XTFPGA_SPI_BUSY ) & &
i < BUSY_WAIT_US ; + + i )
udelay ( 1 ) ;
WARN_ON_ONCE ( i = = BUSY_WAIT_US ) ;
}
static u32 xtfpga_spi_txrx_word ( struct spi_device * spi , unsigned nsecs ,
2018-07-28 11:19:13 +03:00
u32 v , u8 bits , unsigned flags )
2014-03-12 21:55:24 +04:00
{
struct xtfpga_spi * xspi = spi_master_get_devdata ( spi - > master ) ;
xspi - > data = ( xspi - > data < < bits ) | ( v & GENMASK ( bits - 1 , 0 ) ) ;
xspi - > data_sz + = bits ;
if ( xspi - > data_sz > = 16 ) {
xtfpga_spi_write32 ( xspi , XTFPGA_SPI_DATA ,
xspi - > data > > ( xspi - > data_sz - 16 ) ) ;
xspi - > data_sz - = 16 ;
xtfpga_spi_write32 ( xspi , XTFPGA_SPI_START , 1 ) ;
xtfpga_spi_wait_busy ( xspi ) ;
xtfpga_spi_write32 ( xspi , XTFPGA_SPI_START , 0 ) ;
}
return 0 ;
}
static void xtfpga_spi_chipselect ( struct spi_device * spi , int is_on )
{
struct xtfpga_spi * xspi = spi_master_get_devdata ( spi - > master ) ;
WARN_ON ( xspi - > data_sz ! = 0 ) ;
xspi - > data_sz = 0 ;
}
static int xtfpga_spi_probe ( struct platform_device * pdev )
{
struct xtfpga_spi * xspi ;
int ret ;
struct spi_master * master ;
master = spi_alloc_master ( & pdev - > dev , sizeof ( struct xtfpga_spi ) ) ;
if ( ! master )
return - ENOMEM ;
master - > flags = SPI_MASTER_NO_RX ;
master - > bits_per_word_mask = SPI_BPW_RANGE_MASK ( 1 , 16 ) ;
master - > bus_num = pdev - > dev . id ;
master - > dev . of_node = pdev - > dev . of_node ;
xspi = spi_master_get_devdata ( master ) ;
xspi - > bitbang . master = master ;
xspi - > bitbang . chipselect = xtfpga_spi_chipselect ;
xspi - > bitbang . txrx_word [ SPI_MODE_0 ] = xtfpga_spi_txrx_word ;
2019-09-21 16:35:08 +03:00
xspi - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2014-03-12 21:55:24 +04:00
if ( IS_ERR ( xspi - > regs ) ) {
ret = PTR_ERR ( xspi - > regs ) ;
goto err ;
}
xtfpga_spi_write32 ( xspi , XTFPGA_SPI_START , 0 ) ;
usleep_range ( 1000 , 2000 ) ;
if ( xtfpga_spi_read32 ( xspi , XTFPGA_SPI_BUSY ) ) {
dev_err ( & pdev - > dev , " Device stuck in busy state \n " ) ;
ret = - EBUSY ;
goto err ;
}
ret = spi_bitbang_start ( & xspi - > bitbang ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " spi_bitbang_start failed \n " ) ;
goto err ;
}
platform_set_drvdata ( pdev , master ) ;
return 0 ;
err :
spi_master_put ( master ) ;
return ret ;
}
static int xtfpga_spi_remove ( struct platform_device * pdev )
{
struct spi_master * master = platform_get_drvdata ( pdev ) ;
struct xtfpga_spi * xspi = spi_master_get_devdata ( master ) ;
spi_bitbang_stop ( & xspi - > bitbang ) ;
spi_master_put ( master ) ;
return 0 ;
}
MODULE_ALIAS ( " platform: " XTFPGA_SPI_NAME ) ;
# ifdef CONFIG_OF
static const struct of_device_id xtfpga_spi_of_match [ ] = {
{ . compatible = " cdns,xtfpga-spi " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , xtfpga_spi_of_match ) ;
# endif
static struct platform_driver xtfpga_spi_driver = {
. probe = xtfpga_spi_probe ,
. remove = xtfpga_spi_remove ,
. driver = {
. name = XTFPGA_SPI_NAME ,
. of_match_table = of_match_ptr ( xtfpga_spi_of_match ) ,
} ,
} ;
module_platform_driver ( xtfpga_spi_driver ) ;
MODULE_AUTHOR ( " Max Filippov <jcmvbkbc@gmail.com> " ) ;
MODULE_DESCRIPTION ( " xtensa xtfpga SPI driver " ) ;
MODULE_LICENSE ( " GPL " ) ;