|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * OMAP4 Keypad Driver | 
|  | * | 
|  | * Copyright (C) 2010 Texas Instruments | 
|  | * | 
|  | * Author: Abraham Arce <x0066660@ti.com> | 
|  | * Initial Code: Syed Rafiuddin <rafiuddin.syed@ti.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/clk.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/input.h> | 
|  | #include <linux/input/matrix_keypad.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/pm_runtime.h> | 
|  | #include <linux/pm_wakeirq.h> | 
|  |  | 
|  | /* OMAP4 registers */ | 
|  | #define OMAP4_KBD_REVISION		0x00 | 
|  | #define OMAP4_KBD_SYSCONFIG		0x10 | 
|  | #define OMAP4_KBD_SYSSTATUS		0x14 | 
|  | #define OMAP4_KBD_IRQSTATUS		0x18 | 
|  | #define OMAP4_KBD_IRQENABLE		0x1C | 
|  | #define OMAP4_KBD_WAKEUPENABLE		0x20 | 
|  | #define OMAP4_KBD_PENDING		0x24 | 
|  | #define OMAP4_KBD_CTRL			0x28 | 
|  | #define OMAP4_KBD_DEBOUNCINGTIME	0x2C | 
|  | #define OMAP4_KBD_LONGKEYTIME		0x30 | 
|  | #define OMAP4_KBD_TIMEOUT		0x34 | 
|  | #define OMAP4_KBD_STATEMACHINE		0x38 | 
|  | #define OMAP4_KBD_ROWINPUTS		0x3C | 
|  | #define OMAP4_KBD_COLUMNOUTPUTS		0x40 | 
|  | #define OMAP4_KBD_FULLCODE31_0		0x44 | 
|  | #define OMAP4_KBD_FULLCODE63_32		0x48 | 
|  |  | 
|  | /* OMAP4 bit definitions */ | 
|  | #define OMAP4_DEF_IRQENABLE_EVENTEN	BIT(0) | 
|  | #define OMAP4_DEF_IRQENABLE_LONGKEY	BIT(1) | 
|  | #define OMAP4_DEF_WUP_EVENT_ENA		BIT(0) | 
|  | #define OMAP4_DEF_WUP_LONG_KEY_ENA	BIT(1) | 
|  | #define OMAP4_DEF_CTRL_NOSOFTMODE	BIT(1) | 
|  | #define OMAP4_DEF_CTRL_PTV_SHIFT	2 | 
|  |  | 
|  | /* OMAP4 values */ | 
|  | #define OMAP4_VAL_IRQDISABLE		0x0 | 
|  |  | 
|  | /* | 
|  | * Errata i689: If a key is released for a time shorter than debounce time, | 
|  | * the keyboard will idle and never detect the key release. The workaround | 
|  | * is to use at least a 12ms debounce time. See omap5432 TRM chapter | 
|  | * "26.4.6.2 Keyboard Controller Timer" for more information. | 
|  | */ | 
|  | #define OMAP4_KEYPAD_PTV_DIV_128        0x6 | 
|  | #define OMAP4_KEYPAD_DEBOUNCINGTIME_MS(dbms, ptv)     \ | 
|  | ((((dbms) * 1000) / ((1 << ((ptv) + 1)) * (1000000 / 32768))) - 1) | 
|  | #define OMAP4_VAL_DEBOUNCINGTIME_16MS					\ | 
|  | OMAP4_KEYPAD_DEBOUNCINGTIME_MS(16, OMAP4_KEYPAD_PTV_DIV_128) | 
|  | #define OMAP4_KEYPAD_AUTOIDLE_MS	50	/* Approximate measured time */ | 
|  | #define OMAP4_KEYPAD_IDLE_CHECK_MS	(OMAP4_KEYPAD_AUTOIDLE_MS / 2) | 
|  |  | 
|  | enum { | 
|  | KBD_REVISION_OMAP4 = 0, | 
|  | KBD_REVISION_OMAP5, | 
|  | }; | 
|  |  | 
|  | struct omap4_keypad { | 
|  | struct input_dev *input; | 
|  |  | 
|  | void __iomem *base; | 
|  | unsigned int irq; | 
|  | struct mutex lock;		/* for key scan */ | 
|  |  | 
|  | unsigned int rows; | 
|  | unsigned int cols; | 
|  | u32 reg_offset; | 
|  | u32 irqreg_offset; | 
|  | unsigned int row_shift; | 
|  | bool no_autorepeat; | 
|  | u64 keys; | 
|  | unsigned short *keymap; | 
|  | struct clk *fck; | 
|  | }; | 
|  |  | 
|  | static int kbd_readl(struct omap4_keypad *keypad_data, u32 offset) | 
|  | { | 
|  | return __raw_readl(keypad_data->base + | 
|  | keypad_data->reg_offset + offset); | 
|  | } | 
|  |  | 
|  | static void kbd_writel(struct omap4_keypad *keypad_data, u32 offset, u32 value) | 
|  | { | 
|  | __raw_writel(value, | 
|  | keypad_data->base + keypad_data->reg_offset + offset); | 
|  | } | 
|  |  | 
|  | static int kbd_read_irqreg(struct omap4_keypad *keypad_data, u32 offset) | 
|  | { | 
|  | return __raw_readl(keypad_data->base + | 
|  | keypad_data->irqreg_offset + offset); | 
|  | } | 
|  |  | 
|  | static void kbd_write_irqreg(struct omap4_keypad *keypad_data, | 
|  | u32 offset, u32 value) | 
|  | { | 
|  | __raw_writel(value, | 
|  | keypad_data->base + keypad_data->irqreg_offset + offset); | 
|  | } | 
|  |  | 
|  | static int omap4_keypad_report_keys(struct omap4_keypad *keypad_data, | 
|  | u64 keys, bool down) | 
|  | { | 
|  | struct input_dev *input_dev = keypad_data->input; | 
|  | unsigned int col, row, code; | 
|  | DECLARE_BITMAP(mask, 64); | 
|  | unsigned long bit; | 
|  | int events = 0; | 
|  |  | 
|  | bitmap_from_u64(mask, keys); | 
|  |  | 
|  | for_each_set_bit(bit, mask, keypad_data->rows * BITS_PER_BYTE) { | 
|  | row = bit / BITS_PER_BYTE; | 
|  | col = bit % BITS_PER_BYTE; | 
|  | code = MATRIX_SCAN_CODE(row, col, keypad_data->row_shift); | 
|  |  | 
|  | input_event(input_dev, EV_MSC, MSC_SCAN, code); | 
|  | input_report_key(input_dev, keypad_data->keymap[code], down); | 
|  |  | 
|  | events++; | 
|  | } | 
|  |  | 
|  | if (events) | 
|  | input_sync(input_dev); | 
|  |  | 
|  | return events; | 
|  | } | 
|  |  | 
|  | static void omap4_keypad_scan_keys(struct omap4_keypad *keypad_data, u64 keys) | 
|  | { | 
|  | u64 changed; | 
|  |  | 
|  | guard(mutex)(&keypad_data->lock); | 
|  |  | 
|  | changed = keys ^ keypad_data->keys; | 
|  |  | 
|  | /* | 
|  | * Report key up events separately and first. This matters in case we | 
|  | * lost key-up interrupt and just now catching up. | 
|  | */ | 
|  | omap4_keypad_report_keys(keypad_data, changed & ~keys, false); | 
|  |  | 
|  | /* Report key down events */ | 
|  | omap4_keypad_report_keys(keypad_data, changed & keys, true); | 
|  |  | 
|  | keypad_data->keys = keys; | 
|  | } | 
|  |  | 
|  | /* Interrupt handlers */ | 
|  | static irqreturn_t omap4_keypad_irq_handler(int irq, void *dev_id) | 
|  | { | 
|  | struct omap4_keypad *keypad_data = dev_id; | 
|  |  | 
|  | if (kbd_read_irqreg(keypad_data, OMAP4_KBD_IRQSTATUS)) | 
|  | return IRQ_WAKE_THREAD; | 
|  |  | 
|  | return IRQ_NONE; | 
|  | } | 
|  |  | 
|  | static irqreturn_t omap4_keypad_irq_thread_fn(int irq, void *dev_id) | 
|  | { | 
|  | struct omap4_keypad *keypad_data = dev_id; | 
|  | struct device *dev = keypad_data->input->dev.parent; | 
|  | u32 low, high; | 
|  | int error; | 
|  | u64 keys; | 
|  |  | 
|  | error = pm_runtime_resume_and_get(dev); | 
|  | if (error) | 
|  | return IRQ_NONE; | 
|  |  | 
|  | low = kbd_readl(keypad_data, OMAP4_KBD_FULLCODE31_0); | 
|  | high = kbd_readl(keypad_data, OMAP4_KBD_FULLCODE63_32); | 
|  | keys = low | (u64)high << 32; | 
|  |  | 
|  | omap4_keypad_scan_keys(keypad_data, keys); | 
|  |  | 
|  | /* clear pending interrupts */ | 
|  | kbd_write_irqreg(keypad_data, OMAP4_KBD_IRQSTATUS, | 
|  | kbd_read_irqreg(keypad_data, OMAP4_KBD_IRQSTATUS)); | 
|  |  | 
|  | pm_runtime_mark_last_busy(dev); | 
|  | pm_runtime_put_autosuspend(dev); | 
|  |  | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static int omap4_keypad_open(struct input_dev *input) | 
|  | { | 
|  | struct omap4_keypad *keypad_data = input_get_drvdata(input); | 
|  | struct device *dev = input->dev.parent; | 
|  | int error; | 
|  |  | 
|  | error = pm_runtime_resume_and_get(dev); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | error = clk_prepare_enable(keypad_data->fck); | 
|  | if (error) | 
|  | goto out; | 
|  |  | 
|  | disable_irq(keypad_data->irq); | 
|  |  | 
|  | kbd_writel(keypad_data, OMAP4_KBD_CTRL, | 
|  | OMAP4_DEF_CTRL_NOSOFTMODE | | 
|  | (OMAP4_KEYPAD_PTV_DIV_128 << OMAP4_DEF_CTRL_PTV_SHIFT)); | 
|  | kbd_writel(keypad_data, OMAP4_KBD_DEBOUNCINGTIME, | 
|  | OMAP4_VAL_DEBOUNCINGTIME_16MS); | 
|  | /* clear pending interrupts */ | 
|  | kbd_write_irqreg(keypad_data, OMAP4_KBD_IRQSTATUS, | 
|  | kbd_read_irqreg(keypad_data, OMAP4_KBD_IRQSTATUS)); | 
|  | kbd_write_irqreg(keypad_data, OMAP4_KBD_IRQENABLE, | 
|  | OMAP4_DEF_IRQENABLE_EVENTEN); | 
|  | kbd_writel(keypad_data, OMAP4_KBD_WAKEUPENABLE, | 
|  | OMAP4_DEF_WUP_EVENT_ENA); | 
|  |  | 
|  | enable_irq(keypad_data->irq); | 
|  |  | 
|  | out: | 
|  | pm_runtime_mark_last_busy(dev); | 
|  | pm_runtime_put_autosuspend(dev); | 
|  |  | 
|  | return error; | 
|  | } | 
|  |  | 
|  | static void omap4_keypad_stop(struct omap4_keypad *keypad_data) | 
|  | { | 
|  | /* Disable interrupts and wake-up events */ | 
|  | kbd_write_irqreg(keypad_data, OMAP4_KBD_IRQENABLE, | 
|  | OMAP4_VAL_IRQDISABLE); | 
|  | kbd_writel(keypad_data, OMAP4_KBD_WAKEUPENABLE, 0); | 
|  |  | 
|  | /* clear pending interrupts */ | 
|  | kbd_write_irqreg(keypad_data, OMAP4_KBD_IRQSTATUS, | 
|  | kbd_read_irqreg(keypad_data, OMAP4_KBD_IRQSTATUS)); | 
|  | } | 
|  |  | 
|  | static void omap4_keypad_close(struct input_dev *input) | 
|  | { | 
|  | struct omap4_keypad *keypad_data = input_get_drvdata(input); | 
|  | struct device *dev = input->dev.parent; | 
|  | int error; | 
|  |  | 
|  | error = pm_runtime_resume_and_get(dev); | 
|  | if (error) | 
|  | dev_err(dev, "%s: pm_runtime_resume_and_get() failed: %d\n", | 
|  | __func__, error); | 
|  |  | 
|  | disable_irq(keypad_data->irq); | 
|  | omap4_keypad_stop(keypad_data); | 
|  | enable_irq(keypad_data->irq); | 
|  | clk_disable_unprepare(keypad_data->fck); | 
|  |  | 
|  | pm_runtime_mark_last_busy(dev); | 
|  | pm_runtime_put_autosuspend(dev); | 
|  | } | 
|  |  | 
|  | static int omap4_keypad_parse_dt(struct device *dev, | 
|  | struct omap4_keypad *keypad_data) | 
|  | { | 
|  | struct device_node *np = dev->of_node; | 
|  | int err; | 
|  |  | 
|  | err = matrix_keypad_parse_properties(dev, &keypad_data->rows, | 
|  | &keypad_data->cols); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | keypad_data->no_autorepeat = of_property_read_bool(np, "linux,input-no-autorepeat"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int omap4_keypad_check_revision(struct device *dev, | 
|  | struct omap4_keypad *keypad_data) | 
|  | { | 
|  | unsigned int rev; | 
|  |  | 
|  | rev = __raw_readl(keypad_data->base + OMAP4_KBD_REVISION); | 
|  | rev &= 0x03 << 30; | 
|  | rev >>= 30; | 
|  | switch (rev) { | 
|  | case KBD_REVISION_OMAP4: | 
|  | keypad_data->reg_offset = 0x00; | 
|  | keypad_data->irqreg_offset = 0x00; | 
|  | break; | 
|  | case KBD_REVISION_OMAP5: | 
|  | keypad_data->reg_offset = 0x10; | 
|  | keypad_data->irqreg_offset = 0x0c; | 
|  | break; | 
|  | default: | 
|  | dev_err(dev, "Keypad reports unsupported revision %d", rev); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Errata ID i689 "1.32 Keyboard Key Up Event Can Be Missed". | 
|  | * Interrupt may not happen for key-up events. We must clear stuck | 
|  | * key-up events after the keyboard hardware has auto-idled. | 
|  | */ | 
|  | static int omap4_keypad_runtime_suspend(struct device *dev) | 
|  | { | 
|  | struct platform_device *pdev = to_platform_device(dev); | 
|  | struct omap4_keypad *keypad_data = platform_get_drvdata(pdev); | 
|  | u32 active; | 
|  |  | 
|  | active = kbd_readl(keypad_data, OMAP4_KBD_STATEMACHINE); | 
|  | if (active) { | 
|  | pm_runtime_mark_last_busy(dev); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | omap4_keypad_scan_keys(keypad_data, 0); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dev_pm_ops omap4_keypad_pm_ops = { | 
|  | RUNTIME_PM_OPS(omap4_keypad_runtime_suspend, NULL, NULL) | 
|  | }; | 
|  |  | 
|  | static void omap4_disable_pm(void *d) | 
|  | { | 
|  | pm_runtime_dont_use_autosuspend(d); | 
|  | pm_runtime_disable(d); | 
|  | } | 
|  |  | 
|  | static int omap4_keypad_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct omap4_keypad *keypad_data; | 
|  | struct input_dev *input_dev; | 
|  | unsigned int max_keys; | 
|  | int irq; | 
|  | int error; | 
|  |  | 
|  | irq = platform_get_irq(pdev, 0); | 
|  | if (irq < 0) | 
|  | return irq; | 
|  |  | 
|  | keypad_data = devm_kzalloc(dev, sizeof(*keypad_data), GFP_KERNEL); | 
|  | if (!keypad_data) { | 
|  | dev_err(dev, "keypad_data memory allocation failed\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | keypad_data->irq = irq; | 
|  | keypad_data->fck = devm_clk_get(&pdev->dev, "fck"); | 
|  | if (IS_ERR(keypad_data->fck)) | 
|  | return dev_err_probe(&pdev->dev, PTR_ERR(keypad_data->fck), | 
|  | "unable to get fck"); | 
|  |  | 
|  | mutex_init(&keypad_data->lock); | 
|  | platform_set_drvdata(pdev, keypad_data); | 
|  |  | 
|  | error = omap4_keypad_parse_dt(dev, keypad_data); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | keypad_data->base = devm_platform_ioremap_resource(pdev, 0); | 
|  | if (IS_ERR(keypad_data->base)) | 
|  | return PTR_ERR(keypad_data->base); | 
|  |  | 
|  | pm_runtime_use_autosuspend(dev); | 
|  | pm_runtime_set_autosuspend_delay(dev, OMAP4_KEYPAD_IDLE_CHECK_MS); | 
|  | pm_runtime_enable(dev); | 
|  |  | 
|  | error = devm_add_action_or_reset(dev, omap4_disable_pm, dev); | 
|  | if (error) { | 
|  | dev_err(dev, "unable to register cleanup action\n"); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Enable clocks for the keypad module so that we can read | 
|  | * revision register. | 
|  | */ | 
|  | error = pm_runtime_resume_and_get(dev); | 
|  | if (error) { | 
|  | dev_err(dev, "pm_runtime_resume_and_get() failed\n"); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | error = omap4_keypad_check_revision(dev, keypad_data); | 
|  | if (!error) { | 
|  | /* Ensure device does not raise interrupts */ | 
|  | omap4_keypad_stop(keypad_data); | 
|  | } | 
|  |  | 
|  | pm_runtime_mark_last_busy(dev); | 
|  | pm_runtime_put_autosuspend(dev); | 
|  | if (error) | 
|  | return error; | 
|  |  | 
|  | /* input device allocation */ | 
|  | keypad_data->input = input_dev = devm_input_allocate_device(dev); | 
|  | if (!input_dev) | 
|  | return -ENOMEM; | 
|  |  | 
|  | input_dev->name = pdev->name; | 
|  | input_dev->id.bustype = BUS_HOST; | 
|  | input_dev->id.vendor = 0x0001; | 
|  | input_dev->id.product = 0x0001; | 
|  | input_dev->id.version = 0x0001; | 
|  |  | 
|  | input_dev->open = omap4_keypad_open; | 
|  | input_dev->close = omap4_keypad_close; | 
|  |  | 
|  | input_set_capability(input_dev, EV_MSC, MSC_SCAN); | 
|  | if (!keypad_data->no_autorepeat) | 
|  | __set_bit(EV_REP, input_dev->evbit); | 
|  |  | 
|  | input_set_drvdata(input_dev, keypad_data); | 
|  |  | 
|  | keypad_data->row_shift = get_count_order(keypad_data->cols); | 
|  | max_keys = keypad_data->rows << keypad_data->row_shift; | 
|  | keypad_data->keymap = devm_kcalloc(dev, | 
|  | max_keys, | 
|  | sizeof(keypad_data->keymap[0]), | 
|  | GFP_KERNEL); | 
|  | if (!keypad_data->keymap) { | 
|  | dev_err(dev, "Not enough memory for keymap\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | error = matrix_keypad_build_keymap(NULL, NULL, | 
|  | keypad_data->rows, keypad_data->cols, | 
|  | keypad_data->keymap, input_dev); | 
|  | if (error) { | 
|  | dev_err(dev, "failed to build keymap\n"); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | error = devm_request_threaded_irq(dev, keypad_data->irq, | 
|  | omap4_keypad_irq_handler, | 
|  | omap4_keypad_irq_thread_fn, | 
|  | IRQF_ONESHOT, | 
|  | "omap4-keypad", keypad_data); | 
|  | if (error) { | 
|  | dev_err(dev, "failed to register interrupt\n"); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | error = input_register_device(keypad_data->input); | 
|  | if (error) { | 
|  | dev_err(dev, "failed to register input device\n"); | 
|  | return error; | 
|  | } | 
|  |  | 
|  | device_init_wakeup(dev, true); | 
|  | error = dev_pm_set_wake_irq(dev, keypad_data->irq); | 
|  | if (error) | 
|  | dev_warn(dev, "failed to set up wakeup irq: %d\n", error); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void omap4_keypad_remove(struct platform_device *pdev) | 
|  | { | 
|  | dev_pm_clear_wake_irq(&pdev->dev); | 
|  | } | 
|  |  | 
|  | static const struct of_device_id omap_keypad_dt_match[] = { | 
|  | { .compatible = "ti,omap4-keypad" }, | 
|  | {}, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, omap_keypad_dt_match); | 
|  |  | 
|  | static struct platform_driver omap4_keypad_driver = { | 
|  | .probe		= omap4_keypad_probe, | 
|  | .remove		= omap4_keypad_remove, | 
|  | .driver		= { | 
|  | .name	= "omap4-keypad", | 
|  | .of_match_table = omap_keypad_dt_match, | 
|  | .pm = pm_ptr(&omap4_keypad_pm_ops), | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(omap4_keypad_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Texas Instruments"); | 
|  | MODULE_DESCRIPTION("OMAP4 Keypad Driver"); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_ALIAS("platform:omap4-keypad"); |