2021-06-08 20:17:52 +09:30
// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright (c) 2021 IBM Corp. */
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/errno.h>
# include <linux/list.h>
# include <linux/module.h>
# include <linux/sched/signal.h>
# include <linux/serio.h>
# include <linux/slab.h>
# include "kcs_bmc_client.h"
struct kcs_bmc_serio {
struct list_head entry ;
struct kcs_bmc_client client ;
struct serio * port ;
spinlock_t lock ;
} ;
static inline struct kcs_bmc_serio * client_to_kcs_bmc_serio ( struct kcs_bmc_client * client )
{
return container_of ( client , struct kcs_bmc_serio , client ) ;
}
static irqreturn_t kcs_bmc_serio_event ( struct kcs_bmc_client * client )
{
struct kcs_bmc_serio * priv ;
u8 handled = IRQ_NONE ;
u8 status ;
priv = client_to_kcs_bmc_serio ( client ) ;
spin_lock ( & priv - > lock ) ;
status = kcs_bmc_read_status ( client - > dev ) ;
if ( status & KCS_BMC_STR_IBF )
handled = serio_interrupt ( priv - > port , kcs_bmc_read_data ( client - > dev ) , 0 ) ;
spin_unlock ( & priv - > lock ) ;
return handled ;
}
static const struct kcs_bmc_client_ops kcs_bmc_serio_client_ops = {
. event = kcs_bmc_serio_event ,
} ;
static int kcs_bmc_serio_open ( struct serio * port )
{
struct kcs_bmc_serio * priv = port - > port_data ;
return kcs_bmc_enable_device ( priv - > client . dev , & priv - > client ) ;
}
static void kcs_bmc_serio_close ( struct serio * port )
{
struct kcs_bmc_serio * priv = port - > port_data ;
kcs_bmc_disable_device ( priv - > client . dev , & priv - > client ) ;
}
static DEFINE_SPINLOCK ( kcs_bmc_serio_instances_lock ) ;
static LIST_HEAD ( kcs_bmc_serio_instances ) ;
static int kcs_bmc_serio_add_device ( struct kcs_bmc_device * kcs_bmc )
{
struct kcs_bmc_serio * priv ;
struct serio * port ;
priv = devm_kzalloc ( kcs_bmc - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
2021-09-07 23:06:32 +02:00
if ( ! priv )
return - ENOMEM ;
2021-06-08 20:17:52 +09:30
/* Use kzalloc() as the allocation is cleaned up with kfree() via serio_unregister_port() */
port = kzalloc ( sizeof ( * port ) , GFP_KERNEL ) ;
2021-09-07 23:06:32 +02:00
if ( ! port )
2021-06-08 20:17:52 +09:30
return - ENOMEM ;
port - > id . type = SERIO_8042 ;
port - > open = kcs_bmc_serio_open ;
port - > close = kcs_bmc_serio_close ;
port - > port_data = priv ;
port - > dev . parent = kcs_bmc - > dev ;
spin_lock_init ( & priv - > lock ) ;
priv - > port = port ;
priv - > client . dev = kcs_bmc ;
priv - > client . ops = & kcs_bmc_serio_client_ops ;
spin_lock_irq ( & kcs_bmc_serio_instances_lock ) ;
list_add ( & priv - > entry , & kcs_bmc_serio_instances ) ;
spin_unlock_irq ( & kcs_bmc_serio_instances_lock ) ;
serio_register_port ( port ) ;
dev_info ( kcs_bmc - > dev , " Initialised serio client for channel %d " , kcs_bmc - > channel ) ;
return 0 ;
}
static int kcs_bmc_serio_remove_device ( struct kcs_bmc_device * kcs_bmc )
{
struct kcs_bmc_serio * priv = NULL , * pos ;
spin_lock_irq ( & kcs_bmc_serio_instances_lock ) ;
list_for_each_entry ( pos , & kcs_bmc_serio_instances , entry ) {
if ( pos - > client . dev = = kcs_bmc ) {
priv = pos ;
list_del ( & pos - > entry ) ;
break ;
}
}
spin_unlock_irq ( & kcs_bmc_serio_instances_lock ) ;
if ( ! priv )
return - ENODEV ;
/* kfree()s priv->port via put_device() */
serio_unregister_port ( priv - > port ) ;
/* Ensure the IBF IRQ is disabled if we were the active client */
kcs_bmc_disable_device ( kcs_bmc , & priv - > client ) ;
devm_kfree ( priv - > client . dev - > dev , priv ) ;
return 0 ;
}
static const struct kcs_bmc_driver_ops kcs_bmc_serio_driver_ops = {
. add_device = kcs_bmc_serio_add_device ,
. remove_device = kcs_bmc_serio_remove_device ,
} ;
static struct kcs_bmc_driver kcs_bmc_serio_driver = {
. ops = & kcs_bmc_serio_driver_ops ,
} ;
2022-09-22 19:19:24 +08:00
static int __init kcs_bmc_serio_init ( void )
2021-06-08 20:17:52 +09:30
{
kcs_bmc_register_driver ( & kcs_bmc_serio_driver ) ;
return 0 ;
}
module_init ( kcs_bmc_serio_init ) ;
2022-09-22 19:19:24 +08:00
static void __exit kcs_bmc_serio_exit ( void )
2021-06-08 20:17:52 +09:30
{
kcs_bmc_unregister_driver ( & kcs_bmc_serio_driver ) ;
}
module_exit ( kcs_bmc_serio_exit ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Andrew Jeffery <andrew@aj.id.au> " ) ;
MODULE_DESCRIPTION ( " Adapter driver for serio access to BMC KCS devices " ) ;