2011-01-04 23:28:22 +03:00
/*
* SPI controller driver for the Atheros AR71XX / AR724X / AR913X SoCs
*
* Copyright ( C ) 2009 - 2011 Gabor Juhos < juhosg @ openwrt . org >
*
* This driver has been based on the spi - gpio . c :
* Copyright ( C ) 2006 , 2008 David Brownell
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
*/
# include <linux/kernel.h>
2011-11-16 23:01:43 +04:00
# include <linux/module.h>
2011-01-04 23:28:22 +03:00
# include <linux/delay.h>
# include <linux/spinlock.h>
# include <linux/platform_device.h>
# include <linux/io.h>
# include <linux/spi/spi.h>
# include <linux/spi/spi_bitbang.h>
# include <linux/bitops.h>
# include <linux/gpio.h>
2012-12-27 13:42:24 +04:00
# include <linux/clk.h>
# include <linux/err.h>
2011-01-04 23:28:22 +03:00
# include <asm/mach-ath79/ar71xx_regs.h>
# include <asm/mach-ath79/ath79_spi_platform.h>
# define DRV_NAME "ath79-spi"
2012-12-27 13:42:24 +04:00
# define ATH79_SPI_RRW_DELAY_FACTOR 12000
# define MHZ (1000 * 1000)
2011-01-04 23:28:22 +03:00
struct ath79_spi {
struct spi_bitbang bitbang ;
u32 ioc_base ;
u32 reg_ctrl ;
void __iomem * base ;
2012-12-27 13:42:24 +04:00
struct clk * clk ;
unsigned rrw_delay ;
2011-01-04 23:28:22 +03:00
} ;
static inline u32 ath79_spi_rr ( struct ath79_spi * sp , unsigned reg )
{
return ioread32 ( sp - > base + reg ) ;
}
static inline void ath79_spi_wr ( struct ath79_spi * sp , unsigned reg , u32 val )
{
iowrite32 ( val , sp - > base + reg ) ;
}
static inline struct ath79_spi * ath79_spidev_to_sp ( struct spi_device * spi )
{
return spi_master_get_devdata ( spi - > master ) ;
}
2012-12-27 13:42:24 +04:00
static inline void ath79_spi_delay ( struct ath79_spi * sp , unsigned nsecs )
{
if ( nsecs > sp - > rrw_delay )
ndelay ( nsecs - sp - > rrw_delay ) ;
}
2011-01-04 23:28:22 +03:00
static void ath79_spi_chipselect ( struct spi_device * spi , int is_active )
{
struct ath79_spi * sp = ath79_spidev_to_sp ( spi ) ;
int cs_high = ( spi - > mode & SPI_CS_HIGH ) ? is_active : ! is_active ;
if ( is_active ) {
/* set initial clock polarity */
if ( spi - > mode & SPI_CPOL )
sp - > ioc_base | = AR71XX_SPI_IOC_CLK ;
else
sp - > ioc_base & = ~ AR71XX_SPI_IOC_CLK ;
ath79_spi_wr ( sp , AR71XX_SPI_REG_IOC , sp - > ioc_base ) ;
}
if ( spi - > chip_select ) {
/* SPI is normally active-low */
2015-04-24 17:19:22 +03:00
gpio_set_value ( spi - > cs_gpio , cs_high ) ;
2011-01-04 23:28:22 +03:00
} else {
if ( cs_high )
sp - > ioc_base | = AR71XX_SPI_IOC_CS0 ;
else
sp - > ioc_base & = ~ AR71XX_SPI_IOC_CS0 ;
ath79_spi_wr ( sp , AR71XX_SPI_REG_IOC , sp - > ioc_base ) ;
}
}
2012-12-27 13:42:28 +04:00
static void ath79_spi_enable ( struct ath79_spi * sp )
2011-01-04 23:28:22 +03:00
{
/* enable GPIO mode */
ath79_spi_wr ( sp , AR71XX_SPI_REG_FS , AR71XX_SPI_FS_GPIO ) ;
/* save CTRL register */
sp - > reg_ctrl = ath79_spi_rr ( sp , AR71XX_SPI_REG_CTRL ) ;
sp - > ioc_base = ath79_spi_rr ( sp , AR71XX_SPI_REG_IOC ) ;
/* TODO: setup speed? */
ath79_spi_wr ( sp , AR71XX_SPI_REG_CTRL , 0x43 ) ;
2012-12-27 13:42:28 +04:00
}
static void ath79_spi_disable ( struct ath79_spi * sp )
{
/* restore CTRL register */
ath79_spi_wr ( sp , AR71XX_SPI_REG_CTRL , sp - > reg_ctrl ) ;
/* disable GPIO mode */
ath79_spi_wr ( sp , AR71XX_SPI_REG_FS , 0 ) ;
}
static int ath79_spi_setup_cs ( struct spi_device * spi )
{
2015-04-24 17:19:24 +03:00
struct ath79_spi * sp = ath79_spidev_to_sp ( spi ) ;
2012-12-27 13:42:28 +04:00
int status ;
2015-04-24 17:19:22 +03:00
if ( spi - > chip_select & & ! gpio_is_valid ( spi - > cs_gpio ) )
2012-12-27 13:42:28 +04:00
return - EINVAL ;
2011-01-04 23:28:22 +03:00
2012-12-27 13:42:27 +04:00
status = 0 ;
2011-01-04 23:28:22 +03:00
if ( spi - > chip_select ) {
2012-12-27 13:42:27 +04:00
unsigned long flags ;
flags = GPIOF_DIR_OUT ;
if ( spi - > mode & SPI_CS_HIGH )
flags | = GPIOF_INIT_LOW ;
2014-03-02 23:54:42 +04:00
else
flags | = GPIOF_INIT_HIGH ;
2012-12-27 13:42:27 +04:00
2015-04-24 17:19:22 +03:00
status = gpio_request_one ( spi - > cs_gpio , flags ,
2012-12-27 13:42:27 +04:00
dev_name ( & spi - > dev ) ) ;
2015-04-24 17:19:24 +03:00
} else {
if ( spi - > mode & SPI_CS_HIGH )
sp - > ioc_base & = ~ AR71XX_SPI_IOC_CS0 ;
else
sp - > ioc_base | = AR71XX_SPI_IOC_CS0 ;
ath79_spi_wr ( sp , AR71XX_SPI_REG_IOC , sp - > ioc_base ) ;
2011-01-04 23:28:22 +03:00
}
2012-12-27 13:42:27 +04:00
return status ;
2011-01-04 23:28:22 +03:00
}
static void ath79_spi_cleanup_cs ( struct spi_device * spi )
{
if ( spi - > chip_select ) {
2015-04-24 17:19:22 +03:00
gpio_free ( spi - > cs_gpio ) ;
2011-01-04 23:28:22 +03:00
}
}
static int ath79_spi_setup ( struct spi_device * spi )
{
int status = 0 ;
if ( ! spi - > controller_state ) {
status = ath79_spi_setup_cs ( spi ) ;
if ( status )
return status ;
}
status = spi_bitbang_setup ( spi ) ;
if ( status & & ! spi - > controller_state )
ath79_spi_cleanup_cs ( spi ) ;
return status ;
}
static void ath79_spi_cleanup ( struct spi_device * spi )
{
ath79_spi_cleanup_cs ( spi ) ;
spi_bitbang_cleanup ( spi ) ;
}
static u32 ath79_spi_txrx_mode0 ( struct spi_device * spi , unsigned nsecs ,
u32 word , u8 bits )
{
struct ath79_spi * sp = ath79_spidev_to_sp ( spi ) ;
u32 ioc = sp - > ioc_base ;
/* clock starts at inactive polarity */
for ( word < < = ( 32 - bits ) ; likely ( bits ) ; bits - - ) {
u32 out ;
if ( word & ( 1 < < 31 ) )
out = ioc | AR71XX_SPI_IOC_DO ;
else
out = ioc & ~ AR71XX_SPI_IOC_DO ;
/* setup MSB (to slave) on trailing edge */
ath79_spi_wr ( sp , AR71XX_SPI_REG_IOC , out ) ;
2012-12-27 13:42:24 +04:00
ath79_spi_delay ( sp , nsecs ) ;
2011-01-04 23:28:22 +03:00
ath79_spi_wr ( sp , AR71XX_SPI_REG_IOC , out | AR71XX_SPI_IOC_CLK ) ;
2012-12-27 13:42:24 +04:00
ath79_spi_delay ( sp , nsecs ) ;
2012-12-27 13:42:25 +04:00
if ( bits = = 1 )
ath79_spi_wr ( sp , AR71XX_SPI_REG_IOC , out ) ;
2011-01-04 23:28:22 +03:00
word < < = 1 ;
}
return ath79_spi_rr ( sp , AR71XX_SPI_REG_RDS ) ;
}
2012-12-07 20:57:14 +04:00
static int ath79_spi_probe ( struct platform_device * pdev )
2011-01-04 23:28:22 +03:00
{
struct spi_master * master ;
struct ath79_spi * sp ;
struct ath79_spi_platform_data * pdata ;
struct resource * r ;
2012-12-27 13:42:24 +04:00
unsigned long rate ;
2011-01-04 23:28:22 +03:00
int ret ;
master = spi_alloc_master ( & pdev - > dev , sizeof ( * sp ) ) ;
if ( master = = NULL ) {
dev_err ( & pdev - > dev , " failed to allocate spi master \n " ) ;
return - ENOMEM ;
}
sp = spi_master_get_devdata ( master ) ;
2015-04-24 17:19:22 +03:00
master - > dev . of_node = pdev - > dev . of_node ;
2011-01-04 23:28:22 +03:00
platform_set_drvdata ( pdev , sp ) ;
2013-07-30 11:58:59 +04:00
pdata = dev_get_platdata ( & pdev - > dev ) ;
2011-01-04 23:28:22 +03:00
2013-05-22 06:36:35 +04:00
master - > bits_per_word_mask = SPI_BPW_RANGE_MASK ( 1 , 32 ) ;
2011-01-04 23:28:22 +03:00
master - > setup = ath79_spi_setup ;
master - > cleanup = ath79_spi_cleanup ;
if ( pdata ) {
master - > bus_num = pdata - > bus_num ;
master - > num_chipselect = pdata - > num_chipselect ;
}
2013-09-10 11:43:41 +04:00
sp - > bitbang . master = master ;
2011-01-04 23:28:22 +03:00
sp - > bitbang . chipselect = ath79_spi_chipselect ;
sp - > bitbang . txrx_word [ SPI_MODE_0 ] = ath79_spi_txrx_mode0 ;
sp - > bitbang . setup_transfer = spi_bitbang_setup_transfer ;
sp - > bitbang . flags = SPI_CS_HIGH ;
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2015-09-27 19:47:35 +03:00
sp - > base = devm_ioremap_resource ( & pdev - > dev , r ) ;
if ( IS_ERR ( sp - > base ) ) {
ret = PTR_ERR ( sp - > base ) ;
2011-01-04 23:28:22 +03:00
goto err_put_master ;
}
2013-12-09 14:14:58 +04:00
sp - > clk = devm_clk_get ( & pdev - > dev , " ahb " ) ;
2012-12-27 13:42:24 +04:00
if ( IS_ERR ( sp - > clk ) ) {
ret = PTR_ERR ( sp - > clk ) ;
2013-12-09 14:14:58 +04:00
goto err_put_master ;
2012-12-27 13:42:24 +04:00
}
2015-04-24 17:19:23 +03:00
ret = clk_prepare_enable ( sp - > clk ) ;
2012-12-27 13:42:24 +04:00
if ( ret )
2013-12-09 14:14:58 +04:00
goto err_put_master ;
2012-12-27 13:42:24 +04:00
rate = DIV_ROUND_UP ( clk_get_rate ( sp - > clk ) , MHZ ) ;
if ( ! rate ) {
ret = - EINVAL ;
goto err_clk_disable ;
}
sp - > rrw_delay = ATH79_SPI_RRW_DELAY_FACTOR / rate ;
dev_dbg ( & pdev - > dev , " register read/write delay is %u nsecs \n " ,
sp - > rrw_delay ) ;
2012-12-27 13:42:28 +04:00
ath79_spi_enable ( sp ) ;
2011-01-04 23:28:22 +03:00
ret = spi_bitbang_start ( & sp - > bitbang ) ;
if ( ret )
2012-12-27 13:42:28 +04:00
goto err_disable ;
2011-01-04 23:28:22 +03:00
return 0 ;
2012-12-27 13:42:28 +04:00
err_disable :
ath79_spi_disable ( sp ) ;
2012-12-27 13:42:24 +04:00
err_clk_disable :
2015-04-24 17:19:23 +03:00
clk_disable_unprepare ( sp - > clk ) ;
2011-01-04 23:28:22 +03:00
err_put_master :
spi_master_put ( sp - > bitbang . master ) ;
return ret ;
}
2012-12-07 20:57:14 +04:00
static int ath79_spi_remove ( struct platform_device * pdev )
2011-01-04 23:28:22 +03:00
{
struct ath79_spi * sp = platform_get_drvdata ( pdev ) ;
spi_bitbang_stop ( & sp - > bitbang ) ;
2012-12-27 13:42:28 +04:00
ath79_spi_disable ( sp ) ;
2015-04-24 17:19:23 +03:00
clk_disable_unprepare ( sp - > clk ) ;
2011-01-04 23:28:22 +03:00
spi_master_put ( sp - > bitbang . master ) ;
return 0 ;
}
2013-02-05 23:57:55 +04:00
static void ath79_spi_shutdown ( struct platform_device * pdev )
{
ath79_spi_remove ( pdev ) ;
}
2015-04-24 17:19:22 +03:00
static const struct of_device_id ath79_spi_of_match [ ] = {
{ . compatible = " qca,ar7100-spi " , } ,
{ } ,
} ;
2011-01-04 23:28:22 +03:00
static struct platform_driver ath79_spi_driver = {
. probe = ath79_spi_probe ,
2012-12-07 20:57:14 +04:00
. remove = ath79_spi_remove ,
2013-02-05 23:57:55 +04:00
. shutdown = ath79_spi_shutdown ,
2011-01-04 23:28:22 +03:00
. driver = {
. name = DRV_NAME ,
2015-04-24 17:19:22 +03:00
. of_match_table = ath79_spi_of_match ,
2011-01-04 23:28:22 +03:00
} ,
} ;
2011-10-05 21:29:49 +04:00
module_platform_driver ( ath79_spi_driver ) ;
2011-01-04 23:28:22 +03:00
MODULE_DESCRIPTION ( " SPI controller driver for Atheros AR71XX/AR724X/AR913X " ) ;
MODULE_AUTHOR ( " Gabor Juhos <juhosg@openwrt.org> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;