2019-05-19 15:08:20 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2016-08-04 07:30:37 +03:00
/*
* J - Core SPI controller driver
*
* Copyright ( C ) 2012 - 2016 Smart Energy Instruments , Inc .
*
* Current version by Rich Felker
* Based loosely on initial version by Oleksandr G Zhadan
*
*/
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/errno.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/spi/spi.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/of.h>
# include <linux/delay.h>
# define DRV_NAME "jcore_spi"
# define CTRL_REG 0x0
# define DATA_REG 0x4
# define JCORE_SPI_CTRL_XMIT 0x02
# define JCORE_SPI_STAT_BUSY 0x02
# define JCORE_SPI_CTRL_LOOP 0x08
# define JCORE_SPI_CTRL_CS_BITS 0x15
# define JCORE_SPI_WAIT_RDY_MAX_LOOP 2000000
struct jcore_spi {
struct spi_master * master ;
void __iomem * base ;
unsigned int cs_reg ;
unsigned int speed_reg ;
unsigned int speed_hz ;
unsigned int clock_freq ;
} ;
static int jcore_spi_wait ( void __iomem * ctrl_reg )
{
unsigned timeout = JCORE_SPI_WAIT_RDY_MAX_LOOP ;
do {
if ( ! ( readl ( ctrl_reg ) & JCORE_SPI_STAT_BUSY ) )
return 0 ;
cpu_relax ( ) ;
} while ( - - timeout ) ;
return - EBUSY ;
}
static void jcore_spi_program ( struct jcore_spi * hw )
{
void __iomem * ctrl_reg = hw - > base + CTRL_REG ;
if ( jcore_spi_wait ( ctrl_reg ) )
dev_err ( hw - > master - > dev . parent ,
" timeout waiting to program ctrl reg. \n " ) ;
writel ( hw - > cs_reg | hw - > speed_reg , ctrl_reg ) ;
}
static void jcore_spi_chipsel ( struct spi_device * spi , bool value )
{
struct jcore_spi * hw = spi_master_get_devdata ( spi - > master ) ;
u32 csbit = 1U < < ( 2 * spi - > chip_select ) ;
dev_dbg ( hw - > master - > dev . parent , " chipselect %d \n " , spi - > chip_select ) ;
if ( value )
hw - > cs_reg | = csbit ;
else
hw - > cs_reg & = ~ csbit ;
jcore_spi_program ( hw ) ;
}
static void jcore_spi_baudrate ( struct jcore_spi * hw , int speed )
{
2021-03-24 09:16:39 +03:00
if ( speed = = hw - > speed_hz )
return ;
2016-08-04 07:30:37 +03:00
hw - > speed_hz = speed ;
if ( speed > = hw - > clock_freq / 2 )
hw - > speed_reg = 0 ;
else
hw - > speed_reg = ( ( hw - > clock_freq / 2 / speed ) - 1 ) < < 27 ;
jcore_spi_program ( hw ) ;
dev_dbg ( hw - > master - > dev . parent , " speed=%d reg=0x%x \n " ,
speed , hw - > speed_reg ) ;
}
static int jcore_spi_txrx ( struct spi_master * master , struct spi_device * spi ,
struct spi_transfer * t )
{
struct jcore_spi * hw = spi_master_get_devdata ( master ) ;
void __iomem * ctrl_reg = hw - > base + CTRL_REG ;
void __iomem * data_reg = hw - > base + DATA_REG ;
u32 xmit ;
/* data buffers */
const unsigned char * tx ;
unsigned char * rx ;
unsigned int len ;
unsigned int count ;
jcore_spi_baudrate ( hw , t - > speed_hz ) ;
xmit = hw - > cs_reg | hw - > speed_reg | JCORE_SPI_CTRL_XMIT ;
tx = t - > tx_buf ;
rx = t - > rx_buf ;
len = t - > len ;
for ( count = 0 ; count < len ; count + + ) {
if ( jcore_spi_wait ( ctrl_reg ) )
break ;
writel ( tx ? * tx + + : 0 , data_reg ) ;
writel ( xmit , ctrl_reg ) ;
if ( jcore_spi_wait ( ctrl_reg ) )
break ;
if ( rx )
* rx + + = readl ( data_reg ) ;
}
spi_finalize_current_transfer ( master ) ;
if ( count < len )
return - EREMOTEIO ;
return 0 ;
}
static int jcore_spi_probe ( struct platform_device * pdev )
{
struct device_node * node = pdev - > dev . of_node ;
struct jcore_spi * hw ;
struct spi_master * master ;
struct resource * res ;
u32 clock_freq ;
struct clk * clk ;
int err = - ENODEV ;
master = spi_alloc_master ( & pdev - > dev , sizeof ( struct jcore_spi ) ) ;
if ( ! master )
return err ;
/* Setup the master state. */
master - > num_chipselect = 3 ;
master - > mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH ;
master - > transfer_one = jcore_spi_txrx ;
master - > set_cs = jcore_spi_chipsel ;
master - > dev . of_node = node ;
master - > bus_num = pdev - > id ;
hw = spi_master_get_devdata ( master ) ;
hw - > master = master ;
platform_set_drvdata ( pdev , hw ) ;
/* Find and map our resources */
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res )
goto exit_busy ;
if ( ! devm_request_mem_region ( & pdev - > dev , res - > start ,
resource_size ( res ) , pdev - > name ) )
goto exit_busy ;
2020-01-06 11:43:50 +03:00
hw - > base = devm_ioremap ( & pdev - > dev , res - > start ,
2016-08-04 07:30:37 +03:00
resource_size ( res ) ) ;
if ( ! hw - > base )
goto exit_busy ;
/*
* The SPI clock rate controlled via a configurable clock divider
* which is applied to the reference clock . A 50 MHz reference is
* most suitable for obtaining standard SPI clock rates , but some
* designs may have a different reference clock , and the DT must
* make the driver aware so that it can properly program the
* requested rate . If the clock is omitted , 50 MHz is assumed .
*/
clock_freq = 50000000 ;
clk = devm_clk_get ( & pdev - > dev , " ref_clk " ) ;
2018-03-17 13:05:44 +03:00
if ( ! IS_ERR ( clk ) ) {
if ( clk_prepare_enable ( clk ) = = 0 ) {
2016-08-04 07:30:37 +03:00
clock_freq = clk_get_rate ( clk ) ;
2018-03-17 13:05:44 +03:00
clk_disable_unprepare ( clk ) ;
} else
2016-08-04 07:30:37 +03:00
dev_warn ( & pdev - > dev , " could not enable ref_clk \n " ) ;
}
hw - > clock_freq = clock_freq ;
/* Initialize all CS bits to high. */
hw - > cs_reg = JCORE_SPI_CTRL_CS_BITS ;
jcore_spi_baudrate ( hw , 400000 ) ;
/* Register our spi controller */
err = devm_spi_register_master ( & pdev - > dev , master ) ;
2018-03-17 13:05:44 +03:00
if ( err )
2016-08-04 07:30:37 +03:00
goto exit ;
return 0 ;
exit_busy :
err = - EBUSY ;
exit :
spi_master_put ( master ) ;
return err ;
}
static const struct of_device_id jcore_spi_of_match [ ] = {
{ . compatible = " jcore,spi2 " } ,
{ } ,
} ;
2016-11-23 19:37:09 +03:00
MODULE_DEVICE_TABLE ( of , jcore_spi_of_match ) ;
2016-08-04 07:30:37 +03:00
static struct platform_driver jcore_spi_driver = {
. probe = jcore_spi_probe ,
. driver = {
. name = DRV_NAME ,
. of_match_table = jcore_spi_of_match ,
} ,
} ;
module_platform_driver ( jcore_spi_driver ) ;
MODULE_DESCRIPTION ( " J-Core SPI driver " ) ;
MODULE_AUTHOR ( " Rich Felker <dalias@libc.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;