1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2015-2018, Intel Corporation.
4 * Copyright (c) 2021, IBM Corp.
5 */
6
7#include <linux/device.h>
8#include <linux/list.h>
9#include <linux/module.h>
10#include <linux/mutex.h>
11
12#include "kcs_bmc.h"
13
14/* Implement both the device and client interfaces here */
15#include "kcs_bmc_device.h"
16#include "kcs_bmc_client.h"
17
18/* Record registered devices and drivers */
19static DEFINE_MUTEX(kcs_bmc_lock);
20static LIST_HEAD(kcs_bmc_devices);
21static LIST_HEAD(kcs_bmc_drivers);
22
23/* Consumer data access */
24
25u8 kcs_bmc_read_data(struct kcs_bmc_device *kcs_bmc)
26{
27	return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.idr);
28}
29EXPORT_SYMBOL(kcs_bmc_read_data);
30
31void kcs_bmc_write_data(struct kcs_bmc_device *kcs_bmc, u8 data)
32{
33	kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.odr, data);
34}
35EXPORT_SYMBOL(kcs_bmc_write_data);
36
37u8 kcs_bmc_read_status(struct kcs_bmc_device *kcs_bmc)
38{
39	return kcs_bmc->ops->io_inputb(kcs_bmc, kcs_bmc->ioreg.str);
40}
41EXPORT_SYMBOL(kcs_bmc_read_status);
42
43void kcs_bmc_write_status(struct kcs_bmc_device *kcs_bmc, u8 data)
44{
45	kcs_bmc->ops->io_outputb(kcs_bmc, kcs_bmc->ioreg.str, data);
46}
47EXPORT_SYMBOL(kcs_bmc_write_status);
48
49void kcs_bmc_update_status(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 val)
50{
51	kcs_bmc->ops->io_updateb(kcs_bmc, kcs_bmc->ioreg.str, mask, val);
52}
53EXPORT_SYMBOL(kcs_bmc_update_status);
54
55irqreturn_t kcs_bmc_handle_event(struct kcs_bmc_device *kcs_bmc)
56{
57	struct kcs_bmc_client *client;
58	irqreturn_t rc = IRQ_NONE;
59	unsigned long flags;
60
61	spin_lock_irqsave(&kcs_bmc->lock, flags);
62	client = kcs_bmc->client;
63	if (client)
64		rc = client->ops->event(client);
65	spin_unlock_irqrestore(&kcs_bmc->lock, flags);
66
67	return rc;
68}
69EXPORT_SYMBOL(kcs_bmc_handle_event);
70
71int kcs_bmc_enable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
72{
73	int rc;
74
75	spin_lock_irq(&kcs_bmc->lock);
76	if (kcs_bmc->client) {
77		rc = -EBUSY;
78	} else {
79		u8 mask = KCS_BMC_EVENT_TYPE_IBF;
80
81		kcs_bmc->client = client;
82		kcs_bmc_update_event_mask(kcs_bmc, mask, mask);
83		rc = 0;
84	}
85	spin_unlock_irq(&kcs_bmc->lock);
86
87	return rc;
88}
89EXPORT_SYMBOL(kcs_bmc_enable_device);
90
91void kcs_bmc_disable_device(struct kcs_bmc_device *kcs_bmc, struct kcs_bmc_client *client)
92{
93	spin_lock_irq(&kcs_bmc->lock);
94	if (client == kcs_bmc->client) {
95		u8 mask = KCS_BMC_EVENT_TYPE_IBF | KCS_BMC_EVENT_TYPE_OBE;
96
97		kcs_bmc_update_event_mask(kcs_bmc, mask, 0);
98		kcs_bmc->client = NULL;
99	}
100	spin_unlock_irq(&kcs_bmc->lock);
101}
102EXPORT_SYMBOL(kcs_bmc_disable_device);
103
104int kcs_bmc_add_device(struct kcs_bmc_device *kcs_bmc)
105{
106	struct kcs_bmc_driver *drv;
107	int error = 0;
108	int rc;
109
110	spin_lock_init(&kcs_bmc->lock);
111	kcs_bmc->client = NULL;
112
113	mutex_lock(&kcs_bmc_lock);
114	list_add(&kcs_bmc->entry, &kcs_bmc_devices);
115	list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
116		rc = drv->ops->add_device(kcs_bmc);
117		if (!rc)
118			continue;
119
120		dev_err(kcs_bmc->dev, "Failed to add chardev for KCS channel %d: %d",
121			kcs_bmc->channel, rc);
122		error = rc;
123	}
124	mutex_unlock(&kcs_bmc_lock);
125
126	return error;
127}
128EXPORT_SYMBOL(kcs_bmc_add_device);
129
130void kcs_bmc_remove_device(struct kcs_bmc_device *kcs_bmc)
131{
132	struct kcs_bmc_driver *drv;
133	int rc;
134
135	mutex_lock(&kcs_bmc_lock);
136	list_del(&kcs_bmc->entry);
137	list_for_each_entry(drv, &kcs_bmc_drivers, entry) {
138		rc = drv->ops->remove_device(kcs_bmc);
139		if (rc)
140			dev_err(kcs_bmc->dev, "Failed to remove chardev for KCS channel %d: %d",
141				kcs_bmc->channel, rc);
142	}
143	mutex_unlock(&kcs_bmc_lock);
144}
145EXPORT_SYMBOL(kcs_bmc_remove_device);
146
147void kcs_bmc_register_driver(struct kcs_bmc_driver *drv)
148{
149	struct kcs_bmc_device *kcs_bmc;
150	int rc;
151
152	mutex_lock(&kcs_bmc_lock);
153	list_add(&drv->entry, &kcs_bmc_drivers);
154	list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
155		rc = drv->ops->add_device(kcs_bmc);
156		if (rc)
157			dev_err(kcs_bmc->dev, "Failed to add driver for KCS channel %d: %d",
158				kcs_bmc->channel, rc);
159	}
160	mutex_unlock(&kcs_bmc_lock);
161}
162EXPORT_SYMBOL(kcs_bmc_register_driver);
163
164void kcs_bmc_unregister_driver(struct kcs_bmc_driver *drv)
165{
166	struct kcs_bmc_device *kcs_bmc;
167	int rc;
168
169	mutex_lock(&kcs_bmc_lock);
170	list_del(&drv->entry);
171	list_for_each_entry(kcs_bmc, &kcs_bmc_devices, entry) {
172		rc = drv->ops->remove_device(kcs_bmc);
173		if (rc)
174			dev_err(kcs_bmc->dev, "Failed to remove driver for KCS channel %d: %d",
175				kcs_bmc->channel, rc);
176	}
177	mutex_unlock(&kcs_bmc_lock);
178}
179EXPORT_SYMBOL(kcs_bmc_unregister_driver);
180
181void kcs_bmc_update_event_mask(struct kcs_bmc_device *kcs_bmc, u8 mask, u8 events)
182{
183	kcs_bmc->ops->irq_mask_update(kcs_bmc, mask, events);
184}
185EXPORT_SYMBOL(kcs_bmc_update_event_mask);
186
187MODULE_LICENSE("GPL v2");
188MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>");
189MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
190MODULE_DESCRIPTION("KCS BMC to handle the IPMI request from system software");
191