1/* SPDX-License-Identifier: GPL-2.0-only */ 2/* 3 * i8042-loongsonio.h 4 * 5 * Copyright (C) 2020 Loongson Technology Corporation Limited 6 * Author: Jianmin Lv <lvjianmin@loongson.cn> 7 * Huacai Chen <chenhuacai@loongson.cn> 8 */ 9 10#ifndef _I8042_LOONGSONIO_H 11#define _I8042_LOONGSONIO_H 12 13/* 14 * Names. 15 */ 16 17#define I8042_KBD_PHYS_DESC "isa0060/serio0" 18#define I8042_AUX_PHYS_DESC "isa0060/serio1" 19#define I8042_MUX_PHYS_DESC "isa0060/serio%d" 20 21/* 22 * IRQs. 23 */ 24#define I8042_MAP_IRQ(x) (x) 25 26#define I8042_KBD_IRQ i8042_kbd_irq 27#define I8042_AUX_IRQ i8042_aux_irq 28 29static int i8042_kbd_irq; 30static int i8042_aux_irq; 31 32/* 33 * Register numbers. 34 */ 35 36#define I8042_COMMAND_REG i8042_command_reg 37#define I8042_STATUS_REG i8042_command_reg 38#define I8042_DATA_REG i8042_data_reg 39 40static int i8042_command_reg = 0x64; 41static int i8042_data_reg = 0x60; 42 43 44static inline int i8042_read_data(void) 45{ 46 return inb(I8042_DATA_REG); 47} 48 49static inline int i8042_read_status(void) 50{ 51 return inb(I8042_STATUS_REG); 52} 53 54static inline void i8042_write_data(int val) 55{ 56 outb(val, I8042_DATA_REG); 57} 58 59static inline void i8042_write_command(int val) 60{ 61 outb(val, I8042_COMMAND_REG); 62} 63 64#ifdef CONFIG_PNP 65#include <linux/pnp.h> 66 67static bool i8042_pnp_kbd_registered; 68static unsigned int i8042_pnp_kbd_devices; 69static bool i8042_pnp_aux_registered; 70static unsigned int i8042_pnp_aux_devices; 71 72static int i8042_pnp_command_reg; 73static int i8042_pnp_data_reg; 74static int i8042_pnp_kbd_irq; 75static int i8042_pnp_aux_irq; 76 77static char i8042_pnp_kbd_name[32]; 78static char i8042_pnp_aux_name[32]; 79 80static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size) 81{ 82 strlcpy(dst, "PNP:", dst_size); 83 84 while (id) { 85 strlcat(dst, " ", dst_size); 86 strlcat(dst, id->id, dst_size); 87 id = id->next; 88 } 89} 90 91static int i8042_pnp_kbd_probe(struct pnp_dev *dev, 92 const struct pnp_device_id *did) 93{ 94 if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) 95 i8042_pnp_data_reg = pnp_port_start(dev, 0); 96 97 if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1) 98 i8042_pnp_command_reg = pnp_port_start(dev, 1); 99 100 if (pnp_irq_valid(dev, 0)) 101 i8042_pnp_kbd_irq = pnp_irq(dev, 0); 102 103 strlcpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); 104 if (strlen(pnp_dev_name(dev))) { 105 strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); 106 strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev), 107 sizeof(i8042_pnp_kbd_name)); 108 } 109 i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id, 110 sizeof(i8042_kbd_firmware_id)); 111 112 /* Keyboard ports are always supposed to be wakeup-enabled */ 113 device_set_wakeup_enable(&dev->dev, true); 114 115 i8042_pnp_kbd_devices++; 116 return 0; 117} 118 119static int i8042_pnp_aux_probe(struct pnp_dev *dev, 120 const struct pnp_device_id *did) 121{ 122 if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) 123 i8042_pnp_data_reg = pnp_port_start(dev, 0); 124 125 if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1) 126 i8042_pnp_command_reg = pnp_port_start(dev, 1); 127 128 if (pnp_irq_valid(dev, 0)) 129 i8042_pnp_aux_irq = pnp_irq(dev, 0); 130 131 strlcpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); 132 if (strlen(pnp_dev_name(dev))) { 133 strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); 134 strlcat(i8042_pnp_aux_name, pnp_dev_name(dev), 135 sizeof(i8042_pnp_aux_name)); 136 } 137 i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id, 138 sizeof(i8042_aux_firmware_id)); 139 140 i8042_pnp_aux_devices++; 141 return 0; 142} 143 144static const struct pnp_device_id pnp_kbd_devids[] = { 145 { .id = "PNP0300", .driver_data = 0 }, 146 { .id = "PNP0301", .driver_data = 0 }, 147 { .id = "PNP0302", .driver_data = 0 }, 148 { .id = "PNP0303", .driver_data = 0 }, 149 { .id = "PNP0304", .driver_data = 0 }, 150 { .id = "PNP0305", .driver_data = 0 }, 151 { .id = "PNP0306", .driver_data = 0 }, 152 { .id = "PNP0309", .driver_data = 0 }, 153 { .id = "PNP030a", .driver_data = 0 }, 154 { .id = "PNP030b", .driver_data = 0 }, 155 { .id = "PNP0320", .driver_data = 0 }, 156 { .id = "PNP0343", .driver_data = 0 }, 157 { .id = "PNP0344", .driver_data = 0 }, 158 { .id = "PNP0345", .driver_data = 0 }, 159 { .id = "CPQA0D7", .driver_data = 0 }, 160 { .id = "", }, 161}; 162MODULE_DEVICE_TABLE(pnp, pnp_kbd_devids); 163 164static struct pnp_driver i8042_pnp_kbd_driver = { 165 .name = "i8042 kbd", 166 .id_table = pnp_kbd_devids, 167 .probe = i8042_pnp_kbd_probe, 168 .driver = { 169 .probe_type = PROBE_FORCE_SYNCHRONOUS, 170 .suppress_bind_attrs = true, 171 }, 172}; 173 174static const struct pnp_device_id pnp_aux_devids[] = { 175 { .id = "AUI0200", .driver_data = 0 }, 176 { .id = "FJC6000", .driver_data = 0 }, 177 { .id = "FJC6001", .driver_data = 0 }, 178 { .id = "PNP0f03", .driver_data = 0 }, 179 { .id = "PNP0f0b", .driver_data = 0 }, 180 { .id = "PNP0f0e", .driver_data = 0 }, 181 { .id = "PNP0f12", .driver_data = 0 }, 182 { .id = "PNP0f13", .driver_data = 0 }, 183 { .id = "PNP0f19", .driver_data = 0 }, 184 { .id = "PNP0f1c", .driver_data = 0 }, 185 { .id = "SYN0801", .driver_data = 0 }, 186 { .id = "", }, 187}; 188MODULE_DEVICE_TABLE(pnp, pnp_aux_devids); 189 190static struct pnp_driver i8042_pnp_aux_driver = { 191 .name = "i8042 aux", 192 .id_table = pnp_aux_devids, 193 .probe = i8042_pnp_aux_probe, 194 .driver = { 195 .probe_type = PROBE_FORCE_SYNCHRONOUS, 196 .suppress_bind_attrs = true, 197 }, 198}; 199 200static void i8042_pnp_exit(void) 201{ 202 if (i8042_pnp_kbd_registered) { 203 i8042_pnp_kbd_registered = false; 204 pnp_unregister_driver(&i8042_pnp_kbd_driver); 205 } 206 207 if (i8042_pnp_aux_registered) { 208 i8042_pnp_aux_registered = false; 209 pnp_unregister_driver(&i8042_pnp_aux_driver); 210 } 211} 212#ifdef CONFIG_ACPI 213#include <linux/acpi.h> 214#endif 215static int __init i8042_pnp_init(void) 216{ 217 char kbd_irq_str[4] = { 0 }, aux_irq_str[4] = { 0 }; 218 bool pnp_data_busted = false; 219 int err; 220 221 if (i8042_nopnp) { 222 pr_info("PNP detection disabled\n"); 223 return 0; 224 } 225 226 err = pnp_register_driver(&i8042_pnp_kbd_driver); 227 if (!err) 228 i8042_pnp_kbd_registered = true; 229 230 err = pnp_register_driver(&i8042_pnp_aux_driver); 231 if (!err) 232 i8042_pnp_aux_registered = true; 233 234 if (!i8042_pnp_kbd_devices && !i8042_pnp_aux_devices) { 235 i8042_pnp_exit(); 236 pr_info("PNP: No PS/2 controller found.\n"); 237#ifdef CONFIG_ACPI 238 if (acpi_disabled == 0) 239 return -ENODEV; 240#endif 241 pr_info("Probing ports directly.\n"); 242 return 0; 243 } 244 245 if (i8042_pnp_kbd_devices) 246 snprintf(kbd_irq_str, sizeof(kbd_irq_str), 247 "%d", i8042_pnp_kbd_irq); 248 if (i8042_pnp_aux_devices) 249 snprintf(aux_irq_str, sizeof(aux_irq_str), 250 "%d", i8042_pnp_aux_irq); 251 252 pr_info("PNP: PS/2 Controller [%s%s%s] at %#x,%#x irq %s%s%s\n", 253 i8042_pnp_kbd_name, 254 (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "", 255 i8042_pnp_aux_name, 256 i8042_pnp_data_reg, i8042_pnp_command_reg, 257 kbd_irq_str, 258 (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "", 259 aux_irq_str); 260 261 if (((i8042_pnp_data_reg & ~0xf) == (i8042_data_reg & ~0xf) && 262 i8042_pnp_data_reg != i8042_data_reg) || 263 !i8042_pnp_data_reg) { 264 pr_warn("PNP: PS/2 controller has invalid data port %#x; using default %#x\n", 265 i8042_pnp_data_reg, i8042_data_reg); 266 i8042_pnp_data_reg = i8042_data_reg; 267 pnp_data_busted = true; 268 } 269 270 if (((i8042_pnp_command_reg & ~0xf) == (i8042_command_reg & ~0xf) && 271 i8042_pnp_command_reg != i8042_command_reg) || 272 !i8042_pnp_command_reg) { 273 pr_warn("PNP: PS/2 controller has invalid command port %#x; using default %#x\n", 274 i8042_pnp_command_reg, i8042_command_reg); 275 i8042_pnp_command_reg = i8042_command_reg; 276 pnp_data_busted = true; 277 } 278 279 if (!i8042_nokbd && !i8042_pnp_kbd_irq) { 280 pr_warn("PNP: PS/2 controller doesn't have KBD irq; using default %d\n", 281 i8042_kbd_irq); 282 i8042_pnp_kbd_irq = i8042_kbd_irq; 283 pnp_data_busted = true; 284 } 285 286 if (!i8042_noaux && !i8042_pnp_aux_irq) { 287 if (!pnp_data_busted && i8042_pnp_kbd_irq) { 288 pr_warn("PNP: PS/2 appears to have AUX port disabled, " 289 "if this is incorrect please boot with i8042.nopnp\n"); 290 i8042_noaux = true; 291 } else { 292 pr_warn("PNP: PS/2 controller doesn't have AUX irq; using default %d\n", 293 i8042_aux_irq); 294 i8042_pnp_aux_irq = i8042_aux_irq; 295 } 296 } 297 298 i8042_data_reg = i8042_pnp_data_reg; 299 i8042_command_reg = i8042_pnp_command_reg; 300 i8042_kbd_irq = i8042_pnp_kbd_irq; 301 i8042_aux_irq = i8042_pnp_aux_irq; 302 303 return 0; 304} 305 306#else /* !CONFIG_PNP */ 307static inline int i8042_pnp_init(void) { return 0; } 308static inline void i8042_pnp_exit(void) { } 309#endif /* CONFIG_PNP */ 310 311static int __init i8042_platform_init(void) 312{ 313 int retval; 314 315 i8042_kbd_irq = I8042_MAP_IRQ(1); 316 i8042_aux_irq = I8042_MAP_IRQ(12); 317 318 retval = i8042_pnp_init(); 319 if (retval) 320 return retval; 321 322 return retval; 323} 324 325static inline void i8042_platform_exit(void) 326{ 327 i8042_pnp_exit(); 328} 329 330#endif /* _I8042_LOONGSONIO_H */ 331