2011-09-19 17:44:59 +00:00
/*
* opal driver interface to hvc_console . c
*
* Copyright 2011 Benjamin Herrenschmidt < benh @ kernel . crashing . org > , IBM Corp .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
*/
# undef DEBUG
# include <linux/types.h>
# include <linux/init.h>
# include <linux/delay.h>
# include <linux/slab.h>
# include <linux/console.h>
# include <linux/of.h>
# include <linux/of_platform.h>
2011-11-07 17:12:28 +11:00
# include <linux/export.h>
2015-05-15 14:06:39 +10:00
# include <linux/interrupt.h>
2011-09-19 17:44:59 +00:00
# include <asm/hvconsole.h>
# include <asm/prom.h>
# include <asm/firmware.h>
# include <asm/hvsi.h>
# include <asm/udbg.h>
# include <asm/opal.h>
# include "hvc_console.h"
static const char hvc_opal_name [ ] = " hvc_opal " ;
2015-03-16 20:17:10 +01:00
static const struct of_device_id hvc_opal_match [ ] = {
2011-09-19 17:44:59 +00:00
{ . name = " serial " , . compatible = " ibm,opal-console-raw " } ,
{ . name = " serial " , . compatible = " ibm,opal-console-hvsi " } ,
{ } ,
} ;
typedef enum hv_protocol {
HV_PROTOCOL_RAW ,
HV_PROTOCOL_HVSI
} hv_protocol_t ;
struct hvc_opal_priv {
hv_protocol_t proto ; /* Raw data or HVSI packets */
struct hvsi_priv hvsi ; /* HVSI specific data */
} ;
static struct hvc_opal_priv * hvc_opal_privs [ MAX_NR_HVC_CONSOLES ] ;
/* For early boot console */
static struct hvc_opal_priv hvc_opal_boot_priv ;
static u32 hvc_opal_boot_termno ;
static const struct hv_ops hvc_opal_raw_ops = {
. get_chars = opal_get_chars ,
. put_chars = opal_put_chars ,
. notifier_add = notifier_add_irq ,
. notifier_del = notifier_del_irq ,
. notifier_hangup = notifier_hangup_irq ,
} ;
static int hvc_opal_hvsi_get_chars ( uint32_t vtermno , char * buf , int count )
{
struct hvc_opal_priv * pv = hvc_opal_privs [ vtermno ] ;
if ( WARN_ON ( ! pv ) )
return - ENODEV ;
return hvsilib_get_chars ( & pv - > hvsi , buf , count ) ;
}
static int hvc_opal_hvsi_put_chars ( uint32_t vtermno , const char * buf , int count )
{
struct hvc_opal_priv * pv = hvc_opal_privs [ vtermno ] ;
if ( WARN_ON ( ! pv ) )
return - ENODEV ;
return hvsilib_put_chars ( & pv - > hvsi , buf , count ) ;
}
static int hvc_opal_hvsi_open ( struct hvc_struct * hp , int data )
{
struct hvc_opal_priv * pv = hvc_opal_privs [ hp - > vtermno ] ;
int rc ;
pr_devel ( " HVSI@%x: do open ! \n " , hp - > vtermno ) ;
rc = notifier_add_irq ( hp , data ) ;
if ( rc )
return rc ;
return hvsilib_open ( & pv - > hvsi , hp ) ;
}
static void hvc_opal_hvsi_close ( struct hvc_struct * hp , int data )
{
struct hvc_opal_priv * pv = hvc_opal_privs [ hp - > vtermno ] ;
pr_devel ( " HVSI@%x: do close ! \n " , hp - > vtermno ) ;
hvsilib_close ( & pv - > hvsi , hp ) ;
notifier_del_irq ( hp , data ) ;
}
void hvc_opal_hvsi_hangup ( struct hvc_struct * hp , int data )
{
struct hvc_opal_priv * pv = hvc_opal_privs [ hp - > vtermno ] ;
pr_devel ( " HVSI@%x: do hangup ! \n " , hp - > vtermno ) ;
hvsilib_close ( & pv - > hvsi , hp ) ;
notifier_hangup_irq ( hp , data ) ;
}
static int hvc_opal_hvsi_tiocmget ( struct hvc_struct * hp )
{
struct hvc_opal_priv * pv = hvc_opal_privs [ hp - > vtermno ] ;
if ( ! pv )
return - EINVAL ;
return pv - > hvsi . mctrl ;
}
static int hvc_opal_hvsi_tiocmset ( struct hvc_struct * hp , unsigned int set ,
unsigned int clear )
{
struct hvc_opal_priv * pv = hvc_opal_privs [ hp - > vtermno ] ;
pr_devel ( " HVSI@%x: Set modem control, set=%x,clr=%x \n " ,
hp - > vtermno , set , clear ) ;
if ( set & TIOCM_DTR )
hvsilib_write_mctrl ( & pv - > hvsi , 1 ) ;
else if ( clear & TIOCM_DTR )
hvsilib_write_mctrl ( & pv - > hvsi , 0 ) ;
return 0 ;
}
static const struct hv_ops hvc_opal_hvsi_ops = {
. get_chars = hvc_opal_hvsi_get_chars ,
. put_chars = hvc_opal_hvsi_put_chars ,
. notifier_add = hvc_opal_hvsi_open ,
. notifier_del = hvc_opal_hvsi_close ,
. notifier_hangup = hvc_opal_hvsi_hangup ,
. tiocmget = hvc_opal_hvsi_tiocmget ,
. tiocmset = hvc_opal_hvsi_tiocmset ,
} ;
2012-11-19 13:21:50 -05:00
static int hvc_opal_probe ( struct platform_device * dev )
2011-09-19 17:44:59 +00:00
{
const struct hv_ops * ops ;
struct hvc_struct * hp ;
struct hvc_opal_priv * pv ;
hv_protocol_t proto ;
2015-05-15 14:06:39 +10:00
unsigned int termno , irq , boot = 0 ;
2011-09-19 17:44:59 +00:00
const __be32 * reg ;
if ( of_device_is_compatible ( dev - > dev . of_node , " ibm,opal-console-raw " ) ) {
proto = HV_PROTOCOL_RAW ;
ops = & hvc_opal_raw_ops ;
} else if ( of_device_is_compatible ( dev - > dev . of_node ,
" ibm,opal-console-hvsi " ) ) {
proto = HV_PROTOCOL_HVSI ;
ops = & hvc_opal_hvsi_ops ;
} else {
2012-10-24 23:29:41 +09:00
pr_err ( " hvc_opal: Unknown protocol for %s \n " ,
2011-09-19 17:44:59 +00:00
dev - > dev . of_node - > full_name ) ;
return - ENXIO ;
}
reg = of_get_property ( dev - > dev . of_node , " reg " , NULL ) ;
termno = reg ? be32_to_cpup ( reg ) : 0 ;
/* Is it our boot one ? */
if ( hvc_opal_privs [ termno ] = = & hvc_opal_boot_priv ) {
pv = hvc_opal_privs [ termno ] ;
boot = 1 ;
} else if ( hvc_opal_privs [ termno ] = = NULL ) {
pv = kzalloc ( sizeof ( struct hvc_opal_priv ) , GFP_KERNEL ) ;
if ( ! pv )
return - ENOMEM ;
pv - > proto = proto ;
hvc_opal_privs [ termno ] = pv ;
if ( proto = = HV_PROTOCOL_HVSI )
hvsilib_init ( & pv - > hvsi , opal_get_chars , opal_put_chars ,
termno , 0 ) ;
/* Instanciate now to establish a mapping index==vtermno */
hvc_instantiate ( termno , termno , ops ) ;
} else {
pr_err ( " hvc_opal: Device %s has duplicate terminal number #%d \n " ,
dev - > dev . of_node - > full_name , termno ) ;
return - ENXIO ;
}
pr_info ( " hvc%d: %s protocol on %s%s \n " , termno ,
proto = = HV_PROTOCOL_RAW ? " raw " : " hvsi " ,
dev - > dev . of_node - > full_name ,
boot ? " (boot console) " : " " ) ;
2016-07-11 13:38:58 +10:00
irq = irq_of_parse_and_map ( dev - > dev . of_node , 0 ) ;
if ( ! irq ) {
pr_info ( " hvc%d: No interrupts property, using OPAL event \n " ,
termno ) ;
irq = opal_event_request ( ilog2 ( OPAL_EVENT_CONSOLE_INPUT ) ) ;
}
2015-05-15 14:06:39 +10:00
if ( ! irq ) {
pr_err ( " hvc_opal: Unable to map interrupt for device %s \n " ,
dev - > dev . of_node - > full_name ) ;
return irq ;
}
hp = hvc_alloc ( termno , irq , ops , MAX_VIO_PUT_CHARS ) ;
2011-09-19 17:44:59 +00:00
if ( IS_ERR ( hp ) )
return PTR_ERR ( hp ) ;
2016-07-11 13:38:57 +10:00
/* hvc consoles on powernv may need to share a single irq */
hp - > flags = IRQF_SHARED ;
2011-09-19 17:44:59 +00:00
dev_set_drvdata ( & dev - > dev , hp ) ;
return 0 ;
}
2012-11-19 13:26:18 -05:00
static int hvc_opal_remove ( struct platform_device * dev )
2011-09-19 17:44:59 +00:00
{
struct hvc_struct * hp = dev_get_drvdata ( & dev - > dev ) ;
int rc , termno ;
termno = hp - > vtermno ;
rc = hvc_remove ( hp ) ;
if ( rc = = 0 ) {
if ( hvc_opal_privs [ termno ] ! = & hvc_opal_boot_priv )
kfree ( hvc_opal_privs [ termno ] ) ;
hvc_opal_privs [ termno ] = NULL ;
}
return rc ;
}
static struct platform_driver hvc_opal_driver = {
. probe = hvc_opal_probe ,
2012-11-19 13:21:06 -05:00
. remove = hvc_opal_remove ,
2011-09-19 17:44:59 +00:00
. driver = {
. name = hvc_opal_name ,
. of_match_table = hvc_opal_match ,
}
} ;
static int __init hvc_opal_init ( void )
{
if ( ! firmware_has_feature ( FW_FEATURE_OPAL ) )
return - ENODEV ;
/* Register as a vio device to receive callbacks */
return platform_driver_register ( & hvc_opal_driver ) ;
}
2014-01-15 16:35:43 -05:00
device_initcall ( hvc_opal_init ) ;
2011-09-19 17:44:59 +00:00
static void udbg_opal_putc ( char c )
{
unsigned int termno = hvc_opal_boot_termno ;
int count = - 1 ;
if ( c = = ' \n ' )
udbg_opal_putc ( ' \r ' ) ;
do {
switch ( hvc_opal_boot_priv . proto ) {
case HV_PROTOCOL_RAW :
count = opal_put_chars ( termno , & c , 1 ) ;
break ;
case HV_PROTOCOL_HVSI :
count = hvc_opal_hvsi_put_chars ( termno , & c , 1 ) ;
break ;
}
} while ( count = = 0 | | count = = - EAGAIN ) ;
}
static int udbg_opal_getc_poll ( void )
{
unsigned int termno = hvc_opal_boot_termno ;
int rc = 0 ;
char c ;
switch ( hvc_opal_boot_priv . proto ) {
case HV_PROTOCOL_RAW :
rc = opal_get_chars ( termno , & c , 1 ) ;
break ;
case HV_PROTOCOL_HVSI :
rc = hvc_opal_hvsi_get_chars ( termno , & c , 1 ) ;
break ;
}
if ( ! rc )
return - 1 ;
return c ;
}
static int udbg_opal_getc ( void )
{
int ch ;
for ( ; ; ) {
ch = udbg_opal_getc_poll ( ) ;
if ( ch = = - 1 ) {
/* This shouldn't be needed...but... */
volatile unsigned long delay ;
for ( delay = 0 ; delay < 2000000 ; delay + + )
;
} else {
return ch ;
}
}
}
static void udbg_init_opal_common ( void )
{
udbg_putc = udbg_opal_putc ;
udbg_getc = udbg_opal_getc ;
udbg_getc_poll = udbg_opal_getc_poll ;
tb_ticks_per_usec = 0x200 ; /* Make udelay not suck */
}
void __init hvc_opal_init_early ( void )
{
2014-03-28 08:12:18 -07:00
struct device_node * stdout_node = of_node_get ( of_stdout ) ;
2013-09-23 12:05:08 +10:00
const __be32 * termno ;
2011-09-19 17:44:59 +00:00
const struct hv_ops * ops ;
u32 index ;
2014-03-28 08:12:18 -07:00
/* If the console wasn't in /chosen, try /ibm,opal */
if ( ! stdout_node ) {
2011-09-19 17:44:59 +00:00
struct device_node * opal , * np ;
/* Current OPAL takeover doesn't provide the stdout
* path , so we hard wire it
*/
opal = of_find_node_by_path ( " /ibm,opal/consoles " ) ;
if ( opal )
pr_devel ( " hvc_opal: Found consoles in new location \n " ) ;
if ( ! opal ) {
opal = of_find_node_by_path ( " /ibm,opal " ) ;
if ( opal )
pr_devel ( " hvc_opal: "
" Found consoles in old location \n " ) ;
}
if ( ! opal )
return ;
for_each_child_of_node ( opal , np ) {
if ( ! strcmp ( np - > name , " serial " ) ) {
stdout_node = np ;
break ;
}
}
of_node_put ( opal ) ;
}
if ( ! stdout_node )
return ;
termno = of_get_property ( stdout_node , " reg " , NULL ) ;
2013-09-23 12:05:08 +10:00
index = termno ? be32_to_cpup ( termno ) : 0 ;
2011-09-19 17:44:59 +00:00
if ( index > = MAX_NR_HVC_CONSOLES )
return ;
hvc_opal_privs [ index ] = & hvc_opal_boot_priv ;
/* Check the protocol */
if ( of_device_is_compatible ( stdout_node , " ibm,opal-console-raw " ) ) {
hvc_opal_boot_priv . proto = HV_PROTOCOL_RAW ;
ops = & hvc_opal_raw_ops ;
pr_devel ( " hvc_opal: Found RAW console \n " ) ;
}
else if ( of_device_is_compatible ( stdout_node , " ibm,opal-console-hvsi " ) ) {
hvc_opal_boot_priv . proto = HV_PROTOCOL_HVSI ;
ops = & hvc_opal_hvsi_ops ;
hvsilib_init ( & hvc_opal_boot_priv . hvsi , opal_get_chars ,
opal_put_chars , index , 1 ) ;
/* HVSI, perform the handshake now */
hvsilib_establish ( & hvc_opal_boot_priv . hvsi ) ;
pr_devel ( " hvc_opal: Found HVSI console \n " ) ;
} else
goto out ;
hvc_opal_boot_termno = index ;
udbg_init_opal_common ( ) ;
add_preferred_console ( " hvc " , index , NULL ) ;
hvc_instantiate ( index , index , ops ) ;
out :
of_node_put ( stdout_node ) ;
}
# ifdef CONFIG_PPC_EARLY_DEBUG_OPAL_RAW
2012-07-04 15:51:10 +10:00
void __init udbg_init_debug_opal_raw ( void )
2011-09-19 17:44:59 +00:00
{
u32 index = CONFIG_PPC_EARLY_DEBUG_OPAL_VTERMNO ;
hvc_opal_privs [ index ] = & hvc_opal_boot_priv ;
hvc_opal_boot_priv . proto = HV_PROTOCOL_RAW ;
hvc_opal_boot_termno = index ;
udbg_init_opal_common ( ) ;
}
# endif /* CONFIG_PPC_EARLY_DEBUG_OPAL_RAW */
# ifdef CONFIG_PPC_EARLY_DEBUG_OPAL_HVSI
void __init udbg_init_debug_opal_hvsi ( void )
{
u32 index = CONFIG_PPC_EARLY_DEBUG_OPAL_VTERMNO ;
hvc_opal_privs [ index ] = & hvc_opal_boot_priv ;
hvc_opal_boot_termno = index ;
udbg_init_opal_common ( ) ;
hvsilib_init ( & hvc_opal_boot_priv . hvsi , opal_get_chars , opal_put_chars ,
index , 1 ) ;
hvsilib_establish ( & hvc_opal_boot_priv . hvsi ) ;
}
# endif /* CONFIG_PPC_EARLY_DEBUG_OPAL_HVSI */