2019-06-01 11:08:37 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2017-06-14 18:36:29 +03:00
/*
* Altera Passive Serial SPI Driver
*
* Copyright ( c ) 2017 United Western Technologies , Corporation
*
* Joshua Clayton < stillcompiling @ gmail . com >
*
* Manage Altera FPGA firmware that is loaded over SPI using the passive
* serial configuration method .
* Firmware must be in binary " rbf " format .
* Works on Arria 10 , Cyclone V and Stratix V . Should work on Cyclone series .
* May work on other Altera FPGAs .
*/
# include <linux/bitrev.h>
# include <linux/delay.h>
# include <linux/fpga/fpga-mgr.h>
# include <linux/gpio/consumer.h>
# include <linux/module.h>
# include <linux/of_gpio.h>
# include <linux/of_device.h>
# include <linux/spi/spi.h>
# include <linux/sizes.h>
enum altera_ps_devtype {
CYCLONE5 ,
ARRIA10 ,
} ;
struct altera_ps_data {
enum altera_ps_devtype devtype ;
int status_wait_min_us ;
int status_wait_max_us ;
int t_cfg_us ;
int t_st2ck_us ;
} ;
struct altera_ps_conf {
struct gpio_desc * config ;
struct gpio_desc * confd ;
struct gpio_desc * status ;
struct spi_device * spi ;
const struct altera_ps_data * data ;
u32 info_flags ;
char mgr_name [ 64 ] ;
} ;
/* | Arria 10 | Cyclone5 | Stratix5 |
* t_CF2ST0 | [ ; 600 ] | [ ; 600 ] | [ ; 600 ] | ns
* t_CFG | [ 2 ; ] | [ 2 ; ] | [ 2 ; ] | µ s
* t_STATUS | [ 268 ; 3000 ] | [ 268 ; 1506 ] | [ 268 ; 1506 ] | µ s
* t_CF2ST1 | [ ; 3000 ] | [ ; 1506 ] | [ ; 1506 ] | µ s
* t_CF2CK | [ 3010 ; ] | [ 1506 ; ] | [ 1506 ; ] | µ s
* t_ST2CK | [ 10 ; ] | [ 2 ; ] | [ 2 ; ] | µ s
* t_CD2UM | [ 175 ; 830 ] | [ 175 ; 437 ] | [ 175 ; 437 ] | µ s
*/
static struct altera_ps_data c5_data = {
/* these values for Cyclone5 are compatible with Stratix5 */
. devtype = CYCLONE5 ,
. status_wait_min_us = 268 ,
. status_wait_max_us = 1506 ,
. t_cfg_us = 2 ,
. t_st2ck_us = 2 ,
} ;
static struct altera_ps_data a10_data = {
. devtype = ARRIA10 ,
. status_wait_min_us = 268 , /* min(t_STATUS) */
. status_wait_max_us = 3000 , /* max(t_CF2ST1) */
. t_cfg_us = 2 , /* max { min(t_CFG), max(tCF2ST0) } */
. t_st2ck_us = 10 , /* min(t_ST2CK) */
} ;
2018-11-26 20:35:28 +03:00
/* Array index is enum altera_ps_devtype */
static const struct altera_ps_data * altera_ps_data_map [ ] = {
& c5_data ,
& a10_data ,
} ;
2017-06-14 18:36:29 +03:00
static const struct of_device_id of_ef_match [ ] = {
{ . compatible = " altr,fpga-passive-serial " , . data = & c5_data } ,
{ . compatible = " altr,fpga-arria10-passive-serial " , . data = & a10_data } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , of_ef_match ) ;
static enum fpga_mgr_states altera_ps_state ( struct fpga_manager * mgr )
{
struct altera_ps_conf * conf = mgr - > priv ;
if ( gpiod_get_value_cansleep ( conf - > status ) )
return FPGA_MGR_STATE_RESET ;
return FPGA_MGR_STATE_UNKNOWN ;
}
static inline void altera_ps_delay ( int delay_us )
{
if ( delay_us > 10 )
usleep_range ( delay_us , delay_us + 5 ) ;
else
udelay ( delay_us ) ;
}
static int altera_ps_write_init ( struct fpga_manager * mgr ,
struct fpga_image_info * info ,
const char * buf , size_t count )
{
struct altera_ps_conf * conf = mgr - > priv ;
int min , max , waits ;
int i ;
conf - > info_flags = info - > flags ;
if ( info - > flags & FPGA_MGR_PARTIAL_RECONFIG ) {
dev_err ( & mgr - > dev , " Partial reconfiguration not supported. \n " ) ;
return - EINVAL ;
}
gpiod_set_value_cansleep ( conf - > config , 1 ) ;
/* wait min reset pulse time */
altera_ps_delay ( conf - > data - > t_cfg_us ) ;
if ( ! gpiod_get_value_cansleep ( conf - > status ) ) {
dev_err ( & mgr - > dev , " Status pin failed to show a reset \n " ) ;
return - EIO ;
}
gpiod_set_value_cansleep ( conf - > config , 0 ) ;
min = conf - > data - > status_wait_min_us ;
max = conf - > data - > status_wait_max_us ;
waits = max / min ;
if ( max % min )
waits + + ;
/* wait for max { max(t_STATUS), max(t_CF2ST1) } */
for ( i = 0 ; i < waits ; i + + ) {
usleep_range ( min , min + 10 ) ;
if ( ! gpiod_get_value_cansleep ( conf - > status ) ) {
/* wait for min(t_ST2CK)*/
altera_ps_delay ( conf - > data - > t_st2ck_us ) ;
return 0 ;
}
}
dev_err ( & mgr - > dev , " Status pin not ready. \n " ) ;
return - EIO ;
}
static void rev_buf ( char * buf , size_t len )
{
2017-06-14 18:36:32 +03:00
u32 * fw32 = ( u32 * ) buf ;
size_t extra_bytes = ( len & 0x03 ) ;
const u32 * fw_end = ( u32 * ) ( buf + len - extra_bytes ) ;
2017-06-14 18:36:29 +03:00
/* set buffer to lsb first */
2017-06-14 18:36:32 +03:00
while ( fw32 < fw_end ) {
* fw32 = bitrev8x4 ( * fw32 ) ;
fw32 + + ;
}
if ( extra_bytes ) {
buf = ( char * ) fw_end ;
while ( extra_bytes ) {
* buf = bitrev8 ( * buf ) ;
buf + + ;
extra_bytes - - ;
}
2017-06-14 18:36:29 +03:00
}
}
static int altera_ps_write ( struct fpga_manager * mgr , const char * buf ,
size_t count )
{
struct altera_ps_conf * conf = mgr - > priv ;
const char * fw_data = buf ;
const char * fw_data_end = fw_data + count ;
while ( fw_data < fw_data_end ) {
int ret ;
size_t stride = min_t ( size_t , fw_data_end - fw_data , SZ_4K ) ;
if ( ! ( conf - > info_flags & FPGA_MGR_BITSTREAM_LSB_FIRST ) )
rev_buf ( ( char * ) fw_data , stride ) ;
ret = spi_write ( conf - > spi , fw_data , stride ) ;
if ( ret ) {
dev_err ( & mgr - > dev , " spi error in firmware write: %d \n " ,
ret ) ;
return ret ;
}
fw_data + = stride ;
}
return 0 ;
}
static int altera_ps_write_complete ( struct fpga_manager * mgr ,
struct fpga_image_info * info )
{
struct altera_ps_conf * conf = mgr - > priv ;
2019-01-24 23:45:54 +03:00
static const char dummy [ ] = { 0 } ;
2017-06-14 18:36:29 +03:00
int ret ;
if ( gpiod_get_value_cansleep ( conf - > status ) ) {
dev_err ( & mgr - > dev , " Error during configuration. \n " ) ;
return - EIO ;
}
2019-07-25 05:48:45 +03:00
if ( conf - > confd ) {
2017-06-14 18:36:29 +03:00
if ( ! gpiod_get_raw_value_cansleep ( conf - > confd ) ) {
dev_err ( & mgr - > dev , " CONF_DONE is inactive! \n " ) ;
return - EIO ;
}
}
/*
* After CONF_DONE goes high , send two additional falling edges on DCLK
* to begin initialization and enter user mode
*/
ret = spi_write ( conf - > spi , dummy , 1 ) ;
if ( ret ) {
dev_err ( & mgr - > dev , " spi error during end sequence: %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
static const struct fpga_manager_ops altera_ps_ops = {
. state = altera_ps_state ,
. write_init = altera_ps_write_init ,
. write = altera_ps_write ,
. write_complete = altera_ps_write_complete ,
} ;
2018-11-26 20:35:28 +03:00
static const struct altera_ps_data * id_to_data ( const struct spi_device_id * id )
{
kernel_ulong_t devtype = id - > driver_data ;
const struct altera_ps_data * data ;
/* someone added a altera_ps_devtype without adding to the map array */
if ( devtype > = ARRAY_SIZE ( altera_ps_data_map ) )
return NULL ;
data = altera_ps_data_map [ devtype ] ;
if ( ! data | | data - > devtype ! = devtype )
return NULL ;
return data ;
}
2017-06-14 18:36:29 +03:00
static int altera_ps_probe ( struct spi_device * spi )
{
struct altera_ps_conf * conf ;
const struct of_device_id * of_id ;
2018-05-17 02:49:55 +03:00
struct fpga_manager * mgr ;
2017-06-14 18:36:29 +03:00
conf = devm_kzalloc ( & spi - > dev , sizeof ( * conf ) , GFP_KERNEL ) ;
if ( ! conf )
return - ENOMEM ;
2018-11-26 20:35:28 +03:00
if ( spi - > dev . of_node ) {
of_id = of_match_device ( of_ef_match , & spi - > dev ) ;
if ( ! of_id )
return - ENODEV ;
conf - > data = of_id - > data ;
} else {
conf - > data = id_to_data ( spi_get_device_id ( spi ) ) ;
if ( ! conf - > data )
return - ENODEV ;
}
2017-06-14 18:36:29 +03:00
conf - > spi = spi ;
2018-04-15 21:33:08 +03:00
conf - > config = devm_gpiod_get ( & spi - > dev , " nconfig " , GPIOD_OUT_LOW ) ;
2017-06-14 18:36:29 +03:00
if ( IS_ERR ( conf - > config ) ) {
dev_err ( & spi - > dev , " Failed to get config gpio: %ld \n " ,
PTR_ERR ( conf - > config ) ) ;
return PTR_ERR ( conf - > config ) ;
}
conf - > status = devm_gpiod_get ( & spi - > dev , " nstat " , GPIOD_IN ) ;
if ( IS_ERR ( conf - > status ) ) {
dev_err ( & spi - > dev , " Failed to get status gpio: %ld \n " ,
PTR_ERR ( conf - > status ) ) ;
return PTR_ERR ( conf - > status ) ;
}
2019-07-25 05:48:45 +03:00
conf - > confd = devm_gpiod_get_optional ( & spi - > dev , " confd " , GPIOD_IN ) ;
2017-06-14 18:36:29 +03:00
if ( IS_ERR ( conf - > confd ) ) {
2019-07-25 05:48:45 +03:00
dev_err ( & spi - > dev , " Failed to get confd gpio: %ld \n " ,
PTR_ERR ( conf - > confd ) ) ;
return PTR_ERR ( conf - > confd ) ;
} else if ( ! conf - > confd ) {
dev_warn ( & spi - > dev , " Not using confd gpio " ) ;
2017-06-14 18:36:29 +03:00
}
/* Register manager with unique name */
snprintf ( conf - > mgr_name , sizeof ( conf - > mgr_name ) , " %s %s " ,
dev_driver_string ( & spi - > dev ) , dev_name ( & spi - > dev ) ) ;
2021-11-19 04:55:51 +03:00
mgr = devm_fpga_mgr_register ( & spi - > dev , conf - > mgr_name ,
& altera_ps_ops , conf ) ;
return PTR_ERR_OR_ZERO ( mgr ) ;
2017-06-14 18:36:29 +03:00
}
static const struct spi_device_id altera_ps_spi_ids [ ] = {
2018-11-26 20:35:28 +03:00
{ " cyclone-ps-spi " , CYCLONE5 } ,
{ " fpga-passive-serial " , CYCLONE5 } ,
{ " fpga-arria10-passive-serial " , ARRIA10 } ,
2017-06-14 18:36:29 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( spi , altera_ps_spi_ids ) ;
static struct spi_driver altera_ps_driver = {
. driver = {
. name = " altera-ps-spi " ,
. owner = THIS_MODULE ,
. of_match_table = of_match_ptr ( of_ef_match ) ,
} ,
. id_table = altera_ps_spi_ids ,
. probe = altera_ps_probe ,
} ;
module_spi_driver ( altera_ps_driver )
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Joshua Clayton <stillcompiling@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Module to load Altera FPGA firmware over SPI " ) ;