162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* Copyright (c) 2021 IBM Corp. */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/delay.h>
562306a36Sopenharmony_ci#include <linux/device.h>
662306a36Sopenharmony_ci#include <linux/errno.h>
762306a36Sopenharmony_ci#include <linux/list.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/sched/signal.h>
1062306a36Sopenharmony_ci#include <linux/serio.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "kcs_bmc_client.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistruct kcs_bmc_serio {
1662306a36Sopenharmony_ci	struct list_head entry;
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci	struct kcs_bmc_client client;
1962306a36Sopenharmony_ci	struct serio *port;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci	spinlock_t lock;
2262306a36Sopenharmony_ci};
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic inline struct kcs_bmc_serio *client_to_kcs_bmc_serio(struct kcs_bmc_client *client)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	return container_of(client, struct kcs_bmc_serio, client);
2762306a36Sopenharmony_ci}
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic irqreturn_t kcs_bmc_serio_event(struct kcs_bmc_client *client)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	struct kcs_bmc_serio *priv;
3262306a36Sopenharmony_ci	u8 handled = IRQ_NONE;
3362306a36Sopenharmony_ci	u8 status;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	priv = client_to_kcs_bmc_serio(client);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	spin_lock(&priv->lock);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	status = kcs_bmc_read_status(client->dev);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if (status & KCS_BMC_STR_IBF)
4262306a36Sopenharmony_ci		handled = serio_interrupt(priv->port, kcs_bmc_read_data(client->dev), 0);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	spin_unlock(&priv->lock);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	return handled;
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic const struct kcs_bmc_client_ops kcs_bmc_serio_client_ops = {
5062306a36Sopenharmony_ci	.event = kcs_bmc_serio_event,
5162306a36Sopenharmony_ci};
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int kcs_bmc_serio_open(struct serio *port)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct kcs_bmc_serio *priv = port->port_data;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	return kcs_bmc_enable_device(priv->client.dev, &priv->client);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void kcs_bmc_serio_close(struct serio *port)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct kcs_bmc_serio *priv = port->port_data;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	kcs_bmc_disable_device(priv->client.dev, &priv->client);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic DEFINE_SPINLOCK(kcs_bmc_serio_instances_lock);
6862306a36Sopenharmony_cistatic LIST_HEAD(kcs_bmc_serio_instances);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic int kcs_bmc_serio_add_device(struct kcs_bmc_device *kcs_bmc)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct kcs_bmc_serio *priv;
7362306a36Sopenharmony_ci	struct serio *port;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL);
7662306a36Sopenharmony_ci	if (!priv)
7762306a36Sopenharmony_ci		return -ENOMEM;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	/* Use kzalloc() as the allocation is cleaned up with kfree() via serio_unregister_port() */
8062306a36Sopenharmony_ci	port = kzalloc(sizeof(*port), GFP_KERNEL);
8162306a36Sopenharmony_ci	if (!port)
8262306a36Sopenharmony_ci		return -ENOMEM;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	port->id.type = SERIO_8042;
8562306a36Sopenharmony_ci	port->open = kcs_bmc_serio_open;
8662306a36Sopenharmony_ci	port->close = kcs_bmc_serio_close;
8762306a36Sopenharmony_ci	port->port_data = priv;
8862306a36Sopenharmony_ci	port->dev.parent = kcs_bmc->dev;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	spin_lock_init(&priv->lock);
9162306a36Sopenharmony_ci	priv->port = port;
9262306a36Sopenharmony_ci	priv->client.dev = kcs_bmc;
9362306a36Sopenharmony_ci	priv->client.ops = &kcs_bmc_serio_client_ops;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	spin_lock_irq(&kcs_bmc_serio_instances_lock);
9662306a36Sopenharmony_ci	list_add(&priv->entry, &kcs_bmc_serio_instances);
9762306a36Sopenharmony_ci	spin_unlock_irq(&kcs_bmc_serio_instances_lock);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	serio_register_port(port);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	dev_info(kcs_bmc->dev, "Initialised serio client for channel %d", kcs_bmc->channel);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	return 0;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic int kcs_bmc_serio_remove_device(struct kcs_bmc_device *kcs_bmc)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct kcs_bmc_serio *priv = NULL, *pos;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	spin_lock_irq(&kcs_bmc_serio_instances_lock);
11162306a36Sopenharmony_ci	list_for_each_entry(pos, &kcs_bmc_serio_instances, entry) {
11262306a36Sopenharmony_ci		if (pos->client.dev == kcs_bmc) {
11362306a36Sopenharmony_ci			priv = pos;
11462306a36Sopenharmony_ci			list_del(&pos->entry);
11562306a36Sopenharmony_ci			break;
11662306a36Sopenharmony_ci		}
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci	spin_unlock_irq(&kcs_bmc_serio_instances_lock);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	if (!priv)
12162306a36Sopenharmony_ci		return -ENODEV;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	/* kfree()s priv->port via put_device() */
12462306a36Sopenharmony_ci	serio_unregister_port(priv->port);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	/* Ensure the IBF IRQ is disabled if we were the active client */
12762306a36Sopenharmony_ci	kcs_bmc_disable_device(kcs_bmc, &priv->client);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	devm_kfree(priv->client.dev->dev, priv);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	return 0;
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_cistatic const struct kcs_bmc_driver_ops kcs_bmc_serio_driver_ops = {
13562306a36Sopenharmony_ci	.add_device = kcs_bmc_serio_add_device,
13662306a36Sopenharmony_ci	.remove_device = kcs_bmc_serio_remove_device,
13762306a36Sopenharmony_ci};
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic struct kcs_bmc_driver kcs_bmc_serio_driver = {
14062306a36Sopenharmony_ci	.ops = &kcs_bmc_serio_driver_ops,
14162306a36Sopenharmony_ci};
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic int __init kcs_bmc_serio_init(void)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	kcs_bmc_register_driver(&kcs_bmc_serio_driver);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	return 0;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_cimodule_init(kcs_bmc_serio_init);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic void __exit kcs_bmc_serio_exit(void)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	kcs_bmc_unregister_driver(&kcs_bmc_serio_driver);
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_cimodule_exit(kcs_bmc_serio_exit);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
15862306a36Sopenharmony_ciMODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
15962306a36Sopenharmony_ciMODULE_DESCRIPTION("Adapter driver for serio access to BMC KCS devices");
160