Skip to contentSkip to conversation

OLPC: create a generic OLPC EC driver

PATCH RESEND In review Andres Salomon 2 participants 11 messages Jul 19, 2012 – Jul 26, 2012

Subsystems (6)
lore
Share & tools
lore
#0 lore raw

cover OLPC: create a generic OLPC EC driver

The OLPC EC (Embedded Controller) code that is currently upstream is x86-only, originally written for the XO-1. Since then, we've had the XO-1.5 (also x86), and XO-1.75 (arm-based) enter mass production. The 1.75 uses a vastly different EC protocol, and future hardware revisions are likely to change it even further.

However, the drivers do share quite a bit of code, so it makes sense to have a platform-agnostic driver that calls into platform-specific hooks for each XO's EC driver. This is the first stab and creating such a beast (with further patches pending). Aside from the lack of code duplication, this is helpful for fixing bugs in one place (for example, we fixed an EC suspend/resume bug in 1.75 that I've just seen happen on 1.5 without these patches. With these patches, the problem goes away).

These patches are against Linus's current HEAD; let me know if they don't apply somewhere, and I'll happily redo them against the -next tree. I'm assuming that these changes (which touch places like x86, wireless, and staging) should go through either the x86 tree, or through akpm's tree.

Alternatively, if the reviews are positive and I can get SOBs from the relevant maintainers, I can set up a platform-olpc tree somewhere and request a pull from Linus.

#1 lore raw

PATCH RESEND 1/9 Platform: OLPC: add a stub to drivers/platform/ for the OLPC EC driver

The OLPC EC driver has outgrown arch/x86/platform/. It's time to both share common code amongst different architectures, as well as move it out of arch/x86/. The XO-1.75 is ARM-based, and the EC driver shares a lot of code with the x86 code.

---
 arch/x86/include/asm/olpc.h     |   19 +++----------------
 arch/x86/platform/olpc/olpc.c   |    4 ++--
 drivers/platform/Makefile       |    1 +
 drivers/platform/olpc/olpc-ec.c |   16 ++++++++++++++++
 include/linux/olpc-ec.h         |   29 +++++++++++++++++++++++++++++
 5 files changed, 51 insertions(+), 18 deletions(-)
 create mode 100644 drivers/platform/olpc/olpc-ec.c
 create mode 100644 include/linux/olpc-ec.h
arch/x86/include/asm/olpc.h +3 -16
index 87bdbca..513e999 100644
@@ -4,6 +4,7 @@
 #define _ASM_X86_OLPC_H
 
 #include <asm/geode.h>
+#include <linux/olpc-ec.h>
 
 struct olpc_platform_t {
 	int flags;
@@ -102,22 +103,8 @@ extern int pci_olpc_init(void);
 
 /* EC related functions */
 
-extern int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen,
-		unsigned char *outbuf, size_t outlen);
-
-/* EC commands */
-
-#define EC_FIRMWARE_REV			0x08
-#define EC_WRITE_SCI_MASK		0x1b
-#define EC_WAKE_UP_WLAN			0x24
-#define EC_WLAN_LEAVE_RESET		0x25
-#define EC_READ_EB_MODE			0x2a
-#define EC_SET_SCI_INHIBIT		0x32
-#define EC_SET_SCI_INHIBIT_RELEASE	0x34
-#define EC_WLAN_ENTER_RESET		0x35
-#define EC_WRITE_EXT_SCI_MASK		0x38
-#define EC_SCI_QUERY			0x84
-#define EC_EXT_SCI_QUERY		0x85
+extern int olpc_ec_cmd_x86(unsigned char cmd, unsigned char *inbuf,
+		size_t inlen, unsigned char *outbuf, size_t outlen);
 
 /* SCI source values */
 
arch/x86/platform/olpc/olpc.c +2 -2
index a4bee53..796e199 100644
@@ -125,7 +125,7 @@ static int __wait_on_obf(unsigned int line, unsigned int port, int desired)
  * <http://wiki.laptop.org/go/Ec_specification>.  Unfortunately, while
  * OpenFirmware's source is available, the EC's is not.
  */
-int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen,
+int olpc_ec_cmd_x86(unsigned char cmd, unsigned char *inbuf, size_t inlen,
 		unsigned char *outbuf,  size_t outlen)
 {
 	unsigned long flags;
@@ -201,7 +201,7 @@ err:
 	spin_unlock_irqrestore(&ec_lock, flags);
 	return ret;
 }
-EXPORT_SYMBOL_GPL(olpc_ec_cmd);
+EXPORT_SYMBOL_GPL(olpc_ec_cmd_x86);
 
 void olpc_ec_wakeup_set(u16 value)
 {
drivers/platform/Makefile +1 -0
index 782953a..b17c16c 100644
@@ -3,3 +3,4 @@
 #
 
 obj-$(CONFIG_X86)		+= x86/
+obj-$(CONFIG_OLPC)		+= olpc/
drivers/platform/olpc/olpc-ec.c new file +16 -0
new file mode 100644
index 0000000..4202603
@@ -0,0 +1,16 @@
+/*
+ * Generic driver for the OLPC Embedded Controller.
+ *
+ * Copyright (C) 2011-2012 One Laptop per Child Foundation.
+ *
+ * Licensed under the GPL v2 or later.
+ */
+#include <linux/module.h>
+#include <asm/olpc.h>
+
+int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
+{
+	/* Currently a stub; this will be expanded upon later. */
+	return olpc_ec_cmd_x86(cmd, inbuf, inlen, outbuf, outlen);
+}
+EXPORT_SYMBOL_GPL(olpc_ec_cmd);
include/linux/olpc-ec.h new file +29 -0
new file mode 100644
index 0000000..6d4e426
@@ -0,0 +1,29 @@
+#ifndef _LINUX_OLPC_EC_H
+#define _LINUX_OLPC_EC_H
+
+/* XO-1 EC commands */
+#define EC_FIRMWARE_REV			0x08
+#define EC_WRITE_SCI_MASK		0x1b
+#define EC_WAKE_UP_WLAN			0x24
+#define EC_WLAN_LEAVE_RESET		0x25
+#define EC_READ_EB_MODE			0x2a
+#define EC_SET_SCI_INHIBIT		0x32
+#define EC_SET_SCI_INHIBIT_RELEASE	0x34
+#define EC_WLAN_ENTER_RESET		0x35
+#define EC_WRITE_EXT_SCI_MASK		0x38
+#define EC_SCI_QUERY			0x84
+#define EC_EXT_SCI_QUERY		0x85
+
+#ifdef CONFIG_OLPC
+
+extern int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf,
+		size_t outlen);
+
+#else
+
+static inline int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf,
+		size_t outlen) { return -ENODEV; }
+
+#endif /* CONFIG_OLPC */
+
+#endif /* _LINUX_OLPC_EC_H */
Signature · 1 line
1.7.2.5
#2 lore raw

PATCH RESEND 2/9 drivers: OLPC: update various drivers to include olpc-ec.h

In reply to Andres Salomon

Switch over to using olpc-ec.h in multiple steps, so as not to break builds. This covers every driver that calls olpc_ec_cmd().

---
 arch/x86/include/asm/olpc.h            |    1 -
 arch/x86/platform/olpc/olpc-xo1-pm.c   |    1 +
 arch/x86/platform/olpc/olpc-xo1-sci.c  |    1 +
 arch/x86/platform/olpc/olpc-xo15-sci.c |    1 +
 arch/x86/platform/olpc/olpc.c          |    1 +
 drivers/net/wireless/libertas/if_usb.c |    1 +
 drivers/platform/x86/xo1-rfkill.c      |    3 +--
 drivers/power/olpc_battery.c           |    1 +
 drivers/staging/olpc_dcon/olpc_dcon.c  |    1 +
 9 files changed, 8 insertions(+), 3 deletions(-)
arch/x86/include/asm/olpc.h +0 -1
index 513e999..5b28f3e 100644
@@ -4,7 +4,6 @@
 #define _ASM_X86_OLPC_H
 
 #include <asm/geode.h>
-#include <linux/olpc-ec.h>
 
 struct olpc_platform_t {
 	int flags;
arch/x86/platform/olpc/olpc-xo1-pm.c +1 -0
index 0ce8616c..8054b64 100644
@@ -18,6 +18,7 @@
 #include <linux/pm.h>
 #include <linux/mfd/core.h>
 #include <linux/suspend.h>
+#include <linux/olpc-ec.h>
 
 #include <asm/io.h>
 #include <asm/olpc.h>
arch/x86/platform/olpc/olpc-xo1-sci.c +1 -0
index 04b8c73..63d4aa4 100644
@@ -23,6 +23,7 @@
 #include <linux/power_supply.h>
 #include <linux/suspend.h>
 #include <linux/workqueue.h>
+#include <linux/olpc-ec.h>
 
 #include <asm/io.h>
 #include <asm/msr.h>
arch/x86/platform/olpc/olpc-xo15-sci.c +1 -0
index 23e5b9d..9899fef 100644
@@ -13,6 +13,7 @@
 #include <linux/slab.h>
 #include <linux/workqueue.h>
 #include <linux/power_supply.h>
+#include <linux/olpc-ec.h>
 
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
arch/x86/platform/olpc/olpc.c +1 -0
index 796e199..a3fa180 100644
@@ -22,6 +22,7 @@
 #include <linux/syscore_ops.h>
 #include <linux/debugfs.h>
 #include <linux/mutex.h>
+#include <linux/olpc-ec.h>
 
 #include <asm/geode.h>
 #include <asm/setup.h>
drivers/net/wireless/libertas/if_usb.c +1 -0
index cd3b0d4..a9828b1 100644
@@ -10,6 +10,7 @@
 #include <linux/netdevice.h>
 #include <linux/slab.h>
 #include <linux/usb.h>
+#include <linux/olpc-ec.h>
 
 #ifdef CONFIG_OLPC
 #include <asm/olpc.h>
drivers/platform/x86/xo1-rfkill.c +1 -2
index b57ad86..1da13ed 100644
@@ -12,8 +12,7 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/rfkill.h>
-
-#include <asm/olpc.h>
+#include <linux/olpc-ec.h>
 
 static bool card_blocked;
 
drivers/power/olpc_battery.c +1 -0
index 7385092..eaf917d 100644
@@ -17,6 +17,7 @@
 #include <linux/power_supply.h>
 #include <linux/jiffies.h>
 #include <linux/sched.h>
+#include <linux/olpc-ec.h>
 #include <asm/olpc.h>
 
 
drivers/staging/olpc_dcon/olpc_dcon.c +1 -0
index 992275c..2c4bd74 100644
@@ -27,6 +27,7 @@
 #include <linux/uaccess.h>
 #include <linux/ctype.h>
 #include <linux/reboot.h>
+#include <linux/olpc-ec.h>
 #include <asm/tsc.h>
 #include <asm/olpc.h>
 
Signature · 1 line
1.7.2.5
#3 lore raw

PATCH RESEND 3/9 Platform: OLPC: allow EC cmd to be overridden, and create a workqueue to call it

In reply to Andres Salomon

This provides a new API allows different OLPC architectures to override the EC driver. x86 and ARM OLPC machines use completely different EC backends.

The olpc_ec_cmd is synchronous, and waits for the workqueue to send the command to the EC. Multiple callers can run olpc_ec_cmd() at once, and they will by serialized and sleep while only one executes on the EC at a time.

We don't provide an unregister function, as that doesn't make sense within the context of OLPC machines - there's only ever 1 EC, it's critical to functionality, and it certainly not hotpluggable.

---
 drivers/platform/olpc/olpc-ec.c |  112 ++++++++++++++++++++++++++++++++++++++-
 include/linux/olpc-ec.h         |    6 ++
 2 files changed, 116 insertions(+), 2 deletions(-)
drivers/platform/olpc/olpc-ec.c +110 -2
index 4202603..44e6a4f 100644
@@ -5,12 +5,120 @@
  *
  * Licensed under the GPL v2 or later.
  */
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
 #include <linux/module.h>
+#include <linux/list.h>
+#include <linux/olpc-ec.h>
 #include <asm/olpc.h>
 
+struct ec_cmd_desc {
+	u8 cmd;
+	u8 *inbuf, *outbuf;
+	size_t inlen, outlen;
+
+	int err;
+	struct completion finished;
+	struct list_head node;
+
+	void *priv;
+};
+
+static void olpc_ec_worker(struct work_struct *w);
+
+static DECLARE_WORK(ec_worker, olpc_ec_worker);
+static LIST_HEAD(ec_cmd_q);
+static DEFINE_SPINLOCK(ec_cmd_q_lock);
+
+static struct olpc_ec_driver *ec_driver;
+static void *ec_cb_arg;
+static DEFINE_MUTEX(ec_cb_lock);
+
+void olpc_ec_driver_register(struct olpc_ec_driver *drv, void *arg)
+{
+	ec_driver = drv;
+	ec_cb_arg = arg;
+}
+EXPORT_SYMBOL_GPL(olpc_ec_driver_register);
+
+static void olpc_ec_worker(struct work_struct *w)
+{
+	struct ec_cmd_desc *desc = NULL;
+	unsigned long flags;
+
+	/* Grab the first pending command from the queue */
+	spin_lock_irqsave(&ec_cmd_q_lock, flags);
+	if (!list_empty(&ec_cmd_q)) {
+		desc = list_first_entry(&ec_cmd_q, struct ec_cmd_desc, node);
+		list_del(&desc->node);
+	}
+	spin_unlock_irqrestore(&ec_cmd_q_lock, flags);
+
+	/* Do we actually have anything to do? */
+	if (!desc)
+		return;
+
+	/* Protect the EC hw with a mutex; only run one cmd at a time */
+	mutex_lock(&ec_cb_lock);
+	desc->err = ec_driver->ec_cmd(desc->cmd, desc->inbuf, desc->inlen,
+			desc->outbuf, desc->outlen, ec_cb_arg);
+	mutex_unlock(&ec_cb_lock);
+
+	/* Finished, wake up olpc_ec_cmd() */
+	complete(&desc->finished);
+
+	/* Run the worker thread again in case there are more cmds pending */
+	schedule_work(&ec_worker);
+}
+
+/*
+ * Throw a cmd descripter onto the list.  We now have SMP OLPC machines, so
+ * locking is pretty critical.
+ */
+static void queue_ec_descriptor(struct ec_cmd_desc *desc)
+{
+	unsigned long flags;
+
+	INIT_LIST_HEAD(&desc->node);
+
+	spin_lock_irqsave(&ec_cmd_q_lock, flags);
+	list_add_tail(&desc->node, &ec_cmd_q);
+	spin_unlock_irqrestore(&ec_cmd_q_lock, flags);
+
+	schedule_work(&ec_worker);
+}
+
 int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
 {
-	/* Currently a stub; this will be expanded upon later. */
-	return olpc_ec_cmd_x86(cmd, inbuf, inlen, outbuf, outlen);
+	struct ec_cmd_desc desc;
+
+	/* XXX: this will be removed in later patches */
+	/* Are we using old-style callers? */
+	if (!ec_driver || !ec_driver->ec_cmd)
+		return olpc_ec_cmd_x86(cmd, inbuf, inlen, outbuf, outlen);
+
+	/* Ensure a driver and ec hook have been registered */
+	if (WARN_ON(!ec_driver || !ec_driver->ec_cmd))
+		return -ENODEV;
+
+	might_sleep();
+
+	desc.cmd = cmd;
+	desc.inbuf = inbuf;
+	desc.outbuf = outbuf;
+	desc.inlen = inlen;
+	desc.outlen = outlen;
+	desc.err = 0;
+	init_completion(&desc.finished);
+
+	queue_ec_descriptor(&desc);
+
+	/* Timeouts must be handled in the platform-specific EC hook */
+	wait_for_completion(&desc.finished);
+
+	/* The worker thread dequeues the cmd; no need to do anything here */
+	return desc.err;
 }
 EXPORT_SYMBOL_GPL(olpc_ec_cmd);
include/linux/olpc-ec.h +6 -0
index 6d4e426..231e96f 100644
@@ -14,8 +14,14 @@
 #define EC_SCI_QUERY			0x84
 #define EC_EXT_SCI_QUERY		0x85
 
+struct olpc_ec_driver {
+	int (*ec_cmd)(u8, u8 *, size_t, u8 *, size_t, void *);
+};
+
 #ifdef CONFIG_OLPC
 
+extern void olpc_ec_driver_register(struct olpc_ec_driver *drv, void *arg);
+
 extern int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf,
 		size_t outlen);
 
Signature · 1 line
1.7.2.5
#4 lore raw

PATCH RESEND 4/9 Platform: OLPC: turn EC driver into a platform_driver

In reply to Andres Salomon

The 1.75-based OLPC EC driver already does this; let's do it for all EC drivers. This gives us nice suspend/resume hooks, amongst other things.

We want to run the EC's suspend hooks later than other drivers (which may be setting wakeup masks or be running EC commands). We also want to run the EC's resume hooks earlier than other drivers (which may want to run EC commands).

---
 drivers/platform/olpc/olpc-ec.c |   48 +++++++++++++++++++++++++++++++++++++++
 include/linux/olpc-ec.h         |    6 +++++
 2 files changed, 54 insertions(+), 0 deletions(-)
drivers/platform/olpc/olpc-ec.c +48 -0
index 44e6a4f..d00523c 100644
@@ -8,6 +8,7 @@
 #include <linux/completion.h>
 #include <linux/spinlock.h>
 #include <linux/mutex.h>
+#include <linux/platform_device.h>
 #include <linux/workqueue.h>
 #include <linux/module.h>
 #include <linux/list.h>
@@ -122,3 +123,50 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
 	return desc.err;
 }
 EXPORT_SYMBOL_GPL(olpc_ec_cmd);
+
+static int olpc_ec_probe(struct platform_device *pdev)
+{
+	int err;
+
+	if (!ec_driver)
+		return -ENODEV;
+
+	err = ec_driver->probe ? ec_driver->probe(pdev) : 0;
+
+	return err;
+}
+
+static int olpc_ec_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	return ec_driver->suspend ? ec_driver->suspend(pdev) : 0;
+}
+
+static int olpc_ec_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	return ec_driver->resume ? ec_driver->resume(pdev) : 0;
+}
+
+static const struct dev_pm_ops olpc_ec_pm_ops = {
+	.suspend_late = olpc_ec_suspend,
+	.resume_early = olpc_ec_resume,
+};
+
+static struct platform_driver olpc_ec_plat_driver = {
+	.probe = olpc_ec_probe,
+	.driver = {
+		.name = "olpc-ec",
+		.pm = &olpc_ec_pm_ops,
+	},
+};
+
+static int __init olpc_ec_init_module(void)
+{
+	return platform_driver_register(&olpc_ec_plat_driver);
+}
+
+module_init(olpc_ec_init_module);
+
+MODULE_AUTHOR("Andres Salomon <[email protected]>");
+MODULE_LICENSE("GPL");
include/linux/olpc-ec.h +6 -0
index 231e96f..5bb6e76 100644
@@ -14,7 +14,13 @@
 #define EC_SCI_QUERY			0x84
 #define EC_EXT_SCI_QUERY		0x85
 
+struct platform_device;
+
 struct olpc_ec_driver {
+	int (*probe)(struct platform_device *);
+	int (*suspend)(struct platform_device *);
+	int (*resume)(struct platform_device *);
+
 	int (*ec_cmd)(u8, u8 *, size_t, u8 *, size_t, void *);
 };
 
Signature · 1 line
1.7.2.5
#5 lore raw

PATCH RESEND 5/9 Platform: OLPC: add a suspended flag to the EC driver

In reply to Andres Salomon

A problem we've noticed on XO-1.75 is when we suspend in the middle of an EC command. Don't allow that.

In the process, create a private object for the generic EC driver to use; we have a framework for passing around a struct, use that rather than a proliferation of global variables.

---
 drivers/platform/olpc/olpc-ec.c |   46 ++++++++++++++++++++++++++++++++++++++-
 1 files changed, 45 insertions(+), 1 deletions(-)
drivers/platform/olpc/olpc-ec.c +45 -1
index d00523c..cfba41f 100644
@@ -9,6 +9,7 @@
 #include <linux/spinlock.h>
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
+#include <linux/slab.h>
 #include <linux/workqueue.h>
 #include <linux/module.h>
 #include <linux/list.h>
@@ -27,6 +28,21 @@ struct ec_cmd_desc {
 	void *priv;
 };
 
+struct olpc_ec_priv {
+	struct olpc_ec_driver *drv;
+
+	/*
+	 * Running an EC command while suspending means we don't always finish
+	 * the command before the machine suspends.  This means that the EC
+	 * is expecting the command protocol to finish, but we after a period
+	 * of time (while the OS is asleep) the EC times out and restarts its
+	 * idle loop.  Meanwhile, the OS wakes up, thinks it's still in the
+	 * middle of the command protocol, starts throwing random things at
+	 * the EC... and everyone's uphappy.
+	 */
+	bool suspended;
+};
+
 static void olpc_ec_worker(struct work_struct *w);
 
 static DECLARE_WORK(ec_worker, olpc_ec_worker);
@@ -34,6 +50,7 @@ static LIST_HEAD(ec_cmd_q);
 static DEFINE_SPINLOCK(ec_cmd_q_lock);
 
 static struct olpc_ec_driver *ec_driver;
+static struct olpc_ec_priv *ec_priv;
 static void *ec_cb_arg;
 static DEFINE_MUTEX(ec_cb_lock);
 
@@ -93,6 +110,7 @@ static void queue_ec_descriptor(struct ec_cmd_desc *desc)
 
 int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
 {
+	struct olpc_ec_priv *ec = ec_priv;
 	struct ec_cmd_desc desc;
 
 	/* XXX: this will be removed in later patches */
@@ -104,6 +122,13 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
 	if (WARN_ON(!ec_driver || !ec_driver->ec_cmd))
 		return -ENODEV;
 
+	if (!ec)
+		return -ENOMEM;
+
+	/* Suspending in the middle of a command hoses things really badly */
+	if (WARN_ON(ec->suspended))
+		return -EBUSY;
+
 	might_sleep();
 
 	desc.cmd = cmd;
@@ -126,11 +151,19 @@ EXPORT_SYMBOL_GPL(olpc_ec_cmd);
 
 static int olpc_ec_probe(struct platform_device *pdev)
 {
+	struct olpc_ec_priv *ec;
 	int err;
 
 	if (!ec_driver)
 		return -ENODEV;
 
+	ec = kzalloc(sizeof(*ec), GFP_KERNEL);
+	if (!ec)
+		return -ENOMEM;
+	ec->drv = ec_driver;
+	ec_priv = ec;
+	platform_set_drvdata(pdev, ec);
+
 	err = ec_driver->probe ? ec_driver->probe(pdev) : 0;
 
 	return err;
@@ -139,12 +172,23 @@ static int olpc_ec_probe(struct platform_device *pdev)
 static int olpc_ec_suspend(struct device *dev)
 {
 	struct platform_device *pdev = to_platform_device(dev);
-	return ec_driver->suspend ? ec_driver->suspend(pdev) : 0;
+	struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
+	int err = 0;
+
+	if (ec_driver->suspend)
+		err = ec_driver->suspend(pdev);
+	if (!err)
+		ec->suspended = true;
+
+	return err;
 }
 
 static int olpc_ec_resume(struct device *dev)
 {
 	struct platform_device *pdev = to_platform_device(dev);
+	struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
+
+	ec->suspended = false;
 	return ec_driver->resume ? ec_driver->resume(pdev) : 0;
 }
 
Signature · 1 line
1.7.2.5
#6 lore raw

PATCH RESEND 6/9 x86: OLPC: switch over to using new EC driver on x86

In reply to Andres Salomon

This uses the new EC driver framework in drivers/platform/olpc. The XO-1 and XO-1.5-specific code is still in arch/x86, but the generic stuff (including a new workqueue; no more running EC commands with IRQs disabled!) can be shared with other architectures.

---
 arch/x86/include/asm/olpc.h     |    5 ---
 arch/x86/platform/olpc/olpc.c   |   53 ++++++++++++++++++++-------------------
 drivers/platform/olpc/olpc-ec.c |    5 ---
 3 files changed, 27 insertions(+), 36 deletions(-)
arch/x86/include/asm/olpc.h +0 -5
index 5b28f3e..72f9adf6 100644
@@ -100,11 +100,6 @@ extern void olpc_xo1_pm_wakeup_clear(u16 value);
 
 extern int pci_olpc_init(void);
 
-/* EC related functions */
-
-extern int olpc_ec_cmd_x86(unsigned char cmd, unsigned char *inbuf,
-		size_t inlen, unsigned char *outbuf, size_t outlen);
-
 /* SCI source values */
 
 #define EC_SCI_SRC_EMPTY	0x00
arch/x86/platform/olpc/olpc.c +27 -26

120 lines not on this page · raw message

drivers/platform/olpc/olpc-ec.c +0 -5
index cfba41f..a3d32c2 100644
@@ -113,11 +113,6 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
 	struct olpc_ec_priv *ec = ec_priv;
 	struct ec_cmd_desc desc;
 
-	/* XXX: this will be removed in later patches */
-	/* Are we using old-style callers? */
-	if (!ec_driver || !ec_driver->ec_cmd)
-		return olpc_ec_cmd_x86(cmd, inbuf, inlen, outbuf, outlen);
-
 	/* Ensure a driver and ec hook have been registered */
 	if (WARN_ON(!ec_driver || !ec_driver->ec_cmd))
 		return -ENODEV;
Signature · 1 line
1.7.2.5
#7 lore raw

PATCH RESEND 7/9 Platform: OLPC: move debugfs support from x86 EC driver

In reply to Andres Salomon

There's nothing about the debugfs interface for the EC driver that is architecture-specific, so move it into the arch-independent driver.

The code is mostly unchanged with the exception of renamed variables, coding style changes, and API updates.

---
 arch/x86/platform/olpc/olpc.c   |   97 --------------------------------
 drivers/platform/olpc/olpc-ec.c |  117 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 117 insertions(+), 97 deletions(-)
arch/x86/platform/olpc/olpc.c +0 -97
index 4590096..ed41b43 100644
@@ -19,7 +19,6 @@
 #include <linux/platform_device.h>
 #include <linux/of.h>
 #include <linux/syscore_ops.h>
-#include <linux/debugfs.h>
 #include <linux/mutex.h>
 #include <linux/olpc-ec.h>
 
@@ -31,15 +30,6 @@
 struct olpc_platform_t olpc_platform_info;
 EXPORT_SYMBOL_GPL(olpc_platform_info);
 
-/* debugfs interface to EC commands */
-#define EC_MAX_CMD_ARGS (5 + 1)	/* cmd byte + 5 args */
-#define EC_MAX_CMD_REPLY (8)
-
-static struct dentry *ec_debugfs_dir;
-static DEFINE_MUTEX(ec_debugfs_cmd_lock);
-static unsigned char ec_debugfs_resp[EC_MAX_CMD_REPLY];
-static unsigned int ec_debugfs_resp_bytes;
-
 /* EC event mask to be applied during suspend (defining wakeup sources). */
 static u16 ec_wakeup_mask;
 
@@ -273,91 +263,6 @@ int olpc_ec_sci_query(u16 *sci_value)
 }
 EXPORT_SYMBOL_GPL(olpc_ec_sci_query);
 
-static ssize_t ec_debugfs_cmd_write(struct file *file, const char __user *buf,
-				    size_t size, loff_t *ppos)
-{
-	int i, m;
-	unsigned char ec_cmd[EC_MAX_CMD_ARGS];
-	unsigned int ec_cmd_int[EC_MAX_CMD_ARGS];
-	char cmdbuf[64];
-	int ec_cmd_bytes;
-
-	mutex_lock(&ec_debugfs_cmd_lock);
-
-	size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf), ppos, buf, size);
-
-	m = sscanf(cmdbuf, "%x:%u %x %x %x %x %x", &ec_cmd_int[0],
-		   &ec_debugfs_resp_bytes,
-		   &ec_cmd_int[1], &ec_cmd_int[2], &ec_cmd_int[3],
-		   &ec_cmd_int[4], &ec_cmd_int[5]);
-	if (m < 2 || ec_debugfs_resp_bytes > EC_MAX_CMD_REPLY) {
-		/* reset to prevent overflow on read */
-		ec_debugfs_resp_bytes = 0;
-
-		printk(KERN_DEBUG "olpc-ec: bad ec cmd:  "
-		       "cmd:response-count [arg1 [arg2 ...]]\n");
-		size = -EINVAL;
-		goto out;
-	}
-
-	/* convert scanf'd ints to char */
-	ec_cmd_bytes = m - 2;
-	for (i = 0; i <= ec_cmd_bytes; i++)
-		ec_cmd[i] = ec_cmd_int[i];
-
-	printk(KERN_DEBUG "olpc-ec: debugfs cmd 0x%02x with %d args "
-	       "%02x %02x %02x %02x %02x, want %d returns\n",
-	       ec_cmd[0], ec_cmd_bytes, ec_cmd[1], ec_cmd[2], ec_cmd[3],
-	       ec_cmd[4], ec_cmd[5], ec_debugfs_resp_bytes);
-
-	olpc_ec_cmd(ec_cmd[0], (ec_cmd_bytes == 0) ? NULL : &ec_cmd[1],
-		    ec_cmd_bytes, ec_debugfs_resp, ec_debugfs_resp_bytes);
-
-	printk(KERN_DEBUG "olpc-ec: response "
-	       "%02x %02x %02x %02x %02x %02x %02x %02x (%d bytes expected)\n",
-	       ec_debugfs_resp[0], ec_debugfs_resp[1], ec_debugfs_resp[2],
-	       ec_debugfs_resp[3], ec_debugfs_resp[4], ec_debugfs_resp[5],
-	       ec_debugfs_resp[6], ec_debugfs_resp[7], ec_debugfs_resp_bytes);
-
-out:
-	mutex_unlock(&ec_debugfs_cmd_lock);
-	return size;
-}
-
-static ssize_t ec_debugfs_cmd_read(struct file *file, char __user *buf,
-				   size_t size, loff_t *ppos)
-{
-	unsigned int i, r;
-	char *rp;
-	char respbuf[64];
-
-	mutex_lock(&ec_debugfs_cmd_lock);
-	rp = respbuf;
-	rp += sprintf(rp, "%02x", ec_debugfs_resp[0]);
-	for (i = 1; i < ec_debugfs_resp_bytes; i++)
-		rp += sprintf(rp, ", %02x", ec_debugfs_resp[i]);
-	mutex_unlock(&ec_debugfs_cmd_lock);
-	rp += sprintf(rp, "\n");
-
-	r = rp - respbuf;
-	return simple_read_from_buffer(buf, size, ppos, respbuf, r);
-}
-
-static const struct file_operations ec_debugfs_genops = {
-	.write	 = ec_debugfs_cmd_write,
-	.read	 = ec_debugfs_cmd_read,
-};
-
-static void setup_debugfs(void)
-{
-	ec_debugfs_dir = debugfs_create_dir("olpc-ec", 0);
-	if (ec_debugfs_dir == ERR_PTR(-ENODEV))
-		return;
-
-	debugfs_create_file("cmd", 0600, ec_debugfs_dir, NULL,
-			    &ec_debugfs_genops);
-}
-
 static int olpc_ec_suspend(struct platform_device *pdev)
 {
 	return olpc_ec_mask_write(ec_wakeup_mask);
@@ -470,8 +375,6 @@ static int __init olpc_init(void)
 			return r;
 	}
 
-	setup_debugfs();
-
 	return 0;
 }
 
drivers/platform/olpc/olpc-ec.c +117 -0

141 lines not on this page · raw message

Signature · 1 line
1.7.2.5
#8 lore raw

PATCH RESEND 8/9 Platform: OLPC: move global variables into priv struct

In reply to Andres Salomon

Populate olpc_ec_priv with variables that were previously global. This makes things a tad bit clearer, IMO.

---
 drivers/platform/olpc/olpc-ec.c |   48 ++++++++++++++++++++++----------------
 1 files changed, 28 insertions(+), 20 deletions(-)
drivers/platform/olpc/olpc-ec.c +28 -20
index 1a15a79..0f9f859 100644
@@ -31,6 +31,12 @@ struct ec_cmd_desc {
 
 struct olpc_ec_priv {
 	struct olpc_ec_driver *drv;
+	struct work_struct worker;
+	struct mutex cmd_lock;
+
+	/* Pending EC commands */
+	struct list_head cmd_q;
+	spinlock_t cmd_q_lock;
 
 	struct dentry *dbgfs_dir;
 
@@ -46,16 +52,9 @@ struct olpc_ec_priv {
 	bool suspended;
 };
 
-static void olpc_ec_worker(struct work_struct *w);
-
-static DECLARE_WORK(ec_worker, olpc_ec_worker);
-static LIST_HEAD(ec_cmd_q);
-static DEFINE_SPINLOCK(ec_cmd_q_lock);
-
 static struct olpc_ec_driver *ec_driver;
 static struct olpc_ec_priv *ec_priv;
 static void *ec_cb_arg;
-static DEFINE_MUTEX(ec_cb_lock);
 
 void olpc_ec_driver_register(struct olpc_ec_driver *drv, void *arg)
 {
@@ -66,49 +65,51 @@ EXPORT_SYMBOL_GPL(olpc_ec_driver_register);
 
 static void olpc_ec_worker(struct work_struct *w)
 {
+	struct olpc_ec_priv *ec = container_of(w, struct olpc_ec_priv, worker);
 	struct ec_cmd_desc *desc = NULL;
 	unsigned long flags;
 
 	/* Grab the first pending command from the queue */
-	spin_lock_irqsave(&ec_cmd_q_lock, flags);
-	if (!list_empty(&ec_cmd_q)) {
-		desc = list_first_entry(&ec_cmd_q, struct ec_cmd_desc, node);
+	spin_lock_irqsave(&ec->cmd_q_lock, flags);
+	if (!list_empty(&ec->cmd_q)) {
+		desc = list_first_entry(&ec->cmd_q, struct ec_cmd_desc, node);
 		list_del(&desc->node);
 	}
-	spin_unlock_irqrestore(&ec_cmd_q_lock, flags);
+	spin_unlock_irqrestore(&ec->cmd_q_lock, flags);
 
 	/* Do we actually have anything to do? */
 	if (!desc)
 		return;
 
 	/* Protect the EC hw with a mutex; only run one cmd at a time */
-	mutex_lock(&ec_cb_lock);
+	mutex_lock(&ec->cmd_lock);
 	desc->err = ec_driver->ec_cmd(desc->cmd, desc->inbuf, desc->inlen,
 			desc->outbuf, desc->outlen, ec_cb_arg);
-	mutex_unlock(&ec_cb_lock);
+	mutex_unlock(&ec->cmd_lock);
 
 	/* Finished, wake up olpc_ec_cmd() */
 	complete(&desc->finished);
 
 	/* Run the worker thread again in case there are more cmds pending */
-	schedule_work(&ec_worker);
+	schedule_work(&ec->worker);
 }
 
 /*
  * Throw a cmd descripter onto the list.  We now have SMP OLPC machines, so
  * locking is pretty critical.
  */
-static void queue_ec_descriptor(struct ec_cmd_desc *desc)
+static void queue_ec_descriptor(struct ec_cmd_desc *desc,
+		struct olpc_ec_priv *ec)
 {
 	unsigned long flags;
 
 	INIT_LIST_HEAD(&desc->node);
 
-	spin_lock_irqsave(&ec_cmd_q_lock, flags);
-	list_add_tail(&desc->node, &ec_cmd_q);
-	spin_unlock_irqrestore(&ec_cmd_q_lock, flags);
+	spin_lock_irqsave(&ec->cmd_q_lock, flags);
+	list_add_tail(&desc->node, &ec->cmd_q);
+	spin_unlock_irqrestore(&ec->cmd_q_lock, flags);
 
-	schedule_work(&ec_worker);
+	schedule_work(&ec->worker);
 }
 
 int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
@@ -137,7 +138,7 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
 	desc.err = 0;
 	init_completion(&desc.finished);
 
-	queue_ec_descriptor(&desc);
+	queue_ec_descriptor(&desc, ec);
 
 	/* Timeouts must be handled in the platform-specific EC hook */
 	wait_for_completion(&desc.finished);
@@ -266,7 +267,14 @@ static int olpc_ec_probe(struct platform_device *pdev)
 	ec = kzalloc(sizeof(*ec), GFP_KERNEL);
 	if (!ec)
 		return -ENOMEM;
+
 	ec->drv = ec_driver;
+	INIT_WORK(&ec->worker, olpc_ec_worker);
+	mutex_init(&ec->cmd_lock);
+
+	INIT_LIST_HEAD(&ec->cmd_q);
+	spin_lock_init(&ec->cmd_q_lock);
+
 	ec_priv = ec;
 	platform_set_drvdata(pdev, ec);
 
Signature · 1 line
1.7.2.5
#9 lore raw

PATCH RESEND 9/9 x86: OLPC: move s/r-related EC cmds to EC driver

In reply to Andres Salomon

The new EC driver calls platform-specific suspend and resume hooks; run XO-1-specific EC commands from there, rather than deep in s/r code. If we attempt to run EC commands after the new EC driver has suspended, it is refused by the ec->suspended checks.

---
 arch/x86/platform/olpc/olpc-xo1-pm.c |   15 ------------
 arch/x86/platform/olpc/olpc.c        |   43 ++++++++++++++++++++++++++++-----
 2 files changed, 36 insertions(+), 22 deletions(-)
arch/x86/platform/olpc/olpc-xo1-pm.c +0 -15
index 8054b64..d75582d 100644
@@ -52,16 +52,11 @@ EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear);
 static int xo1_power_state_enter(suspend_state_t pm_state)
 {
 	unsigned long saved_sci_mask;
-	int r;
 
 	/* Only STR is supported */
 	if (pm_state != PM_SUSPEND_MEM)
 		return -EINVAL;
 
-	r = olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0);
-	if (r)
-		return r;
-
 	/*
 	 * Save SCI mask (this gets lost since PM1_EN is used as a mask for
 	 * wakeup events, which is not necessarily the same event set)
@@ -77,16 +72,6 @@ static int xo1_power_state_enter(suspend_state_t pm_state)
 	/* Restore SCI mask (using dword access to CS5536_PM1_EN) */
 	outl(saved_sci_mask, acpi_base + CS5536_PM1_STS);
 
-	/* Tell the EC to stop inhibiting SCIs */
-	olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0);
-
-	/*
-	 * Tell the wireless module to restart USB communication.
-	 * Must be done twice.
-	 */
-	olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
-	olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
-
 	return 0;
 }
 
arch/x86/platform/olpc/olpc.c +36 -7

63 lines not on this page · raw message

Signature · 1 line
1.7.2.5
Reviewed-by on the v1 series
#10 lore raw
In reply to Andres Salomon
Quoted 20 lines · Andres Salomon

The OLPC EC (Embedded Controller) code that is currently upstream is x86-only, originally written for the XO-1. Since then, we've had the XO-1.5 (also x86), and XO-1.75 (arm-based) enter mass production. The 1.75 uses a vastly different EC protocol, and future hardware revisions are likely to change it even further.

However, the drivers do share quite a bit of code, so it makes sense to have a platform-agnostic driver that calls into platform-specific hooks for each XO's EC driver. This is the first stab and creating such a beast (with further patches pending). Aside from the lack of code duplication, this is helpful for fixing bugs in one place (for example, we fixed an EC suspend/resume bug in 1.75 that I've just seen happen on 1.5 without these patches. With these patches, the problem goes away).

These patches are against Linus's current HEAD; let me know if they don't apply somewhere, and I'll happily redo them against the -next tree. I'm assuming that these changes (which touch places like x86, wireless, and staging) should go through either the x86 tree, or through akpm's tree.

Quoted 3 lines

Alternatively, if the reviews are positive and I can get SOBs from the relevant maintainers, I can set up a platform-olpc tree somewhere and request a pull from Linus.

Either via akpm or a separate tree are fine with me.