18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * SGI IOC3 PS/2 controller driver for linux 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2019 Thomas Bogendoerfer <tbogendoerfer@suse.de> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Based on code Copyright (C) 2005 Stanislaw Skowronek <skylark@unaligned.org> 88c2ecf20Sopenharmony_ci * Copyright (C) 2009 Johannes Dickgreber <tanzy@gmx.de> 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/delay.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/serio.h> 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <asm/sn/ioc3.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistruct ioc3kbd_data { 218c2ecf20Sopenharmony_ci struct ioc3_serioregs __iomem *regs; 228c2ecf20Sopenharmony_ci struct serio *kbd, *aux; 238c2ecf20Sopenharmony_ci bool kbd_exists, aux_exists; 248c2ecf20Sopenharmony_ci int irq; 258c2ecf20Sopenharmony_ci}; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic int ioc3kbd_wait(struct ioc3_serioregs __iomem *regs, u32 mask) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci unsigned long timeout = 0; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci while ((readl(®s->km_csr) & mask) && (timeout < 250)) { 328c2ecf20Sopenharmony_ci udelay(50); 338c2ecf20Sopenharmony_ci timeout++; 348c2ecf20Sopenharmony_ci } 358c2ecf20Sopenharmony_ci return (timeout >= 250) ? -ETIMEDOUT : 0; 368c2ecf20Sopenharmony_ci} 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic int ioc3kbd_write(struct serio *dev, u8 val) 398c2ecf20Sopenharmony_ci{ 408c2ecf20Sopenharmony_ci struct ioc3kbd_data *d = dev->port_data; 418c2ecf20Sopenharmony_ci int ret; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci ret = ioc3kbd_wait(d->regs, KM_CSR_K_WRT_PEND); 448c2ecf20Sopenharmony_ci if (ret) 458c2ecf20Sopenharmony_ci return ret; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci writel(val, &d->regs->k_wd); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return 0; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int ioc3kbd_start(struct serio *dev) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct ioc3kbd_data *d = dev->port_data; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci d->kbd_exists = true; 578c2ecf20Sopenharmony_ci return 0; 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic void ioc3kbd_stop(struct serio *dev) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct ioc3kbd_data *d = dev->port_data; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci d->kbd_exists = false; 658c2ecf20Sopenharmony_ci} 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic int ioc3aux_write(struct serio *dev, u8 val) 688c2ecf20Sopenharmony_ci{ 698c2ecf20Sopenharmony_ci struct ioc3kbd_data *d = dev->port_data; 708c2ecf20Sopenharmony_ci int ret; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci ret = ioc3kbd_wait(d->regs, KM_CSR_M_WRT_PEND); 738c2ecf20Sopenharmony_ci if (ret) 748c2ecf20Sopenharmony_ci return ret; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci writel(val, &d->regs->m_wd); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci return 0; 798c2ecf20Sopenharmony_ci} 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic int ioc3aux_start(struct serio *dev) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci struct ioc3kbd_data *d = dev->port_data; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci d->aux_exists = true; 868c2ecf20Sopenharmony_ci return 0; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic void ioc3aux_stop(struct serio *dev) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci struct ioc3kbd_data *d = dev->port_data; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci d->aux_exists = false; 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic void ioc3kbd_process_data(struct serio *dev, u32 data) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci if (data & KM_RD_VALID_0) 998c2ecf20Sopenharmony_ci serio_interrupt(dev, (data >> KM_RD_DATA_0_SHIFT) & 0xff, 0); 1008c2ecf20Sopenharmony_ci if (data & KM_RD_VALID_1) 1018c2ecf20Sopenharmony_ci serio_interrupt(dev, (data >> KM_RD_DATA_1_SHIFT) & 0xff, 0); 1028c2ecf20Sopenharmony_ci if (data & KM_RD_VALID_2) 1038c2ecf20Sopenharmony_ci serio_interrupt(dev, (data >> KM_RD_DATA_2_SHIFT) & 0xff, 0); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic irqreturn_t ioc3kbd_intr(int itq, void *dev_id) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct ioc3kbd_data *d = dev_id; 1098c2ecf20Sopenharmony_ci u32 data_k, data_m; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci data_k = readl(&d->regs->k_rd); 1128c2ecf20Sopenharmony_ci if (d->kbd_exists) 1138c2ecf20Sopenharmony_ci ioc3kbd_process_data(d->kbd, data_k); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci data_m = readl(&d->regs->m_rd); 1168c2ecf20Sopenharmony_ci if (d->aux_exists) 1178c2ecf20Sopenharmony_ci ioc3kbd_process_data(d->aux, data_m); 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci return IRQ_HANDLED; 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic int ioc3kbd_probe(struct platform_device *pdev) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci struct ioc3_serioregs __iomem *regs; 1258c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1268c2ecf20Sopenharmony_ci struct ioc3kbd_data *d; 1278c2ecf20Sopenharmony_ci struct serio *sk, *sa; 1288c2ecf20Sopenharmony_ci int irq, ret; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci regs = devm_platform_ioremap_resource(pdev, 0); 1318c2ecf20Sopenharmony_ci if (IS_ERR(regs)) 1328c2ecf20Sopenharmony_ci return PTR_ERR(regs); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 1358c2ecf20Sopenharmony_ci if (irq < 0) 1368c2ecf20Sopenharmony_ci return -ENXIO; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL); 1398c2ecf20Sopenharmony_ci if (!d) 1408c2ecf20Sopenharmony_ci return -ENOMEM; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci sk = kzalloc(sizeof(*sk), GFP_KERNEL); 1438c2ecf20Sopenharmony_ci if (!sk) 1448c2ecf20Sopenharmony_ci return -ENOMEM; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci sa = kzalloc(sizeof(*sa), GFP_KERNEL); 1478c2ecf20Sopenharmony_ci if (!sa) { 1488c2ecf20Sopenharmony_ci kfree(sk); 1498c2ecf20Sopenharmony_ci return -ENOMEM; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci sk->id.type = SERIO_8042; 1538c2ecf20Sopenharmony_ci sk->write = ioc3kbd_write; 1548c2ecf20Sopenharmony_ci sk->start = ioc3kbd_start; 1558c2ecf20Sopenharmony_ci sk->stop = ioc3kbd_stop; 1568c2ecf20Sopenharmony_ci snprintf(sk->name, sizeof(sk->name), "IOC3 keyboard %d", pdev->id); 1578c2ecf20Sopenharmony_ci snprintf(sk->phys, sizeof(sk->phys), "ioc3/serio%dkbd", pdev->id); 1588c2ecf20Sopenharmony_ci sk->port_data = d; 1598c2ecf20Sopenharmony_ci sk->dev.parent = dev; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci sa->id.type = SERIO_8042; 1628c2ecf20Sopenharmony_ci sa->write = ioc3aux_write; 1638c2ecf20Sopenharmony_ci sa->start = ioc3aux_start; 1648c2ecf20Sopenharmony_ci sa->stop = ioc3aux_stop; 1658c2ecf20Sopenharmony_ci snprintf(sa->name, sizeof(sa->name), "IOC3 auxiliary %d", pdev->id); 1668c2ecf20Sopenharmony_ci snprintf(sa->phys, sizeof(sa->phys), "ioc3/serio%daux", pdev->id); 1678c2ecf20Sopenharmony_ci sa->port_data = d; 1688c2ecf20Sopenharmony_ci sa->dev.parent = dev; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci d->regs = regs; 1718c2ecf20Sopenharmony_ci d->kbd = sk; 1728c2ecf20Sopenharmony_ci d->aux = sa; 1738c2ecf20Sopenharmony_ci d->irq = irq; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, d); 1768c2ecf20Sopenharmony_ci serio_register_port(d->kbd); 1778c2ecf20Sopenharmony_ci serio_register_port(d->aux); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci ret = request_irq(irq, ioc3kbd_intr, IRQF_SHARED, "ioc3-kbd", d); 1808c2ecf20Sopenharmony_ci if (ret) { 1818c2ecf20Sopenharmony_ci dev_err(dev, "could not request IRQ %d\n", irq); 1828c2ecf20Sopenharmony_ci serio_unregister_port(d->kbd); 1838c2ecf20Sopenharmony_ci serio_unregister_port(d->aux); 1848c2ecf20Sopenharmony_ci return ret; 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci /* enable ports */ 1888c2ecf20Sopenharmony_ci writel(KM_CSR_K_CLAMP_3 | KM_CSR_M_CLAMP_3, ®s->km_csr); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci return 0; 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cistatic int ioc3kbd_remove(struct platform_device *pdev) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci struct ioc3kbd_data *d = platform_get_drvdata(pdev); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci free_irq(d->irq, d); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci serio_unregister_port(d->kbd); 2008c2ecf20Sopenharmony_ci serio_unregister_port(d->aux); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci return 0; 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic struct platform_driver ioc3kbd_driver = { 2068c2ecf20Sopenharmony_ci .probe = ioc3kbd_probe, 2078c2ecf20Sopenharmony_ci .remove = ioc3kbd_remove, 2088c2ecf20Sopenharmony_ci .driver = { 2098c2ecf20Sopenharmony_ci .name = "ioc3-kbd", 2108c2ecf20Sopenharmony_ci }, 2118c2ecf20Sopenharmony_ci}; 2128c2ecf20Sopenharmony_cimodule_platform_driver(ioc3kbd_driver); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ciMODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfer@suse.de>"); 2158c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SGI IOC3 serio driver"); 2168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 217