1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // Copyright 2022 NXP. |
4 | |
5 | #include <linux/device.h> |
6 | #include <linux/err.h> |
7 | #include <linux/init.h> |
8 | #include <linux/input.h> |
9 | #include <linux/interrupt.h> |
10 | #include <linux/io.h> |
11 | #include <linux/jiffies.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/mfd/syscon.h> |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> |
16 | #include <linux/of_address.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/pm_wakeirq.h> |
19 | #include <linux/regmap.h> |
20 | |
21 | #define BBNSM_CTRL 0x8 |
22 | #define BBNSM_INT_EN 0x10 |
23 | #define BBNSM_EVENTS 0x14 |
24 | #define BBNSM_PAD_CTRL 0x24 |
25 | |
26 | #define BBNSM_BTN_PRESSED BIT(7) |
27 | #define BBNSM_PWR_ON BIT(6) |
28 | #define BBNSM_BTN_OFF BIT(5) |
29 | #define BBNSM_EMG_OFF BIT(4) |
30 | #define BBNSM_PWRKEY_EVENTS (BBNSM_PWR_ON | BBNSM_BTN_OFF | BBNSM_EMG_OFF) |
31 | #define BBNSM_DP_EN BIT(24) |
32 | |
33 | #define DEBOUNCE_TIME 30 |
34 | #define REPEAT_INTERVAL 60 |
35 | |
36 | struct bbnsm_pwrkey { |
37 | struct regmap *regmap; |
38 | int irq; |
39 | int keycode; |
40 | int keystate; /* 1:pressed */ |
41 | struct timer_list check_timer; |
42 | struct input_dev *input; |
43 | }; |
44 | |
45 | static void bbnsm_pwrkey_check_for_events(struct timer_list *t) |
46 | { |
47 | struct bbnsm_pwrkey *bbnsm = from_timer(bbnsm, t, check_timer); |
48 | struct input_dev *input = bbnsm->input; |
49 | u32 state; |
50 | |
51 | regmap_read(map: bbnsm->regmap, BBNSM_EVENTS, val: &state); |
52 | |
53 | state = state & BBNSM_BTN_PRESSED ? 1 : 0; |
54 | |
55 | /* only report new event if status changed */ |
56 | if (state ^ bbnsm->keystate) { |
57 | bbnsm->keystate = state; |
58 | input_event(dev: input, EV_KEY, code: bbnsm->keycode, value: state); |
59 | input_sync(dev: input); |
60 | pm_relax(dev: bbnsm->input->dev.parent); |
61 | } |
62 | |
63 | /* repeat check if pressed long */ |
64 | if (state) |
65 | mod_timer(timer: &bbnsm->check_timer, |
66 | expires: jiffies + msecs_to_jiffies(REPEAT_INTERVAL)); |
67 | } |
68 | |
69 | static irqreturn_t bbnsm_pwrkey_interrupt(int irq, void *dev_id) |
70 | { |
71 | struct platform_device *pdev = dev_id; |
72 | struct bbnsm_pwrkey *bbnsm = platform_get_drvdata(pdev); |
73 | u32 event; |
74 | |
75 | regmap_read(map: bbnsm->regmap, BBNSM_EVENTS, val: &event); |
76 | if (!(event & BBNSM_BTN_OFF)) |
77 | return IRQ_NONE; |
78 | |
79 | pm_wakeup_event(dev: bbnsm->input->dev.parent, msec: 0); |
80 | |
81 | mod_timer(timer: &bbnsm->check_timer, |
82 | expires: jiffies + msecs_to_jiffies(DEBOUNCE_TIME)); |
83 | |
84 | /* clear PWR OFF */ |
85 | regmap_write(map: bbnsm->regmap, BBNSM_EVENTS, BBNSM_BTN_OFF); |
86 | |
87 | return IRQ_HANDLED; |
88 | } |
89 | |
90 | static void bbnsm_pwrkey_act(void *pdata) |
91 | { |
92 | struct bbnsm_pwrkey *bbnsm = pdata; |
93 | |
94 | timer_shutdown_sync(timer: &bbnsm->check_timer); |
95 | } |
96 | |
97 | static int bbnsm_pwrkey_probe(struct platform_device *pdev) |
98 | { |
99 | struct bbnsm_pwrkey *bbnsm; |
100 | struct input_dev *input; |
101 | struct device_node *np = pdev->dev.of_node; |
102 | int error; |
103 | |
104 | bbnsm = devm_kzalloc(dev: &pdev->dev, size: sizeof(*bbnsm), GFP_KERNEL); |
105 | if (!bbnsm) |
106 | return -ENOMEM; |
107 | |
108 | bbnsm->regmap = syscon_node_to_regmap(np: np->parent); |
109 | if (IS_ERR(ptr: bbnsm->regmap)) { |
110 | dev_err(&pdev->dev, "bbnsm pwerkey get regmap failed\n" ); |
111 | return PTR_ERR(ptr: bbnsm->regmap); |
112 | } |
113 | |
114 | if (device_property_read_u32(dev: &pdev->dev, propname: "linux,code" , |
115 | val: &bbnsm->keycode)) { |
116 | bbnsm->keycode = KEY_POWER; |
117 | dev_warn(&pdev->dev, "key code is not specified, using default KEY_POWER\n" ); |
118 | } |
119 | |
120 | bbnsm->irq = platform_get_irq(pdev, 0); |
121 | if (bbnsm->irq < 0) |
122 | return -EINVAL; |
123 | |
124 | /* config the BBNSM power related register */ |
125 | regmap_update_bits(map: bbnsm->regmap, BBNSM_CTRL, BBNSM_DP_EN, BBNSM_DP_EN); |
126 | |
127 | /* clear the unexpected interrupt before driver ready */ |
128 | regmap_write_bits(map: bbnsm->regmap, BBNSM_EVENTS, BBNSM_PWRKEY_EVENTS, |
129 | BBNSM_PWRKEY_EVENTS); |
130 | |
131 | timer_setup(&bbnsm->check_timer, bbnsm_pwrkey_check_for_events, 0); |
132 | |
133 | input = devm_input_allocate_device(&pdev->dev); |
134 | if (!input) { |
135 | dev_err(&pdev->dev, "failed to allocate the input device\n" ); |
136 | return -ENOMEM; |
137 | } |
138 | |
139 | input->name = pdev->name; |
140 | input->phys = "bbnsm-pwrkey/input0" ; |
141 | input->id.bustype = BUS_HOST; |
142 | |
143 | input_set_capability(dev: input, EV_KEY, code: bbnsm->keycode); |
144 | |
145 | /* input customer action to cancel release timer */ |
146 | error = devm_add_action(&pdev->dev, bbnsm_pwrkey_act, bbnsm); |
147 | if (error) { |
148 | dev_err(&pdev->dev, "failed to register remove action\n" ); |
149 | return error; |
150 | } |
151 | |
152 | bbnsm->input = input; |
153 | platform_set_drvdata(pdev, data: bbnsm); |
154 | |
155 | error = devm_request_irq(dev: &pdev->dev, irq: bbnsm->irq, handler: bbnsm_pwrkey_interrupt, |
156 | IRQF_SHARED, devname: pdev->name, dev_id: pdev); |
157 | if (error) { |
158 | dev_err(&pdev->dev, "interrupt not available.\n" ); |
159 | return error; |
160 | } |
161 | |
162 | error = input_register_device(input); |
163 | if (error) { |
164 | dev_err(&pdev->dev, "failed to register input device\n" ); |
165 | return error; |
166 | } |
167 | |
168 | device_init_wakeup(dev: &pdev->dev, enable: true); |
169 | error = dev_pm_set_wake_irq(dev: &pdev->dev, irq: bbnsm->irq); |
170 | if (error) |
171 | dev_warn(&pdev->dev, "irq wake enable failed.\n" ); |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static const struct of_device_id bbnsm_pwrkey_ids[] = { |
177 | { .compatible = "nxp,imx93-bbnsm-pwrkey" }, |
178 | { /* sentinel */ } |
179 | }; |
180 | MODULE_DEVICE_TABLE(of, bbnsm_pwrkey_ids); |
181 | |
182 | static struct platform_driver bbnsm_pwrkey_driver = { |
183 | .driver = { |
184 | .name = "bbnsm_pwrkey" , |
185 | .of_match_table = bbnsm_pwrkey_ids, |
186 | }, |
187 | .probe = bbnsm_pwrkey_probe, |
188 | }; |
189 | module_platform_driver(bbnsm_pwrkey_driver); |
190 | |
191 | MODULE_AUTHOR("Jacky Bai <ping.bai@nxp.com>" ); |
192 | MODULE_DESCRIPTION("NXP bbnsm power key Driver" ); |
193 | MODULE_LICENSE("GPL" ); |
194 | |