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