2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2011-02-14 05:20:39 +03:00
/*
* OpenCores tiny SPI master driver
*
2020-07-08 22:44:00 +03:00
* https : //opencores.org/project,tiny_spi
2011-02-14 05:20:39 +03:00
*
* Copyright ( C ) 2011 Thomas Chou < thomas @ wytron . com . tw >
*
* Based on spi_s3c24xx . c , which is :
* Copyright ( c ) 2006 Ben Dooks
* Copyright ( c ) 2006 Simtec Electronics
* Ben Dooks < ben @ simtec . co . uk >
*/
# include <linux/interrupt.h>
# include <linux/errno.h>
2011-07-03 23:44:29 +04:00
# include <linux/module.h>
2011-02-14 05:20:39 +03:00
# include <linux/platform_device.h>
# include <linux/spi/spi.h>
# include <linux/spi/spi_bitbang.h>
# include <linux/spi/spi_oc_tiny.h>
# include <linux/io.h>
# include <linux/of.h>
# define DRV_NAME "spi_oc_tiny"
# define TINY_SPI_RXDATA 0
# define TINY_SPI_TXDATA 4
# define TINY_SPI_STATUS 8
# define TINY_SPI_CONTROL 12
# define TINY_SPI_BAUD 16
# define TINY_SPI_STATUS_TXE 0x1
# define TINY_SPI_STATUS_TXR 0x2
struct tiny_spi {
/* bitbang has to be first */
struct spi_bitbang bitbang ;
struct completion done ;
void __iomem * base ;
int irq ;
unsigned int freq ;
unsigned int baudwidth ;
unsigned int baud ;
unsigned int speed_hz ;
unsigned int mode ;
unsigned int len ;
unsigned int txc , rxc ;
const u8 * txp ;
u8 * rxp ;
} ;
static inline struct tiny_spi * tiny_spi_to_hw ( struct spi_device * sdev )
{
return spi_master_get_devdata ( sdev - > master ) ;
}
static unsigned int tiny_spi_baud ( struct spi_device * spi , unsigned int hz )
{
struct tiny_spi * hw = tiny_spi_to_hw ( spi ) ;
return min ( DIV_ROUND_UP ( hw - > freq , hz * 2 ) , ( 1U < < hw - > baudwidth ) ) - 1 ;
}
static int tiny_spi_setup_transfer ( struct spi_device * spi ,
struct spi_transfer * t )
{
struct tiny_spi * hw = tiny_spi_to_hw ( spi ) ;
unsigned int baud = hw - > baud ;
if ( t ) {
if ( t - > speed_hz & & t - > speed_hz ! = hw - > speed_hz )
baud = tiny_spi_baud ( spi , t - > speed_hz ) ;
}
writel ( baud , hw - > base + TINY_SPI_BAUD ) ;
writel ( hw - > mode , hw - > base + TINY_SPI_CONTROL ) ;
return 0 ;
}
static int tiny_spi_setup ( struct spi_device * spi )
{
struct tiny_spi * hw = tiny_spi_to_hw ( spi ) ;
if ( spi - > max_speed_hz ! = hw - > speed_hz ) {
hw - > speed_hz = spi - > max_speed_hz ;
hw - > baud = tiny_spi_baud ( spi , hw - > speed_hz ) ;
}
2021-05-10 16:12:14 +03:00
hw - > mode = spi - > mode & SPI_MODE_X_MASK ;
2011-02-14 05:20:39 +03:00
return 0 ;
}
static inline void tiny_spi_wait_txr ( struct tiny_spi * hw )
{
while ( ! ( readb ( hw - > base + TINY_SPI_STATUS ) &
TINY_SPI_STATUS_TXR ) )
cpu_relax ( ) ;
}
static inline void tiny_spi_wait_txe ( struct tiny_spi * hw )
{
while ( ! ( readb ( hw - > base + TINY_SPI_STATUS ) &
TINY_SPI_STATUS_TXE ) )
cpu_relax ( ) ;
}
static int tiny_spi_txrx_bufs ( struct spi_device * spi , struct spi_transfer * t )
{
struct tiny_spi * hw = tiny_spi_to_hw ( spi ) ;
const u8 * txp = t - > tx_buf ;
u8 * rxp = t - > rx_buf ;
unsigned int i ;
if ( hw - > irq > = 0 ) {
2012-07-30 18:16:14 +04:00
/* use interrupt driven data transfer */
2011-02-14 05:20:39 +03:00
hw - > len = t - > len ;
hw - > txp = t - > tx_buf ;
hw - > rxp = t - > rx_buf ;
hw - > txc = 0 ;
hw - > rxc = 0 ;
/* send the first byte */
if ( t - > len > 1 ) {
writeb ( hw - > txp ? * hw - > txp + + : 0 ,
hw - > base + TINY_SPI_TXDATA ) ;
hw - > txc + + ;
writeb ( hw - > txp ? * hw - > txp + + : 0 ,
hw - > base + TINY_SPI_TXDATA ) ;
hw - > txc + + ;
writeb ( TINY_SPI_STATUS_TXR , hw - > base + TINY_SPI_STATUS ) ;
} else {
writeb ( hw - > txp ? * hw - > txp + + : 0 ,
hw - > base + TINY_SPI_TXDATA ) ;
hw - > txc + + ;
writeb ( TINY_SPI_STATUS_TXE , hw - > base + TINY_SPI_STATUS ) ;
}
wait_for_completion ( & hw - > done ) ;
} else {
2014-01-08 12:00:04 +04:00
/* we need to tighten the transfer loop */
writeb ( txp ? * txp + + : 0 , hw - > base + TINY_SPI_TXDATA ) ;
for ( i = 1 ; i < t - > len ; i + + ) {
writeb ( txp ? * txp + + : 0 , hw - > base + TINY_SPI_TXDATA ) ;
if ( rxp | | ( i ! = t - > len - 1 ) )
2011-02-14 05:20:39 +03:00
tiny_spi_wait_txr ( hw ) ;
2014-01-08 12:00:04 +04:00
if ( rxp )
* rxp + + = readb ( hw - > base + TINY_SPI_TXDATA ) ;
2011-02-14 05:20:39 +03:00
}
tiny_spi_wait_txe ( hw ) ;
2014-01-08 12:00:04 +04:00
if ( rxp )
* rxp + + = readb ( hw - > base + TINY_SPI_RXDATA ) ;
2011-02-14 05:20:39 +03:00
}
2014-01-08 12:00:04 +04:00
2011-02-14 05:20:39 +03:00
return t - > len ;
}
static irqreturn_t tiny_spi_irq ( int irq , void * dev )
{
struct tiny_spi * hw = dev ;
writeb ( 0 , hw - > base + TINY_SPI_STATUS ) ;
if ( hw - > rxc + 1 = = hw - > len ) {
if ( hw - > rxp )
* hw - > rxp + + = readb ( hw - > base + TINY_SPI_RXDATA ) ;
hw - > rxc + + ;
complete ( & hw - > done ) ;
} else {
if ( hw - > rxp )
* hw - > rxp + + = readb ( hw - > base + TINY_SPI_TXDATA ) ;
hw - > rxc + + ;
if ( hw - > txc < hw - > len ) {
writeb ( hw - > txp ? * hw - > txp + + : 0 ,
hw - > base + TINY_SPI_TXDATA ) ;
hw - > txc + + ;
writeb ( TINY_SPI_STATUS_TXR ,
hw - > base + TINY_SPI_STATUS ) ;
} else {
writeb ( TINY_SPI_STATUS_TXE ,
hw - > base + TINY_SPI_STATUS ) ;
}
}
return IRQ_HANDLED ;
}
# ifdef CONFIG_OF
# include <linux/of_gpio.h>
2012-12-07 20:57:14 +04:00
static int tiny_spi_of_probe ( struct platform_device * pdev )
2011-02-14 05:20:39 +03:00
{
struct tiny_spi * hw = platform_get_drvdata ( pdev ) ;
struct device_node * np = pdev - > dev . of_node ;
2015-09-09 14:55:53 +03:00
u32 val ;
2011-02-14 05:20:39 +03:00
if ( ! np )
return 0 ;
hw - > bitbang . master - > dev . of_node = pdev - > dev . of_node ;
2015-09-09 14:55:53 +03:00
if ( ! of_property_read_u32 ( np , " clock-frequency " , & val ) )
hw - > freq = val ;
if ( ! of_property_read_u32 ( np , " baud-width " , & val ) )
hw - > baudwidth = val ;
2011-02-14 05:20:39 +03:00
return 0 ;
}
# else /* !CONFIG_OF */
2012-12-07 20:57:14 +04:00
static int tiny_spi_of_probe ( struct platform_device * pdev )
2011-02-14 05:20:39 +03:00
{
return 0 ;
}
# endif /* CONFIG_OF */
2012-12-07 20:57:14 +04:00
static int tiny_spi_probe ( struct platform_device * pdev )
2011-02-14 05:20:39 +03:00
{
2013-07-30 11:58:59 +04:00
struct tiny_spi_platform_data * platp = dev_get_platdata ( & pdev - > dev ) ;
2011-02-14 05:20:39 +03:00
struct tiny_spi * hw ;
struct spi_master * master ;
int err = - ENODEV ;
master = spi_alloc_master ( & pdev - > dev , sizeof ( struct tiny_spi ) ) ;
if ( ! master )
return err ;
/* setup the master state. */
master - > bus_num = pdev - > id ;
master - > mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH ;
master - > setup = tiny_spi_setup ;
2019-12-05 12:24:11 +03:00
master - > use_gpio_descriptors = true ;
2011-02-14 05:20:39 +03:00
hw = spi_master_get_devdata ( master ) ;
platform_set_drvdata ( pdev , hw ) ;
/* setup the state for the bitbang driver */
2013-09-10 11:43:41 +04:00
hw - > bitbang . master = master ;
2011-02-14 05:20:39 +03:00
hw - > bitbang . setup_transfer = tiny_spi_setup_transfer ;
hw - > bitbang . txrx_bufs = tiny_spi_txrx_bufs ;
/* find and map our resources */
2019-09-04 16:59:03 +03:00
hw - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2013-08-24 21:13:15 +04:00
if ( IS_ERR ( hw - > base ) ) {
err = PTR_ERR ( hw - > base ) ;
goto exit ;
}
2011-02-14 05:20:39 +03:00
/* irq is optional */
hw - > irq = platform_get_irq ( pdev , 0 ) ;
if ( hw - > irq > = 0 ) {
init_completion ( & hw - > done ) ;
err = devm_request_irq ( & pdev - > dev , hw - > irq , tiny_spi_irq , 0 ,
pdev - > name , hw ) ;
if ( err )
goto exit ;
}
/* find platform data */
if ( platp ) {
hw - > freq = platp - > freq ;
hw - > baudwidth = platp - > baudwidth ;
} else {
err = tiny_spi_of_probe ( pdev ) ;
if ( err )
goto exit ;
}
/* register our spi controller */
err = spi_bitbang_start ( & hw - > bitbang ) ;
if ( err )
goto exit ;
dev_info ( & pdev - > dev , " base %p, irq %d \n " , hw - > base , hw - > irq ) ;
return 0 ;
exit :
spi_master_put ( master ) ;
return err ;
}
2012-12-07 20:57:14 +04:00
static int tiny_spi_remove ( struct platform_device * pdev )
2011-02-14 05:20:39 +03:00
{
struct tiny_spi * hw = platform_get_drvdata ( pdev ) ;
struct spi_master * master = hw - > bitbang . master ;
spi_bitbang_stop ( & hw - > bitbang ) ;
spi_master_put ( master ) ;
return 0 ;
}
# ifdef CONFIG_OF
static const struct of_device_id tiny_spi_match [ ] = {
{ . compatible = " opencores,tiny-spi-rtlsvn2 " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , tiny_spi_match ) ;
# endif /* CONFIG_OF */
static struct platform_driver tiny_spi_driver = {
. probe = tiny_spi_probe ,
2012-12-07 20:57:14 +04:00
. remove = tiny_spi_remove ,
2011-02-14 05:20:39 +03:00
. driver = {
. name = DRV_NAME ,
. pm = NULL ,
2013-03-14 14:01:50 +04:00
. of_match_table = of_match_ptr ( tiny_spi_match ) ,
2011-02-14 05:20:39 +03:00
} ,
} ;
2011-10-05 21:29:49 +04:00
module_platform_driver ( tiny_spi_driver ) ;
2011-02-14 05:20:39 +03:00
MODULE_DESCRIPTION ( " OpenCores tiny SPI driver " ) ;
MODULE_AUTHOR ( " Thomas Chou <thomas@wytron.com.tw> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;