1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* Copyright (c) 2021 IBM Corp. */ 3 4 #include <linux/delay.h> 5 #include <linux/device.h> 6 #include <linux/errno.h> 7 #include <linux/list.h> 8 #include <linux/module.h> 9 #include <linux/sched/signal.h> 10 #include <linux/serio.h> 11 #include <linux/slab.h> 12 13 #include "kcs_bmc_client.h" 14 15 struct kcs_bmc_serio { 16 struct list_head entry; 17 18 struct kcs_bmc_client client; 19 struct serio *port; 20 21 spinlock_t lock; 22 }; 23 24 static inline struct kcs_bmc_serio *client_to_kcs_bmc_serio(struct kcs_bmc_client *client) 25 { 26 return container_of(client, struct kcs_bmc_serio, client); 27 } 28 29 static irqreturn_t kcs_bmc_serio_event(struct kcs_bmc_client *client) 30 { 31 struct kcs_bmc_serio *priv; 32 u8 handled = IRQ_NONE; 33 u8 status; 34 35 priv = client_to_kcs_bmc_serio(client); 36 37 spin_lock(&priv->lock); 38 39 status = kcs_bmc_read_status(client->dev); 40 41 if (status & KCS_BMC_STR_IBF) 42 handled = serio_interrupt(priv->port, kcs_bmc_read_data(client->dev), 0); 43 44 spin_unlock(&priv->lock); 45 46 return handled; 47 } 48 49 static const struct kcs_bmc_client_ops kcs_bmc_serio_client_ops = { 50 .event = kcs_bmc_serio_event, 51 }; 52 53 static int kcs_bmc_serio_open(struct serio *port) 54 { 55 struct kcs_bmc_serio *priv = port->port_data; 56 57 return kcs_bmc_enable_device(priv->client.dev, &priv->client); 58 } 59 60 static void kcs_bmc_serio_close(struct serio *port) 61 { 62 struct kcs_bmc_serio *priv = port->port_data; 63 64 kcs_bmc_disable_device(priv->client.dev, &priv->client); 65 } 66 67 static DEFINE_SPINLOCK(kcs_bmc_serio_instances_lock); 68 static LIST_HEAD(kcs_bmc_serio_instances); 69 70 static int kcs_bmc_serio_add_device(struct kcs_bmc_device *kcs_bmc) 71 { 72 struct kcs_bmc_serio *priv; 73 struct serio *port; 74 75 priv = devm_kzalloc(kcs_bmc->dev, sizeof(*priv), GFP_KERNEL); 76 if (!priv) 77 return -ENOMEM; 78 79 /* Use kzalloc() as the allocation is cleaned up with kfree() via serio_unregister_port() */ 80 port = kzalloc(sizeof(*port), GFP_KERNEL); 81 if (!port) 82 return -ENOMEM; 83 84 port->id.type = SERIO_8042; 85 port->open = kcs_bmc_serio_open; 86 port->close = kcs_bmc_serio_close; 87 port->port_data = priv; 88 port->dev.parent = kcs_bmc->dev; 89 90 spin_lock_init(&priv->lock); 91 priv->port = port; 92 priv->client.dev = kcs_bmc; 93 priv->client.ops = &kcs_bmc_serio_client_ops; 94 95 spin_lock_irq(&kcs_bmc_serio_instances_lock); 96 list_add(&priv->entry, &kcs_bmc_serio_instances); 97 spin_unlock_irq(&kcs_bmc_serio_instances_lock); 98 99 serio_register_port(port); 100 101 dev_info(kcs_bmc->dev, "Initialised serio client for channel %d", kcs_bmc->channel); 102 103 return 0; 104 } 105 106 static int kcs_bmc_serio_remove_device(struct kcs_bmc_device *kcs_bmc) 107 { 108 struct kcs_bmc_serio *priv = NULL, *pos; 109 110 spin_lock_irq(&kcs_bmc_serio_instances_lock); 111 list_for_each_entry(pos, &kcs_bmc_serio_instances, entry) { 112 if (pos->client.dev == kcs_bmc) { 113 priv = pos; 114 list_del(&pos->entry); 115 break; 116 } 117 } 118 spin_unlock_irq(&kcs_bmc_serio_instances_lock); 119 120 if (!priv) 121 return -ENODEV; 122 123 /* kfree()s priv->port via put_device() */ 124 serio_unregister_port(priv->port); 125 126 /* Ensure the IBF IRQ is disabled if we were the active client */ 127 kcs_bmc_disable_device(kcs_bmc, &priv->client); 128 129 devm_kfree(priv->client.dev->dev, priv); 130 131 return 0; 132 } 133 134 static const struct kcs_bmc_driver_ops kcs_bmc_serio_driver_ops = { 135 .add_device = kcs_bmc_serio_add_device, 136 .remove_device = kcs_bmc_serio_remove_device, 137 }; 138 139 static struct kcs_bmc_driver kcs_bmc_serio_driver = { 140 .ops = &kcs_bmc_serio_driver_ops, 141 }; 142 143 static int __init kcs_bmc_serio_init(void) 144 { 145 kcs_bmc_register_driver(&kcs_bmc_serio_driver); 146 147 return 0; 148 } 149 module_init(kcs_bmc_serio_init); 150 151 static void __exit kcs_bmc_serio_exit(void) 152 { 153 kcs_bmc_unregister_driver(&kcs_bmc_serio_driver); 154 } 155 module_exit(kcs_bmc_serio_exit); 156 157 MODULE_LICENSE("GPL v2"); 158 MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); 159 MODULE_DESCRIPTION("Adapter driver for serio access to BMC KCS devices"); 160