Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | // 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) do { } while (0) #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; strscpy(ser_dev->name, "nvec mouse", sizeof(ser_dev->name)); strscpy(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 void 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); } #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_new = 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"); |