1 | // SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0-or-later |
2 | /* |
3 | * Dell Wyse 3020 a.k.a. "Ariel" Power Button Driver |
4 | * |
5 | * Copyright (C) 2020 Lubomir Rintel |
6 | */ |
7 | |
8 | #include <linux/device.h> |
9 | #include <linux/gfp.h> |
10 | #include <linux/input.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/mod_devicetable.h> |
13 | #include <linux/module.h> |
14 | #include <linux/spi/spi.h> |
15 | |
16 | #define RESP_COUNTER(response) (response.header & 0x3) |
17 | #define RESP_SIZE(response) ((response.header >> 2) & 0x3) |
18 | #define RESP_TYPE(response) ((response.header >> 4) & 0xf) |
19 | |
20 | struct ec_input_response { |
21 | u8 reserved; |
22 | u8 ; |
23 | u8 data[3]; |
24 | } __packed; |
25 | |
26 | struct ariel_pwrbutton { |
27 | struct spi_device *client; |
28 | struct input_dev *input; |
29 | u8 msg_counter; |
30 | }; |
31 | |
32 | static int ec_input_read(struct ariel_pwrbutton *priv, |
33 | struct ec_input_response *response) |
34 | { |
35 | u8 read_request[] = { 0x00, 0x5a, 0xa5, 0x00, 0x00 }; |
36 | struct spi_device *spi = priv->client; |
37 | struct spi_transfer t = { |
38 | .tx_buf = read_request, |
39 | .rx_buf = response, |
40 | .len = sizeof(read_request), |
41 | }; |
42 | |
43 | compiletime_assert(sizeof(read_request) == sizeof(*response), |
44 | "SPI xfer request/response size mismatch" ); |
45 | |
46 | return spi_sync_transfer(spi, xfers: &t, num_xfers: 1); |
47 | } |
48 | |
49 | static irqreturn_t ec_input_interrupt(int irq, void *dev_id) |
50 | { |
51 | struct ariel_pwrbutton *priv = dev_id; |
52 | struct spi_device *spi = priv->client; |
53 | struct ec_input_response response; |
54 | int error; |
55 | int i; |
56 | |
57 | error = ec_input_read(priv, response: &response); |
58 | if (error < 0) { |
59 | dev_err(&spi->dev, "EC read failed: %d\n" , error); |
60 | goto out; |
61 | } |
62 | |
63 | if (priv->msg_counter == RESP_COUNTER(response)) { |
64 | dev_warn(&spi->dev, "No new data to read?\n" ); |
65 | goto out; |
66 | } |
67 | |
68 | priv->msg_counter = RESP_COUNTER(response); |
69 | |
70 | if (RESP_TYPE(response) != 0x3 && RESP_TYPE(response) != 0xc) { |
71 | dev_dbg(&spi->dev, "Ignoring message that's not kbd data\n" ); |
72 | goto out; |
73 | } |
74 | |
75 | for (i = 0; i < RESP_SIZE(response); i++) { |
76 | switch (response.data[i]) { |
77 | case 0x74: |
78 | input_report_key(dev: priv->input, KEY_POWER, value: 1); |
79 | input_sync(dev: priv->input); |
80 | break; |
81 | case 0xf4: |
82 | input_report_key(dev: priv->input, KEY_POWER, value: 0); |
83 | input_sync(dev: priv->input); |
84 | break; |
85 | default: |
86 | dev_dbg(&spi->dev, "Unknown scan code: %02x\n" , |
87 | response.data[i]); |
88 | } |
89 | } |
90 | |
91 | out: |
92 | return IRQ_HANDLED; |
93 | } |
94 | |
95 | static int ariel_pwrbutton_probe(struct spi_device *spi) |
96 | { |
97 | struct ec_input_response response; |
98 | struct ariel_pwrbutton *priv; |
99 | int error; |
100 | |
101 | if (!spi->irq) { |
102 | dev_err(&spi->dev, "Missing IRQ.\n" ); |
103 | return -EINVAL; |
104 | } |
105 | |
106 | priv = devm_kzalloc(dev: &spi->dev, size: sizeof(*priv), GFP_KERNEL); |
107 | if (!priv) |
108 | return -ENOMEM; |
109 | |
110 | priv->client = spi; |
111 | spi_set_drvdata(spi, data: priv); |
112 | |
113 | priv->input = devm_input_allocate_device(&spi->dev); |
114 | if (!priv->input) |
115 | return -ENOMEM; |
116 | priv->input->name = "Power Button" ; |
117 | priv->input->dev.parent = &spi->dev; |
118 | input_set_capability(dev: priv->input, EV_KEY, KEY_POWER); |
119 | error = input_register_device(priv->input); |
120 | if (error) { |
121 | dev_err(&spi->dev, "error registering input device: %d\n" , error); |
122 | return error; |
123 | } |
124 | |
125 | error = ec_input_read(priv, response: &response); |
126 | if (error < 0) { |
127 | dev_err(&spi->dev, "EC read failed: %d\n" , error); |
128 | return error; |
129 | } |
130 | priv->msg_counter = RESP_COUNTER(response); |
131 | |
132 | error = devm_request_threaded_irq(dev: &spi->dev, irq: spi->irq, NULL, |
133 | thread_fn: ec_input_interrupt, |
134 | IRQF_ONESHOT, |
135 | devname: "Ariel EC Input" , dev_id: priv); |
136 | |
137 | if (error) { |
138 | dev_err(&spi->dev, "Failed to request IRQ %d: %d\n" , |
139 | spi->irq, error); |
140 | return error; |
141 | } |
142 | |
143 | return 0; |
144 | } |
145 | |
146 | static const struct of_device_id ariel_pwrbutton_of_match[] = { |
147 | { .compatible = "dell,wyse-ariel-ec-input" }, |
148 | { } |
149 | }; |
150 | MODULE_DEVICE_TABLE(of, ariel_pwrbutton_of_match); |
151 | |
152 | static const struct spi_device_id ariel_pwrbutton_spi_ids[] = { |
153 | { .name = "wyse-ariel-ec-input" }, |
154 | { } |
155 | }; |
156 | MODULE_DEVICE_TABLE(spi, ariel_pwrbutton_spi_ids); |
157 | |
158 | static struct spi_driver ariel_pwrbutton_driver = { |
159 | .driver = { |
160 | .name = "dell-wyse-ariel-ec-input" , |
161 | .of_match_table = ariel_pwrbutton_of_match, |
162 | }, |
163 | .probe = ariel_pwrbutton_probe, |
164 | .id_table = ariel_pwrbutton_spi_ids, |
165 | }; |
166 | module_spi_driver(ariel_pwrbutton_driver); |
167 | |
168 | MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>" ); |
169 | MODULE_DESCRIPTION("Dell Wyse 3020 Power Button Input Driver" ); |
170 | MODULE_LICENSE("Dual BSD/GPL" ); |
171 | |