From patchwork Thu Mar 9 23:53:08 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitry Torokhov X-Patchwork-Id: 9614355 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 3DC2760417 for ; Thu, 9 Mar 2017 23:53:15 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2C71B286CF for ; Thu, 9 Mar 2017 23:53:15 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 1D72D286E6; Thu, 9 Mar 2017 23:53:15 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.3 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RCVD_IN_SORBS_SPAM, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4411C286CF for ; Thu, 9 Mar 2017 23:53:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753832AbdCIXxN (ORCPT ); Thu, 9 Mar 2017 18:53:13 -0500 Received: from mail-pf0-f196.google.com ([209.85.192.196]:36374 "EHLO mail-pf0-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751067AbdCIXxM (ORCPT ); Thu, 9 Mar 2017 18:53:12 -0500 Received: by mail-pf0-f196.google.com with SMTP id j5so8768361pfb.3; Thu, 09 Mar 2017 15:53:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to:user-agent; bh=vlHGYHtJr4Syc3io2WoEEJX/pS3LnH1f9TTs2FkqhNI=; b=oL2Pk5KutV3qaJvDbcRD/2gip1d9Jm8XdkFideqX9F9ryQWMuRZtvyz/4rbHavAHvx zO2SJ4FM6flxS4qNB5NNRigc07iUX7CHO46LLfDyKouK3CPcBVitQhnwh/A7UzIlRRzE tZg74ckZuODd/+GBpuq3Yqm3i0XHyKgA2zcJcqz+6qTujrrbeC3njegBrajHjduCdQ/K oeD5md8R60icLqC9urgT1JQWudo1cKzs0M2MdqRv3EK3PaCj9w/ijjbsn7qPsaiK2tk1 d92Z2Xda2MjPMXXhmbHVSCZskCt/Ah78O7urklYQ7/8lhExpFH9AtKyX4ugqLckf07Ge LQNw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to:user-agent; bh=vlHGYHtJr4Syc3io2WoEEJX/pS3LnH1f9TTs2FkqhNI=; b=VBUClX16vHxbfuPGs0UALFs+rhsfTZm/hD5TN1LjYtwqpJMyLNydD/ihZyn5/idx2J UPA68hnShwZTGKmaowfBtFALtqHQXIl3+9yM+XxtOVbC8wwVt84NXd1bm/SAt1zbN8lL NSA1F6o3MNz69HzfRCqm2TRe7Sddf+jbSIjHefJIxLWJYmtU2sEpOAYymRrtTSIFFcu2 ETKFj2lXGw0ufzR731jK/pK6a5aWqBls2OC0wyBW8qFG+VkBccyBCreQ9I6e32s5BtSu rL1AUmloq7CpVLQFiXqFb4ZJZ8i+6Wj4VMIrNy5ebpt+wrp9n7iCzOwp5in2KnBTHTsq x9Ew== X-Gm-Message-State: AMke39nLqG0AnXfyMTNEl0YJMDjle0FK7p9MX3Nw6faPtIEZ3Se1CrNC8kLi+bUYG6SLjQ== X-Received: by 10.98.196.221 with SMTP id h90mr17197094pfk.149.1489103591223; Thu, 09 Mar 2017 15:53:11 -0800 (PST) Received: from dtor-ws ([2620:0:1000:1311:d59f:e25a:29a2:90b1]) by smtp.gmail.com with ESMTPSA id p77sm14485206pfj.99.2017.03.09.15.53.10 (version=TLS1_2 cipher=AES128-SHA bits=128/128); Thu, 09 Mar 2017 15:53:10 -0800 (PST) Date: Thu, 9 Mar 2017 15:53:08 -0800 From: Dmitry Torokhov To: Benjamin Tissoires Cc: Andrew Duggan , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, Wolfram Sang Subject: [PATCH 6/8] Input: psmouse - add support for SMBus companions Message-ID: <20170309235308.GI20077@dtor-ws> References: <20170309221644.17035-1-dmitry.torokhov@gmail.com> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <20170309221644.17035-1-dmitry.torokhov@gmail.com> X-Mailer: git-send-email 2.12.0.246.ga2ecc84866-goog User-Agent: Mutt/1.5.21 (2010-09-15) Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Benjamin Tissoires This provides glue between PS/2 devices that enumerate the RMI4 devices and Elan touchpads to the RMI4 (or Elan) SMBus driver. The SMBus devices keep their PS/2 connection alive. If the initialization process goes too far (psmouse_activate called), the device disconnects from the I2C bus and stays on the PS/2 bus, that is why we explicitly disable PS/2 device reporting (by calling psmouse_deactivate) before trying to register SMBus companion device. The HID over I2C devices are enumerated through the ACPI DSDT, and their PS/2 device also exports the InterTouch bit in the extended capability 0x0C. However, the firmware keeps its I2C connection open even after going further in the PS/2 initialization. We don't need to take extra precautions with those device, especially because they block their PS/2 communication when HID over I2C is used. Signed-off-by: Benjamin Tissoires Signed-off-by: Dmitry Torokhov --- [ Added Wolfram to the CC ] drivers/input/mouse/Kconfig | 4 + drivers/input/mouse/Makefile | 2 + drivers/input/mouse/psmouse-base.c | 16 ++- drivers/input/mouse/psmouse-smbus.c | 280 ++++++++++++++++++++++++++++++++++++ drivers/input/mouse/psmouse.h | 33 +++++ 5 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 drivers/input/mouse/psmouse-smbus.c diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig index 096abb4ad5cd..87bde8a210c7 100644 --- a/drivers/input/mouse/Kconfig +++ b/drivers/input/mouse/Kconfig @@ -171,6 +171,10 @@ config MOUSE_PS2_VMMOUSE If unsure, say N. +config MOUSE_PS2_SMBUS + bool + depends on MOUSE_PS2 + config MOUSE_SERIAL tristate "Serial mouse" select SERIO diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile index 6168b134937b..56bf0ad877c6 100644 --- a/drivers/input/mouse/Makefile +++ b/drivers/input/mouse/Makefile @@ -39,6 +39,8 @@ psmouse-$(CONFIG_MOUSE_PS2_TOUCHKIT) += touchkit_ps2.o psmouse-$(CONFIG_MOUSE_PS2_CYPRESS) += cypress_ps2.o psmouse-$(CONFIG_MOUSE_PS2_VMMOUSE) += vmmouse.o +psmouse-$(CONFIG_MOUSE_PS2_SMBUS) += psmouse-smbus.o + elan_i2c-objs := elan_i2c_core.o elan_i2c-$(CONFIG_MOUSE_ELAN_I2C_I2C) += elan_i2c_i2c.o elan_i2c-$(CONFIG_MOUSE_ELAN_I2C_SMBUS) += elan_i2c_smbus.o diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index 40f09ce84f14..bdb48b2acc57 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -1996,16 +1996,27 @@ static int __init psmouse_init(void) synaptics_module_init(); hgpk_module_init(); + err = psmouse_smbus_module_init(); + if (err) + return err; + kpsmoused_wq = alloc_ordered_workqueue("kpsmoused", 0); if (!kpsmoused_wq) { pr_err("failed to create kpsmoused workqueue\n"); - return -ENOMEM; + err = -ENOMEM; + goto err_smbus_exit; } err = serio_register_driver(&psmouse_drv); if (err) - destroy_workqueue(kpsmoused_wq); + goto err_destroy_wq; + return 0; + +err_destroy_wq: + destroy_workqueue(kpsmoused_wq); +err_smbus_exit: + psmouse_smbus_module_exit(); return err; } @@ -2013,6 +2024,7 @@ static void __exit psmouse_exit(void) { serio_unregister_driver(&psmouse_drv); destroy_workqueue(kpsmoused_wq); + psmouse_smbus_module_exit(); } module_init(psmouse_init); diff --git a/drivers/input/mouse/psmouse-smbus.c b/drivers/input/mouse/psmouse-smbus.c new file mode 100644 index 000000000000..5bda551b752f --- /dev/null +++ b/drivers/input/mouse/psmouse-smbus.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2017 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include "psmouse.h" + +struct psmouse_smbus_dev { + struct psmouse *psmouse; + struct i2c_client *client; + struct list_head node; + unsigned short addr; + bool dead; +}; + +static LIST_HEAD(psmouse_smbus_list); +static DEFINE_MUTEX(psmouse_smbus_mutex); + +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; + + if (i2c_probe_func_quick_read(adapter, smbdev->addr) < 0) + continue; + + /* Device seems to be there, let's try switching over */ + psmouse_dbg(smbdev->psmouse, + "SMBus companion 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) +{ + struct psmouse_smbus_dev *smbdev; + + mutex_lock(&psmouse_smbus_mutex); + + list_for_each_entry(smbdev, &psmouse_smbus_list, node) { + if (smbdev->client == client) { + psmouse_dbg(smbdev->psmouse, + "Marking SMBus companion %s as gone\n", + dev_name(&smbdev->client->dev)); + smbdev->client = NULL; + smbdev->dead = true; + serio_rescan(smbdev->psmouse->ps2dev.serio); + } + } + + 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: + if (dev->type == &i2c_client_type) + 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) +{ + psmouse_deactivate(psmouse); + + 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; + + schedule_work(&rwork->work); + } +} + +static void psmouse_smbus_disconnect(struct psmouse *psmouse) +{ + struct psmouse_smbus_dev *smbdev = psmouse->private; + + mutex_lock(&psmouse_smbus_mutex); + list_del(&smbdev->node); + mutex_unlock(&psmouse_smbus_mutex); + + if (smbdev->client) { + psmouse_dbg(smbdev->psmouse, + "posting removal request for SMBus companion %s\n", + dev_name(&smbdev->client->dev)); + psmouse_smbus_schedule_remove(smbdev->client); + } + + kfree(smbdev); + psmouse->private = NULL; +} + +struct psmouse_smbus_companion_req { + struct i2c_board_info *board; + struct i2c_client **client; +}; + +static int psmouse_smbus_create_companion(struct device *dev, void *data) +{ + struct psmouse_smbus_companion_req *req = data; + unsigned short addr[] = { req->board->addr, I2C_CLIENT_END }; + struct i2c_adapter *adapter; + + adapter = i2c_verify_adapter(dev); + if (!adapter) + return 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_HOST_NOTIFY)) + return 0; + + *req->client = i2c_new_probed_device(adapter, req->board, addr, NULL); + if (!*req->client) + return 0; + + /* We have our(?) device, stop iterating i2c bus. */ + 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, struct i2c_board_info *board, + bool leave_breadcrumbs) +{ + struct psmouse_smbus_companion_req req; + struct psmouse_smbus_dev *smbdev; + int error; + + smbdev = kzalloc(sizeof(*smbdev), GFP_KERNEL); + if (!smbdev) + return -ENOMEM; + + smbdev->psmouse = psmouse; + smbdev->addr = board->addr; + + 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; + + psmouse_deactivate(psmouse); + + 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 */ + req.board = board; + req.client = &smbdev->client; + error = i2c_for_each_dev(&req, psmouse_smbus_create_companion); + + if (smbdev->client) { + /* We have our companion device */ + return 0; + } + + 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; + + error = bus_register_notifier(&i2c_bus_type, &psmouse_smbus_notifier); + if (error) { + pr_err("failed to register i2c bus notifier: %d\n", error); + return error; + } + + return 0; +} + +void __exit psmouse_smbus_module_exit(void) +{ + bus_unregister_notifier(&i2c_bus_type, &psmouse_smbus_notifier); + flush_scheduled_work(); +} diff --git a/drivers/input/mouse/psmouse.h b/drivers/input/mouse/psmouse.h index e853dee05e79..186c760e398e 100644 --- a/drivers/input/mouse/psmouse.h +++ b/drivers/input/mouse/psmouse.h @@ -209,5 +209,38 @@ static struct psmouse_attribute psmouse_attr_##_name = { \ &(psmouse)->ps2dev.serio->dev, \ psmouse_fmt(format), ##__VA_ARGS__) +#ifdef CONFIG_MOUSE_PS2_SMBUS + +int psmouse_smbus_module_init(void); +void psmouse_smbus_module_exit(void); + +struct i2c_board_info; + +int psmouse_smbus_init(struct psmouse *psmouse, struct i2c_board_info *board, + bool leave_breadcrumbs); +void psmouse_smbus_cleanup(struct psmouse *psmouse); + +#else /* !CONFIG_MOUSE_PS2_SMBUS */ + +static inline int psmouse_smbus_module_init(void) +{ + return 0; +} + +static inline void psmouse_smbus_module_exit(void) +{ +} + +int psmouse_smbus_init(struct psmouse *psmouse, struct i2c_board_info *board, + bool leave_breadcrumbs) +{ + return -ENOSYS; +} + +void psmouse_smbus_cleanup(struct psmouse *psmouse) +{ +} + +#endif /* CONFIG_MOUSE_PS2_SMBUS */ #endif /* _PSMOUSE_H */