| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * nvec_ps2: mouse driver for a NVIDIA compliant embedded controller |
| * |
| * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> |
| * |
| * Authors: Pierre-Hugues Husson <phhusson@free.fr> |
| * Ilya Petrov <ilya.muromec@gmail.com> |
| * Marc Dietrich <marvin24@gmx.de> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/serio.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| |
| #include "nvec.h" |
| |
| #define PACKET_SIZE 6 |
| |
| #define ENABLE_MOUSE 0xf4 |
| #define DISABLE_MOUSE 0xf5 |
| #define PSMOUSE_RST 0xff |
| |
| #ifdef NVEC_PS2_DEBUG |
| #define NVEC_PHD(str, buf, len) \ |
| print_hex_dump(KERN_DEBUG, str, DUMP_PREFIX_NONE, \ |
| 16, 1, buf, len, false) |
| #else |
| #define NVEC_PHD(str, buf, len) |
| #endif |
| |
| enum ps2_subcmds { |
| SEND_COMMAND = 1, |
| RECEIVE_N, |
| AUTO_RECEIVE_N, |
| CANCEL_AUTO_RECEIVE, |
| }; |
| |
| struct nvec_ps2 { |
| struct serio *ser_dev; |
| struct notifier_block notifier; |
| struct nvec_chip *nvec; |
| }; |
| |
| static struct nvec_ps2 ps2_dev; |
| |
| static int ps2_startstreaming(struct serio *ser_dev) |
| { |
| unsigned char buf[] = { NVEC_PS2, AUTO_RECEIVE_N, PACKET_SIZE }; |
| |
| return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); |
| } |
| |
| static void ps2_stopstreaming(struct serio *ser_dev) |
| { |
| unsigned char buf[] = { NVEC_PS2, CANCEL_AUTO_RECEIVE }; |
| |
| nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); |
| } |
| |
| static int ps2_sendcommand(struct serio *ser_dev, unsigned char cmd) |
| { |
| unsigned char buf[] = { NVEC_PS2, SEND_COMMAND, ENABLE_MOUSE, 1 }; |
| |
| buf[2] = cmd & 0xff; |
| |
| dev_dbg(&ser_dev->dev, "Sending ps2 cmd %02x\n", cmd); |
| return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); |
| } |
| |
| static int nvec_ps2_notifier(struct notifier_block *nb, |
| unsigned long event_type, void *data) |
| { |
| int i; |
| unsigned char *msg = data; |
| |
| switch (event_type) { |
| case NVEC_PS2_EVT: |
| for (i = 0; i < msg[1]; i++) |
| serio_interrupt(ps2_dev.ser_dev, msg[2 + i], 0); |
| NVEC_PHD("ps/2 mouse event: ", &msg[2], msg[1]); |
| return NOTIFY_STOP; |
| |
| case NVEC_PS2: |
| if (msg[2] == 1) { |
| for (i = 0; i < (msg[1] - 2); i++) |
| serio_interrupt(ps2_dev.ser_dev, msg[i + 4], 0); |
| NVEC_PHD("ps/2 mouse reply: ", &msg[4], msg[1] - 2); |
| } |
| |
| else if (msg[1] != 2) /* !ack */ |
| NVEC_PHD("unhandled mouse event: ", msg, msg[1] + 2); |
| return NOTIFY_STOP; |
| } |
| |
| return NOTIFY_DONE; |
| } |
| |
| static int nvec_mouse_probe(struct platform_device *pdev) |
| { |
| struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); |
| struct serio *ser_dev; |
| |
| ser_dev = kzalloc(sizeof(*ser_dev), GFP_KERNEL); |
| if (!ser_dev) |
| return -ENOMEM; |
| |
| ser_dev->id.type = SERIO_8042; |
| ser_dev->write = ps2_sendcommand; |
| ser_dev->start = ps2_startstreaming; |
| ser_dev->stop = ps2_stopstreaming; |
| |
| strlcpy(ser_dev->name, "nvec mouse", sizeof(ser_dev->name)); |
| strlcpy(ser_dev->phys, "nvec", sizeof(ser_dev->phys)); |
| |
| ps2_dev.ser_dev = ser_dev; |
| ps2_dev.notifier.notifier_call = nvec_ps2_notifier; |
| ps2_dev.nvec = nvec; |
| nvec_register_notifier(nvec, &ps2_dev.notifier, 0); |
| |
| serio_register_port(ser_dev); |
| |
| return 0; |
| } |
| |
| static int nvec_mouse_remove(struct platform_device *pdev) |
| { |
| struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); |
| |
| ps2_sendcommand(ps2_dev.ser_dev, DISABLE_MOUSE); |
| ps2_stopstreaming(ps2_dev.ser_dev); |
| nvec_unregister_notifier(nvec, &ps2_dev.notifier); |
| serio_unregister_port(ps2_dev.ser_dev); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int nvec_mouse_suspend(struct device *dev) |
| { |
| /* disable mouse */ |
| ps2_sendcommand(ps2_dev.ser_dev, DISABLE_MOUSE); |
| |
| /* send cancel autoreceive */ |
| ps2_stopstreaming(ps2_dev.ser_dev); |
| |
| return 0; |
| } |
| |
| static int nvec_mouse_resume(struct device *dev) |
| { |
| /* start streaming */ |
| ps2_startstreaming(ps2_dev.ser_dev); |
| |
| /* enable mouse */ |
| ps2_sendcommand(ps2_dev.ser_dev, ENABLE_MOUSE); |
| |
| return 0; |
| } |
| #endif |
| |
| static SIMPLE_DEV_PM_OPS(nvec_mouse_pm_ops, nvec_mouse_suspend, |
| nvec_mouse_resume); |
| |
| static struct platform_driver nvec_mouse_driver = { |
| .probe = nvec_mouse_probe, |
| .remove = nvec_mouse_remove, |
| .driver = { |
| .name = "nvec-mouse", |
| .pm = &nvec_mouse_pm_ops, |
| }, |
| }; |
| |
| module_platform_driver(nvec_mouse_driver); |
| |
| MODULE_DESCRIPTION("NVEC mouse driver"); |
| MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); |
| MODULE_ALIAS("platform:nvec-mouse"); |
| MODULE_LICENSE("GPL"); |