18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Fujitsu Lifebook Application Panel button drive 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2007 Stephen Hemminger <shemminger@linux-foundation.org> 68c2ecf20Sopenharmony_ci * Copyright (C) 2001-2003 Jochen Eisinger <jochen@penguin-breeder.org> 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Many Fujitsu Lifebook laptops have a small panel of buttons that are 98c2ecf20Sopenharmony_ci * accessible via the i2c/smbus interface. This driver polls those 108c2ecf20Sopenharmony_ci * buttons and generates input events. 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * For more details see: 138c2ecf20Sopenharmony_ci * http://apanel.sourceforge.net/tech.php 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/ioport.h> 198c2ecf20Sopenharmony_ci#include <linux/io.h> 208c2ecf20Sopenharmony_ci#include <linux/input.h> 218c2ecf20Sopenharmony_ci#include <linux/i2c.h> 228c2ecf20Sopenharmony_ci#include <linux/leds.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#define APANEL_NAME "Fujitsu Application Panel" 258c2ecf20Sopenharmony_ci#define APANEL "apanel" 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* How often we poll keys - msecs */ 288c2ecf20Sopenharmony_ci#define POLL_INTERVAL_DEFAULT 1000 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* Magic constants in BIOS that tell about buttons */ 318c2ecf20Sopenharmony_cienum apanel_devid { 328c2ecf20Sopenharmony_ci APANEL_DEV_NONE = 0, 338c2ecf20Sopenharmony_ci APANEL_DEV_APPBTN = 1, 348c2ecf20Sopenharmony_ci APANEL_DEV_CDBTN = 2, 358c2ecf20Sopenharmony_ci APANEL_DEV_LCD = 3, 368c2ecf20Sopenharmony_ci APANEL_DEV_LED = 4, 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci APANEL_DEV_MAX, 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cienum apanel_chip { 428c2ecf20Sopenharmony_ci CHIP_NONE = 0, 438c2ecf20Sopenharmony_ci CHIP_OZ992C = 1, 448c2ecf20Sopenharmony_ci CHIP_OZ163T = 2, 458c2ecf20Sopenharmony_ci CHIP_OZ711M3 = 4, 468c2ecf20Sopenharmony_ci}; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci/* Result of BIOS snooping/probing -- what features are supported */ 498c2ecf20Sopenharmony_cistatic enum apanel_chip device_chip[APANEL_DEV_MAX]; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci#define MAX_PANEL_KEYS 12 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistruct apanel { 548c2ecf20Sopenharmony_ci struct input_dev *idev; 558c2ecf20Sopenharmony_ci struct i2c_client *client; 568c2ecf20Sopenharmony_ci unsigned short keymap[MAX_PANEL_KEYS]; 578c2ecf20Sopenharmony_ci u16 nkeys; 588c2ecf20Sopenharmony_ci struct led_classdev mail_led; 598c2ecf20Sopenharmony_ci}; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic const unsigned short apanel_keymap[MAX_PANEL_KEYS] = { 628c2ecf20Sopenharmony_ci [0] = KEY_MAIL, 638c2ecf20Sopenharmony_ci [1] = KEY_WWW, 648c2ecf20Sopenharmony_ci [2] = KEY_PROG2, 658c2ecf20Sopenharmony_ci [3] = KEY_PROG1, 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci [8] = KEY_FORWARD, 688c2ecf20Sopenharmony_ci [9] = KEY_REWIND, 698c2ecf20Sopenharmony_ci [10] = KEY_STOPCD, 708c2ecf20Sopenharmony_ci [11] = KEY_PLAYPAUSE, 718c2ecf20Sopenharmony_ci}; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic void report_key(struct input_dev *input, unsigned keycode) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci dev_dbg(input->dev.parent, "report key %#x\n", keycode); 768c2ecf20Sopenharmony_ci input_report_key(input, keycode, 1); 778c2ecf20Sopenharmony_ci input_sync(input); 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci input_report_key(input, keycode, 0); 808c2ecf20Sopenharmony_ci input_sync(input); 818c2ecf20Sopenharmony_ci} 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci/* Poll for key changes 848c2ecf20Sopenharmony_ci * 858c2ecf20Sopenharmony_ci * Read Application keys via SMI 868c2ecf20Sopenharmony_ci * A (0x4), B (0x8), Internet (0x2), Email (0x1). 878c2ecf20Sopenharmony_ci * 888c2ecf20Sopenharmony_ci * CD keys: 898c2ecf20Sopenharmony_ci * Forward (0x100), Rewind (0x200), Stop (0x400), Pause (0x800) 908c2ecf20Sopenharmony_ci */ 918c2ecf20Sopenharmony_cistatic void apanel_poll(struct input_dev *idev) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci struct apanel *ap = input_get_drvdata(idev); 948c2ecf20Sopenharmony_ci u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; 958c2ecf20Sopenharmony_ci s32 data; 968c2ecf20Sopenharmony_ci int i; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci data = i2c_smbus_read_word_data(ap->client, cmd); 998c2ecf20Sopenharmony_ci if (data < 0) 1008c2ecf20Sopenharmony_ci return; /* ignore errors (due to ACPI??) */ 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* write back to clear latch */ 1038c2ecf20Sopenharmony_ci i2c_smbus_write_word_data(ap->client, cmd, 0); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci if (!data) 1068c2ecf20Sopenharmony_ci return; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci dev_dbg(&idev->dev, APANEL ": data %#x\n", data); 1098c2ecf20Sopenharmony_ci for (i = 0; i < idev->keycodemax; i++) 1108c2ecf20Sopenharmony_ci if ((1u << i) & data) 1118c2ecf20Sopenharmony_ci report_key(idev, ap->keymap[i]); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic int mail_led_set(struct led_classdev *led, 1158c2ecf20Sopenharmony_ci enum led_brightness value) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci struct apanel *ap = container_of(led, struct apanel, mail_led); 1188c2ecf20Sopenharmony_ci u16 led_bits = value != LED_OFF ? 0x8000 : 0x0000; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci return i2c_smbus_write_word_data(ap->client, 0x10, led_bits); 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int apanel_probe(struct i2c_client *client, 1248c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 1258c2ecf20Sopenharmony_ci{ 1268c2ecf20Sopenharmony_ci struct apanel *ap; 1278c2ecf20Sopenharmony_ci struct input_dev *idev; 1288c2ecf20Sopenharmony_ci u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; 1298c2ecf20Sopenharmony_ci int i, err; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci ap = devm_kzalloc(&client->dev, sizeof(*ap), GFP_KERNEL); 1328c2ecf20Sopenharmony_ci if (!ap) 1338c2ecf20Sopenharmony_ci return -ENOMEM; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci idev = devm_input_allocate_device(&client->dev); 1368c2ecf20Sopenharmony_ci if (!idev) 1378c2ecf20Sopenharmony_ci return -ENOMEM; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci ap->idev = idev; 1408c2ecf20Sopenharmony_ci ap->client = client; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci i2c_set_clientdata(client, ap); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci err = i2c_smbus_write_word_data(client, cmd, 0); 1458c2ecf20Sopenharmony_ci if (err) { 1468c2ecf20Sopenharmony_ci dev_warn(&client->dev, "smbus write error %d\n", err); 1478c2ecf20Sopenharmony_ci return err; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci input_set_drvdata(idev, ap); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci idev->name = APANEL_NAME " buttons"; 1538c2ecf20Sopenharmony_ci idev->phys = "apanel/input0"; 1548c2ecf20Sopenharmony_ci idev->id.bustype = BUS_HOST; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci memcpy(ap->keymap, apanel_keymap, sizeof(apanel_keymap)); 1578c2ecf20Sopenharmony_ci idev->keycode = ap->keymap; 1588c2ecf20Sopenharmony_ci idev->keycodesize = sizeof(ap->keymap[0]); 1598c2ecf20Sopenharmony_ci idev->keycodemax = (device_chip[APANEL_DEV_CDBTN] != CHIP_NONE) ? 12 : 4; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci set_bit(EV_KEY, idev->evbit); 1628c2ecf20Sopenharmony_ci for (i = 0; i < idev->keycodemax; i++) 1638c2ecf20Sopenharmony_ci if (ap->keymap[i]) 1648c2ecf20Sopenharmony_ci set_bit(ap->keymap[i], idev->keybit); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci err = input_setup_polling(idev, apanel_poll); 1678c2ecf20Sopenharmony_ci if (err) 1688c2ecf20Sopenharmony_ci return err; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci input_set_poll_interval(idev, POLL_INTERVAL_DEFAULT); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci err = input_register_device(idev); 1738c2ecf20Sopenharmony_ci if (err) 1748c2ecf20Sopenharmony_ci return err; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci if (device_chip[APANEL_DEV_LED] != CHIP_NONE) { 1778c2ecf20Sopenharmony_ci ap->mail_led.name = "mail:blue"; 1788c2ecf20Sopenharmony_ci ap->mail_led.brightness_set_blocking = mail_led_set; 1798c2ecf20Sopenharmony_ci err = devm_led_classdev_register(&client->dev, &ap->mail_led); 1808c2ecf20Sopenharmony_ci if (err) 1818c2ecf20Sopenharmony_ci return err; 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci return 0; 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic void apanel_shutdown(struct i2c_client *client) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci struct apanel *ap = i2c_get_clientdata(client); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (device_chip[APANEL_DEV_LED] != CHIP_NONE) 1928c2ecf20Sopenharmony_ci led_set_brightness(&ap->mail_led, LED_OFF); 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic const struct i2c_device_id apanel_id[] = { 1968c2ecf20Sopenharmony_ci { "fujitsu_apanel", 0 }, 1978c2ecf20Sopenharmony_ci { } 1988c2ecf20Sopenharmony_ci}; 1998c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, apanel_id); 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic struct i2c_driver apanel_driver = { 2028c2ecf20Sopenharmony_ci .driver = { 2038c2ecf20Sopenharmony_ci .name = APANEL, 2048c2ecf20Sopenharmony_ci }, 2058c2ecf20Sopenharmony_ci .probe = apanel_probe, 2068c2ecf20Sopenharmony_ci .shutdown = apanel_shutdown, 2078c2ecf20Sopenharmony_ci .id_table = apanel_id, 2088c2ecf20Sopenharmony_ci}; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci/* Scan the system ROM for the signature "FJKEYINF" */ 2118c2ecf20Sopenharmony_cistatic __init const void __iomem *bios_signature(const void __iomem *bios) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci ssize_t offset; 2148c2ecf20Sopenharmony_ci const unsigned char signature[] = "FJKEYINF"; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci for (offset = 0; offset < 0x10000; offset += 0x10) { 2178c2ecf20Sopenharmony_ci if (check_signature(bios + offset, signature, 2188c2ecf20Sopenharmony_ci sizeof(signature)-1)) 2198c2ecf20Sopenharmony_ci return bios + offset; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci pr_notice(APANEL ": Fujitsu BIOS signature '%s' not found...\n", 2228c2ecf20Sopenharmony_ci signature); 2238c2ecf20Sopenharmony_ci return NULL; 2248c2ecf20Sopenharmony_ci} 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_cistatic int __init apanel_init(void) 2278c2ecf20Sopenharmony_ci{ 2288c2ecf20Sopenharmony_ci void __iomem *bios; 2298c2ecf20Sopenharmony_ci const void __iomem *p; 2308c2ecf20Sopenharmony_ci u8 devno; 2318c2ecf20Sopenharmony_ci unsigned char i2c_addr; 2328c2ecf20Sopenharmony_ci int found = 0; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci bios = ioremap(0xF0000, 0x10000); /* Can't fail */ 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci p = bios_signature(bios); 2378c2ecf20Sopenharmony_ci if (!p) { 2388c2ecf20Sopenharmony_ci iounmap(bios); 2398c2ecf20Sopenharmony_ci return -ENODEV; 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci /* just use the first address */ 2438c2ecf20Sopenharmony_ci p += 8; 2448c2ecf20Sopenharmony_ci i2c_addr = readb(p + 3) >> 1; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci for ( ; (devno = readb(p)) & 0x7f; p += 4) { 2478c2ecf20Sopenharmony_ci unsigned char method, slave, chip; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci method = readb(p + 1); 2508c2ecf20Sopenharmony_ci chip = readb(p + 2); 2518c2ecf20Sopenharmony_ci slave = readb(p + 3) >> 1; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci if (slave != i2c_addr) { 2548c2ecf20Sopenharmony_ci pr_notice(APANEL ": only one SMBus slave " 2558c2ecf20Sopenharmony_ci "address supported, skipping device...\n"); 2568c2ecf20Sopenharmony_ci continue; 2578c2ecf20Sopenharmony_ci } 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci /* translate alternative device numbers */ 2608c2ecf20Sopenharmony_ci switch (devno) { 2618c2ecf20Sopenharmony_ci case 6: 2628c2ecf20Sopenharmony_ci devno = APANEL_DEV_APPBTN; 2638c2ecf20Sopenharmony_ci break; 2648c2ecf20Sopenharmony_ci case 7: 2658c2ecf20Sopenharmony_ci devno = APANEL_DEV_LED; 2668c2ecf20Sopenharmony_ci break; 2678c2ecf20Sopenharmony_ci } 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci if (devno >= APANEL_DEV_MAX) 2708c2ecf20Sopenharmony_ci pr_notice(APANEL ": unknown device %u found\n", devno); 2718c2ecf20Sopenharmony_ci else if (device_chip[devno] != CHIP_NONE) 2728c2ecf20Sopenharmony_ci pr_warn(APANEL ": duplicate entry for devno %u\n", 2738c2ecf20Sopenharmony_ci devno); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci else if (method != 1 && method != 2 && method != 4) { 2768c2ecf20Sopenharmony_ci pr_notice(APANEL ": unknown method %u for devno %u\n", 2778c2ecf20Sopenharmony_ci method, devno); 2788c2ecf20Sopenharmony_ci } else { 2798c2ecf20Sopenharmony_ci device_chip[devno] = (enum apanel_chip) chip; 2808c2ecf20Sopenharmony_ci ++found; 2818c2ecf20Sopenharmony_ci } 2828c2ecf20Sopenharmony_ci } 2838c2ecf20Sopenharmony_ci iounmap(bios); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci if (found == 0) { 2868c2ecf20Sopenharmony_ci pr_info(APANEL ": no input devices reported by BIOS\n"); 2878c2ecf20Sopenharmony_ci return -EIO; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci return i2c_add_driver(&apanel_driver); 2918c2ecf20Sopenharmony_ci} 2928c2ecf20Sopenharmony_cimodule_init(apanel_init); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_cistatic void __exit apanel_cleanup(void) 2958c2ecf20Sopenharmony_ci{ 2968c2ecf20Sopenharmony_ci i2c_del_driver(&apanel_driver); 2978c2ecf20Sopenharmony_ci} 2988c2ecf20Sopenharmony_cimodule_exit(apanel_cleanup); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ciMODULE_AUTHOR("Stephen Hemminger <shemminger@linux-foundation.org>"); 3018c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(APANEL_NAME " driver"); 3028c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ciMODULE_ALIAS("dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:*"); 3058c2ecf20Sopenharmony_ciMODULE_ALIAS("dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:*"); 306