1 /* 2 * OPAL Operator Panel Display Driver 3 * 4 * Copyright 2016, Suraj Jitindar Singh, IBM Corporation. 5 */ 6 7 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 8 9 #include <linux/init.h> 10 #include <linux/module.h> 11 #include <linux/kernel.h> 12 #include <linux/fs.h> 13 #include <linux/device.h> 14 #include <linux/errno.h> 15 #include <linux/mutex.h> 16 #include <linux/of.h> 17 #include <linux/slab.h> 18 #include <linux/platform_device.h> 19 #include <linux/miscdevice.h> 20 21 #include <asm/opal.h> 22 23 /* 24 * This driver creates a character device (/dev/op_panel) which exposes the 25 * operator panel (character LCD display) on IBM Power Systems machines 26 * with FSPs. 27 * A character buffer written to the device will be displayed on the 28 * operator panel. 29 */ 30 31 static DEFINE_MUTEX(oppanel_mutex); 32 33 static u32 num_lines, oppanel_size; 34 static oppanel_line_t *oppanel_lines; 35 static char *oppanel_data; 36 37 static loff_t oppanel_llseek(struct file *filp, loff_t offset, int whence) 38 { 39 return fixed_size_llseek(filp, offset, whence, oppanel_size); 40 } 41 42 static ssize_t oppanel_read(struct file *filp, char __user *userbuf, size_t len, 43 loff_t *f_pos) 44 { 45 return simple_read_from_buffer(userbuf, len, f_pos, oppanel_data, 46 oppanel_size); 47 } 48 49 static int __op_panel_update_display(void) 50 { 51 struct opal_msg msg; 52 int rc, token; 53 54 token = opal_async_get_token_interruptible(); 55 if (token < 0) { 56 if (token != -ERESTARTSYS) 57 pr_debug("Couldn't get OPAL async token [token=%d]\n", 58 token); 59 return token; 60 } 61 62 rc = opal_write_oppanel_async(token, oppanel_lines, num_lines); 63 switch (rc) { 64 case OPAL_ASYNC_COMPLETION: 65 rc = opal_async_wait_response(token, &msg); 66 if (rc) { 67 pr_debug("Failed to wait for async response [rc=%d]\n", 68 rc); 69 break; 70 } 71 rc = opal_get_async_rc(msg); 72 if (rc != OPAL_SUCCESS) { 73 pr_debug("OPAL async call returned failed [rc=%d]\n", 74 rc); 75 break; 76 } 77 case OPAL_SUCCESS: 78 break; 79 default: 80 pr_debug("OPAL write op-panel call failed [rc=%d]\n", rc); 81 } 82 83 opal_async_release_token(token); 84 return rc; 85 } 86 87 static ssize_t oppanel_write(struct file *filp, const char __user *userbuf, 88 size_t len, loff_t *f_pos) 89 { 90 loff_t f_pos_prev = *f_pos; 91 ssize_t ret; 92 int rc; 93 94 if (!*f_pos) 95 memset(oppanel_data, ' ', oppanel_size); 96 else if (*f_pos >= oppanel_size) 97 return -EFBIG; 98 99 ret = simple_write_to_buffer(oppanel_data, oppanel_size, f_pos, userbuf, 100 len); 101 if (ret > 0) { 102 rc = __op_panel_update_display(); 103 if (rc != OPAL_SUCCESS) { 104 pr_err_ratelimited("OPAL call failed to write to op panel display [rc=%d]\n", 105 rc); 106 *f_pos = f_pos_prev; 107 return -EIO; 108 } 109 } 110 return ret; 111 } 112 113 static int oppanel_open(struct inode *inode, struct file *filp) 114 { 115 if (!mutex_trylock(&oppanel_mutex)) { 116 pr_debug("Device Busy\n"); 117 return -EBUSY; 118 } 119 return 0; 120 } 121 122 static int oppanel_release(struct inode *inode, struct file *filp) 123 { 124 mutex_unlock(&oppanel_mutex); 125 return 0; 126 } 127 128 static const struct file_operations oppanel_fops = { 129 .owner = THIS_MODULE, 130 .llseek = oppanel_llseek, 131 .read = oppanel_read, 132 .write = oppanel_write, 133 .open = oppanel_open, 134 .release = oppanel_release 135 }; 136 137 static struct miscdevice oppanel_dev = { 138 .minor = MISC_DYNAMIC_MINOR, 139 .name = "op_panel", 140 .fops = &oppanel_fops 141 }; 142 143 static int oppanel_probe(struct platform_device *pdev) 144 { 145 struct device_node *np = pdev->dev.of_node; 146 u32 line_len; 147 int rc, i; 148 149 rc = of_property_read_u32(np, "#length", &line_len); 150 if (rc) { 151 pr_err_ratelimited("Operator panel length property not found\n"); 152 return rc; 153 } 154 rc = of_property_read_u32(np, "#lines", &num_lines); 155 if (rc) { 156 pr_err_ratelimited("Operator panel lines property not found\n"); 157 return rc; 158 } 159 oppanel_size = line_len * num_lines; 160 161 pr_devel("Operator panel of size %u found with %u lines of length %u\n", 162 oppanel_size, num_lines, line_len); 163 164 oppanel_data = kcalloc(oppanel_size, sizeof(*oppanel_data), GFP_KERNEL); 165 if (!oppanel_data) 166 return -ENOMEM; 167 168 oppanel_lines = kcalloc(num_lines, sizeof(oppanel_line_t), GFP_KERNEL); 169 if (!oppanel_lines) { 170 rc = -ENOMEM; 171 goto free_oppanel_data; 172 } 173 174 memset(oppanel_data, ' ', oppanel_size); 175 for (i = 0; i < num_lines; i++) { 176 oppanel_lines[i].line_len = cpu_to_be64(line_len); 177 oppanel_lines[i].line = cpu_to_be64(__pa(&oppanel_data[i * 178 line_len])); 179 } 180 181 rc = misc_register(&oppanel_dev); 182 if (rc) { 183 pr_err_ratelimited("Failed to register as misc device\n"); 184 goto free_oppanel; 185 } 186 187 return 0; 188 189 free_oppanel: 190 kfree(oppanel_lines); 191 free_oppanel_data: 192 kfree(oppanel_data); 193 return rc; 194 } 195 196 static int oppanel_remove(struct platform_device *pdev) 197 { 198 misc_deregister(&oppanel_dev); 199 kfree(oppanel_lines); 200 kfree(oppanel_data); 201 return 0; 202 } 203 204 static const struct of_device_id oppanel_match[] = { 205 { .compatible = "ibm,opal-oppanel" }, 206 { }, 207 }; 208 209 static struct platform_driver oppanel_driver = { 210 .driver = { 211 .name = "powernv-op-panel", 212 .of_match_table = oppanel_match, 213 }, 214 .probe = oppanel_probe, 215 .remove = oppanel_remove, 216 }; 217 218 module_platform_driver(oppanel_driver); 219 220 MODULE_DEVICE_TABLE(of, oppanel_match); 221 MODULE_LICENSE("GPL v2"); 222 MODULE_DESCRIPTION("PowerNV Operator Panel LCD Display Driver"); 223 MODULE_AUTHOR("Suraj Jitindar Singh <sjitindarsingh@gmail.com>"); 224