2013-01-22 03:38:47 +04:00
/*
* Copyright ( C ) 2007 Google , Inc .
* Copyright ( C ) 2012 Intel , Inc .
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* 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 .
*
*/
# include <linux/console.h>
# include <linux/interrupt.h>
# include <linux/platform_device.h>
# include <linux/tty.h>
# include <linux/tty_flip.h>
# include <linux/slab.h>
# include <linux/io.h>
# include <linux/module.h>
2014-05-12 19:57:05 +04:00
# include <linux/goldfish.h>
2013-01-22 03:38:47 +04:00
enum {
GOLDFISH_TTY_PUT_CHAR = 0x00 ,
GOLDFISH_TTY_BYTES_READY = 0x04 ,
GOLDFISH_TTY_CMD = 0x08 ,
GOLDFISH_TTY_DATA_PTR = 0x10 ,
GOLDFISH_TTY_DATA_LEN = 0x14 ,
2014-05-12 19:55:26 +04:00
GOLDFISH_TTY_DATA_PTR_HIGH = 0x18 ,
2013-01-22 03:38:47 +04:00
GOLDFISH_TTY_CMD_INT_DISABLE = 0 ,
GOLDFISH_TTY_CMD_INT_ENABLE = 1 ,
GOLDFISH_TTY_CMD_WRITE_BUFFER = 2 ,
GOLDFISH_TTY_CMD_READ_BUFFER = 3 ,
} ;
struct goldfish_tty {
struct tty_port port ;
spinlock_t lock ;
void __iomem * base ;
u32 irq ;
int opencount ;
struct console console ;
} ;
static DEFINE_MUTEX ( goldfish_tty_lock ) ;
static struct tty_driver * goldfish_tty_driver ;
static u32 goldfish_tty_line_count = 8 ;
static u32 goldfish_tty_current_line_count ;
static struct goldfish_tty * goldfish_ttys ;
static void goldfish_tty_do_write ( int line , const char * buf , unsigned count )
{
unsigned long irq_flags ;
struct goldfish_tty * qtty = & goldfish_ttys [ line ] ;
void __iomem * base = qtty - > base ;
spin_lock_irqsave ( & qtty - > lock , irq_flags ) ;
2014-05-12 19:57:05 +04:00
gf_write64 ( ( u64 ) buf , base + GOLDFISH_TTY_DATA_PTR ,
base + GOLDFISH_TTY_DATA_PTR_HIGH ) ;
2013-01-22 03:38:47 +04:00
writel ( count , base + GOLDFISH_TTY_DATA_LEN ) ;
writel ( GOLDFISH_TTY_CMD_WRITE_BUFFER , base + GOLDFISH_TTY_CMD ) ;
spin_unlock_irqrestore ( & qtty - > lock , irq_flags ) ;
}
static irqreturn_t goldfish_tty_interrupt ( int irq , void * dev_id )
{
struct platform_device * pdev = dev_id ;
struct goldfish_tty * qtty = & goldfish_ttys [ pdev - > id ] ;
void __iomem * base = qtty - > base ;
unsigned long irq_flags ;
unsigned char * buf ;
u32 count ;
count = readl ( base + GOLDFISH_TTY_BYTES_READY ) ;
2014-05-12 19:57:14 +04:00
if ( count = = 0 )
2013-01-22 03:38:47 +04:00
return IRQ_NONE ;
2013-01-25 19:05:30 +04:00
count = tty_prepare_flip_string ( & qtty - > port , & buf , count ) ;
spin_lock_irqsave ( & qtty - > lock , irq_flags ) ;
2014-05-12 19:57:05 +04:00
gf_write64 ( ( u64 ) buf , base + GOLDFISH_TTY_DATA_PTR ,
base + GOLDFISH_TTY_DATA_PTR_HIGH ) ;
2013-01-25 19:05:30 +04:00
writel ( count , base + GOLDFISH_TTY_DATA_LEN ) ;
writel ( GOLDFISH_TTY_CMD_READ_BUFFER , base + GOLDFISH_TTY_CMD ) ;
spin_unlock_irqrestore ( & qtty - > lock , irq_flags ) ;
tty_schedule_flip ( & qtty - > port ) ;
2013-01-22 03:38:47 +04:00
return IRQ_HANDLED ;
}
static int goldfish_tty_activate ( struct tty_port * port , struct tty_struct * tty )
{
2014-05-12 19:57:14 +04:00
struct goldfish_tty * qtty = container_of ( port , struct goldfish_tty ,
port ) ;
2013-01-22 03:38:47 +04:00
writel ( GOLDFISH_TTY_CMD_INT_ENABLE , qtty - > base + GOLDFISH_TTY_CMD ) ;
return 0 ;
}
static void goldfish_tty_shutdown ( struct tty_port * port )
{
2014-05-12 19:57:14 +04:00
struct goldfish_tty * qtty = container_of ( port , struct goldfish_tty ,
port ) ;
2013-01-22 03:38:47 +04:00
writel ( GOLDFISH_TTY_CMD_INT_DISABLE , qtty - > base + GOLDFISH_TTY_CMD ) ;
}
2014-05-12 19:57:14 +04:00
static int goldfish_tty_open ( struct tty_struct * tty , struct file * filp )
2013-01-22 03:38:47 +04:00
{
struct goldfish_tty * qtty = & goldfish_ttys [ tty - > index ] ;
return tty_port_open ( & qtty - > port , tty , filp ) ;
}
2014-05-12 19:57:14 +04:00
static void goldfish_tty_close ( struct tty_struct * tty , struct file * filp )
2013-01-22 03:38:47 +04:00
{
tty_port_close ( tty - > port , tty , filp ) ;
}
static void goldfish_tty_hangup ( struct tty_struct * tty )
{
tty_port_hangup ( tty - > port ) ;
}
2014-05-12 19:57:14 +04:00
static int goldfish_tty_write ( struct tty_struct * tty , const unsigned char * buf ,
int count )
2013-01-22 03:38:47 +04:00
{
goldfish_tty_do_write ( tty - > index , buf , count ) ;
return count ;
}
static int goldfish_tty_write_room ( struct tty_struct * tty )
{
return 0x10000 ;
}
static int goldfish_tty_chars_in_buffer ( struct tty_struct * tty )
{
struct goldfish_tty * qtty = & goldfish_ttys [ tty - > index ] ;
void __iomem * base = qtty - > base ;
return readl ( base + GOLDFISH_TTY_BYTES_READY ) ;
}
2014-05-12 19:57:14 +04:00
static void goldfish_tty_console_write ( struct console * co , const char * b ,
unsigned count )
2013-01-22 03:38:47 +04:00
{
goldfish_tty_do_write ( co - > index , b , count ) ;
}
2014-05-12 19:57:14 +04:00
static struct tty_driver * goldfish_tty_console_device ( struct console * c ,
int * index )
2013-01-22 03:38:47 +04:00
{
* index = c - > index ;
return goldfish_tty_driver ;
}
static int goldfish_tty_console_setup ( struct console * co , char * options )
{
2014-05-12 19:57:14 +04:00
if ( ( unsigned ) co - > index > goldfish_tty_line_count )
2013-01-22 03:38:47 +04:00
return - ENODEV ;
2014-09-28 22:10:17 +04:00
if ( ! goldfish_ttys [ co - > index ] . base )
2013-01-22 03:38:47 +04:00
return - ENODEV ;
return 0 ;
}
static struct tty_port_operations goldfish_port_ops = {
. activate = goldfish_tty_activate ,
. shutdown = goldfish_tty_shutdown
} ;
2014-05-12 19:57:14 +04:00
static const struct tty_operations goldfish_tty_ops = {
2013-01-22 03:38:47 +04:00
. open = goldfish_tty_open ,
. close = goldfish_tty_close ,
. hangup = goldfish_tty_hangup ,
. write = goldfish_tty_write ,
. write_room = goldfish_tty_write_room ,
. chars_in_buffer = goldfish_tty_chars_in_buffer ,
} ;
static int goldfish_tty_create_driver ( void )
{
int ret ;
struct tty_driver * tty ;
2014-05-12 19:57:14 +04:00
goldfish_ttys = kzalloc ( sizeof ( * goldfish_ttys ) *
goldfish_tty_line_count , GFP_KERNEL ) ;
if ( goldfish_ttys = = NULL ) {
2013-01-22 03:38:47 +04:00
ret = - ENOMEM ;
goto err_alloc_goldfish_ttys_failed ;
}
tty = alloc_tty_driver ( goldfish_tty_line_count ) ;
2014-05-12 19:57:14 +04:00
if ( tty = = NULL ) {
2013-01-22 03:38:47 +04:00
ret = - ENOMEM ;
goto err_alloc_tty_driver_failed ;
}
tty - > driver_name = " goldfish " ;
tty - > name = " ttyGF " ;
tty - > type = TTY_DRIVER_TYPE_SERIAL ;
tty - > subtype = SERIAL_TYPE_NORMAL ;
tty - > init_termios = tty_std_termios ;
2014-05-12 19:57:14 +04:00
tty - > flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW |
TTY_DRIVER_DYNAMIC_DEV ;
2013-01-22 03:38:47 +04:00
tty_set_operations ( tty , & goldfish_tty_ops ) ;
ret = tty_register_driver ( tty ) ;
2014-05-12 19:57:14 +04:00
if ( ret )
2013-01-22 03:38:47 +04:00
goto err_tty_register_driver_failed ;
goldfish_tty_driver = tty ;
return 0 ;
err_tty_register_driver_failed :
put_tty_driver ( tty ) ;
err_alloc_tty_driver_failed :
kfree ( goldfish_ttys ) ;
goldfish_ttys = NULL ;
err_alloc_goldfish_ttys_failed :
return ret ;
}
static void goldfish_tty_delete_driver ( void )
{
tty_unregister_driver ( goldfish_tty_driver ) ;
put_tty_driver ( goldfish_tty_driver ) ;
goldfish_tty_driver = NULL ;
kfree ( goldfish_ttys ) ;
goldfish_ttys = NULL ;
}
static int goldfish_tty_probe ( struct platform_device * pdev )
{
struct goldfish_tty * qtty ;
int ret = - EINVAL ;
int i ;
struct resource * r ;
struct device * ttydev ;
void __iomem * base ;
u32 irq ;
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2014-05-12 19:57:14 +04:00
if ( r = = NULL )
2013-01-22 03:38:47 +04:00
return - EINVAL ;
base = ioremap ( r - > start , 0x1000 ) ;
if ( base = = NULL )
pr_err ( " goldfish_tty: unable to remap base \n " ) ;
r = platform_get_resource ( pdev , IORESOURCE_IRQ , 0 ) ;
2014-05-12 19:57:14 +04:00
if ( r = = NULL )
2013-01-22 03:38:47 +04:00
goto err_unmap ;
irq = r - > start ;
2014-05-12 19:57:14 +04:00
if ( pdev - > id > = goldfish_tty_line_count )
2013-01-22 03:38:47 +04:00
goto err_unmap ;
mutex_lock ( & goldfish_tty_lock ) ;
2014-05-12 19:57:14 +04:00
if ( goldfish_tty_current_line_count = = 0 ) {
2013-01-22 03:38:47 +04:00
ret = goldfish_tty_create_driver ( ) ;
2014-05-12 19:57:14 +04:00
if ( ret )
2013-01-22 03:38:47 +04:00
goto err_create_driver_failed ;
}
goldfish_tty_current_line_count + + ;
qtty = & goldfish_ttys [ pdev - > id ] ;
spin_lock_init ( & qtty - > lock ) ;
tty_port_init ( & qtty - > port ) ;
qtty - > port . ops = & goldfish_port_ops ;
qtty - > base = base ;
qtty - > irq = irq ;
writel ( GOLDFISH_TTY_CMD_INT_DISABLE , base + GOLDFISH_TTY_CMD ) ;
2014-05-12 19:57:14 +04:00
ret = request_irq ( irq , goldfish_tty_interrupt , IRQF_SHARED ,
" goldfish_tty " , pdev ) ;
if ( ret )
2013-01-22 03:38:47 +04:00
goto err_request_irq_failed ;
ttydev = tty_port_register_device ( & qtty - > port , goldfish_tty_driver ,
pdev - > id , & pdev - > dev ) ;
2014-05-12 19:57:14 +04:00
if ( IS_ERR ( ttydev ) ) {
2013-01-22 03:38:47 +04:00
ret = PTR_ERR ( ttydev ) ;
goto err_tty_register_device_failed ;
}
strcpy ( qtty - > console . name , " ttyGF " ) ;
qtty - > console . write = goldfish_tty_console_write ;
qtty - > console . device = goldfish_tty_console_device ;
qtty - > console . setup = goldfish_tty_console_setup ;
qtty - > console . flags = CON_PRINTBUFFER ;
qtty - > console . index = pdev - > id ;
register_console ( & qtty - > console ) ;
mutex_unlock ( & goldfish_tty_lock ) ;
return 0 ;
tty_unregister_device ( goldfish_tty_driver , i ) ;
err_tty_register_device_failed :
free_irq ( irq , pdev ) ;
err_request_irq_failed :
goldfish_tty_current_line_count - - ;
2014-05-12 19:57:14 +04:00
if ( goldfish_tty_current_line_count = = 0 )
2013-01-22 03:38:47 +04:00
goldfish_tty_delete_driver ( ) ;
err_create_driver_failed :
mutex_unlock ( & goldfish_tty_lock ) ;
err_unmap :
iounmap ( base ) ;
return ret ;
}
static int goldfish_tty_remove ( struct platform_device * pdev )
{
struct goldfish_tty * qtty ;
mutex_lock ( & goldfish_tty_lock ) ;
qtty = & goldfish_ttys [ pdev - > id ] ;
unregister_console ( & qtty - > console ) ;
tty_unregister_device ( goldfish_tty_driver , pdev - > id ) ;
iounmap ( qtty - > base ) ;
2014-09-28 22:10:17 +04:00
qtty - > base = NULL ;
2013-01-22 03:38:47 +04:00
free_irq ( qtty - > irq , pdev ) ;
goldfish_tty_current_line_count - - ;
2014-05-12 19:57:14 +04:00
if ( goldfish_tty_current_line_count = = 0 )
2013-01-22 03:38:47 +04:00
goldfish_tty_delete_driver ( ) ;
mutex_unlock ( & goldfish_tty_lock ) ;
return 0 ;
}
static struct platform_driver goldfish_tty_platform_driver = {
. probe = goldfish_tty_probe ,
. remove = goldfish_tty_remove ,
. driver = {
. name = " goldfish_tty "
}
} ;
module_platform_driver ( goldfish_tty_platform_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;