1 // SPDX-License-Identifier: GPL-2.0-only
3 * OPAL Operator Panel Display Driver
5 * Copyright 2016, Suraj Jitindar Singh, IBM Corporation.
8 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
10 #include <linux/init.h>
11 #include <linux/module.h>
12 #include <linux/kernel.h>
14 #include <linux/device.h>
15 #include <linux/errno.h>
16 #include <linux/mutex.h>
18 #include <linux/slab.h>
19 #include <linux/platform_device.h>
20 #include <linux/miscdevice.h>
25 * This driver creates a character device (/dev/op_panel) which exposes the
26 * operator panel (character LCD display) on IBM Power Systems machines
28 * A character buffer written to the device will be displayed on the
32 static DEFINE_MUTEX(oppanel_mutex
);
34 static u32 num_lines
, oppanel_size
;
35 static oppanel_line_t
*oppanel_lines
;
36 static char *oppanel_data
;
38 static loff_t
oppanel_llseek(struct file
*filp
, loff_t offset
, int whence
)
40 return fixed_size_llseek(filp
, offset
, whence
, oppanel_size
);
43 static ssize_t
oppanel_read(struct file
*filp
, char __user
*userbuf
, size_t len
,
46 return simple_read_from_buffer(userbuf
, len
, f_pos
, oppanel_data
,
50 static int __op_panel_update_display(void)
55 token
= opal_async_get_token_interruptible();
57 if (token
!= -ERESTARTSYS
)
58 pr_debug("Couldn't get OPAL async token [token=%d]\n",
63 rc
= opal_write_oppanel_async(token
, oppanel_lines
, num_lines
);
65 case OPAL_ASYNC_COMPLETION
:
66 rc
= opal_async_wait_response(token
, &msg
);
68 pr_debug("Failed to wait for async response [rc=%d]\n",
72 rc
= opal_get_async_rc(msg
);
73 if (rc
!= OPAL_SUCCESS
) {
74 pr_debug("OPAL async call returned failed [rc=%d]\n",
81 pr_debug("OPAL write op-panel call failed [rc=%d]\n", rc
);
84 opal_async_release_token(token
);
88 static ssize_t
oppanel_write(struct file
*filp
, const char __user
*userbuf
,
89 size_t len
, loff_t
*f_pos
)
91 loff_t f_pos_prev
= *f_pos
;
96 memset(oppanel_data
, ' ', oppanel_size
);
97 else if (*f_pos
>= oppanel_size
)
100 ret
= simple_write_to_buffer(oppanel_data
, oppanel_size
, f_pos
, userbuf
,
103 rc
= __op_panel_update_display();
104 if (rc
!= OPAL_SUCCESS
) {
105 pr_err_ratelimited("OPAL call failed to write to op panel display [rc=%d]\n",
114 static int oppanel_open(struct inode
*inode
, struct file
*filp
)
116 if (!mutex_trylock(&oppanel_mutex
)) {
117 pr_debug("Device Busy\n");
123 static int oppanel_release(struct inode
*inode
, struct file
*filp
)
125 mutex_unlock(&oppanel_mutex
);
129 static const struct file_operations oppanel_fops
= {
130 .owner
= THIS_MODULE
,
131 .llseek
= oppanel_llseek
,
132 .read
= oppanel_read
,
133 .write
= oppanel_write
,
134 .open
= oppanel_open
,
135 .release
= oppanel_release
138 static struct miscdevice oppanel_dev
= {
139 .minor
= MISC_DYNAMIC_MINOR
,
141 .fops
= &oppanel_fops
144 static int oppanel_probe(struct platform_device
*pdev
)
146 struct device_node
*np
= pdev
->dev
.of_node
;
150 rc
= of_property_read_u32(np
, "#length", &line_len
);
152 pr_err_ratelimited("Operator panel length property not found\n");
155 rc
= of_property_read_u32(np
, "#lines", &num_lines
);
157 pr_err_ratelimited("Operator panel lines property not found\n");
160 oppanel_size
= line_len
* num_lines
;
162 pr_devel("Operator panel of size %u found with %u lines of length %u\n",
163 oppanel_size
, num_lines
, line_len
);
165 oppanel_data
= kcalloc(oppanel_size
, sizeof(*oppanel_data
), GFP_KERNEL
);
169 oppanel_lines
= kcalloc(num_lines
, sizeof(oppanel_line_t
), GFP_KERNEL
);
170 if (!oppanel_lines
) {
172 goto free_oppanel_data
;
175 memset(oppanel_data
, ' ', oppanel_size
);
176 for (i
= 0; i
< num_lines
; i
++) {
177 oppanel_lines
[i
].line_len
= cpu_to_be64(line_len
);
178 oppanel_lines
[i
].line
= cpu_to_be64(__pa(&oppanel_data
[i
*
182 rc
= misc_register(&oppanel_dev
);
184 pr_err_ratelimited("Failed to register as misc device\n");
191 kfree(oppanel_lines
);
197 static int oppanel_remove(struct platform_device
*pdev
)
199 misc_deregister(&oppanel_dev
);
200 kfree(oppanel_lines
);
205 static const struct of_device_id oppanel_match
[] = {
206 { .compatible
= "ibm,opal-oppanel" },
210 static struct platform_driver oppanel_driver
= {
212 .name
= "powernv-op-panel",
213 .of_match_table
= oppanel_match
,
215 .probe
= oppanel_probe
,
216 .remove
= oppanel_remove
,
219 module_platform_driver(oppanel_driver
);
221 MODULE_DEVICE_TABLE(of
, oppanel_match
);
222 MODULE_LICENSE("GPL v2");
223 MODULE_DESCRIPTION("PowerNV Operator Panel LCD Display Driver");
224 MODULE_AUTHOR("Suraj Jitindar Singh <sjitindarsingh@gmail.com>");