2017-11-06 18:11:51 +01:00
// SPDX-License-Identifier: GPL-2.0
2017-02-02 13:48:08 -06:00
/*
* Copyright ( C ) 2016 - 2017 Linaro Ltd . , Rob Herring < robh @ kernel . org >
*/
# include <linux/kernel.h>
# include <linux/serdev.h>
# include <linux/tty.h>
# include <linux/tty_driver.h>
2017-03-28 17:59:31 +02:00
# include <linux/poll.h>
2017-02-02 13:48:08 -06:00
# define SERPORT_ACTIVE 1
struct serport {
struct tty_port * port ;
struct tty_struct * tty ;
struct tty_driver * tty_drv ;
int tty_idx ;
unsigned long flags ;
} ;
/*
* Callback functions from the tty port .
*/
static int ttyport_receive_buf ( struct tty_port * port , const unsigned char * cp ,
const unsigned char * fp , size_t count )
{
struct serdev_controller * ctrl = port - > client_data ;
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
2017-11-03 15:30:52 +01:00
int ret ;
2017-02-02 13:48:08 -06:00
if ( ! test_bit ( SERPORT_ACTIVE , & serport - > flags ) )
return 0 ;
2017-11-03 15:30:52 +01:00
ret = serdev_controller_receive_buf ( ctrl , cp , count ) ;
dev_WARN_ONCE ( & ctrl - > dev , ret < 0 | | ret > count ,
" receive_buf returns %d (count = %zu) \n " ,
ret , count ) ;
if ( ret < 0 )
return 0 ;
else if ( ret > count )
return count ;
return ret ;
2017-02-02 13:48:08 -06:00
}
static void ttyport_write_wakeup ( struct tty_port * port )
{
struct serdev_controller * ctrl = port - > client_data ;
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
2017-11-03 15:30:55 +01:00
struct tty_struct * tty ;
tty = tty_port_tty_get ( port ) ;
if ( ! tty )
return ;
2017-02-02 13:48:08 -06:00
2017-11-03 15:30:55 +01:00
if ( test_and_clear_bit ( TTY_DO_WRITE_WAKEUP , & tty - > flags ) & &
2017-03-28 17:59:31 +02:00
test_bit ( SERPORT_ACTIVE , & serport - > flags ) )
2017-02-02 13:48:08 -06:00
serdev_controller_write_wakeup ( ctrl ) ;
2017-03-28 17:59:31 +02:00
2017-12-18 12:00:19 +01:00
/* Wake up any tty_wait_until_sent() */
wake_up_interruptible ( & tty - > write_wait ) ;
2017-11-03 15:30:55 +01:00
tty_kref_put ( tty ) ;
2017-02-02 13:48:08 -06:00
}
static const struct tty_port_client_operations client_ops = {
. receive_buf = ttyport_receive_buf ,
. write_wakeup = ttyport_write_wakeup ,
} ;
/*
* Callback functions from the serdev core .
*/
static int ttyport_write_buf ( struct serdev_controller * ctrl , const unsigned char * data , size_t len )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
if ( ! test_bit ( SERPORT_ACTIVE , & serport - > flags ) )
return 0 ;
set_bit ( TTY_DO_WRITE_WAKEUP , & tty - > flags ) ;
return tty - > ops - > write ( serport - > tty , data , len ) ;
}
static void ttyport_write_flush ( struct serdev_controller * ctrl )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
tty_driver_flush_buffer ( tty ) ;
}
static int ttyport_write_room ( struct serdev_controller * ctrl )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
return tty_write_room ( tty ) ;
}
static int ttyport_open ( struct serdev_controller * ctrl )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty ;
struct ktermios ktermios ;
2017-10-16 15:06:20 +02:00
int ret ;
2017-02-02 13:48:08 -06:00
tty = tty_init_dev ( serport - > tty_drv , serport - > tty_idx ) ;
2017-02-08 10:23:24 +03:00
if ( IS_ERR ( tty ) )
return PTR_ERR ( tty ) ;
2017-02-02 13:48:08 -06:00
serport - > tty = tty ;
2017-10-16 15:06:20 +02:00
if ( ! tty - > ops - > open | | ! tty - > ops - > close ) {
ret = - ENODEV ;
2017-10-16 15:06:19 +02:00
goto err_unlock ;
2017-10-16 15:06:20 +02:00
}
2017-10-16 15:06:19 +02:00
2017-10-16 15:06:20 +02:00
ret = tty - > ops - > open ( serport - > tty , NULL ) ;
if ( ret )
goto err_close ;
2017-02-02 13:48:08 -06:00
2017-11-03 15:30:57 +01:00
tty_unlock ( serport - > tty ) ;
2017-02-02 13:48:08 -06:00
/* Bring the UART into a known 8 bits no parity hw fc state */
ktermios = tty - > termios ;
ktermios . c_iflag & = ~ ( IGNBRK | BRKINT | PARMRK | ISTRIP |
INLCR | IGNCR | ICRNL | IXON ) ;
ktermios . c_oflag & = ~ OPOST ;
ktermios . c_lflag & = ~ ( ECHO | ECHONL | ICANON | ISIG | IEXTEN ) ;
ktermios . c_cflag & = ~ ( CSIZE | PARENB ) ;
ktermios . c_cflag | = CS8 ;
ktermios . c_cflag | = CRTSCTS ;
2017-11-03 15:30:58 +01:00
/* Hangups are not supported so make sure to ignore carrier detect. */
ktermios . c_cflag | = CLOCAL ;
2017-02-02 13:48:08 -06:00
tty_set_termios ( tty , & ktermios ) ;
set_bit ( SERPORT_ACTIVE , & serport - > flags ) ;
return 0 ;
2017-10-16 15:06:19 +02:00
2017-10-16 15:06:20 +02:00
err_close :
tty - > ops - > close ( tty , NULL ) ;
2017-10-16 15:06:19 +02:00
err_unlock :
tty_unlock ( tty ) ;
tty_release_struct ( tty , serport - > tty_idx ) ;
2017-10-16 15:06:20 +02:00
return ret ;
2017-02-02 13:48:08 -06:00
}
static void ttyport_close ( struct serdev_controller * ctrl )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
clear_bit ( SERPORT_ACTIVE , & serport - > flags ) ;
2017-11-03 15:30:56 +01:00
tty_lock ( tty ) ;
2017-02-02 13:48:08 -06:00
if ( tty - > ops - > close )
tty - > ops - > close ( tty , NULL ) ;
2017-11-03 15:30:56 +01:00
tty_unlock ( tty ) ;
2017-02-02 13:48:08 -06:00
tty_release_struct ( tty , serport - > tty_idx ) ;
}
static unsigned int ttyport_set_baudrate ( struct serdev_controller * ctrl , unsigned int speed )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
struct ktermios ktermios = tty - > termios ;
ktermios . c_cflag & = ~ CBAUD ;
tty_termios_encode_baud_rate ( & ktermios , speed , speed ) ;
/* tty_set_termios() return not checked as it is always 0 */
tty_set_termios ( tty , & ktermios ) ;
2017-05-10 10:53:27 +02:00
return ktermios . c_ospeed ;
2017-02-02 13:48:08 -06:00
}
static void ttyport_set_flow_control ( struct serdev_controller * ctrl , bool enable )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
struct ktermios ktermios = tty - > termios ;
if ( enable )
ktermios . c_cflag | = CRTSCTS ;
else
ktermios . c_cflag & = ~ CRTSCTS ;
tty_set_termios ( tty , & ktermios ) ;
}
2018-01-22 18:56:32 +01:00
static int ttyport_set_parity ( struct serdev_controller * ctrl ,
enum serdev_parity parity )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
struct ktermios ktermios = tty - > termios ;
ktermios . c_cflag & = ~ ( PARENB | PARODD | CMSPAR ) ;
if ( parity ! = SERDEV_PARITY_NONE ) {
ktermios . c_cflag | = PARENB ;
if ( parity = = SERDEV_PARITY_ODD )
ktermios . c_cflag | = PARODD ;
}
tty_set_termios ( tty , & ktermios ) ;
if ( ( tty - > termios . c_cflag & ( PARENB | PARODD | CMSPAR ) ) ! =
( ktermios . c_cflag & ( PARENB | PARODD | CMSPAR ) ) )
return - EINVAL ;
return 0 ;
}
2017-03-28 17:59:31 +02:00
static void ttyport_wait_until_sent ( struct serdev_controller * ctrl , long timeout )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
tty_wait_until_sent ( tty , timeout ) ;
}
2017-03-28 17:59:32 +02:00
static int ttyport_get_tiocm ( struct serdev_controller * ctrl )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
if ( ! tty - > ops - > tiocmget )
2023-03-16 22:52:11 +05:30
return - EOPNOTSUPP ;
2017-03-28 17:59:32 +02:00
2019-01-30 10:52:03 +01:00
return tty - > ops - > tiocmget ( tty ) ;
2017-03-28 17:59:32 +02:00
}
static int ttyport_set_tiocm ( struct serdev_controller * ctrl , unsigned int set , unsigned int clear )
{
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
struct tty_struct * tty = serport - > tty ;
if ( ! tty - > ops - > tiocmset )
2023-03-16 22:52:11 +05:30
return - EOPNOTSUPP ;
2017-03-28 17:59:32 +02:00
2019-01-30 10:52:03 +01:00
return tty - > ops - > tiocmset ( tty , set , clear ) ;
2017-03-28 17:59:32 +02:00
}
2017-02-02 13:48:08 -06:00
static const struct serdev_controller_ops ctrl_ops = {
. write_buf = ttyport_write_buf ,
. write_flush = ttyport_write_flush ,
. write_room = ttyport_write_room ,
. open = ttyport_open ,
. close = ttyport_close ,
. set_flow_control = ttyport_set_flow_control ,
2018-01-22 18:56:32 +01:00
. set_parity = ttyport_set_parity ,
2017-02-02 13:48:08 -06:00
. set_baudrate = ttyport_set_baudrate ,
2017-03-28 17:59:31 +02:00
. wait_until_sent = ttyport_wait_until_sent ,
2017-03-28 17:59:32 +02:00
. get_tiocm = ttyport_get_tiocm ,
. set_tiocm = ttyport_set_tiocm ,
2017-02-02 13:48:08 -06:00
} ;
struct device * serdev_tty_port_register ( struct tty_port * port ,
struct device * parent ,
struct tty_driver * drv , int idx )
{
struct serdev_controller * ctrl ;
struct serport * serport ;
int ret ;
if ( ! port | | ! drv | | ! parent )
return ERR_PTR ( - ENODEV ) ;
ctrl = serdev_controller_alloc ( parent , sizeof ( struct serport ) ) ;
if ( ! ctrl )
return ERR_PTR ( - ENOMEM ) ;
serport = serdev_controller_get_drvdata ( ctrl ) ;
serport - > port = port ;
serport - > tty_idx = idx ;
serport - > tty_drv = drv ;
ctrl - > ops = & ctrl_ops ;
2017-04-11 19:07:29 +02:00
port - > client_ops = & client_ops ;
port - > client_data = ctrl ;
2017-02-02 13:48:08 -06:00
ret = serdev_controller_add ( ctrl ) ;
if ( ret )
2017-04-11 19:07:29 +02:00
goto err_reset_data ;
2017-02-02 13:48:08 -06:00
dev_info ( & ctrl - > dev , " tty port %s%d registered \n " , drv - > name , idx ) ;
return & ctrl - > dev ;
2017-04-11 19:07:29 +02:00
err_reset_data :
port - > client_data = NULL ;
2020-02-10 15:57:30 +01:00
port - > client_ops = & tty_port_default_client_ops ;
2017-02-02 13:48:08 -06:00
serdev_controller_put ( ctrl ) ;
2017-04-11 19:07:29 +02:00
2017-02-02 13:48:08 -06:00
return ERR_PTR ( ret ) ;
}
2017-05-18 17:33:00 +02:00
int serdev_tty_port_unregister ( struct tty_port * port )
2017-02-02 13:48:08 -06:00
{
struct serdev_controller * ctrl = port - > client_data ;
struct serport * serport = serdev_controller_get_drvdata ( ctrl ) ;
if ( ! serport )
2017-05-18 17:33:00 +02:00
return - ENODEV ;
2017-02-02 13:48:08 -06:00
serdev_controller_remove ( ctrl ) ;
port - > client_data = NULL ;
2020-02-10 15:57:30 +01:00
port - > client_ops = & tty_port_default_client_ops ;
2017-02-02 13:48:08 -06:00
serdev_controller_put ( ctrl ) ;
2017-05-18 17:33:00 +02:00
return 0 ;
2017-02-02 13:48:08 -06:00
}