2019-04-05 09:49:53 +00:00
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* Freescale DPAA2 Platforms Console Driver
*
* Copyright 2015 - 2016 Freescale Semiconductor Inc .
* Copyright 2018 NXP
*/
# define pr_fmt(fmt) "dpaa2-console: " fmt
# include <linux/module.h>
# include <linux/of_device.h>
# include <linux/of_address.h>
# include <linux/miscdevice.h>
# include <linux/uaccess.h>
# include <linux/slab.h>
# include <linux/fs.h>
# include <linux/io.h>
/* MC firmware base low/high registers indexes */
# define MCFBALR_OFFSET 0
# define MCFBAHR_OFFSET 1
/* Bit masks used to get the most/least significant part of the MC base addr */
# define MC_FW_ADDR_MASK_HIGH 0x1FFFF
# define MC_FW_ADDR_MASK_LOW 0xE0000000
# define MC_BUFFER_OFFSET 0x01000000
# define MC_BUFFER_SIZE (1024 * 1024 * 16)
# define MC_OFFSET_DELTA MC_BUFFER_OFFSET
# define AIOP_BUFFER_OFFSET 0x06000000
# define AIOP_BUFFER_SIZE (1024 * 1024 * 16)
# define AIOP_OFFSET_DELTA 0
# define LOG_HEADER_FLAG_BUFFER_WRAPAROUND 0x80000000
# define LAST_BYTE(a) ((a) & ~(LOG_HEADER_FLAG_BUFFER_WRAPAROUND))
/* MC and AIOP Magic words */
# define MAGIC_MC 0x4d430100
# define MAGIC_AIOP 0x41494F50
struct log_header {
__le32 magic_word ;
char reserved [ 4 ] ;
__le32 buf_start ;
__le32 buf_length ;
__le32 last_byte ;
} ;
struct console_data {
void __iomem * map_addr ;
struct log_header __iomem * hdr ;
void __iomem * start_addr ;
void __iomem * end_addr ;
void __iomem * end_of_data ;
void __iomem * cur_ptr ;
} ;
static struct resource mc_base_addr ;
static inline void adjust_end ( struct console_data * cd )
{
u32 last_byte = readl ( & cd - > hdr - > last_byte ) ;
cd - > end_of_data = cd - > start_addr + LAST_BYTE ( last_byte ) ;
}
static u64 get_mc_fw_base_address ( void )
{
u64 mcfwbase = 0ULL ;
u32 __iomem * mcfbaregs ;
mcfbaregs = ioremap ( mc_base_addr . start , resource_size ( & mc_base_addr ) ) ;
if ( ! mcfbaregs ) {
2019-05-21 09:56:24 +01:00
pr_err ( " could not map MC Firmware Base registers \n " ) ;
2019-04-05 09:49:53 +00:00
return 0 ;
}
mcfwbase = readl ( mcfbaregs + MCFBAHR_OFFSET ) &
MC_FW_ADDR_MASK_HIGH ;
mcfwbase < < = 32 ;
mcfwbase | = readl ( mcfbaregs + MCFBALR_OFFSET ) & MC_FW_ADDR_MASK_LOW ;
iounmap ( mcfbaregs ) ;
pr_debug ( " MC base address at 0x%016llx \n " , mcfwbase ) ;
return mcfwbase ;
}
static ssize_t dpaa2_console_size ( struct console_data * cd )
{
ssize_t size ;
if ( cd - > cur_ptr < = cd - > end_of_data )
size = cd - > end_of_data - cd - > cur_ptr ;
else
size = ( cd - > end_addr - cd - > cur_ptr ) +
( cd - > end_of_data - cd - > start_addr ) ;
return size ;
}
static int dpaa2_generic_console_open ( struct inode * node , struct file * fp ,
u64 offset , u64 size ,
u32 expected_magic ,
u32 offset_delta )
{
u32 read_magic , wrapped , last_byte , buf_start , buf_length ;
struct console_data * cd ;
u64 base_addr ;
int err ;
cd = kmalloc ( sizeof ( * cd ) , GFP_KERNEL ) ;
if ( ! cd )
return - ENOMEM ;
base_addr = get_mc_fw_base_address ( ) ;
if ( ! base_addr ) {
err = - EIO ;
goto err_fwba ;
}
cd - > map_addr = ioremap ( base_addr + offset , size ) ;
if ( ! cd - > map_addr ) {
pr_err ( " cannot map console log memory \n " ) ;
err = - EIO ;
goto err_ioremap ;
}
cd - > hdr = ( struct log_header __iomem * ) cd - > map_addr ;
read_magic = readl ( & cd - > hdr - > magic_word ) ;
last_byte = readl ( & cd - > hdr - > last_byte ) ;
buf_start = readl ( & cd - > hdr - > buf_start ) ;
buf_length = readl ( & cd - > hdr - > buf_length ) ;
if ( read_magic ! = expected_magic ) {
pr_warn ( " expected = %08x, read = %08x \n " ,
expected_magic , read_magic ) ;
err = - EIO ;
goto err_magic ;
}
cd - > start_addr = cd - > map_addr + buf_start - offset_delta ;
cd - > end_addr = cd - > start_addr + buf_length ;
wrapped = last_byte & LOG_HEADER_FLAG_BUFFER_WRAPAROUND ;
adjust_end ( cd ) ;
if ( wrapped & & cd - > end_of_data ! = cd - > end_addr )
cd - > cur_ptr = cd - > end_of_data + 1 ;
else
cd - > cur_ptr = cd - > start_addr ;
fp - > private_data = cd ;
return 0 ;
err_magic :
iounmap ( cd - > map_addr ) ;
err_ioremap :
err_fwba :
kfree ( cd ) ;
return err ;
}
static int dpaa2_mc_console_open ( struct inode * node , struct file * fp )
{
return dpaa2_generic_console_open ( node , fp ,
MC_BUFFER_OFFSET , MC_BUFFER_SIZE ,
MAGIC_MC , MC_OFFSET_DELTA ) ;
}
static int dpaa2_aiop_console_open ( struct inode * node , struct file * fp )
{
return dpaa2_generic_console_open ( node , fp ,
AIOP_BUFFER_OFFSET , AIOP_BUFFER_SIZE ,
MAGIC_AIOP , AIOP_OFFSET_DELTA ) ;
}
static int dpaa2_console_close ( struct inode * node , struct file * fp )
{
struct console_data * cd = fp - > private_data ;
iounmap ( cd - > map_addr ) ;
kfree ( cd ) ;
return 0 ;
}
static ssize_t dpaa2_console_read ( struct file * fp , char __user * buf ,
size_t count , loff_t * f_pos )
{
struct console_data * cd = fp - > private_data ;
size_t bytes = dpaa2_console_size ( cd ) ;
size_t bytes_end = cd - > end_addr - cd - > cur_ptr ;
size_t written = 0 ;
void * kbuf ;
int err ;
/* Check if we need to adjust the end of data addr */
adjust_end ( cd ) ;
if ( cd - > end_of_data = = cd - > cur_ptr )
return 0 ;
if ( count < bytes )
bytes = count ;
kbuf = kmalloc ( bytes , GFP_KERNEL ) ;
if ( ! kbuf )
return - ENOMEM ;
if ( bytes > bytes_end ) {
memcpy_fromio ( kbuf , cd - > cur_ptr , bytes_end ) ;
if ( copy_to_user ( buf , kbuf , bytes_end ) ) {
err = - EFAULT ;
goto err_free_buf ;
}
buf + = bytes_end ;
cd - > cur_ptr = cd - > start_addr ;
bytes - = bytes_end ;
written + = bytes_end ;
}
memcpy_fromio ( kbuf , cd - > cur_ptr , bytes ) ;
if ( copy_to_user ( buf , kbuf , bytes ) ) {
err = - EFAULT ;
goto err_free_buf ;
}
cd - > cur_ptr + = bytes ;
written + = bytes ;
2021-04-23 12:01:51 +03:00
kfree ( kbuf ) ;
2019-04-05 09:49:53 +00:00
return written ;
err_free_buf :
kfree ( kbuf ) ;
return err ;
}
static const struct file_operations dpaa2_mc_console_fops = {
. owner = THIS_MODULE ,
. open = dpaa2_mc_console_open ,
. release = dpaa2_console_close ,
. read = dpaa2_console_read ,
} ;
static struct miscdevice dpaa2_mc_console_dev = {
. minor = MISC_DYNAMIC_MINOR ,
. name = " dpaa2_mc_console " ,
. fops = & dpaa2_mc_console_fops
} ;
static const struct file_operations dpaa2_aiop_console_fops = {
. owner = THIS_MODULE ,
. open = dpaa2_aiop_console_open ,
. release = dpaa2_console_close ,
. read = dpaa2_console_read ,
} ;
static struct miscdevice dpaa2_aiop_console_dev = {
. minor = MISC_DYNAMIC_MINOR ,
. name = " dpaa2_aiop_console " ,
. fops = & dpaa2_aiop_console_fops
} ;
static int dpaa2_console_probe ( struct platform_device * pdev )
{
int error ;
error = of_address_to_resource ( pdev - > dev . of_node , 0 , & mc_base_addr ) ;
if ( error < 0 ) {
pr_err ( " of_address_to_resource() failed for %pOF with %d \n " ,
pdev - > dev . of_node , error ) ;
return error ;
}
error = misc_register ( & dpaa2_mc_console_dev ) ;
if ( error ) {
pr_err ( " cannot register device %s \n " ,
dpaa2_mc_console_dev . name ) ;
goto err_register_mc ;
}
error = misc_register ( & dpaa2_aiop_console_dev ) ;
if ( error ) {
pr_err ( " cannot register device %s \n " ,
dpaa2_aiop_console_dev . name ) ;
goto err_register_aiop ;
}
return 0 ;
err_register_aiop :
misc_deregister ( & dpaa2_mc_console_dev ) ;
err_register_mc :
return error ;
}
static int dpaa2_console_remove ( struct platform_device * pdev )
{
misc_deregister ( & dpaa2_mc_console_dev ) ;
misc_deregister ( & dpaa2_aiop_console_dev ) ;
return 0 ;
}
static const struct of_device_id dpaa2_console_match_table [ ] = {
{ . compatible = " fsl,dpaa2-console " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , dpaa2_console_match_table ) ;
static struct platform_driver dpaa2_console_driver = {
. driver = {
. name = " dpaa2-console " ,
. pm = NULL ,
. of_match_table = dpaa2_console_match_table ,
} ,
. probe = dpaa2_console_probe ,
. remove = dpaa2_console_remove ,
} ;
module_platform_driver ( dpaa2_console_driver ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;
MODULE_AUTHOR ( " Roy Pledge <roy.pledge@nxp.com> " ) ;
MODULE_DESCRIPTION ( " DPAA2 console driver " ) ;