kcs_bmc_serio.c (3803B)
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 15struct 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 24static 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 29static 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 49static const struct kcs_bmc_client_ops kcs_bmc_serio_client_ops = { 50 .event = kcs_bmc_serio_event, 51}; 52 53static 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 60static 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 67static DEFINE_SPINLOCK(kcs_bmc_serio_instances_lock); 68static LIST_HEAD(kcs_bmc_serio_instances); 69 70static 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 106static 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 134static 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 139static struct kcs_bmc_driver kcs_bmc_serio_driver = { 140 .ops = &kcs_bmc_serio_driver_ops, 141}; 142 143static int kcs_bmc_serio_init(void) 144{ 145 kcs_bmc_register_driver(&kcs_bmc_serio_driver); 146 147 return 0; 148} 149module_init(kcs_bmc_serio_init); 150 151static void kcs_bmc_serio_exit(void) 152{ 153 kcs_bmc_unregister_driver(&kcs_bmc_serio_driver); 154} 155module_exit(kcs_bmc_serio_exit); 156 157MODULE_LICENSE("GPL v2"); 158MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); 159MODULE_DESCRIPTION("Adapter driver for serio access to BMC KCS devices");