2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2016-12-14 13:16:12 +02:00
/*
* Copyright ( c ) 2015 , NVIDIA Corporation .
*/
# include <linux/platform_device.h>
# include <linux/dma-mapping.h>
# include <linux/firmware.h>
# include <linux/pci_ids.h>
# include <linux/iopoll.h>
# include "falcon.h"
# include "drm.h"
enum falcon_memory {
FALCON_MEMORY_IMEM ,
FALCON_MEMORY_DATA ,
} ;
static void falcon_writel ( struct falcon * falcon , u32 value , u32 offset )
{
writel ( value , falcon - > regs + offset ) ;
}
int falcon_wait_idle ( struct falcon * falcon )
{
u32 value ;
return readl_poll_timeout ( falcon - > regs + FALCON_IDLESTATE , value ,
( value = = 0 ) , 10 , 100000 ) ;
}
static int falcon_dma_wait_idle ( struct falcon * falcon )
{
u32 value ;
return readl_poll_timeout ( falcon - > regs + FALCON_DMATRFCMD , value ,
( value & FALCON_DMATRFCMD_IDLE ) , 10 , 100000 ) ;
}
static int falcon_copy_chunk ( struct falcon * falcon ,
phys_addr_t base ,
unsigned long offset ,
enum falcon_memory target )
{
u32 cmd = FALCON_DMATRFCMD_SIZE_256B ;
if ( target = = FALCON_MEMORY_IMEM )
cmd | = FALCON_DMATRFCMD_IMEM ;
falcon_writel ( falcon , offset , FALCON_DMATRFMOFFS ) ;
falcon_writel ( falcon , base , FALCON_DMATRFFBOFFS ) ;
falcon_writel ( falcon , cmd , FALCON_DMATRFCMD ) ;
return falcon_dma_wait_idle ( falcon ) ;
}
static void falcon_copy_firmware_image ( struct falcon * falcon ,
const struct firmware * firmware )
{
2019-10-28 13:37:16 +01:00
u32 * virt = falcon - > firmware . virt ;
2016-12-14 13:16:12 +02:00
size_t i ;
/* copy the whole thing taking into account endianness */
for ( i = 0 ; i < firmware - > size / sizeof ( u32 ) ; i + + )
2019-10-28 13:37:16 +01:00
virt [ i ] = le32_to_cpu ( ( ( u32 * ) firmware - > data ) [ i ] ) ;
2016-12-14 13:16:12 +02:00
}
static int falcon_parse_firmware_image ( struct falcon * falcon )
{
2019-10-28 13:37:16 +01:00
struct falcon_fw_bin_header_v1 * bin = ( void * ) falcon - > firmware . virt ;
2016-12-14 13:16:12 +02:00
struct falcon_fw_os_header_v1 * os ;
/* endian problems would show up right here */
if ( bin - > magic ! = PCI_VENDOR_ID_NVIDIA ) {
dev_err ( falcon - > dev , " incorrect firmware magic \n " ) ;
return - EINVAL ;
}
/* currently only version 1 is supported */
if ( bin - > version ! = 1 ) {
dev_err ( falcon - > dev , " unsupported firmware version \n " ) ;
return - EINVAL ;
}
/* check that the firmware size is consistent */
if ( bin - > size > falcon - > firmware . size ) {
dev_err ( falcon - > dev , " firmware image size inconsistency \n " ) ;
return - EINVAL ;
}
2019-10-28 13:37:16 +01:00
os = falcon - > firmware . virt + bin - > os_header_offset ;
2016-12-14 13:16:12 +02:00
falcon - > firmware . bin_data . size = bin - > os_size ;
falcon - > firmware . bin_data . offset = bin - > os_data_offset ;
falcon - > firmware . code . offset = os - > code_offset ;
falcon - > firmware . code . size = os - > code_size ;
falcon - > firmware . data . offset = os - > data_offset ;
falcon - > firmware . data . size = os - > data_size ;
return 0 ;
}
int falcon_read_firmware ( struct falcon * falcon , const char * name )
{
int err ;
/* request_firmware prints error if it fails */
err = request_firmware ( & falcon - > firmware . firmware , name , falcon - > dev ) ;
if ( err < 0 )
return err ;
2019-10-28 13:37:15 +01:00
falcon - > firmware . size = falcon - > firmware . firmware - > size ;
2016-12-14 13:16:12 +02:00
return 0 ;
}
int falcon_load_firmware ( struct falcon * falcon )
{
const struct firmware * firmware = falcon - > firmware . firmware ;
int err ;
/* copy firmware image into local area. this also ensures endianness */
falcon_copy_firmware_image ( falcon , firmware ) ;
/* parse the image data */
err = falcon_parse_firmware_image ( falcon ) ;
if ( err < 0 ) {
dev_err ( falcon - > dev , " failed to parse firmware image \n " ) ;
2019-10-28 13:37:15 +01:00
return err ;
2016-12-14 13:16:12 +02:00
}
release_firmware ( firmware ) ;
falcon - > firmware . firmware = NULL ;
return 0 ;
}
int falcon_init ( struct falcon * falcon )
{
2019-10-28 13:37:16 +01:00
falcon - > firmware . virt = NULL ;
2016-12-14 13:16:12 +02:00
return 0 ;
}
void falcon_exit ( struct falcon * falcon )
{
2019-10-28 13:37:15 +01:00
if ( falcon - > firmware . firmware )
2016-12-14 13:16:12 +02:00
release_firmware ( falcon - > firmware . firmware ) ;
}
int falcon_boot ( struct falcon * falcon )
{
unsigned long offset ;
2018-11-23 13:06:39 +01:00
u32 value ;
2016-12-14 13:16:12 +02:00
int err ;
2019-10-28 13:37:16 +01:00
if ( ! falcon - > firmware . virt )
2016-12-14 13:16:12 +02:00
return - EINVAL ;
2018-11-23 13:06:39 +01:00
err = readl_poll_timeout ( falcon - > regs + FALCON_DMACTL , value ,
( value & ( FALCON_DMACTL_IMEM_SCRUBBING |
FALCON_DMACTL_DMEM_SCRUBBING ) ) = = 0 ,
10 , 10000 ) ;
if ( err < 0 )
return err ;
2016-12-14 13:16:12 +02:00
falcon_writel ( falcon , 0 , FALCON_DMACTL ) ;
/* setup the address of the binary data so Falcon can access it later */
2019-10-28 13:37:16 +01:00
falcon_writel ( falcon , ( falcon - > firmware . iova +
2016-12-14 13:16:12 +02:00
falcon - > firmware . bin_data . offset ) > > 8 ,
FALCON_DMATRFBASE ) ;
/* copy the data segment into Falcon internal memory */
for ( offset = 0 ; offset < falcon - > firmware . data . size ; offset + = 256 )
falcon_copy_chunk ( falcon ,
falcon - > firmware . data . offset + offset ,
offset , FALCON_MEMORY_DATA ) ;
/* copy the first code segment into Falcon internal memory */
falcon_copy_chunk ( falcon , falcon - > firmware . code . offset ,
0 , FALCON_MEMORY_IMEM ) ;
/* setup falcon interrupts */
falcon_writel ( falcon , FALCON_IRQMSET_EXT ( 0xff ) |
FALCON_IRQMSET_SWGEN1 |
FALCON_IRQMSET_SWGEN0 |
FALCON_IRQMSET_EXTERR |
FALCON_IRQMSET_HALT |
FALCON_IRQMSET_WDTMR ,
FALCON_IRQMSET ) ;
falcon_writel ( falcon , FALCON_IRQDEST_EXT ( 0xff ) |
FALCON_IRQDEST_SWGEN1 |
FALCON_IRQDEST_SWGEN0 |
FALCON_IRQDEST_EXTERR |
FALCON_IRQDEST_HALT ,
FALCON_IRQDEST ) ;
/* enable interface */
falcon_writel ( falcon , FALCON_ITFEN_MTHDEN |
FALCON_ITFEN_CTXEN ,
FALCON_ITFEN ) ;
/* boot falcon */
falcon_writel ( falcon , 0x00000000 , FALCON_BOOTVEC ) ;
falcon_writel ( falcon , FALCON_CPUCTL_STARTCPU , FALCON_CPUCTL ) ;
err = falcon_wait_idle ( falcon ) ;
if ( err < 0 ) {
dev_err ( falcon - > dev , " Falcon boot failed due to timeout \n " ) ;
return err ;
}
return 0 ;
}
void falcon_execute_method ( struct falcon * falcon , u32 method , u32 data )
{
falcon_writel ( falcon , method > > 2 , FALCON_UCLASS_METHOD_OFFSET ) ;
falcon_writel ( falcon , data , FALCON_UCLASS_METHOD_DATA ) ;
}