1273db8f0SThomas Bogendoerfer // SPDX-License-Identifier: GPL-2.0 2273db8f0SThomas Bogendoerfer /* 3273db8f0SThomas Bogendoerfer * SGI IOC3 PS/2 controller driver for linux 4273db8f0SThomas Bogendoerfer * 5273db8f0SThomas Bogendoerfer * Copyright (C) 2019 Thomas Bogendoerfer <tbogendoerfer@suse.de> 6273db8f0SThomas Bogendoerfer * 7273db8f0SThomas Bogendoerfer * Based on code Copyright (C) 2005 Stanislaw Skowronek <skylark@unaligned.org> 8273db8f0SThomas Bogendoerfer * Copyright (C) 2009 Johannes Dickgreber <tanzy@gmx.de> 9273db8f0SThomas Bogendoerfer */ 10273db8f0SThomas Bogendoerfer 11273db8f0SThomas Bogendoerfer #include <linux/delay.h> 12273db8f0SThomas Bogendoerfer #include <linux/init.h> 13273db8f0SThomas Bogendoerfer #include <linux/io.h> 14273db8f0SThomas Bogendoerfer #include <linux/serio.h> 15273db8f0SThomas Bogendoerfer #include <linux/module.h> 16273db8f0SThomas Bogendoerfer #include <linux/platform_device.h> 17273db8f0SThomas Bogendoerfer 18273db8f0SThomas Bogendoerfer #include <asm/sn/ioc3.h> 19273db8f0SThomas Bogendoerfer 20273db8f0SThomas Bogendoerfer struct ioc3kbd_data { 21273db8f0SThomas Bogendoerfer struct ioc3_serioregs __iomem *regs; 22273db8f0SThomas Bogendoerfer struct serio *kbd, *aux; 23273db8f0SThomas Bogendoerfer bool kbd_exists, aux_exists; 24273db8f0SThomas Bogendoerfer int irq; 25273db8f0SThomas Bogendoerfer }; 26273db8f0SThomas Bogendoerfer 27273db8f0SThomas Bogendoerfer static int ioc3kbd_wait(struct ioc3_serioregs __iomem *regs, u32 mask) 28273db8f0SThomas Bogendoerfer { 29273db8f0SThomas Bogendoerfer unsigned long timeout = 0; 30273db8f0SThomas Bogendoerfer 31273db8f0SThomas Bogendoerfer while ((readl(®s->km_csr) & mask) && (timeout < 250)) { 32273db8f0SThomas Bogendoerfer udelay(50); 33273db8f0SThomas Bogendoerfer timeout++; 34273db8f0SThomas Bogendoerfer } 35273db8f0SThomas Bogendoerfer return (timeout >= 250) ? -ETIMEDOUT : 0; 36273db8f0SThomas Bogendoerfer } 37273db8f0SThomas Bogendoerfer 38273db8f0SThomas Bogendoerfer static int ioc3kbd_write(struct serio *dev, u8 val) 39273db8f0SThomas Bogendoerfer { 40273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d = dev->port_data; 41273db8f0SThomas Bogendoerfer int ret; 42273db8f0SThomas Bogendoerfer 43273db8f0SThomas Bogendoerfer ret = ioc3kbd_wait(d->regs, KM_CSR_K_WRT_PEND); 44273db8f0SThomas Bogendoerfer if (ret) 45273db8f0SThomas Bogendoerfer return ret; 46273db8f0SThomas Bogendoerfer 47273db8f0SThomas Bogendoerfer writel(val, &d->regs->k_wd); 48273db8f0SThomas Bogendoerfer 49273db8f0SThomas Bogendoerfer return 0; 50273db8f0SThomas Bogendoerfer } 51273db8f0SThomas Bogendoerfer 52273db8f0SThomas Bogendoerfer static int ioc3kbd_start(struct serio *dev) 53273db8f0SThomas Bogendoerfer { 54273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d = dev->port_data; 55273db8f0SThomas Bogendoerfer 56273db8f0SThomas Bogendoerfer d->kbd_exists = true; 57273db8f0SThomas Bogendoerfer return 0; 58273db8f0SThomas Bogendoerfer } 59273db8f0SThomas Bogendoerfer 60273db8f0SThomas Bogendoerfer static void ioc3kbd_stop(struct serio *dev) 61273db8f0SThomas Bogendoerfer { 62273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d = dev->port_data; 63273db8f0SThomas Bogendoerfer 64273db8f0SThomas Bogendoerfer d->kbd_exists = false; 65273db8f0SThomas Bogendoerfer } 66273db8f0SThomas Bogendoerfer 67273db8f0SThomas Bogendoerfer static int ioc3aux_write(struct serio *dev, u8 val) 68273db8f0SThomas Bogendoerfer { 69273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d = dev->port_data; 70273db8f0SThomas Bogendoerfer int ret; 71273db8f0SThomas Bogendoerfer 72273db8f0SThomas Bogendoerfer ret = ioc3kbd_wait(d->regs, KM_CSR_M_WRT_PEND); 73273db8f0SThomas Bogendoerfer if (ret) 74273db8f0SThomas Bogendoerfer return ret; 75273db8f0SThomas Bogendoerfer 76273db8f0SThomas Bogendoerfer writel(val, &d->regs->m_wd); 77273db8f0SThomas Bogendoerfer 78273db8f0SThomas Bogendoerfer return 0; 79273db8f0SThomas Bogendoerfer } 80273db8f0SThomas Bogendoerfer 81273db8f0SThomas Bogendoerfer static int ioc3aux_start(struct serio *dev) 82273db8f0SThomas Bogendoerfer { 83273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d = dev->port_data; 84273db8f0SThomas Bogendoerfer 85273db8f0SThomas Bogendoerfer d->aux_exists = true; 86273db8f0SThomas Bogendoerfer return 0; 87273db8f0SThomas Bogendoerfer } 88273db8f0SThomas Bogendoerfer 89273db8f0SThomas Bogendoerfer static void ioc3aux_stop(struct serio *dev) 90273db8f0SThomas Bogendoerfer { 91273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d = dev->port_data; 92273db8f0SThomas Bogendoerfer 93273db8f0SThomas Bogendoerfer d->aux_exists = false; 94273db8f0SThomas Bogendoerfer } 95273db8f0SThomas Bogendoerfer 96273db8f0SThomas Bogendoerfer static void ioc3kbd_process_data(struct serio *dev, u32 data) 97273db8f0SThomas Bogendoerfer { 98273db8f0SThomas Bogendoerfer if (data & KM_RD_VALID_0) 99273db8f0SThomas Bogendoerfer serio_interrupt(dev, (data >> KM_RD_DATA_0_SHIFT) & 0xff, 0); 100273db8f0SThomas Bogendoerfer if (data & KM_RD_VALID_1) 101273db8f0SThomas Bogendoerfer serio_interrupt(dev, (data >> KM_RD_DATA_1_SHIFT) & 0xff, 0); 102273db8f0SThomas Bogendoerfer if (data & KM_RD_VALID_2) 103273db8f0SThomas Bogendoerfer serio_interrupt(dev, (data >> KM_RD_DATA_2_SHIFT) & 0xff, 0); 104273db8f0SThomas Bogendoerfer } 105273db8f0SThomas Bogendoerfer 106273db8f0SThomas Bogendoerfer static irqreturn_t ioc3kbd_intr(int itq, void *dev_id) 107273db8f0SThomas Bogendoerfer { 108273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d = dev_id; 109273db8f0SThomas Bogendoerfer u32 data_k, data_m; 110273db8f0SThomas Bogendoerfer 111273db8f0SThomas Bogendoerfer data_k = readl(&d->regs->k_rd); 112273db8f0SThomas Bogendoerfer if (d->kbd_exists) 113273db8f0SThomas Bogendoerfer ioc3kbd_process_data(d->kbd, data_k); 114273db8f0SThomas Bogendoerfer 115273db8f0SThomas Bogendoerfer data_m = readl(&d->regs->m_rd); 116273db8f0SThomas Bogendoerfer if (d->aux_exists) 117273db8f0SThomas Bogendoerfer ioc3kbd_process_data(d->aux, data_m); 118273db8f0SThomas Bogendoerfer 119273db8f0SThomas Bogendoerfer return IRQ_HANDLED; 120273db8f0SThomas Bogendoerfer } 121273db8f0SThomas Bogendoerfer 122273db8f0SThomas Bogendoerfer static int ioc3kbd_probe(struct platform_device *pdev) 123273db8f0SThomas Bogendoerfer { 124273db8f0SThomas Bogendoerfer struct ioc3_serioregs __iomem *regs; 125273db8f0SThomas Bogendoerfer struct device *dev = &pdev->dev; 126273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d; 127273db8f0SThomas Bogendoerfer struct serio *sk, *sa; 128273db8f0SThomas Bogendoerfer int irq, ret; 129273db8f0SThomas Bogendoerfer 130273db8f0SThomas Bogendoerfer regs = devm_platform_ioremap_resource(pdev, 0); 131273db8f0SThomas Bogendoerfer if (IS_ERR(regs)) 132273db8f0SThomas Bogendoerfer return PTR_ERR(regs); 133273db8f0SThomas Bogendoerfer 134273db8f0SThomas Bogendoerfer irq = platform_get_irq(pdev, 0); 135273db8f0SThomas Bogendoerfer if (irq < 0) 136273db8f0SThomas Bogendoerfer return -ENXIO; 137273db8f0SThomas Bogendoerfer 138273db8f0SThomas Bogendoerfer d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL); 139273db8f0SThomas Bogendoerfer if (!d) 140273db8f0SThomas Bogendoerfer return -ENOMEM; 141273db8f0SThomas Bogendoerfer 142273db8f0SThomas Bogendoerfer sk = kzalloc(sizeof(*sk), GFP_KERNEL); 143273db8f0SThomas Bogendoerfer if (!sk) 144273db8f0SThomas Bogendoerfer return -ENOMEM; 145273db8f0SThomas Bogendoerfer 146273db8f0SThomas Bogendoerfer sa = kzalloc(sizeof(*sa), GFP_KERNEL); 147273db8f0SThomas Bogendoerfer if (!sa) { 148273db8f0SThomas Bogendoerfer kfree(sk); 149273db8f0SThomas Bogendoerfer return -ENOMEM; 150273db8f0SThomas Bogendoerfer } 151273db8f0SThomas Bogendoerfer 152273db8f0SThomas Bogendoerfer sk->id.type = SERIO_8042; 153273db8f0SThomas Bogendoerfer sk->write = ioc3kbd_write; 154273db8f0SThomas Bogendoerfer sk->start = ioc3kbd_start; 155273db8f0SThomas Bogendoerfer sk->stop = ioc3kbd_stop; 156273db8f0SThomas Bogendoerfer snprintf(sk->name, sizeof(sk->name), "IOC3 keyboard %d", pdev->id); 157273db8f0SThomas Bogendoerfer snprintf(sk->phys, sizeof(sk->phys), "ioc3/serio%dkbd", pdev->id); 158273db8f0SThomas Bogendoerfer sk->port_data = d; 159273db8f0SThomas Bogendoerfer sk->dev.parent = dev; 160273db8f0SThomas Bogendoerfer 161273db8f0SThomas Bogendoerfer sa->id.type = SERIO_8042; 162273db8f0SThomas Bogendoerfer sa->write = ioc3aux_write; 163273db8f0SThomas Bogendoerfer sa->start = ioc3aux_start; 164273db8f0SThomas Bogendoerfer sa->stop = ioc3aux_stop; 165273db8f0SThomas Bogendoerfer snprintf(sa->name, sizeof(sa->name), "IOC3 auxiliary %d", pdev->id); 166273db8f0SThomas Bogendoerfer snprintf(sa->phys, sizeof(sa->phys), "ioc3/serio%daux", pdev->id); 167273db8f0SThomas Bogendoerfer sa->port_data = d; 168273db8f0SThomas Bogendoerfer sa->dev.parent = dev; 169273db8f0SThomas Bogendoerfer 170273db8f0SThomas Bogendoerfer d->regs = regs; 171273db8f0SThomas Bogendoerfer d->kbd = sk; 172273db8f0SThomas Bogendoerfer d->aux = sa; 173273db8f0SThomas Bogendoerfer d->irq = irq; 174273db8f0SThomas Bogendoerfer 175273db8f0SThomas Bogendoerfer platform_set_drvdata(pdev, d); 176273db8f0SThomas Bogendoerfer serio_register_port(d->kbd); 177273db8f0SThomas Bogendoerfer serio_register_port(d->aux); 178273db8f0SThomas Bogendoerfer 179273db8f0SThomas Bogendoerfer ret = request_irq(irq, ioc3kbd_intr, IRQF_SHARED, "ioc3-kbd", d); 180273db8f0SThomas Bogendoerfer if (ret) { 181273db8f0SThomas Bogendoerfer dev_err(dev, "could not request IRQ %d\n", irq); 182273db8f0SThomas Bogendoerfer serio_unregister_port(d->kbd); 183273db8f0SThomas Bogendoerfer serio_unregister_port(d->aux); 184273db8f0SThomas Bogendoerfer return ret; 185273db8f0SThomas Bogendoerfer } 186273db8f0SThomas Bogendoerfer 187273db8f0SThomas Bogendoerfer /* enable ports */ 188273db8f0SThomas Bogendoerfer writel(KM_CSR_K_CLAMP_3 | KM_CSR_M_CLAMP_3, ®s->km_csr); 189273db8f0SThomas Bogendoerfer 190273db8f0SThomas Bogendoerfer return 0; 191273db8f0SThomas Bogendoerfer } 192273db8f0SThomas Bogendoerfer 193150e792dSUwe Kleine-König static void ioc3kbd_remove(struct platform_device *pdev) 194273db8f0SThomas Bogendoerfer { 195273db8f0SThomas Bogendoerfer struct ioc3kbd_data *d = platform_get_drvdata(pdev); 196273db8f0SThomas Bogendoerfer 197273db8f0SThomas Bogendoerfer free_irq(d->irq, d); 198273db8f0SThomas Bogendoerfer 199273db8f0SThomas Bogendoerfer serio_unregister_port(d->kbd); 200273db8f0SThomas Bogendoerfer serio_unregister_port(d->aux); 201273db8f0SThomas Bogendoerfer } 202273db8f0SThomas Bogendoerfer 203*d40e9edcSKarel Balej static const struct platform_device_id ioc3kbd_id_table[] = { 204*d40e9edcSKarel Balej { "ioc3-kbd", }, 205*d40e9edcSKarel Balej { } 206*d40e9edcSKarel Balej }; 207*d40e9edcSKarel Balej MODULE_DEVICE_TABLE(platform, ioc3kbd_id_table); 208*d40e9edcSKarel Balej 209273db8f0SThomas Bogendoerfer static struct platform_driver ioc3kbd_driver = { 210273db8f0SThomas Bogendoerfer .probe = ioc3kbd_probe, 211150e792dSUwe Kleine-König .remove_new = ioc3kbd_remove, 212*d40e9edcSKarel Balej .id_table = ioc3kbd_id_table, 213273db8f0SThomas Bogendoerfer .driver = { 214273db8f0SThomas Bogendoerfer .name = "ioc3-kbd", 215273db8f0SThomas Bogendoerfer }, 216273db8f0SThomas Bogendoerfer }; 217273db8f0SThomas Bogendoerfer module_platform_driver(ioc3kbd_driver); 218273db8f0SThomas Bogendoerfer 219273db8f0SThomas Bogendoerfer MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfer@suse.de>"); 220273db8f0SThomas Bogendoerfer MODULE_DESCRIPTION("SGI IOC3 serio driver"); 221273db8f0SThomas Bogendoerfer MODULE_LICENSE("GPL"); 222