2021-01-20 09:58:27 -08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018 - 2020 Broadcom .
*/
# include <linux/tty.h>
# include <linux/tty_driver.h>
# include <linux/tty_flip.h>
# include "bcm_vk.h"
/* TTYVK base offset is 0x30000 into BAR1 */
# define BAR1_TTYVK_BASE_OFFSET 0x300000
/* Each TTYVK channel (TO or FROM) is 0x10000 */
# define BAR1_TTYVK_CHAN_OFFSET 0x100000
/* Each TTYVK channel has TO and FROM, hence the * 2 */
# define BAR1_TTYVK_BASE(index) (BAR1_TTYVK_BASE_OFFSET + \
( ( index ) * BAR1_TTYVK_CHAN_OFFSET * 2 ) )
/* TO TTYVK channel base comes before FROM for each index */
# define TO_TTYK_BASE(index) BAR1_TTYVK_BASE(index)
# define FROM_TTYK_BASE(index) (BAR1_TTYVK_BASE(index) + \
BAR1_TTYVK_CHAN_OFFSET )
struct bcm_vk_tty_chan {
u32 reserved ;
u32 size ;
u32 wr ;
u32 rd ;
u32 * data ;
} ;
# define VK_BAR_CHAN(v, DIR, e) ((v)->DIR##_offset \
+ offsetof ( struct bcm_vk_tty_chan , e ) )
# define VK_BAR_CHAN_SIZE(v, DIR) VK_BAR_CHAN(v, DIR, size)
# define VK_BAR_CHAN_WR(v, DIR) VK_BAR_CHAN(v, DIR, wr)
# define VK_BAR_CHAN_RD(v, DIR) VK_BAR_CHAN(v, DIR, rd)
# define VK_BAR_CHAN_DATA(v, DIR, off) (VK_BAR_CHAN(v, DIR, data) + (off))
# define VK_BAR0_REGSEG_TTY_DB_OFFSET 0x86c
/* Poll every 1/10 of second - temp hack till we use MSI interrupt */
# define SERIAL_TIMER_VALUE (HZ / 10)
static void bcm_vk_tty_poll ( struct timer_list * t )
{
struct bcm_vk * vk = from_timer ( vk , t , serial_timer ) ;
queue_work ( vk - > tty_wq_thread , & vk - > tty_wq_work ) ;
mod_timer ( & vk - > serial_timer , jiffies + SERIAL_TIMER_VALUE ) ;
}
irqreturn_t bcm_vk_tty_irqhandler ( int irq , void * dev_id )
{
struct bcm_vk * vk = dev_id ;
queue_work ( vk - > tty_wq_thread , & vk - > tty_wq_work ) ;
return IRQ_HANDLED ;
}
static void bcm_vk_tty_wq_handler ( struct work_struct * work )
{
struct bcm_vk * vk = container_of ( work , struct bcm_vk , tty_wq_work ) ;
struct bcm_vk_tty * vktty ;
int card_status ;
int count ;
unsigned char c ;
int i ;
int wr ;
card_status = vkread32 ( vk , BAR_0 , BAR_CARD_STATUS ) ;
if ( BCM_VK_INTF_IS_DOWN ( card_status ) )
return ;
for ( i = 0 ; i < BCM_VK_NUM_TTY ; i + + ) {
count = 0 ;
/* Check the card status that the tty channel is ready */
if ( ( card_status & BIT ( i ) ) = = 0 )
continue ;
vktty = & vk - > tty [ i ] ;
/* Don't increment read index if tty app is closed */
if ( ! vktty - > is_opened )
continue ;
/* Fetch the wr offset in buffer from VK */
wr = vkread32 ( vk , BAR_1 , VK_BAR_CHAN_WR ( vktty , from ) ) ;
/* safe to ignore until bar read gives proper size */
if ( vktty - > from_size = = 0 )
continue ;
if ( wr > = vktty - > from_size ) {
dev_err ( & vk - > pdev - > dev ,
" ERROR: wq handler ttyVK%d wr:0x%x > 0x%x \n " ,
i , wr , vktty - > from_size ) ;
/* Need to signal and close device in this case */
continue ;
}
/*
* Simple read of circular buffer and
* insert into tty flip buffer
*/
while ( vk - > tty [ i ] . rd ! = wr ) {
c = vkread8 ( vk , BAR_1 ,
VK_BAR_CHAN_DATA ( vktty , from , vktty - > rd ) ) ;
vktty - > rd + + ;
if ( vktty - > rd > = vktty - > from_size )
vktty - > rd = 0 ;
tty_insert_flip_char ( & vktty - > port , c , TTY_NORMAL ) ;
count + + ;
}
if ( count ) {
tty_flip_buffer_push ( & vktty - > port ) ;
/* Update read offset from shadow register to card */
vkwrite32 ( vk , vktty - > rd , BAR_1 ,
VK_BAR_CHAN_RD ( vktty , from ) ) ;
}
}
}
static int bcm_vk_tty_open ( struct tty_struct * tty , struct file * file )
{
int card_status ;
struct bcm_vk * vk ;
struct bcm_vk_tty * vktty ;
int index ;
/* initialize the pointer in case something fails */
tty - > driver_data = NULL ;
vk = ( struct bcm_vk * ) dev_get_drvdata ( tty - > dev ) ;
index = tty - > index ;
if ( index > = BCM_VK_NUM_TTY )
return - EINVAL ;
vktty = & vk - > tty [ index ] ;
vktty - > pid = task_pid_nr ( current ) ;
vktty - > to_offset = TO_TTYK_BASE ( index ) ;
vktty - > from_offset = FROM_TTYK_BASE ( index ) ;
/* Do not allow tty device to be opened if tty on card not ready */
card_status = vkread32 ( vk , BAR_0 , BAR_CARD_STATUS ) ;
if ( BCM_VK_INTF_IS_DOWN ( card_status ) | | ( ( card_status & BIT ( index ) ) = = 0 ) )
return - EBUSY ;
/*
* Get shadow registers of the buffer sizes and the " to " write offset
* and " from " read offset
*/
vktty - > to_size = vkread32 ( vk , BAR_1 , VK_BAR_CHAN_SIZE ( vktty , to ) ) ;
vktty - > wr = vkread32 ( vk , BAR_1 , VK_BAR_CHAN_WR ( vktty , to ) ) ;
vktty - > from_size = vkread32 ( vk , BAR_1 , VK_BAR_CHAN_SIZE ( vktty , from ) ) ;
vktty - > rd = vkread32 ( vk , BAR_1 , VK_BAR_CHAN_RD ( vktty , from ) ) ;
vktty - > is_opened = true ;
if ( tty - > count = = 1 & & ! vktty - > irq_enabled ) {
timer_setup ( & vk - > serial_timer , bcm_vk_tty_poll , 0 ) ;
mod_timer ( & vk - > serial_timer , jiffies + SERIAL_TIMER_VALUE ) ;
}
return 0 ;
}
static void bcm_vk_tty_close ( struct tty_struct * tty , struct file * file )
{
struct bcm_vk * vk = dev_get_drvdata ( tty - > dev ) ;
if ( tty - > index > = BCM_VK_NUM_TTY )
return ;
vk - > tty [ tty - > index ] . is_opened = false ;
if ( tty - > count = = 1 )
del_timer_sync ( & vk - > serial_timer ) ;
}
static void bcm_vk_tty_doorbell ( struct bcm_vk * vk , u32 db_val )
{
vkwrite32 ( vk , db_val , BAR_0 ,
VK_BAR0_REGSEG_DB_BASE + VK_BAR0_REGSEG_TTY_DB_OFFSET ) ;
}
static int bcm_vk_tty_write ( struct tty_struct * tty ,
const unsigned char * buffer ,
int count )
{
int index ;
struct bcm_vk * vk ;
struct bcm_vk_tty * vktty ;
int i ;
index = tty - > index ;
vk = dev_get_drvdata ( tty - > dev ) ;
vktty = & vk - > tty [ index ] ;
/* Simple write each byte to circular buffer */
for ( i = 0 ; i < count ; i + + ) {
vkwrite8 ( vk , buffer [ i ] , BAR_1 ,
VK_BAR_CHAN_DATA ( vktty , to , vktty - > wr ) ) ;
vktty - > wr + + ;
if ( vktty - > wr > = vktty - > to_size )
vktty - > wr = 0 ;
}
/* Update write offset from shadow register to card */
vkwrite32 ( vk , vktty - > wr , BAR_1 , VK_BAR_CHAN_WR ( vktty , to ) ) ;
bcm_vk_tty_doorbell ( vk , 0 ) ;
return count ;
}
2021-05-05 11:19:15 +02:00
static unsigned int bcm_vk_tty_write_room ( struct tty_struct * tty )
2021-01-20 09:58:27 -08:00
{
struct bcm_vk * vk = dev_get_drvdata ( tty - > dev ) ;
return vk - > tty [ tty - > index ] . to_size - 1 ;
}
static const struct tty_operations serial_ops = {
. open = bcm_vk_tty_open ,
. close = bcm_vk_tty_close ,
. write = bcm_vk_tty_write ,
. write_room = bcm_vk_tty_write_room ,
} ;
int bcm_vk_tty_init ( struct bcm_vk * vk , char * name )
{
int i ;
int err ;
struct tty_driver * tty_drv ;
struct device * dev = & vk - > pdev - > dev ;
tty_drv = tty_alloc_driver
( BCM_VK_NUM_TTY ,
TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV ) ;
if ( IS_ERR ( tty_drv ) )
return PTR_ERR ( tty_drv ) ;
/* Save struct tty_driver for uninstalling the device */
vk - > tty_drv = tty_drv ;
/* initialize the tty driver */
tty_drv - > driver_name = KBUILD_MODNAME ;
tty_drv - > name = kstrdup ( name , GFP_KERNEL ) ;
if ( ! tty_drv - > name ) {
err = - ENOMEM ;
2021-07-23 09:43:16 +02:00
goto err_tty_driver_kref_put ;
2021-01-20 09:58:27 -08:00
}
tty_drv - > type = TTY_DRIVER_TYPE_SERIAL ;
tty_drv - > subtype = SERIAL_TYPE_NORMAL ;
tty_drv - > init_termios = tty_std_termios ;
tty_set_operations ( tty_drv , & serial_ops ) ;
/* register the tty driver */
err = tty_register_driver ( tty_drv ) ;
if ( err ) {
dev_err ( dev , " tty_register_driver failed \n " ) ;
goto err_kfree_tty_name ;
}
for ( i = 0 ; i < BCM_VK_NUM_TTY ; i + + ) {
struct device * tty_dev ;
tty_port_init ( & vk - > tty [ i ] . port ) ;
tty_dev = tty_port_register_device ( & vk - > tty [ i ] . port , tty_drv ,
i , dev ) ;
if ( IS_ERR ( tty_dev ) ) {
err = PTR_ERR ( tty_dev ) ;
goto unwind ;
}
dev_set_drvdata ( tty_dev , vk ) ;
vk - > tty [ i ] . is_opened = false ;
}
INIT_WORK ( & vk - > tty_wq_work , bcm_vk_tty_wq_handler ) ;
vk - > tty_wq_thread = create_singlethread_workqueue ( " tty " ) ;
if ( ! vk - > tty_wq_thread ) {
dev_err ( dev , " Fail to create tty workqueue thread \n " ) ;
err = - ENOMEM ;
goto unwind ;
}
return 0 ;
unwind :
while ( - - i > = 0 )
tty_port_unregister_device ( & vk - > tty [ i ] . port , tty_drv , i ) ;
tty_unregister_driver ( tty_drv ) ;
err_kfree_tty_name :
kfree ( tty_drv - > name ) ;
tty_drv - > name = NULL ;
2021-07-23 09:43:16 +02:00
err_tty_driver_kref_put :
tty_driver_kref_put ( tty_drv ) ;
2021-01-20 09:58:27 -08:00
return err ;
}
void bcm_vk_tty_exit ( struct bcm_vk * vk )
{
int i ;
del_timer_sync ( & vk - > serial_timer ) ;
for ( i = 0 ; i < BCM_VK_NUM_TTY ; + + i ) {
tty_port_unregister_device ( & vk - > tty [ i ] . port ,
vk - > tty_drv ,
i ) ;
tty_port_destroy ( & vk - > tty [ i ] . port ) ;
}
tty_unregister_driver ( vk - > tty_drv ) ;
kfree ( vk - > tty_drv - > name ) ;
vk - > tty_drv - > name = NULL ;
2021-07-23 09:43:16 +02:00
tty_driver_kref_put ( vk - > tty_drv ) ;
2021-01-20 09:58:27 -08:00
}
void bcm_vk_tty_terminate_tty_user ( struct bcm_vk * vk )
{
struct bcm_vk_tty * vktty ;
int i ;
for ( i = 0 ; i < BCM_VK_NUM_TTY ; + + i ) {
vktty = & vk - > tty [ i ] ;
if ( vktty - > pid )
kill_pid ( find_vpid ( vktty - > pid ) , SIGKILL , 1 ) ;
}
}
2021-02-03 14:38:26 -08:00
void bcm_vk_tty_wq_exit ( struct bcm_vk * vk )
{
cancel_work_sync ( & vk - > tty_wq_work ) ;
destroy_workqueue ( vk - > tty_wq_thread ) ;
}