1
0
Files
kernel-49/drivers/input/keyboard/twl4030_keypad.c
Greg Kroah-Hartman 49f4e44fac Merge 4.9.241 into android-4.9-q
Changes in 4.9.241
	ibmveth: Identify ingress large send packets.
	tipc: fix the skb_unshare() in tipc_buf_append()
	net/ipv4: always honour route mtu during forwarding
	r8169: fix data corruption issue on RTL8402
	ALSA: bebob: potential info leak in hwdep_read()
	net: hdlc: In hdlc_rcv, check to make sure dev is an HDLC device
	net: hdlc_raw_eth: Clear the IFF_TX_SKB_SHARING flag after calling ether_setup
	nfc: Ensure presence of NFC_ATTR_FIRMWARE_NAME attribute in nfc_genl_fw_download()
	tcp: fix to update snd_wl1 in bulk receiver fast path
	icmp: randomize the global rate limiter
	cifs: remove bogus debug code
	KVM: x86/mmu: Commit zap of remaining invalid pages when recovering lpages
	ima: Don't ignore errors from crypto_shash_update()
	crypto: algif_aead - Do not set MAY_BACKLOG on the async path
	EDAC/i5100: Fix error handling order in i5100_init_one()
	crypto: ixp4xx - Fix the size used in a 'dma_free_coherent()' call
	media: Revert "media: exynos4-is: Add missed check for pinctrl_lookup_state()"
	media: m5mols: Check function pointer in m5mols_sensor_power
	media: omap3isp: Fix memleak in isp_probe
	crypto: omap-sham - fix digcnt register handling with export/import
	media: tc358743: initialize variable
	media: platform: fcp: Fix a reference count leak.
	media: ti-vpe: Fix a missing check and reference count leak
	regulator: resolve supply after creating regulator
	ath10k: provide survey info as accumulated data
	ath6kl: prevent potential array overflow in ath6kl_add_new_sta()
	ath9k: Fix potential out of bounds in ath9k_htc_txcompletion_cb()
	wcn36xx: Fix reported 802.11n rx_highest rate wcn3660/wcn3680
	ASoC: qcom: lpass-platform: fix memory leak
	mwifiex: Do not use GFP_KERNEL in atomic context
	drm/gma500: fix error check
	scsi: qla4xxx: Fix an error handling path in 'qla4xxx_get_host_stats()'
	scsi: csiostor: Fix wrong return value in csio_hw_prep_fw()
	backlight: sky81452-backlight: Fix refcount imbalance on error
	VMCI: check return value of get_user_pages_fast() for errors
	tty: serial: earlycon dependency
	tty: hvcs: Don't NULL tty->driver_data until hvcs_cleanup()
	pty: do tty_flip_buffer_push without port->lock in pty_write
	drivers/virt/fsl_hypervisor: Fix error handling path
	video: fbdev: vga16fb: fix setting of pixclock because a pass-by-value error
	video: fbdev: sis: fix null ptr dereference
	HID: roccat: add bounds checking in kone_sysfs_write_settings()
	ath6kl: wmi: prevent a shift wrapping bug in ath6kl_wmi_delete_pstream_cmd()
	misc: mic: scif: Fix error handling path
	ALSA: seq: oss: Avoid mutex lock for a long-time ioctl
	quota: clear padding in v2r1_mem2diskdqb()
	net: enic: Cure the enic api locking trainwreck
	mfd: sm501: Fix leaks in probe()
	iwlwifi: mvm: split a print to avoid a WARNING in ROC
	usb: gadget: f_ncm: fix ncm_bitrate for SuperSpeed and above.
	usb: gadget: u_ether: enable qmult on SuperSpeed Plus as well
	nl80211: fix non-split wiphy information
	scsi: be2iscsi: Fix a theoretical leak in beiscsi_create_eqs()
	mwifiex: fix double free
	net: korina: fix kfree of rx/tx descriptor array
	IB/mlx4: Fix starvation in paravirt mux/demux
	IB/mlx4: Adjust delayed work when a dup is observed
	powerpc/pseries: Fix missing of_node_put() in rng_init()
	powerpc/icp-hv: Fix missing of_node_put() in success path
	mtd: lpddr: fix excessive stack usage with clang
	mtd: mtdoops: Don't write panic data twice
	ARM: 9007/1: l2c: fix prefetch bits init in L2X0_AUX_CTRL using DT values
	RDMA/qedr: Fix use of uninitialized field
	powerpc/tau: Use appropriate temperature sample interval
	powerpc/tau: Remove duplicated set_thresholds() call
	powerpc/tau: Disable TAU between measurements
	perf intel-pt: Fix "context_switch event has no tid" error
	RDMA/hns: Set the unsupported wr opcode
	kdb: Fix pager search for multi-line strings
	overflow: Include header file with SIZE_MAX declaration
	powerpc/perf: Exclude pmc5/6 from the irrelevant PMU group constraints
	powerpc/perf/hv-gpci: Fix starting index value
	cpufreq: powernv: Fix frame-size-overflow in powernv_cpufreq_reboot_notifier
	IB/rdmavt: Fix sizeof mismatch
	lib/crc32.c: fix trivial typo in preprocessor condition
	rapidio: fix error handling path
	rapidio: fix the missed put_device() for rio_mport_add_riodev
	clk: at91: clk-main: update key before writing AT91_CKGR_MOR
	clk: bcm2835: add missing release if devm_clk_hw_register fails
	vfio/pci: Clear token on bypass registration failure
	Input: imx6ul_tsc - clean up some errors in imx6ul_tsc_resume()
	Input: ep93xx_keypad - fix handling of platform_get_irq() error
	Input: omap4-keypad - fix handling of platform_get_irq() error
	Input: twl4030_keypad - fix handling of platform_get_irq() error
	Input: sun4i-ps2 - fix handling of platform_get_irq() error
	KVM: x86: emulating RDPID failure shall return #UD rather than #GP
	memory: omap-gpmc: Fix a couple off by ones
	memory: fsl-corenet-cf: Fix handling of platform_get_irq() error
	arm64: dts: qcom: msm8916: Fix MDP/DSI interrupts
	arm64: dts: zynqmp: Remove additional compatible string for i2c IPs
	powerpc/powernv/dump: Fix race while processing OPAL dump
	nvmet: fix uninitialized work for zero kato
	NTB: hw: amd: fix an issue about leak system resources
	crypto: ccp - fix error handling
	media: firewire: fix memory leak
	media: ati_remote: sanity check for both endpoints
	media: exynos4-is: Fix several reference count leaks due to pm_runtime_get_sync
	media: exynos4-is: Fix a reference count leak due to pm_runtime_get_sync
	media: exynos4-is: Fix a reference count leak
	media: vsp1: Fix runtime PM imbalance on error
	media: platform: s3c-camif: Fix runtime PM imbalance on error
	media: platform: sti: hva: Fix runtime PM imbalance on error
	media: bdisp: Fix runtime PM imbalance on error
	media: media/pci: prevent memory leak in bttv_probe
	media: uvcvideo: Ensure all probed info is returned to v4l2
	mmc: sdio: Check for CISTPL_VERS_1 buffer size
	media: saa7134: avoid a shift overflow
	fs: dlm: fix configfs memory leak
	ntfs: add check for mft record size in superblock
	PM: hibernate: remove the bogus call to get_gendisk() in software_resume()
	scsi: mvumi: Fix error return in mvumi_io_attach()
	scsi: target: core: Add CONTROL field for trace events
	mic: vop: copy data to kernel space then write to io memory
	misc: vop: add round_up(x,4) for vring_size to avoid kernel panic
	usb: gadget: function: printer: fix use-after-free in __lock_acquire
	udf: Limit sparing table size
	udf: Avoid accessing uninitialized data on failed inode read
	USB: cdc-acm: handle broken union descriptors
	ath9k: hif_usb: fix race condition between usb_get_urb() and usb_kill_anchored_urbs()
	misc: rtsx: Fix memory leak in rtsx_pci_probe
	reiserfs: only call unlock_new_inode() if I_NEW
	xfs: make sure the rt allocator doesn't run off the end
	usb: ohci: Default to per-port over-current protection
	Bluetooth: Only mark socket zapped after unlocking
	scsi: ibmvfc: Fix error return in ibmvfc_probe()
	brcmsmac: fix memory leak in wlc_phy_attach_lcnphy
	rtl8xxxu: prevent potential memory leak
	Fix use after free in get_capset_info callback.
	tty: ipwireless: fix error handling
	ipvs: Fix uninit-value in do_ip_vs_set_ctl()
	reiserfs: Fix memory leak in reiserfs_parse_options()
	brcm80211: fix possible memleak in brcmf_proto_msgbuf_attach
	usb: core: Solve race condition in anchor cleanup functions
	ath10k: check idx validity in __ath10k_htt_rx_ring_fill_n()
	net: korina: cast KSEG0 address to pointer in kfree
	usb: cdc-acm: add quirk to blacklist ETAS ES58X devices
	USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
	eeprom: at25: set minimum read/write access stride to 1
	usb: gadget: f_ncm: allow using NCM in SuperSpeed Plus gadgets.
	Linux 4.9.241

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Change-Id: Ie59605b312e5d0314299cad46ab57df803070564
2020-11-10 11:35:46 +03:00

473 lines
12 KiB
C

/*
* twl4030_keypad.c - driver for 8x8 keypad controller in twl4030 chips
*
* Copyright (C) 2007 Texas Instruments, Inc.
* Copyright (C) 2008 Nokia Corporation
*
* Code re-written for 2430SDP by:
* Syed Mohammed Khasim <x0khasim@ti.com>
*
* Initial Code:
* Manjunatha G K <manjugk@ti.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/i2c/twl.h>
#include <linux/slab.h>
#include <linux/of.h>
/*
* The TWL4030 family chips include a keypad controller that supports
* up to an 8x8 switch matrix. The controller can issue system wakeup
* events, since it uses only the always-on 32KiHz oscillator, and has
* an internal state machine that decodes pressed keys, including
* multi-key combinations.
*
* This driver lets boards define what keycodes they wish to report for
* which scancodes, as part of the "struct twl4030_keypad_data" used in
* the probe() routine.
*
* See the TPS65950 documentation; that's the general availability
* version of the TWL5030 second generation part.
*/
#define TWL4030_MAX_ROWS 8 /* TWL4030 hard limit */
#define TWL4030_MAX_COLS 8
/*
* Note that we add space for an extra column so that we can handle
* row lines connected to the gnd (see twl4030_col_xlate()).
*/
#define TWL4030_ROW_SHIFT 4
#define TWL4030_KEYMAP_SIZE (TWL4030_MAX_ROWS << TWL4030_ROW_SHIFT)
struct twl4030_keypad {
unsigned short keymap[TWL4030_KEYMAP_SIZE];
u16 kp_state[TWL4030_MAX_ROWS];
bool autorepeat;
unsigned int n_rows;
unsigned int n_cols;
int irq;
struct device *dbg_dev;
struct input_dev *input;
};
/*----------------------------------------------------------------------*/
/* arbitrary prescaler value 0..7 */
#define PTV_PRESCALER 4
/* Register Offsets */
#define KEYP_CTRL 0x00
#define KEYP_DEB 0x01
#define KEYP_LONG_KEY 0x02
#define KEYP_LK_PTV 0x03
#define KEYP_TIMEOUT_L 0x04
#define KEYP_TIMEOUT_H 0x05
#define KEYP_KBC 0x06
#define KEYP_KBR 0x07
#define KEYP_SMS 0x08
#define KEYP_FULL_CODE_7_0 0x09 /* row 0 column status */
#define KEYP_FULL_CODE_15_8 0x0a /* ... row 1 ... */
#define KEYP_FULL_CODE_23_16 0x0b
#define KEYP_FULL_CODE_31_24 0x0c
#define KEYP_FULL_CODE_39_32 0x0d
#define KEYP_FULL_CODE_47_40 0x0e
#define KEYP_FULL_CODE_55_48 0x0f
#define KEYP_FULL_CODE_63_56 0x10
#define KEYP_ISR1 0x11
#define KEYP_IMR1 0x12
#define KEYP_ISR2 0x13
#define KEYP_IMR2 0x14
#define KEYP_SIR 0x15
#define KEYP_EDR 0x16 /* edge triggers */
#define KEYP_SIH_CTRL 0x17
/* KEYP_CTRL_REG Fields */
#define KEYP_CTRL_SOFT_NRST BIT(0)
#define KEYP_CTRL_SOFTMODEN BIT(1)
#define KEYP_CTRL_LK_EN BIT(2)
#define KEYP_CTRL_TOE_EN BIT(3)
#define KEYP_CTRL_TOLE_EN BIT(4)
#define KEYP_CTRL_RP_EN BIT(5)
#define KEYP_CTRL_KBD_ON BIT(6)
/* KEYP_DEB, KEYP_LONG_KEY, KEYP_TIMEOUT_x*/
#define KEYP_PERIOD_US(t, prescale) ((t) / (31 << ((prescale) + 1)) - 1)
/* KEYP_LK_PTV_REG Fields */
#define KEYP_LK_PTV_PTV_SHIFT 5
/* KEYP_{IMR,ISR,SIR} Fields */
#define KEYP_IMR1_MIS BIT(3)
#define KEYP_IMR1_TO BIT(2)
#define KEYP_IMR1_LK BIT(1)
#define KEYP_IMR1_KP BIT(0)
/* KEYP_EDR Fields */
#define KEYP_EDR_KP_FALLING 0x01
#define KEYP_EDR_KP_RISING 0x02
#define KEYP_EDR_KP_BOTH 0x03
#define KEYP_EDR_LK_FALLING 0x04
#define KEYP_EDR_LK_RISING 0x08
#define KEYP_EDR_TO_FALLING 0x10
#define KEYP_EDR_TO_RISING 0x20
#define KEYP_EDR_MIS_FALLING 0x40
#define KEYP_EDR_MIS_RISING 0x80
/*----------------------------------------------------------------------*/
static int twl4030_kpread(struct twl4030_keypad *kp,
u8 *data, u32 reg, u8 num_bytes)
{
int ret = twl_i2c_read(TWL4030_MODULE_KEYPAD, data, reg, num_bytes);
if (ret < 0)
dev_warn(kp->dbg_dev,
"Couldn't read TWL4030: %X - ret %d[%x]\n",
reg, ret, ret);
return ret;
}
static int twl4030_kpwrite_u8(struct twl4030_keypad *kp, u8 data, u32 reg)
{
int ret = twl_i2c_write_u8(TWL4030_MODULE_KEYPAD, data, reg);
if (ret < 0)
dev_warn(kp->dbg_dev,
"Could not write TWL4030: %X - ret %d[%x]\n",
reg, ret, ret);
return ret;
}
static inline u16 twl4030_col_xlate(struct twl4030_keypad *kp, u8 col)
{
/*
* If all bits in a row are active for all columns then
* we have that row line connected to gnd. Mark this
* key on as if it was on matrix position n_cols (i.e.
* one higher than the size of the matrix).
*/
if (col == 0xFF)
return 1 << kp->n_cols;
else
return col & ((1 << kp->n_cols) - 1);
}
static int twl4030_read_kp_matrix_state(struct twl4030_keypad *kp, u16 *state)
{
u8 new_state[TWL4030_MAX_ROWS];
int row;
int ret = twl4030_kpread(kp, new_state,
KEYP_FULL_CODE_7_0, kp->n_rows);
if (ret >= 0)
for (row = 0; row < kp->n_rows; row++)
state[row] = twl4030_col_xlate(kp, new_state[row]);
return ret;
}
static bool twl4030_is_in_ghost_state(struct twl4030_keypad *kp, u16 *key_state)
{
int i;
u16 check = 0;
for (i = 0; i < kp->n_rows; i++) {
u16 col = key_state[i];
if ((col & check) && hweight16(col) > 1)
return true;
check |= col;
}
return false;
}
static void twl4030_kp_scan(struct twl4030_keypad *kp, bool release_all)
{
struct input_dev *input = kp->input;
u16 new_state[TWL4030_MAX_ROWS];
int col, row;
if (release_all) {
memset(new_state, 0, sizeof(new_state));
} else {
/* check for any changes */
int ret = twl4030_read_kp_matrix_state(kp, new_state);
if (ret < 0) /* panic ... */
return;
if (twl4030_is_in_ghost_state(kp, new_state))
return;
}
/* check for changes and print those */
for (row = 0; row < kp->n_rows; row++) {
int changed = new_state[row] ^ kp->kp_state[row];
if (!changed)
continue;
/* Extra column handles "all gnd" rows */
for (col = 0; col < kp->n_cols + 1; col++) {
int code;
if (!(changed & (1 << col)))
continue;
dev_dbg(kp->dbg_dev, "key [%d:%d] %s\n", row, col,
(new_state[row] & (1 << col)) ?
"press" : "release");
code = MATRIX_SCAN_CODE(row, col, TWL4030_ROW_SHIFT);
input_event(input, EV_MSC, MSC_SCAN, code);
input_report_key(input, kp->keymap[code],
new_state[row] & (1 << col));
}
kp->kp_state[row] = new_state[row];
}
input_sync(input);
}
/*
* Keypad interrupt handler
*/
static irqreturn_t do_kp_irq(int irq, void *_kp)
{
struct twl4030_keypad *kp = _kp;
u8 reg;
int ret;
/* Read & Clear TWL4030 pending interrupt */
ret = twl4030_kpread(kp, &reg, KEYP_ISR1, 1);
/*
* Release all keys if I2C has gone bad or
* the KEYP has gone to idle state.
*/
if (ret >= 0 && (reg & KEYP_IMR1_KP))
twl4030_kp_scan(kp, false);
else
twl4030_kp_scan(kp, true);
return IRQ_HANDLED;
}
static int twl4030_kp_program(struct twl4030_keypad *kp)
{
u8 reg;
int i;
/* Enable controller, with hardware decoding but not autorepeat */
reg = KEYP_CTRL_SOFT_NRST | KEYP_CTRL_SOFTMODEN
| KEYP_CTRL_TOE_EN | KEYP_CTRL_KBD_ON;
if (twl4030_kpwrite_u8(kp, reg, KEYP_CTRL) < 0)
return -EIO;
/*
* NOTE: we could use sih_setup() here to package keypad
* event sources as four different IRQs ... but we don't.
*/
/* Enable TO rising and KP rising and falling edge detection */
reg = KEYP_EDR_KP_BOTH | KEYP_EDR_TO_RISING;
if (twl4030_kpwrite_u8(kp, reg, KEYP_EDR) < 0)
return -EIO;
/* Set PTV prescaler Field */
reg = (PTV_PRESCALER << KEYP_LK_PTV_PTV_SHIFT);
if (twl4030_kpwrite_u8(kp, reg, KEYP_LK_PTV) < 0)
return -EIO;
/* Set key debounce time to 20 ms */
i = KEYP_PERIOD_US(20000, PTV_PRESCALER);
if (twl4030_kpwrite_u8(kp, i, KEYP_DEB) < 0)
return -EIO;
/* Set timeout period to 200 ms */
i = KEYP_PERIOD_US(200000, PTV_PRESCALER);
if (twl4030_kpwrite_u8(kp, (i & 0xFF), KEYP_TIMEOUT_L) < 0)
return -EIO;
if (twl4030_kpwrite_u8(kp, (i >> 8), KEYP_TIMEOUT_H) < 0)
return -EIO;
/*
* Enable Clear-on-Read; disable remembering events that fire
* after the IRQ but before our handler acks (reads) them.
*/
reg = TWL4030_SIH_CTRL_COR_MASK | TWL4030_SIH_CTRL_PENDDIS_MASK;
if (twl4030_kpwrite_u8(kp, reg, KEYP_SIH_CTRL) < 0)
return -EIO;
/* initialize key state; irqs update it from here on */
if (twl4030_read_kp_matrix_state(kp, kp->kp_state) < 0)
return -EIO;
return 0;
}
/*
* Registers keypad device with input subsystem
* and configures TWL4030 keypad registers
*/
static int twl4030_kp_probe(struct platform_device *pdev)
{
struct twl4030_keypad_data *pdata = dev_get_platdata(&pdev->dev);
const struct matrix_keymap_data *keymap_data = NULL;
struct twl4030_keypad *kp;
struct input_dev *input;
u8 reg;
int error;
kp = devm_kzalloc(&pdev->dev, sizeof(*kp), GFP_KERNEL);
if (!kp)
return -ENOMEM;
input = devm_input_allocate_device(&pdev->dev);
if (!input)
return -ENOMEM;
/* get the debug device */
kp->dbg_dev = &pdev->dev;
kp->input = input;
/* setup input device */
input->name = "TWL4030 Keypad";
input->phys = "twl4030_keypad/input0";
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0003;
if (pdata) {
if (!pdata->rows || !pdata->cols || !pdata->keymap_data) {
dev_err(&pdev->dev, "Missing platform_data\n");
return -EINVAL;
}
kp->n_rows = pdata->rows;
kp->n_cols = pdata->cols;
kp->autorepeat = pdata->rep;
keymap_data = pdata->keymap_data;
} else {
error = matrix_keypad_parse_of_params(&pdev->dev, &kp->n_rows,
&kp->n_cols);
if (error)
return error;
kp->autorepeat = true;
}
if (kp->n_rows > TWL4030_MAX_ROWS || kp->n_cols > TWL4030_MAX_COLS) {
dev_err(&pdev->dev,
"Invalid rows/cols amount specified in platform/devicetree data\n");
return -EINVAL;
}
kp->irq = platform_get_irq(pdev, 0);
if (kp->irq < 0)
return kp->irq;
error = matrix_keypad_build_keymap(keymap_data, NULL,
TWL4030_MAX_ROWS,
1 << TWL4030_ROW_SHIFT,
kp->keymap, input);
if (error) {
dev_err(kp->dbg_dev, "Failed to build keymap\n");
return error;
}
input_set_capability(input, EV_MSC, MSC_SCAN);
/* Enable auto repeat feature of Linux input subsystem */
if (kp->autorepeat)
__set_bit(EV_REP, input->evbit);
error = input_register_device(input);
if (error) {
dev_err(kp->dbg_dev,
"Unable to register twl4030 keypad device\n");
return error;
}
error = twl4030_kp_program(kp);
if (error)
return error;
/*
* This ISR will always execute in kernel thread context because of
* the need to access the TWL4030 over the I2C bus.
*
* NOTE: we assume this host is wired to TWL4040 INT1, not INT2 ...
*/
error = devm_request_threaded_irq(&pdev->dev, kp->irq, NULL, do_kp_irq,
0, pdev->name, kp);
if (error) {
dev_info(kp->dbg_dev, "request_irq failed for irq no=%d: %d\n",
kp->irq, error);
return error;
}
/* Enable KP and TO interrupts now. */
reg = (u8) ~(KEYP_IMR1_KP | KEYP_IMR1_TO);
if (twl4030_kpwrite_u8(kp, reg, KEYP_IMR1)) {
/* mask all events - we don't care about the result */
(void) twl4030_kpwrite_u8(kp, 0xff, KEYP_IMR1);
return -EIO;
}
platform_set_drvdata(pdev, kp);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id twl4030_keypad_dt_match_table[] = {
{ .compatible = "ti,twl4030-keypad" },
{},
};
MODULE_DEVICE_TABLE(of, twl4030_keypad_dt_match_table);
#endif
/*
* NOTE: twl4030 are multi-function devices connected via I2C.
* So this device is a child of an I2C parent, thus it needs to
* support unplug/replug (which most platform devices don't).
*/
static struct platform_driver twl4030_kp_driver = {
.probe = twl4030_kp_probe,
.driver = {
.name = "twl4030_keypad",
.of_match_table = of_match_ptr(twl4030_keypad_dt_match_table),
},
};
module_platform_driver(twl4030_kp_driver);
MODULE_AUTHOR("Texas Instruments");
MODULE_DESCRIPTION("TWL4030 Keypad Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:twl4030_keypad");