109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
243a1dd9bSSuraj Jitindar Singh /*
343a1dd9bSSuraj Jitindar Singh * OPAL Operator Panel Display Driver
443a1dd9bSSuraj Jitindar Singh *
543a1dd9bSSuraj Jitindar Singh * Copyright 2016, Suraj Jitindar Singh, IBM Corporation.
643a1dd9bSSuraj Jitindar Singh */
743a1dd9bSSuraj Jitindar Singh
843a1dd9bSSuraj Jitindar Singh #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
943a1dd9bSSuraj Jitindar Singh
1043a1dd9bSSuraj Jitindar Singh #include <linux/init.h>
1143a1dd9bSSuraj Jitindar Singh #include <linux/module.h>
1243a1dd9bSSuraj Jitindar Singh #include <linux/kernel.h>
1343a1dd9bSSuraj Jitindar Singh #include <linux/fs.h>
1443a1dd9bSSuraj Jitindar Singh #include <linux/device.h>
1543a1dd9bSSuraj Jitindar Singh #include <linux/errno.h>
1643a1dd9bSSuraj Jitindar Singh #include <linux/mutex.h>
1743a1dd9bSSuraj Jitindar Singh #include <linux/of.h>
1843a1dd9bSSuraj Jitindar Singh #include <linux/slab.h>
1943a1dd9bSSuraj Jitindar Singh #include <linux/platform_device.h>
2043a1dd9bSSuraj Jitindar Singh #include <linux/miscdevice.h>
2143a1dd9bSSuraj Jitindar Singh
2243a1dd9bSSuraj Jitindar Singh #include <asm/opal.h>
2343a1dd9bSSuraj Jitindar Singh
2443a1dd9bSSuraj Jitindar Singh /*
2543a1dd9bSSuraj Jitindar Singh * This driver creates a character device (/dev/op_panel) which exposes the
2643a1dd9bSSuraj Jitindar Singh * operator panel (character LCD display) on IBM Power Systems machines
2743a1dd9bSSuraj Jitindar Singh * with FSPs.
2843a1dd9bSSuraj Jitindar Singh * A character buffer written to the device will be displayed on the
2943a1dd9bSSuraj Jitindar Singh * operator panel.
3043a1dd9bSSuraj Jitindar Singh */
3143a1dd9bSSuraj Jitindar Singh
3243a1dd9bSSuraj Jitindar Singh static DEFINE_MUTEX(oppanel_mutex);
3343a1dd9bSSuraj Jitindar Singh
3443a1dd9bSSuraj Jitindar Singh static u32 num_lines, oppanel_size;
3543a1dd9bSSuraj Jitindar Singh static oppanel_line_t *oppanel_lines;
3643a1dd9bSSuraj Jitindar Singh static char *oppanel_data;
3743a1dd9bSSuraj Jitindar Singh
oppanel_llseek(struct file * filp,loff_t offset,int whence)3843a1dd9bSSuraj Jitindar Singh static loff_t oppanel_llseek(struct file *filp, loff_t offset, int whence)
3943a1dd9bSSuraj Jitindar Singh {
4043a1dd9bSSuraj Jitindar Singh return fixed_size_llseek(filp, offset, whence, oppanel_size);
4143a1dd9bSSuraj Jitindar Singh }
4243a1dd9bSSuraj Jitindar Singh
oppanel_read(struct file * filp,char __user * userbuf,size_t len,loff_t * f_pos)4343a1dd9bSSuraj Jitindar Singh static ssize_t oppanel_read(struct file *filp, char __user *userbuf, size_t len,
4443a1dd9bSSuraj Jitindar Singh loff_t *f_pos)
4543a1dd9bSSuraj Jitindar Singh {
4643a1dd9bSSuraj Jitindar Singh return simple_read_from_buffer(userbuf, len, f_pos, oppanel_data,
4743a1dd9bSSuraj Jitindar Singh oppanel_size);
4843a1dd9bSSuraj Jitindar Singh }
4943a1dd9bSSuraj Jitindar Singh
__op_panel_update_display(void)5043a1dd9bSSuraj Jitindar Singh static int __op_panel_update_display(void)
5143a1dd9bSSuraj Jitindar Singh {
5243a1dd9bSSuraj Jitindar Singh struct opal_msg msg;
5343a1dd9bSSuraj Jitindar Singh int rc, token;
5443a1dd9bSSuraj Jitindar Singh
5543a1dd9bSSuraj Jitindar Singh token = opal_async_get_token_interruptible();
5643a1dd9bSSuraj Jitindar Singh if (token < 0) {
5743a1dd9bSSuraj Jitindar Singh if (token != -ERESTARTSYS)
5843a1dd9bSSuraj Jitindar Singh pr_debug("Couldn't get OPAL async token [token=%d]\n",
5943a1dd9bSSuraj Jitindar Singh token);
6043a1dd9bSSuraj Jitindar Singh return token;
6143a1dd9bSSuraj Jitindar Singh }
6243a1dd9bSSuraj Jitindar Singh
6343a1dd9bSSuraj Jitindar Singh rc = opal_write_oppanel_async(token, oppanel_lines, num_lines);
6443a1dd9bSSuraj Jitindar Singh switch (rc) {
6543a1dd9bSSuraj Jitindar Singh case OPAL_ASYNC_COMPLETION:
6643a1dd9bSSuraj Jitindar Singh rc = opal_async_wait_response(token, &msg);
6743a1dd9bSSuraj Jitindar Singh if (rc) {
6843a1dd9bSSuraj Jitindar Singh pr_debug("Failed to wait for async response [rc=%d]\n",
6943a1dd9bSSuraj Jitindar Singh rc);
7043a1dd9bSSuraj Jitindar Singh break;
7143a1dd9bSSuraj Jitindar Singh }
7243a1dd9bSSuraj Jitindar Singh rc = opal_get_async_rc(msg);
7343a1dd9bSSuraj Jitindar Singh if (rc != OPAL_SUCCESS) {
7443a1dd9bSSuraj Jitindar Singh pr_debug("OPAL async call returned failed [rc=%d]\n",
7543a1dd9bSSuraj Jitindar Singh rc);
7643a1dd9bSSuraj Jitindar Singh break;
7743a1dd9bSSuraj Jitindar Singh }
78*479857a9SGustavo A. R. Silva break;
7943a1dd9bSSuraj Jitindar Singh case OPAL_SUCCESS:
8043a1dd9bSSuraj Jitindar Singh break;
8143a1dd9bSSuraj Jitindar Singh default:
8243a1dd9bSSuraj Jitindar Singh pr_debug("OPAL write op-panel call failed [rc=%d]\n", rc);
8343a1dd9bSSuraj Jitindar Singh }
8443a1dd9bSSuraj Jitindar Singh
8543a1dd9bSSuraj Jitindar Singh opal_async_release_token(token);
8643a1dd9bSSuraj Jitindar Singh return rc;
8743a1dd9bSSuraj Jitindar Singh }
8843a1dd9bSSuraj Jitindar Singh
oppanel_write(struct file * filp,const char __user * userbuf,size_t len,loff_t * f_pos)8943a1dd9bSSuraj Jitindar Singh static ssize_t oppanel_write(struct file *filp, const char __user *userbuf,
9043a1dd9bSSuraj Jitindar Singh size_t len, loff_t *f_pos)
9143a1dd9bSSuraj Jitindar Singh {
9243a1dd9bSSuraj Jitindar Singh loff_t f_pos_prev = *f_pos;
9343a1dd9bSSuraj Jitindar Singh ssize_t ret;
9443a1dd9bSSuraj Jitindar Singh int rc;
9543a1dd9bSSuraj Jitindar Singh
9643a1dd9bSSuraj Jitindar Singh if (!*f_pos)
9743a1dd9bSSuraj Jitindar Singh memset(oppanel_data, ' ', oppanel_size);
9843a1dd9bSSuraj Jitindar Singh else if (*f_pos >= oppanel_size)
9943a1dd9bSSuraj Jitindar Singh return -EFBIG;
10043a1dd9bSSuraj Jitindar Singh
10143a1dd9bSSuraj Jitindar Singh ret = simple_write_to_buffer(oppanel_data, oppanel_size, f_pos, userbuf,
10243a1dd9bSSuraj Jitindar Singh len);
10343a1dd9bSSuraj Jitindar Singh if (ret > 0) {
10443a1dd9bSSuraj Jitindar Singh rc = __op_panel_update_display();
10543a1dd9bSSuraj Jitindar Singh if (rc != OPAL_SUCCESS) {
10643a1dd9bSSuraj Jitindar Singh pr_err_ratelimited("OPAL call failed to write to op panel display [rc=%d]\n",
10743a1dd9bSSuraj Jitindar Singh rc);
10843a1dd9bSSuraj Jitindar Singh *f_pos = f_pos_prev;
10943a1dd9bSSuraj Jitindar Singh return -EIO;
11043a1dd9bSSuraj Jitindar Singh }
11143a1dd9bSSuraj Jitindar Singh }
11243a1dd9bSSuraj Jitindar Singh return ret;
11343a1dd9bSSuraj Jitindar Singh }
11443a1dd9bSSuraj Jitindar Singh
oppanel_open(struct inode * inode,struct file * filp)11543a1dd9bSSuraj Jitindar Singh static int oppanel_open(struct inode *inode, struct file *filp)
11643a1dd9bSSuraj Jitindar Singh {
11743a1dd9bSSuraj Jitindar Singh if (!mutex_trylock(&oppanel_mutex)) {
11843a1dd9bSSuraj Jitindar Singh pr_debug("Device Busy\n");
11943a1dd9bSSuraj Jitindar Singh return -EBUSY;
12043a1dd9bSSuraj Jitindar Singh }
12143a1dd9bSSuraj Jitindar Singh return 0;
12243a1dd9bSSuraj Jitindar Singh }
12343a1dd9bSSuraj Jitindar Singh
oppanel_release(struct inode * inode,struct file * filp)12443a1dd9bSSuraj Jitindar Singh static int oppanel_release(struct inode *inode, struct file *filp)
12543a1dd9bSSuraj Jitindar Singh {
12643a1dd9bSSuraj Jitindar Singh mutex_unlock(&oppanel_mutex);
12743a1dd9bSSuraj Jitindar Singh return 0;
12843a1dd9bSSuraj Jitindar Singh }
12943a1dd9bSSuraj Jitindar Singh
13043a1dd9bSSuraj Jitindar Singh static const struct file_operations oppanel_fops = {
13143a1dd9bSSuraj Jitindar Singh .owner = THIS_MODULE,
13243a1dd9bSSuraj Jitindar Singh .llseek = oppanel_llseek,
13343a1dd9bSSuraj Jitindar Singh .read = oppanel_read,
13443a1dd9bSSuraj Jitindar Singh .write = oppanel_write,
13543a1dd9bSSuraj Jitindar Singh .open = oppanel_open,
13643a1dd9bSSuraj Jitindar Singh .release = oppanel_release
13743a1dd9bSSuraj Jitindar Singh };
13843a1dd9bSSuraj Jitindar Singh
13943a1dd9bSSuraj Jitindar Singh static struct miscdevice oppanel_dev = {
14043a1dd9bSSuraj Jitindar Singh .minor = MISC_DYNAMIC_MINOR,
14143a1dd9bSSuraj Jitindar Singh .name = "op_panel",
14243a1dd9bSSuraj Jitindar Singh .fops = &oppanel_fops
14343a1dd9bSSuraj Jitindar Singh };
14443a1dd9bSSuraj Jitindar Singh
oppanel_probe(struct platform_device * pdev)14543a1dd9bSSuraj Jitindar Singh static int oppanel_probe(struct platform_device *pdev)
14643a1dd9bSSuraj Jitindar Singh {
14743a1dd9bSSuraj Jitindar Singh struct device_node *np = pdev->dev.of_node;
14843a1dd9bSSuraj Jitindar Singh u32 line_len;
14943a1dd9bSSuraj Jitindar Singh int rc, i;
15043a1dd9bSSuraj Jitindar Singh
15143a1dd9bSSuraj Jitindar Singh rc = of_property_read_u32(np, "#length", &line_len);
15243a1dd9bSSuraj Jitindar Singh if (rc) {
15343a1dd9bSSuraj Jitindar Singh pr_err_ratelimited("Operator panel length property not found\n");
15443a1dd9bSSuraj Jitindar Singh return rc;
15543a1dd9bSSuraj Jitindar Singh }
15643a1dd9bSSuraj Jitindar Singh rc = of_property_read_u32(np, "#lines", &num_lines);
15743a1dd9bSSuraj Jitindar Singh if (rc) {
15843a1dd9bSSuraj Jitindar Singh pr_err_ratelimited("Operator panel lines property not found\n");
15943a1dd9bSSuraj Jitindar Singh return rc;
16043a1dd9bSSuraj Jitindar Singh }
16143a1dd9bSSuraj Jitindar Singh oppanel_size = line_len * num_lines;
16243a1dd9bSSuraj Jitindar Singh
16343a1dd9bSSuraj Jitindar Singh pr_devel("Operator panel of size %u found with %u lines of length %u\n",
16443a1dd9bSSuraj Jitindar Singh oppanel_size, num_lines, line_len);
16543a1dd9bSSuraj Jitindar Singh
16643a1dd9bSSuraj Jitindar Singh oppanel_data = kcalloc(oppanel_size, sizeof(*oppanel_data), GFP_KERNEL);
16743a1dd9bSSuraj Jitindar Singh if (!oppanel_data)
16843a1dd9bSSuraj Jitindar Singh return -ENOMEM;
16943a1dd9bSSuraj Jitindar Singh
17043a1dd9bSSuraj Jitindar Singh oppanel_lines = kcalloc(num_lines, sizeof(oppanel_line_t), GFP_KERNEL);
17143a1dd9bSSuraj Jitindar Singh if (!oppanel_lines) {
17243a1dd9bSSuraj Jitindar Singh rc = -ENOMEM;
17343a1dd9bSSuraj Jitindar Singh goto free_oppanel_data;
17443a1dd9bSSuraj Jitindar Singh }
17543a1dd9bSSuraj Jitindar Singh
17643a1dd9bSSuraj Jitindar Singh memset(oppanel_data, ' ', oppanel_size);
17743a1dd9bSSuraj Jitindar Singh for (i = 0; i < num_lines; i++) {
17843a1dd9bSSuraj Jitindar Singh oppanel_lines[i].line_len = cpu_to_be64(line_len);
17943a1dd9bSSuraj Jitindar Singh oppanel_lines[i].line = cpu_to_be64(__pa(&oppanel_data[i *
18043a1dd9bSSuraj Jitindar Singh line_len]));
18143a1dd9bSSuraj Jitindar Singh }
18243a1dd9bSSuraj Jitindar Singh
18343a1dd9bSSuraj Jitindar Singh rc = misc_register(&oppanel_dev);
18443a1dd9bSSuraj Jitindar Singh if (rc) {
18543a1dd9bSSuraj Jitindar Singh pr_err_ratelimited("Failed to register as misc device\n");
18643a1dd9bSSuraj Jitindar Singh goto free_oppanel;
18743a1dd9bSSuraj Jitindar Singh }
18843a1dd9bSSuraj Jitindar Singh
18943a1dd9bSSuraj Jitindar Singh return 0;
19043a1dd9bSSuraj Jitindar Singh
19143a1dd9bSSuraj Jitindar Singh free_oppanel:
19243a1dd9bSSuraj Jitindar Singh kfree(oppanel_lines);
19343a1dd9bSSuraj Jitindar Singh free_oppanel_data:
19443a1dd9bSSuraj Jitindar Singh kfree(oppanel_data);
19543a1dd9bSSuraj Jitindar Singh return rc;
19643a1dd9bSSuraj Jitindar Singh }
19743a1dd9bSSuraj Jitindar Singh
oppanel_remove(struct platform_device * pdev)19843a1dd9bSSuraj Jitindar Singh static int oppanel_remove(struct platform_device *pdev)
19943a1dd9bSSuraj Jitindar Singh {
20043a1dd9bSSuraj Jitindar Singh misc_deregister(&oppanel_dev);
20143a1dd9bSSuraj Jitindar Singh kfree(oppanel_lines);
20243a1dd9bSSuraj Jitindar Singh kfree(oppanel_data);
20343a1dd9bSSuraj Jitindar Singh return 0;
20443a1dd9bSSuraj Jitindar Singh }
20543a1dd9bSSuraj Jitindar Singh
20643a1dd9bSSuraj Jitindar Singh static const struct of_device_id oppanel_match[] = {
20743a1dd9bSSuraj Jitindar Singh { .compatible = "ibm,opal-oppanel" },
20843a1dd9bSSuraj Jitindar Singh { },
20943a1dd9bSSuraj Jitindar Singh };
21043a1dd9bSSuraj Jitindar Singh
21143a1dd9bSSuraj Jitindar Singh static struct platform_driver oppanel_driver = {
21243a1dd9bSSuraj Jitindar Singh .driver = {
21343a1dd9bSSuraj Jitindar Singh .name = "powernv-op-panel",
21443a1dd9bSSuraj Jitindar Singh .of_match_table = oppanel_match,
21543a1dd9bSSuraj Jitindar Singh },
21643a1dd9bSSuraj Jitindar Singh .probe = oppanel_probe,
21743a1dd9bSSuraj Jitindar Singh .remove = oppanel_remove,
21843a1dd9bSSuraj Jitindar Singh };
21943a1dd9bSSuraj Jitindar Singh
22043a1dd9bSSuraj Jitindar Singh module_platform_driver(oppanel_driver);
22143a1dd9bSSuraj Jitindar Singh
22243a1dd9bSSuraj Jitindar Singh MODULE_DEVICE_TABLE(of, oppanel_match);
22343a1dd9bSSuraj Jitindar Singh MODULE_LICENSE("GPL v2");
22443a1dd9bSSuraj Jitindar Singh MODULE_DESCRIPTION("PowerNV Operator Panel LCD Display Driver");
22543a1dd9bSSuraj Jitindar Singh MODULE_AUTHOR("Suraj Jitindar Singh <sjitindarsingh@gmail.com>");
226