1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Fujitsu Lifebook Application Panel button drive |
4 | * |
5 | * Copyright (C) 2007 Stephen Hemminger <shemminger@linux-foundation.org> |
6 | * Copyright (C) 2001-2003 Jochen Eisinger <jochen@penguin-breeder.org> |
7 | * |
8 | * Many Fujitsu Lifebook laptops have a small panel of buttons that are |
9 | * accessible via the i2c/smbus interface. This driver polls those |
10 | * buttons and generates input events. |
11 | * |
12 | * For more details see: |
13 | * http://apanel.sourceforge.net/tech.php |
14 | */ |
15 | |
16 | #include <linux/kernel.h> |
17 | #include <linux/module.h> |
18 | #include <linux/ioport.h> |
19 | #include <linux/io.h> |
20 | #include <linux/input.h> |
21 | #include <linux/i2c.h> |
22 | #include <linux/leds.h> |
23 | |
24 | #define APANEL_NAME "Fujitsu Application Panel" |
25 | #define APANEL "apanel" |
26 | |
27 | /* How often we poll keys - msecs */ |
28 | #define POLL_INTERVAL_DEFAULT 1000 |
29 | |
30 | /* Magic constants in BIOS that tell about buttons */ |
31 | enum apanel_devid { |
32 | APANEL_DEV_NONE = 0, |
33 | APANEL_DEV_APPBTN = 1, |
34 | APANEL_DEV_CDBTN = 2, |
35 | APANEL_DEV_LCD = 3, |
36 | APANEL_DEV_LED = 4, |
37 | |
38 | APANEL_DEV_MAX, |
39 | }; |
40 | |
41 | enum apanel_chip { |
42 | CHIP_NONE = 0, |
43 | CHIP_OZ992C = 1, |
44 | CHIP_OZ163T = 2, |
45 | CHIP_OZ711M3 = 4, |
46 | }; |
47 | |
48 | /* Result of BIOS snooping/probing -- what features are supported */ |
49 | static enum apanel_chip device_chip[APANEL_DEV_MAX]; |
50 | |
51 | #define MAX_PANEL_KEYS 12 |
52 | |
53 | struct apanel { |
54 | struct input_dev *idev; |
55 | struct i2c_client *client; |
56 | unsigned short keymap[MAX_PANEL_KEYS]; |
57 | u16 nkeys; |
58 | struct led_classdev mail_led; |
59 | }; |
60 | |
61 | static const unsigned short apanel_keymap[MAX_PANEL_KEYS] = { |
62 | [0] = KEY_MAIL, |
63 | [1] = KEY_WWW, |
64 | [2] = KEY_PROG2, |
65 | [3] = KEY_PROG1, |
66 | |
67 | [8] = KEY_FORWARD, |
68 | [9] = KEY_REWIND, |
69 | [10] = KEY_STOPCD, |
70 | [11] = KEY_PLAYPAUSE, |
71 | }; |
72 | |
73 | static void report_key(struct input_dev *input, unsigned keycode) |
74 | { |
75 | dev_dbg(input->dev.parent, "report key %#x\n" , keycode); |
76 | input_report_key(dev: input, code: keycode, value: 1); |
77 | input_sync(dev: input); |
78 | |
79 | input_report_key(dev: input, code: keycode, value: 0); |
80 | input_sync(dev: input); |
81 | } |
82 | |
83 | /* Poll for key changes |
84 | * |
85 | * Read Application keys via SMI |
86 | * A (0x4), B (0x8), Internet (0x2), Email (0x1). |
87 | * |
88 | * CD keys: |
89 | * Forward (0x100), Rewind (0x200), Stop (0x400), Pause (0x800) |
90 | */ |
91 | static void apanel_poll(struct input_dev *idev) |
92 | { |
93 | struct apanel *ap = input_get_drvdata(dev: idev); |
94 | u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; |
95 | s32 data; |
96 | int i; |
97 | |
98 | data = i2c_smbus_read_word_data(client: ap->client, command: cmd); |
99 | if (data < 0) |
100 | return; /* ignore errors (due to ACPI??) */ |
101 | |
102 | /* write back to clear latch */ |
103 | i2c_smbus_write_word_data(client: ap->client, command: cmd, value: 0); |
104 | |
105 | if (!data) |
106 | return; |
107 | |
108 | dev_dbg(&idev->dev, APANEL ": data %#x\n" , data); |
109 | for (i = 0; i < idev->keycodemax; i++) |
110 | if ((1u << i) & data) |
111 | report_key(input: idev, keycode: ap->keymap[i]); |
112 | } |
113 | |
114 | static int mail_led_set(struct led_classdev *led, |
115 | enum led_brightness value) |
116 | { |
117 | struct apanel *ap = container_of(led, struct apanel, mail_led); |
118 | u16 led_bits = value != LED_OFF ? 0x8000 : 0x0000; |
119 | |
120 | return i2c_smbus_write_word_data(client: ap->client, command: 0x10, value: led_bits); |
121 | } |
122 | |
123 | static int apanel_probe(struct i2c_client *client) |
124 | { |
125 | struct apanel *ap; |
126 | struct input_dev *idev; |
127 | u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; |
128 | int i, err; |
129 | |
130 | ap = devm_kzalloc(dev: &client->dev, size: sizeof(*ap), GFP_KERNEL); |
131 | if (!ap) |
132 | return -ENOMEM; |
133 | |
134 | idev = devm_input_allocate_device(&client->dev); |
135 | if (!idev) |
136 | return -ENOMEM; |
137 | |
138 | ap->idev = idev; |
139 | ap->client = client; |
140 | |
141 | i2c_set_clientdata(client, data: ap); |
142 | |
143 | err = i2c_smbus_write_word_data(client, command: cmd, value: 0); |
144 | if (err) { |
145 | dev_warn(&client->dev, "smbus write error %d\n" , err); |
146 | return err; |
147 | } |
148 | |
149 | input_set_drvdata(dev: idev, data: ap); |
150 | |
151 | idev->name = APANEL_NAME " buttons" ; |
152 | idev->phys = "apanel/input0" ; |
153 | idev->id.bustype = BUS_HOST; |
154 | |
155 | memcpy(ap->keymap, apanel_keymap, sizeof(apanel_keymap)); |
156 | idev->keycode = ap->keymap; |
157 | idev->keycodesize = sizeof(ap->keymap[0]); |
158 | idev->keycodemax = (device_chip[APANEL_DEV_CDBTN] != CHIP_NONE) ? 12 : 4; |
159 | |
160 | set_bit(EV_KEY, addr: idev->evbit); |
161 | for (i = 0; i < idev->keycodemax; i++) |
162 | if (ap->keymap[i]) |
163 | set_bit(nr: ap->keymap[i], addr: idev->keybit); |
164 | |
165 | err = input_setup_polling(dev: idev, poll_fn: apanel_poll); |
166 | if (err) |
167 | return err; |
168 | |
169 | input_set_poll_interval(dev: idev, POLL_INTERVAL_DEFAULT); |
170 | |
171 | err = input_register_device(idev); |
172 | if (err) |
173 | return err; |
174 | |
175 | if (device_chip[APANEL_DEV_LED] != CHIP_NONE) { |
176 | ap->mail_led.name = "mail:blue" ; |
177 | ap->mail_led.brightness_set_blocking = mail_led_set; |
178 | err = devm_led_classdev_register(parent: &client->dev, led_cdev: &ap->mail_led); |
179 | if (err) |
180 | return err; |
181 | } |
182 | |
183 | return 0; |
184 | } |
185 | |
186 | static void apanel_shutdown(struct i2c_client *client) |
187 | { |
188 | struct apanel *ap = i2c_get_clientdata(client); |
189 | |
190 | if (device_chip[APANEL_DEV_LED] != CHIP_NONE) |
191 | led_set_brightness(led_cdev: &ap->mail_led, brightness: LED_OFF); |
192 | } |
193 | |
194 | static const struct i2c_device_id apanel_id[] = { |
195 | { "fujitsu_apanel" , 0 }, |
196 | { } |
197 | }; |
198 | MODULE_DEVICE_TABLE(i2c, apanel_id); |
199 | |
200 | static struct i2c_driver apanel_driver = { |
201 | .driver = { |
202 | .name = APANEL, |
203 | }, |
204 | .probe = apanel_probe, |
205 | .shutdown = apanel_shutdown, |
206 | .id_table = apanel_id, |
207 | }; |
208 | |
209 | /* Scan the system ROM for the signature "FJKEYINF" */ |
210 | static __init const void __iomem *bios_signature(const void __iomem *bios) |
211 | { |
212 | ssize_t offset; |
213 | const unsigned char signature[] = "FJKEYINF" ; |
214 | |
215 | for (offset = 0; offset < 0x10000; offset += 0x10) { |
216 | if (check_signature(io_addr: bios + offset, signature, |
217 | length: sizeof(signature)-1)) |
218 | return bios + offset; |
219 | } |
220 | pr_notice(APANEL ": Fujitsu BIOS signature '%s' not found...\n" , |
221 | signature); |
222 | return NULL; |
223 | } |
224 | |
225 | static int __init apanel_init(void) |
226 | { |
227 | void __iomem *bios; |
228 | const void __iomem *p; |
229 | u8 devno; |
230 | unsigned char i2c_addr; |
231 | int found = 0; |
232 | |
233 | bios = ioremap(offset: 0xF0000, size: 0x10000); /* Can't fail */ |
234 | |
235 | p = bios_signature(bios); |
236 | if (!p) { |
237 | iounmap(addr: bios); |
238 | return -ENODEV; |
239 | } |
240 | |
241 | /* just use the first address */ |
242 | p += 8; |
243 | i2c_addr = readb(addr: p + 3) >> 1; |
244 | |
245 | for ( ; (devno = readb(addr: p)) & 0x7f; p += 4) { |
246 | unsigned char method, slave, chip; |
247 | |
248 | method = readb(addr: p + 1); |
249 | chip = readb(addr: p + 2); |
250 | slave = readb(addr: p + 3) >> 1; |
251 | |
252 | if (slave != i2c_addr) { |
253 | pr_notice(APANEL ": only one SMBus slave " |
254 | "address supported, skipping device...\n" ); |
255 | continue; |
256 | } |
257 | |
258 | /* translate alternative device numbers */ |
259 | switch (devno) { |
260 | case 6: |
261 | devno = APANEL_DEV_APPBTN; |
262 | break; |
263 | case 7: |
264 | devno = APANEL_DEV_LED; |
265 | break; |
266 | } |
267 | |
268 | if (devno >= APANEL_DEV_MAX) |
269 | pr_notice(APANEL ": unknown device %u found\n" , devno); |
270 | else if (device_chip[devno] != CHIP_NONE) |
271 | pr_warn(APANEL ": duplicate entry for devno %u\n" , |
272 | devno); |
273 | |
274 | else if (method != 1 && method != 2 && method != 4) { |
275 | pr_notice(APANEL ": unknown method %u for devno %u\n" , |
276 | method, devno); |
277 | } else { |
278 | device_chip[devno] = (enum apanel_chip) chip; |
279 | ++found; |
280 | } |
281 | } |
282 | iounmap(addr: bios); |
283 | |
284 | if (found == 0) { |
285 | pr_info(APANEL ": no input devices reported by BIOS\n" ); |
286 | return -EIO; |
287 | } |
288 | |
289 | return i2c_add_driver(&apanel_driver); |
290 | } |
291 | module_init(apanel_init); |
292 | |
293 | static void __exit apanel_cleanup(void) |
294 | { |
295 | i2c_del_driver(driver: &apanel_driver); |
296 | } |
297 | module_exit(apanel_cleanup); |
298 | |
299 | MODULE_AUTHOR("Stephen Hemminger <shemminger@linux-foundation.org>" ); |
300 | MODULE_DESCRIPTION(APANEL_NAME " driver" ); |
301 | MODULE_LICENSE("GPL" ); |
302 | |
303 | MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:*" ); |
304 | MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:*" ); |
305 | |