All of lore.kernel.org
 help / color / mirror / Atom feed
From: Daniel Drake <dsd@laptop.org>
To: dmitry.torokhov@gmail.com, dtor@mail.ru
Cc: linux-input@vger.kernel.org, pgf@laptop.org,
	devicetree-discuss@lists.ozlabs.org
Subject: [PATCH v3] Add OLPC AP-SP input driver
Date: Fri, 28 Jun 2013 13:19:58 -0400 (EDT)	[thread overview]
Message-ID: <20130628171958.CC031FAAD5@dev.laptop.org> (raw)

The OLPC XO-1.75 and XO-4 laptops include a PS/2 touchpad and an AT
keyboard, yet they do not have a hardware PS/2 controller. Instead, a
firmware runs on a dedicated core ("Security Processor", part of the SoC)
that acts as a PS/2 controller through bit-banging.

Communication between the main cpu (Application Processor) and the
Security Processor happens via a standard command mechanism implemented
by the SoC. Add a driver for this interface to enable keyboard/mouse
input on this platform.

Original author: Saadia Baloch
Signed-off-by: Daniel Drake <dsd@laptop.org>
---
 .../devicetree/bindings/serio/olpc,ap-sp.txt       |  13 +
 drivers/input/serio/Kconfig                        |  10 +
 drivers/input/serio/Makefile                       |   1 +
 drivers/input/serio/olpc_apsp.c                    | 280 +++++++++++++++++++++
 4 files changed, 304 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
 create mode 100644 drivers/input/serio/olpc_apsp.c

v2: Use dev_dbg(). Disable interrupts during unload, and remove bad use of
devm.

v3: Avoid sleep in write function. Reference count open/close operations
as both ports share the same interrupt. Fix devm_ioremap_resource() error
handling. Use SERIO_8042 type. Only register IRQ handler after port structure
is ready.

diff --git a/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt b/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
new file mode 100644
index 0000000..0e72183
--- /dev/null
+++ b/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt
@@ -0,0 +1,13 @@
+OLPC AP-SP serio interface
+
+Required properties:
+- compatible : "olpc,ap-sp"
+- reg : base address and length of SoC's WTM registers
+- interrupts : SP-AP interrupt
+
+Example:
+	ap-sp@d4290000 {
+		compatible = "olpc,ap-sp";
+		reg = <0xd4290000 0x1000>;
+		interrupts = <40>;
+	}
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
index 1bda828..94c17c2 100644
--- a/drivers/input/serio/Kconfig
+++ b/drivers/input/serio/Kconfig
@@ -256,4 +256,14 @@ config SERIO_APBPS2
 	  To compile this driver as a module, choose M here: the module will
 	  be called apbps2.
 
+config SERIO_OLPC_APSP
+	tristate "OLPC AP-SP input support"
+	depends on OF
+	help
+	  Say Y here if you want support for the keyboard and touchpad included
+	  in the OLPC XO-1.75 and XO-4 laptops.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called olpc_apsp.
+
 endif
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile
index 8edb36c..12298b1 100644
--- a/drivers/input/serio/Makefile
+++ b/drivers/input/serio/Makefile
@@ -27,3 +27,4 @@ obj-$(CONFIG_SERIO_XILINX_XPS_PS2)	+= xilinx_ps2.o
 obj-$(CONFIG_SERIO_ALTERA_PS2)	+= altera_ps2.o
 obj-$(CONFIG_SERIO_ARC_PS2)	+= arc_ps2.o
 obj-$(CONFIG_SERIO_APBPS2)	+= apbps2.o
+obj-$(CONFIG_SERIO_OLPC_APSP)	+= olpc_apsp.o
diff --git a/drivers/input/serio/olpc_apsp.c b/drivers/input/serio/olpc_apsp.c
new file mode 100644
index 0000000..c4d3624
--- /dev/null
+++ b/drivers/input/serio/olpc_apsp.c
@@ -0,0 +1,280 @@
+/*
+ * OLPC serio driver for multiplexed input from Marvell MMP security processor
+ *
+ * Copyright (C) 2011-2013 One Laptop Per Child
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/serio.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+/*
+ * The OLPC XO-1.75 and XO-4 laptops do not have a hardware PS/2 controller.
+ * Instead, the OLPC firmware runs a bit-banging PS/2 implementation on an
+ * otherwise-unused slow processor which is included in the Marvell MMP2/MMP3
+ * SoC, known as the "Security Processor" (SP) or "Wireless Trusted Module"
+ * (WTM). This firmware then reports its results via the WTM registers,
+ * which we read from the Application Processor (AP, i.e. main CPU) in this
+ * driver.
+ *
+ * On the hardware side we have a PS/2 mouse and an AT keyboard, the data
+ * is multiplexed through this system. We create a serio port for each one,
+ * and demultiplex the data accordingly.
+ */
+
+/* WTM register offsets */
+#define SECURE_PROCESSOR_COMMAND	0x40
+#define COMMAND_RETURN_STATUS		0x80
+#define COMMAND_FIFO_STATUS		0xc4
+#define PJ_RST_INTERRUPT		0xc8
+#define PJ_INTERRUPT_MASK		0xcc
+
+/* The upper byte of SECURE_PROCESSOR_COMMAND and COMMAND_RETURN_STATUS is
+ * used to identify which port (device) is being talked to. The lower byte
+ * is the data being sent/received. */
+#define PORT_MASK	0xff00
+#define DATA_MASK	0x00ff
+#define PORT_SHIFT	8
+#define KEYBOARD_PORT	0
+#define TOUCHPAD_PORT	1
+
+/* COMMAND_FIFO_STATUS */
+#define CMD_CNTR_MASK		0x7 /* Number of pending/unprocessed commands */
+#define MAX_PENDING_CMDS	4 /* from device specs */
+
+/* PJ_RST_INTERRUPT */
+#define SP_COMMAND_COMPLETE_RESET	0x1
+
+/* PJ_INTERRUPT_MASK */
+#define INT_0	(1 << 0)
+
+/* COMMAND_FIFO_STATUS */
+#define CMD_STS_MASK	0x100
+
+struct olpc_apsp {
+	struct device *dev;
+	struct serio *kbio;
+	struct serio *padio;
+	void __iomem *base;
+	int open_count;
+	int irq;
+};
+
+static int olpc_apsp_write(struct serio *port, unsigned char val)
+{
+	struct olpc_apsp *priv = port->port_data;
+	unsigned int i;
+	u32 which = 0;
+
+	if (port == priv->padio)
+		which = TOUCHPAD_PORT << PORT_SHIFT;
+	else
+		which = KEYBOARD_PORT << PORT_SHIFT;
+
+	dev_dbg(priv->dev, "olpc_apsp_write which=%x val=%x\n", which, val);
+	for (i = 0; i < 50; i++) {
+		u32 sts = readl(priv->base + COMMAND_FIFO_STATUS);
+		if ((sts & CMD_CNTR_MASK) < MAX_PENDING_CMDS) {
+			writel(which | val,
+			       priv->base + SECURE_PROCESSOR_COMMAND);
+			return 0;
+		}
+		/* SP busy. This has not been seen in practice. */
+		mdelay(1);
+	}
+
+	dev_dbg(priv->dev, "olpc_apsp_write timeout, status=%x\n",
+	    readl(priv->base + COMMAND_FIFO_STATUS));
+	return -ETIMEDOUT;
+}
+
+static irqreturn_t olpc_apsp_rx(int irq, void *dev_id)
+{
+	struct olpc_apsp *priv = dev_id;
+	unsigned int w, tmp;
+	struct serio *serio;
+
+	/*
+	 * Write 1 to PJ_RST_INTERRUPT to acknowledge and clear the interrupt
+	 * Write 0xff00 to SECURE_PROCESSOR_COMMAND.
+	 */
+	tmp = readl(priv->base + PJ_RST_INTERRUPT);
+	if (!(tmp & SP_COMMAND_COMPLETE_RESET)) {
+		dev_warn(priv->dev, "spurious interrupt?\n");
+		return IRQ_NONE;
+	}
+
+	w = readl(priv->base + COMMAND_RETURN_STATUS);
+	dev_dbg(priv->dev, "olpc_apsp_rx %x\n", w);
+
+	if (w >> PORT_SHIFT == KEYBOARD_PORT)
+		serio = priv->kbio;
+	else
+		serio = priv->padio;
+
+	serio_interrupt(serio, w & DATA_MASK, 0);
+
+	/* Ack and clear interrupt */
+	writel(tmp | SP_COMMAND_COMPLETE_RESET, priv->base + PJ_RST_INTERRUPT);
+	writel(PORT_MASK, priv->base + SECURE_PROCESSOR_COMMAND);
+
+	pm_wakeup_event(priv->dev, 1000);
+	return IRQ_HANDLED;
+}
+
+static int olpc_apsp_open(struct serio *port)
+{
+	struct olpc_apsp *priv = port->port_data;
+	unsigned int tmp;
+
+	if (priv->open_count++ == 0) {
+		/* Enable interrupt 0 by clearing its bit */
+		tmp = readl(priv->base + PJ_INTERRUPT_MASK);
+		writel(tmp & ~INT_0, priv->base + PJ_INTERRUPT_MASK);
+	}
+
+	return 0;
+}
+
+static void olpc_apsp_close(struct serio *port)
+{
+	struct olpc_apsp *priv = port->port_data;
+	unsigned int tmp;
+
+	if (--priv->open_count == 0) {
+		/* Disable interrupt 0 */
+		tmp = readl(priv->base + PJ_INTERRUPT_MASK);
+		writel(tmp | INT_0, priv->base + PJ_INTERRUPT_MASK);
+	}
+}
+
+static int olpc_apsp_probe(struct platform_device *pdev)
+{
+	struct serio *kb_serio, *pad_serio;
+	struct olpc_apsp *priv;
+	struct resource *res;
+	struct device_node *np;
+	unsigned long l;
+	int error;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(struct olpc_apsp), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	np = pdev->dev.of_node;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENOENT;
+
+	priv->irq = platform_get_irq(pdev, 0);
+	if (priv->irq < 0)
+		return priv->irq;
+
+	priv->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->base)) {
+		dev_err(&pdev->dev, "Failed to map WTM registers\n");
+		return PTR_ERR(priv->base);
+	}
+
+	l = readl(priv->base + COMMAND_FIFO_STATUS);
+	if (!(l & CMD_STS_MASK)) {
+		dev_err(&pdev->dev, "SP cannot accept commands.\n");
+		return -ENODEV;
+	}
+
+	/* KEYBOARD */
+	kb_serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+	if (!kb_serio)
+		return -ENOMEM;
+	kb_serio->id.type	= SERIO_8042_XL;
+	kb_serio->write		= olpc_apsp_write;
+	kb_serio->open		= olpc_apsp_open;
+	kb_serio->close		= olpc_apsp_close;
+	kb_serio->port_data	= priv;
+	kb_serio->dev.parent	= &pdev->dev;
+	strlcpy(kb_serio->name, "sp keyboard", sizeof(kb_serio->name));
+	strlcpy(kb_serio->phys, "sp/serio0", sizeof(kb_serio->phys));
+	priv->kbio		= kb_serio;
+	serio_register_port(kb_serio);
+
+	/* TOUCHPAD */
+	pad_serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
+	if (!pad_serio) {
+		error = -ENOMEM;
+		goto err_pad;
+	}
+	pad_serio->id.type	= SERIO_8042;
+	pad_serio->write	= olpc_apsp_write;
+	pad_serio->open		= olpc_apsp_open;
+	pad_serio->close	= olpc_apsp_close;
+	pad_serio->port_data	= priv;
+	pad_serio->dev.parent	= &pdev->dev;
+	strlcpy(pad_serio->name, "sp touchpad", sizeof(pad_serio->name));
+	strlcpy(pad_serio->phys, "sp/serio1", sizeof(pad_serio->phys));
+	priv->padio		= pad_serio;
+	serio_register_port(pad_serio);
+
+	error = request_irq(priv->irq, olpc_apsp_rx, 0, "olpc-apsp", priv);
+	if (error) {
+		dev_err(&pdev->dev, "Failed to request IRQ\n");
+		goto err_irq;
+	}
+
+	priv->dev = &pdev->dev;
+	device_init_wakeup(priv->dev, 1);
+	platform_set_drvdata(pdev, priv);
+	dev_dbg(&pdev->dev, "probed successfully.\n");
+	return 0;
+
+err_irq:
+	serio_unregister_port(pad_serio);
+err_pad:
+	serio_unregister_port(kb_serio);
+	return error;
+}
+
+static int olpc_apsp_remove(struct platform_device *pdev)
+{
+	struct olpc_apsp *priv = platform_get_drvdata(pdev);
+	free_irq(priv->irq, priv);
+	serio_unregister_port(priv->kbio);
+	serio_unregister_port(priv->padio);
+	return 0;
+}
+
+static struct of_device_id olpc_apsp_dt_ids[] = {
+	{ .compatible = "olpc,ap-sp", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, olpc_apsp_driver_dt_ids);
+
+static struct platform_driver olpc_apsp_driver = {
+	.probe		= olpc_apsp_probe,
+	.remove		= olpc_apsp_remove,
+	.driver		= {
+		.name	= "olpc-apsp",
+		.owner	= THIS_MODULE,
+		.of_match_table = olpc_apsp_dt_ids,
+	},
+};
+
+MODULE_DESCRIPTION("OLPC AP-SP serio driver");
+MODULE_LICENSE("GPL");
+module_platform_driver(olpc_apsp_driver);
-- 
1.8.1.4


             reply	other threads:[~2013-06-28 17:18 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-06-28 17:19 Daniel Drake [this message]
2013-06-28 18:20 ` [PATCH v3] Add OLPC AP-SP input driver Dmitry Torokhov
2013-06-28 19:15   ` Daniel Drake
2013-06-28 20:32     ` Dmitry Torokhov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20130628171958.CC031FAAD5@dev.laptop.org \
    --to=dsd@laptop.org \
    --cc=devicetree-discuss@lists.ozlabs.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=dtor@mail.ru \
    --cc=linux-input@vger.kernel.org \
    --cc=pgf@laptop.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.