18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * pmi driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * (C) Copyright IBM Deutschland Entwicklung GmbH 2005
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * PMI (Platform Management Interrupt) is a way to communicate
88c2ecf20Sopenharmony_ci * with the BMC (Baseboard Management Controller) via interrupts.
98c2ecf20Sopenharmony_ci * Unlike IPMI it is bidirectional and has a low latency.
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * Author: Christian Krafft <krafft@de.ibm.com>
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci#include <linux/slab.h>
168c2ecf20Sopenharmony_ci#include <linux/completion.h>
178c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
188c2ecf20Sopenharmony_ci#include <linux/module.h>
198c2ecf20Sopenharmony_ci#include <linux/workqueue.h>
208c2ecf20Sopenharmony_ci#include <linux/of_device.h>
218c2ecf20Sopenharmony_ci#include <linux/of_platform.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include <asm/io.h>
248c2ecf20Sopenharmony_ci#include <asm/pmi.h>
258c2ecf20Sopenharmony_ci#include <asm/prom.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistruct pmi_data {
288c2ecf20Sopenharmony_ci	struct list_head	handler;
298c2ecf20Sopenharmony_ci	spinlock_t		handler_spinlock;
308c2ecf20Sopenharmony_ci	spinlock_t		pmi_spinlock;
318c2ecf20Sopenharmony_ci	struct mutex		msg_mutex;
328c2ecf20Sopenharmony_ci	pmi_message_t		msg;
338c2ecf20Sopenharmony_ci	struct completion	*completion;
348c2ecf20Sopenharmony_ci	struct platform_device	*dev;
358c2ecf20Sopenharmony_ci	int			irq;
368c2ecf20Sopenharmony_ci	u8 __iomem		*pmi_reg;
378c2ecf20Sopenharmony_ci	struct work_struct	work;
388c2ecf20Sopenharmony_ci};
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic struct pmi_data *data;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic irqreturn_t pmi_irq_handler(int irq, void *dev_id)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	u8 type;
458c2ecf20Sopenharmony_ci	int rc;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	spin_lock(&data->pmi_spinlock);
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	type = ioread8(data->pmi_reg + PMI_READ_TYPE);
508c2ecf20Sopenharmony_ci	pr_debug("pmi: got message of type %d\n", type);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	if (type & PMI_ACK && !data->completion) {
538c2ecf20Sopenharmony_ci		printk(KERN_WARNING "pmi: got unexpected ACK message.\n");
548c2ecf20Sopenharmony_ci		rc = -EIO;
558c2ecf20Sopenharmony_ci		goto unlock;
568c2ecf20Sopenharmony_ci	}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	if (data->completion && !(type & PMI_ACK)) {
598c2ecf20Sopenharmony_ci		printk(KERN_WARNING "pmi: expected ACK, but got %d\n", type);
608c2ecf20Sopenharmony_ci		rc = -EIO;
618c2ecf20Sopenharmony_ci		goto unlock;
628c2ecf20Sopenharmony_ci	}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	data->msg.type = type;
658c2ecf20Sopenharmony_ci	data->msg.data0 = ioread8(data->pmi_reg + PMI_READ_DATA0);
668c2ecf20Sopenharmony_ci	data->msg.data1 = ioread8(data->pmi_reg + PMI_READ_DATA1);
678c2ecf20Sopenharmony_ci	data->msg.data2 = ioread8(data->pmi_reg + PMI_READ_DATA2);
688c2ecf20Sopenharmony_ci	rc = 0;
698c2ecf20Sopenharmony_ciunlock:
708c2ecf20Sopenharmony_ci	spin_unlock(&data->pmi_spinlock);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	if (rc == -EIO) {
738c2ecf20Sopenharmony_ci		rc = IRQ_HANDLED;
748c2ecf20Sopenharmony_ci		goto out;
758c2ecf20Sopenharmony_ci	}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	if (data->msg.type & PMI_ACK) {
788c2ecf20Sopenharmony_ci		complete(data->completion);
798c2ecf20Sopenharmony_ci		rc = IRQ_HANDLED;
808c2ecf20Sopenharmony_ci		goto out;
818c2ecf20Sopenharmony_ci	}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	schedule_work(&data->work);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	rc = IRQ_HANDLED;
868c2ecf20Sopenharmony_ciout:
878c2ecf20Sopenharmony_ci	return rc;
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic const struct of_device_id pmi_match[] = {
928c2ecf20Sopenharmony_ci	{ .type = "ibm,pmi", .name = "ibm,pmi" },
938c2ecf20Sopenharmony_ci	{ .type = "ibm,pmi" },
948c2ecf20Sopenharmony_ci	{},
958c2ecf20Sopenharmony_ci};
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, pmi_match);
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistatic void pmi_notify_handlers(struct work_struct *work)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	struct pmi_handler *handler;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	spin_lock(&data->handler_spinlock);
1048c2ecf20Sopenharmony_ci	list_for_each_entry(handler, &data->handler, node) {
1058c2ecf20Sopenharmony_ci		pr_debug("pmi: notifying handler %p\n", handler);
1068c2ecf20Sopenharmony_ci		if (handler->type == data->msg.type)
1078c2ecf20Sopenharmony_ci			handler->handle_pmi_message(data->msg);
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci	spin_unlock(&data->handler_spinlock);
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic int pmi_of_probe(struct platform_device *dev)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct device_node *np = dev->dev.of_node;
1158c2ecf20Sopenharmony_ci	int rc;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	if (data) {
1188c2ecf20Sopenharmony_ci		printk(KERN_ERR "pmi: driver has already been initialized.\n");
1198c2ecf20Sopenharmony_ci		rc = -EBUSY;
1208c2ecf20Sopenharmony_ci		goto out;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	data = kzalloc(sizeof(struct pmi_data), GFP_KERNEL);
1248c2ecf20Sopenharmony_ci	if (!data) {
1258c2ecf20Sopenharmony_ci		printk(KERN_ERR "pmi: could not allocate memory.\n");
1268c2ecf20Sopenharmony_ci		rc = -ENOMEM;
1278c2ecf20Sopenharmony_ci		goto out;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	data->pmi_reg = of_iomap(np, 0);
1318c2ecf20Sopenharmony_ci	if (!data->pmi_reg) {
1328c2ecf20Sopenharmony_ci		printk(KERN_ERR "pmi: invalid register address.\n");
1338c2ecf20Sopenharmony_ci		rc = -EFAULT;
1348c2ecf20Sopenharmony_ci		goto error_cleanup_data;
1358c2ecf20Sopenharmony_ci	}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&data->handler);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	mutex_init(&data->msg_mutex);
1408c2ecf20Sopenharmony_ci	spin_lock_init(&data->pmi_spinlock);
1418c2ecf20Sopenharmony_ci	spin_lock_init(&data->handler_spinlock);
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	INIT_WORK(&data->work, pmi_notify_handlers);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	data->dev = dev;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	data->irq = irq_of_parse_and_map(np, 0);
1488c2ecf20Sopenharmony_ci	if (!data->irq) {
1498c2ecf20Sopenharmony_ci		printk(KERN_ERR "pmi: invalid interrupt.\n");
1508c2ecf20Sopenharmony_ci		rc = -EFAULT;
1518c2ecf20Sopenharmony_ci		goto error_cleanup_iomap;
1528c2ecf20Sopenharmony_ci	}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	rc = request_irq(data->irq, pmi_irq_handler, 0, "pmi", NULL);
1558c2ecf20Sopenharmony_ci	if (rc) {
1568c2ecf20Sopenharmony_ci		printk(KERN_ERR "pmi: can't request IRQ %d: returned %d\n",
1578c2ecf20Sopenharmony_ci				data->irq, rc);
1588c2ecf20Sopenharmony_ci		goto error_cleanup_iomap;
1598c2ecf20Sopenharmony_ci	}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	printk(KERN_INFO "pmi: found pmi device at addr %p.\n", data->pmi_reg);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	goto out;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cierror_cleanup_iomap:
1668c2ecf20Sopenharmony_ci	iounmap(data->pmi_reg);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_cierror_cleanup_data:
1698c2ecf20Sopenharmony_ci	kfree(data);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ciout:
1728c2ecf20Sopenharmony_ci	return rc;
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cistatic int pmi_of_remove(struct platform_device *dev)
1768c2ecf20Sopenharmony_ci{
1778c2ecf20Sopenharmony_ci	struct pmi_handler *handler, *tmp;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	free_irq(data->irq, NULL);
1808c2ecf20Sopenharmony_ci	iounmap(data->pmi_reg);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	spin_lock(&data->handler_spinlock);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	list_for_each_entry_safe(handler, tmp, &data->handler, node)
1858c2ecf20Sopenharmony_ci		list_del(&handler->node);
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	spin_unlock(&data->handler_spinlock);
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	kfree(data);
1908c2ecf20Sopenharmony_ci	data = NULL;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	return 0;
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic struct platform_driver pmi_of_platform_driver = {
1968c2ecf20Sopenharmony_ci	.probe		= pmi_of_probe,
1978c2ecf20Sopenharmony_ci	.remove		= pmi_of_remove,
1988c2ecf20Sopenharmony_ci	.driver = {
1998c2ecf20Sopenharmony_ci		.name = "pmi",
2008c2ecf20Sopenharmony_ci		.of_match_table = pmi_match,
2018c2ecf20Sopenharmony_ci	},
2028c2ecf20Sopenharmony_ci};
2038c2ecf20Sopenharmony_cimodule_platform_driver(pmi_of_platform_driver);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ciint pmi_send_message(pmi_message_t msg)
2068c2ecf20Sopenharmony_ci{
2078c2ecf20Sopenharmony_ci	unsigned long flags;
2088c2ecf20Sopenharmony_ci	DECLARE_COMPLETION_ONSTACK(completion);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	if (!data)
2118c2ecf20Sopenharmony_ci		return -ENODEV;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	mutex_lock(&data->msg_mutex);
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	data->msg = msg;
2168c2ecf20Sopenharmony_ci	pr_debug("pmi_send_message: msg is %08x\n", *(u32*)&msg);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	data->completion = &completion;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	spin_lock_irqsave(&data->pmi_spinlock, flags);
2218c2ecf20Sopenharmony_ci	iowrite8(msg.data0, data->pmi_reg + PMI_WRITE_DATA0);
2228c2ecf20Sopenharmony_ci	iowrite8(msg.data1, data->pmi_reg + PMI_WRITE_DATA1);
2238c2ecf20Sopenharmony_ci	iowrite8(msg.data2, data->pmi_reg + PMI_WRITE_DATA2);
2248c2ecf20Sopenharmony_ci	iowrite8(msg.type, data->pmi_reg + PMI_WRITE_TYPE);
2258c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&data->pmi_spinlock, flags);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	pr_debug("pmi_send_message: wait for completion\n");
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	wait_for_completion_interruptible_timeout(data->completion,
2308c2ecf20Sopenharmony_ci						  PMI_TIMEOUT);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	data->completion = NULL;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	mutex_unlock(&data->msg_mutex);
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	return 0;
2378c2ecf20Sopenharmony_ci}
2388c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pmi_send_message);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ciint pmi_register_handler(struct pmi_handler *handler)
2418c2ecf20Sopenharmony_ci{
2428c2ecf20Sopenharmony_ci	if (!data)
2438c2ecf20Sopenharmony_ci		return -ENODEV;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	spin_lock(&data->handler_spinlock);
2468c2ecf20Sopenharmony_ci	list_add_tail(&handler->node, &data->handler);
2478c2ecf20Sopenharmony_ci	spin_unlock(&data->handler_spinlock);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	return 0;
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pmi_register_handler);
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_civoid pmi_unregister_handler(struct pmi_handler *handler)
2548c2ecf20Sopenharmony_ci{
2558c2ecf20Sopenharmony_ci	if (!data)
2568c2ecf20Sopenharmony_ci		return;
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	pr_debug("pmi: unregistering handler %p\n", handler);
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	spin_lock(&data->handler_spinlock);
2618c2ecf20Sopenharmony_ci	list_del(&handler->node);
2628c2ecf20Sopenharmony_ci	spin_unlock(&data->handler_spinlock);
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(pmi_unregister_handler);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2678c2ecf20Sopenharmony_ciMODULE_AUTHOR("Christian Krafft <krafft@de.ibm.com>");
2688c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("IBM Platform Management Interrupt driver");
269