/* * gsc3280_ps.c - driver for PS2 controller in Loongson soc * * Copyright (C) 2013 BLX IC Design Corp.,Ltd. * Author: ansonn, ansonn.wang@gmail.com * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GSC3280_PS2_DEBUG 0x0 /*----------------------------------------------------------------------------*/ #ifdef GSC3280_PS2_DEBUG #define dprintk(msg...) printk(KERN_DEBUG "GSC3280 PS2: " msg); #else #define dprintk(msg...) #endif /*----------------------------------------------------------------------------*/ #define PS2_IBUF 0x00 #define PS2_OBUF 0x04 #define PS2_STR 0x08 #define PS2_CR 0x0c #define PS2_CPSR 0x10 #define PS2_DVR 0x14 #define PS2_STR_OBF 0x02 #define PS2_STR_IBF 0x01 #define PS2_STR_SFLG 0x04 #define PS2_STR_A2 0x08 #define PS2_STR_KDI 0x10 #define PS2_STR_DIBF 0x20 #define PS2_STR_PERR 0x80 #define GSC3280_MOD_CTL0 0xbc04a008 #define GSC3280_IOMUX_CFG0 0xbc04a0b0 #define GSC3280_IOMUX_CFG1 0xbc04a0b4 #define GSC3280_MOD_SRST0 0xbc04a010 //#define DEFAULT_CPSR_KBD 0x05 //#define DEFAULT_CPSR_MOS 0x05 //#define DEFAULT_DVR 0x15 #define DEFAULT_CPSR_KBD 0x08 #define DEFAULT_CPSR_MOS 0x08 #define DEFAULT_DVR 0x45 #define PS2_BUFFER_SIZE 16 /*----------------------------------------------------------------------------*/ #define ps2_reg_readl(port, reg) \ __raw_readl((port)->regs + PS2_##reg) #define ps2_reg_writel(port, reg, value) \ __raw_writel((value), (port)->regs + PS2_##reg) #define ps2_write_command(p, v) ps2_reg_writel(p, CR, v) #define ps2_write_data(p, v) ps2_reg_writel(p, OBUF, v) #define ps2_read_data(p) ps2_reg_readl(p, IBUF) #define ps2_read_status(p) ps2_reg_readl(p, STR) /*----------------------------------------------------------------------------*/ /* Command */ #define PS2_CMD1_CTL_TEST 0x01aa #define PS2_CMD2_CTL_TEST 0x01ab #define PS2_CMD_CTL_RCTR 0x0120 #define PS2_CMD_CTL_WCTR 0x1060 #define PS2_CMD_AUX_LOOP 0x11d3 #define PS2_CMD_KBD_LOOP 0x11d2 #define PS2_CMD_KBD_START 0x00ae #define PS2_CMD_MOS_START 0x00a8 #define PS2_CMD_KBD_STOP 0x00ad #define PS2_CMD_MOS_STOP 0x00a7 /* Ret Code */ #define PS2_RET1_CTL_TEST 0x55 #define PS2_RET2_CTL_TEST 0x00 #define PS2_CTL_TIMEOUT 10000 /* Control register bits. */ #define PS2_CTR_KBDINT 0x01 #define PS2_CTR_MOSINT 0x02 #define PS2_CTR_KBDDIS 0x10 #define PS2_CTR_MOSDIS 0x20 #define PS2_CTR_SYS 0x04 /*----------------------------------------------------------------------------*/ enum gsc3280_ps2_type { MOS_PORT, KBD_PORT, PORT_BUTT }; /*----------------------------------------------------------------------------*/ struct gsc3280_ps2_port { struct platform_device *pdev; void __iomem *regs; unsigned int irq; unsigned char init_ctr; unsigned char ctr; spinlock_t lock; enum gsc3280_ps2_type port_t; struct serio serio; }; /*----------------------------------------------------------------------------*/ struct gsc3280_ps2 { struct gsc3280_ps2_port ports; }; /*----------------------------------------------------------------------------*/ static int gsc3280_ps2_wait_read(struct gsc3280_ps2_port *port) { int i = 0; while ((!(ps2_reg_readl(port, STR) & PS2_STR_IBF)) && (i < PS2_CTL_TIMEOUT)) { udelay(50); i++; } return (i == PS2_CTL_TIMEOUT); } /*----------------------------------------------------------------------------*/ static int gsc3280_ps2_wait_write(struct gsc3280_ps2_port *port) { int i = 0; while ((ps2_reg_readl(port, STR) & PS2_STR_OBF) && (i < PS2_CTL_TIMEOUT)) { udelay(50); i++; } return (i == PS2_CTL_TIMEOUT); } /*----------------------------------------------------------------------------*/ static int gsc3280_ps2_command(struct gsc3280_ps2_port *port, unsigned char *param, int command) { int i, ret; /* wait */ ret = gsc3280_ps2_wait_write(port); if (ret) return ret; //dprintk("%02x -> gsc3280 ps2 (command)", command & 0xff); ps2_write_command(port, command & 0xff); #if GSC3280_PS2_DEBUG printk(KERN_ALERT"write command %x####################\n",command & 0xff); #endif /* write parameter */ for (i = 0; i < ((command >> 12) & 0xf); i++) { ret = gsc3280_ps2_wait_write(port); if (ret) return ret; //dprintk("%02x -> gsc3280_ps2 (parameter)", param[i]); ps2_write_data(port, param[i]); #if GSC3280_PS2_DEBUG printk(KERN_ALERT"write param[%d] = %d#########################\n",i,param[i]); #endif } /* read parameter */ for (i = 0; i < ((command >> 8) & 0xf); i++) { ret = gsc3280_ps2_wait_read(port); if (ret) { //dprintk(" -- gsc3280_ps2_wait_read (timeout)"); return ret; } param[i] = (unsigned char)ps2_read_data(port); #if GSC3280_PS2_DEBUG printk(KERN_ALERT"read param[%d] = %x#########################\n",i,param[i]); #endif //dprintk("%02x <- gsc3280 ps2 (return)", param[i]); } return 0; } /*----------------------------------------------------------------------------*/ static int gsc3280_ps2port_selftest(struct gsc3280_ps2_port *ps2_port) { int i = 0; unsigned char param; #if GSC3280_PS2_DEBUG printk(KERN_ALERT"in gsc3280_ps2port_selftest##########\n"); #endif /* * We try this 5 times; */ while (i++ < 5) { if (i > 1) msleep(50); if (gsc3280_ps2_command(ps2_port, ¶m, PS2_CMD1_CTL_TEST)) { printk(KERN_ERR "gsc3280: ps2 port self test timeout.\n"); return -ENODEV; } if (param != PS2_RET1_CTL_TEST) continue; if (gsc3280_ps2_command(ps2_port, ¶m, PS2_CMD2_CTL_TEST)) { printk(KERN_ERR "gsc3280: ps2 port self test timeout.\n"); return -ENODEV; } if (param != PS2_RET2_CTL_TEST) continue; return 0; } printk(KERN_ERR "gsc3280: ps2 port self test error.param[%#x]\n", param); return -EIO; } /*----------------------------------------------------------------------------*/ static int gsc3280_ps2_flush(struct gsc3280_ps2_port *ps2_port) { unsigned char data, str; int i = 0; while (((str = ps2_read_status(ps2_port)) & PS2_STR_IBF) && (i < PS2_BUFFER_SIZE)) { udelay(50); data = (unsigned char)ps2_read_data(ps2_port); i++; dprintk("%02x <- gsc3280_ps2 (flush)", data); } return i; } /*----------------------------------------------------------------------------*/ static int gsc3280_ps2port_init(struct gsc3280_ps2_port *ps2_port) { int n = 0; unsigned char ctr[2]; do { if (n >= 10) { printk(KERN_ERR "gsc3280_ps2: Unable to get stable CTR read.\n"); return -EIO; } if (n != 0) udelay(50); if (gsc3280_ps2_command(ps2_port, &ctr[n++ % 2], PS2_CMD_CTL_RCTR)) { printk(KERN_ERR "gsc3280_ps2: Can't read CTR while initializing.\n"); return -EIO; } } while (n < 2 || ctr[0] != ctr[1]); ps2_port->init_ctr = ps2_port->ctr = ctr[0]; dprintk(KERN_ERR "INIT CTR= [%#x]", ctr[0]); /* init: disable port && enable interrupt */ ps2_port->ctr |= PS2_CTR_KBDINT; ps2_port->ctr |= PS2_CTR_MOSINT; ps2_port->ctr |= PS2_CTR_SYS; /* write CTR back */ if (gsc3280_ps2_command(ps2_port, &ps2_port->ctr, PS2_CMD_CTL_WCTR)) { printk(KERN_ERR "gsc3280_ps2: Can't write CTR while initializing.\n"); return -EIO; } gsc3280_ps2_flush(ps2_port); return 0; } /*----------------------------------------------------------------------------*/ static int gsc3280_check_port(struct gsc3280_ps2_port *ps2_port) { int ret; int cmd; unsigned char param; gsc3280_ps2_flush(ps2_port); cmd = (ps2_port->port_t == KBD_PORT?PS2_CMD_KBD_LOOP:PS2_CMD_AUX_LOOP); /* internal loop test */ param = 0x5a; ret = gsc3280_ps2_command(ps2_port, ¶m, cmd); if (ret || param != 0x5a) { printk(KERN_ERR "internal loop test %s error", ps2_port->port_t == KBD_PORT?"keyboard":"mouse"); return -1; } #if GSC3280_PS2_DEBUG printk(KERN_ALERT"gsc3280_check ok####################\n"); #endif return 0; } /*----------------------------------------------------------------------------*/ int gsc3280_port_write(struct serio *io, unsigned char val) { int ret = 0; unsigned long flags; struct gsc3280_ps2_port *ps2_port = io->port_data; #if GSC3280_PS2_DEBUG // printk(KERN_ALERT"in gsc3280_port_write#######################\n"); #endif spin_lock_irqsave(&ps2_port->lock, flags); ret = gsc3280_ps2_wait_write(ps2_port); if (0 == ret) { ps2_write_data(ps2_port, val); #if GSC3280_PS2_DEBUG printk(KERN_ALERT"write outbuf data = 0x%x\n",val); // printk(KERN_ALERT"PS2_OBUF=0x%x\n",*(volatile unsigned int *)(0xbc107004)); #endif } else printk(KERN_ERR "%s In gsc3280_port_write wait write timeout", ps2_port->port_t == KBD_PORT?"Keyboard":"Mouse"); spin_unlock_irqrestore(&ps2_port->lock, flags); return ret; } /*----------------------------------------------------------------------------*/ int gsc3280_port_open(struct serio *io) { unsigned char param = 0; struct gsc3280_ps2_port *ps2_port = io->port_data; if (KBD_PORT == ps2_port->port_t) gsc3280_ps2_command(ps2_port, ¶m, PS2_CMD_KBD_START); else if (MOS_PORT == ps2_port->port_t) { gsc3280_ps2_command(ps2_port, ¶m, PS2_CMD_MOS_START); #if GSC3280_PS2_DEBUG printk(KERN_ALERT"port_t=MOS_PORT\n##############"); #endif } else { printk(KERN_ERR "gsc3280_port_open No Such Device type"); return -ENXIO; } #if GSC3280_PS2_DEBUG printk(KERN_ALERT"open gsc3280_ps2##############\n"); #endif return 0; } /*----------------------------------------------------------------------------*/ void gsc3280_port_close(struct serio *io) { unsigned char param = 0; struct gsc3280_ps2_port *ps2_port = io->port_data; if (KBD_PORT == ps2_port->port_t) gsc3280_ps2_command(ps2_port, ¶m, PS2_CMD_KBD_STOP); else if (MOS_PORT == ps2_port->port_t) gsc3280_ps2_command(ps2_port, ¶m, PS2_CMD_MOS_STOP); else { printk(KERN_ERR "gsc3280_port_open No Such Device type"); } #if GSC3280_PS2_DEBUG printk(KERN_ALERT"gsc3280_port_close######ps2_port->port_t=%d###################\n",ps2_port->port_t); #endif return; } /*----------------------------------------------------------------------------*/ int gsc3280_init_port_io(struct gsc3280_ps2_port *ps2_port) { struct serio *serio = &ps2_port->serio; serio->id.type = SERIO_8042; serio->write = gsc3280_port_write; serio->open = gsc3280_port_open; serio->close = gsc3280_port_close; serio->port_data = ps2_port; serio->dev.parent = &ps2_port->pdev->dev; snprintf(serio->name, sizeof(serio->name), "gsc3280 PS/2 %s", (ps2_port->port_t==KBD_PORT?"keyboard":"mouse")); #if GSC3280_PS2_DEBUG printk(KERN_ALERT"in init_port_io , ps2_port->port_t=%s ###############################\n", (ps2_port->port_t==KBD_PORT?"keyboard":"mouse")); #endif snprintf(serio->phys, sizeof(serio->phys), "gsc3280/serio%d", ps2_port->port_t); #if GSC3280_PS2_DEBUG printk(KERN_ALERT"gsc3280/serio%d####################\n",ps2_port->port_t); #endif return 0; } /*----------------------------------------------------------------------------*/ static irqreturn_t gsc3280_ps2_interrupt(int irq, void *_ptr) { struct gsc3280_ps2_port *ps2_port = _ptr; int retval = IRQ_NONE; unsigned int io_flags = 0; unsigned long status; status = ps2_read_status(ps2_port); if (status & PS2_STR_IBF) { unsigned char val = (unsigned char) ps2_read_data(ps2_port); if (status & PS2_STR_PERR) io_flags |= SERIO_PARITY; serio_interrupt(&ps2_port->serio, val, io_flags); retval = IRQ_HANDLED; } return retval; } /*----------------------------------------------------------------------------*/ static int gsc3280_register_port(struct gsc3280_ps2_port *ps2_port) { int ret = 0; ret = request_irq(ps2_port->irq, gsc3280_ps2_interrupt, IRQF_SHARED, "gsc3280_ps2", ps2_port); if (ret) { printk(KERN_ERR "gsc3280_register_port: rigister irq failed"); return -1; } gsc3280_init_port_io(ps2_port); serio_register_port(&ps2_port->serio); return 0; } /*----------------------------------------------------------------------------*/ static int gsc3280_init_port(struct gsc3280_ps2_port *ps2_port) { int ret; /* Set Div Freq */ if (KBD_PORT == ps2_port->port_t) ps2_reg_writel(ps2_port, CPSR, DEFAULT_CPSR_KBD); else if (MOS_PORT == ps2_port->port_t) ps2_reg_writel(ps2_port, CPSR, DEFAULT_CPSR_MOS); ps2_reg_writel(ps2_port, DVR, DEFAULT_DVR); ret = gsc3280_ps2port_selftest(ps2_port); if (ret) return ret; ret = gsc3280_ps2port_init(ps2_port); if (ret) return ret; ret = gsc3280_check_port(ps2_port); if (ret) return ret; return 0; } /*----------------------------------------------------------------------------*/ static int gsc3280_scan_port(int port_idx, struct gsc3280_ps2_port *ps2_port, struct platform_device *pdev) { int ret = 0; struct resource *regs; //ps2_port->port_t = port_idx; regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs) { dprintk("get regs resources failed port[%d]\n", pdev->id); ret = -ENOMEM; goto out; } ps2_port->regs = ioremap(regs->start, resource_size(regs)); if (!ps2_port->regs) { dprintk("could not map I/O memory port[%d]\n", pdev->id); ret = -ENOMEM; goto out; } ps2_port->irq = platform_get_irq(pdev, 0); if (ps2_port->irq < 0) { dprintk("gsc3280 ps2_%d could not get irq\n", pdev->id); ret = -ENXIO; goto out_unremap; } if (gsc3280_init_port(ps2_port)) { ret = -ENXIO; goto out_unremap; } if (gsc3280_register_port(ps2_port)) { ret = -ENXIO; goto out_unremap; } #if GSC3280_PS2_DEBUG printk(KERN_ALERT"after gsc3280_register_port###############\n"); #endif return 0; out_unremap: iounmap(ps2_port->regs); out: ps2_port->port_t = PORT_BUTT; return ret; } /*----------------------------------------------------------------------------*/ static int __devinit gsc3280_ps2_probe(struct platform_device *pdev) { int i; int n; int ret = 0; // int exist_port = 0; struct gsc3280_ps2 *ps2_dev; unsigned int reg_val = 0; *(volatile unsigned int *)(GSC3280_MOD_SRST0) |= (1<<13); for(n=5000;n>0;n--); *(volatile unsigned int *)(GSC3280_MOD_SRST0) &= (~(1<<13)); ps2_dev = kzalloc(sizeof(struct gsc3280_ps2), GFP_KERNEL); if (!ps2_dev) { dprintk("kzalloc gsc3280_ps2 failed\n"); ret = -ENOMEM; goto out; } /* gsc3280 have 2 ports */ ps2_dev->ports.pdev = pdev; ps2_dev->ports.port_t = *(enum gsc3280_ps2_type *)pdev->dev.platform_data; if (gsc3280_scan_port(i, &ps2_dev->ports, pdev) != 0) { dprintk(KERN_INFO "gsc3280 ps2 port %d init failed!", pdev->id); goto free; } #if GSC3280_PS2_DEBUG printk(KERN_ALERT"in gsc3280_probe#################################################\n"); #endif platform_set_drvdata(pdev, ps2_dev); return 0; free: kfree(ps2_dev); out: return ret; } /*----------------------------------------------------------------------------*/ static int __devexit gsc3280_ps2_remove(struct platform_device *pdev) { // int i; struct gsc3280_ps2 *ps2_dev = platform_get_drvdata(pdev); if (PORT_BUTT != ps2_dev->ports.port_t) { serio_unregister_port(&ps2_dev->ports.serio); free_irq(ps2_dev->ports.irq, &ps2_dev->ports); gsc3280_ps2_command(&ps2_dev->ports, &ps2_dev->ports.init_ctr, PS2_CMD_CTL_WCTR); } kfree(ps2_dev); platform_set_drvdata(pdev, NULL); return 0; } /*----------------------------------------------------------------------------*/ #ifdef CONFIG_PM static int gsc3280_ps2_suspend(struct platform_device *pdev, pm_message_t state) { return 0; } /*----------------------------------------------------------------------------*/ static int gsc3280_ps2_resume(struct platform_device *pdev) { return 0; } #else #define gsc3280_ps2_suspend NULL #define gsc3280_ps2_resume NULL #endif /*----------------------------------------------------------------------------*/ static struct platform_driver gsc3280_ps2_driver = { .driver = { .name = "gsc3280_ps2", .owner = THIS_MODULE, }, .probe = gsc3280_ps2_probe, .remove = __devexit_p(gsc3280_ps2_remove), .suspend = gsc3280_ps2_suspend, .resume = gsc3280_ps2_resume, }; /*----------------------------------------------------------------------------*/ static int __init gsc3280_ps2_init(void) { //printk(KERN_ALERT"in ps2 init################################\n"); return platform_driver_register(&gsc3280_ps2_driver); } /*----------------------------------------------------------------------------*/ static void __exit gsc3280_ps2_exit(void) { platform_driver_unregister(&gsc3280_ps2_driver); } /*----------------------------------------------------------------------------*/ module_init(gsc3280_ps2_init); module_exit(gsc3280_ps2_exit); /*----------------------------------------------------------------------------*/ MODULE_AUTHOR("ansonn.wang@gmail.com"); MODULE_DESCRIPTION("Loongson keyboard and mouse controller driver"); MODULE_LICENSE("GPL"); /*----------------------------------------------------------------------------*/