| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2015-2018, Intel Corporation. | |
| * Copyright (c) 2021, IBM Corp. | |
| */ | |
| #include <linux/device.h> | |
| #include <linux/list.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include "kcs_bmc.h" | |
| /* Implement both the device and client interfaces here */ | |
| #include "kcs_bmc_device.h" | |
| #include "kcs_bmc_client.h" | |
| /* Record registered devices and drivers */ | |
| static DEFINE_MUTEX(kcs_bmc_lock); | |
| static LIST_HEAD(kcs_bmc_devices); | |
| static LIST_HEAD(kcs_bmc_drivers); | |
| /* Consumer data access */ | |
| u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc) | |
| { | |
| return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_read_data); | |
| void kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data) | |
| { | |
| kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_write_data); | |
| u8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc) | |
| { | |
| return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.str); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_read_status); | |
| void kcs_bmc_write_status(struct kcs_bmc_device *kcs_bmc, u8 data) | |
| { | |
| kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_write_status); | |
| void kcs_bmc_update_status(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 val) | |
| { | |
| kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_update_status); | |
| irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc) | |
| { | |
| struct kcs_bmc_client *client; | |
| irqreturn_t rc = IRQ_NONE; | |
| unsigned long flags; | |
| spin_lock_irqsave(&kcs_bmc->lock, flags); | |
| client = kcs_bmc->client; | |
| if (client) | |
| rc = client->ops->event(client); | |
| spin_unlock_irqrestore(&kcs_bmc->lock, flags); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_handle_event); | |
| int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client) | |
| { | |
| int rc; | |
| spin_lock_irq(&kcs_bmc->lock); | |
| if (kcs_bmc->client) { | |
| rc = -EBUSY; | |
| } else { | |
| u8 mask = KCS_BMC_EVENT_TYPE_IBF; | |
| kcs_bmc->client = client; | |
| kcs_bmc_update_event_mask(kcs_bmc, mask, mask); | |
| rc = 0; | |
| } | |
| spin_unlock_irq(&kcs_bmc->lock); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_enable_device); | |
| void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client) | |
| { | |
| spin_lock_irq(&kcs_bmc->lock); | |
| if (client == kcs_bmc->client) { | |
| u8 mask = KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE; | |
| kcs_bmc_update_event_mask(kcs_bmc, mask, 0); | |
| kcs_bmc->client = NULL; | |
| } | |
| spin_unlock_irq(&kcs_bmc->lock); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_disable_device); | |
| int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc) | |
| { | |
| struct kcs_bmc_driver *drv; | |
| int error = 0; | |
| int rc; | |
| spin_lock_init(&kcs_bmc->lock); | |
| kcs_bmc->client = NULL; | |
| mutex_lock(&kcs_bmc_lock); | |
| list_add(&kcs_bmc->entry, &kcs_bmc_devices); | |
| list_for_each_entry(drv, &kcs_bmc_drivers, entry) { | |
| rc = drv->ops->add_device(kcs_bmc); | |
| if (!rc) | |
| continue; | |
| dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d", | |
| kcs_bmc->channel, rc); | |
| error = rc; | |
| } | |
| mutex_unlock(&kcs_bmc_lock); | |
| return error; | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_add_device); | |
| void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc) | |
| { | |
| struct kcs_bmc_driver *drv; | |
| int rc; | |
| mutex_lock(&kcs_bmc_lock); | |
| list_del(&kcs_bmc->entry); | |
| list_for_each_entry(drv, &kcs_bmc_drivers, entry) { | |
| rc = drv->ops->remove_device(kcs_bmc); | |
| if (rc) | |
| dev_err(kcs_bmc->dev, "Failed to remove chardev for KCS channel %d: %d", | |
| kcs_bmc->channel, rc); | |
| } | |
| mutex_unlock(&kcs_bmc_lock); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_remove_device); | |
| void kcs_bmc_register_driver(struct kcs_bmc_driver *drv) | |
| { | |
| struct kcs_bmc_device *kcs_bmc; | |
| int rc; | |
| mutex_lock(&kcs_bmc_lock); | |
| list_add(&drv->entry, &kcs_bmc_drivers); | |
| list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) { | |
| rc = drv->ops->add_device(kcs_bmc); | |
| if (rc) | |
| dev_err(kcs_bmc->dev, "Failed to add driver for KCS channel %d: %d", | |
| kcs_bmc->channel, rc); | |
| } | |
| mutex_unlock(&kcs_bmc_lock); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_register_driver); | |
| void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv) | |
| { | |
| struct kcs_bmc_device *kcs_bmc; | |
| int rc; | |
| mutex_lock(&kcs_bmc_lock); | |
| list_del(&drv->entry); | |
| list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) { | |
| rc = drv->ops->remove_device(kcs_bmc); | |
| if (rc) | |
| dev_err(kcs_bmc->dev, "Failed to remove driver for KCS channel %d: %d", | |
| kcs_bmc->channel, rc); | |
| } | |
| mutex_unlock(&kcs_bmc_lock); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_unregister_driver); | |
| void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events) | |
| { | |
| kcs_bmc->ops->irq_mask_update(kcs_bmc, mask, events); | |
| } | |
| EXPORT_SYMBOL(kcs_bmc_update_event_mask); | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>"); | |
| MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); | |
| MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software"); |