/* * gsc3280_keypad.c - driver for 4x4 keypad 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 #define GSC3280_KP_DEBUG /*----------------------------------------------------------------------------*/ #ifdef GSC3280_KP_DEBUG #define dprintk(msg...) printk(KERN_DEBUG "Gsc3280 keypad: " msg); #else #define dprintk(msg...) #endif #define DEVICE_NAME "gsc3280_Keypad" #define PYH_NAME "input/gsc3280_keypad" #define GSC3280_MAX_BTN 16 #define gsc3280_kp_ver 0x0001 /*----------------------------------------------------------------------------*/ #define KP_INIT_STA 0x00 #define KP_INIT_MSK 0x04 #define KP_CTRL 0x08 #define KP_VALUE 0x0c #define KP_EN 0x10 #define SCAN_STEP 7813 #define DEBOUNCE_LIMIT 0x04 #define ICM 0x00 #define LPM 0x00 #define KP_CTRL_INIT (LPM | ICM << 1 | DEBOUNCE_LIMIT << 8 | SCAN_STEP << 16) /*----------------------------------------------------------------------------*/ #define kp_reg_readl(port, reg) \ __raw_readl((port)->regs + KP_##reg) #define kp_reg_writel(port, reg, value) \ __raw_writel((value), (port)->regs + KP_##reg) /*----------------------------------------------------------------------------*/ struct gsc3280_keypad { void __iomem *regs; unsigned int valid_irq; unsigned int wakeup_irq; struct input_dev *input; struct timer_list timer; }; /*----------------------------------------------------------------------------*/ static const int gsc3280_keycode[GSC3280_MAX_BTN] = { KEY_STOP, KEY_GOTO, KEY_NUMLOCK, KEY_POWER, KEY_3, KEY_6, KEY_9, KEY_ESC, KEY_2, KEY_5, KEY_8, KEY_0, KEY_1, KEY_4, KEY_7, KEY_ENTER }; /*----------------------------------------------------------------------------*/ static int gsc3280_kp_open(struct input_dev *dev) { struct gsc3280_keypad *kp = input_get_drvdata(dev); kp_reg_writel(kp, EN, 0x01); return 0; } /*----------------------------------------------------------------------------*/ static void gsc3280_kp_close(struct input_dev *dev) { struct gsc3280_keypad *kp = input_get_drvdata(dev); /* clr start bit */ kp_reg_writel(kp, EN, 0x00); return; } /*----------------------------------------------------------------------------*/ static irqreturn_t kp_interrupt(int irq, void *_ptr) { int val_0, val_1; unsigned int stu_0, stu_1; struct gsc3280_keypad *kp = _ptr; if (irq == kp->valid_irq) { unsigned int reg_v = kp_reg_readl(kp, VALUE); unsigned int int_sta = kp_reg_readl(kp, INIT_STA); /* CLR Init */ kp_reg_writel(kp, INIT_STA, 0x03); /* invalid interrupt */ if (0x01 != int_sta) return IRQ_NONE; val_0 = reg_v & 0xF; val_1 = (reg_v >> 8) & 0xF; stu_0 = (reg_v >> 4) & 0x3; stu_1 = (reg_v >> 12) & 0x3; if (stu_0) input_report_key(kp->input, gsc3280_keycode[val_0], (stu_0==2?0:1)); if (stu_1) input_report_key(kp->input, gsc3280_keycode[val_1], (stu_1==2?0:1)); input_sync(kp->input); } else if (irq == kp->wakeup_irq) { /* NOTICE WAKEUP */ input_report_key(kp->input, KEY_WAKEUP, 1); input_report_key(kp->input, KEY_WAKEUP, 0); input_sync(kp->input); } else return IRQ_NONE; return IRQ_HANDLED; } /*----------------------------------------------------------------------------*/ static int __devinit hex32xx_keypad_probe(struct platform_device *pdev) { int i; struct resource *regs; struct gsc3280_keypad *kp; int ret; kp = kzalloc(sizeof(struct gsc3280_keypad), GFP_KERNEL); if (!kp) { dprintk("kzalloc gsc3280_keypad failed\n"); ret = -ENOMEM; goto out; } regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs) { dprintk("get regs resources failed\n"); ret = -ENOMEM; goto out_free_kp; } kp->regs = ioremap(regs->start, resource_size(regs)); if (!kp->regs) { ret = -ENOMEM; dprintk("could not map I/O memory\n"); goto out_free_kp; } kp->valid_irq = platform_get_irq(pdev, 0); kp->wakeup_irq = platform_get_irq(pdev, 1); if (kp->valid_irq < 0 || kp->wakeup_irq < 0) { dprintk("hex32xx keypad could not get irq\n"); ret = -ENXIO; goto out_unremap; } ret = request_irq(kp->valid_irq, kp_interrupt, IRQF_SHARED, "keypad_valid", kp); if (ret) { dprintk("could'not request valid_irq %d\n", kp->wakeup_irq); goto out_unremap; } ret = request_irq(kp->wakeup_irq, kp_interrupt, IRQF_SHARED, "keypad_wakeup", kp); if (ret) { free_irq(kp->valid_irq, kp); dprintk("could'not request wakeup_irq %d\n", kp->wakeup_irq); goto out_unremap; } /* init device */ kp_reg_writel(kp, CTRL, KP_CTRL_INIT); kp_reg_writel(kp, INIT_MSK, 0x00); kp->input = input_allocate_device(); if (!kp->input) { ret = -ENOMEM; goto out_free_irq; } /* event type set */ set_bit(EV_KEY, kp->input->evbit); kp->input->name = DEVICE_NAME; kp->input->phys = PYH_NAME; kp->input->id.bustype = BUS_HOST; kp->input->id.vendor = 0; kp->input->id.product = 0; kp->input->id.version = gsc3280_kp_ver; for (i = 0; i < GSC3280_MAX_BTN; i++) set_bit(gsc3280_keycode[i], kp->input->keybit); set_bit(KEY_WAKEUP, kp->input->keybit); input_set_drvdata(kp->input, kp); kp->input->open = gsc3280_kp_open; kp->input->close = gsc3280_kp_close; ret = input_register_device(kp->input); if (ret) { dprintk("unable to register input device: %d\n", ret); goto out_freeinput; } platform_set_drvdata(pdev, kp); return 0; out_freeinput: input_free_device(kp->input); out_free_irq: free_irq(kp->valid_irq, kp); free_irq(kp->wakeup_irq, kp); out_unremap: iounmap(kp->regs); out_free_kp: kfree(kp); out: return -1; } /*----------------------------------------------------------------------------*/ static int __devexit hex32xx_keypad_remove(struct platform_device *pdev) { struct gsc3280_keypad *kp = platform_get_drvdata(pdev); free_irq(kp->valid_irq, kp); free_irq(kp->wakeup_irq, kp); iounmap(kp->regs); input_unregister_device(kp->input); platform_set_drvdata(pdev, NULL); input_free_device(kp->input); kfree(kp); return 0; } /*----------------------------------------------------------------------------*/ #ifdef CONFIG_PM static int gsc3280_kp_suspend(struct platform_device *pdev, pm_message_t state) { unsigned int reg_value; struct gsc3280_keypad *kp = platform_get_drvdata(pdev); /* enable wakeup interrupt */ reg_value = kp_reg_readl(kp, CTRL) | 0x01; kp_reg_writel(kp, CTRL, reg_value); return 0; } /*----------------------------------------------------------------------------*/ static int gsc3280_kp_resume(struct platform_device *pdev) { unsigned int reg_value; struct gsc3280_keypad *kp = platform_get_drvdata(pdev); /* disnable wakeup interrupt */ reg_value = kp_reg_readl(kp, CTRL) & ~0x01; kp_reg_writel(kp, CTRL, reg_value); return 0; } #else #define gsc3280_kp_suspend NULL #define gsc3280_kp_resume NULL #endif /*----------------------------------------------------------------------------*/ /* platform device */ static struct platform_driver gsc3280_keypad_driver = { .remove = __devexit_p(hex32xx_keypad_remove), .driver = { .name = "gsc3280_keypad", .owner = THIS_MODULE, }, .suspend = gsc3280_kp_suspend, .resume = gsc3280_kp_resume, }; /*----------------------------------------------------------------------------*/ static int __init gsc3280_keypad_init(void) { return platform_driver_probe(&gsc3280_keypad_driver, hex32xx_keypad_probe); } /*----------------------------------------------------------------------------*/ static void __exit gsc3280_keypad_exit(void) { platform_driver_unregister(&gsc3280_keypad_driver); } /*----------------------------------------------------------------------------*/ module_init(gsc3280_keypad_init); module_exit(gsc3280_keypad_exit); /*----------------------------------------------------------------------------*/ MODULE_AUTHOR("ansonn.wang@gmail.com"); MODULE_DESCRIPTION("Loongson Keypad Driver"); MODULE_LICENSE("GPL");