2016-02-23 13:44:28 +03:00
/*
* SPI master driver for ICP DAS LP - 8841 RTC
*
* Copyright ( C ) 2016 Sergei Ianovich
*
* based on
*
* Dallas DS1302 RTC Support
* Copyright ( C ) 2002 David McCullough
* Copyright ( C ) 2003 - 2007 Paul Mundt
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*/
# include <linux/delay.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/spi/spi.h>
# define DRIVER_NAME "spi_lp8841_rtc"
# define SPI_LP8841_RTC_CE 0x01
# define SPI_LP8841_RTC_CLK 0x02
# define SPI_LP8841_RTC_nWE 0x04
# define SPI_LP8841_RTC_MOSI 0x08
# define SPI_LP8841_RTC_MISO 0x01
/*
* REVISIT If there is support for SPI_3WIRE and SPI_LSB_FIRST in SPI
* GPIO driver , this SPI driver can be replaced by a simple GPIO driver
* providing 3 GPIO pins .
*/
struct spi_lp8841_rtc {
void * iomem ;
unsigned long state ;
} ;
static inline void
setsck ( struct spi_lp8841_rtc * data , int is_on )
{
if ( is_on )
data - > state | = SPI_LP8841_RTC_CLK ;
else
data - > state & = ~ SPI_LP8841_RTC_CLK ;
writeb ( data - > state , data - > iomem ) ;
}
static inline void
setmosi ( struct spi_lp8841_rtc * data , int is_on )
{
if ( is_on )
data - > state | = SPI_LP8841_RTC_MOSI ;
else
data - > state & = ~ SPI_LP8841_RTC_MOSI ;
writeb ( data - > state , data - > iomem ) ;
}
static inline int
getmiso ( struct spi_lp8841_rtc * data )
{
return ioread8 ( data - > iomem ) & SPI_LP8841_RTC_MISO ;
}
static inline u32
bitbang_txrx_be_cpha0_lsb ( struct spi_lp8841_rtc * data ,
unsigned usecs , unsigned cpol , unsigned flags ,
u32 word , u8 bits )
{
/* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */
u32 shift = 32 - bits ;
/* clock starts at inactive polarity */
for ( ; likely ( bits ) ; bits - - ) {
/* setup LSB (to slave) on leading edge */
if ( ( flags & SPI_MASTER_NO_TX ) = = 0 )
setmosi ( data , ( word & 1 ) ) ;
usleep_range ( usecs , usecs + 1 ) ; /* T(setup) */
/* sample LSB (from slave) on trailing edge */
word > > = 1 ;
if ( ( flags & SPI_MASTER_NO_RX ) = = 0 )
word | = ( getmiso ( data ) < < 31 ) ;
setsck ( data , ! cpol ) ;
usleep_range ( usecs , usecs + 1 ) ;
setsck ( data , cpol ) ;
}
word > > = shift ;
return word ;
}
static int
spi_lp8841_rtc_transfer_one ( struct spi_master * master ,
struct spi_device * spi ,
struct spi_transfer * t )
{
struct spi_lp8841_rtc * data = spi_master_get_devdata ( master ) ;
unsigned count = t - > len ;
const u8 * tx = t - > tx_buf ;
u8 * rx = t - > rx_buf ;
u8 word = 0 ;
int ret = 0 ;
if ( tx ) {
data - > state & = ~ SPI_LP8841_RTC_nWE ;
writeb ( data - > state , data - > iomem ) ;
while ( likely ( count > 0 ) ) {
word = * tx + + ;
bitbang_txrx_be_cpha0_lsb ( data , 1 , 0 ,
SPI_MASTER_NO_RX , word , 8 ) ;
count - - ;
}
} else if ( rx ) {
data - > state | = SPI_LP8841_RTC_nWE ;
writeb ( data - > state , data - > iomem ) ;
while ( likely ( count > 0 ) ) {
word = bitbang_txrx_be_cpha0_lsb ( data , 1 , 0 ,
SPI_MASTER_NO_TX , word , 8 ) ;
* rx + + = word ;
count - - ;
}
} else {
ret = - EINVAL ;
}
spi_finalize_current_transfer ( master ) ;
return ret ;
}
static void
spi_lp8841_rtc_set_cs ( struct spi_device * spi , bool enable )
{
struct spi_lp8841_rtc * data = spi_master_get_devdata ( spi - > master ) ;
data - > state = 0 ;
writeb ( data - > state , data - > iomem ) ;
if ( enable ) {
usleep_range ( 4 , 5 ) ;
data - > state | = SPI_LP8841_RTC_CE ;
writeb ( data - > state , data - > iomem ) ;
usleep_range ( 4 , 5 ) ;
}
}
static int
spi_lp8841_rtc_setup ( struct spi_device * spi )
{
if ( ( spi - > mode & SPI_CS_HIGH ) = = 0 ) {
dev_err ( & spi - > dev , " unsupported active low chip select \n " ) ;
return - EINVAL ;
}
if ( ( spi - > mode & SPI_LSB_FIRST ) = = 0 ) {
dev_err ( & spi - > dev , " unsupported MSB first mode \n " ) ;
return - EINVAL ;
}
if ( ( spi - > mode & SPI_3WIRE ) = = 0 ) {
dev_err ( & spi - > dev , " unsupported wiring. 3 wires required \n " ) ;
return - EINVAL ;
}
return 0 ;
}
# ifdef CONFIG_OF
static const struct of_device_id spi_lp8841_rtc_dt_ids [ ] = {
{ . compatible = " icpdas,lp8841-spi-rtc " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , spi_lp8841_rtc_dt_ids ) ;
# endif
static int
spi_lp8841_rtc_probe ( struct platform_device * pdev )
{
int ret ;
struct spi_master * master ;
struct spi_lp8841_rtc * data ;
void * iomem ;
master = spi_alloc_master ( & pdev - > dev , sizeof ( * data ) ) ;
if ( ! master )
return - ENOMEM ;
platform_set_drvdata ( pdev , master ) ;
master - > flags = SPI_MASTER_HALF_DUPLEX ;
master - > mode_bits = SPI_CS_HIGH | SPI_3WIRE | SPI_LSB_FIRST ;
master - > bus_num = pdev - > id ;
master - > num_chipselect = 1 ;
master - > setup = spi_lp8841_rtc_setup ;
master - > set_cs = spi_lp8841_rtc_set_cs ;
master - > transfer_one = spi_lp8841_rtc_transfer_one ;
master - > bits_per_word_mask = SPI_BPW_MASK ( 8 ) ;
# ifdef CONFIG_OF
master - > dev . of_node = pdev - > dev . of_node ;
# endif
data = spi_master_get_devdata ( master ) ;
iomem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2016-02-25 14:37:40 +03:00
data - > iomem = devm_ioremap_resource ( & pdev - > dev , iomem ) ;
ret = PTR_ERR_OR_ZERO ( data - > iomem ) ;
if ( ret ) {
2016-02-23 13:44:28 +03:00
dev_err ( & pdev - > dev , " failed to get IO address \n " ) ;
goto err_put_master ;
}
/* register with the SPI framework */
ret = devm_spi_register_master ( & pdev - > dev , master ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " cannot register spi master \n " ) ;
goto err_put_master ;
}
return ret ;
err_put_master :
spi_master_put ( master ) ;
return ret ;
}
MODULE_ALIAS ( " platform: " DRIVER_NAME ) ;
static struct platform_driver spi_lp8841_rtc_driver = {
. driver = {
. name = DRIVER_NAME ,
. of_match_table = of_match_ptr ( spi_lp8841_rtc_dt_ids ) ,
} ,
. probe = spi_lp8841_rtc_probe ,
} ;
module_platform_driver ( spi_lp8841_rtc_driver ) ;
MODULE_DESCRIPTION ( " SPI master driver for ICP DAS LP-8841 RTC " ) ;
MODULE_AUTHOR ( " Sergei Ianovich " ) ;
MODULE_LICENSE ( " GPL " ) ;