Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * OPAL Operator Panel Display Driver
0004  *
0005  * Copyright 2016, Suraj Jitindar Singh, IBM Corporation.
0006  */
0007 
0008 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0009 
0010 #include <linux/init.h>
0011 #include <linux/module.h>
0012 #include <linux/kernel.h>
0013 #include <linux/fs.h>
0014 #include <linux/device.h>
0015 #include <linux/errno.h>
0016 #include <linux/mutex.h>
0017 #include <linux/of.h>
0018 #include <linux/slab.h>
0019 #include <linux/platform_device.h>
0020 #include <linux/miscdevice.h>
0021 
0022 #include <asm/opal.h>
0023 
0024 /*
0025  * This driver creates a character device (/dev/op_panel) which exposes the
0026  * operator panel (character LCD display) on IBM Power Systems machines
0027  * with FSPs.
0028  * A character buffer written to the device will be displayed on the
0029  * operator panel.
0030  */
0031 
0032 static DEFINE_MUTEX(oppanel_mutex);
0033 
0034 static u32      num_lines, oppanel_size;
0035 static oppanel_line_t   *oppanel_lines;
0036 static char     *oppanel_data;
0037 
0038 static loff_t oppanel_llseek(struct file *filp, loff_t offset, int whence)
0039 {
0040     return fixed_size_llseek(filp, offset, whence, oppanel_size);
0041 }
0042 
0043 static ssize_t oppanel_read(struct file *filp, char __user *userbuf, size_t len,
0044                 loff_t *f_pos)
0045 {
0046     return simple_read_from_buffer(userbuf, len, f_pos, oppanel_data,
0047             oppanel_size);
0048 }
0049 
0050 static int __op_panel_update_display(void)
0051 {
0052     struct opal_msg msg;
0053     int rc, token;
0054 
0055     token = opal_async_get_token_interruptible();
0056     if (token < 0) {
0057         if (token != -ERESTARTSYS)
0058             pr_debug("Couldn't get OPAL async token [token=%d]\n",
0059                 token);
0060         return token;
0061     }
0062 
0063     rc = opal_write_oppanel_async(token, oppanel_lines, num_lines);
0064     switch (rc) {
0065     case OPAL_ASYNC_COMPLETION:
0066         rc = opal_async_wait_response(token, &msg);
0067         if (rc) {
0068             pr_debug("Failed to wait for async response [rc=%d]\n",
0069                 rc);
0070             break;
0071         }
0072         rc = opal_get_async_rc(msg);
0073         if (rc != OPAL_SUCCESS) {
0074             pr_debug("OPAL async call returned failed [rc=%d]\n",
0075                 rc);
0076             break;
0077         }
0078         break;
0079     case OPAL_SUCCESS:
0080         break;
0081     default:
0082         pr_debug("OPAL write op-panel call failed [rc=%d]\n", rc);
0083     }
0084 
0085     opal_async_release_token(token);
0086     return rc;
0087 }
0088 
0089 static ssize_t oppanel_write(struct file *filp, const char __user *userbuf,
0090                  size_t len, loff_t *f_pos)
0091 {
0092     loff_t f_pos_prev = *f_pos;
0093     ssize_t ret;
0094     int rc;
0095 
0096     if (!*f_pos)
0097         memset(oppanel_data, ' ', oppanel_size);
0098     else if (*f_pos >= oppanel_size)
0099         return -EFBIG;
0100 
0101     ret = simple_write_to_buffer(oppanel_data, oppanel_size, f_pos, userbuf,
0102             len);
0103     if (ret > 0) {
0104         rc = __op_panel_update_display();
0105         if (rc != OPAL_SUCCESS) {
0106             pr_err_ratelimited("OPAL call failed to write to op panel display [rc=%d]\n",
0107                 rc);
0108             *f_pos = f_pos_prev;
0109             return -EIO;
0110         }
0111     }
0112     return ret;
0113 }
0114 
0115 static int oppanel_open(struct inode *inode, struct file *filp)
0116 {
0117     if (!mutex_trylock(&oppanel_mutex)) {
0118         pr_debug("Device Busy\n");
0119         return -EBUSY;
0120     }
0121     return 0;
0122 }
0123 
0124 static int oppanel_release(struct inode *inode, struct file *filp)
0125 {
0126     mutex_unlock(&oppanel_mutex);
0127     return 0;
0128 }
0129 
0130 static const struct file_operations oppanel_fops = {
0131     .owner      = THIS_MODULE,
0132     .llseek     = oppanel_llseek,
0133     .read       = oppanel_read,
0134     .write      = oppanel_write,
0135     .open       = oppanel_open,
0136     .release    = oppanel_release
0137 };
0138 
0139 static struct miscdevice oppanel_dev = {
0140     .minor      = MISC_DYNAMIC_MINOR,
0141     .name       = "op_panel",
0142     .fops       = &oppanel_fops
0143 };
0144 
0145 static int oppanel_probe(struct platform_device *pdev)
0146 {
0147     struct device_node *np = pdev->dev.of_node;
0148     u32 line_len;
0149     int rc, i;
0150 
0151     rc = of_property_read_u32(np, "#length", &line_len);
0152     if (rc) {
0153         pr_err_ratelimited("Operator panel length property not found\n");
0154         return rc;
0155     }
0156     rc = of_property_read_u32(np, "#lines", &num_lines);
0157     if (rc) {
0158         pr_err_ratelimited("Operator panel lines property not found\n");
0159         return rc;
0160     }
0161     oppanel_size = line_len * num_lines;
0162 
0163     pr_devel("Operator panel of size %u found with %u lines of length %u\n",
0164             oppanel_size, num_lines, line_len);
0165 
0166     oppanel_data = kcalloc(oppanel_size, sizeof(*oppanel_data), GFP_KERNEL);
0167     if (!oppanel_data)
0168         return -ENOMEM;
0169 
0170     oppanel_lines = kcalloc(num_lines, sizeof(oppanel_line_t), GFP_KERNEL);
0171     if (!oppanel_lines) {
0172         rc = -ENOMEM;
0173         goto free_oppanel_data;
0174     }
0175 
0176     memset(oppanel_data, ' ', oppanel_size);
0177     for (i = 0; i < num_lines; i++) {
0178         oppanel_lines[i].line_len = cpu_to_be64(line_len);
0179         oppanel_lines[i].line = cpu_to_be64(__pa(&oppanel_data[i *
0180                         line_len]));
0181     }
0182 
0183     rc = misc_register(&oppanel_dev);
0184     if (rc) {
0185         pr_err_ratelimited("Failed to register as misc device\n");
0186         goto free_oppanel;
0187     }
0188 
0189     return 0;
0190 
0191 free_oppanel:
0192     kfree(oppanel_lines);
0193 free_oppanel_data:
0194     kfree(oppanel_data);
0195     return rc;
0196 }
0197 
0198 static int oppanel_remove(struct platform_device *pdev)
0199 {
0200     misc_deregister(&oppanel_dev);
0201     kfree(oppanel_lines);
0202     kfree(oppanel_data);
0203     return 0;
0204 }
0205 
0206 static const struct of_device_id oppanel_match[] = {
0207     { .compatible = "ibm,opal-oppanel" },
0208     { },
0209 };
0210 
0211 static struct platform_driver oppanel_driver = {
0212     .driver = {
0213         .name       = "powernv-op-panel",
0214         .of_match_table = oppanel_match,
0215     },
0216     .probe  = oppanel_probe,
0217     .remove = oppanel_remove,
0218 };
0219 
0220 module_platform_driver(oppanel_driver);
0221 
0222 MODULE_DEVICE_TABLE(of, oppanel_match);
0223 MODULE_LICENSE("GPL v2");
0224 MODULE_DESCRIPTION("PowerNV Operator Panel LCD Display Driver");
0225 MODULE_AUTHOR("Suraj Jitindar Singh <sjitindarsingh@gmail.com>");