diff options
| -rw-r--r-- | drivers/input/keyboard/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/input/keyboard/Makefile | 1 | ||||
| -rw-r--r-- | drivers/input/keyboard/snvs_pwrkey.c | 227 |
3 files changed, 239 insertions, 0 deletions
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 4cd94fd6cbad..82a8fb50afac 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig | |||
| @@ -401,6 +401,17 @@ config KEYBOARD_MPR121 | |||
| 401 | To compile this driver as a module, choose M here: the | 401 | To compile this driver as a module, choose M here: the |
| 402 | module will be called mpr121_touchkey. | 402 | module will be called mpr121_touchkey. |
| 403 | 403 | ||
| 404 | config KEYBOARD_SNVS_PWRKEY | ||
| 405 | tristate "IMX SNVS Power Key Driver" | ||
| 406 | depends on SOC_IMX6SX | ||
| 407 | depends on OF | ||
| 408 | help | ||
| 409 | This is the snvs powerkey driver for the Freescale i.MX application | ||
| 410 | processors that are newer than i.MX6 SX. | ||
| 411 | |||
| 412 | To compile this driver as a module, choose M here; the | ||
| 413 | module will be called snvs_pwrkey. | ||
| 414 | |||
| 404 | config KEYBOARD_IMX | 415 | config KEYBOARD_IMX |
| 405 | tristate "IMX keypad support" | 416 | tristate "IMX keypad support" |
| 406 | depends on ARCH_MXC | 417 | depends on ARCH_MXC |
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index df28d5553c05..1d416ddf84e4 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile | |||
| @@ -51,6 +51,7 @@ obj-$(CONFIG_KEYBOARD_QT1070) += qt1070.o | |||
| 51 | obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o | 51 | obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o |
| 52 | obj-$(CONFIG_KEYBOARD_SAMSUNG) += samsung-keypad.o | 52 | obj-$(CONFIG_KEYBOARD_SAMSUNG) += samsung-keypad.o |
| 53 | obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o | 53 | obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o |
| 54 | obj-$(CONFIG_KEYBOARD_SNVS_PWRKEY) += snvs_pwrkey.o | ||
| 54 | obj-$(CONFIG_KEYBOARD_SPEAR) += spear-keyboard.o | 55 | obj-$(CONFIG_KEYBOARD_SPEAR) += spear-keyboard.o |
| 55 | obj-$(CONFIG_KEYBOARD_STMPE) += stmpe-keypad.o | 56 | obj-$(CONFIG_KEYBOARD_STMPE) += stmpe-keypad.o |
| 56 | obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o | 57 | obj-$(CONFIG_KEYBOARD_STOWAWAY) += stowaway.o |
diff --git a/drivers/input/keyboard/snvs_pwrkey.c b/drivers/input/keyboard/snvs_pwrkey.c new file mode 100644 index 000000000000..512a1fc2a864 --- /dev/null +++ b/drivers/input/keyboard/snvs_pwrkey.c | |||
| @@ -0,0 +1,227 @@ | |||
| 1 | /* | ||
| 2 | * Driver for the IMX SNVS ON/OFF Power Key | ||
| 3 | * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. | ||
| 4 | * | ||
| 5 | * The code contained herein is licensed under the GNU General Public | ||
| 6 | * License. You may obtain a copy of the GNU General Public License | ||
| 7 | * Version 2 or later at the following locations: | ||
| 8 | * | ||
| 9 | * http://www.opensource.org/licenses/gpl-license.html | ||
| 10 | * http://www.gnu.org/copyleft/gpl.html | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include <linux/device.h> | ||
| 14 | #include <linux/err.h> | ||
| 15 | #include <linux/init.h> | ||
| 16 | #include <linux/input.h> | ||
| 17 | #include <linux/interrupt.h> | ||
| 18 | #include <linux/io.h> | ||
| 19 | #include <linux/jiffies.h> | ||
| 20 | #include <linux/kernel.h> | ||
| 21 | #include <linux/module.h> | ||
| 22 | #include <linux/of.h> | ||
| 23 | #include <linux/of_address.h> | ||
| 24 | #include <linux/platform_device.h> | ||
| 25 | #include <linux/mfd/syscon.h> | ||
| 26 | #include <linux/regmap.h> | ||
| 27 | |||
| 28 | #define SNVS_LPSR_REG 0x4C /* LP Status Register */ | ||
| 29 | #define SNVS_LPCR_REG 0x38 /* LP Control Register */ | ||
| 30 | #define SNVS_HPSR_REG 0x14 | ||
| 31 | #define SNVS_HPSR_BTN BIT(6) | ||
| 32 | #define SNVS_LPSR_SPO BIT(18) | ||
| 33 | #define SNVS_LPCR_DEP_EN BIT(5) | ||
| 34 | |||
| 35 | #define DEBOUNCE_TIME 30 | ||
| 36 | #define REPEAT_INTERVAL 60 | ||
| 37 | |||
| 38 | struct pwrkey_drv_data { | ||
| 39 | struct regmap *snvs; | ||
| 40 | int irq; | ||
| 41 | int keycode; | ||
| 42 | int keystate; /* 1:pressed */ | ||
| 43 | int wakeup; | ||
| 44 | struct timer_list check_timer; | ||
| 45 | struct input_dev *input; | ||
| 46 | }; | ||
| 47 | |||
| 48 | static void imx_imx_snvs_check_for_events(unsigned long data) | ||
| 49 | { | ||
| 50 | struct pwrkey_drv_data *pdata = (struct pwrkey_drv_data *) data; | ||
| 51 | struct input_dev *input = pdata->input; | ||
| 52 | u32 state; | ||
| 53 | |||
| 54 | regmap_read(pdata->snvs, SNVS_HPSR_REG, &state); | ||
| 55 | state = state & SNVS_HPSR_BTN ? 1 : 0; | ||
| 56 | |||
| 57 | /* only report new event if status changed */ | ||
| 58 | if (state ^ pdata->keystate) { | ||
| 59 | pdata->keystate = state; | ||
| 60 | input_event(input, EV_KEY, pdata->keycode, state); | ||
| 61 | input_sync(input); | ||
| 62 | pm_relax(pdata->input->dev.parent); | ||
| 63 | } | ||
| 64 | |||
| 65 | /* repeat check if pressed long */ | ||
| 66 | if (state) { | ||
| 67 | mod_timer(&pdata->check_timer, | ||
| 68 | jiffies + msecs_to_jiffies(REPEAT_INTERVAL)); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | static irqreturn_t imx_snvs_pwrkey_interrupt(int irq, void *dev_id) | ||
| 73 | { | ||
| 74 | struct platform_device *pdev = dev_id; | ||
| 75 | struct pwrkey_drv_data *pdata = platform_get_drvdata(pdev); | ||
| 76 | u32 lp_status; | ||
| 77 | |||
| 78 | pm_wakeup_event(pdata->input->dev.parent, 0); | ||
| 79 | |||
| 80 | regmap_read(pdata->snvs, SNVS_LPSR_REG, &lp_status); | ||
| 81 | if (lp_status & SNVS_LPSR_SPO) | ||
| 82 | mod_timer(&pdata->check_timer, jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); | ||
| 83 | |||
| 84 | /* clear SPO status */ | ||
| 85 | regmap_write(pdata->snvs, SNVS_LPSR_REG, SNVS_LPSR_SPO); | ||
| 86 | |||
| 87 | return IRQ_HANDLED; | ||
| 88 | } | ||
| 89 | |||
| 90 | static void imx_snvs_pwrkey_act(void *pdata) | ||
| 91 | { | ||
| 92 | struct pwrkey_drv_data *pd = pdata; | ||
| 93 | |||
| 94 | del_timer_sync(&pd->check_timer); | ||
| 95 | } | ||
| 96 | |||
| 97 | static int imx_snvs_pwrkey_probe(struct platform_device *pdev) | ||
| 98 | { | ||
| 99 | struct pwrkey_drv_data *pdata = NULL; | ||
| 100 | struct input_dev *input = NULL; | ||
| 101 | struct device_node *np; | ||
| 102 | int error; | ||
| 103 | |||
| 104 | /* Get SNVS register Page */ | ||
| 105 | np = pdev->dev.of_node; | ||
| 106 | if (!np) | ||
| 107 | return -ENODEV; | ||
| 108 | |||
| 109 | pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | ||
| 110 | if (!pdata) | ||
| 111 | return -ENOMEM; | ||
| 112 | |||
| 113 | pdata->snvs = syscon_regmap_lookup_by_phandle(np, "regmap");; | ||
| 114 | |||
| 115 | if (!pdata->snvs) { | ||
| 116 | dev_err(&pdev->dev, "Can't get snvs syscon\n"); | ||
| 117 | return -ENODEV; | ||
| 118 | } | ||
| 119 | |||
| 120 | if (of_property_read_u32(np, "linux,keycode", &pdata->keycode)) { | ||
| 121 | pdata->keycode = KEY_POWER; | ||
| 122 | dev_warn(&pdev->dev, "KEY_POWER without setting in dts\n"); | ||
| 123 | } | ||
| 124 | |||
| 125 | pdata->wakeup = of_property_read_bool(np, "wakeup"); | ||
| 126 | |||
| 127 | pdata->irq = platform_get_irq(pdev, 0); | ||
| 128 | if (pdata->irq < 0) { | ||
| 129 | dev_err(&pdev->dev, "no irq defined in platform data\n"); | ||
| 130 | return -EINVAL; | ||
| 131 | } | ||
| 132 | |||
| 133 | regmap_update_bits(pdata->snvs, SNVS_LPCR_REG, SNVS_LPCR_DEP_EN, SNVS_LPCR_DEP_EN); | ||
| 134 | |||
| 135 | /* clear the unexpected interrupt before driver ready */ | ||
| 136 | regmap_write(pdata->snvs, SNVS_LPSR_REG, SNVS_LPSR_SPO); | ||
| 137 | |||
| 138 | setup_timer(&pdata->check_timer, | ||
| 139 | imx_imx_snvs_check_for_events, (unsigned long) pdata); | ||
| 140 | |||
| 141 | input = devm_input_allocate_device(&pdev->dev); | ||
| 142 | if (!input) { | ||
| 143 | dev_err(&pdev->dev, "failed to allocate the input device\n"); | ||
| 144 | return -ENOMEM; | ||
| 145 | } | ||
| 146 | |||
| 147 | input->name = pdev->name; | ||
| 148 | input->phys = "snvs-pwrkey/input0"; | ||
| 149 | input->id.bustype = BUS_HOST; | ||
| 150 | |||
| 151 | input_set_capability(input, EV_KEY, pdata->keycode); | ||
| 152 | |||
| 153 | /* input customer action to cancel release timer */ | ||
| 154 | error = devm_add_action(&pdev->dev, imx_snvs_pwrkey_act, pdata); | ||
| 155 | if (error) { | ||
| 156 | dev_err(&pdev->dev, "failed to register remove action\n"); | ||
| 157 | return error; | ||
| 158 | } | ||
| 159 | |||
| 160 | error = devm_request_irq(&pdev->dev, pdata->irq, | ||
| 161 | imx_snvs_pwrkey_interrupt, | ||
| 162 | 0, pdev->name, pdev); | ||
| 163 | |||
| 164 | if (error) { | ||
| 165 | dev_err(&pdev->dev, "interrupt not available.\n"); | ||
| 166 | return error; | ||
| 167 | } | ||
| 168 | |||
| 169 | error = input_register_device(input); | ||
| 170 | if (error < 0) { | ||
| 171 | dev_err(&pdev->dev, "failed to register input device\n"); | ||
