2013-04-10 01:20:21 +04:00
/*
* Remote processor machine - specific module for DA8XX
*
* Copyright ( C ) 2013 Texas Instruments , Inc .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation .
*/
# include <linux/bitops.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/kernel.h>
# include <linux/module.h>
2017-08-01 18:48:44 +03:00
# include <linux/of_reserved_mem.h>
2013-04-10 01:20:21 +04:00
# include <linux/platform_device.h>
# include <linux/remoteproc.h>
# include <mach/clock.h> /* for davinci_clk_reset_assert/deassert() */
# include "remoteproc_internal.h"
static char * da8xx_fw_name ;
2018-05-02 12:56:59 +03:00
module_param ( da8xx_fw_name , charp , 0444 ) ;
2013-04-10 01:20:21 +04:00
MODULE_PARM_DESC ( da8xx_fw_name ,
2015-02-28 02:18:22 +03:00
" Name of DSP firmware file in /lib/firmware (if not specified defaults to 'rproc-dsp-fw') " ) ;
2013-04-10 01:20:21 +04:00
/*
* OMAP - L138 Technical References :
* http : //www.ti.com/product/omap-l138
*/
# define SYSCFG_CHIPSIG0 BIT(0)
# define SYSCFG_CHIPSIG1 BIT(1)
# define SYSCFG_CHIPSIG2 BIT(2)
# define SYSCFG_CHIPSIG3 BIT(3)
# define SYSCFG_CHIPSIG4 BIT(4)
2017-08-01 18:48:42 +03:00
# define DA8XX_RPROC_LOCAL_ADDRESS_MASK (SZ_16M - 1)
/**
* struct da8xx_rproc_mem - internal memory structure
* @ cpu_addr : MPU virtual address of the memory region
* @ bus_addr : Bus address used to access the memory region
* @ dev_addr : Device address of the memory region from DSP view
* @ size : Size of the memory region
*/
struct da8xx_rproc_mem {
void __iomem * cpu_addr ;
phys_addr_t bus_addr ;
u32 dev_addr ;
size_t size ;
} ;
2013-04-10 01:20:21 +04:00
/**
* struct da8xx_rproc - da8xx remote processor instance state
* @ rproc : rproc handle
2017-08-01 18:48:42 +03:00
* @ mem : internal memory regions data
* @ num_mems : number of internal memory regions
2013-04-10 01:20:21 +04:00
* @ dsp_clk : placeholder for platform ' s DSP clk
* @ ack_fxn : chip - specific ack function for ack ' ing irq
* @ irq_data : ack_fxn function parameter
* @ chipsig : virt ptr to DSP interrupt registers ( CHIPSIG & CHIPSIG_CLR )
* @ bootreg : virt ptr to DSP boot address register ( HOST1CFG )
* @ irq : irq # used by this instance
*/
struct da8xx_rproc {
struct rproc * rproc ;
2017-08-01 18:48:42 +03:00
struct da8xx_rproc_mem * mem ;
int num_mems ;
2013-04-10 01:20:21 +04:00
struct clk * dsp_clk ;
void ( * ack_fxn ) ( struct irq_data * data ) ;
struct irq_data * irq_data ;
void __iomem * chipsig ;
void __iomem * bootreg ;
int irq ;
} ;
/**
* handle_event ( ) - inbound virtqueue message workqueue function
*
* This function is registered as a kernel thread and is scheduled by the
* kernel handler .
*/
static irqreturn_t handle_event ( int irq , void * p )
{
struct rproc * rproc = ( struct rproc * ) p ;
/* Process incoming buffers on all our vrings */
rproc_vq_interrupt ( rproc , 0 ) ;
rproc_vq_interrupt ( rproc , 1 ) ;
return IRQ_HANDLED ;
}
/**
* da8xx_rproc_callback ( ) - inbound virtqueue message handler
*
* This handler is invoked directly by the kernel whenever the remote
* core ( DSP ) has modified the state of a virtqueue . There is no
* " payload " message indicating the virtqueue index as is the case with
* mailbox - based implementations on OMAP4 . As such , this handler " polls "
* each known virtqueue index for every invocation .
*/
static irqreturn_t da8xx_rproc_callback ( int irq , void * p )
{
struct rproc * rproc = ( struct rproc * ) p ;
struct da8xx_rproc * drproc = ( struct da8xx_rproc * ) rproc - > priv ;
u32 chipsig ;
chipsig = readl ( drproc - > chipsig ) ;
if ( chipsig & SYSCFG_CHIPSIG0 ) {
/* Clear interrupt level source */
writel ( SYSCFG_CHIPSIG0 , drproc - > chipsig + 4 ) ;
/*
* ACK intr to AINTC .
*
* It has already been ack ' ed by the kernel before calling
* this function , but since the ARM < - > DSP interrupts in the
* CHIPSIG register are " level " instead of " pulse " variety ,
* we need to ack it after taking down the level else we ' ll
* be called again immediately after returning .
*/
drproc - > ack_fxn ( drproc - > irq_data ) ;
return IRQ_WAKE_THREAD ;
}
return IRQ_HANDLED ;
}
static int da8xx_rproc_start ( struct rproc * rproc )
{
struct device * dev = rproc - > dev . parent ;
struct da8xx_rproc * drproc = ( struct da8xx_rproc * ) rproc - > priv ;
struct clk * dsp_clk = drproc - > dsp_clk ;
2018-05-02 12:56:57 +03:00
int ret ;
2013-04-10 01:20:21 +04:00
/* hw requires the start (boot) address be on 1KB boundary */
if ( rproc - > bootaddr & 0x3ff ) {
dev_err ( dev , " invalid boot address: must be aligned to 1KB \n " ) ;
return - EINVAL ;
}
writel ( rproc - > bootaddr , drproc - > bootreg ) ;
2018-05-02 12:56:58 +03:00
ret = clk_prepare_enable ( dsp_clk ) ;
2018-05-02 12:56:57 +03:00
if ( ret ) {
2018-05-02 12:56:58 +03:00
dev_err ( dev , " clk_prepare_enable() failed: %d \n " , ret ) ;
2018-05-02 12:56:57 +03:00
return ret ;
}
2013-04-10 01:20:21 +04:00
davinci_clk_reset_deassert ( dsp_clk ) ;
return 0 ;
}
static int da8xx_rproc_stop ( struct rproc * rproc )
{
struct da8xx_rproc * drproc = rproc - > priv ;
2017-05-19 01:09:01 +03:00
davinci_clk_reset_assert ( drproc - > dsp_clk ) ;
2018-05-02 12:56:58 +03:00
clk_disable_unprepare ( drproc - > dsp_clk ) ;
2013-04-10 01:20:21 +04:00
return 0 ;
}
/* kick a virtqueue */
static void da8xx_rproc_kick ( struct rproc * rproc , int vqid )
{
struct da8xx_rproc * drproc = ( struct da8xx_rproc * ) rproc - > priv ;
2016-08-13 02:42:17 +03:00
/* Interrupt remote proc */
2013-04-10 01:20:21 +04:00
writel ( SYSCFG_CHIPSIG2 , drproc - > chipsig ) ;
}
2017-01-01 13:43:37 +03:00
static const struct rproc_ops da8xx_rproc_ops = {
2013-04-10 01:20:21 +04:00
. start = da8xx_rproc_start ,
. stop = da8xx_rproc_stop ,
. kick = da8xx_rproc_kick ,
} ;
2017-08-01 18:48:42 +03:00
static int da8xx_rproc_get_internal_memories ( struct platform_device * pdev ,
struct da8xx_rproc * drproc )
{
static const char * const mem_names [ ] = { " l2sram " , " l1pram " , " l1dram " } ;
int num_mems = ARRAY_SIZE ( mem_names ) ;
struct device * dev = & pdev - > dev ;
struct resource * res ;
int i ;
drproc - > mem = devm_kcalloc ( dev , num_mems , sizeof ( * drproc - > mem ) ,
GFP_KERNEL ) ;
if ( ! drproc - > mem )
return - ENOMEM ;
for ( i = 0 ; i < num_mems ; i + + ) {
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM ,
mem_names [ i ] ) ;
drproc - > mem [ i ] . cpu_addr = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( drproc - > mem [ i ] . cpu_addr ) ) {
dev_err ( dev , " failed to parse and map %s memory \n " ,
mem_names [ i ] ) ;
return PTR_ERR ( drproc - > mem [ i ] . cpu_addr ) ;
}
drproc - > mem [ i ] . bus_addr = res - > start ;
drproc - > mem [ i ] . dev_addr =
res - > start & DA8XX_RPROC_LOCAL_ADDRESS_MASK ;
drproc - > mem [ i ] . size = resource_size ( res ) ;
dev_dbg ( dev , " memory %8s: bus addr %pa size 0x%x va %p da 0x%x \n " ,
mem_names [ i ] , & drproc - > mem [ i ] . bus_addr ,
drproc - > mem [ i ] . size , drproc - > mem [ i ] . cpu_addr ,
drproc - > mem [ i ] . dev_addr ) ;
}
drproc - > num_mems = num_mems ;
return 0 ;
}
2013-04-10 01:20:21 +04:00
static int da8xx_rproc_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct da8xx_rproc * drproc ;
struct rproc * rproc ;
struct irq_data * irq_data ;
struct resource * bootreg_res ;
struct resource * chipsig_res ;
struct clk * dsp_clk ;
void __iomem * chipsig ;
void __iomem * bootreg ;
int irq ;
int ret ;
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
dev_err ( dev , " platform_get_irq(pdev, 0) error: %d \n " , irq ) ;
return irq ;
}
irq_data = irq_get_irq_data ( irq ) ;
if ( ! irq_data ) {
dev_err ( dev , " irq_get_irq_data(%d): NULL \n " , irq ) ;
return - EINVAL ;
}
2017-08-01 18:48:41 +03:00
bootreg_res = platform_get_resource_byname ( pdev , IORESOURCE_MEM ,
" host1cfg " ) ;
2013-04-10 01:20:21 +04:00
bootreg = devm_ioremap_resource ( dev , bootreg_res ) ;
if ( IS_ERR ( bootreg ) )
return PTR_ERR ( bootreg ) ;
2017-08-01 18:48:41 +03:00
chipsig_res = platform_get_resource_byname ( pdev , IORESOURCE_MEM ,
" chipsig " ) ;
2013-04-10 01:20:21 +04:00
chipsig = devm_ioremap_resource ( dev , chipsig_res ) ;
if ( IS_ERR ( chipsig ) )
return PTR_ERR ( chipsig ) ;
dsp_clk = devm_clk_get ( dev , NULL ) ;
if ( IS_ERR ( dsp_clk ) ) {
dev_err ( dev , " clk_get error: %ld \n " , PTR_ERR ( dsp_clk ) ) ;
return PTR_ERR ( dsp_clk ) ;
}
2017-08-01 18:48:44 +03:00
if ( dev - > of_node ) {
ret = of_reserved_mem_device_init ( dev ) ;
if ( ret ) {
dev_err ( dev , " device does not have specific CMA pool: %d \n " ,
ret ) ;
return ret ;
}
}
2013-04-10 01:20:21 +04:00
rproc = rproc_alloc ( dev , " dsp " , & da8xx_rproc_ops , da8xx_fw_name ,
sizeof ( * drproc ) ) ;
2017-08-01 18:48:44 +03:00
if ( ! rproc ) {
ret = - ENOMEM ;
goto free_mem ;
}
2013-04-10 01:20:21 +04:00
drproc = rproc - > priv ;
drproc - > rproc = rproc ;
2017-05-19 01:09:00 +03:00
drproc - > dsp_clk = dsp_clk ;
2015-01-10 00:21:58 +03:00
rproc - > has_iommu = false ;
2013-04-10 01:20:21 +04:00
2017-08-01 18:48:42 +03:00
ret = da8xx_rproc_get_internal_memories ( pdev , drproc ) ;
if ( ret )
goto free_rproc ;
2013-04-10 01:20:21 +04:00
platform_set_drvdata ( pdev , rproc ) ;
/* everything the ISR needs is now setup, so hook it up */
ret = devm_request_threaded_irq ( dev , irq , da8xx_rproc_callback ,
handle_event , 0 , " da8xx-remoteproc " ,
rproc ) ;
if ( ret ) {
dev_err ( dev , " devm_request_threaded_irq error: %d \n " , ret ) ;
goto free_rproc ;
}
/*
* rproc_add ( ) can end up enabling the DSP ' s clk with the DSP
* * not * in reset , but da8xx_rproc_start ( ) needs the DSP to be
* held in reset at the time it is called .
*/
2017-05-19 01:09:00 +03:00
ret = davinci_clk_reset_assert ( drproc - > dsp_clk ) ;
2013-04-10 01:20:21 +04:00
if ( ret )
goto free_rproc ;
drproc - > chipsig = chipsig ;
drproc - > bootreg = bootreg ;
drproc - > ack_fxn = irq_data - > chip - > irq_ack ;
drproc - > irq_data = irq_data ;
drproc - > irq = irq ;
ret = rproc_add ( rproc ) ;
if ( ret ) {
dev_err ( dev , " rproc_add failed: %d \n " , ret ) ;
goto free_rproc ;
}
return 0 ;
free_rproc :
2016-10-03 03:46:38 +03:00
rproc_free ( rproc ) ;
2017-08-01 18:48:44 +03:00
free_mem :
if ( dev - > of_node )
of_reserved_mem_device_release ( dev ) ;
2013-04-10 01:20:21 +04:00
return ret ;
}
static int da8xx_rproc_remove ( struct platform_device * pdev )
{
struct rproc * rproc = platform_get_drvdata ( pdev ) ;
struct da8xx_rproc * drproc = ( struct da8xx_rproc * ) rproc - > priv ;
2017-08-01 18:48:44 +03:00
struct device * dev = & pdev - > dev ;
2013-04-10 01:20:21 +04:00
/*
* The devm subsystem might end up releasing things before
* freeing the irq , thus allowing an interrupt to sneak in while
* the device is being removed . This should prevent that .
*/
disable_irq ( drproc - > irq ) ;
rproc_del ( rproc ) ;
2016-10-03 03:46:38 +03:00
rproc_free ( rproc ) ;
2017-08-01 18:48:44 +03:00
if ( dev - > of_node )
of_reserved_mem_device_release ( dev ) ;
2013-04-10 01:20:21 +04:00
return 0 ;
}
2017-08-01 18:48:44 +03:00
static const struct of_device_id davinci_rproc_of_match [ ] __maybe_unused = {
{ . compatible = " ti,da850-dsp " , } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , davinci_rproc_of_match ) ;
2013-04-10 01:20:21 +04:00
static struct platform_driver da8xx_rproc_driver = {
. probe = da8xx_rproc_probe ,
. remove = da8xx_rproc_remove ,
. driver = {
. name = " davinci-rproc " ,
2017-08-01 18:48:44 +03:00
. of_match_table = of_match_ptr ( davinci_rproc_of_match ) ,
2013-04-10 01:20:21 +04:00
} ,
} ;
module_platform_driver ( da8xx_rproc_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " DA8XX Remote Processor control driver " ) ;