forked from kdave/btrfs-devel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
This patch adds key and switch support for the Azoteq IQS620A, IQS621, IQS622, IQS624 and IQS625 multi-function sensors. Signed-off-by: Jeff LaBundy <[email protected]> Acked-by: Dmitry Torokhov <[email protected]> Signed-off-by: Lee Jones <[email protected]>
- Loading branch information
Showing
3 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,335 @@ | ||
// SPDX-License-Identifier: GPL-2.0+ | ||
/* | ||
* Azoteq IQS620A/621/622/624/625 Keys and Switches | ||
* | ||
* Copyright (C) 2019 Jeff LaBundy <[email protected]> | ||
*/ | ||
|
||
#include <linux/device.h> | ||
#include <linux/input.h> | ||
#include <linux/kernel.h> | ||
#include <linux/mfd/iqs62x.h> | ||
#include <linux/module.h> | ||
#include <linux/notifier.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/property.h> | ||
#include <linux/regmap.h> | ||
#include <linux/slab.h> | ||
|
||
enum { | ||
IQS62X_SW_HALL_N, | ||
IQS62X_SW_HALL_S, | ||
}; | ||
|
||
static const char * const iqs62x_switch_names[] = { | ||
[IQS62X_SW_HALL_N] = "hall-switch-north", | ||
[IQS62X_SW_HALL_S] = "hall-switch-south", | ||
}; | ||
|
||
struct iqs62x_switch_desc { | ||
enum iqs62x_event_flag flag; | ||
unsigned int code; | ||
bool enabled; | ||
}; | ||
|
||
struct iqs62x_keys_private { | ||
struct iqs62x_core *iqs62x; | ||
struct input_dev *input; | ||
struct notifier_block notifier; | ||
struct iqs62x_switch_desc switches[ARRAY_SIZE(iqs62x_switch_names)]; | ||
unsigned int keycode[IQS62X_NUM_KEYS]; | ||
unsigned int keycodemax; | ||
u8 interval; | ||
}; | ||
|
||
static int iqs62x_keys_parse_prop(struct platform_device *pdev, | ||
struct iqs62x_keys_private *iqs62x_keys) | ||
{ | ||
struct fwnode_handle *child; | ||
unsigned int val; | ||
int ret, i; | ||
|
||
ret = device_property_count_u32(&pdev->dev, "linux,keycodes"); | ||
if (ret > IQS62X_NUM_KEYS) { | ||
dev_err(&pdev->dev, "Too many keycodes present\n"); | ||
return -EINVAL; | ||
} else if (ret < 0) { | ||
dev_err(&pdev->dev, "Failed to count keycodes: %d\n", ret); | ||
return ret; | ||
} | ||
iqs62x_keys->keycodemax = ret; | ||
|
||
ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes", | ||
iqs62x_keys->keycode, | ||
iqs62x_keys->keycodemax); | ||
if (ret) { | ||
dev_err(&pdev->dev, "Failed to read keycodes: %d\n", ret); | ||
return ret; | ||
} | ||
|
||
for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) { | ||
child = device_get_named_child_node(&pdev->dev, | ||
iqs62x_switch_names[i]); | ||
if (!child) | ||
continue; | ||
|
||
ret = fwnode_property_read_u32(child, "linux,code", &val); | ||
if (ret) { | ||
dev_err(&pdev->dev, "Failed to read switch code: %d\n", | ||
ret); | ||
return ret; | ||
} | ||
iqs62x_keys->switches[i].code = val; | ||
iqs62x_keys->switches[i].enabled = true; | ||
|
||
if (fwnode_property_present(child, "azoteq,use-prox")) | ||
iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ? | ||
IQS62X_EVENT_HALL_N_P : | ||
IQS62X_EVENT_HALL_S_P); | ||
else | ||
iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ? | ||
IQS62X_EVENT_HALL_N_T : | ||
IQS62X_EVENT_HALL_S_T); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int iqs62x_keys_init(struct iqs62x_keys_private *iqs62x_keys) | ||
{ | ||
struct iqs62x_core *iqs62x = iqs62x_keys->iqs62x; | ||
enum iqs62x_event_flag flag; | ||
unsigned int event_reg, val; | ||
unsigned int event_mask = 0; | ||
int ret, i; | ||
|
||
switch (iqs62x->dev_desc->prod_num) { | ||
case IQS620_PROD_NUM: | ||
case IQS621_PROD_NUM: | ||
case IQS622_PROD_NUM: | ||
event_reg = IQS620_GLBL_EVENT_MASK; | ||
|
||
/* | ||
* Discreet button, hysteresis and SAR UI flags represent keys | ||
* and are unmasked if mapped to a valid keycode. | ||
*/ | ||
for (i = 0; i < iqs62x_keys->keycodemax; i++) { | ||
if (iqs62x_keys->keycode[i] == KEY_RESERVED) | ||
continue; | ||
|
||
if (iqs62x_events[i].reg == IQS62X_EVENT_PROX) | ||
event_mask |= iqs62x->dev_desc->prox_mask; | ||
else if (iqs62x_events[i].reg == IQS62X_EVENT_HYST) | ||
event_mask |= (iqs62x->dev_desc->hyst_mask | | ||
iqs62x->dev_desc->sar_mask); | ||
} | ||
|
||
ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->hall_flags, | ||
&val); | ||
if (ret) | ||
return ret; | ||
|
||
/* | ||
* Hall UI flags represent switches and are unmasked if their | ||
* corresponding child nodes are present. | ||
*/ | ||
for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) { | ||
if (!(iqs62x_keys->switches[i].enabled)) | ||
continue; | ||
|
||
flag = iqs62x_keys->switches[i].flag; | ||
|
||
if (iqs62x_events[flag].reg != IQS62X_EVENT_HALL) | ||
continue; | ||
|
||
event_mask |= iqs62x->dev_desc->hall_mask; | ||
|
||
input_report_switch(iqs62x_keys->input, | ||
iqs62x_keys->switches[i].code, | ||
(val & iqs62x_events[flag].mask) == | ||
iqs62x_events[flag].val); | ||
} | ||
|
||
input_sync(iqs62x_keys->input); | ||
break; | ||
|
||
case IQS624_PROD_NUM: | ||
event_reg = IQS624_HALL_UI; | ||
|
||
/* | ||
* Interval change events represent keys and are unmasked if | ||
* either wheel movement flag is mapped to a valid keycode. | ||
*/ | ||
if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP] != KEY_RESERVED) | ||
event_mask |= IQS624_HALL_UI_INT_EVENT; | ||
|
||
if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN] != KEY_RESERVED) | ||
event_mask |= IQS624_HALL_UI_INT_EVENT; | ||
|
||
ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->interval, | ||
&val); | ||
if (ret) | ||
return ret; | ||
|
||
iqs62x_keys->interval = val; | ||
break; | ||
|
||
default: | ||
return 0; | ||
} | ||
|
||
return regmap_update_bits(iqs62x->regmap, event_reg, event_mask, 0); | ||
} | ||
|
||
static int iqs62x_keys_notifier(struct notifier_block *notifier, | ||
unsigned long event_flags, void *context) | ||
{ | ||
struct iqs62x_event_data *event_data = context; | ||
struct iqs62x_keys_private *iqs62x_keys; | ||
int ret, i; | ||
|
||
iqs62x_keys = container_of(notifier, struct iqs62x_keys_private, | ||
notifier); | ||
|
||
if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) { | ||
ret = iqs62x_keys_init(iqs62x_keys); | ||
if (ret) { | ||
dev_err(iqs62x_keys->input->dev.parent, | ||
"Failed to re-initialize device: %d\n", ret); | ||
return NOTIFY_BAD; | ||
} | ||
|
||
return NOTIFY_OK; | ||
} | ||
|
||
for (i = 0; i < iqs62x_keys->keycodemax; i++) { | ||
if (iqs62x_events[i].reg == IQS62X_EVENT_WHEEL && | ||
event_data->interval == iqs62x_keys->interval) | ||
continue; | ||
|
||
input_report_key(iqs62x_keys->input, iqs62x_keys->keycode[i], | ||
event_flags & BIT(i)); | ||
} | ||
|
||
for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) | ||
if (iqs62x_keys->switches[i].enabled) | ||
input_report_switch(iqs62x_keys->input, | ||
iqs62x_keys->switches[i].code, | ||
event_flags & | ||
BIT(iqs62x_keys->switches[i].flag)); | ||
|
||
input_sync(iqs62x_keys->input); | ||
|
||
if (event_data->interval == iqs62x_keys->interval) | ||
return NOTIFY_OK; | ||
|
||
/* | ||
* Each frame contains at most one wheel event (up or down), in which | ||
* case a complementary release cycle is emulated. | ||
*/ | ||
if (event_flags & BIT(IQS62X_EVENT_WHEEL_UP)) { | ||
input_report_key(iqs62x_keys->input, | ||
iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP], | ||
0); | ||
input_sync(iqs62x_keys->input); | ||
} else if (event_flags & BIT(IQS62X_EVENT_WHEEL_DN)) { | ||
input_report_key(iqs62x_keys->input, | ||
iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN], | ||
0); | ||
input_sync(iqs62x_keys->input); | ||
} | ||
|
||
iqs62x_keys->interval = event_data->interval; | ||
|
||
return NOTIFY_OK; | ||
} | ||
|
||
static int iqs62x_keys_probe(struct platform_device *pdev) | ||
{ | ||
struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent); | ||
struct iqs62x_keys_private *iqs62x_keys; | ||
struct input_dev *input; | ||
int ret, i; | ||
|
||
iqs62x_keys = devm_kzalloc(&pdev->dev, sizeof(*iqs62x_keys), | ||
GFP_KERNEL); | ||
if (!iqs62x_keys) | ||
return -ENOMEM; | ||
|
||
platform_set_drvdata(pdev, iqs62x_keys); | ||
|
||
ret = iqs62x_keys_parse_prop(pdev, iqs62x_keys); | ||
if (ret) | ||
return ret; | ||
|
||
input = devm_input_allocate_device(&pdev->dev); | ||
if (!input) | ||
return -ENOMEM; | ||
|
||
input->keycodemax = iqs62x_keys->keycodemax; | ||
input->keycode = iqs62x_keys->keycode; | ||
input->keycodesize = sizeof(*iqs62x_keys->keycode); | ||
|
||
input->name = iqs62x->dev_desc->dev_name; | ||
input->id.bustype = BUS_I2C; | ||
|
||
for (i = 0; i < iqs62x_keys->keycodemax; i++) | ||
if (iqs62x_keys->keycode[i] != KEY_RESERVED) | ||
input_set_capability(input, EV_KEY, | ||
iqs62x_keys->keycode[i]); | ||
|
||
for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) | ||
if (iqs62x_keys->switches[i].enabled) | ||
input_set_capability(input, EV_SW, | ||
iqs62x_keys->switches[i].code); | ||
|
||
iqs62x_keys->iqs62x = iqs62x; | ||
iqs62x_keys->input = input; | ||
|
||
ret = iqs62x_keys_init(iqs62x_keys); | ||
if (ret) { | ||
dev_err(&pdev->dev, "Failed to initialize device: %d\n", ret); | ||
return ret; | ||
} | ||
|
||
ret = input_register_device(iqs62x_keys->input); | ||
if (ret) { | ||
dev_err(&pdev->dev, "Failed to register device: %d\n", ret); | ||
return ret; | ||
} | ||
|
||
iqs62x_keys->notifier.notifier_call = iqs62x_keys_notifier; | ||
ret = blocking_notifier_chain_register(&iqs62x_keys->iqs62x->nh, | ||
&iqs62x_keys->notifier); | ||
if (ret) | ||
dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret); | ||
|
||
return ret; | ||
} | ||
|
||
static int iqs62x_keys_remove(struct platform_device *pdev) | ||
{ | ||
struct iqs62x_keys_private *iqs62x_keys = platform_get_drvdata(pdev); | ||
int ret; | ||
|
||
ret = blocking_notifier_chain_unregister(&iqs62x_keys->iqs62x->nh, | ||
&iqs62x_keys->notifier); | ||
if (ret) | ||
dev_err(&pdev->dev, "Failed to unregister notifier: %d\n", ret); | ||
|
||
return ret; | ||
} | ||
|
||
static struct platform_driver iqs62x_keys_platform_driver = { | ||
.driver = { | ||
.name = "iqs62x-keys", | ||
}, | ||
.probe = iqs62x_keys_probe, | ||
.remove = iqs62x_keys_remove, | ||
}; | ||
module_platform_driver(iqs62x_keys_platform_driver); | ||
|
||
MODULE_AUTHOR("Jeff LaBundy <[email protected]>"); | ||
MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Keys and Switches"); | ||
MODULE_LICENSE("GPL"); | ||
MODULE_ALIAS("platform:iqs62x-keys"); |