IBM PowerNV machines with FSPs have an operator panel with a LCD display.
Currently this oppanel display can be accessed through the
powernv_op_panel kernel module. We would like to be able to access this
display easily from other places in the Kernel.

Add a new interface through which the operator panel display can be
accessed from within the kernel. The function opal_oppanel_write acts like
a print function and will respect newline and carriage return characters
while trying to print the input to the display. The function
opal_oppanel_read will provide access to what is currently in the display
buffer.

Signed-off-by: Suraj Jitindar Singh <sjitindarsi...@gmail.com>
---
 MAINTAINERS                                   |   1 +
 arch/powerpc/include/asm/opal.h               |   7 +
 arch/powerpc/platforms/powernv/Kconfig        |   4 +
 arch/powerpc/platforms/powernv/Makefile       |   1 +
 arch/powerpc/platforms/powernv/opal-oppanel.c | 292 ++++++++++++++++++++++++++
 drivers/char/Kconfig                          |   1 +
 6 files changed, 306 insertions(+)
 create mode 100644 arch/powerpc/platforms/powernv/opal-oppanel.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b1703ca..95841e1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9218,6 +9218,7 @@ M:        Suraj Jitindar Singh <sjitindarsi...@gmail.com>
 L:     linuxppc-dev@lists.ozlabs.org
 S:     Maintained
 F:     drivers/char/powernv-op-panel.c
+F:     arch/powerpc/platforms/powernv/opal-oppanel.c
 
 PNP SUPPORT
 M:     "Rafael J. Wysocki" <rafael.j.wyso...@intel.com>
diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
index ea9e7f4..2802e1f 100644
--- a/arch/powerpc/include/asm/opal.h
+++ b/arch/powerpc/include/asm/opal.h
@@ -286,6 +286,13 @@ static inline int opal_get_async_rc(struct opal_msg msg)
                return be64_to_cpu(msg.params[1]);
 }
 
+#if defined(CONFIG_POWERNV_OP_PANEL) || defined(CONFIG_POWERNV_OP_PANEL_MODULE)
+extern int opal_oppanel_write(char *msg);
+extern void opal_oppanel_read(char *msg);
+extern void opal_oppanel_get_size(u32 *size);
+extern int opal_oppanel_init(void);
+#endif /* CONFIG_POWERNV_OP_PANEL */
+
 #endif /* __ASSEMBLY__ */
 
 #endif /* _ASM_POWERPC_OPAL_H */
diff --git a/arch/powerpc/platforms/powernv/Kconfig 
b/arch/powerpc/platforms/powernv/Kconfig
index 604190c..df325f7 100644
--- a/arch/powerpc/platforms/powernv/Kconfig
+++ b/arch/powerpc/platforms/powernv/Kconfig
@@ -26,3 +26,7 @@ config OPAL_PRD
        help
          This enables the opal-prd driver, a facility to run processor
          recovery diagnostics on OpenPower machines
+
+config POWERNV_OP_PANEL_BASE
+       bool
+       default n
diff --git a/arch/powerpc/platforms/powernv/Makefile 
b/arch/powerpc/platforms/powernv/Makefile
index cd9711e..0565351 100644
--- a/arch/powerpc/platforms/powernv/Makefile
+++ b/arch/powerpc/platforms/powernv/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_PPC_SCOM)        += opal-xscom.o
 obj-$(CONFIG_MEMORY_FAILURE)   += opal-memory-errors.o
 obj-$(CONFIG_TRACEPOINTS)      += opal-tracepoints.o
 obj-$(CONFIG_OPAL_PRD) += opal-prd.o
+obj-$(CONFIG_POWERNV_OP_PANEL_BASE)    += opal-oppanel.o
diff --git a/arch/powerpc/platforms/powernv/opal-oppanel.c 
b/arch/powerpc/platforms/powernv/opal-oppanel.c
new file mode 100644
index 0000000..1ddf414
--- /dev/null
+++ b/arch/powerpc/platforms/powernv/opal-oppanel.c
@@ -0,0 +1,292 @@
+/*
+ * OPAL PowerNV Operator Panel Display Code
+ *
+ * Copyright 2016, Suraj Jitindar Singh, IBM Corporation.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+#include <asm/opal.h>
+
+enum {
+       MODE_LF,
+       MODE_CR,
+       MODE_NC,
+};
+
+static DEFINE_MUTEX(opal_oppanel_mutex);
+
+static u32             num_lines, line_len, buf_len;
+static char            *buf;
+static oppanel_line_t  *lines;
+static int             cursor;
+
+static inline int last_line_index(void)
+{
+       return buf_len - line_len;
+}
+
+/*
+ * oppanel_write_to_display
+ * Write the contents of buf to the physical operator panel LCD display
+ *
+ * @return:    OPAL_SUCCESS on successful write
+ *             else write failed
+ */
+static int oppanel_write_to_display(void)
+{
+       struct opal_msg msg;
+       int rc, token;
+
+       token = opal_async_get_token_interruptible();
+       if (token < 0) {
+               if (token != -ERESTARTSYS)
+                       pr_debug("Couldn't get OPAL async token [token=%d]\n",
+                               token);
+               return token;
+       }
+
+       rc = opal_write_oppanel_async(token, lines, num_lines);
+       switch (rc) {
+       case OPAL_ASYNC_COMPLETION:
+               rc = opal_async_wait_response(token, &msg);
+               if (rc) {
+                       pr_debug("Failed to wait for async response [rc=%d]\n",
+                               rc);
+                       break;
+               }
+               rc = opal_get_async_rc(msg);
+               if (rc != OPAL_SUCCESS) {
+                       pr_debug("OPAL async call returned failed [rc=%d]\n",
+                               rc);
+                       break;
+               }
+       case OPAL_SUCCESS:
+               break;
+       default:
+               pr_debug("OPAL write op-panel call failed [rc=%d]\n", rc);
+       }
+
+       opal_async_release_token(token);
+       return rc;
+}
+
+/*
+ * oppanel_shift_up
+ * Shift the lines in buf up by one
+ */
+static void oppanel_shift_up(void)
+{
+       int i;
+
+       /* Shift line lengths */
+       for (i = 0; i < (num_lines - 1); i++)
+               lines[i].line_len = lines[i+1].line_len;
+
+       /* i = num_lines - 1 -> last line */
+       lines[i].line_len = 0;
+       memcpy(buf, &buf[line_len], line_len * i);
+       memset(&buf[i*line_len], '\0', line_len);
+}
+
+/*
+ * oppanel_update_display
+ * Update the oppanel display based on the current buf and cursor values
+ *
+ * @param:     mode, operation to perform
+ * @return:    OPAL_SUCCESS on success
+ *             else update failed
+ */
+static int oppanel_update_display(int mode)
+{
+       int rc;
+
+       lines[num_lines-1].line_len = cpu_to_be64(cursor + line_len - buf_len);
+       rc = oppanel_write_to_display();
+       /* May still be busy from last write call, if so we should retry */
+       while (rc != OPAL_SUCCESS) {
+               if (rc == OPAL_BUSY_EVENT || rc == -ERESTARTSYS)
+                       rc = oppanel_write_to_display();
+               else
+                       break;
+       }
+       switch (mode) {
+       case MODE_LF:
+               oppanel_shift_up();
+       case MODE_CR:
+               cursor = last_line_index();
+               break;
+       default:
+               break;
+       }
+
+       return rc;
+}
+
+/*
+ * opal_oppanel_write
+ * Write a message (msg) to the operator panel LCD display present on IBM
+ * powernv machines
+ *
+ * @param:     msg, the message buffer to be written to the display
+ * @return:    0               Success
+ *             -EIO            Opal call failed
+ *             -EBUSY          Resource currently in use
+ *             -ENODEV         Required resources not initialised,
+ *                             either init function not called yet
+ *                             or init failed likely due to no oppanel
+ */
+int opal_oppanel_write(char *msg)
+{
+       int rc = OPAL_SUCCESS;
+
+       if (!msg || !*msg)
+               return 0;
+       if (!buf || !buf_len || !lines)
+               return -ENODEV;
+
+       if (mutex_trylock(&opal_oppanel_mutex)) {
+               int i, prev_cursor = cursor;
+               char prev_buf[buf_len];
+
+               memcpy(prev_buf, buf, buf_len);
+
+               for (i = 0; msg[i] && rc == OPAL_SUCCESS; i++) {
+                       char c = msg[i];
+
+                       switch (c) {
+                       case '\n':
+                               rc = oppanel_update_display(MODE_LF);
+                               break;
+                       case '\r':
+                               if (cursor != last_line_index())
+                                       rc = oppanel_update_display(MODE_CR);
+                               break;
+                       default:
+                               buf[cursor++] = c;
+                               /* End of display line */
+                               if (cursor >= buf_len)
+                                       rc = oppanel_update_display(MODE_LF);
+                               /* End of input buffer */
+                               else if (!msg[i+1])
+                                       rc = oppanel_update_display(MODE_NC);
+                               break;
+                       }
+               }
+
+               if (rc != OPAL_SUCCESS) {
+                       cursor = prev_cursor;
+                       memcpy(buf, prev_buf, buf_len);
+                       rc = -EIO;
+               }
+               mutex_unlock(&opal_oppanel_mutex);
+       } else
+               rc = -EBUSY;
+
+       return rc;
+}
+EXPORT_SYMBOL_GPL(opal_oppanel_write);
+
+/*
+ * opal_oppanel_read
+ * Read the operator panel message buffer and copy it to msg
+ *
+ * @param:     msg, a char buffer of size atleast buf_len,
+ *             as obtained by calling opal_oppanel_get_size.
+ */
+void opal_oppanel_read(char *msg)
+{
+       memcpy(msg, buf, buf_len);
+}
+EXPORT_SYMBOL_GPL(opal_oppanel_read);
+
+/*
+ * opal_oppanel_get_size
+ * Get the size of the physical operator panel display
+ *
+ * @param:     size, u32 pointer to where the size value should be stored.
+ */
+void opal_oppanel_get_size(u32 *size)
+{
+       *size = buf_len;
+}
+EXPORT_SYMBOL_GPL(opal_oppanel_get_size);
+
+/*
+ * opal_oppanel_init
+ * Initalise the operator panel, this must be called before any other
+ * function in this file
+ */
+int __init opal_oppanel_init(void)
+{
+       struct platform_device *pdev;
+       struct device_node *np;
+       int rc, i;
+
+       np = of_find_node_by_path("/ibm,opal/oppanel");
+       if (!np) {
+               pr_err("Opal node 'oppanel' not found\n");
+               rc = -ENODEV;
+               goto np_put;
+       }
+
+       /* Read length and number of lines device tree properties */
+       rc = of_property_read_u32(np, "#length", &line_len);
+       if (rc) {
+               pr_err("Opal node 'oppanel', '#length' property not found\n");
+               goto np_put;
+       }
+       rc = of_property_read_u32(np, "#lines", &num_lines);
+       if (rc) {
+               pr_err("Opal node 'oppanel', '#lines' property not found\n");
+               goto np_put;
+       }
+       buf_len = line_len * num_lines;
+
+       /* Allocate Memory */
+       buf = kcalloc(buf_len, sizeof(*buf), GFP_KERNEL);
+       if (!buf) {
+               rc = -ENOMEM;
+               goto np_put;
+       }
+       lines = kcalloc(num_lines, sizeof(*lines), GFP_KERNEL);
+       if (!lines) {
+               rc = -ENOMEM;
+               goto free_buf;
+       }
+
+       /* Setup lines structure */
+       for (i = 0; i < num_lines; i++) {
+               lines[i].line_len = 0;
+               lines[i].line = cpu_to_be64(__pa(&buf[i * line_len]));
+       }
+       cursor = last_line_index();
+
+       /* Create platform device */
+       pdev = of_platform_device_create(np, NULL, NULL);
+       rc = PTR_ERR_OR_ZERO(pdev);
+       if (rc)
+               goto free_lines;
+
+       /* We still need to node_put on success */
+       rc = 0;
+       goto np_put;
+
+free_lines:
+       kfree(lines);
+free_buf:
+       kfree(buf);
+np_put:
+       of_node_put(np);
+       return rc;
+}
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 5fcb797..95f6885 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -181,6 +181,7 @@ config IBM_BSR
 config POWERNV_OP_PANEL
        tristate "IBM POWERNV Operator Panel Display support"
        depends on PPC_POWERNV
+       select POWERNV_OP_PANEL_BASE
        help
          If you say Y here, a special character device node, /dev/op_panel,
          will be created which exposes the operator panel display on IBM
-- 
2.5.5

_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@lists.ozlabs.org
https://lists.ozlabs.org/listinfo/linuxppc-dev

Reply via email to