2021-08-02 20:23:09 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* datasheet : https : //www.nxp.com/docs/en/data-sheet/K20P144M120SF3.pdf
*
* Copyright ( C ) 2018 - 2021 Collabora
* Copyright ( C ) 2018 - 2021 GE Healthcare
*/
# include <linux/delay.h>
# include <linux/firmware.h>
# include <linux/gpio/consumer.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/spi/spi.h>
# define ACHC_MAX_FREQ_HZ 300000
# define ACHC_FAST_READ_FREQ_HZ 1000000
struct achc_data {
struct spi_device * main ;
struct spi_device * ezport ;
struct gpio_desc * reset ;
struct mutex device_lock ; /* avoid concurrent device access */
} ;
# define EZPORT_RESET_DELAY_MS 100
# define EZPORT_STARTUP_DELAY_MS 200
# define EZPORT_WRITE_WAIT_MS 10
# define EZPORT_TRANSFER_SIZE 2048
# define EZPORT_CMD_SP 0x02 /* flash section program */
# define EZPORT_CMD_RDSR 0x05 /* read status register */
# define EZPORT_CMD_WREN 0x06 /* write enable */
# define EZPORT_CMD_FAST_READ 0x0b /* flash read data at high speed */
# define EZPORT_CMD_RESET 0xb9 /* reset chip */
# define EZPORT_CMD_BE 0xc7 /* bulk erase */
# define EZPORT_CMD_SE 0xd8 /* sector erase */
# define EZPORT_SECTOR_SIZE 4096
# define EZPORT_SECTOR_MASK (EZPORT_SECTOR_SIZE - 1)
# define EZPORT_STATUS_WIP BIT(0) /* write in progress */
# define EZPORT_STATUS_WEN BIT(1) /* write enable */
# define EZPORT_STATUS_BEDIS BIT(2) /* bulk erase disable */
# define EZPORT_STATUS_FLEXRAM BIT(3) /* FlexRAM mode */
# define EZPORT_STATUS_WEF BIT(6) /* write error flag */
# define EZPORT_STATUS_FS BIT(7) /* flash security */
static void ezport_reset ( struct gpio_desc * reset )
{
gpiod_set_value ( reset , 1 ) ;
msleep ( EZPORT_RESET_DELAY_MS ) ;
gpiod_set_value ( reset , 0 ) ;
msleep ( EZPORT_STARTUP_DELAY_MS ) ;
}
static int ezport_start_programming ( struct spi_device * spi , struct gpio_desc * reset )
{
struct spi_message msg ;
struct spi_transfer assert_cs = {
. cs_change = 1 ,
} ;
struct spi_transfer release_cs = { } ;
int ret ;
spi_bus_lock ( spi - > master ) ;
/* assert chip select */
spi_message_init ( & msg ) ;
spi_message_add_tail ( & assert_cs , & msg ) ;
ret = spi_sync_locked ( spi , & msg ) ;
if ( ret )
goto fail ;
msleep ( EZPORT_STARTUP_DELAY_MS ) ;
/* reset with asserted chip select to switch into programming mode */
ezport_reset ( reset ) ;
/* release chip select */
spi_message_init ( & msg ) ;
spi_message_add_tail ( & release_cs , & msg ) ;
ret = spi_sync_locked ( spi , & msg ) ;
fail :
spi_bus_unlock ( spi - > master ) ;
return ret ;
}
static void ezport_stop_programming ( struct spi_device * spi , struct gpio_desc * reset )
{
/* reset without asserted chip select to return into normal mode */
spi_bus_lock ( spi - > master ) ;
ezport_reset ( reset ) ;
spi_bus_unlock ( spi - > master ) ;
}
static int ezport_get_status_register ( struct spi_device * spi )
{
int ret ;
ret = spi_w8r8 ( spi , EZPORT_CMD_RDSR ) ;
if ( ret < 0 )
return ret ;
if ( ret = = 0xff ) {
dev_err ( & spi - > dev , " Invalid EzPort status, EzPort is not functional! \n " ) ;
return - EINVAL ;
}
return ret ;
}
static int ezport_soft_reset ( struct spi_device * spi )
{
u8 cmd = EZPORT_CMD_RESET ;
int ret ;
ret = spi_write ( spi , & cmd , 1 ) ;
if ( ret < 0 )
return ret ;
msleep ( EZPORT_STARTUP_DELAY_MS ) ;
return 0 ;
}
static int ezport_send_simple ( struct spi_device * spi , u8 cmd )
{
int ret ;
ret = spi_write ( spi , & cmd , 1 ) ;
if ( ret < 0 )
return ret ;
return ezport_get_status_register ( spi ) ;
}
static int ezport_wait_write ( struct spi_device * spi , u32 retries )
{
int ret ;
u32 i ;
for ( i = 0 ; i < retries ; i + + ) {
ret = ezport_get_status_register ( spi ) ;
if ( ret > = 0 & & ! ( ret & EZPORT_STATUS_WIP ) )
break ;
msleep ( EZPORT_WRITE_WAIT_MS ) ;
}
return ret ;
}
static int ezport_write_enable ( struct spi_device * spi )
{
int ret = 0 , retries = 3 ;
for ( retries = 0 ; retries < 3 ; retries + + ) {
ret = ezport_send_simple ( spi , EZPORT_CMD_WREN ) ;
if ( ret > 0 & & ret & EZPORT_STATUS_WEN )
break ;
}
if ( ! ( ret & EZPORT_STATUS_WEN ) ) {
dev_err ( & spi - > dev , " EzPort write enable timed out \n " ) ;
return - ETIMEDOUT ;
}
return 0 ;
}
static int ezport_bulk_erase ( struct spi_device * spi )
{
int ret ;
static const u8 cmd = EZPORT_CMD_BE ;
dev_dbg ( & spi - > dev , " EzPort bulk erase... \n " ) ;
ret = ezport_write_enable ( spi ) ;
if ( ret < 0 )
return ret ;
ret = spi_write ( spi , & cmd , 1 ) ;
if ( ret < 0 )
return ret ;
ret = ezport_wait_write ( spi , 1000 ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static int ezport_section_erase ( struct spi_device * spi , u32 address )
{
u8 query [ ] = { EZPORT_CMD_SE , ( address > > 16 ) & 0xff , ( address > > 8 ) & 0xff , address & 0xff } ;
int ret ;
dev_dbg ( & spi - > dev , " Ezport section erase @ 0x%06x... \n " , address ) ;
if ( address & EZPORT_SECTOR_MASK )
return - EINVAL ;
ret = ezport_write_enable ( spi ) ;
if ( ret < 0 )
return ret ;
ret = spi_write ( spi , query , sizeof ( query ) ) ;
if ( ret < 0 )
return ret ;
return ezport_wait_write ( spi , 200 ) ;
}
static int ezport_flash_transfer ( struct spi_device * spi , u32 address ,
const u8 * payload , size_t payload_size )
{
struct spi_transfer xfers [ 2 ] = { } ;
u8 * command ;
int ret ;
dev_dbg ( & spi - > dev , " EzPort write %zu bytes @ 0x%06x... \n " , payload_size , address ) ;
ret = ezport_write_enable ( spi ) ;
if ( ret < 0 )
return ret ;
command = kmalloc ( 4 , GFP_KERNEL | GFP_DMA ) ;
if ( ! command )
return - ENOMEM ;
command [ 0 ] = EZPORT_CMD_SP ;
command [ 1 ] = address > > 16 ;
command [ 2 ] = address > > 8 ;
command [ 3 ] = address > > 0 ;
xfers [ 0 ] . tx_buf = command ;
xfers [ 0 ] . len = 4 ;
xfers [ 1 ] . tx_buf = payload ;
xfers [ 1 ] . len = payload_size ;
ret = spi_sync_transfer ( spi , xfers , 2 ) ;
kfree ( command ) ;
if ( ret < 0 )
return ret ;
return ezport_wait_write ( spi , 40 ) ;
}
static int ezport_flash_compare ( struct spi_device * spi , u32 address ,
const u8 * payload , size_t payload_size )
{
struct spi_transfer xfers [ 2 ] = { } ;
u8 * buffer ;
int ret ;
buffer = kmalloc ( payload_size + 5 , GFP_KERNEL | GFP_DMA ) ;
if ( ! buffer )
return - ENOMEM ;
buffer [ 0 ] = EZPORT_CMD_FAST_READ ;
buffer [ 1 ] = address > > 16 ;
buffer [ 2 ] = address > > 8 ;
buffer [ 3 ] = address > > 0 ;
xfers [ 0 ] . tx_buf = buffer ;
xfers [ 0 ] . len = 4 ;
xfers [ 0 ] . speed_hz = ACHC_FAST_READ_FREQ_HZ ;
xfers [ 1 ] . rx_buf = buffer + 4 ;
xfers [ 1 ] . len = payload_size + 1 ;
xfers [ 1 ] . speed_hz = ACHC_FAST_READ_FREQ_HZ ;
ret = spi_sync_transfer ( spi , xfers , 2 ) ;
if ( ret )
goto err ;
/* FAST_READ receives one dummy byte before the real data */
ret = memcmp ( payload , buffer + 4 + 1 , payload_size ) ;
if ( ret ) {
ret = - EBADMSG ;
2021-08-16 00:42:06 +03:00
dev_dbg ( & spi - > dev , " Verification failure @ %06x " , address ) ;
2021-08-02 20:23:09 +03:00
print_hex_dump_bytes ( " fw: " , DUMP_PREFIX_OFFSET , payload , payload_size ) ;
print_hex_dump_bytes ( " dev: " , DUMP_PREFIX_OFFSET , buffer + 4 , payload_size ) ;
}
err :
kfree ( buffer ) ;
return ret ;
}
static int ezport_firmware_compare_data ( struct spi_device * spi ,
const u8 * data , size_t size )
{
int ret ;
size_t address = 0 ;
size_t transfer_size ;
dev_dbg ( & spi - > dev , " EzPort compare data with %zu bytes... \n " , size ) ;
ret = ezport_get_status_register ( spi ) ;
if ( ret < 0 )
return ret ;
if ( ret & EZPORT_STATUS_FS ) {
dev_info ( & spi - > dev , " Device is in secure mode (status=0x%02x)! \n " , ret ) ;
dev_info ( & spi - > dev , " FW verification is not possible \n " ) ;
return - EACCES ;
}
while ( size - address > 0 ) {
transfer_size = min ( ( size_t ) EZPORT_TRANSFER_SIZE , size - address ) ;
ret = ezport_flash_compare ( spi , address , data + address , transfer_size ) ;
if ( ret )
return ret ;
address + = transfer_size ;
}
return 0 ;
}
static int ezport_firmware_flash_data ( struct spi_device * spi ,
const u8 * data , size_t size )
{
int ret ;
size_t address = 0 ;
size_t transfer_size ;
dev_dbg ( & spi - > dev , " EzPort flash data with %zu bytes... \n " , size ) ;
ret = ezport_get_status_register ( spi ) ;
if ( ret < 0 )
return ret ;
if ( ret & EZPORT_STATUS_FS ) {
ret = ezport_bulk_erase ( spi ) ;
if ( ret < 0 )
return ret ;
if ( ret & EZPORT_STATUS_FS )
return - EINVAL ;
}
while ( size - address > 0 ) {
if ( ! ( address & EZPORT_SECTOR_MASK ) ) {
ret = ezport_section_erase ( spi , address ) ;
if ( ret < 0 )
return ret ;
if ( ret & EZPORT_STATUS_WIP | | ret & EZPORT_STATUS_WEF )
return - EIO ;
}
transfer_size = min ( ( size_t ) EZPORT_TRANSFER_SIZE , size - address ) ;
ret = ezport_flash_transfer ( spi , address ,
data + address , transfer_size ) ;
if ( ret < 0 )
return ret ;
else if ( ret & EZPORT_STATUS_WIP )
return - ETIMEDOUT ;
else if ( ret & EZPORT_STATUS_WEF )
return - EIO ;
address + = transfer_size ;
}
dev_dbg ( & spi - > dev , " EzPort verify flashed data... \n " ) ;
ret = ezport_firmware_compare_data ( spi , data , size ) ;
/* allow missing FW verfication in secure mode */
if ( ret = = - EACCES )
ret = 0 ;
if ( ret < 0 )
dev_err ( & spi - > dev , " Failed to verify flashed data: %d \n " , ret ) ;
ret = ezport_soft_reset ( spi ) ;
if ( ret < 0 )
dev_warn ( & spi - > dev , " EzPort reset failed! \n " ) ;
return ret ;
}
static int ezport_firmware_load ( struct spi_device * spi , const char * fwname )
{
const struct firmware * fw ;
int ret ;
ret = request_firmware ( & fw , fwname , & spi - > dev ) ;
if ( ret ) {
dev_err ( & spi - > dev , " Could not get firmware: %d \n " , ret ) ;
return ret ;
}
ret = ezport_firmware_flash_data ( spi , fw - > data , fw - > size ) ;
release_firmware ( fw ) ;
return ret ;
}
/**
* ezport_flash - flash device firmware
* @ spi : SPI device for NXP EzPort interface
* @ reset : the gpio connected to the device reset pin
* @ fwname : filename of the firmware that should be flashed
*
* Context : can sleep
*
* Return : 0 on success ; negative errno on failure
*/
static int ezport_flash ( struct spi_device * spi , struct gpio_desc * reset , const char * fwname )
{
int ret ;
ret = ezport_start_programming ( spi , reset ) ;
if ( ret )
return ret ;
ret = ezport_firmware_load ( spi , fwname ) ;
ezport_stop_programming ( spi , reset ) ;
if ( ret )
dev_err ( & spi - > dev , " Failed to flash firmware: %d \n " , ret ) ;
else
dev_dbg ( & spi - > dev , " Finished FW flashing! \n " ) ;
return ret ;
}
static ssize_t update_firmware_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct achc_data * achc = dev_get_drvdata ( dev ) ;
unsigned long value ;
int ret ;
ret = kstrtoul ( buf , 0 , & value ) ;
if ( ret < 0 | | value ! = 1 )
return - EINVAL ;
mutex_lock ( & achc - > device_lock ) ;
ret = ezport_flash ( achc - > ezport , achc - > reset , " achc.bin " ) ;
mutex_unlock ( & achc - > device_lock ) ;
if ( ret < 0 )
return ret ;
return count ;
}
static DEVICE_ATTR_WO ( update_firmware ) ;
static ssize_t reset_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct achc_data * achc = dev_get_drvdata ( dev ) ;
int ret ;
mutex_lock ( & achc - > device_lock ) ;
ret = gpiod_get_value ( achc - > reset ) ;
mutex_unlock ( & achc - > device_lock ) ;
if ( ret < 0 )
return ret ;
return sysfs_emit ( buf , " %d \n " , ret ) ;
}
static ssize_t reset_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
{
struct achc_data * achc = dev_get_drvdata ( dev ) ;
unsigned long value ;
int ret ;
ret = kstrtoul ( buf , 0 , & value ) ;
if ( ret < 0 | | value > 1 )
return - EINVAL ;
mutex_lock ( & achc - > device_lock ) ;
gpiod_set_value ( achc - > reset , value ) ;
mutex_unlock ( & achc - > device_lock ) ;
return count ;
}
static DEVICE_ATTR_RW ( reset ) ;
static struct attribute * gehc_achc_attrs [ ] = {
& dev_attr_update_firmware . attr ,
& dev_attr_reset . attr ,
NULL ,
} ;
ATTRIBUTE_GROUPS ( gehc_achc ) ;
static void unregister_ezport ( void * data )
{
struct spi_device * ezport = data ;
spi_unregister_device ( ezport ) ;
}
static int gehc_achc_probe ( struct spi_device * spi )
{
struct achc_data * achc ;
int ezport_reg , ret ;
spi - > max_speed_hz = ACHC_MAX_FREQ_HZ ;
spi - > bits_per_word = 8 ;
spi - > mode = SPI_MODE_0 ;
achc = devm_kzalloc ( & spi - > dev , sizeof ( * achc ) , GFP_KERNEL ) ;
if ( ! achc )
return - ENOMEM ;
spi_set_drvdata ( spi , achc ) ;
achc - > main = spi ;
mutex_init ( & achc - > device_lock ) ;
ret = of_property_read_u32_index ( spi - > dev . of_node , " reg " , 1 , & ezport_reg ) ;
if ( ret )
return dev_err_probe ( & spi - > dev , ret , " missing second reg entry! \n " ) ;
achc - > ezport = spi_new_ancillary_device ( spi , ezport_reg ) ;
if ( IS_ERR ( achc - > ezport ) )
return PTR_ERR ( achc - > ezport ) ;
ret = devm_add_action_or_reset ( & spi - > dev , unregister_ezport , achc - > ezport ) ;
if ( ret )
return ret ;
achc - > reset = devm_gpiod_get ( & spi - > dev , " reset " , GPIOD_OUT_LOW ) ;
if ( IS_ERR ( achc - > reset ) )
return dev_err_probe ( & spi - > dev , PTR_ERR ( achc - > reset ) , " Could not get reset gpio \n " ) ;
return 0 ;
}
static const struct spi_device_id gehc_achc_id [ ] = {
{ " ge,achc " , 0 } ,
2021-09-23 22:46:09 +03:00
{ " achc " , 0 } ,
2021-08-02 20:23:09 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( spi , gehc_achc_id ) ;
static const struct of_device_id gehc_achc_of_match [ ] = {
{ . compatible = " ge,achc " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , gehc_achc_of_match ) ;
static struct spi_driver gehc_achc_spi_driver = {
. driver = {
. name = " gehc-achc " ,
. of_match_table = gehc_achc_of_match ,
. dev_groups = gehc_achc_groups ,
} ,
. probe = gehc_achc_probe ,
. id_table = gehc_achc_id ,
} ;
module_spi_driver ( gehc_achc_spi_driver ) ;
MODULE_DESCRIPTION ( " GEHC ACHC driver " ) ;
MODULE_AUTHOR ( " Sebastian Reichel <sebastian.reichel@collabora.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;