2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2017-03-02 10:48:23 -08:00
/*
* Copyright ( c ) 2017 Red Hat , Inc
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/libps2.h>
# include <linux/i2c.h>
# include <linux/serio.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
# include "psmouse.h"
struct psmouse_smbus_dev {
struct i2c_board_info board ;
struct psmouse * psmouse ;
struct i2c_client * client ;
struct list_head node ;
bool dead ;
2018-05-22 17:30:31 -07:00
bool need_deactivate ;
2017-03-02 10:48:23 -08:00
} ;
static LIST_HEAD ( psmouse_smbus_list ) ;
static DEFINE_MUTEX ( psmouse_smbus_mutex ) ;
2022-05-16 21:04:59 -07:00
static struct workqueue_struct * psmouse_smbus_wq ;
2017-03-02 10:48:23 -08:00
static void psmouse_smbus_check_adapter ( struct i2c_adapter * adapter )
{
struct psmouse_smbus_dev * smbdev ;
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_HOST_NOTIFY ) )
return ;
mutex_lock ( & psmouse_smbus_mutex ) ;
list_for_each_entry ( smbdev , & psmouse_smbus_list , node ) {
if ( smbdev - > dead )
continue ;
if ( smbdev - > client )
continue ;
/*
* Here would be a good place to check if device is actually
* present , but it seems that SMBus will not respond unless we
* fully reset PS / 2 connection . So cross our fingers , and try
* to switch over , hopefully our system will not have too many
* " host notify " I2C adapters .
*/
psmouse_dbg ( smbdev - > psmouse ,
" SMBus candidate adapter appeared, triggering rescan \n " ) ;
serio_rescan ( smbdev - > psmouse - > ps2dev . serio ) ;
}
mutex_unlock ( & psmouse_smbus_mutex ) ;
}
static void psmouse_smbus_detach_i2c_client ( struct i2c_client * client )
{
2017-04-01 11:33:11 -07:00
struct psmouse_smbus_dev * smbdev , * tmp ;
2017-03-02 10:48:23 -08:00
mutex_lock ( & psmouse_smbus_mutex ) ;
2017-04-01 11:33:11 -07:00
list_for_each_entry_safe ( smbdev , tmp , & psmouse_smbus_list , node ) {
if ( smbdev - > client ! = client )
continue ;
kfree ( client - > dev . platform_data ) ;
client - > dev . platform_data = NULL ;
if ( ! smbdev - > dead ) {
2017-03-02 10:48:23 -08:00
psmouse_dbg ( smbdev - > psmouse ,
" Marking SMBus companion %s as gone \n " ,
dev_name ( & smbdev - > client - > dev ) ) ;
smbdev - > dead = true ;
2022-02-15 13:32:26 -08:00
device_link_remove ( & smbdev - > client - > dev ,
& smbdev - > psmouse - > ps2dev . serio - > dev ) ;
2017-03-02 10:48:23 -08:00
serio_rescan ( smbdev - > psmouse - > ps2dev . serio ) ;
2017-04-01 11:33:11 -07:00
} else {
list_del ( & smbdev - > node ) ;
kfree ( smbdev ) ;
2017-03-02 10:48:23 -08:00
}
}
mutex_unlock ( & psmouse_smbus_mutex ) ;
}
static int psmouse_smbus_notifier_call ( struct notifier_block * nb ,
unsigned long action , void * data )
{
struct device * dev = data ;
switch ( action ) {
case BUS_NOTIFY_ADD_DEVICE :
if ( dev - > type = = & i2c_adapter_type )
psmouse_smbus_check_adapter ( to_i2c_adapter ( dev ) ) ;
break ;
case BUS_NOTIFY_REMOVED_DEVICE :
2017-03-25 10:45:13 -07:00
if ( dev - > type = = & i2c_client_type )
2017-03-02 10:48:23 -08:00
psmouse_smbus_detach_i2c_client ( to_i2c_client ( dev ) ) ;
break ;
}
return 0 ;
}
static struct notifier_block psmouse_smbus_notifier = {
. notifier_call = psmouse_smbus_notifier_call ,
} ;
static psmouse_ret_t psmouse_smbus_process_byte ( struct psmouse * psmouse )
{
return PSMOUSE_FULL_PACKET ;
}
static int psmouse_smbus_reconnect ( struct psmouse * psmouse )
{
2018-05-22 17:30:31 -07:00
struct psmouse_smbus_dev * smbdev = psmouse - > private ;
if ( smbdev - > need_deactivate )
psmouse_deactivate ( psmouse ) ;
2017-03-02 10:48:23 -08:00
return 0 ;
}
struct psmouse_smbus_removal_work {
struct work_struct work ;
struct i2c_client * client ;
} ;
static void psmouse_smbus_remove_i2c_device ( struct work_struct * work )
{
struct psmouse_smbus_removal_work * rwork =
container_of ( work , struct psmouse_smbus_removal_work , work ) ;
dev_dbg ( & rwork - > client - > dev , " destroying SMBus companion device \n " ) ;
i2c_unregister_device ( rwork - > client ) ;
kfree ( rwork ) ;
}
/*
* This schedules removal of SMBus companion device . We have to do
* it in a separate tread to avoid deadlocking on psmouse_mutex in
* case the device has a trackstick ( which is also driven by psmouse ) .
*
* Note that this may be racing with i2c adapter removal , but we
* can ' t do anything about that : i2c automatically destroys clients
* attached to an adapter that is being removed . This has to be
* fixed in i2c core .
*/
static void psmouse_smbus_schedule_remove ( struct i2c_client * client )
{
struct psmouse_smbus_removal_work * rwork ;
rwork = kzalloc ( sizeof ( * rwork ) , GFP_KERNEL ) ;
if ( rwork ) {
INIT_WORK ( & rwork - > work , psmouse_smbus_remove_i2c_device ) ;
rwork - > client = client ;
2022-05-16 21:04:59 -07:00
queue_work ( psmouse_smbus_wq , & rwork - > work ) ;
2017-03-02 10:48:23 -08:00
}
}
static void psmouse_smbus_disconnect ( struct psmouse * psmouse )
{
struct psmouse_smbus_dev * smbdev = psmouse - > private ;
mutex_lock ( & psmouse_smbus_mutex ) ;
2017-04-01 11:33:11 -07:00
if ( smbdev - > dead ) {
list_del ( & smbdev - > node ) ;
kfree ( smbdev ) ;
} else {
smbdev - > dead = true ;
2022-02-15 13:32:26 -08:00
device_link_remove ( & smbdev - > client - > dev ,
& psmouse - > ps2dev . serio - > dev ) ;
2017-03-02 10:48:23 -08:00
psmouse_dbg ( smbdev - > psmouse ,
" posting removal request for SMBus companion %s \n " ,
dev_name ( & smbdev - > client - > dev ) ) ;
psmouse_smbus_schedule_remove ( smbdev - > client ) ;
}
2017-04-01 11:33:11 -07:00
mutex_unlock ( & psmouse_smbus_mutex ) ;
2017-03-02 10:48:23 -08:00
psmouse - > private = NULL ;
}
static int psmouse_smbus_create_companion ( struct device * dev , void * data )
{
struct psmouse_smbus_dev * smbdev = data ;
unsigned short addr_list [ ] = { smbdev - > board . addr , I2C_CLIENT_END } ;
struct i2c_adapter * adapter ;
2020-02-10 09:57:23 -08:00
struct i2c_client * client ;
2017-03-02 10:48:23 -08:00
adapter = i2c_verify_adapter ( dev ) ;
if ( ! adapter )
return 0 ;
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_SMBUS_HOST_NOTIFY ) )
return 0 ;
2020-02-10 09:57:23 -08:00
client = i2c_new_scanned_device ( adapter , & smbdev - > board ,
addr_list , NULL ) ;
if ( IS_ERR ( client ) )
2017-03-02 10:48:23 -08:00
return 0 ;
/* We have our(?) device, stop iterating i2c bus. */
2020-02-10 09:57:23 -08:00
smbdev - > client = client ;
2017-03-02 10:48:23 -08:00
return 1 ;
}
void psmouse_smbus_cleanup ( struct psmouse * psmouse )
{
struct psmouse_smbus_dev * smbdev , * tmp ;
mutex_lock ( & psmouse_smbus_mutex ) ;
list_for_each_entry_safe ( smbdev , tmp , & psmouse_smbus_list , node ) {
if ( psmouse = = smbdev - > psmouse ) {
list_del ( & smbdev - > node ) ;
kfree ( smbdev ) ;
}
}
mutex_unlock ( & psmouse_smbus_mutex ) ;
}
int psmouse_smbus_init ( struct psmouse * psmouse ,
const struct i2c_board_info * board ,
const void * pdata , size_t pdata_size ,
2018-05-22 17:30:31 -07:00
bool need_deactivate ,
2017-03-02 10:48:23 -08:00
bool leave_breadcrumbs )
{
struct psmouse_smbus_dev * smbdev ;
int error ;
smbdev = kzalloc ( sizeof ( * smbdev ) , GFP_KERNEL ) ;
if ( ! smbdev )
return - ENOMEM ;
smbdev - > psmouse = psmouse ;
smbdev - > board = * board ;
2018-05-22 17:30:31 -07:00
smbdev - > need_deactivate = need_deactivate ;
2017-03-02 10:48:23 -08:00
2018-05-22 17:28:23 -07:00
if ( pdata ) {
smbdev - > board . platform_data = kmemdup ( pdata , pdata_size ,
GFP_KERNEL ) ;
if ( ! smbdev - > board . platform_data ) {
kfree ( smbdev ) ;
return - ENOMEM ;
}
2017-03-02 10:48:23 -08:00
}
2018-05-22 17:30:31 -07:00
if ( need_deactivate )
psmouse_deactivate ( psmouse ) ;
2017-03-02 10:48:23 -08:00
psmouse - > private = smbdev ;
psmouse - > protocol_handler = psmouse_smbus_process_byte ;
psmouse - > reconnect = psmouse_smbus_reconnect ;
psmouse - > fast_reconnect = psmouse_smbus_reconnect ;
psmouse - > disconnect = psmouse_smbus_disconnect ;
psmouse - > resync_time = 0 ;
mutex_lock ( & psmouse_smbus_mutex ) ;
list_add_tail ( & smbdev - > node , & psmouse_smbus_list ) ;
mutex_unlock ( & psmouse_smbus_mutex ) ;
/* Bind to already existing adapters right away */
error = i2c_for_each_dev ( smbdev , psmouse_smbus_create_companion ) ;
if ( smbdev - > client ) {
/* We have our companion device */
2022-02-15 13:32:26 -08:00
if ( ! device_link_add ( & smbdev - > client - > dev ,
& psmouse - > ps2dev . serio - > dev ,
DL_FLAG_STATELESS ) )
psmouse_warn ( psmouse ,
" failed to set up link with iSMBus companion %s \n " ,
dev_name ( & smbdev - > client - > dev ) ) ;
2017-03-02 10:48:23 -08:00
return 0 ;
}
/*
* If we did not create i2c device we will not need platform
* data even if we are leaving breadcrumbs .
*/
kfree ( smbdev - > board . platform_data ) ;
smbdev - > board . platform_data = NULL ;
if ( error < 0 | | ! leave_breadcrumbs ) {
mutex_lock ( & psmouse_smbus_mutex ) ;
list_del ( & smbdev - > node ) ;
mutex_unlock ( & psmouse_smbus_mutex ) ;
kfree ( smbdev ) ;
}
return error < 0 ? error : - EAGAIN ;
}
int __init psmouse_smbus_module_init ( void )
{
int error ;
2022-05-16 21:04:59 -07:00
psmouse_smbus_wq = alloc_workqueue ( " psmouse-smbus " , 0 , 0 ) ;
if ( ! psmouse_smbus_wq )
return - ENOMEM ;
2017-03-02 10:48:23 -08:00
error = bus_register_notifier ( & i2c_bus_type , & psmouse_smbus_notifier ) ;
if ( error ) {
pr_err ( " failed to register i2c bus notifier: %d \n " , error ) ;
2022-05-16 21:04:59 -07:00
destroy_workqueue ( psmouse_smbus_wq ) ;
2017-03-02 10:48:23 -08:00
return error ;
}
return 0 ;
}
void psmouse_smbus_module_exit ( void )
{
bus_unregister_notifier ( & i2c_bus_type , & psmouse_smbus_notifier ) ;
2022-05-16 21:04:59 -07:00
destroy_workqueue ( psmouse_smbus_wq ) ;
2017-03-02 10:48:23 -08:00
}