2012-11-07 21:30:29 +04:00
/*
* CLPS711X SPI bus driver
*
* Copyright ( C ) 2012 Alexander Shiyan < shc_work @ mail . ru >
*
* 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 .
*/
# include <linux/io.h>
# include <linux/clk.h>
# include <linux/init.h>
# include <linux/gpio.h>
# include <linux/delay.h>
# include <linux/module.h>
# include <linux/interrupt.h>
# include <linux/platform_device.h>
# include <linux/spi/spi.h>
# include <linux/platform_data/spi-clps711x.h>
# include <mach/hardware.h>
# define DRIVER_NAME "spi-clps711x"
struct spi_clps711x_data {
struct completion done ;
struct clk * spi_clk ;
u32 max_speed_hz ;
u8 * tx_buf ;
u8 * rx_buf ;
int count ;
int len ;
int chipselect [ 0 ] ;
} ;
static int spi_clps711x_setup ( struct spi_device * spi )
{
struct spi_clps711x_data * hw = spi_master_get_devdata ( spi - > master ) ;
/* We are expect that SPI-device is not selected */
gpio_direction_output ( hw - > chipselect [ spi - > chip_select ] ,
! ( spi - > mode & SPI_CS_HIGH ) ) ;
return 0 ;
}
static void spi_clps711x_setup_mode ( struct spi_device * spi )
{
/* Setup edge for transfer */
if ( spi - > mode & SPI_CPHA )
clps_writew ( clps_readw ( SYSCON3 ) | SYSCON3_ADCCKNSEN , SYSCON3 ) ;
else
clps_writew ( clps_readw ( SYSCON3 ) & ~ SYSCON3_ADCCKNSEN , SYSCON3 ) ;
}
static int spi_clps711x_setup_xfer ( struct spi_device * spi ,
struct spi_transfer * xfer )
{
u32 speed = xfer - > speed_hz ? : spi - > max_speed_hz ;
2012-12-18 12:55:43 +04:00
u8 bpw = xfer - > bits_per_word ;
2012-11-07 21:30:29 +04:00
struct spi_clps711x_data * hw = spi_master_get_devdata ( spi - > master ) ;
if ( bpw ! = 8 ) {
dev_err ( & spi - > dev , " Unsupported master bus width %i \n " , bpw ) ;
return - EINVAL ;
}
/* Setup SPI frequency divider */
if ( ! speed | | ( speed > = hw - > max_speed_hz ) )
clps_writel ( ( clps_readl ( SYSCON1 ) & ~ SYSCON1_ADCKSEL_MASK ) |
SYSCON1_ADCKSEL ( 3 ) , SYSCON1 ) ;
else if ( speed > = ( hw - > max_speed_hz / 2 ) )
clps_writel ( ( clps_readl ( SYSCON1 ) & ~ SYSCON1_ADCKSEL_MASK ) |
SYSCON1_ADCKSEL ( 2 ) , SYSCON1 ) ;
else if ( speed > = ( hw - > max_speed_hz / 8 ) )
clps_writel ( ( clps_readl ( SYSCON1 ) & ~ SYSCON1_ADCKSEL_MASK ) |
SYSCON1_ADCKSEL ( 1 ) , SYSCON1 ) ;
else
clps_writel ( ( clps_readl ( SYSCON1 ) & ~ SYSCON1_ADCKSEL_MASK ) |
SYSCON1_ADCKSEL ( 0 ) , SYSCON1 ) ;
return 0 ;
}
static int spi_clps711x_transfer_one_message ( struct spi_master * master ,
struct spi_message * msg )
{
struct spi_clps711x_data * hw = spi_master_get_devdata ( master ) ;
struct spi_transfer * xfer ;
int status = 0 , cs = hw - > chipselect [ msg - > spi - > chip_select ] ;
u32 data ;
spi_clps711x_setup_mode ( msg - > spi ) ;
list_for_each_entry ( xfer , & msg - > transfers , transfer_list ) {
if ( spi_clps711x_setup_xfer ( msg - > spi , xfer ) ) {
status = - EINVAL ;
goto out_xfr ;
}
gpio_set_value ( cs , ! ! ( msg - > spi - > mode & SPI_CS_HIGH ) ) ;
INIT_COMPLETION ( hw - > done ) ;
hw - > count = 0 ;
hw - > len = xfer - > len ;
hw - > tx_buf = ( u8 * ) xfer - > tx_buf ;
hw - > rx_buf = ( u8 * ) xfer - > rx_buf ;
/* Initiate transfer */
data = hw - > tx_buf ? hw - > tx_buf [ hw - > count ] : 0 ;
clps_writel ( data | SYNCIO_FRMLEN ( 8 ) | SYNCIO_TXFRMEN , SYNCIO ) ;
wait_for_completion ( & hw - > done ) ;
if ( xfer - > delay_usecs )
udelay ( xfer - > delay_usecs ) ;
if ( xfer - > cs_change | |
list_is_last ( & xfer - > transfer_list , & msg - > transfers ) )
gpio_set_value ( cs , ! ( msg - > spi - > mode & SPI_CS_HIGH ) ) ;
msg - > actual_length + = xfer - > len ;
}
out_xfr :
msg - > status = status ;
spi_finalize_current_message ( master ) ;
return 0 ;
}
static irqreturn_t spi_clps711x_isr ( int irq , void * dev_id )
{
struct spi_clps711x_data * hw = ( struct spi_clps711x_data * ) dev_id ;
u32 data ;
/* Handle RX */
data = clps_readb ( SYNCIO ) ;
if ( hw - > rx_buf )
hw - > rx_buf [ hw - > count ] = ( u8 ) data ;
hw - > count + + ;
/* Handle TX */
if ( hw - > count < hw - > len ) {
data = hw - > tx_buf ? hw - > tx_buf [ hw - > count ] : 0 ;
clps_writel ( data | SYNCIO_FRMLEN ( 8 ) | SYNCIO_TXFRMEN , SYNCIO ) ;
} else
complete ( & hw - > done ) ;
return IRQ_HANDLED ;
}
2012-12-07 20:57:14 +04:00
static int spi_clps711x_probe ( struct platform_device * pdev )
2012-11-07 21:30:29 +04:00
{
int i , ret ;
struct spi_master * master ;
struct spi_clps711x_data * hw ;
struct spi_clps711x_pdata * pdata = dev_get_platdata ( & pdev - > dev ) ;
if ( ! pdata ) {
dev_err ( & pdev - > dev , " No platform data supplied \n " ) ;
return - EINVAL ;
}
if ( pdata - > num_chipselect < 1 ) {
dev_err ( & pdev - > dev , " At least one CS must be defined \n " ) ;
return - EINVAL ;
}
master = spi_alloc_master ( & pdev - > dev ,
sizeof ( struct spi_clps711x_data ) +
sizeof ( int ) * pdata - > num_chipselect ) ;
if ( ! master ) {
dev_err ( & pdev - > dev , " SPI allocating memory error \n " ) ;
return - ENOMEM ;
}
master - > bus_num = pdev - > id ;
master - > mode_bits = SPI_CPHA | SPI_CS_HIGH ;
2013-05-22 06:36:35 +04:00
master - > bits_per_word_mask = SPI_BPW_MASK ( 8 ) ;
2012-11-07 21:30:29 +04:00
master - > num_chipselect = pdata - > num_chipselect ;
master - > setup = spi_clps711x_setup ;
master - > transfer_one_message = spi_clps711x_transfer_one_message ;
hw = spi_master_get_devdata ( master ) ;
for ( i = 0 ; i < master - > num_chipselect ; i + + ) {
hw - > chipselect [ i ] = pdata - > chipselect [ i ] ;
if ( ! gpio_is_valid ( hw - > chipselect [ i ] ) ) {
dev_err ( & pdev - > dev , " Invalid CS GPIO %i \n " , i ) ;
ret = - EINVAL ;
goto err_out ;
}
if ( gpio_request ( hw - > chipselect [ i ] , DRIVER_NAME ) ) {
dev_err ( & pdev - > dev , " Can't get CS GPIO %i \n " , i ) ;
ret = - EINVAL ;
goto err_out ;
}
}
hw - > spi_clk = devm_clk_get ( & pdev - > dev , " spi " ) ;
if ( IS_ERR ( hw - > spi_clk ) ) {
dev_err ( & pdev - > dev , " Can't get clocks \n " ) ;
ret = PTR_ERR ( hw - > spi_clk ) ;
goto err_out ;
}
hw - > max_speed_hz = clk_get_rate ( hw - > spi_clk ) ;
init_completion ( & hw - > done ) ;
platform_set_drvdata ( pdev , master ) ;
/* Disable extended mode due hardware problems */
clps_writew ( clps_readw ( SYSCON3 ) & ~ SYSCON3_ADCCON , SYSCON3 ) ;
/* Clear possible pending interrupt */
clps_readl ( SYNCIO ) ;
ret = devm_request_irq ( & pdev - > dev , IRQ_SSEOTI , spi_clps711x_isr , 0 ,
dev_name ( & pdev - > dev ) , hw ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Can't request IRQ \n " ) ;
clk_put ( hw - > spi_clk ) ;
goto clk_out ;
}
ret = spi_register_master ( master ) ;
if ( ! ret ) {
dev_info ( & pdev - > dev ,
" SPI bus driver initialized. Master clock %u Hz \n " ,
hw - > max_speed_hz ) ;
return 0 ;
}
dev_err ( & pdev - > dev , " Failed to register master \n " ) ;
devm_free_irq ( & pdev - > dev , IRQ_SSEOTI , hw ) ;
clk_out :
devm_clk_put ( & pdev - > dev , hw - > spi_clk ) ;
err_out :
while ( - - i > = 0 )
if ( gpio_is_valid ( hw - > chipselect [ i ] ) )
gpio_free ( hw - > chipselect [ i ] ) ;
spi_master_put ( master ) ;
kfree ( master ) ;
return ret ;
}
2012-12-07 20:57:14 +04:00
static int spi_clps711x_remove ( struct platform_device * pdev )
2012-11-07 21:30:29 +04:00
{
int i ;
struct spi_master * master = platform_get_drvdata ( pdev ) ;
struct spi_clps711x_data * hw = spi_master_get_devdata ( master ) ;
devm_free_irq ( & pdev - > dev , IRQ_SSEOTI , hw ) ;
for ( i = 0 ; i < master - > num_chipselect ; i + + )
if ( gpio_is_valid ( hw - > chipselect [ i ] ) )
gpio_free ( hw - > chipselect [ i ] ) ;
devm_clk_put ( & pdev - > dev , hw - > spi_clk ) ;
spi_unregister_master ( master ) ;
kfree ( master ) ;
return 0 ;
}
static struct platform_driver clps711x_spi_driver = {
. driver = {
. name = DRIVER_NAME ,
. owner = THIS_MODULE ,
} ,
. probe = spi_clps711x_probe ,
2012-12-07 20:57:14 +04:00
. remove = spi_clps711x_remove ,
2012-11-07 21:30:29 +04:00
} ;
module_platform_driver ( clps711x_spi_driver ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Alexander Shiyan <shc_work@mail.ru> " ) ;
MODULE_DESCRIPTION ( " CLPS711X SPI bus driver " ) ;