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