162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Fujitsu Lifebook Application Panel button drive
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2007 Stephen Hemminger <shemminger@linux-foundation.org>
662306a36Sopenharmony_ci *  Copyright (C) 2001-2003 Jochen Eisinger <jochen@penguin-breeder.org>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Many Fujitsu Lifebook laptops have a small panel of buttons that are
962306a36Sopenharmony_ci * accessible via the i2c/smbus interface. This driver polls those
1062306a36Sopenharmony_ci * buttons and generates input events.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * For more details see:
1362306a36Sopenharmony_ci *	http://apanel.sourceforge.net/tech.php
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/kernel.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/ioport.h>
1962306a36Sopenharmony_ci#include <linux/io.h>
2062306a36Sopenharmony_ci#include <linux/input.h>
2162306a36Sopenharmony_ci#include <linux/i2c.h>
2262306a36Sopenharmony_ci#include <linux/leds.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define APANEL_NAME	"Fujitsu Application Panel"
2562306a36Sopenharmony_ci#define APANEL		"apanel"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/* How often we poll keys - msecs */
2862306a36Sopenharmony_ci#define POLL_INTERVAL_DEFAULT	1000
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/* Magic constants in BIOS that tell about buttons */
3162306a36Sopenharmony_cienum apanel_devid {
3262306a36Sopenharmony_ci	APANEL_DEV_NONE	  = 0,
3362306a36Sopenharmony_ci	APANEL_DEV_APPBTN = 1,
3462306a36Sopenharmony_ci	APANEL_DEV_CDBTN  = 2,
3562306a36Sopenharmony_ci	APANEL_DEV_LCD	  = 3,
3662306a36Sopenharmony_ci	APANEL_DEV_LED	  = 4,
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	APANEL_DEV_MAX,
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cienum apanel_chip {
4262306a36Sopenharmony_ci	CHIP_NONE    = 0,
4362306a36Sopenharmony_ci	CHIP_OZ992C  = 1,
4462306a36Sopenharmony_ci	CHIP_OZ163T  = 2,
4562306a36Sopenharmony_ci	CHIP_OZ711M3 = 4,
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/* Result of BIOS snooping/probing -- what features are supported */
4962306a36Sopenharmony_cistatic enum apanel_chip device_chip[APANEL_DEV_MAX];
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci#define MAX_PANEL_KEYS	12
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistruct apanel {
5462306a36Sopenharmony_ci	struct input_dev *idev;
5562306a36Sopenharmony_ci	struct i2c_client *client;
5662306a36Sopenharmony_ci	unsigned short keymap[MAX_PANEL_KEYS];
5762306a36Sopenharmony_ci	u16 nkeys;
5862306a36Sopenharmony_ci	struct led_classdev mail_led;
5962306a36Sopenharmony_ci};
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic const unsigned short apanel_keymap[MAX_PANEL_KEYS] = {
6262306a36Sopenharmony_ci	[0] = KEY_MAIL,
6362306a36Sopenharmony_ci	[1] = KEY_WWW,
6462306a36Sopenharmony_ci	[2] = KEY_PROG2,
6562306a36Sopenharmony_ci	[3] = KEY_PROG1,
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	[8] = KEY_FORWARD,
6862306a36Sopenharmony_ci	[9] = KEY_REWIND,
6962306a36Sopenharmony_ci	[10] = KEY_STOPCD,
7062306a36Sopenharmony_ci	[11] = KEY_PLAYPAUSE,
7162306a36Sopenharmony_ci};
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic void report_key(struct input_dev *input, unsigned keycode)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	dev_dbg(input->dev.parent, "report key %#x\n", keycode);
7662306a36Sopenharmony_ci	input_report_key(input, keycode, 1);
7762306a36Sopenharmony_ci	input_sync(input);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	input_report_key(input, keycode, 0);
8062306a36Sopenharmony_ci	input_sync(input);
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci/* Poll for key changes
8462306a36Sopenharmony_ci *
8562306a36Sopenharmony_ci * Read Application keys via SMI
8662306a36Sopenharmony_ci *  A (0x4), B (0x8), Internet (0x2), Email (0x1).
8762306a36Sopenharmony_ci *
8862306a36Sopenharmony_ci * CD keys:
8962306a36Sopenharmony_ci * Forward (0x100), Rewind (0x200), Stop (0x400), Pause (0x800)
9062306a36Sopenharmony_ci */
9162306a36Sopenharmony_cistatic void apanel_poll(struct input_dev *idev)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct apanel *ap = input_get_drvdata(idev);
9462306a36Sopenharmony_ci	u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8;
9562306a36Sopenharmony_ci	s32 data;
9662306a36Sopenharmony_ci	int i;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	data = i2c_smbus_read_word_data(ap->client, cmd);
9962306a36Sopenharmony_ci	if (data < 0)
10062306a36Sopenharmony_ci		return;	/* ignore errors (due to ACPI??) */
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	/* write back to clear latch */
10362306a36Sopenharmony_ci	i2c_smbus_write_word_data(ap->client, cmd, 0);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	if (!data)
10662306a36Sopenharmony_ci		return;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	dev_dbg(&idev->dev, APANEL ": data %#x\n", data);
10962306a36Sopenharmony_ci	for (i = 0; i < idev->keycodemax; i++)
11062306a36Sopenharmony_ci		if ((1u << i) & data)
11162306a36Sopenharmony_ci			report_key(idev, ap->keymap[i]);
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic int mail_led_set(struct led_classdev *led,
11562306a36Sopenharmony_ci			 enum led_brightness value)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct apanel *ap = container_of(led, struct apanel, mail_led);
11862306a36Sopenharmony_ci	u16 led_bits = value != LED_OFF ? 0x8000 : 0x0000;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	return i2c_smbus_write_word_data(ap->client, 0x10, led_bits);
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic int apanel_probe(struct i2c_client *client)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct apanel *ap;
12662306a36Sopenharmony_ci	struct input_dev *idev;
12762306a36Sopenharmony_ci	u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8;
12862306a36Sopenharmony_ci	int i, err;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	ap = devm_kzalloc(&client->dev, sizeof(*ap), GFP_KERNEL);
13162306a36Sopenharmony_ci	if (!ap)
13262306a36Sopenharmony_ci		return -ENOMEM;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	idev = devm_input_allocate_device(&client->dev);
13562306a36Sopenharmony_ci	if (!idev)
13662306a36Sopenharmony_ci		return -ENOMEM;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	ap->idev = idev;
13962306a36Sopenharmony_ci	ap->client = client;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	i2c_set_clientdata(client, ap);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	err = i2c_smbus_write_word_data(client, cmd, 0);
14462306a36Sopenharmony_ci	if (err) {
14562306a36Sopenharmony_ci		dev_warn(&client->dev, "smbus write error %d\n", err);
14662306a36Sopenharmony_ci		return err;
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	input_set_drvdata(idev, ap);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	idev->name = APANEL_NAME " buttons";
15262306a36Sopenharmony_ci	idev->phys = "apanel/input0";
15362306a36Sopenharmony_ci	idev->id.bustype = BUS_HOST;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	memcpy(ap->keymap, apanel_keymap, sizeof(apanel_keymap));
15662306a36Sopenharmony_ci	idev->keycode = ap->keymap;
15762306a36Sopenharmony_ci	idev->keycodesize = sizeof(ap->keymap[0]);
15862306a36Sopenharmony_ci	idev->keycodemax = (device_chip[APANEL_DEV_CDBTN] != CHIP_NONE) ? 12 : 4;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	set_bit(EV_KEY, idev->evbit);
16162306a36Sopenharmony_ci	for (i = 0; i < idev->keycodemax; i++)
16262306a36Sopenharmony_ci		if (ap->keymap[i])
16362306a36Sopenharmony_ci			set_bit(ap->keymap[i], idev->keybit);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	err = input_setup_polling(idev, apanel_poll);
16662306a36Sopenharmony_ci	if (err)
16762306a36Sopenharmony_ci		return err;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	input_set_poll_interval(idev, POLL_INTERVAL_DEFAULT);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	err = input_register_device(idev);
17262306a36Sopenharmony_ci	if (err)
17362306a36Sopenharmony_ci		return err;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	if (device_chip[APANEL_DEV_LED] != CHIP_NONE) {
17662306a36Sopenharmony_ci		ap->mail_led.name = "mail:blue";
17762306a36Sopenharmony_ci		ap->mail_led.brightness_set_blocking = mail_led_set;
17862306a36Sopenharmony_ci		err = devm_led_classdev_register(&client->dev, &ap->mail_led);
17962306a36Sopenharmony_ci		if (err)
18062306a36Sopenharmony_ci			return err;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	return 0;
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic void apanel_shutdown(struct i2c_client *client)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	struct apanel *ap = i2c_get_clientdata(client);
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (device_chip[APANEL_DEV_LED] != CHIP_NONE)
19162306a36Sopenharmony_ci		led_set_brightness(&ap->mail_led, LED_OFF);
19262306a36Sopenharmony_ci}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic const struct i2c_device_id apanel_id[] = {
19562306a36Sopenharmony_ci	{ "fujitsu_apanel", 0 },
19662306a36Sopenharmony_ci	{ }
19762306a36Sopenharmony_ci};
19862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, apanel_id);
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic struct i2c_driver apanel_driver = {
20162306a36Sopenharmony_ci	.driver = {
20262306a36Sopenharmony_ci		.name = APANEL,
20362306a36Sopenharmony_ci	},
20462306a36Sopenharmony_ci	.probe		= apanel_probe,
20562306a36Sopenharmony_ci	.shutdown	= apanel_shutdown,
20662306a36Sopenharmony_ci	.id_table	= apanel_id,
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci/* Scan the system ROM for the signature "FJKEYINF" */
21062306a36Sopenharmony_cistatic __init const void __iomem *bios_signature(const void __iomem *bios)
21162306a36Sopenharmony_ci{
21262306a36Sopenharmony_ci	ssize_t offset;
21362306a36Sopenharmony_ci	const unsigned char signature[] = "FJKEYINF";
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	for (offset = 0; offset < 0x10000; offset += 0x10) {
21662306a36Sopenharmony_ci		if (check_signature(bios + offset, signature,
21762306a36Sopenharmony_ci				    sizeof(signature)-1))
21862306a36Sopenharmony_ci			return bios + offset;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci	pr_notice(APANEL ": Fujitsu BIOS signature '%s' not found...\n",
22162306a36Sopenharmony_ci		  signature);
22262306a36Sopenharmony_ci	return NULL;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic int __init apanel_init(void)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	void __iomem *bios;
22862306a36Sopenharmony_ci	const void __iomem *p;
22962306a36Sopenharmony_ci	u8 devno;
23062306a36Sopenharmony_ci	unsigned char i2c_addr;
23162306a36Sopenharmony_ci	int found = 0;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	bios = ioremap(0xF0000, 0x10000); /* Can't fail */
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	p = bios_signature(bios);
23662306a36Sopenharmony_ci	if (!p) {
23762306a36Sopenharmony_ci		iounmap(bios);
23862306a36Sopenharmony_ci		return -ENODEV;
23962306a36Sopenharmony_ci	}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	/* just use the first address */
24262306a36Sopenharmony_ci	p += 8;
24362306a36Sopenharmony_ci	i2c_addr = readb(p + 3) >> 1;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	for ( ; (devno = readb(p)) & 0x7f; p += 4) {
24662306a36Sopenharmony_ci		unsigned char method, slave, chip;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci		method = readb(p + 1);
24962306a36Sopenharmony_ci		chip = readb(p + 2);
25062306a36Sopenharmony_ci		slave = readb(p + 3) >> 1;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci		if (slave != i2c_addr) {
25362306a36Sopenharmony_ci			pr_notice(APANEL ": only one SMBus slave "
25462306a36Sopenharmony_ci				  "address supported, skipping device...\n");
25562306a36Sopenharmony_ci			continue;
25662306a36Sopenharmony_ci		}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci		/* translate alternative device numbers */
25962306a36Sopenharmony_ci		switch (devno) {
26062306a36Sopenharmony_ci		case 6:
26162306a36Sopenharmony_ci			devno = APANEL_DEV_APPBTN;
26262306a36Sopenharmony_ci			break;
26362306a36Sopenharmony_ci		case 7:
26462306a36Sopenharmony_ci			devno = APANEL_DEV_LED;
26562306a36Sopenharmony_ci			break;
26662306a36Sopenharmony_ci		}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci		if (devno >= APANEL_DEV_MAX)
26962306a36Sopenharmony_ci			pr_notice(APANEL ": unknown device %u found\n", devno);
27062306a36Sopenharmony_ci		else if (device_chip[devno] != CHIP_NONE)
27162306a36Sopenharmony_ci			pr_warn(APANEL ": duplicate entry for devno %u\n",
27262306a36Sopenharmony_ci				devno);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci		else if (method != 1 && method != 2 && method != 4) {
27562306a36Sopenharmony_ci			pr_notice(APANEL ": unknown method %u for devno %u\n",
27662306a36Sopenharmony_ci				  method, devno);
27762306a36Sopenharmony_ci		} else {
27862306a36Sopenharmony_ci			device_chip[devno] = (enum apanel_chip) chip;
27962306a36Sopenharmony_ci			++found;
28062306a36Sopenharmony_ci		}
28162306a36Sopenharmony_ci	}
28262306a36Sopenharmony_ci	iounmap(bios);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	if (found == 0) {
28562306a36Sopenharmony_ci		pr_info(APANEL ": no input devices reported by BIOS\n");
28662306a36Sopenharmony_ci		return -EIO;
28762306a36Sopenharmony_ci	}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	return i2c_add_driver(&apanel_driver);
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_cimodule_init(apanel_init);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic void __exit apanel_cleanup(void)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	i2c_del_driver(&apanel_driver);
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_cimodule_exit(apanel_cleanup);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ciMODULE_AUTHOR("Stephen Hemminger <shemminger@linux-foundation.org>");
30062306a36Sopenharmony_ciMODULE_DESCRIPTION(APANEL_NAME " driver");
30162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ciMODULE_ALIAS("dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:*");
30462306a36Sopenharmony_ciMODULE_ALIAS("dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:*");
305