2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2007-12-14 19:08:37 +03:00
/*
* Fujitsu Lifebook Application Panel button drive
*
* Copyright ( C ) 2007 Stephen Hemminger < shemminger @ linux - foundation . org >
* Copyright ( C ) 2001 - 2003 Jochen Eisinger < jochen @ penguin - breeder . org >
*
* Many Fujitsu Lifebook laptops have a small panel of buttons that are
* accessible via the i2c / smbus interface . This driver polls those
* buttons and generates input events .
*
* For more details see :
* http : //apanel.sourceforge.net/tech.php
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/ioport.h>
# include <linux/io.h>
2019-10-30 03:02:19 +03:00
# include <linux/input.h>
2007-12-14 19:08:37 +03:00
# include <linux/i2c.h>
# include <linux/leds.h>
# define APANEL_NAME "Fujitsu Application Panel"
# define APANEL "apanel"
/* How often we poll keys - msecs */
# define POLL_INTERVAL_DEFAULT 1000
/* Magic constants in BIOS that tell about buttons */
enum apanel_devid {
APANEL_DEV_NONE = 0 ,
APANEL_DEV_APPBTN = 1 ,
APANEL_DEV_CDBTN = 2 ,
APANEL_DEV_LCD = 3 ,
APANEL_DEV_LED = 4 ,
APANEL_DEV_MAX ,
} ;
enum apanel_chip {
CHIP_NONE = 0 ,
CHIP_OZ992C = 1 ,
CHIP_OZ163T = 2 ,
CHIP_OZ711M3 = 4 ,
} ;
/* Result of BIOS snooping/probing -- what features are supported */
static enum apanel_chip device_chip [ APANEL_DEV_MAX ] ;
# define MAX_PANEL_KEYS 12
struct apanel {
2019-10-30 03:02:19 +03:00
struct input_dev * idev ;
2009-01-07 16:29:17 +03:00
struct i2c_client * client ;
2007-12-14 19:08:37 +03:00
unsigned short keymap [ MAX_PANEL_KEYS ] ;
2019-10-30 03:02:19 +03:00
u16 nkeys ;
2007-12-14 19:08:37 +03:00
struct led_classdev mail_led ;
} ;
2019-10-30 03:02:19 +03:00
static const unsigned short apanel_keymap [ MAX_PANEL_KEYS ] = {
[ 0 ] = KEY_MAIL ,
[ 1 ] = KEY_WWW ,
[ 2 ] = KEY_PROG2 ,
[ 3 ] = KEY_PROG1 ,
2007-12-14 19:08:37 +03:00
2019-10-30 03:02:19 +03:00
[ 8 ] = KEY_FORWARD ,
[ 9 ] = KEY_REWIND ,
[ 10 ] = KEY_STOPCD ,
[ 11 ] = KEY_PLAYPAUSE ,
} ;
2007-12-14 19:08:37 +03:00
static void report_key ( struct input_dev * input , unsigned keycode )
{
2019-10-30 03:02:19 +03:00
dev_dbg ( input - > dev . parent , " report key %#x \n " , keycode ) ;
2007-12-14 19:08:37 +03:00
input_report_key ( input , keycode , 1 ) ;
input_sync ( input ) ;
input_report_key ( input , keycode , 0 ) ;
input_sync ( input ) ;
}
/* Poll for key changes
*
* Read Application keys via SMI
* A ( 0x4 ) , B ( 0x8 ) , Internet ( 0x2 ) , Email ( 0x1 ) .
*
* CD keys :
* Forward ( 0x100 ) , Rewind ( 0x200 ) , Stop ( 0x400 ) , Pause ( 0x800 )
*/
2019-10-30 03:02:19 +03:00
static void apanel_poll ( struct input_dev * idev )
2007-12-14 19:08:37 +03:00
{
2019-10-30 03:02:19 +03:00
struct apanel * ap = input_get_drvdata ( idev ) ;
2007-12-14 19:08:37 +03:00
u8 cmd = device_chip [ APANEL_DEV_APPBTN ] = = CHIP_OZ992C ? 0 : 8 ;
s32 data ;
int i ;
2009-01-07 16:29:17 +03:00
data = i2c_smbus_read_word_data ( ap - > client , cmd ) ;
2007-12-14 19:08:37 +03:00
if ( data < 0 )
return ; /* ignore errors (due to ACPI??) */
/* write back to clear latch */
2009-01-07 16:29:17 +03:00
i2c_smbus_write_word_data ( ap - > client , cmd , 0 ) ;
2007-12-14 19:08:37 +03:00
if ( ! data )
return ;
dev_dbg ( & idev - > dev , APANEL " : data %#x \n " , data ) ;
for ( i = 0 ; i < idev - > keycodemax ; i + + )
if ( ( 1u < < i ) & data )
report_key ( idev , ap - > keymap [ i ] ) ;
}
2019-02-06 21:32:46 +03:00
static int mail_led_set ( struct led_classdev * led ,
2007-12-14 19:08:37 +03:00
enum led_brightness value )
{
struct apanel * ap = container_of ( led , struct apanel , mail_led ) ;
2019-02-06 21:32:46 +03:00
u16 led_bits = value ! = LED_OFF ? 0x8000 : 0x0000 ;
2007-12-14 19:08:37 +03:00
2019-02-06 21:32:46 +03:00
return i2c_smbus_write_word_data ( ap - > client , 0x10 , led_bits ) ;
2007-12-14 19:08:37 +03:00
}
2022-11-19 01:39:02 +03:00
static int apanel_probe ( struct i2c_client * client )
2007-12-14 19:08:37 +03:00
{
struct apanel * ap ;
struct input_dev * idev ;
u8 cmd = device_chip [ APANEL_DEV_APPBTN ] = = CHIP_OZ992C ? 0 : 8 ;
2019-10-30 03:02:19 +03:00
int i , err ;
2007-12-14 19:08:37 +03:00
2019-10-30 03:02:19 +03:00
ap = devm_kzalloc ( & client - > dev , sizeof ( * ap ) , GFP_KERNEL ) ;
if ( ! ap )
return - ENOMEM ;
2007-12-14 19:08:37 +03:00
2019-10-30 03:02:19 +03:00
idev = devm_input_allocate_device ( & client - > dev ) ;
if ( ! idev )
return - ENOMEM ;
2007-12-14 19:08:37 +03:00
2019-10-30 03:02:19 +03:00
ap - > idev = idev ;
2009-01-07 16:29:17 +03:00
ap - > client = client ;
2007-12-14 19:08:37 +03:00
2009-01-07 16:29:17 +03:00
i2c_set_clientdata ( client , ap ) ;
2007-12-14 19:08:37 +03:00
2009-01-07 16:29:17 +03:00
err = i2c_smbus_write_word_data ( client , cmd , 0 ) ;
2007-12-14 19:08:37 +03:00
if ( err ) {
2019-10-30 03:02:19 +03:00
dev_warn ( & client - > dev , " smbus write error %d \n " , err ) ;
return err ;
2007-12-14 19:08:37 +03:00
}
2019-10-30 03:02:19 +03:00
input_set_drvdata ( idev , ap ) ;
2007-12-14 19:08:37 +03:00
idev - > name = APANEL_NAME " buttons " ;
idev - > phys = " apanel/input0 " ;
idev - > id . bustype = BUS_HOST ;
2019-10-30 03:02:19 +03:00
memcpy ( ap - > keymap , apanel_keymap , sizeof ( apanel_keymap ) ) ;
2007-12-14 19:08:37 +03:00
idev - > keycode = ap - > keymap ;
idev - > keycodesize = sizeof ( ap - > keymap [ 0 ] ) ;
idev - > keycodemax = ( device_chip [ APANEL_DEV_CDBTN ] ! = CHIP_NONE ) ? 12 : 4 ;
2019-10-30 03:02:19 +03:00
set_bit ( EV_KEY , idev - > evbit ) ;
2007-12-14 19:08:37 +03:00
for ( i = 0 ; i < idev - > keycodemax ; i + + )
if ( ap - > keymap [ i ] )
set_bit ( ap - > keymap [ i ] , idev - > keybit ) ;
2019-10-30 03:02:19 +03:00
err = input_setup_polling ( idev , apanel_poll ) ;
2007-12-14 19:08:37 +03:00
if ( err )
2019-10-30 03:02:19 +03:00
return err ;
input_set_poll_interval ( idev , POLL_INTERVAL_DEFAULT ) ;
err = input_register_device ( idev ) ;
if ( err )
return err ;
2007-12-14 19:08:37 +03:00
if ( device_chip [ APANEL_DEV_LED ] ! = CHIP_NONE ) {
2019-10-30 03:02:19 +03:00
ap - > mail_led . name = " mail:blue " ;
ap - > mail_led . brightness_set_blocking = mail_led_set ;
err = devm_led_classdev_register ( & client - > dev , & ap - > mail_led ) ;
2007-12-14 19:08:37 +03:00
if ( err )
2019-10-30 03:02:19 +03:00
return err ;
2007-12-14 19:08:37 +03:00
}
return 0 ;
}
2019-10-30 03:02:19 +03:00
static void apanel_shutdown ( struct i2c_client * client )
{
struct apanel * ap = i2c_get_clientdata ( client ) ;
if ( device_chip [ APANEL_DEV_LED ] ! = CHIP_NONE )
led_set_brightness ( & ap - > mail_led , LED_OFF ) ;
}
static const struct i2c_device_id apanel_id [ ] = {
{ " fujitsu_apanel " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , apanel_id ) ;
static struct i2c_driver apanel_driver = {
. driver = {
. name = APANEL ,
} ,
2023-05-17 19:55:42 +03:00
. probe = apanel_probe ,
2019-10-30 03:02:19 +03:00
. shutdown = apanel_shutdown ,
. id_table = apanel_id ,
} ;
2007-12-14 19:08:37 +03:00
/* Scan the system ROM for the signature "FJKEYINF" */
static __init const void __iomem * bios_signature ( const void __iomem * bios )
{
ssize_t offset ;
const unsigned char signature [ ] = " FJKEYINF " ;
for ( offset = 0 ; offset < 0x10000 ; offset + = 0x10 ) {
if ( check_signature ( bios + offset , signature ,
sizeof ( signature ) - 1 ) )
return bios + offset ;
}
pr_notice ( APANEL " : Fujitsu BIOS signature '%s' not found... \n " ,
signature ) ;
return NULL ;
}
static int __init apanel_init ( void )
{
void __iomem * bios ;
const void __iomem * p ;
u8 devno ;
2009-01-07 16:29:17 +03:00
unsigned char i2c_addr ;
2007-12-14 19:08:37 +03:00
int found = 0 ;
bios = ioremap ( 0xF0000 , 0x10000 ) ; /* Can't fail */
p = bios_signature ( bios ) ;
if ( ! p ) {
iounmap ( bios ) ;
return - ENODEV ;
}
/* just use the first address */
p + = 8 ;
2009-01-07 16:29:17 +03:00
i2c_addr = readb ( p + 3 ) > > 1 ;
2007-12-14 19:08:37 +03:00
for ( ; ( devno = readb ( p ) ) & 0x7f ; p + = 4 ) {
unsigned char method , slave , chip ;
method = readb ( p + 1 ) ;
chip = readb ( p + 2 ) ;
slave = readb ( p + 3 ) > > 1 ;
2009-01-07 16:29:17 +03:00
if ( slave ! = i2c_addr ) {
2007-12-14 19:08:37 +03:00
pr_notice ( APANEL " : only one SMBus slave "
2016-06-23 20:04:39 +03:00
" address supported, skipping device... \n " ) ;
2007-12-14 19:08:37 +03:00
continue ;
}
/* translate alternative device numbers */
switch ( devno ) {
case 6 :
devno = APANEL_DEV_APPBTN ;
break ;
case 7 :
devno = APANEL_DEV_LED ;
break ;
}
if ( devno > = APANEL_DEV_MAX )
pr_notice ( APANEL " : unknown device %u found \n " , devno ) ;
else if ( device_chip [ devno ] ! = CHIP_NONE )
2017-03-18 03:15:38 +03:00
pr_warn ( APANEL " : duplicate entry for devno %u \n " ,
devno ) ;
2007-12-14 19:08:37 +03:00
else if ( method ! = 1 & & method ! = 2 & & method ! = 4 ) {
pr_notice ( APANEL " : unknown method %u for devno %u \n " ,
method , devno ) ;
} else {
device_chip [ devno ] = ( enum apanel_chip ) chip ;
+ + found ;
}
}
iounmap ( bios ) ;
if ( found = = 0 ) {
pr_info ( APANEL " : no input devices reported by BIOS \n " ) ;
return - EIO ;
}
return i2c_add_driver ( & apanel_driver ) ;
}
module_init ( apanel_init ) ;
static void __exit apanel_cleanup ( void )
{
i2c_del_driver ( & apanel_driver ) ;
}
module_exit ( apanel_cleanup ) ;
MODULE_AUTHOR ( " Stephen Hemminger <shemminger@linux-foundation.org> " ) ;
MODULE_DESCRIPTION ( APANEL_NAME " driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:* " ) ;
MODULE_ALIAS ( " dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:* " ) ;