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",
82 pr_debug("OPAL write op-panel call failed [rc=%d]\n", rc
);
85 opal_async_release_token(token
);
89 static ssize_t
oppanel_write(struct file
*filp
, const char __user
*userbuf
,
90 size_t len
, loff_t
*f_pos
)
92 loff_t f_pos_prev
= *f_pos
;
97 memset(oppanel_data
, ' ', oppanel_size
);
98 else if (*f_pos
>= oppanel_size
)
101 ret
= simple_write_to_buffer(oppanel_data
, oppanel_size
, f_pos
, userbuf
,
104 rc
= __op_panel_update_display();
105 if (rc
!= OPAL_SUCCESS
) {
106 pr_err_ratelimited("OPAL call failed to write to op panel display [rc=%d]\n",
115 static int oppanel_open(struct inode
*inode
, struct file
*filp
)
117 if (!mutex_trylock(&oppanel_mutex
)) {
118 pr_debug("Device Busy\n");
124 static int oppanel_release(struct inode
*inode
, struct file
*filp
)
126 mutex_unlock(&oppanel_mutex
);
130 static const struct file_operations oppanel_fops
= {
131 .owner
= THIS_MODULE
,
132 .llseek
= oppanel_llseek
,
133 .read
= oppanel_read
,
134 .write
= oppanel_write
,
135 .open
= oppanel_open
,
136 .release
= oppanel_release
139 static struct miscdevice oppanel_dev
= {
140 .minor
= MISC_DYNAMIC_MINOR
,
142 .fops
= &oppanel_fops
145 static int oppanel_probe(struct platform_device
*pdev
)
147 struct device_node
*np
= pdev
->dev
.of_node
;
151 rc
= of_property_read_u32(np
, "#length", &line_len
);
153 pr_err_ratelimited("Operator panel length property not found\n");
156 rc
= of_property_read_u32(np
, "#lines", &num_lines
);
158 pr_err_ratelimited("Operator panel lines property not found\n");
161 oppanel_size
= line_len
* num_lines
;
163 pr_devel("Operator panel of size %u found with %u lines of length %u\n",
164 oppanel_size
, num_lines
, line_len
);
166 oppanel_data
= kcalloc(oppanel_size
, sizeof(*oppanel_data
), GFP_KERNEL
);
170 oppanel_lines
= kcalloc(num_lines
, sizeof(oppanel_line_t
), GFP_KERNEL
);
171 if (!oppanel_lines
) {
173 goto free_oppanel_data
;
176 memset(oppanel_data
, ' ', oppanel_size
);
177 for (i
= 0; i
< num_lines
; i
++) {
178 oppanel_lines
[i
].line_len
= cpu_to_be64(line_len
);
179 oppanel_lines
[i
].line
= cpu_to_be64(__pa(&oppanel_data
[i
*
183 rc
= misc_register(&oppanel_dev
);
185 pr_err_ratelimited("Failed to register as misc device\n");
192 kfree(oppanel_lines
);
198 static void oppanel_remove(struct platform_device
*pdev
)
200 misc_deregister(&oppanel_dev
);
201 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_new
= 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>");