Commit | Line | Data |
---|---|---|
9eb9cc93 PPL |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (C) STMicroelectronics 2018 | |
3 | // Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics. | |
4 | ||
5 | #include <linux/input.h> | |
6 | #include <linux/interrupt.h> | |
7 | #include <linux/mfd/stpmic1.h> | |
8 | #include <linux/module.h> | |
9 | #include <linux/of.h> | |
10 | #include <linux/platform_device.h> | |
11 | #include <linux/property.h> | |
12 | #include <linux/regmap.h> | |
13 | ||
14 | /** | |
15 | * struct stpmic1_onkey - OnKey data | |
16 | * @input_dev: pointer to input device | |
17 | * @irq_falling: irq that we are hooked on to | |
18 | * @irq_rising: irq that we are hooked on to | |
19 | */ | |
20 | struct stpmic1_onkey { | |
21 | struct input_dev *input_dev; | |
22 | int irq_falling; | |
23 | int irq_rising; | |
24 | }; | |
25 | ||
26 | static irqreturn_t onkey_falling_irq(int irq, void *ponkey) | |
27 | { | |
28 | struct stpmic1_onkey *onkey = ponkey; | |
29 | struct input_dev *input_dev = onkey->input_dev; | |
30 | ||
31 | input_report_key(input_dev, KEY_POWER, 1); | |
32 | pm_wakeup_event(input_dev->dev.parent, 0); | |
33 | input_sync(input_dev); | |
34 | ||
35 | return IRQ_HANDLED; | |
36 | } | |
37 | ||
38 | static irqreturn_t onkey_rising_irq(int irq, void *ponkey) | |
39 | { | |
40 | struct stpmic1_onkey *onkey = ponkey; | |
41 | struct input_dev *input_dev = onkey->input_dev; | |
42 | ||
43 | input_report_key(input_dev, KEY_POWER, 0); | |
44 | pm_wakeup_event(input_dev->dev.parent, 0); | |
45 | input_sync(input_dev); | |
46 | ||
47 | return IRQ_HANDLED; | |
48 | } | |
49 | ||
50 | static int stpmic1_onkey_probe(struct platform_device *pdev) | |
51 | { | |
52 | struct stpmic1 *pmic = dev_get_drvdata(pdev->dev.parent); | |
53 | struct device *dev = &pdev->dev; | |
54 | struct input_dev *input_dev; | |
55 | struct stpmic1_onkey *onkey; | |
56 | unsigned int val, reg = 0; | |
57 | int error; | |
58 | ||
59 | onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL); | |
60 | if (!onkey) | |
61 | return -ENOMEM; | |
62 | ||
63 | onkey->irq_falling = platform_get_irq_byname(pdev, "onkey-falling"); | |
64 | if (onkey->irq_falling < 0) { | |
65 | dev_err(dev, "failed: request IRQ onkey-falling %d\n", | |
66 | onkey->irq_falling); | |
67 | return onkey->irq_falling; | |
68 | } | |
69 | ||
70 | onkey->irq_rising = platform_get_irq_byname(pdev, "onkey-rising"); | |
71 | if (onkey->irq_rising < 0) { | |
72 | dev_err(dev, "failed: request IRQ onkey-rising %d\n", | |
73 | onkey->irq_rising); | |
74 | return onkey->irq_rising; | |
75 | } | |
76 | ||
77 | if (!device_property_read_u32(dev, "power-off-time-sec", &val)) { | |
78 | if (val > 0 && val <= 16) { | |
79 | dev_dbg(dev, "power-off-time=%d seconds\n", val); | |
80 | reg |= PONKEY_PWR_OFF; | |
81 | reg |= ((16 - val) & PONKEY_TURNOFF_TIMER_MASK); | |
82 | } else { | |
83 | dev_err(dev, "power-off-time-sec out of range\n"); | |
84 | return -EINVAL; | |
85 | } | |
86 | } | |
87 | ||
88 | if (device_property_present(dev, "st,onkey-clear-cc-flag")) | |
89 | reg |= PONKEY_CC_FLAG_CLEAR; | |
90 | ||
91 | error = regmap_update_bits(pmic->regmap, PKEY_TURNOFF_CR, | |
92 | PONKEY_TURNOFF_MASK, reg); | |
93 | if (error) { | |
94 | dev_err(dev, "PKEY_TURNOFF_CR write failed: %d\n", error); | |
95 | return error; | |
96 | } | |
97 | ||
98 | if (device_property_present(dev, "st,onkey-pu-inactive")) { | |
99 | error = regmap_update_bits(pmic->regmap, PADS_PULL_CR, | |
100 | PONKEY_PU_INACTIVE, | |
101 | PONKEY_PU_INACTIVE); | |
102 | if (error) { | |
103 | dev_err(dev, "ONKEY Pads configuration failed: %d\n", | |
104 | error); | |
105 | return error; | |
106 | } | |
107 | } | |
108 | ||
109 | input_dev = devm_input_allocate_device(dev); | |
110 | if (!input_dev) { | |
111 | dev_err(dev, "Can't allocate Pwr Onkey Input Device\n"); | |
112 | return -ENOMEM; | |
113 | } | |
114 | ||
115 | input_dev->name = "pmic_onkey"; | |
116 | input_dev->phys = "pmic_onkey/input0"; | |
117 | ||
118 | input_set_capability(input_dev, EV_KEY, KEY_POWER); | |
119 | ||
120 | onkey->input_dev = input_dev; | |
121 | ||
122 | /* interrupt is nested in a thread */ | |
123 | error = devm_request_threaded_irq(dev, onkey->irq_falling, NULL, | |
124 | onkey_falling_irq, IRQF_ONESHOT, | |
125 | dev_name(dev), onkey); | |
126 | if (error) { | |
127 | dev_err(dev, "Can't get IRQ Onkey Falling: %d\n", error); | |
128 | return error; | |
129 | } | |
130 | ||
131 | error = devm_request_threaded_irq(dev, onkey->irq_rising, NULL, | |
132 | onkey_rising_irq, IRQF_ONESHOT, | |
133 | dev_name(dev), onkey); | |
134 | if (error) { | |
135 | dev_err(dev, "Can't get IRQ Onkey Rising: %d\n", error); | |
136 | return error; | |
137 | } | |
138 | ||
139 | error = input_register_device(input_dev); | |
140 | if (error) { | |
141 | dev_err(dev, "Can't register power button: %d\n", error); | |
142 | return error; | |
143 | } | |
144 | ||
145 | platform_set_drvdata(pdev, onkey); | |
146 | device_init_wakeup(dev, true); | |
147 | ||
148 | return 0; | |
149 | } | |
150 | ||
151 | static int __maybe_unused stpmic1_onkey_suspend(struct device *dev) | |
152 | { | |
153 | struct platform_device *pdev = to_platform_device(dev); | |
154 | struct stpmic1_onkey *onkey = platform_get_drvdata(pdev); | |
155 | ||
156 | if (device_may_wakeup(dev)) { | |
157 | enable_irq_wake(onkey->irq_falling); | |
158 | enable_irq_wake(onkey->irq_rising); | |
159 | } | |
160 | return 0; | |
161 | } | |
162 | ||
163 | static int __maybe_unused stpmic1_onkey_resume(struct device *dev) | |
164 | { | |
165 | struct platform_device *pdev = to_platform_device(dev); | |
166 | struct stpmic1_onkey *onkey = platform_get_drvdata(pdev); | |
167 | ||
168 | if (device_may_wakeup(dev)) { | |
169 | disable_irq_wake(onkey->irq_falling); | |
170 | disable_irq_wake(onkey->irq_rising); | |
171 | } | |
172 | return 0; | |
173 | } | |
174 | ||
175 | static SIMPLE_DEV_PM_OPS(stpmic1_onkey_pm, | |
176 | stpmic1_onkey_suspend, | |
177 | stpmic1_onkey_resume); | |
178 | ||
179 | static const struct of_device_id of_stpmic1_onkey_match[] = { | |
180 | { .compatible = "st,stpmic1-onkey" }, | |
181 | { }, | |
182 | }; | |
183 | ||
184 | MODULE_DEVICE_TABLE(of, of_stpmic1_onkey_match); | |
185 | ||
186 | static struct platform_driver stpmic1_onkey_driver = { | |
187 | .probe = stpmic1_onkey_probe, | |
188 | .driver = { | |
189 | .name = "stpmic1_onkey", | |
190 | .of_match_table = of_match_ptr(of_stpmic1_onkey_match), | |
191 | .pm = &stpmic1_onkey_pm, | |
192 | }, | |
193 | }; | |
194 | module_platform_driver(stpmic1_onkey_driver); | |
195 | ||
196 | MODULE_DESCRIPTION("Onkey driver for STPMIC1"); | |
197 | MODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>"); | |
198 | MODULE_LICENSE("GPL v2"); |