xref: /openbmc/linux/drivers/char/powernv-op-panel.c (revision 762f99f4f3cb41a775b5157dd761217beba65873)
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