diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/input/keyboard/spear-keyboard.c | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/drivers/input/keyboard/spear-keyboard.c b/drivers/input/keyboard/spear-keyboard.c new file mode 100644 index 000000000..9838c79cb --- /dev/null +++ b/drivers/input/keyboard/spear-keyboard.c @@ -0,0 +1,390 @@ +/* + * SPEAr Keyboard Driver + * Based on omap-keypad driver + * + * Copyright (C) 2010 ST Microelectronics + * Rajeev Kumar <rajeevkumar.linux@gmail.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_wakeup.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/platform_data/keyboard-spear.h> + +/* Keyboard Registers */ +#define MODE_CTL_REG 0x00 +#define STATUS_REG 0x0C +#define DATA_REG 0x10 +#define INTR_MASK 0x54 + +/* Register Values */ +#define NUM_ROWS 16 +#define NUM_COLS 16 +#define MODE_CTL_PCLK_FREQ_SHIFT 9 +#define MODE_CTL_PCLK_FREQ_MSK 0x7F + +#define MODE_CTL_KEYBOARD (0x2 << 0) +#define MODE_CTL_SCAN_RATE_10 (0x0 << 2) +#define MODE_CTL_SCAN_RATE_20 (0x1 << 2) +#define MODE_CTL_SCAN_RATE_40 (0x2 << 2) +#define MODE_CTL_SCAN_RATE_80 (0x3 << 2) +#define MODE_CTL_KEYNUM_SHIFT 6 +#define MODE_CTL_START_SCAN (0x1 << 8) + +#define STATUS_DATA_AVAIL (0x1 << 1) + +#define DATA_ROW_MASK 0xF0 +#define DATA_COLUMN_MASK 0x0F + +#define ROW_SHIFT 4 + +struct spear_kbd { + struct input_dev *input; + void __iomem *io_base; + struct clk *clk; + unsigned int irq; + unsigned int mode; + unsigned int suspended_rate; + unsigned short last_key; + unsigned short keycodes[NUM_ROWS * NUM_COLS]; + bool rep; + bool irq_wake_enabled; + u32 mode_ctl_reg; +}; + +static irqreturn_t spear_kbd_interrupt(int irq, void *dev_id) +{ + struct spear_kbd *kbd = dev_id; + struct input_dev *input = kbd->input; + unsigned int key; + u32 sts, val; + + sts = readl_relaxed(kbd->io_base + STATUS_REG); + if (!(sts & STATUS_DATA_AVAIL)) + return IRQ_NONE; + + if (kbd->last_key != KEY_RESERVED) { + input_report_key(input, kbd->last_key, 0); + kbd->last_key = KEY_RESERVED; + } + + /* following reads active (row, col) pair */ + val = readl_relaxed(kbd->io_base + DATA_REG) & + (DATA_ROW_MASK | DATA_COLUMN_MASK); + key = kbd->keycodes[val]; + + input_event(input, EV_MSC, MSC_SCAN, val); + input_report_key(input, key, 1); + input_sync(input); + + kbd->last_key = key; + + /* clear interrupt */ + writel_relaxed(0, kbd->io_base + STATUS_REG); + + return IRQ_HANDLED; +} + +static int spear_kbd_open(struct input_dev *dev) +{ + struct spear_kbd *kbd = input_get_drvdata(dev); + int error; + u32 val; + + kbd->last_key = KEY_RESERVED; + + error = clk_enable(kbd->clk); + if (error) + return error; + + /* keyboard rate to be programmed is input clock (in MHz) - 1 */ + val = clk_get_rate(kbd->clk) / 1000000 - 1; + val = (val & MODE_CTL_PCLK_FREQ_MSK) << MODE_CTL_PCLK_FREQ_SHIFT; + + /* program keyboard */ + val = MODE_CTL_SCAN_RATE_80 | MODE_CTL_KEYBOARD | val | + (kbd->mode << MODE_CTL_KEYNUM_SHIFT); + writel_relaxed(val, kbd->io_base + MODE_CTL_REG); + writel_relaxed(1, kbd->io_base + STATUS_REG); + + /* start key scan */ + val = readl_relaxed(kbd->io_base + MODE_CTL_REG); + val |= MODE_CTL_START_SCAN; + writel_relaxed(val, kbd->io_base + MODE_CTL_REG); + + return 0; +} + +static void spear_kbd_close(struct input_dev *dev) +{ + struct spear_kbd *kbd = input_get_drvdata(dev); + u32 val; + + /* stop key scan */ + val = readl_relaxed(kbd->io_base + MODE_CTL_REG); + val &= ~MODE_CTL_START_SCAN; + writel_relaxed(val, kbd->io_base + MODE_CTL_REG); + + clk_disable(kbd->clk); + + kbd->last_key = KEY_RESERVED; +} + +#ifdef CONFIG_OF +static int spear_kbd_parse_dt(struct platform_device *pdev, + struct spear_kbd *kbd) +{ + struct device_node *np = pdev->dev.of_node; + int error; + u32 val, suspended_rate; + + if (!np) { + dev_err(&pdev->dev, "Missing DT data\n"); + return -EINVAL; + } + + if (of_property_read_bool(np, "autorepeat")) + kbd->rep = true; + + if (of_property_read_u32(np, "suspended_rate", &suspended_rate)) + kbd->suspended_rate = suspended_rate; + + error = of_property_read_u32(np, "st,mode", &val); + if (error) { + dev_err(&pdev->dev, "DT: Invalid or missing mode\n"); + return error; + } + + kbd->mode = val; + return 0; +} +#else +static inline int spear_kbd_parse_dt(struct platform_device *pdev, + struct spear_kbd *kbd) +{ + return -ENOSYS; +} +#endif + +static int spear_kbd_probe(struct platform_device *pdev) +{ + struct kbd_platform_data *pdata = dev_get_platdata(&pdev->dev); + const struct matrix_keymap_data *keymap = pdata ? pdata->keymap : NULL; + struct spear_kbd *kbd; + struct input_dev *input_dev; + struct resource *res; + int irq; + int error; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + kbd = devm_kzalloc(&pdev->dev, sizeof(*kbd), GFP_KERNEL); + if (!kbd) { + dev_err(&pdev->dev, "not enough memory for driver data\n"); + return -ENOMEM; + } + + input_dev = devm_input_allocate_device(&pdev->dev); + if (!input_dev) { + dev_err(&pdev->dev, "unable to allocate input device\n"); + return -ENOMEM; + } + + kbd->input = input_dev; + kbd->irq = irq; + + if (!pdata) { + error = spear_kbd_parse_dt(pdev, kbd); + if (error) + return error; + } else { + kbd->mode = pdata->mode; + kbd->rep = pdata->rep; + kbd->suspended_rate = pdata->suspended_rate; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + kbd->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(kbd->io_base)) + return PTR_ERR(kbd->io_base); + + kbd->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(kbd->clk)) + return PTR_ERR(kbd->clk); + + input_dev->name = "Spear Keyboard"; + input_dev->phys = "keyboard/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->open = spear_kbd_open; + input_dev->close = spear_kbd_close; + + error = matrix_keypad_build_keymap(keymap, NULL, NUM_ROWS, NUM_COLS, + kbd->keycodes, input_dev); + if (error) { + dev_err(&pdev->dev, "Failed to build keymap\n"); + return error; + } + + if (kbd->rep) + __set_bit(EV_REP, input_dev->evbit); + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + input_set_drvdata(input_dev, kbd); + + error = devm_request_irq(&pdev->dev, irq, spear_kbd_interrupt, 0, + "keyboard", kbd); + if (error) { + dev_err(&pdev->dev, "request_irq failed\n"); + return error; + } + + error = clk_prepare(kbd->clk); + if (error) + return error; + + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, "Unable to register keyboard device\n"); + clk_unprepare(kbd->clk); + return error; + } + + device_init_wakeup(&pdev->dev, 1); + platform_set_drvdata(pdev, kbd); + + return 0; +} + +static int spear_kbd_remove(struct platform_device *pdev) +{ + struct spear_kbd *kbd = platform_get_drvdata(pdev); + + input_unregister_device(kbd->input); + clk_unprepare(kbd->clk); + + return 0; +} + +static int __maybe_unused spear_kbd_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spear_kbd *kbd = platform_get_drvdata(pdev); + struct input_dev *input_dev = kbd->input; + unsigned int rate = 0, mode_ctl_reg, val; + + mutex_lock(&input_dev->mutex); + + /* explicitly enable clock as we may program device */ + clk_enable(kbd->clk); + + mode_ctl_reg = readl_relaxed(kbd->io_base + MODE_CTL_REG); + + if (device_may_wakeup(&pdev->dev)) { + if (!enable_irq_wake(kbd->irq)) + kbd->irq_wake_enabled = true; + + /* + * reprogram the keyboard operating frequency as on some + * platform it may change during system suspended + */ + if (kbd->suspended_rate) + rate = kbd->suspended_rate / 1000000 - 1; + else + rate = clk_get_rate(kbd->clk) / 1000000 - 1; + + val = mode_ctl_reg & + ~(MODE_CTL_PCLK_FREQ_MSK << MODE_CTL_PCLK_FREQ_SHIFT); + val |= (rate & MODE_CTL_PCLK_FREQ_MSK) + << MODE_CTL_PCLK_FREQ_SHIFT; + writel_relaxed(val, kbd->io_base + MODE_CTL_REG); + + } else { + if (input_device_enabled(input_dev)) { + writel_relaxed(mode_ctl_reg & ~MODE_CTL_START_SCAN, + kbd->io_base + MODE_CTL_REG); + clk_disable(kbd->clk); + } + } + + /* store current configuration */ + if (input_device_enabled(input_dev)) + kbd->mode_ctl_reg = mode_ctl_reg; + + /* restore previous clk state */ + clk_disable(kbd->clk); + + mutex_unlock(&input_dev->mutex); + + return 0; +} + +static int __maybe_unused spear_kbd_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct spear_kbd *kbd = platform_get_drvdata(pdev); + struct input_dev *input_dev = kbd->input; + + mutex_lock(&input_dev->mutex); + + if (device_may_wakeup(&pdev->dev)) { + if (kbd->irq_wake_enabled) { + kbd->irq_wake_enabled = false; + disable_irq_wake(kbd->irq); + } + } else { + if (input_device_enabled(input_dev)) + clk_enable(kbd->clk); + } + + /* restore current configuration */ + if (input_device_enabled(input_dev)) + writel_relaxed(kbd->mode_ctl_reg, kbd->io_base + MODE_CTL_REG); + + mutex_unlock(&input_dev->mutex); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(spear_kbd_pm_ops, spear_kbd_suspend, spear_kbd_resume); + +#ifdef CONFIG_OF +static const struct of_device_id spear_kbd_id_table[] = { + { .compatible = "st,spear300-kbd" }, + {} +}; +MODULE_DEVICE_TABLE(of, spear_kbd_id_table); +#endif + +static struct platform_driver spear_kbd_driver = { + .probe = spear_kbd_probe, + .remove = spear_kbd_remove, + .driver = { + .name = "keyboard", + .pm = &spear_kbd_pm_ops, + .of_match_table = of_match_ptr(spear_kbd_id_table), + }, +}; +module_platform_driver(spear_kbd_driver); + +MODULE_AUTHOR("Rajeev Kumar"); +MODULE_DESCRIPTION("SPEAr Keyboard Driver"); +MODULE_LICENSE("GPL"); |