mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
8.6 KiB
335 lines
8.6 KiB
// 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");
|
|
|