162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * pmi driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * PMI (Platform Management Interrupt) is a way to communicate 862306a36Sopenharmony_ci * with the BMC (Baseboard Management Controller) via interrupts. 962306a36Sopenharmony_ci * Unlike IPMI it is bidirectional and has a low latency. 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Author: Christian Krafft <krafft@de.ibm.com> 1262306a36Sopenharmony_ci */ 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <linux/interrupt.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci#include <linux/completion.h> 1762306a36Sopenharmony_ci#include <linux/spinlock.h> 1862306a36Sopenharmony_ci#include <linux/module.h> 1962306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 2062306a36Sopenharmony_ci#include <linux/workqueue.h> 2162306a36Sopenharmony_ci#include <linux/of_address.h> 2262306a36Sopenharmony_ci#include <linux/of_irq.h> 2362306a36Sopenharmony_ci#include <linux/platform_device.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include <asm/io.h> 2662306a36Sopenharmony_ci#include <asm/pmi.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct pmi_data { 2962306a36Sopenharmony_ci struct list_head handler; 3062306a36Sopenharmony_ci spinlock_t handler_spinlock; 3162306a36Sopenharmony_ci spinlock_t pmi_spinlock; 3262306a36Sopenharmony_ci struct mutex msg_mutex; 3362306a36Sopenharmony_ci pmi_message_t msg; 3462306a36Sopenharmony_ci struct completion *completion; 3562306a36Sopenharmony_ci struct platform_device *dev; 3662306a36Sopenharmony_ci int irq; 3762306a36Sopenharmony_ci u8 __iomem *pmi_reg; 3862306a36Sopenharmony_ci struct work_struct work; 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic struct pmi_data *data; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic irqreturn_t pmi_irq_handler(int irq, void *dev_id) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci u8 type; 4662306a36Sopenharmony_ci int rc; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci spin_lock(&data->pmi_spinlock); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci type = ioread8(data->pmi_reg + PMI_READ_TYPE); 5162306a36Sopenharmony_ci pr_debug("pmi: got message of type %d\n", type); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci if (type & PMI_ACK && !data->completion) { 5462306a36Sopenharmony_ci printk(KERN_WARNING "pmi: got unexpected ACK message.\n"); 5562306a36Sopenharmony_ci rc = -EIO; 5662306a36Sopenharmony_ci goto unlock; 5762306a36Sopenharmony_ci } 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci if (data->completion && !(type & PMI_ACK)) { 6062306a36Sopenharmony_ci printk(KERN_WARNING "pmi: expected ACK, but got %d\n", type); 6162306a36Sopenharmony_ci rc = -EIO; 6262306a36Sopenharmony_ci goto unlock; 6362306a36Sopenharmony_ci } 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci data->msg.type = type; 6662306a36Sopenharmony_ci data->msg.data0 = ioread8(data->pmi_reg + PMI_READ_DATA0); 6762306a36Sopenharmony_ci data->msg.data1 = ioread8(data->pmi_reg + PMI_READ_DATA1); 6862306a36Sopenharmony_ci data->msg.data2 = ioread8(data->pmi_reg + PMI_READ_DATA2); 6962306a36Sopenharmony_ci rc = 0; 7062306a36Sopenharmony_ciunlock: 7162306a36Sopenharmony_ci spin_unlock(&data->pmi_spinlock); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci if (rc == -EIO) { 7462306a36Sopenharmony_ci rc = IRQ_HANDLED; 7562306a36Sopenharmony_ci goto out; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci if (data->msg.type & PMI_ACK) { 7962306a36Sopenharmony_ci complete(data->completion); 8062306a36Sopenharmony_ci rc = IRQ_HANDLED; 8162306a36Sopenharmony_ci goto out; 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci schedule_work(&data->work); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci rc = IRQ_HANDLED; 8762306a36Sopenharmony_ciout: 8862306a36Sopenharmony_ci return rc; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic const struct of_device_id pmi_match[] = { 9362306a36Sopenharmony_ci { .type = "ibm,pmi", .name = "ibm,pmi" }, 9462306a36Sopenharmony_ci { .type = "ibm,pmi" }, 9562306a36Sopenharmony_ci {}, 9662306a36Sopenharmony_ci}; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, pmi_match); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic void pmi_notify_handlers(struct work_struct *work) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci struct pmi_handler *handler; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci spin_lock(&data->handler_spinlock); 10562306a36Sopenharmony_ci list_for_each_entry(handler, &data->handler, node) { 10662306a36Sopenharmony_ci pr_debug("pmi: notifying handler %p\n", handler); 10762306a36Sopenharmony_ci if (handler->type == data->msg.type) 10862306a36Sopenharmony_ci handler->handle_pmi_message(data->msg); 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci spin_unlock(&data->handler_spinlock); 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int pmi_of_probe(struct platform_device *dev) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci struct device_node *np = dev->dev.of_node; 11662306a36Sopenharmony_ci int rc; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci if (data) { 11962306a36Sopenharmony_ci printk(KERN_ERR "pmi: driver has already been initialized.\n"); 12062306a36Sopenharmony_ci rc = -EBUSY; 12162306a36Sopenharmony_ci goto out; 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci data = kzalloc(sizeof(struct pmi_data), GFP_KERNEL); 12562306a36Sopenharmony_ci if (!data) { 12662306a36Sopenharmony_ci printk(KERN_ERR "pmi: could not allocate memory.\n"); 12762306a36Sopenharmony_ci rc = -ENOMEM; 12862306a36Sopenharmony_ci goto out; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci data->pmi_reg = of_iomap(np, 0); 13262306a36Sopenharmony_ci if (!data->pmi_reg) { 13362306a36Sopenharmony_ci printk(KERN_ERR "pmi: invalid register address.\n"); 13462306a36Sopenharmony_ci rc = -EFAULT; 13562306a36Sopenharmony_ci goto error_cleanup_data; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci INIT_LIST_HEAD(&data->handler); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci mutex_init(&data->msg_mutex); 14162306a36Sopenharmony_ci spin_lock_init(&data->pmi_spinlock); 14262306a36Sopenharmony_ci spin_lock_init(&data->handler_spinlock); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci INIT_WORK(&data->work, pmi_notify_handlers); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci data->dev = dev; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci data->irq = irq_of_parse_and_map(np, 0); 14962306a36Sopenharmony_ci if (!data->irq) { 15062306a36Sopenharmony_ci printk(KERN_ERR "pmi: invalid interrupt.\n"); 15162306a36Sopenharmony_ci rc = -EFAULT; 15262306a36Sopenharmony_ci goto error_cleanup_iomap; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci rc = request_irq(data->irq, pmi_irq_handler, 0, "pmi", NULL); 15662306a36Sopenharmony_ci if (rc) { 15762306a36Sopenharmony_ci printk(KERN_ERR "pmi: can't request IRQ %d: returned %d\n", 15862306a36Sopenharmony_ci data->irq, rc); 15962306a36Sopenharmony_ci goto error_cleanup_iomap; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci printk(KERN_INFO "pmi: found pmi device at addr %p.\n", data->pmi_reg); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci goto out; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cierror_cleanup_iomap: 16762306a36Sopenharmony_ci iounmap(data->pmi_reg); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cierror_cleanup_data: 17062306a36Sopenharmony_ci kfree(data); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ciout: 17362306a36Sopenharmony_ci return rc; 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic int pmi_of_remove(struct platform_device *dev) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct pmi_handler *handler, *tmp; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci free_irq(data->irq, NULL); 18162306a36Sopenharmony_ci iounmap(data->pmi_reg); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci spin_lock(&data->handler_spinlock); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci list_for_each_entry_safe(handler, tmp, &data->handler, node) 18662306a36Sopenharmony_ci list_del(&handler->node); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci spin_unlock(&data->handler_spinlock); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci kfree(data); 19162306a36Sopenharmony_ci data = NULL; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci return 0; 19462306a36Sopenharmony_ci} 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic struct platform_driver pmi_of_platform_driver = { 19762306a36Sopenharmony_ci .probe = pmi_of_probe, 19862306a36Sopenharmony_ci .remove = pmi_of_remove, 19962306a36Sopenharmony_ci .driver = { 20062306a36Sopenharmony_ci .name = "pmi", 20162306a36Sopenharmony_ci .of_match_table = pmi_match, 20262306a36Sopenharmony_ci }, 20362306a36Sopenharmony_ci}; 20462306a36Sopenharmony_cimodule_platform_driver(pmi_of_platform_driver); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ciint pmi_send_message(pmi_message_t msg) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci unsigned long flags; 20962306a36Sopenharmony_ci DECLARE_COMPLETION_ONSTACK(completion); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (!data) 21262306a36Sopenharmony_ci return -ENODEV; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci mutex_lock(&data->msg_mutex); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci data->msg = msg; 21762306a36Sopenharmony_ci pr_debug("pmi_send_message: msg is %08x\n", *(u32*)&msg); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci data->completion = &completion; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci spin_lock_irqsave(&data->pmi_spinlock, flags); 22262306a36Sopenharmony_ci iowrite8(msg.data0, data->pmi_reg + PMI_WRITE_DATA0); 22362306a36Sopenharmony_ci iowrite8(msg.data1, data->pmi_reg + PMI_WRITE_DATA1); 22462306a36Sopenharmony_ci iowrite8(msg.data2, data->pmi_reg + PMI_WRITE_DATA2); 22562306a36Sopenharmony_ci iowrite8(msg.type, data->pmi_reg + PMI_WRITE_TYPE); 22662306a36Sopenharmony_ci spin_unlock_irqrestore(&data->pmi_spinlock, flags); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci pr_debug("pmi_send_message: wait for completion\n"); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci wait_for_completion_interruptible_timeout(data->completion, 23162306a36Sopenharmony_ci PMI_TIMEOUT); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci data->completion = NULL; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci mutex_unlock(&data->msg_mutex); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci return 0; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(pmi_send_message); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ciint pmi_register_handler(struct pmi_handler *handler) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci if (!data) 24462306a36Sopenharmony_ci return -ENODEV; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci spin_lock(&data->handler_spinlock); 24762306a36Sopenharmony_ci list_add_tail(&handler->node, &data->handler); 24862306a36Sopenharmony_ci spin_unlock(&data->handler_spinlock); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci return 0; 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(pmi_register_handler); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_civoid pmi_unregister_handler(struct pmi_handler *handler) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci if (!data) 25762306a36Sopenharmony_ci return; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci pr_debug("pmi: unregistering handler %p\n", handler); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci spin_lock(&data->handler_spinlock); 26262306a36Sopenharmony_ci list_del(&handler->node); 26362306a36Sopenharmony_ci spin_unlock(&data->handler_spinlock); 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(pmi_unregister_handler); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 26862306a36Sopenharmony_ciMODULE_AUTHOR("Christian Krafft <krafft@de.ibm.com>"); 26962306a36Sopenharmony_ciMODULE_DESCRIPTION("IBM Platform Management Interrupt driver"); 270