162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2015-2018, Intel Corporation.
462306a36Sopenharmony_ci * Copyright (c) 2021, IBM Corp.
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/device.h>
862306a36Sopenharmony_ci#include <linux/list.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/mutex.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "kcs_bmc.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci/* Implement both the device and client interfaces here */
1562306a36Sopenharmony_ci#include "kcs_bmc_device.h"
1662306a36Sopenharmony_ci#include "kcs_bmc_client.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/* Record registered devices and drivers */
1962306a36Sopenharmony_cistatic DEFINE_MUTEX(kcs_bmc_lock);
2062306a36Sopenharmony_cistatic LIST_HEAD(kcs_bmc_devices);
2162306a36Sopenharmony_cistatic LIST_HEAD(kcs_bmc_drivers);
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* Consumer data access */
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ciu8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_read_data);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_civoid kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_write_data);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ciu8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_read_status);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_civoid kcs_bmc_write_status(struct kcs_bmc_device *kcs_bmc, u8 data)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_write_status);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_civoid kcs_bmc_update_status(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 val)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_update_status);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ciirqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct kcs_bmc_client *client;
5862306a36Sopenharmony_ci	irqreturn_t rc = IRQ_NONE;
5962306a36Sopenharmony_ci	unsigned long flags;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	spin_lock_irqsave(&kcs_bmc->lock, flags);
6262306a36Sopenharmony_ci	client = kcs_bmc->client;
6362306a36Sopenharmony_ci	if (client)
6462306a36Sopenharmony_ci		rc = client->ops->event(client);
6562306a36Sopenharmony_ci	spin_unlock_irqrestore(&kcs_bmc->lock, flags);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return rc;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_handle_event);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ciint kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	int rc;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
7662306a36Sopenharmony_ci	if (kcs_bmc->client) {
7762306a36Sopenharmony_ci		rc = -EBUSY;
7862306a36Sopenharmony_ci	} else {
7962306a36Sopenharmony_ci		u8 mask = KCS_BMC_EVENT_TYPE_IBF;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci		kcs_bmc->client = client;
8262306a36Sopenharmony_ci		kcs_bmc_update_event_mask(kcs_bmc, mask, mask);
8362306a36Sopenharmony_ci		rc = 0;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return rc;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_enable_device);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_civoid kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	spin_lock_irq(&kcs_bmc->lock);
9462306a36Sopenharmony_ci	if (client == kcs_bmc->client) {
9562306a36Sopenharmony_ci		u8 mask = KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci		kcs_bmc_update_event_mask(kcs_bmc, mask, 0);
9862306a36Sopenharmony_ci		kcs_bmc->client = NULL;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci	spin_unlock_irq(&kcs_bmc->lock);
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_disable_device);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ciint kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
10562306a36Sopenharmony_ci{
10662306a36Sopenharmony_ci	struct kcs_bmc_driver *drv;
10762306a36Sopenharmony_ci	int error = 0;
10862306a36Sopenharmony_ci	int rc;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	spin_lock_init(&kcs_bmc->lock);
11162306a36Sopenharmony_ci	kcs_bmc->client = NULL;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	mutex_lock(&kcs_bmc_lock);
11462306a36Sopenharmony_ci	list_add(&kcs_bmc->entry, &kcs_bmc_devices);
11562306a36Sopenharmony_ci	list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
11662306a36Sopenharmony_ci		rc = drv->ops->add_device(kcs_bmc);
11762306a36Sopenharmony_ci		if (!rc)
11862306a36Sopenharmony_ci			continue;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci		dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
12162306a36Sopenharmony_ci			kcs_bmc->channel, rc);
12262306a36Sopenharmony_ci		error = rc;
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci	mutex_unlock(&kcs_bmc_lock);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return error;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_add_device);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_civoid kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct kcs_bmc_driver *drv;
13362306a36Sopenharmony_ci	int rc;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	mutex_lock(&kcs_bmc_lock);
13662306a36Sopenharmony_ci	list_del(&kcs_bmc->entry);
13762306a36Sopenharmony_ci	list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
13862306a36Sopenharmony_ci		rc = drv->ops->remove_device(kcs_bmc);
13962306a36Sopenharmony_ci		if (rc)
14062306a36Sopenharmony_ci			dev_err(kcs_bmc->dev, "Failed to remove chardev for KCS channel %d: %d",
14162306a36Sopenharmony_ci				kcs_bmc->channel, rc);
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci	mutex_unlock(&kcs_bmc_lock);
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_remove_device);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_civoid kcs_bmc_register_driver(struct kcs_bmc_driver *drv)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	struct kcs_bmc_device *kcs_bmc;
15062306a36Sopenharmony_ci	int rc;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	mutex_lock(&kcs_bmc_lock);
15362306a36Sopenharmony_ci	list_add(&drv->entry, &kcs_bmc_drivers);
15462306a36Sopenharmony_ci	list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
15562306a36Sopenharmony_ci		rc = drv->ops->add_device(kcs_bmc);
15662306a36Sopenharmony_ci		if (rc)
15762306a36Sopenharmony_ci			dev_err(kcs_bmc->dev, "Failed to add driver for KCS channel %d: %d",
15862306a36Sopenharmony_ci				kcs_bmc->channel, rc);
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci	mutex_unlock(&kcs_bmc_lock);
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_register_driver);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_civoid kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	struct kcs_bmc_device *kcs_bmc;
16762306a36Sopenharmony_ci	int rc;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	mutex_lock(&kcs_bmc_lock);
17062306a36Sopenharmony_ci	list_del(&drv->entry);
17162306a36Sopenharmony_ci	list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
17262306a36Sopenharmony_ci		rc = drv->ops->remove_device(kcs_bmc);
17362306a36Sopenharmony_ci		if (rc)
17462306a36Sopenharmony_ci			dev_err(kcs_bmc->dev, "Failed to remove driver for KCS channel %d: %d",
17562306a36Sopenharmony_ci				kcs_bmc->channel, rc);
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci	mutex_unlock(&kcs_bmc_lock);
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_unregister_driver);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_civoid kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	kcs_bmc->ops->irq_mask_update(kcs_bmc, mask, events);
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ciEXPORT_SYMBOL(kcs_bmc_update_event_mask);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
18862306a36Sopenharmony_ciMODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
18962306a36Sopenharmony_ciMODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
19062306a36Sopenharmony_ciMODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
191