[PATCH 6/7] mips: loongson64: Support System Control Interrupts for Lemote Yeeloong.

Yifeng Li
2019-03-02T17:54:24Z

The hardware design of Yeeloong laptops is similar to OLPC: low-level
hardware events are processed by the ENE KB3310B Embedded Controller,
which is connected to the AMD CS5536 southbridge through a GPIO port.

When a hardware event occurs, such as a short-circuit on the USB port,
removing the power supply, plugging in a VGA adapter, opening the lid
of the laptop, or pressing a hotkey on the keyboard, the EC sends a
pulse to CS5536, which then fires a System Control Interrupt to notify
the kernel.

In the previous attempted submission, the logic for handling SCI and
hotkeys was tightly-coupled, and the driver was called the "hotkey"
driver, which was misleading. In this implementation of sci.c, we only
handle the minimum number of things that deal with the underlying
platform hardware directly, such as setting up the CS5536 GPIO and IRQ,
handling the the power supply of USB and camera. The vast majority of
events are passed to the subdrivers via a notification chain, thus,
the SCI logic and the specific hotkey handling logic have been decoupled.

Signed-off-by: Yifeng Li <tomli@tomli.me>
---
arch/mips/loongson64/lemote-2f/Makefile | 2 +-
arch/mips/loongson64/lemote-2f/sci.c | 392 ++++++++++++++++++++++++
2 files changed, 393 insertions(+), 1 deletion(-)
create mode 100644 arch/mips/loongson64/lemote-2f/sci.c

diff --git a/arch/mips/loongson64/lemote-2f/Makefile b/arch/mips/loongson64/lemote-2f/Makefile
index 2b18752424ee..af4a1d347884 100644
--- a/arch/mips/loongson64/lemote-2f/Makefile
+++ b/arch/mips/loongson64/lemote-2f/Makefile
@@ -2,7 +2,7 @@
# Makefile for lemote loongson2f family machines
#

-obj-y += clock.o machtype.o irq.o reset.o dma.o platform.o
+obj-y += clock.o machtype.o irq.o reset.o dma.o platform.o sci.o

#
# Suspend Support
diff --git a/arch/mips/loongson64/lemote-2f/sci.c b/arch/mips/loongson64/lemote-2f/sci.c
new file mode 100644
index 000000000000..96e376f00ecc
--- /dev/null
+++ b/arch/mips/loongson64/lemote-2f/sci.c
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Support for Lemote Yeeloong System Control Interrupts (SCI)
+ *
+ * Copyright (C) 2009 Lemote Inc.
+ * Author: Wu Zhangjin <wuzhangjin@gmail.com>
+ * Liu Junliang <liujl@lemote.com>
+ *
+ * Copyright (C) 2012, 2013, 2014, 2015 Petr Pisar
+ * Author: Petr Pisar <petr.pisar@atlas.cz> (bugfixes)
+ *
+ * Copyright (C) 2017 Jiaxun Yang
+ * Author: Jiaxun Yang <jiaxun.yang@flygoat.com>
+ *
+ * Copyright (C) 2019 Yifeng Li
+ * Author: Yifeng Li <tomli@tomli.me>
+ *
+ * The hardware design of Yeeloong laptops is similar to OLPC: low-level
+ * hardware events are processed by the ENE KB3310B Embedded Controller,
+ * which is connected to the AMD CS5536 southbridge through a GPIO port.
+ *
+ * When a hardware event occurs, such as a short-circuit on the USB port,
+ * removing the power supply, plugging in a VGA adapter, opening the lid
+ * of the laptop, or pressing a hotkey on the keyboard, the EC sends a
+ * pulse to CS5536, which then fires a System Control Interrupt to notify
+ * the kernel.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/power_supply.h>
+#include <linux/backlight.h>
+#include <linux/mfd/yeeloong_kb3310b.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/suspend.h>
+
+#include <loongson.h>
+#include <cs5536/cs5536.h>
+
+static void usb_power_set(bool power)
+{
+ kb3310b_write(KB3310B_REG_USB0_FLAG, power);
+ kb3310b_write(KB3310B_REG_USB1_FLAG, power);
+ kb3310b_write(KB3310B_REG_USB2_FLAG, power);
+}
+
+static void camera_power_set(bool power)
+{
+ kb3310b_write(KB3310B_REG_CAMERA_CONTROL, power);
+}
+
+/*
+ * Handlers for EC events. We only handles the bare-minimum number of events
+ * directly related to the platform hardware. Other events are reported to
+ * subdrivers and handled by them.
+ */
+static void usb0_handler(void)
+{
+ pr_emerg("USB0 Overcurrent occurred!\n");
+}
+
+static void usb2_handler(void)
+{
+ pr_emerg("USB2 Overcurrent occurred!\n");
+}
+
+static void camera_handler(void)
+{
+ camera_power_set(!kb3310b_read(KB3310B_REG_CAMERA_CONTROL));
+}
+
+/*
+ * SCI notifiers. This notifier reports EC events to various
+ * subdrivers.
+ */
+static BLOCKING_NOTIFIER_HEAD(yeeloong_sci_notifier_list);
+
+int yeeloong_sci_register_notify(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&yeeloong_sci_notifier_list,
+ nb);
+}
+EXPORT_SYMBOL_GPL(yeeloong_sci_register_notify);
+
+int yeeloong_sci_unregister_notify(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&yeeloong_sci_notifier_list,
+ nb);
+}
+EXPORT_SYMBOL_GPL(yeeloong_sci_unregister_notify);
+
+/*
+ * Do not handle or notify other drivers about certain events while we're
+ * going down to reboot or sleep. This avoids unexpected behaviors in the
+ * userspace, such as closing the laptop lid while rebooting.
+ */
+static atomic_t reboot_flag;
+static atomic_t sleep_flag;
+
+static int
+notify_reboot(struct notifier_block *nb, unsigned long event, void *buf)
+{
+ switch (event) {
+ case SYS_RESTART:
+ case SYS_HALT:
+ case SYS_POWER_OFF:
+ atomic_set(&reboot_flag, 1);
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int
+notify_pm(struct notifier_block *nb, unsigned long event, void *buf)
+{
+ switch (event) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ atomic_inc(&sleep_flag);
+ break;
+ case PM_POST_HIBERNATION:
+ case PM_POST_SUSPEND:
+ case PM_RESTORE_PREPARE: /* do we need this ?? */
+ atomic_dec(&sleep_flag);
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block reboot_notifier = {
+ .notifier_call = notify_reboot,
+};
+
+static struct notifier_block pm_notifier = {
+ .notifier_call = notify_pm,
+};
+
+static bool is_spurious_event(int event)
+{
+ if (event == KB3310B_EVENT_LID || event == KB3310B_EVENT_SLEEP)
+ return !!(atomic_read(&reboot_flag) | atomic_read(&sleep_flag));
+ else
+ return false;
+}
+
+/*
+ * Invoke the handler of the reported SCI event.
+ */
+static void process_sci_event(int event)
+{
+ if (is_spurious_event(event))
+ return;
+
+ switch (event) {
+ case KB3310B_EVENT_USB_OC0:
+ usb0_handler();
+ break;
+ case KB3310B_EVENT_USB_OC2:
+ usb2_handler();
+ break;
+ case KB3310B_EVENT_CAMERA:
+ camera_handler();
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Report this event to other subdrivers, in particular, yeeloong-hotkey
+ * driver will report the hotkey to userspace.
+ */
+ blocking_notifier_call_chain(&yeeloong_sci_notifier_list, event, NULL);
+}
+
+/*
+ * Handle the SCI event and perform the needed action.
+ *
+ * A SCI event lasts about 120 microseconds. It means the function must take
+ * longer than 120 microseconds to complete. It has been shown that the function
+ * already takes 3 ms, so no artificial delay is needed.
+ */
+static irqreturn_t sci_irq_handler(int irq, void *dev_id)
+{
+ int ret, event;
+
+ if (irq != KB3310B_SCI_IRQ_NUM)
+ return IRQ_NONE;
+
+ /* query the event number */
+ ret = kb3310b_query_event_num();
+ if (ret < 0)
+ return IRQ_NONE;
+
+ event = kb3310b_get_event_num();
+ if (event < KB3310B_EVENT_START || event > KB3310B_EVENT_END)
+ return IRQ_NONE;
+
+ /* execute the corresponding action */
+ process_sci_event(event);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Program the GPIO and MSR registers on CS5536.
+ *
+ * TODO: Linux kernel already has a cs5535-gpio kernel driver, but that
+ * driver not adapted for Loongson. It's desirable to convert these
+ * raw operations to use cs5535-gpio. Also, Loongson has its own
+ * CS5536 clocksource driver, though Linux already has cs5535-clockevt,
+ * but only supports clockevent, not clocksource. Clocksource code
+ * should be merged into cs5535-clockevt and Loongson should use
+ * that, too.
+ */
+static int setup_ec_sci(void)
+{
+ u32 hi, lo;
+ u32 gpio_base;
+ unsigned long flags;
+ int ret;
+
+ /* Get GPIO base */
+ _rdmsr(DIVIL_MSR_REG(DIVIL_LBAR_GPIO), &hi, &lo);
+ gpio_base = lo & 0xff00;
+
+ /*
+ * HACK: to prevent any interrupts from being fired, we query
+ * the EC to clear the pending event, and wait for a while to
+ * miss the interrupt intentionally.
+ */
+ ret = kb3310b_query_event_num();
+ if (ret)
+ return ret;
+
+ /* wait for a while */
+ mdelay(10);
+
+ /*
+ * Set GPIO native registers and MSRs for GPIO-27 SCI EVENT PIN
+ *
+ * MSR:
+ * no primary and LPC.
+ * Unrestricted Z Input 8 to IG-10 from Virtual GPIO-0.
+ *
+ * GPIO mode:
+ * input, pull-up, no-invert, event-count and value 0,
+ * no-filter, no-edge. GPIO-27 map to Virtual GPIO-0.
+ */
+ local_irq_save(flags);
+
+ /* set primary mask */
+ _rdmsr(0x80000024, &hi, &lo);
+ lo &= ~(1 << 10);
+ _wrmsr(0x80000024, hi, lo);
+
+ /* set LPC mask */
+ _rdmsr(0x80000025, &hi, &lo);
+ lo &= ~(1 << 10);
+ _wrmsr(0x80000025, hi, lo);
+
+ /* set Unrestricted Z map */
+ _rdmsr(0x80000023, &hi, &lo);
+ lo |= (0x0a << 0);
+ _wrmsr(0x80000023, hi, lo);
+
+ local_irq_restore(flags);
+
+ asm(".set noreorder\n");
+ outl(0x00000800, (gpio_base | 0xA0)); /* GPIO-27 input enable */
+ outl(0x00000800, (gpio_base | 0xA4)); /* GPIO-27 input invert */
+ outl(0x00000800, (gpio_base | 0xB8)); /* GPIO-27 event-int enable */
+ asm(".set reorder\n");
+
+ return 0;
+}
+
+/*
+ * Setup the IRQ handler for SCI. Must be called after setup_ec_sci().
+ */
+static int setup_sci_interrupt(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = request_threaded_irq(KB3310B_SCI_IRQ_NUM, NULL, &sci_irq_handler,
+ IRQF_ONESHOT, "sci", NULL);
+ if (ret)
+ dev_err(&pdev->dev, "unable to request interrupt!\n");
+
+ return ret;
+}
+
+static int yeeloong_sci_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ camera_power_set(KB3310B_BIT_CAMERA_CONTROL_OFF);
+ usb_power_set(KB3310B_BIT_USB_FLAG_ON);
+
+ ret = register_reboot_notifier(&reboot_notifier);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to register reboot_notifier!\n");
+ goto fail;
+ }
+
+ ret = register_pm_notifier(&pm_notifier);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to register pm_notifier!\n");
+ goto fail_reboot;
+ }
+
+ ret = setup_ec_sci();
+ if (ret) {
+ dev_err(&pdev->dev, "unable to setup EC SCI!\n");
+ goto fail_irq;
+ }
+
+ ret = setup_sci_interrupt(pdev);
+ if (ret)
+ goto fail_irq;
+
+ return ret;
+
+fail_irq:
+ free_irq(KB3310B_SCI_IRQ_NUM, NULL);
+ unregister_pm_notifier(&pm_notifier);
+fail_reboot:
+ unregister_reboot_notifier(&reboot_notifier);
+fail:
+ return ret;
+}
+
+#ifdef CONFIG_SUSPEND
+static int
+yeeloong_sci_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ usb_power_set(KB3310B_BIT_USB_FLAG_OFF);
+ camera_power_set(KB3310B_BIT_CAMERA_CONTROL_OFF);
+ return 0;
+}
+
+static int yeeloong_sci_resume(struct platform_device *pdev)
+{
+ int ret;
+
+ usb_power_set(KB3310B_BIT_USB_FLAG_ON);
+
+ ret = setup_ec_sci();
+ if (ret) {
+ dev_err(&pdev->dev, "unable to setup EC SCI!\n");
+ return -EFAULT;
+ }
+
+ /*
+ * Lid switch and power supply may hawe changed while we were
+ * asleep, so we generate a KB3310B_EVENT_LID and KB3310B_EVENT_AC_BAT
+ * events to force the hotkey/battery subdriver to report their new
+ * states.
+ */
+ blocking_notifier_call_chain(&yeeloong_sci_notifier_list,
+ KB3310B_EVENT_LID, NULL);
+ blocking_notifier_call_chain(&yeeloong_sci_notifier_list,
+ KB3310B_EVENT_AC_BAT, NULL);
+
+ return 0;
+}
+#endif
+
+static struct platform_driver yeeloong_sci_driver = {
+ .driver = {
+ .name = "yeeloong_sci",
+ },
+ .probe = yeeloong_sci_probe,
+#ifdef CONFIG_SUSPEND
+ .suspend = yeeloong_sci_suspend,
+ .resume = yeeloong_sci_resume,
+#endif
+};
+
+static int __init yeeloong_sci_init(void)
+{
+ return platform_driver_register(&yeeloong_sci_driver);
+}
+arch_initcall(yeeloong_sci_init);
--
2.20.1