18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2017 Red Hat, Inc
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#define pr_fmt(fmt)		KBUILD_MODNAME ": " fmt
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/kernel.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/libps2.h>
118c2ecf20Sopenharmony_ci#include <linux/i2c.h>
128c2ecf20Sopenharmony_ci#include <linux/serio.h>
138c2ecf20Sopenharmony_ci#include <linux/slab.h>
148c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
158c2ecf20Sopenharmony_ci#include "psmouse.h"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistruct psmouse_smbus_dev {
188c2ecf20Sopenharmony_ci	struct i2c_board_info board;
198c2ecf20Sopenharmony_ci	struct psmouse *psmouse;
208c2ecf20Sopenharmony_ci	struct i2c_client *client;
218c2ecf20Sopenharmony_ci	struct list_head node;
228c2ecf20Sopenharmony_ci	bool dead;
238c2ecf20Sopenharmony_ci	bool need_deactivate;
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic LIST_HEAD(psmouse_smbus_list);
278c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(psmouse_smbus_mutex);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic void psmouse_smbus_check_adapter(struct i2c_adapter *adapter)
308c2ecf20Sopenharmony_ci{
318c2ecf20Sopenharmony_ci	struct psmouse_smbus_dev *smbdev;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_HOST_NOTIFY))
348c2ecf20Sopenharmony_ci		return;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	mutex_lock(&psmouse_smbus_mutex);
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	list_for_each_entry(smbdev, &psmouse_smbus_list, node) {
398c2ecf20Sopenharmony_ci		if (smbdev->dead)
408c2ecf20Sopenharmony_ci			continue;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci		if (smbdev->client)
438c2ecf20Sopenharmony_ci			continue;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci		/*
468c2ecf20Sopenharmony_ci		 * Here would be a good place to check if device is actually
478c2ecf20Sopenharmony_ci		 * present, but it seems that SMBus will not respond unless we
488c2ecf20Sopenharmony_ci		 * fully reset PS/2 connection.  So cross our fingers, and try
498c2ecf20Sopenharmony_ci		 * to switch over, hopefully our system will not have too many
508c2ecf20Sopenharmony_ci		 * "host notify" I2C adapters.
518c2ecf20Sopenharmony_ci		 */
528c2ecf20Sopenharmony_ci		psmouse_dbg(smbdev->psmouse,
538c2ecf20Sopenharmony_ci			    "SMBus candidate adapter appeared, triggering rescan\n");
548c2ecf20Sopenharmony_ci		serio_rescan(smbdev->psmouse->ps2dev.serio);
558c2ecf20Sopenharmony_ci	}
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	mutex_unlock(&psmouse_smbus_mutex);
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic void psmouse_smbus_detach_i2c_client(struct i2c_client *client)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct psmouse_smbus_dev *smbdev, *tmp;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	mutex_lock(&psmouse_smbus_mutex);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	list_for_each_entry_safe(smbdev, tmp, &psmouse_smbus_list, node) {
678c2ecf20Sopenharmony_ci		if (smbdev->client != client)
688c2ecf20Sopenharmony_ci			continue;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci		kfree(client->dev.platform_data);
718c2ecf20Sopenharmony_ci		client->dev.platform_data = NULL;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci		if (!smbdev->dead) {
748c2ecf20Sopenharmony_ci			psmouse_dbg(smbdev->psmouse,
758c2ecf20Sopenharmony_ci				    "Marking SMBus companion %s as gone\n",
768c2ecf20Sopenharmony_ci				    dev_name(&smbdev->client->dev));
778c2ecf20Sopenharmony_ci			smbdev->dead = true;
788c2ecf20Sopenharmony_ci			serio_rescan(smbdev->psmouse->ps2dev.serio);
798c2ecf20Sopenharmony_ci		} else {
808c2ecf20Sopenharmony_ci			list_del(&smbdev->node);
818c2ecf20Sopenharmony_ci			kfree(smbdev);
828c2ecf20Sopenharmony_ci		}
838c2ecf20Sopenharmony_ci	}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	mutex_unlock(&psmouse_smbus_mutex);
868c2ecf20Sopenharmony_ci}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cistatic int psmouse_smbus_notifier_call(struct notifier_block *nb,
898c2ecf20Sopenharmony_ci				       unsigned long action, void *data)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	struct device *dev = data;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	switch (action) {
948c2ecf20Sopenharmony_ci	case BUS_NOTIFY_ADD_DEVICE:
958c2ecf20Sopenharmony_ci		if (dev->type == &i2c_adapter_type)
968c2ecf20Sopenharmony_ci			psmouse_smbus_check_adapter(to_i2c_adapter(dev));
978c2ecf20Sopenharmony_ci		break;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	case BUS_NOTIFY_REMOVED_DEVICE:
1008c2ecf20Sopenharmony_ci		if (dev->type == &i2c_client_type)
1018c2ecf20Sopenharmony_ci			psmouse_smbus_detach_i2c_client(to_i2c_client(dev));
1028c2ecf20Sopenharmony_ci		break;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return 0;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic struct notifier_block psmouse_smbus_notifier = {
1098c2ecf20Sopenharmony_ci	.notifier_call = psmouse_smbus_notifier_call,
1108c2ecf20Sopenharmony_ci};
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic psmouse_ret_t psmouse_smbus_process_byte(struct psmouse *psmouse)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	return PSMOUSE_FULL_PACKET;
1158c2ecf20Sopenharmony_ci}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_cistatic int psmouse_smbus_reconnect(struct psmouse *psmouse)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	struct psmouse_smbus_dev *smbdev = psmouse->private;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	if (smbdev->need_deactivate)
1228c2ecf20Sopenharmony_ci		psmouse_deactivate(psmouse);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	return 0;
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cistruct psmouse_smbus_removal_work {
1288c2ecf20Sopenharmony_ci	struct work_struct work;
1298c2ecf20Sopenharmony_ci	struct i2c_client *client;
1308c2ecf20Sopenharmony_ci};
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic void psmouse_smbus_remove_i2c_device(struct work_struct *work)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	struct psmouse_smbus_removal_work *rwork =
1358c2ecf20Sopenharmony_ci		container_of(work, struct psmouse_smbus_removal_work, work);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	dev_dbg(&rwork->client->dev, "destroying SMBus companion device\n");
1388c2ecf20Sopenharmony_ci	i2c_unregister_device(rwork->client);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	kfree(rwork);
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci/*
1448c2ecf20Sopenharmony_ci * This schedules removal of SMBus companion device. We have to do
1458c2ecf20Sopenharmony_ci * it in a separate tread to avoid deadlocking on psmouse_mutex in
1468c2ecf20Sopenharmony_ci * case the device has a trackstick (which is also driven by psmouse).
1478c2ecf20Sopenharmony_ci *
1488c2ecf20Sopenharmony_ci * Note that this may be racing with i2c adapter removal, but we
1498c2ecf20Sopenharmony_ci * can't do anything about that: i2c automatically destroys clients
1508c2ecf20Sopenharmony_ci * attached to an adapter that is being removed. This has to be
1518c2ecf20Sopenharmony_ci * fixed in i2c core.
1528c2ecf20Sopenharmony_ci */
1538c2ecf20Sopenharmony_cistatic void psmouse_smbus_schedule_remove(struct i2c_client *client)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	struct psmouse_smbus_removal_work *rwork;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	rwork = kzalloc(sizeof(*rwork), GFP_KERNEL);
1588c2ecf20Sopenharmony_ci	if (rwork) {
1598c2ecf20Sopenharmony_ci		INIT_WORK(&rwork->work, psmouse_smbus_remove_i2c_device);
1608c2ecf20Sopenharmony_ci		rwork->client = client;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci		schedule_work(&rwork->work);
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic void psmouse_smbus_disconnect(struct psmouse *psmouse)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	struct psmouse_smbus_dev *smbdev = psmouse->private;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	mutex_lock(&psmouse_smbus_mutex);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	if (smbdev->dead) {
1738c2ecf20Sopenharmony_ci		list_del(&smbdev->node);
1748c2ecf20Sopenharmony_ci		kfree(smbdev);
1758c2ecf20Sopenharmony_ci	} else {
1768c2ecf20Sopenharmony_ci		smbdev->dead = true;
1778c2ecf20Sopenharmony_ci		psmouse_dbg(smbdev->psmouse,
1788c2ecf20Sopenharmony_ci			    "posting removal request for SMBus companion %s\n",
1798c2ecf20Sopenharmony_ci			    dev_name(&smbdev->client->dev));
1808c2ecf20Sopenharmony_ci		psmouse_smbus_schedule_remove(smbdev->client);
1818c2ecf20Sopenharmony_ci	}
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	mutex_unlock(&psmouse_smbus_mutex);
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	psmouse->private = NULL;
1868c2ecf20Sopenharmony_ci}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_cistatic int psmouse_smbus_create_companion(struct device *dev, void *data)
1898c2ecf20Sopenharmony_ci{
1908c2ecf20Sopenharmony_ci	struct psmouse_smbus_dev *smbdev = data;
1918c2ecf20Sopenharmony_ci	unsigned short addr_list[] = { smbdev->board.addr, I2C_CLIENT_END };
1928c2ecf20Sopenharmony_ci	struct i2c_adapter *adapter;
1938c2ecf20Sopenharmony_ci	struct i2c_client *client;
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	adapter = i2c_verify_adapter(dev);
1968c2ecf20Sopenharmony_ci	if (!adapter)
1978c2ecf20Sopenharmony_ci		return 0;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_HOST_NOTIFY))
2008c2ecf20Sopenharmony_ci		return 0;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	client = i2c_new_scanned_device(adapter, &smbdev->board,
2038c2ecf20Sopenharmony_ci					addr_list, NULL);
2048c2ecf20Sopenharmony_ci	if (IS_ERR(client))
2058c2ecf20Sopenharmony_ci		return 0;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	/* We have our(?) device, stop iterating i2c bus. */
2088c2ecf20Sopenharmony_ci	smbdev->client = client;
2098c2ecf20Sopenharmony_ci	return 1;
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_civoid psmouse_smbus_cleanup(struct psmouse *psmouse)
2138c2ecf20Sopenharmony_ci{
2148c2ecf20Sopenharmony_ci	struct psmouse_smbus_dev *smbdev, *tmp;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	mutex_lock(&psmouse_smbus_mutex);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	list_for_each_entry_safe(smbdev, tmp, &psmouse_smbus_list, node) {
2198c2ecf20Sopenharmony_ci		if (psmouse == smbdev->psmouse) {
2208c2ecf20Sopenharmony_ci			list_del(&smbdev->node);
2218c2ecf20Sopenharmony_ci			kfree(smbdev);
2228c2ecf20Sopenharmony_ci		}
2238c2ecf20Sopenharmony_ci	}
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	mutex_unlock(&psmouse_smbus_mutex);
2268c2ecf20Sopenharmony_ci}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ciint psmouse_smbus_init(struct psmouse *psmouse,
2298c2ecf20Sopenharmony_ci		       const struct i2c_board_info *board,
2308c2ecf20Sopenharmony_ci		       const void *pdata, size_t pdata_size,
2318c2ecf20Sopenharmony_ci		       bool need_deactivate,
2328c2ecf20Sopenharmony_ci		       bool leave_breadcrumbs)
2338c2ecf20Sopenharmony_ci{
2348c2ecf20Sopenharmony_ci	struct psmouse_smbus_dev *smbdev;
2358c2ecf20Sopenharmony_ci	int error;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	smbdev = kzalloc(sizeof(*smbdev), GFP_KERNEL);
2388c2ecf20Sopenharmony_ci	if (!smbdev)
2398c2ecf20Sopenharmony_ci		return -ENOMEM;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	smbdev->psmouse = psmouse;
2428c2ecf20Sopenharmony_ci	smbdev->board = *board;
2438c2ecf20Sopenharmony_ci	smbdev->need_deactivate = need_deactivate;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	if (pdata) {
2468c2ecf20Sopenharmony_ci		smbdev->board.platform_data = kmemdup(pdata, pdata_size,
2478c2ecf20Sopenharmony_ci						      GFP_KERNEL);
2488c2ecf20Sopenharmony_ci		if (!smbdev->board.platform_data) {
2498c2ecf20Sopenharmony_ci			kfree(smbdev);
2508c2ecf20Sopenharmony_ci			return -ENOMEM;
2518c2ecf20Sopenharmony_ci		}
2528c2ecf20Sopenharmony_ci	}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	if (need_deactivate)
2558c2ecf20Sopenharmony_ci		psmouse_deactivate(psmouse);
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	psmouse->private = smbdev;
2588c2ecf20Sopenharmony_ci	psmouse->protocol_handler = psmouse_smbus_process_byte;
2598c2ecf20Sopenharmony_ci	psmouse->reconnect = psmouse_smbus_reconnect;
2608c2ecf20Sopenharmony_ci	psmouse->fast_reconnect = psmouse_smbus_reconnect;
2618c2ecf20Sopenharmony_ci	psmouse->disconnect = psmouse_smbus_disconnect;
2628c2ecf20Sopenharmony_ci	psmouse->resync_time = 0;
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	mutex_lock(&psmouse_smbus_mutex);
2658c2ecf20Sopenharmony_ci	list_add_tail(&smbdev->node, &psmouse_smbus_list);
2668c2ecf20Sopenharmony_ci	mutex_unlock(&psmouse_smbus_mutex);
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	/* Bind to already existing adapters right away */
2698c2ecf20Sopenharmony_ci	error = i2c_for_each_dev(smbdev, psmouse_smbus_create_companion);
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci	if (smbdev->client) {
2728c2ecf20Sopenharmony_ci		/* We have our companion device */
2738c2ecf20Sopenharmony_ci		return 0;
2748c2ecf20Sopenharmony_ci	}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	/*
2778c2ecf20Sopenharmony_ci	 * If we did not create i2c device we will not need platform
2788c2ecf20Sopenharmony_ci	 * data even if we are leaving breadcrumbs.
2798c2ecf20Sopenharmony_ci	 */
2808c2ecf20Sopenharmony_ci	kfree(smbdev->board.platform_data);
2818c2ecf20Sopenharmony_ci	smbdev->board.platform_data = NULL;
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	if (error < 0 || !leave_breadcrumbs) {
2848c2ecf20Sopenharmony_ci		mutex_lock(&psmouse_smbus_mutex);
2858c2ecf20Sopenharmony_ci		list_del(&smbdev->node);
2868c2ecf20Sopenharmony_ci		mutex_unlock(&psmouse_smbus_mutex);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci		kfree(smbdev);
2898c2ecf20Sopenharmony_ci	}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	return error < 0 ? error : -EAGAIN;
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ciint __init psmouse_smbus_module_init(void)
2958c2ecf20Sopenharmony_ci{
2968c2ecf20Sopenharmony_ci	int error;
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	error = bus_register_notifier(&i2c_bus_type, &psmouse_smbus_notifier);
2998c2ecf20Sopenharmony_ci	if (error) {
3008c2ecf20Sopenharmony_ci		pr_err("failed to register i2c bus notifier: %d\n", error);
3018c2ecf20Sopenharmony_ci		return error;
3028c2ecf20Sopenharmony_ci	}
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	return 0;
3058c2ecf20Sopenharmony_ci}
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_civoid psmouse_smbus_module_exit(void)
3088c2ecf20Sopenharmony_ci{
3098c2ecf20Sopenharmony_ci	bus_unregister_notifier(&i2c_bus_type, &psmouse_smbus_notifier);
3108c2ecf20Sopenharmony_ci	flush_scheduled_work();
3118c2ecf20Sopenharmony_ci}
312