* [PATCH v2 2/2] HID: google: add support tablet mode switch for Whiskers
From: Dmitry Torokhov @ 2018-10-05 18:41 UTC (permalink / raw)
To: Jiri Kosina, Lee Jones
Cc: Benjamin Tissoires, linux-input, linux-kernel, Nicolas Boichat
In-Reply-To: <20181005184114.207503-1-dtor@chromium.org>
Whiskers is a foldable base, and thus requires combining "base presence"
signal coming from EC with base state signal (folded/unfolded) coming
from USB/HID interface to produce proper SW_TABLET_MODE event.
Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
Signed-off-by: Dmitry Torokhov <dtor@chromium.org>
---
v2 changes (addressing Benjamin comments):
- moved everything into hid-google-hammer.c
- dropped USB interface parsing in favor of report parsing
- removed product/model from tablet mode switch device (this is different
from what Benjamin requested - using Google USB vendor ID - but
it is purely host interface - BUS_HOST - not USB or I2C, and those do
not carry vendor/product ID typically. Userspace is simply supposed
to check capabilities of the device).
drivers/hid/hid-google-hammer.c | 413 ++++++++++++++++++++++++++++++--
1 file changed, 394 insertions(+), 19 deletions(-)
diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
index 6bf4da7ad63a..de3e7d055ca4 100644
--- a/drivers/hid/hid-google-hammer.c
+++ b/drivers/hid/hid-google-hammer.c
@@ -13,16 +13,268 @@
* any later version.
*/
+#include <linux/acpi.h>
#include <linux/hid.h>
#include <linux/leds.h>
+#include <linux/mfd/cros_ec.h>
+#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeup.h>
+#include <asm/unaligned.h>
#include "hid-ids.h"
-#define MAX_BRIGHTNESS 100
+/*
+ * C(hrome)B(ase)A(ttached)S(witch) - switch exported by Chrome EC and reporting
+ * state of the "Whiskers" base - attached or detached. Whiskers USB device also
+ * reports position of the keyboard - folded or not. Combining base state and
+ * position allows us to generate proper "Tablet mode" events.
+ */
+struct cbas_ec {
+ struct device *dev; /* The platform device (EC) */
+ struct input_dev *input;
+ bool base_present;
+ struct notifier_block notifier;
+};
-/* HID usage for keyboard backlight (Alphanumeric display brightness) */
-#define HID_AD_BRIGHTNESS 0x00140046
+static struct cbas_ec cbas_ec;
+static DEFINE_SPINLOCK(cbas_ec_lock);
+static DEFINE_MUTEX(cbas_ec_reglock);
+
+static bool cbas_parse_base_state(const void *data)
+{
+ u32 switches = get_unaligned_le32(data);
+
+ return !!(switches & BIT(EC_MKBP_BASE_ATTACHED));
+}
+
+static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state,
+ bool *state)
+{
+ struct ec_params_mkbp_info *params;
+ struct cros_ec_command *msg;
+ int ret;
+
+ msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)),
+ GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->command = EC_CMD_MKBP_INFO;
+ msg->version = 1;
+ msg->outsize = sizeof(*params);
+ msg->insize = sizeof(u32);
+ params = (struct ec_params_mkbp_info *)msg->data;
+ params->info_type = get_state ?
+ EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED;
+ params->event_type = EC_MKBP_EVENT_SWITCH;
+
+ ret = cros_ec_cmd_xfer_status(ec_dev, msg);
+ if (ret >= 0) {
+ if (ret != sizeof(u32)) {
+ dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n",
+ ret, sizeof(u32));
+ ret = -EPROTO;
+ } else {
+ *state = cbas_parse_base_state(msg->data);
+ ret = 0;
+ }
+ }
+
+ kfree(msg);
+
+ return ret;
+}
+
+static int cbas_ec_notify(struct notifier_block *nb,
+ unsigned long queued_during_suspend,
+ void *_notify)
+{
+ struct cros_ec_device *ec = _notify;
+ unsigned long flags;
+ bool base_present;
+
+ if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) {
+ base_present = cbas_parse_base_state(
+ &ec->event_data.data.switches);
+ dev_dbg(cbas_ec.dev,
+ "%s: base: %d\n", __func__, base_present);
+
+ if (device_may_wakeup(cbas_ec.dev) ||
+ !queued_during_suspend) {
+
+ pm_wakeup_event(cbas_ec.dev, 0);
+
+ spin_lock_irqsave(&cbas_ec_lock, flags);
+
+ /*
+ * While input layer dedupes the events, we do not want
+ * to disrupt the state reported by the base by
+ * overriding it with state reported by the LID. Only
+ * report changes, as we assume that on attach the base
+ * is not folded.
+ */
+ if (base_present != cbas_ec.base_present) {
+ input_report_switch(cbas_ec.input,
+ SW_TABLET_MODE,
+ !base_present);
+ input_sync(cbas_ec.input);
+ cbas_ec.base_present = base_present;
+ }
+
+ spin_unlock_irqrestore(&cbas_ec_lock, flags);
+ }
+ }
+
+ return NOTIFY_OK;
+}
+
+static __maybe_unused int cbas_ec_resume(struct device *dev)
+{
+ struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
+ bool base_present;
+ int error;
+
+ error = cbas_ec_query_base(ec, true, &base_present);
+ if (error) {
+ dev_warn(dev, "failed to fetch base state on resume: %d\n",
+ error);
+ } else {
+ spin_lock_irq(&cbas_ec_lock);
+
+ cbas_ec.base_present = base_present;
+
+ /*
+ * Only report if base is disconnected. If base is connected,
+ * it will resend its state on resume, and we'll update it
+ * in hammer_event().
+ */
+ if (!cbas_ec.base_present) {
+ input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1);
+ input_sync(cbas_ec.input);
+ }
+
+ spin_unlock_irq(&cbas_ec_lock);
+ }
+
+ return 0;
+}
+
+static const SIMPLE_DEV_PM_OPS(cbas_ec_pm_ops, NULL, cbas_ec_resume);
+
+static void cbas_ec_set_input(struct input_dev *input)
+{
+ /* Take the lock so hammer_event() does not race with us here */
+ spin_lock_irq(&cbas_ec_lock);
+ cbas_ec.input = input;
+ spin_unlock_irq(&cbas_ec_lock);
+}
+
+static int __cbas_ec_probe(struct platform_device *pdev)
+{
+ struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
+ struct input_dev *input;
+ bool base_supported;
+ int error;
+
+ error = cbas_ec_query_base(ec, false, &base_supported);
+ if (error)
+ return error;
+
+ if (!base_supported)
+ return -ENXIO;
+
+ input = devm_input_allocate_device(&pdev->dev);
+ if (!input)
+ return -ENOMEM;
+
+ input->name = "Whiskers Tablet Mode Switch";
+ input->id.bustype = BUS_HOST;
+
+ input_set_capability(input, EV_SW, SW_TABLET_MODE);
+
+ error = input_register_device(input);
+ if (error) {
+ dev_err(&pdev->dev, "cannot register input device: %d\n",
+ error);
+ return error;
+ }
+
+ /* Seed the state */
+ error = cbas_ec_query_base(ec, true, &cbas_ec.base_present);
+ if (error) {
+ dev_err(&pdev->dev, "cannot query base state: %d\n", error);
+ return error;
+ }
+
+ input_report_switch(input, SW_TABLET_MODE, !cbas_ec.base_present);
+
+ cbas_ec_set_input(input);
+
+ cbas_ec.dev = &pdev->dev;
+ cbas_ec.notifier.notifier_call = cbas_ec_notify;
+ error = blocking_notifier_chain_register(&ec->event_notifier,
+ &cbas_ec.notifier);
+ if (error) {
+ dev_err(&pdev->dev, "cannot register notifier: %d\n", error);
+ cbas_ec_set_input(NULL);
+ return error;
+ }
+
+ device_init_wakeup(&pdev->dev, true);
+ return 0;
+}
+
+static int cbas_ec_probe(struct platform_device *pdev)
+{
+ int retval;
+
+ mutex_lock(&cbas_ec_reglock);
+
+ if (cbas_ec.input) {
+ retval = -EBUSY;
+ goto out;
+ }
+
+ retval = __cbas_ec_probe(pdev);
+
+out:
+ mutex_unlock(&cbas_ec_reglock);
+ return retval;
+}
+
+static int cbas_ec_remove(struct platform_device *pdev)
+{
+ struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
+
+ mutex_lock(&cbas_ec_reglock);
+
+ blocking_notifier_chain_unregister(&ec->event_notifier,
+ &cbas_ec.notifier);
+ cbas_ec_set_input(NULL);
+
+ mutex_unlock(&cbas_ec_reglock);
+ return 0;
+}
+
+static const struct acpi_device_id cbas_ec_acpi_ids[] = {
+ { "GOOG000B", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, cbas_ec_acpi_ids);
+
+static struct platform_driver cbas_ec_driver = {
+ .probe = cbas_ec_probe,
+ .remove = cbas_ec_remove,
+ .driver = {
+ .name = "cbas_ec",
+ .acpi_match_table = ACPI_PTR(cbas_ec_acpi_ids),
+ .pm = &cbas_ec_pm_ops,
+ },
+};
+
+#define MAX_BRIGHTNESS 100
struct hammer_kbd_leds {
struct led_classdev cdev;
@@ -90,33 +342,130 @@ static int hammer_register_leds(struct hid_device *hdev)
return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
}
-static int hammer_input_configured(struct hid_device *hdev,
- struct hid_input *hi)
+#define HID_UP_GOOGLEVENDOR 0xffd10000
+#define HID_VD_KBD_FOLDED 0x00000019
+#define WHISKERS_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
+
+/* HID usage for keyboard backlight (Alphanumeric display brightness) */
+#define HID_AD_BRIGHTNESS 0x00140046
+
+static int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+ struct hid_field *field,
+ struct hid_usage *usage,
+ unsigned long **bit, int *max)
{
- struct list_head *report_list =
- &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
+ if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
+ usage->hid == WHISKERS_KBD_FOLDED) {
+ /*
+ * We do not want to have this usage mapped as it will get
+ * mixed in with "base attached" signal and delivered over
+ * separate input device for tablet switch mode.
+ */
+ return -1;
+ }
+
+ return 0;
+}
+
+static int hammer_event(struct hid_device *hid, struct hid_field *field,
+ struct hid_usage *usage, __s32 value)
+{
+ unsigned long flags;
+
+ if (hid->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
+ usage->hid == WHISKERS_KBD_FOLDED) {
+ spin_lock_irqsave(&cbas_ec_lock, flags);
+
+ hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
+ cbas_ec.base_present, value);
+
+ /*
+ * We should not get event if base is detached, but in case
+ * we happen to service HID and EC notifications out of order
+ * let's still check the "base present" flag.
+ */
+ if (cbas_ec.input && cbas_ec.base_present) {
+ input_report_switch(cbas_ec.input,
+ SW_TABLET_MODE, value);
+ input_sync(cbas_ec.input);
+ }
+
+ spin_unlock_irqrestore(&cbas_ec_lock, flags);
+ return 1; /* We handled this event */
+ }
+
+ return 0;
+}
+
+static bool hammer_is_keyboard_interface(struct hid_device *hdev)
+{
+ struct hid_report_enum *re = &hdev->report_enum[HID_INPUT_REPORT];
struct hid_report *report;
- if (list_empty(report_list))
- return 0;
+ list_for_each_entry(report, &re->report_list, list)
+ if (report->application == HID_GD_KEYBOARD)
+ return true;
- report = list_first_entry(report_list, struct hid_report, list);
+ return false;
+}
+
+static bool hammer_has_backlight_control(struct hid_device *hdev)
+{
+ struct hid_report_enum *re = &hdev->report_enum[HID_OUTPUT_REPORT];
+ struct hid_report *report;
+ int i, j;
- if (report->maxfield == 1 &&
- report->field[0]->application == HID_GD_KEYBOARD &&
- report->field[0]->maxusage == 1 &&
- report->field[0]->usage[0].hid == HID_AD_BRIGHTNESS) {
- int err = hammer_register_leds(hdev);
+ list_for_each_entry(report, &re->report_list, list) {
+ if (report->application != HID_GD_KEYBOARD)
+ continue;
- if (err)
+ for (i = 0; i < report->maxfield; i++) {
+ struct hid_field *field = report->field[i];
+
+ for (j = 0; j < field->maxusage; j++)
+ if (field->usage[j].hid == HID_AD_BRIGHTNESS)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int hammer_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int error;
+
+ /*
+ * We always want to poll for, and handle tablet mode events from
+ * Whiskers, even when nobody has opened the input device. This also
+ * prevents the hid core from dropping early tablet mode events from
+ * the device.
+ */
+ if (hdev->product == USB_DEVICE_ID_GOOGLE_WHISKERS &&
+ hammer_is_keyboard_interface(hdev))
+ hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
+
+ error = hid_parse(hdev);
+ if (error)
+ return error;
+
+ error = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (error)
+ return error;
+
+ if (hammer_has_backlight_control(hdev)) {
+ error = hammer_register_leds(hdev);
+ if (error)
hid_warn(hdev,
"Failed to register keyboard backlight: %d\n",
- err);
+ error);
}
return 0;
}
+
static const struct hid_device_id hammer_devices[] = {
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) },
@@ -133,8 +482,34 @@ MODULE_DEVICE_TABLE(hid, hammer_devices);
static struct hid_driver hammer_driver = {
.name = "hammer",
.id_table = hammer_devices,
- .input_configured = hammer_input_configured,
+ .probe = hammer_probe,
+ .input_mapping = hammer_input_mapping,
+ .event = hammer_event,
};
-module_hid_driver(hammer_driver);
+
+static int __init hammer_init(void)
+{
+ int error;
+
+ error = platform_driver_register(&cbas_ec_driver);
+ if (error)
+ return error;
+
+ error = hid_register_driver(&hammer_driver);
+ if (error) {
+ platform_driver_unregister(&cbas_ec_driver);
+ return error;
+ }
+
+ return 0;
+}
+module_init(hammer_init);
+
+static void __exit hammer_exit(void)
+{
+ hid_unregister_driver(&hammer_driver);
+ platform_driver_unregister(&cbas_ec_driver);
+}
+module_exit(hammer_exit);
MODULE_LICENSE("GPL");
--
2.19.0.605.g01d371f741-goog
^ permalink raw reply related
* [PATCH v2 1/2] mfd: cros: add "base attached" MKBP switch definition
From: Dmitry Torokhov @ 2018-10-05 18:41 UTC (permalink / raw)
To: Jiri Kosina, Lee Jones
Cc: Benjamin Tissoires, linux-input, linux-kernel, Nicolas Boichat
This adds a "base attached" switch definition to the MKBP protocol that
is used by Whiskers driver to properly determine device state (clamshell
vs tablet mode).
Signed-off-by: Dmitry Torokhov <dtor@chromium.org>
---
v2 changes: None
Lee, I was wondering if it would be OK for cros_ec_commands.h to be
merged through HID tree.
include/linux/mfd/cros_ec_commands.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/linux/mfd/cros_ec_commands.h b/include/linux/mfd/cros_ec_commands.h
index 20ee71f10865..5fd0e429f472 100644
--- a/include/linux/mfd/cros_ec_commands.h
+++ b/include/linux/mfd/cros_ec_commands.h
@@ -2132,6 +2132,7 @@ struct ec_response_get_next_event_v1 {
/* Switches */
#define EC_MKBP_LID_OPEN 0
#define EC_MKBP_TABLET_MODE 1
+#define EC_MKBP_BASE_ATTACHED 2
/*****************************************************************************/
/* Temperature sensor commands */
--
2.19.0.605.g01d371f741-goog
^ permalink raw reply related
* [PATCH] input: st1232: set INPUT_PROP_DIRECT property
From: Martin Kepplinger @ 2018-10-05 8:14 UTC (permalink / raw)
To: dmitry.torokhov, geert+renesas
Cc: linux-input, linux-kernel, Martin Kepplinger
This is how userspace checks for touchscreen devices most reliably.
Signed-off-by: Martin Kepplinger <martink@posteo.de>
---
drivers/input/touchscreen/st1232.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/input/touchscreen/st1232.c b/drivers/input/touchscreen/st1232.c
index d5dfa4053bbf..b71673911aac 100644
--- a/drivers/input/touchscreen/st1232.c
+++ b/drivers/input/touchscreen/st1232.c
@@ -195,6 +195,7 @@ static int st1232_ts_probe(struct i2c_client *client,
input_dev->id.bustype = BUS_I2C;
input_dev->dev.parent = &client->dev;
+ __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
__set_bit(EV_SYN, input_dev->evbit);
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(EV_ABS, input_dev->evbit);
--
2.19.0
^ permalink raw reply related
* Re: [PATCH 0/5] ti_am335x_tsc: Enable wakeup capability
From: Vignesh R @ 2018-10-05 5:11 UTC (permalink / raw)
To: Lee Jones
Cc: Dmitry Torokhov, Jonathan Cameron, linux-iio, linux-omap,
linux-kernel, linux-input
In-Reply-To: <f61848d8-57f6-14c0-eb74-cab488ceda50@ti.com>
On Friday 28 September 2018 11:42 AM, Vignesh R wrote:
> Hi Lee,
>
> On Wednesday 25 July 2018 10:56 AM, Lee Jones wrote:
> [...]
>>>>>>>>
>>>>>>>> Vignesh R (5):
>>>>>>>> mfd: ti_am335x_tscadc: Don't mark TSCADC MFD as wakeup capable
>>>>>>>> Input: ti_am335x_tsc: Mark TSC device as wakeup source
>>>>>>>> mfd: ti_am335x_tscadc: Keep ADC interface on if child is wakeup
>>>>>>>> capable
>>>>>>>> iio: adc: ti_am335x_adc: Disable ADC during suspend unconditionally
>>>>>>>> Input: ti_am335x_tsc: Mark IRQ as wakeup capable
>>>>>>>>
>>>>>>>> drivers/iio/adc/ti_am335x_adc.c | 12 ++++--------
>>>>>>>> drivers/input/touchscreen/ti_am335x_tsc.c | 22 +++++++++++++++++-----
>>>>>>>> drivers/mfd/ti_am335x_tscadc.c | 14 +++++++++++++-
>>>>>>>> 3 files changed, 34 insertions(+), 14 deletions(-)
>>>>>>>>
>>>>>>>
>>>>>>> Gentle ping... Could you review/pick this series? MFD amd IIO bits are
>>>>>>> already ACKed
>>>>>>
>>>>>> MFD patches are reviewed "for my own reference" meaning that we
>>>>>> haven't yet agreed on a merge plan yet.
>>>>>
>>>>> I think this series makes sense to be pushed through a single tree as
>>>>> opposed to being spread between 3, even if it could technically be
>>>>> possible. It looks like Jonathan is fine with going it through either
>>>>> his or some other tree, I am fine with it going through MFD.
>>>>
>>>> I'm happy either way.
>>>>
>>> Thanks Dmitry, Jonathan and Lee Jones!
>>>
>>> Could this be applied to one of the trees now? MFD perhaps?
>>
>> It'll be applied, when it's applied. ;)
>
> I see that this series was not picked up for v4.19. Could you consider
> this series for v4.20? Patches apply cleanly against linux-next.
>
>
Gentle ping...
--
Regards
Vignesh
^ permalink raw reply
* [PATCH v2] HID: i2c-hid: Add a small delay after sleep command for Raydium touchpanel
From: Kai-Heng Feng @ 2018-10-05 4:46 UTC (permalink / raw)
To: jikos
Cc: benjamin.tissoires, hdegoede, linux-input, linux-kernel,
Kai-Heng Feng
Raydium touchpanel (2386:4B33) sometimes does not work in desktop session
although it works in display manager.
During user logging, the display manager exits, close the HID device,
then the device gets runtime suspended and powered off. The desktop
session begins shortly after, opens the HID device, then the device gets
runtime resumed and powered on.
If the trasition from display manager to desktop sesesion is fast, the
touchpanel cannot switch from powered off to powered on in short
timeframe. So add a small delay to workaround the issue.
Signed-off-by: Kai-Heng Feng <kai.heng.feng@canonical.com>
---
v2:
- Use quirk to only match affected touchpanel
- Only delay the next power on if the time hasn't elapsed
drivers/hid/hid-ids.h | 3 +++
drivers/hid/i2c-hid/i2c-hid-core.c | 19 +++++++++++++++++++
2 files changed, 22 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 16342188df19..c1b5f03eb630 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -926,6 +926,9 @@
#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3003 0x3003
#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008 0x3008
+#define I2C_VENDOR_ID_RAYDIUM 0x2386
+#define I2C_PRODUCT_ID_RAYDIUM_4B33 0x4b33
+
#define USB_VENDOR_ID_RAZER 0x1532
#define USB_DEVICE_ID_RAZER_BLADE_14 0x011D
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index 4aab96cf0818..3cde7c1b9c33 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -49,6 +49,7 @@
#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV BIT(0)
#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET BIT(1)
#define I2C_HID_QUIRK_NO_RUNTIME_PM BIT(2)
+#define I2C_HID_QUIRK_DELAY_AFTER_SLEEP BIT(3)
/* flags */
#define I2C_HID_STARTED 0
@@ -158,6 +159,8 @@ struct i2c_hid {
bool irq_wake_enabled;
struct mutex reset_lock;
+
+ unsigned long sleep_delay;
};
static const struct i2c_hid_quirks {
@@ -172,6 +175,8 @@ static const struct i2c_hid_quirks {
{ I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288,
I2C_HID_QUIRK_NO_IRQ_AFTER_RESET |
I2C_HID_QUIRK_NO_RUNTIME_PM },
+ { I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_4B33,
+ I2C_HID_QUIRK_DELAY_AFTER_SLEEP },
{ 0, 0 }
};
@@ -387,6 +392,7 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
int ret;
+ unsigned long now, delay;
i2c_hid_dbg(ihid, "%s\n", __func__);
@@ -404,9 +410,22 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
goto set_pwr_exit;
}
+ if (ihid->quirks & I2C_HID_QUIRK_DELAY_AFTER_SLEEP &&
+ power_state == I2C_HID_PWR_ON) {
+ now = jiffies;
+ if (time_after(ihid->sleep_delay, now)) {
+ delay = jiffies_to_usecs(ihid->sleep_delay - now);
+ usleep_range(delay, delay + 1);
+ }
+ }
+
ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state,
0, NULL, 0, NULL, 0);
+ if (ihid->quirks & I2C_HID_QUIRK_DELAY_AFTER_SLEEP &&
+ power_state == I2C_HID_PWR_SLEEP)
+ ihid->sleep_delay = jiffies + msecs_to_jiffies(20);
+
if (ret)
dev_err(&client->dev, "failed to change power setting.\n");
--
2.17.1
^ permalink raw reply related
* Re: [PATCH] Input: uinput - add a schedule point in uinput_inject_events()
From: Paul E. McKenney @ 2018-10-05 4:09 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, Eric Dumazet, linux-kernel
In-Reply-To: <20181005005432.GA218507@dtor-ws>
On Thu, Oct 04, 2018 at 05:54:32PM -0700, Dmitry Torokhov wrote:
> Large writes to uinput interface may cause rcu stalls. Let's add
> cond_resched() to the loop to avoid this.
>
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Reviewed-by: Paul E. McKenney <paulmck@linux.ibm.com>
> ---
> drivers/input/misc/uinput.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c
> index eb14ddf69346..8ec483e8688b 100644
> --- a/drivers/input/misc/uinput.c
> +++ b/drivers/input/misc/uinput.c
> @@ -598,6 +598,7 @@ static ssize_t uinput_inject_events(struct uinput_device *udev,
>
> input_event(udev->dev, ev.type, ev.code, ev.value);
> bytes += input_event_size();
> + cond_resched();
> }
>
> return bytes;
> --
> 2.19.0.605.g01d371f741-goog
>
>
> --
> Dmitry
>
^ permalink raw reply
* Re: [PATCH] Input: evdev - add a schedule point in evdev_write()
From: Paul E. McKenney @ 2018-10-05 4:08 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, Eric Dumazet, linux-kernel
In-Reply-To: <20181005005359.GA218372@dtor-ws>
On Thu, Oct 04, 2018 at 05:53:59PM -0700, Dmitry Torokhov wrote:
> Large writes to evdev interface may cause rcu stalls. Let's add
> cond_resched() to the loop to avoid this.
>
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Reviewed-by: Paul E. McKenney <paulmck@linux.ibm.com>
> ---
> drivers/input/evdev.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
> index 370206f987f9..f48369d6f3a0 100644
> --- a/drivers/input/evdev.c
> +++ b/drivers/input/evdev.c
> @@ -564,6 +564,7 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer,
>
> input_inject_event(&evdev->handle,
> event.type, event.code, event.value);
> + cond_resched();
> }
>
> out:
> --
> 2.19.0.605.g01d371f741-goog
>
>
> --
> Dmitry
>
^ permalink raw reply
* [PATCH] Input: uinput - add a schedule point in uinput_inject_events()
From: Dmitry Torokhov @ 2018-10-05 0:54 UTC (permalink / raw)
To: linux-input; +Cc: Eric Dumazet, Paul E. McKenney, linux-kernel
Large writes to uinput interface may cause rcu stalls. Let's add
cond_resched() to the loop to avoid this.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/misc/uinput.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c
index eb14ddf69346..8ec483e8688b 100644
--- a/drivers/input/misc/uinput.c
+++ b/drivers/input/misc/uinput.c
@@ -598,6 +598,7 @@ static ssize_t uinput_inject_events(struct uinput_device *udev,
input_event(udev->dev, ev.type, ev.code, ev.value);
bytes += input_event_size();
+ cond_resched();
}
return bytes;
--
2.19.0.605.g01d371f741-goog
--
Dmitry
^ permalink raw reply related
* [PATCH] Input: evdev - add a schedule point in evdev_write()
From: Dmitry Torokhov @ 2018-10-05 0:53 UTC (permalink / raw)
To: linux-input; +Cc: Eric Dumazet, Paul E. McKenney, linux-kernel
Large writes to evdev interface may cause rcu stalls. Let's add
cond_resched() to the loop to avoid this.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/evdev.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index 370206f987f9..f48369d6f3a0 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -564,6 +564,7 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer,
input_inject_event(&evdev->handle,
event.type, event.code, event.value);
+ cond_resched();
}
out:
--
2.19.0.605.g01d371f741-goog
--
Dmitry
^ permalink raw reply related
* Re: [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Eric Dumazet @ 2018-10-04 23:01 UTC (permalink / raw)
To: Dmitry Torokhov, Paul E. McKenney
Cc: Eric Dumazet, linux-kernel, Eric Dumazet, linux-input
In-Reply-To: <20181004225455.GC233675@dtor-ws>
On 10/04/2018 03:54 PM, Dmitry Torokhov wrote:
> OK, I see. I'll apply the patch then.
Thanks !
>
> I think evdev.c needs similar treatment as it will keep looping while
> there is data...
Yeah, presumably other drivers need care as well :/
^ permalink raw reply
* Re: [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Dmitry Torokhov @ 2018-10-04 22:54 UTC (permalink / raw)
To: Paul E. McKenney; +Cc: Eric Dumazet, linux-kernel, Eric Dumazet, linux-input
In-Reply-To: <20181004193407.GK2674@linux.ibm.com>
On Thu, Oct 04, 2018 at 12:34:07PM -0700, Paul E. McKenney wrote:
> On Thu, Oct 04, 2018 at 11:59:49AM -0700, Dmitry Torokhov wrote:
> > Hi Eric,
> >
> > On Thu, Oct 04, 2018 at 08:47:49AM -0700, Eric Dumazet wrote:
> > > syzbot was able to trigger rcu stalls by calling write()
> > > with large number of bytes.
> > >
> > > Add a cond_resched() in the loop to avoid this.
> >
> > I think this simply masks a deeper issue. The code fetches characters
> > from userspace in a loop, takes a lock, quickly places response in an
> > output buffer, and releases interrupt. I do not see why this should
> > cause stalls as we do not hold spinlock/interrupts off for extended
> > period of time.
> >
> > Adding Paul so he can straighten me out...
>
> If you are running a !PREEMPT kernel, then you need the cond_resched()
> to allow the scheduler to choose someone else to run if needed and
> to let RCU know that grace periods can end. Without the cond_resched(),
> if you stay in that loop long enough you will get excessive scheduling
> latencies and eventually even RCU CPU stall warning splats.
>
> In a PREEMPT (instead of !PREEMPT) kernel, you would be right. When
> preemption is enabled, the scheduler can preempt and RCU can sense
> lack of readers from the scheduling-clock interrupt handler. Which
> is why cond_resched() is nothingness in a PREEMPT kernel.
>
> But because people run !PREEMPT as well as PREEMPT kernels, if that loop
> can run for a long time, you need that cond_resched().
OK, I see. I'll apply the patch then.
I think evdev.c needs similar treatment as it will keep looping while
there is data...
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Eric Dumazet @ 2018-10-04 19:45 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: LKML, Eric Dumazet, linux-input, Paul E. McKenney
In-Reply-To: <30074728-D1C4-46D4-8BF5-6AB8ECAE3EBD@gmail.com>
On Thu, Oct 4, 2018 at 12:38 PM Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
>
> On October 4, 2018 12:28:56 PM PDT, Eric Dumazet <edumazet@google.com> wrote:
> >On Thu, Oct 4, 2018 at 11:59 AM Dmitry Torokhov
> ><dmitry.torokhov@gmail.com> wrote:
> >>
> >> Hi Eric,
> >>
> >> On Thu, Oct 04, 2018 at 08:47:49AM -0700, Eric Dumazet wrote:
> >> > syzbot was able to trigger rcu stalls by calling write()
> >> > with large number of bytes.
> >> >
> >> > Add a cond_resched() in the loop to avoid this.
> >>
> >> I think this simply masks a deeper issue. The code fetches characters
> >> from userspace in a loop, takes a lock, quickly places response in an
> >> output buffer, and releases interrupt. I do not see why this should
> >> cause stalls as we do not hold spinlock/interrupts off for extended
> >> period of time.
> >>
> >> Adding Paul so he can straighten me out...
> >>
> >
> >Well...
> >
> >write(fd, buffer, 0x7FFF0000);
> >
> >Takes between 20 seconds and 2 minutes depending on CONFIG options ....
>
> That's fine even if it takes a couple of years. We are not holding spinlock for the entirety of this time, so we should get bumped off CPU at some point.
Well, you are saying that we could get rid of all cond_resched() calls
in the kernel.
You should send patches asap ;)
>
> >
> >So either apply my patch, or add a limit on the max count, and
> >possibly break legitimate user space ?
>
> Legitimate users write a single character at a time and read response, so exciting after, let's say, 32 bytes would be fine. But I still want to understand why we have to do that.
>
> >
> >I dunno...
> >
> >> >
> >> > Link: https://lkml.org/lkml/2018/8/23/1106
> >> > Signed-off-by: Eric Dumazet <edumazet@google.com>
> >> > Reported-by: syzbot+9436b02171ac0894d33e@syzkaller.appspotmail.com
> >> > Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> >> > Cc: linux-input@vger.kernel.org
> >> > ---
> >> > drivers/input/mousedev.c | 1 +
> >> > 1 file changed, 1 insertion(+)
> >> >
> >> > diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c
> >> > index
> >e08228061bcdd2f97aaadece31d6c83eb7539ae5..412fa71245afe26a7a8ad75705566f83633ba347
> >100644
> >> > --- a/drivers/input/mousedev.c
> >> > +++ b/drivers/input/mousedev.c
> >> > @@ -707,6 +707,7 @@ static ssize_t mousedev_write(struct file
> >*file, const char __user *buffer,
> >> > mousedev_generate_response(client, c);
> >> >
> >> > spin_unlock_irq(&client->packet_lock);
> >> > + cond_resched();
> >> > }
> >> >
> >> > kill_fasync(&client->fasync, SIGIO, POLL_IN);
> >> > --
> >> > 2.19.0.605.g01d371f741-goog
> >> >
> >>
> >> Thanks.
> >>
> >> --
> >> Dmitry
>
>
> Thanks.
>
> --
> Dmitry
^ permalink raw reply
* Re: [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Dmitry Torokhov @ 2018-10-04 19:38 UTC (permalink / raw)
To: Eric Dumazet; +Cc: LKML, Eric Dumazet, linux-input, Paul E. McKenney
In-Reply-To: <CANn89iJMhGEkw13KrGbqrvGbznb45QZwdr7eLy_rUV6zitvs=Q@mail.gmail.com>
On October 4, 2018 12:28:56 PM PDT, Eric Dumazet <edumazet@google.com> wrote:
>On Thu, Oct 4, 2018 at 11:59 AM Dmitry Torokhov
><dmitry.torokhov@gmail.com> wrote:
>>
>> Hi Eric,
>>
>> On Thu, Oct 04, 2018 at 08:47:49AM -0700, Eric Dumazet wrote:
>> > syzbot was able to trigger rcu stalls by calling write()
>> > with large number of bytes.
>> >
>> > Add a cond_resched() in the loop to avoid this.
>>
>> I think this simply masks a deeper issue. The code fetches characters
>> from userspace in a loop, takes a lock, quickly places response in an
>> output buffer, and releases interrupt. I do not see why this should
>> cause stalls as we do not hold spinlock/interrupts off for extended
>> period of time.
>>
>> Adding Paul so he can straighten me out...
>>
>
>Well...
>
>write(fd, buffer, 0x7FFF0000);
>
>Takes between 20 seconds and 2 minutes depending on CONFIG options ....
That's fine even if it takes a couple of years. We are not holding spinlock for the entirety of this time, so we should get bumped off CPU at some point.
>
>So either apply my patch, or add a limit on the max count, and
>possibly break legitimate user space ?
Legitimate users write a single character at a time and read response, so exciting after, let's say, 32 bytes would be fine. But I still want to understand why we have to do that.
>
>I dunno...
>
>> >
>> > Link: https://lkml.org/lkml/2018/8/23/1106
>> > Signed-off-by: Eric Dumazet <edumazet@google.com>
>> > Reported-by: syzbot+9436b02171ac0894d33e@syzkaller.appspotmail.com
>> > Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
>> > Cc: linux-input@vger.kernel.org
>> > ---
>> > drivers/input/mousedev.c | 1 +
>> > 1 file changed, 1 insertion(+)
>> >
>> > diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c
>> > index
>e08228061bcdd2f97aaadece31d6c83eb7539ae5..412fa71245afe26a7a8ad75705566f83633ba347
>100644
>> > --- a/drivers/input/mousedev.c
>> > +++ b/drivers/input/mousedev.c
>> > @@ -707,6 +707,7 @@ static ssize_t mousedev_write(struct file
>*file, const char __user *buffer,
>> > mousedev_generate_response(client, c);
>> >
>> > spin_unlock_irq(&client->packet_lock);
>> > + cond_resched();
>> > }
>> >
>> > kill_fasync(&client->fasync, SIGIO, POLL_IN);
>> > --
>> > 2.19.0.605.g01d371f741-goog
>> >
>>
>> Thanks.
>>
>> --
>> Dmitry
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Paul E. McKenney @ 2018-10-04 19:36 UTC (permalink / raw)
To: Eric Dumazet; +Cc: Dmitry Torokhov, LKML, Eric Dumazet, linux-input
In-Reply-To: <CANn89iJMhGEkw13KrGbqrvGbznb45QZwdr7eLy_rUV6zitvs=Q@mail.gmail.com>
On Thu, Oct 04, 2018 at 12:28:56PM -0700, Eric Dumazet wrote:
> On Thu, Oct 4, 2018 at 11:59 AM Dmitry Torokhov
> <dmitry.torokhov@gmail.com> wrote:
> >
> > Hi Eric,
> >
> > On Thu, Oct 04, 2018 at 08:47:49AM -0700, Eric Dumazet wrote:
> > > syzbot was able to trigger rcu stalls by calling write()
> > > with large number of bytes.
> > >
> > > Add a cond_resched() in the loop to avoid this.
> >
> > I think this simply masks a deeper issue. The code fetches characters
> > from userspace in a loop, takes a lock, quickly places response in an
> > output buffer, and releases interrupt. I do not see why this should
> > cause stalls as we do not hold spinlock/interrupts off for extended
> > period of time.
> >
> > Adding Paul so he can straighten me out...
> >
>
> Well...
>
> write(fd, buffer, 0x7FFF0000);
>
> Takes between 20 seconds and 2 minutes depending on CONFIG options ....
And two minutes would get you an RCU CPU stall warning, even on distro
kernels that set the stall-warning time to a full minute (as opposed
to 21 seconds in mainline).
> So either apply my patch, or add a limit on the max count, and
> possibly break legitimate user space ?
>
> I dunno...
I vote for Eric's patch. In fact:
Reviewed-by: Paul E. McKenney <paulmck@linux.ibm.com>
> > > Link: https://lkml.org/lkml/2018/8/23/1106
> > > Signed-off-by: Eric Dumazet <edumazet@google.com>
> > > Reported-by: syzbot+9436b02171ac0894d33e@syzkaller.appspotmail.com
> > > Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> > > Cc: linux-input@vger.kernel.org
> > > ---
> > > drivers/input/mousedev.c | 1 +
> > > 1 file changed, 1 insertion(+)
> > >
> > > diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c
> > > index e08228061bcdd2f97aaadece31d6c83eb7539ae5..412fa71245afe26a7a8ad75705566f83633ba347 100644
> > > --- a/drivers/input/mousedev.c
> > > +++ b/drivers/input/mousedev.c
> > > @@ -707,6 +707,7 @@ static ssize_t mousedev_write(struct file *file, const char __user *buffer,
> > > mousedev_generate_response(client, c);
> > >
> > > spin_unlock_irq(&client->packet_lock);
> > > + cond_resched();
> > > }
> > >
> > > kill_fasync(&client->fasync, SIGIO, POLL_IN);
> > > --
> > > 2.19.0.605.g01d371f741-goog
> > >
> >
> > Thanks.
> >
> > --
> > Dmitry
>
^ permalink raw reply
* Re: [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Paul E. McKenney @ 2018-10-04 19:34 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Eric Dumazet, linux-kernel, Eric Dumazet, linux-input
In-Reply-To: <20181004185949.GA233675@dtor-ws>
On Thu, Oct 04, 2018 at 11:59:49AM -0700, Dmitry Torokhov wrote:
> Hi Eric,
>
> On Thu, Oct 04, 2018 at 08:47:49AM -0700, Eric Dumazet wrote:
> > syzbot was able to trigger rcu stalls by calling write()
> > with large number of bytes.
> >
> > Add a cond_resched() in the loop to avoid this.
>
> I think this simply masks a deeper issue. The code fetches characters
> from userspace in a loop, takes a lock, quickly places response in an
> output buffer, and releases interrupt. I do not see why this should
> cause stalls as we do not hold spinlock/interrupts off for extended
> period of time.
>
> Adding Paul so he can straighten me out...
If you are running a !PREEMPT kernel, then you need the cond_resched()
to allow the scheduler to choose someone else to run if needed and
to let RCU know that grace periods can end. Without the cond_resched(),
if you stay in that loop long enough you will get excessive scheduling
latencies and eventually even RCU CPU stall warning splats.
In a PREEMPT (instead of !PREEMPT) kernel, you would be right. When
preemption is enabled, the scheduler can preempt and RCU can sense
lack of readers from the scheduling-clock interrupt handler. Which
is why cond_resched() is nothingness in a PREEMPT kernel.
But because people run !PREEMPT as well as PREEMPT kernels, if that loop
can run for a long time, you need that cond_resched().
Thanx, Paul
> > Link: https://lkml.org/lkml/2018/8/23/1106
> > Signed-off-by: Eric Dumazet <edumazet@google.com>
> > Reported-by: syzbot+9436b02171ac0894d33e@syzkaller.appspotmail.com
> > Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> > Cc: linux-input@vger.kernel.org
> > ---
> > drivers/input/mousedev.c | 1 +
> > 1 file changed, 1 insertion(+)
> >
> > diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c
> > index e08228061bcdd2f97aaadece31d6c83eb7539ae5..412fa71245afe26a7a8ad75705566f83633ba347 100644
> > --- a/drivers/input/mousedev.c
> > +++ b/drivers/input/mousedev.c
> > @@ -707,6 +707,7 @@ static ssize_t mousedev_write(struct file *file, const char __user *buffer,
> > mousedev_generate_response(client, c);
> >
> > spin_unlock_irq(&client->packet_lock);
> > + cond_resched();
> > }
> >
> > kill_fasync(&client->fasync, SIGIO, POLL_IN);
> > --
> > 2.19.0.605.g01d371f741-goog
> >
>
> Thanks.
>
> --
> Dmitry
>
^ permalink raw reply
* Re: [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Eric Dumazet @ 2018-10-04 19:28 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: LKML, Eric Dumazet, linux-input, Paul E. McKenney
In-Reply-To: <20181004185949.GA233675@dtor-ws>
On Thu, Oct 4, 2018 at 11:59 AM Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
>
> Hi Eric,
>
> On Thu, Oct 04, 2018 at 08:47:49AM -0700, Eric Dumazet wrote:
> > syzbot was able to trigger rcu stalls by calling write()
> > with large number of bytes.
> >
> > Add a cond_resched() in the loop to avoid this.
>
> I think this simply masks a deeper issue. The code fetches characters
> from userspace in a loop, takes a lock, quickly places response in an
> output buffer, and releases interrupt. I do not see why this should
> cause stalls as we do not hold spinlock/interrupts off for extended
> period of time.
>
> Adding Paul so he can straighten me out...
>
Well...
write(fd, buffer, 0x7FFF0000);
Takes between 20 seconds and 2 minutes depending on CONFIG options ....
So either apply my patch, or add a limit on the max count, and
possibly break legitimate user space ?
I dunno...
> >
> > Link: https://lkml.org/lkml/2018/8/23/1106
> > Signed-off-by: Eric Dumazet <edumazet@google.com>
> > Reported-by: syzbot+9436b02171ac0894d33e@syzkaller.appspotmail.com
> > Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> > Cc: linux-input@vger.kernel.org
> > ---
> > drivers/input/mousedev.c | 1 +
> > 1 file changed, 1 insertion(+)
> >
> > diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c
> > index e08228061bcdd2f97aaadece31d6c83eb7539ae5..412fa71245afe26a7a8ad75705566f83633ba347 100644
> > --- a/drivers/input/mousedev.c
> > +++ b/drivers/input/mousedev.c
> > @@ -707,6 +707,7 @@ static ssize_t mousedev_write(struct file *file, const char __user *buffer,
> > mousedev_generate_response(client, c);
> >
> > spin_unlock_irq(&client->packet_lock);
> > + cond_resched();
> > }
> >
> > kill_fasync(&client->fasync, SIGIO, POLL_IN);
> > --
> > 2.19.0.605.g01d371f741-goog
> >
>
> Thanks.
>
> --
> Dmitry
^ permalink raw reply
* Re: [PATCH v2] Input: reserve 2 events code because of HID
From: Jiri Kosina @ 2018-10-04 19:09 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Dmitry Torokhov, Harry Cutts, Peter Hutterer, linux-input,
linux-kernel
In-Reply-To: <20181004123430.29348-1-benjamin.tissoires@redhat.com>
On Thu, 4 Oct 2018, Benjamin Tissoires wrote:
> Prior to commit 190d7f02ce8e ("HID: input: do not increment usages when
> a duplicate is found") from the v4.18 kernel, HID used to shift the
> event codes if a duplicate usage was found. This ended up in a situation
> where a device would export a ton of ABS_MISC+n event codes, or a ton
> of REL_MISC+n event codes.
>
> This is now fixed, however userspace needs to detect those situation.
> Fortunately, ABS_MT_SLOT-1 (ABS_MISC+6) was never assigned a code, and
> so libinput can detect fake multitouch devices from genuine ones by
> checking if ABS_MT_SLOT-1 is set.
>
> Now that we have REL_WHEEL_HI_RES, libinput won't be able to differentiate
> true high res mice from some other device in a pre-v4.18 kernel.
>
> Set in stone that the ABS_MISC+6 and REL_MISC+1 are reserved and should not
> be used so userspace can properly work around those old kernels.
>
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---
>
> Dmitry, I did not take your Acked-by, as I added 2 new events and wanted to
> be sure you are still OK.
>
> Jiri, this will still need to go through your tree, as the input-event-codes
> changes that introduce REL_WHEEL_HI_RES are in your for-4.20/logitech-highres.
Applied, thanks Benjamin.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v2] Input: reserve 2 events code because of HID
From: Dmitry Torokhov @ 2018-10-04 19:01 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Jiri Kosina, Harry Cutts, Peter Hutterer, linux-input,
linux-kernel
In-Reply-To: <20181004123430.29348-1-benjamin.tissoires@redhat.com>
On Thu, Oct 04, 2018 at 02:34:30PM +0200, Benjamin Tissoires wrote:
> Prior to commit 190d7f02ce8e ("HID: input: do not increment usages when
> a duplicate is found") from the v4.18 kernel, HID used to shift the
> event codes if a duplicate usage was found. This ended up in a situation
> where a device would export a ton of ABS_MISC+n event codes, or a ton
> of REL_MISC+n event codes.
>
> This is now fixed, however userspace needs to detect those situation.
> Fortunately, ABS_MT_SLOT-1 (ABS_MISC+6) was never assigned a code, and
> so libinput can detect fake multitouch devices from genuine ones by
> checking if ABS_MT_SLOT-1 is set.
>
> Now that we have REL_WHEEL_HI_RES, libinput won't be able to differentiate
> true high res mice from some other device in a pre-v4.18 kernel.
>
> Set in stone that the ABS_MISC+6 and REL_MISC+1 are reserved and should not
> be used so userspace can properly work around those old kernels.
>
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> ---
>
> Dmitry, I did not take your Acked-by, as I added 2 new events and wanted to
> be sure you are still OK.
Yes, this is still fine.
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
>
> Jiri, this will still need to go through your tree, as the input-event-codes
> changes that introduce REL_WHEEL_HI_RES are in your for-4.20/logitech-highres.
>
> Cheers,
> Benjamin
>
> include/uapi/linux/input-event-codes.h | 19 ++++++++++++++++++-
> 1 file changed, 18 insertions(+), 1 deletion(-)
>
> diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
> index 29fb891ea337..b5e0e9856c47 100644
> --- a/include/uapi/linux/input-event-codes.h
> +++ b/include/uapi/linux/input-event-codes.h
> @@ -708,7 +708,15 @@
> #define REL_DIAL 0x07
> #define REL_WHEEL 0x08
> #define REL_MISC 0x09
> -#define REL_WHEEL_HI_RES 0x0a
> +/*
> + * 0x0a is reserved and should not be used in input drivers.
> + * It was used by HID as REL_MISC+1 and userspace needs to detect if
> + * the next REL_* event is correct or is just REL_MISC + n.
> + * We define here REL_RESERVED so userspace can rely on it and detect
> + * the situation described above.
> + */
> +#define REL_RESERVED 0x0a
> +#define REL_WHEEL_HI_RES 0x0b
> #define REL_MAX 0x0f
> #define REL_CNT (REL_MAX+1)
>
> @@ -745,6 +753,15 @@
>
> #define ABS_MISC 0x28
>
> +/*
> + * 0x2e is reserved and should not be used in input drivers.
> + * It was used by HID as ABS_MISC+6 and userspace needs to detect if
> + * the next ABS_* event is correct or is just ABS_MISC + n.
> + * We define here ABS_RESERVED so userspace can rely on it and detect
> + * the situation described above.
> + */
> +#define ABS_RESERVED 0x2e
> +
> #define ABS_MT_SLOT 0x2f /* MT slot being modified */
> #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
> #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
> --
> 2.14.3
>
--
Dmitry
^ permalink raw reply
* Re: [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Dmitry Torokhov @ 2018-10-04 18:59 UTC (permalink / raw)
To: Eric Dumazet; +Cc: linux-kernel, Eric Dumazet, linux-input, Paul E. McKenney
In-Reply-To: <20181004154749.111595-1-edumazet@google.com>
Hi Eric,
On Thu, Oct 04, 2018 at 08:47:49AM -0700, Eric Dumazet wrote:
> syzbot was able to trigger rcu stalls by calling write()
> with large number of bytes.
>
> Add a cond_resched() in the loop to avoid this.
I think this simply masks a deeper issue. The code fetches characters
from userspace in a loop, takes a lock, quickly places response in an
output buffer, and releases interrupt. I do not see why this should
cause stalls as we do not hold spinlock/interrupts off for extended
period of time.
Adding Paul so he can straighten me out...
>
> Link: https://lkml.org/lkml/2018/8/23/1106
> Signed-off-by: Eric Dumazet <edumazet@google.com>
> Reported-by: syzbot+9436b02171ac0894d33e@syzkaller.appspotmail.com
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Cc: linux-input@vger.kernel.org
> ---
> drivers/input/mousedev.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c
> index e08228061bcdd2f97aaadece31d6c83eb7539ae5..412fa71245afe26a7a8ad75705566f83633ba347 100644
> --- a/drivers/input/mousedev.c
> +++ b/drivers/input/mousedev.c
> @@ -707,6 +707,7 @@ static ssize_t mousedev_write(struct file *file, const char __user *buffer,
> mousedev_generate_response(client, c);
>
> spin_unlock_irq(&client->packet_lock);
> + cond_resched();
> }
>
> kill_fasync(&client->fasync, SIGIO, POLL_IN);
> --
> 2.19.0.605.g01d371f741-goog
>
Thanks.
--
Dmitry
^ permalink raw reply
* [PATCH] Input: mousedev - add a schedule point in mousedev_write()
From: Eric Dumazet @ 2018-10-04 15:47 UTC (permalink / raw)
To: linux-kernel; +Cc: Eric Dumazet, Eric Dumazet, Dmitry Torokhov, linux-input
syzbot was able to trigger rcu stalls by calling write()
with large number of bytes.
Add a cond_resched() in the loop to avoid this.
Link: https://lkml.org/lkml/2018/8/23/1106
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: syzbot+9436b02171ac0894d33e@syzkaller.appspotmail.com
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: linux-input@vger.kernel.org
---
drivers/input/mousedev.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c
index e08228061bcdd2f97aaadece31d6c83eb7539ae5..412fa71245afe26a7a8ad75705566f83633ba347 100644
--- a/drivers/input/mousedev.c
+++ b/drivers/input/mousedev.c
@@ -707,6 +707,7 @@ static ssize_t mousedev_write(struct file *file, const char __user *buffer,
mousedev_generate_response(client, c);
spin_unlock_irq(&client->packet_lock);
+ cond_resched();
}
kill_fasync(&client->fasync, SIGIO, POLL_IN);
--
2.19.0.605.g01d371f741-goog
^ permalink raw reply related
* [PATCH v2] Input: reserve 2 events code because of HID
From: Benjamin Tissoires @ 2018-10-04 12:34 UTC (permalink / raw)
To: Dmitry Torokhov, Jiri Kosina, Harry Cutts, Peter Hutterer
Cc: linux-input, linux-kernel, Benjamin Tissoires
Prior to commit 190d7f02ce8e ("HID: input: do not increment usages when
a duplicate is found") from the v4.18 kernel, HID used to shift the
event codes if a duplicate usage was found. This ended up in a situation
where a device would export a ton of ABS_MISC+n event codes, or a ton
of REL_MISC+n event codes.
This is now fixed, however userspace needs to detect those situation.
Fortunately, ABS_MT_SLOT-1 (ABS_MISC+6) was never assigned a code, and
so libinput can detect fake multitouch devices from genuine ones by
checking if ABS_MT_SLOT-1 is set.
Now that we have REL_WHEEL_HI_RES, libinput won't be able to differentiate
true high res mice from some other device in a pre-v4.18 kernel.
Set in stone that the ABS_MISC+6 and REL_MISC+1 are reserved and should not
be used so userspace can properly work around those old kernels.
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
---
Dmitry, I did not take your Acked-by, as I added 2 new events and wanted to
be sure you are still OK.
Jiri, this will still need to go through your tree, as the input-event-codes
changes that introduce REL_WHEEL_HI_RES are in your for-4.20/logitech-highres.
Cheers,
Benjamin
include/uapi/linux/input-event-codes.h | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
index 29fb891ea337..b5e0e9856c47 100644
--- a/include/uapi/linux/input-event-codes.h
+++ b/include/uapi/linux/input-event-codes.h
@@ -708,7 +708,15 @@
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#define REL_MISC 0x09
-#define REL_WHEEL_HI_RES 0x0a
+/*
+ * 0x0a is reserved and should not be used in input drivers.
+ * It was used by HID as REL_MISC+1 and userspace needs to detect if
+ * the next REL_* event is correct or is just REL_MISC + n.
+ * We define here REL_RESERVED so userspace can rely on it and detect
+ * the situation described above.
+ */
+#define REL_RESERVED 0x0a
+#define REL_WHEEL_HI_RES 0x0b
#define REL_MAX 0x0f
#define REL_CNT (REL_MAX+1)
@@ -745,6 +753,15 @@
#define ABS_MISC 0x28
+/*
+ * 0x2e is reserved and should not be used in input drivers.
+ * It was used by HID as ABS_MISC+6 and userspace needs to detect if
+ * the next ABS_* event is correct or is just ABS_MISC + n.
+ * We define here ABS_RESERVED so userspace can rely on it and detect
+ * the situation described above.
+ */
+#define ABS_RESERVED 0x2e
+
#define ABS_MT_SLOT 0x2f /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
--
2.14.3
^ permalink raw reply related
* Re: [PATCH] Input: reserve 2 events code because of HID
From: Benjamin Tissoires @ 2018-10-04 12:22 UTC (permalink / raw)
To: Peter Hutterer
Cc: Dmitry Torokhov, Jiri Kosina, hcutts, open list:HID CORE LAYER,
lkml
In-Reply-To: <20180908014442.GA16029@jelly>
Oops, looks like this one fell through the cracks.
On Sat, Sep 8, 2018 at 3:44 AM Peter Hutterer <peter.hutterer@who-t.net> wrote:
>
> On Fri, Sep 07, 2018 at 10:51:15AM +0200, Benjamin Tissoires wrote:
> > From: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> >
> > Prior to commit 190d7f02ce8e ("HID: input: do not increment usages when
> > a duplicate is found") from the v4.18 kernel, HID used to shift the
> > event codes if a duplicate usage was found. This ended up in a situation
> > where a device would export a ton of ABS_MISC+n event codes, or a ton
> > of REL_MISC+n event codes.
> >
> > This is now fixed, however userspace needs to detect those situation.
> > Fortunately, ABS_MISC+1 was never assigned a code, and so libinput
> > can detect fake multitouch devices from genuine ones by checking is
> > ABS_MISC+1 is set.
>
> sorry, this isn't quite correct. we use ABS_MT_SLOT - 1 (0x2e) for the
> detection of fake MT devices, i.e.
> if (ABS_MT_SLOT and not ABS_MT_SLOT-1) then multitouch
Will send a v2 ASAP.
>
> That gives you up to ABS_MISC + 6 for legitimate usage. this is handled by
> libevdev, not libinput directly. libevdev adjusts the various bits on "is
> this device an MT device" based on whether ABS_MT_SLOT-1 is set.
>
> I can change this to also check for (ABS_MISC and not ABS_MISC+1) but that
> obviously will depend on updated libraries then. Though I guess it won't
> really be an issue until we fill up the other codes up to including 0x2e
> with real values and expect userspace to handle those.
Nah, better changing the value here.
>
> None of the bits I maintain have special code for REL_MISC+n so that bit
> works fine, IMO.
>
> One request though: instead of just having the value reserved, can we make
> it REL_RESERVED and ABS_RESERVED please? Or ABS_CANARY :) Much easier than
> hardcoding the numeric value.
My first thoughts were that ABS_CANARY has the inconvenient of being
too tempting to be used as a real value.
OTOH, REL_RESERVED and ABS_RESERVED should be fine.
I'll send out a v2 with those changes.
Cheers,
Benjamin
>
> Cheers,
> Peter
>
>
> > Now that we have REL_WHEEL_HI_RES, libinput won't be able to differentiate
> > true high res mice from some other device in a pre-v4.18 kernel.
> >
> > Set in stone that the ABS_MISC+1 and REL_MISC+1 are reserved and should not
> > be used so userspace can properly work around those old kernels.
> >
> > Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
> > ---
> >
> > Hi,
> >
> > while reviewing my local tree, I realize that we might want to be able
> > to differentiate older kernels from new ones that export REL_WHEEL_HI_RES.
> >
> > I know Dmitry was against adding several REL_MISC, so I hope just moving
> > REL_WHEEL_HI_RES by one and reserving the faulty event codes would be good
> > this time.
> >
> > This patch applies on top of the branch for-4.20/logitech-highres from
> > Jiri's tree. It should go through Jiri's tree as well.
> >
> > Cheers,
> > Benjamin
> >
> > include/uapi/linux/input-event-codes.h | 13 ++++++++++++-
> > 1 file changed, 12 insertions(+), 1 deletion(-)
> >
> > diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
> > index 29fb891ea337..30149939249a 100644
> > --- a/include/uapi/linux/input-event-codes.h
> > +++ b/include/uapi/linux/input-event-codes.h
> > @@ -708,7 +708,12 @@
> > #define REL_DIAL 0x07
> > #define REL_WHEEL 0x08
> > #define REL_MISC 0x09
> > -#define REL_WHEEL_HI_RES 0x0a
> > +/*
> > + * 0x0a is reserved and should not be used.
> > + * It was used by HID as REL_MISC+1 and usersapce needs to detect if
> > + * the next REL_* event is correct or is just REL_MISC + n.
> > + */
> > +#define REL_WHEEL_HI_RES 0x0b
> > #define REL_MAX 0x0f
> > #define REL_CNT (REL_MAX+1)
> >
> > @@ -745,6 +750,12 @@
> >
> > #define ABS_MISC 0x28
> >
> > +/*
> > + * 0x29 is reserved and should not be used.
> > + * It was used by HID as ABS_MISC+1 and usersapce needs to detect if
> > + * the next ABS_* event is correct or is just ABS_MISC + n.
> > + */
> > +
> > #define ABS_MT_SLOT 0x2f /* MT slot being modified */
> > #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
> > #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
> > --
> > 2.14.3
> >
^ permalink raw reply
* Re: [PATCH V2] hid: hid-core: Fix a sleep-in-atomic-context bug in __hid_request()
From: Jiri Kosina @ 2018-10-04 7:35 UTC (permalink / raw)
To: Jia-Ju Bai; +Cc: benjamin.tissoires, linux-input, linux-kernel
In-Reply-To: <6bea4185-3325-b04d-56ed-2fdf4c74d8ae@gmail.com>
On Thu, 4 Oct 2018, Jia-Ju Bai wrote:
> > Why? Forcing all the report buffer to be limited to be non-sleeping
> > allocations just because of two drivers, looks like an overkill, and
> > actually calls for more issues (as GFP_ATOMIC is of course in principle
> > less likely to succeed).
>
> Okay, I thought that using GFP_ATOMIC is the simplest way to fix these bugs.
> But I check the Linux kernel code again, and find that hid_hw_request() are
> called at many places.
> So changing this function may affect many drivers.
> I agree to only change the two drivers, and explicitly anotate __hid_request()
> with might_sleep().
Thanks. Are you planning to submit a patch to do that?
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* [PATCH V7 3/3] Input: new da7280 haptic driver
From: Roy Im @ 2018-10-04 6:45 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring
Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
linux-kernel
In-Reply-To: <cover.1538635541.git.Roy.Im@diasemi.com>
Adds support for the Dialog DA7280 LRA/ERM Haptic Driver with
multiple mode and integrated waveform memory and wideband support.
It communicates via an I2C bus to the device.
Signed-off-by: Roy Im <roy.im.opensource@diasemi.com>
---
v7:
- Added more attributes to handle one value per file.
- Replaced and updated the dt-related code and functions called.
- Fixed error/functions.
- Rebased to v4.19-rc6.
v6: No changes.
v5: Fixed errors in Kconfig file.
v4: Updated code as dt-bindings are changed.
v3: No changes.
v2: Fixed kbuild error/warning
drivers/input/misc/Kconfig | 13 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/da7280.c | 1393 +++++++++++++++++++++++++++++++++++++++++++
drivers/input/misc/da7280.h | 412 +++++++++++++
4 files changed, 1819 insertions(+)
create mode 100644 drivers/input/misc/da7280.c
create mode 100644 drivers/input/misc/da7280.h
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index ca59a2b..751cac6 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -851,4 +851,17 @@ config INPUT_SC27XX_VIBRA
To compile this driver as a module, choose M here. The module will
be called sc27xx_vibra.
+config INPUT_DA7280_HAPTICS
+ tristate "Dialog Semiconductor DA7280 haptics support"
+ depends on INPUT && I2C
+ select INPUT_FF_MEMLESS
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the Dialog DA7280 haptics driver.
+ The haptics can be controlled by i2c communication,
+ or by PWM input, or by GPI.
+
+ To compile this driver as a module, choose M here: the
+ module will be called da7280.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 9d0f9d1..d941348 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_INPUT_CMA3000) += cma3000_d0x.o
obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o
obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON) += cpcap-pwrbutton.o
+obj-$(CONFIG_INPUT_DA7280_HAPTICS) += da7280.o
obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
obj-$(CONFIG_INPUT_DA9063_ONKEY) += da9063_onkey.o
diff --git a/drivers/input/misc/da7280.c b/drivers/input/misc/da7280.c
new file mode 100644
index 0000000..b87bf7c
--- /dev/null
+++ b/drivers/input/misc/da7280.c
@@ -0,0 +1,1393 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DA7280 Haptic device driver
+ *
+ * Copyright (c) 2018 Dialog Semiconductor.
+ * Author: Roy Im <Roy.Im.Opensource@diasemi.com>
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+#include "da7280.h"
+
+/* uV unit for voltage rate */
+#define DA7280_VOLTAGE_RATE_MAX 6000000
+#define DA7280_VOLTAGE_RATE_STEP 23400
+#define DA7280_NOMMAX_DFT 0x6B
+#define DA7280_ABSMAX_DFT 0x78
+
+#define DA7280_IMPD_MAX 1500000000
+#define DA7280_IMPD_DEFAULT 22000000
+
+#define DA7280_IMAX_DEFAULT 0x0E
+/* uA unit step and limit for IMAX*/
+#define DA7280_IMAX_STEP 7200
+#define DA7280_IMAX_LIMIT 252000
+
+#define DA7280_RESONT_FREQH_DFT 0x39
+#define DA7280_RESONT_FREQL_DFT 0x32
+#define DA7280_MIN_RESONAT_FREQ_HZ 50
+#define DA7280_MAX_RESONAT_FREQ_HZ 300
+#define DA7280_MIN_PWM_FREQ_KHZ 10
+#define DA7280_MAX_PWM_FREQ_KHZ 250
+
+#define DA7280_SEQ_ID_MAX 15
+#define DA7280_SEQ_LOOP_MAX 15
+#define DA7280_GPI1_SEQ_ID_DEFT 0x0
+
+#define DA7280_SNP_MEM_SIZE 100
+#define DA7280_SNP_MEM_MAX DA7280_SNP_MEM_99
+
+#define IRQ_NUM 3
+
+#define DA7280_SKIP_INIT 0x100
+
+enum da7280_haptic_dev_t {
+ DA7280_LRA = 0,
+ DA7280_ERM_BAR = 1,
+ DA7280_ERM_COIN = 2,
+ DA7280_DEV_MAX,
+};
+
+enum da7280_op_mode {
+ DA7280_INACTIVE = 0,
+ DA7280_DRO_MODE = 1,
+ DA7280_PWM_MODE = 2,
+ DA7280_RTWM_MODE = 3,
+ DA7280_ETWM_MODE = 4,
+ DA7280_OPMODE_MAX,
+};
+
+struct da7280_gpi_ctl {
+ u8 seq_id;
+ u8 mode;
+ u8 polarity;
+};
+
+struct da7280_haptic {
+ struct regmap *regmap;
+ struct input_dev *input_dev;
+ struct device *dev;
+ struct i2c_client *client;
+ struct pwm_device *pwm_dev;
+ bool legacy;
+ int pwm_id;
+ struct work_struct work;
+
+ unsigned int magnitude;
+
+ u8 dev_type;
+ u8 op_mode;
+ u16 nommax;
+ u16 absmax;
+ u32 imax;
+ u32 impd;
+ u32 resonant_freq_h;
+ u32 resonant_freq_l;
+ bool bemf_sense_en;
+ bool freq_track_en;
+ bool acc_en;
+ bool rapid_stop_en;
+ bool amp_pid_en;
+ u8 ps_seq_id;
+ u8 ps_seq_loop;
+ struct da7280_gpi_ctl gpi_ctl[3];
+ bool mem_update;
+ u8 snp_mem[DA7280_SNP_MEM_SIZE];
+ u8 enabled;
+};
+
+static bool da7280_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case DA7280_IRQ_EVENT1:
+ case DA7280_IRQ_EVENT_WARNING_DIAG:
+ case DA7280_IRQ_EVENT_SEQ_DIAG:
+ case DA7280_IRQ_STATUS1:
+ case DA7280_TOP_CTL1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config da7280_haptic_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = DA7280_SNP_MEM_MAX,
+ .volatile_reg = da7280_volatile_register,
+};
+
+static int da7280_haptic_mem_update(struct da7280_haptic *haptics)
+{
+ int error;
+ unsigned int val;
+
+ /* It is recommended to update the patterns
+ * during haptic is not working in order to avoid conflict
+ */
+ error = regmap_read(haptics->regmap, DA7280_IRQ_STATUS1, &val);
+ if (error)
+ return error;
+ if (val & DA7280_STA_WARNING_MASK) {
+ dev_warn(haptics->dev,
+ "Warning! Please check HAPTIC status.\n");
+ return -EBUSY;
+ }
+
+ /* Patterns are not updated if the lock bit is enabled */
+ val = 0;
+ error = regmap_read(haptics->regmap, DA7280_MEM_CTL2, &val);
+ if (error)
+ return error;
+ if (~val & DA7280_WAV_MEM_LOCK_MASK) {
+ dev_warn(haptics->dev,
+ "Please unlock the bit first\n");
+ return -EACCES;
+ }
+
+ /* Set to Inactive mode to make sure safety */
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK,
+ 0);
+ if (error)
+ return error;
+
+ error = regmap_read(haptics->regmap, DA7280_MEM_CTL1, &val);
+ if (error)
+ return error;
+
+ return regmap_bulk_write(haptics->regmap, val,
+ haptics->snp_mem, DA7280_SNP_MEM_MAX - val + 1);
+}
+
+static int da7280_haptic_set_pwm(struct da7280_haptic *haptics)
+{
+ struct pwm_args pargs;
+ u64 period_mag_multi;
+ unsigned int pwm_duty;
+ int error;
+
+ pwm_get_args(haptics->pwm_dev, &pargs);
+ period_mag_multi =
+ (u64)(pargs.period * haptics->magnitude);
+ if (haptics->acc_en)
+ pwm_duty =
+ (unsigned int)(period_mag_multi >> 16);
+ else
+ pwm_duty =
+ (unsigned int)((period_mag_multi >> 16)
+ + pargs.period) / 2;
+
+ error = pwm_config(haptics->pwm_dev,
+ pwm_duty, pargs.period);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to configure pwm : %d\n", error);
+ return error;
+ }
+
+ error = pwm_enable(haptics->pwm_dev);
+ if (error) {
+ pwm_disable(haptics->pwm_dev);
+ dev_err(haptics->dev,
+ "failed to enable haptics pwm device : %d\n", error);
+ }
+
+ return error;
+}
+
+static void da7280_haptic_enable(struct da7280_haptic *haptics)
+{
+ int error = 0;
+
+ if (haptics->enabled)
+ return;
+
+ switch (haptics->op_mode) {
+ case DA7280_DRO_MODE:
+ /* the valid range check when acc_en is enabled */
+ if (haptics->acc_en && haptics->magnitude > 0x7F)
+ haptics->magnitude = 0x7F;
+ else if (haptics->magnitude > 0xFF)
+ haptics->magnitude = 0xFF;
+
+ /* Set driver level
+ * as a % of ACTUATOR_NOMMAX(nommax)
+ */
+ error = regmap_write(haptics->regmap,
+ DA7280_TOP_CTL2,
+ haptics->magnitude);
+ if (error) {
+ dev_err(haptics->dev,
+ "i2c err for driving level set : %d\n",
+ error);
+ return;
+ }
+ break;
+ case DA7280_PWM_MODE:
+ if (da7280_haptic_set_pwm(haptics))
+ return;
+ break;
+ case DA7280_RTWM_MODE:
+ /* PS_SEQ_ID will be played
+ * as many times as the PS_SEQ_LOOP
+ */
+ case DA7280_ETWM_MODE:
+ /* Now users are able to control the GPI(N)
+ * assigned to GPI_0, GPI1 and GPI2 accordingly
+ * please see the datasheet for details.
+ * GPI(N)_SEQUENCE_ID will be played
+ * as many times as the PS_SEQ_LOOP
+ */
+ break;
+ default:
+ dev_err(haptics->dev,
+ "Invalid Mode(%d)\n", haptics->op_mode);
+ return;
+ }
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK,
+ haptics->op_mode);
+ if (error) {
+ dev_err(haptics->dev,
+ "i2c err for op_mode setting : %d\n", error);
+ return;
+ }
+
+ if (haptics->op_mode == DA7280_PWM_MODE ||
+ haptics->op_mode == DA7280_RTWM_MODE) {
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_SEQ_START_MASK,
+ DA7280_SEQ_START_MASK);
+ if (error) {
+ dev_err(haptics->dev,
+ "i2c err for sequence triggering : %d\n",
+ error);
+ return;
+ }
+ }
+
+ haptics->enabled = true;
+}
+
+static void da7280_haptic_disable(struct da7280_haptic *haptics)
+{
+ int error;
+
+ if (!haptics->enabled)
+ return;
+
+ /* Set to Inactive mode */
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK, 0);
+ if (error) {
+ dev_err(haptics->dev,
+ "i2c err for op_mode off : %d\n", error);
+ return;
+ }
+
+ switch (haptics->op_mode) {
+ case DA7280_RTWM_MODE:
+ case DA7280_ETWM_MODE:
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_SEQ_START_MASK, 0);
+ if (error) {
+ dev_err(haptics->dev,
+ "i2c err for RTWM or ETWM mode off : %d\n",
+ error);
+ return;
+ }
+ break;
+ case DA7280_DRO_MODE:
+ error = regmap_write(haptics->regmap,
+ DA7280_TOP_CTL2, 0);
+ if (error) {
+ dev_err(haptics->dev,
+ "i2c err for DRO mode off : %d\n",
+ error);
+ return;
+ }
+ break;
+ case DA7280_PWM_MODE:
+ pwm_disable(haptics->pwm_dev);
+ break;
+ default:
+ dev_err(haptics->dev,
+ "Invalid Mode(%d)\n", haptics->op_mode);
+ break;
+ }
+
+ if (haptics->op_mode < DA7280_OPMODE_MAX)
+ haptics->enabled = false;
+}
+
+static void da7280_haptic_work(struct work_struct *work)
+{
+ struct da7280_haptic *haptics =
+ container_of(work, struct da7280_haptic, work);
+
+ if (haptics->magnitude)
+ da7280_haptic_enable(haptics);
+ else
+ da7280_haptic_disable(haptics);
+}
+
+static int da7280_haptic_play(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+
+ if (effect->u.rumble.strong_magnitude > 0)
+ haptics->magnitude = effect->u.rumble.strong_magnitude;
+ else if (effect->u.rumble.weak_magnitude > 0)
+ haptics->magnitude = effect->u.rumble.weak_magnitude;
+ else
+ haptics->magnitude = 0;
+
+ schedule_work(&haptics->work);
+
+ return 0;
+}
+
+static int da7280_haptic_open(struct input_dev *dev)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+ int error;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK,
+ DA7280_STANDBY_EN_MASK);
+ if (error)
+ dev_err(haptics->dev,
+ "Failed to open haptic, i2c error : %d\n", error);
+
+ return error;
+}
+
+static void da7280_haptic_close(struct input_dev *dev)
+{
+ struct da7280_haptic *haptics = input_get_drvdata(dev);
+ int error;
+
+ cancel_work_sync(&haptics->work);
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK, 0);
+ if (error)
+ goto error_i2c;
+
+ if (haptics->op_mode == DA7280_DRO_MODE) {
+ error = regmap_write(haptics->regmap,
+ DA7280_TOP_CTL2, 0);
+
+ if (error)
+ goto error_i2c;
+ }
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK, 0);
+ if (error)
+ goto error_i2c;
+
+ return;
+
+error_i2c:
+ dev_err(haptics->dev, "DA7280-haptic i2c error : %d\n", error);
+}
+
+static u8 da7280_haptic_of_mode_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "LRA"))
+ return DA7280_LRA;
+ else if (!strcmp(str, "ERM-bar"))
+ return DA7280_ERM_BAR;
+ else if (!strcmp(str, "ERM-coin"))
+ return DA7280_ERM_COIN;
+
+ dev_warn(dev, "Invalid string - set to default\n");
+ return DA7280_LRA;
+}
+
+static u8 da7280_haptic_of_gpi_mode_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "Single-pattern"))
+ return 0;
+ else if (!strcmp(str, "Multi-pattern"))
+ return 1;
+
+ dev_warn(dev, "Invalid string - set to default\n");
+ return 0;
+}
+
+static u8 da7280_haptic_of_gpi_pol_str(struct device *dev,
+ const char *str)
+{
+ if (!strcmp(str, "Rising-edge"))
+ return 0;
+ else if (!strcmp(str, "Falling-edge"))
+ return 1;
+ else if (!strcmp(str, "Both-edge"))
+ return 2;
+
+ dev_warn(dev, "Invalid string - set to default\n");
+ return 0;
+}
+
+static u8 da7280_haptic_of_volt_rating_set(u32 val)
+{
+ u32 voltage;
+
+ voltage = val / DA7280_VOLTAGE_RATE_STEP + 1;
+
+ if (voltage > 0xFF)
+ return 0xFF;
+ return (u8)voltage;
+}
+
+static void da7280_parse_properties(struct device *dev,
+ struct da7280_haptic *haptics)
+{
+ char gpi_str1[] = "dlg,gpi0-seq-id";
+ char gpi_str2[] = "dlg,gpi0-mode";
+ char gpi_str3[] = "dlg,gpi0-polarity";
+ unsigned int i, mem[DA7280_SNP_MEM_SIZE];
+ const char *str;
+ u32 val;
+
+ if (!device_property_read_string(dev, "dlg,actuator-type", &str))
+ haptics->dev_type =
+ da7280_haptic_of_mode_str(dev, str);
+ else /* if no property, then use the mode inside chip */
+ haptics->dev_type = DA7280_DEV_MAX;
+
+ if (device_property_read_u32(dev, "dlg,op-mode", &val) >= 0)
+ if (val > 0 && val < DA7280_OPMODE_MAX)
+ haptics->op_mode = val;
+ else
+ haptics->op_mode = DA7280_DRO_MODE;
+ else
+ haptics->op_mode = DA7280_DRO_MODE;
+
+ if (device_property_read_u32(dev, "dlg,nom-microvolt", &val) >= 0)
+ if (val < DA7280_VOLTAGE_RATE_MAX)
+ haptics->nommax =
+ da7280_haptic_of_volt_rating_set(val);
+ else
+ haptics->nommax = DA7280_SKIP_INIT;
+ else /* if no property, then use the value inside chip */
+ haptics->nommax = DA7280_SKIP_INIT;
+
+ if (device_property_read_u32(dev, "dlg,abs-max-microvolt",
+ &val) >= 0)
+ if (val < DA7280_VOLTAGE_RATE_MAX)
+ haptics->absmax =
+ da7280_haptic_of_volt_rating_set(val);
+ else
+ haptics->absmax = DA7280_SKIP_INIT;
+ else
+ haptics->absmax = DA7280_SKIP_INIT;
+
+ if (device_property_read_u32(dev, "dlg,imax-microamp", &val) >= 0)
+ if (val < DA7280_IMAX_LIMIT)
+ haptics->imax = (val - 28600)
+ / DA7280_IMAX_STEP + 1;
+ else
+ haptics->imax = DA7280_IMAX_DEFAULT;
+ else
+ haptics->imax = DA7280_IMAX_DEFAULT;
+
+ if (device_property_read_u32(dev, "dlg,impd-micro-ohms", &val) >= 0)
+ if (val <= DA7280_IMPD_MAX)
+ haptics->impd = val;
+ else
+ haptics->impd = DA7280_IMPD_DEFAULT;
+ else
+ haptics->impd = DA7280_IMPD_DEFAULT;
+
+ if (device_property_read_u32(dev, "dlg,resonant-freq-hz",
+ &val) >= 0) {
+ if (val < DA7280_MAX_RESONAT_FREQ_HZ &&
+ val > DA7280_MIN_RESONAT_FREQ_HZ) {
+ haptics->resonant_freq_h =
+ ((1000000000 / (val * 1333)) >> 7) & 0xFF;
+ haptics->resonant_freq_l =
+ (1000000000 / (val * 1333)) & 0x7F;
+ } else {
+ haptics->resonant_freq_h =
+ DA7280_RESONT_FREQH_DFT;
+ haptics->resonant_freq_l =
+ DA7280_RESONT_FREQL_DFT;
+ }
+ } else {
+ haptics->resonant_freq_h = DA7280_SKIP_INIT;
+ haptics->resonant_freq_l = DA7280_SKIP_INIT;
+ }
+
+ if (device_property_read_u32(dev, "dlg,ps-seq-id", &val) >= 0)
+ if (val <= DA7280_SEQ_ID_MAX)
+ haptics->ps_seq_id = val;
+ else
+ haptics->ps_seq_id = 0;
+ else /* if no property, set to zero as a default do nothing */
+ haptics->ps_seq_id = 0;
+
+ if (device_property_read_u32(dev, "dlg,ps-seq-loop", &val) >= 0)
+ if (val <= DA7280_SEQ_LOOP_MAX)
+ haptics->ps_seq_loop = val;
+ else
+ haptics->ps_seq_loop = 0;
+ else /* if no property, then do nothing */
+ haptics->ps_seq_loop = 0;
+
+ /* GPI0~2 Control */
+ for (i = 0; i < 3; i++) {
+ gpi_str1[7] = '0' + i;
+ if (device_property_read_u32 (dev, gpi_str1, &val) >= 0)
+ if (val <= DA7280_SEQ_ID_MAX)
+ haptics->gpi_ctl[i].seq_id = val;
+ else
+ haptics->gpi_ctl[i].seq_id =
+ DA7280_GPI1_SEQ_ID_DEFT + i;
+ else /* if no property, then do nothing */
+ haptics->gpi_ctl[i].seq_id =
+ DA7280_GPI1_SEQ_ID_DEFT + i;
+
+ gpi_str2[7] = '0' + i;
+ if (!device_property_read_string(dev, gpi_str2, &str))
+ haptics->gpi_ctl[i].mode =
+ da7280_haptic_of_gpi_mode_str(dev, str);
+ else
+ haptics->gpi_ctl[i].mode = 0;
+
+ gpi_str3[7] = '0' + i;
+ if (!device_property_read_string(dev, gpi_str3, &str))
+ haptics->gpi_ctl[i].polarity =
+ da7280_haptic_of_gpi_pol_str(dev, str);
+ else
+ haptics->gpi_ctl[i].polarity = 0;
+ }
+
+ haptics->bemf_sense_en =
+ device_property_read_bool(dev, "dlg,bemf-sens-enable");
+ haptics->freq_track_en =
+ device_property_read_bool(dev, "dlg,freq-track-enable");
+ haptics->acc_en =
+ device_property_read_bool(dev, "dlg,acc-enable");
+ haptics->rapid_stop_en =
+ device_property_read_bool(dev, "dlg,rapid-stop-enable");
+ haptics->amp_pid_en =
+ device_property_read_bool(dev, "dlg,amp-pid-enable");
+
+ if (device_property_read_u32_array(dev, "dlg,mem-array",
+ &mem[0],
+ DA7280_SNP_MEM_SIZE) >= 0) {
+ haptics->mem_update = true;
+ for (i = 0; i < DA7280_SNP_MEM_SIZE; i++) {
+ if (mem[i] > 0xff)
+ haptics->snp_mem[i] = 0x0;
+ else
+ haptics->snp_mem[i] = (u8)mem[i];
+ }
+ } else {
+ haptics->mem_update = false;
+ }
+}
+
+static irqreturn_t da7280_irq_handler(int irq, void *data)
+{
+ struct da7280_haptic *haptics = data;
+ u8 events[IRQ_NUM];
+ int error;
+
+ /* Check what events have happened */
+ error = regmap_bulk_read(haptics->regmap,
+ DA7280_IRQ_EVENT1,
+ events, IRQ_NUM);
+ if (error)
+ goto error_i2c;
+
+ /* Empty check due to shared interrupt */
+ if ((events[0] | events[1] | events[2]) == 0x00)
+ return IRQ_HANDLED;
+
+ if (events[0] & DA7280_E_SEQ_FAULT_MASK) {
+ /* Stop first if Haptic is working
+ * Otherwise, the fault may happen continually
+ * even though the bit is cleared.
+ */
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_OPERATION_MODE_MASK, 0);
+ if (error)
+ goto error_i2c;
+ }
+
+ /* Clear events */
+ error = regmap_write(haptics->regmap,
+ DA7280_IRQ_EVENT1, events[0]);
+ if (error)
+ goto error_i2c;
+
+ return IRQ_HANDLED;
+
+error_i2c:
+ dev_err(haptics->dev, "da7280 i2c error : %d\n", error);
+ return IRQ_NONE;
+}
+
+static int da7280_init(struct da7280_haptic *haptics)
+{
+ int error, i;
+ unsigned int val = 0;
+ u32 v2i_factor;
+ u8 mask = 0;
+
+ /* If device type is DA7280_DEV_MAX,
+ * then just use default value inside chip.
+ */
+ if (haptics->dev_type == DA7280_DEV_MAX) {
+ error = regmap_read(haptics->regmap, DA7280_TOP_CFG1, &val);
+ if (error)
+ goto error_i2c;
+ if (val & DA7280_ACTUATOR_TYPE_MASK)
+ haptics->dev_type = DA7280_ERM_COIN;
+ else
+ haptics->dev_type = DA7280_LRA;
+ }
+
+ /* Apply user settings */
+ if (haptics->dev_type == DA7280_LRA) {
+ if (haptics->resonant_freq_l != DA7280_SKIP_INIT) {
+ error = regmap_write(haptics->regmap,
+ DA7280_FRQ_LRA_PER_H,
+ haptics->resonant_freq_h);
+ if (error)
+ goto error_i2c;
+ error = regmap_write(haptics->regmap,
+ DA7280_FRQ_LRA_PER_L,
+ haptics->resonant_freq_l);
+ if (error)
+ goto error_i2c;
+ }
+ } else if (haptics->dev_type == DA7280_ERM_COIN) {
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_INT_CFG1,
+ DA7280_BEMF_FAULT_LIM_MASK, 0);
+ if (error)
+ goto error_i2c;
+
+ mask = DA7280_TST_CALIB_IMPEDANCE_DIS_MASK |
+ DA7280_V2I_FACTOR_FREEZE_MASK;
+ val = DA7280_TST_CALIB_IMPEDANCE_DIS_MASK |
+ DA7280_V2I_FACTOR_FREEZE_MASK;
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CFG4,
+ mask, val);
+ if (error)
+ goto error_i2c;
+
+ haptics->acc_en = false;
+ haptics->rapid_stop_en = false;
+ haptics->amp_pid_en = false;
+ }
+
+ /* Should be set to 0 only
+ * in custom waveform and wideband operation
+ */
+ if (haptics->op_mode >= DA7280_RTWM_MODE)
+ haptics->bemf_sense_en = false;
+
+ mask = DA7280_ACTUATOR_TYPE_MASK |
+ DA7280_BEMF_SENSE_EN_MASK |
+ DA7280_FREQ_TRACK_EN_MASK |
+ DA7280_ACCELERATION_EN_MASK |
+ DA7280_RAPID_STOP_EN_MASK |
+ DA7280_AMP_PID_EN_MASK;
+
+ val = (haptics->dev_type ? 1 : 0)
+ << DA7280_ACTUATOR_TYPE_SHIFT |
+ (haptics->bemf_sense_en ? 1 : 0)
+ << DA7280_BEMF_SENSE_EN_SHIFT |
+ (haptics->freq_track_en ? 1 : 0)
+ << DA7280_FREQ_TRACK_EN_SHIFT |
+ (haptics->acc_en ? 1 : 0)
+ << DA7280_ACCELERATION_EN_SHIFT |
+ (haptics->rapid_stop_en ? 1 : 0)
+ << DA7280_RAPID_STOP_EN_SHIFT |
+ (haptics->amp_pid_en ? 1 : 0)
+ << DA7280_AMP_PID_EN_SHIFT;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CFG1, mask, val);
+ if (error)
+ goto error_i2c;
+
+ if (haptics->nommax != DA7280_SKIP_INIT) {
+ error = regmap_write(haptics->regmap,
+ DA7280_ACTUATOR1,
+ haptics->nommax);
+ if (error)
+ goto error_i2c;
+ }
+
+ if (haptics->absmax != DA7280_SKIP_INIT) {
+ error = regmap_write(haptics->regmap, DA7280_ACTUATOR2,
+ haptics->absmax);
+ if (error)
+ goto error_i2c;
+ }
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_ACTUATOR3,
+ DA7280_IMAX_MASK,
+ haptics->imax);
+ if (error)
+ goto error_i2c;
+
+ v2i_factor =
+ haptics->impd * (haptics->imax + 4) / 1610400;
+ error = regmap_write(haptics->regmap,
+ DA7280_CALIB_V2I_L,
+ v2i_factor & 0xff);
+ if (error)
+ goto error_i2c;
+ error = regmap_write(haptics->regmap,
+ DA7280_CALIB_V2I_H,
+ v2i_factor >> 8);
+ if (error)
+ goto error_i2c;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK,
+ DA7280_STANDBY_EN_MASK);
+ if (error)
+ goto error_i2c;
+
+ if (haptics->mem_update) {
+ error = da7280_haptic_mem_update(haptics);
+ if (error)
+ goto error_i2c;
+ }
+
+ /* Set PS_SEQ_ID and PS_SEQ_LOOP */
+ val = haptics->ps_seq_id << DA7280_PS_SEQ_ID_SHIFT |
+ haptics->ps_seq_loop << DA7280_PS_SEQ_LOOP_SHIFT;
+ error = regmap_write(haptics->regmap,
+ DA7280_SEQ_CTL2, val);
+ if (error)
+ goto error_i2c;
+
+ /* GPI(N) CTL */
+ for (i = 0; i < 3; i++) {
+ val = haptics->gpi_ctl[i].seq_id
+ << DA7280_GPI0_SEQUENCE_ID_SHIFT |
+ haptics->gpi_ctl[i].mode
+ << DA7280_GPI0_MODE_SHIFT |
+ haptics->gpi_ctl[i].polarity
+ << DA7280_GPI0_POLARITY_SHIFT;
+ error = regmap_write(haptics->regmap,
+ DA7280_GPI_0_CTL + i, val);
+ if (error)
+ goto error_i2c;
+ }
+
+ /* Clear Interrupts */
+ error = regmap_write(haptics->regmap, DA7280_IRQ_EVENT1, 0xff);
+ if (error)
+ goto error_i2c;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_IRQ_MASK1,
+ DA7280_SEQ_FAULT_M_MASK
+ | DA7280_SEQ_DONE_M_MASK, 0);
+ if (error)
+ goto error_i2c;
+
+ haptics->enabled = false;
+ return 0;
+
+error_i2c:
+ dev_err(haptics->dev, "haptic init - I2C error : %d\n", error);
+ return error;
+}
+
+/* Valid format for ps_seq_id
+ * echo X > ps_seq_id
+ * ex) echo 2 > /sys/class/..../ps_seq_id
+ * 0 <= X <= 15.
+ */
+static ssize_t ps_seq_id_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ long val = 0xff;
+ int error;
+
+ if (kstrtol(&buf[0], 0, &val) < 0)
+ goto err;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_SEQ_CTL2,
+ DA7280_PS_SEQ_ID_MASK,
+ (val & 0xf) >> DA7280_PS_SEQ_ID_SHIFT);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", error);
+ return error;
+ }
+
+ haptics->ps_seq_id = val & 0xf;
+
+ return count;
+
+err:
+ dev_err(dev, "Invalid input\n");
+ return count;
+}
+
+static ssize_t ps_seq_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int error;
+ unsigned int val;
+
+ error = regmap_read(haptics->regmap, DA7280_SEQ_CTL2, &val);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", error);
+ return error;
+ }
+ val = (val & DA7280_PS_SEQ_ID_MASK)
+ >> DA7280_PS_SEQ_ID_SHIFT;
+
+ return sprintf(buf, "ps_seq_id is %d\n", val);
+}
+
+/* Valid format for ps_seq_loop
+ * echo X > ps_seq_loop
+ * ex) echo 2 > /sys/class/..../ps_seq_loop
+ * 0 <= X <= 15.
+ */
+static ssize_t ps_seq_loop_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ long val = 0xff;
+ int error;
+
+ if (kstrtol(&buf[0], 0, &val) < 0)
+ goto err;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_SEQ_CTL2,
+ DA7280_PS_SEQ_LOOP_MASK,
+ (val & 0xF) << DA7280_PS_SEQ_LOOP_SHIFT);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", error);
+ return error;
+ }
+
+ haptics->ps_seq_loop = (val & 0xF);
+
+ return count;
+err:
+ dev_err(dev, "Invalid input value!\n");
+ return count;
+}
+
+static ssize_t ps_seq_loop_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int error;
+ unsigned int val;
+
+ error = regmap_read(haptics->regmap, DA7280_SEQ_CTL2, &val);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", error);
+ return error;
+ }
+ val = (val & DA7280_PS_SEQ_LOOP_MASK)
+ >> DA7280_PS_SEQ_LOOP_SHIFT;
+
+ return sprintf(buf, "ps_seq_loop is %d\n", val);
+}
+
+/* Valid format for GPIx_SEQUENCE_ID
+ * echo X > ./gpi_seq_id0
+ * Range of X: 0 <= X <= 15
+ * ex)
+ * echo 1 > /sys/class/..../gpi_seq_id0
+ * echo 2 > /sys/class/..../gpi_seq_id1
+ * echo 3 > /sys/class/..../gpi_seq_id2
+ */
+static ssize_t gpi_seq_id0_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ long val = 0xff;
+ int error;
+
+ if (kstrtol(&buf[0], 0, &val) < 0)
+ goto err;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_GPI_0_CTL,
+ DA7280_GPI0_SEQUENCE_ID_MASK,
+ (val & 0xf)
+ << DA7280_GPI0_SEQUENCE_ID_SHIFT);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", error);
+ return error;
+ }
+
+ haptics->gpi_ctl[0].seq_id = val & 0xf;
+
+ return count;
+
+err:
+ dev_err(dev, "Invalid input\n");
+ return count;
+}
+
+static ssize_t gpi_seq_id0_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int error;
+ unsigned int val;
+
+ error = regmap_read(haptics->regmap, DA7280_GPI_0_CTL, &val);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", error);
+ return error;
+ }
+ val = (val & DA7280_GPI0_SEQUENCE_ID_MASK)
+ >> DA7280_GPI0_SEQUENCE_ID_SHIFT;
+
+ return sprintf(buf, "gpi_seq_id0 is %d\n", val);
+}
+
+static ssize_t gpi_seq_id1_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ long val = 0xff;
+ int error;
+
+ if (kstrtol(&buf[0], 0, &val) < 0)
+ goto err;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_GPI_1_CTL,
+ DA7280_GPI1_SEQUENCE_ID_MASK,
+ (val & 0xf)
+ << DA7280_GPI1_SEQUENCE_ID_SHIFT);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", error);
+ return error;
+ }
+
+ haptics->gpi_ctl[1].seq_id = val & 0xf;
+
+ return count;
+
+err:
+ dev_err(dev, "Invalid input\n");
+ return count;
+}
+
+static ssize_t gpi_seq_id1_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int error;
+ unsigned int val;
+
+ error = regmap_read(haptics->regmap, DA7280_GPI_1_CTL, &val);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", error);
+ return error;
+ }
+ val = (val & DA7280_GPI1_SEQUENCE_ID_MASK)
+ >> DA7280_GPI1_SEQUENCE_ID_SHIFT;
+
+ return sprintf(buf, "gpi_seq_id1 is %d\n", val);
+}
+
+static ssize_t gpi_seq_id2_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ long val = 0xff;
+ int error;
+
+ if (kstrtol(&buf[0], 0, &val) < 0)
+ goto err;
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_GPI_2_CTL,
+ DA7280_GPI2_SEQUENCE_ID_MASK,
+ (val & 0xf)
+ << DA7280_GPI2_SEQUENCE_ID_SHIFT);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to update register : %d\n", error);
+ return error;
+ }
+
+ haptics->gpi_ctl[2].seq_id = val & 0xf;
+
+ return count;
+
+err:
+ dev_err(dev, "Invalid input\n");
+ return count;
+}
+
+static ssize_t gpi_seq_id2_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int error;
+ unsigned int val;
+
+ error = regmap_read(haptics->regmap, DA7280_GPI_2_CTL, &val);
+ if (error) {
+ dev_err(haptics->dev,
+ "failed to read register : %d\n", error);
+ return error;
+ }
+ val = (val & DA7280_GPI2_SEQUENCE_ID_MASK)
+ >> DA7280_GPI2_SEQUENCE_ID_SHIFT;
+
+ return sprintf(buf, "gpi_seq_id2 is %d\n", val);
+}
+
+#define MAX_PTN_REGS DA7280_SNP_MEM_SIZE
+#define MAX_USER_INPUT_LEN (5 * DA7280_SNP_MEM_SIZE)
+struct parse_data_t {
+ int len;
+ u8 val[MAX_PTN_REGS];
+};
+
+static int da7280_parse_args(struct device *dev,
+ char *cmd, struct parse_data_t *ptn)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ char *tok; /* used to separate tokens */
+ const char ct[] = " \t"; /* space or tab delimits the tokens */
+ int tok_count = 0; /* total number of tokens parsed */
+ int i = 0, val;
+
+ ptn->len = 0;
+
+ /* parse the input string */
+ while ((tok = strsep(&cmd, ct)) != NULL) {
+ /* this is a value to be written to the register */
+ if (kstrtouint(tok, 0, &val) < 0) {
+ dev_err(haptics->dev,
+ "failed to read from %s\n", tok);
+ break;
+ }
+
+ if (i < MAX_PTN_REGS) {
+ ptn->val[i] = val;
+ i++;
+ }
+ tok_count++;
+ }
+
+ /* decide whether it is a read or write operation based on the
+ * value of tok_count and count_flag.
+ * tok_count = 0: no inputs, invalid case.
+ * tok_count = 1: write one value.
+ * tok_count > 1: write multiple values/patterns.
+ */
+ switch (tok_count) {
+ case 0:
+ return -EINVAL;
+ case 1:
+ ptn->len = 1;
+ break;
+ default:
+ ptn->len = i;
+ }
+ return 0;
+}
+
+static ssize_t
+patterns_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ struct parse_data_t mem;
+ char cmd[MAX_USER_INPUT_LEN];
+ unsigned int val;
+ int error;
+
+ error = regmap_read(haptics->regmap, DA7280_MEM_CTL1, &val);
+ if (error)
+ return error;
+
+ if (count > MAX_USER_INPUT_LEN)
+ memcpy(cmd, buf, MAX_USER_INPUT_LEN);
+ else
+ memcpy(cmd, buf, count);
+
+ /* chop of '\n' introduced by echo at the end of the input */
+ if (cmd[count - 1] == '\n')
+ cmd[count - 1] = '\0';
+
+ if (da7280_parse_args(dev, cmd, &mem) < 0)
+ return -EINVAL;
+
+ memcpy(haptics->snp_mem, mem.val, mem.len);
+
+ error = da7280_haptic_mem_update(haptics);
+ if (error)
+ return error;
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(ps_seq_id);
+static DEVICE_ATTR_RW(ps_seq_loop);
+static DEVICE_ATTR_RW(gpi_seq_id0);
+static DEVICE_ATTR_RW(gpi_seq_id1);
+static DEVICE_ATTR_RW(gpi_seq_id2);
+static DEVICE_ATTR_WO(patterns);
+static struct attribute *da7280_sysfs_attr[] = {
+ &dev_attr_ps_seq_id.attr,
+ &dev_attr_ps_seq_loop.attr,
+ &dev_attr_gpi_seq_id0.attr,
+ &dev_attr_gpi_seq_id1.attr,
+ &dev_attr_gpi_seq_id2.attr,
+ &dev_attr_patterns.attr,
+ NULL,
+};
+
+static const struct attribute_group da7280_attr_group = {
+ .attrs = da7280_sysfs_attr,
+};
+
+static int da7280_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct da7280_haptic *haptics;
+ unsigned int period2freq;
+ int error;
+
+ haptics = devm_kzalloc(dev, sizeof(*haptics), GFP_KERNEL);
+ if (!haptics)
+ return -ENOMEM;
+ haptics->dev = dev;
+
+ if (!client->irq) {
+ dev_err(dev, "No IRQ configured\n");
+ return -EINVAL;
+ }
+
+ da7280_parse_properties(&client->dev, haptics);
+
+ if (haptics->op_mode == DA7280_PWM_MODE) {
+ /* Get pwm and regulatot for haptics device */
+ haptics->pwm_dev = devm_pwm_get(&client->dev, NULL);
+ if (IS_ERR(haptics->pwm_dev)) {
+ dev_err(dev, "failed to get PWM device\n");
+ return PTR_ERR(haptics->pwm_dev);
+ }
+
+ /*
+ * FIXME: pwm_apply_args() should be removed when switching to
+ * the atomic PWM API.
+ */
+ pwm_apply_args(haptics->pwm_dev);
+
+ /* Check PWM Period, it must be in 10k ~ 250kHz */
+ period2freq = 1000000 / pwm_get_period(haptics->pwm_dev);
+ if (period2freq < DA7280_MIN_PWM_FREQ_KHZ ||
+ period2freq > DA7280_MAX_PWM_FREQ_KHZ) {
+ dev_err(dev, "Not supported PWM frequency(%d)\n",
+ period2freq);
+ return -EINVAL;
+ }
+ }
+
+ INIT_WORK(&haptics->work, da7280_haptic_work);
+ haptics->client = client;
+ i2c_set_clientdata(client, haptics);
+
+ haptics->regmap =
+ devm_regmap_init_i2c(client, &da7280_haptic_regmap_config);
+ if (IS_ERR(haptics->regmap)) {
+ error = PTR_ERR(haptics->regmap);
+ dev_err(dev, "Failed to allocate register map : %d\n",
+ error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, client->irq, NULL,
+ da7280_irq_handler,
+ IRQF_ONESHOT,
+ "da7280-haptics", haptics);
+ if (error != 0) {
+ dev_err(dev,
+ "Failed to request IRQ : %d\n", client->irq);
+ return error;
+ }
+
+ error = da7280_init(haptics);
+ if (error) {
+ dev_err(dev, "failed to initialize device\n");
+ return error;
+ }
+
+ /* Initialize input device for haptic device */
+ haptics->input_dev = devm_input_allocate_device(dev);
+ if (!haptics->input_dev) {
+ dev_err(dev, "failed to allocate input device\n");
+ return -ENOMEM;
+ }
+
+ haptics->input_dev->name = "da7280-haptic";
+ haptics->input_dev->dev.parent = client->dev.parent;
+ haptics->input_dev->open = da7280_haptic_open;
+ haptics->input_dev->close = da7280_haptic_close;
+ input_set_drvdata(haptics->input_dev, haptics);
+ input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
+
+ error = input_ff_create_memless(haptics->input_dev, NULL,
+ da7280_haptic_play);
+ if (error) {
+ dev_err(dev, "failed to create force-feedback\n");
+ return error;
+ }
+
+ error = input_register_device(haptics->input_dev);
+ if (error) {
+ dev_err(dev, "failed to register input device\n");
+ return error;
+ }
+
+ error = devm_device_add_group(dev, &da7280_attr_group);
+ if (error) {
+ dev_err(dev, "Failed to create sysfs attributes: %d\n",
+ error);
+ }
+
+ return error;
+}
+
+static int __maybe_unused da7280_suspend(struct device *dev)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int error = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ da7280_haptic_disable(haptics);
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK, 0);
+ if (error)
+ dev_err(haptics->dev,
+ "I2C error : %d\n", error);
+
+ mutex_unlock(&haptics->input_dev->mutex);
+ return error;
+}
+
+static int __maybe_unused da7280_resume(struct device *dev)
+{
+ struct da7280_haptic *haptics = dev_get_drvdata(dev);
+ int error = 0;
+
+ mutex_lock(&haptics->input_dev->mutex);
+
+ error = regmap_update_bits(haptics->regmap,
+ DA7280_TOP_CTL1,
+ DA7280_STANDBY_EN_MASK,
+ DA7280_STANDBY_EN_MASK);
+ if (error)
+ dev_err(haptics->dev,
+ "i2c error : %d\n", error);
+
+ mutex_unlock(&haptics->input_dev->mutex);
+ return error;
+}
+
+static const struct of_device_id da7280_of_match[] = {
+ { .compatible = "dlg,da7280", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, da7280_of_match);
+
+static const struct i2c_device_id da7280_i2c_id[] = {
+ { "da7280", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, da7280_i2c_id);
+
+static SIMPLE_DEV_PM_OPS(da7280_pm_ops,
+ da7280_suspend, da7280_resume);
+
+static struct i2c_driver da7280_driver = {
+ .driver = {
+ .name = "da7280",
+ .of_match_table = of_match_ptr(da7280_of_match),
+ .pm = &da7280_pm_ops,
+ },
+ .probe = da7280_probe,
+ .id_table = da7280_i2c_id,
+};
+module_i2c_driver(da7280_driver);
+
+MODULE_DESCRIPTION("DA7280 haptics driver");
+MODULE_AUTHOR("Roy Im <Roy.Im.Opensource@diasemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/da7280.h b/drivers/input/misc/da7280.h
new file mode 100644
index 0000000..d9310b6
--- /dev/null
+++ b/drivers/input/misc/da7280.h
@@ -0,0 +1,412 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * DA7280 Haptic device driver registers
+ *
+ * Copyright (c) 2017 Dialog Semiconductor.
+ * Author: Roy Im <Roy.Im.Opensource@diasemi.com>
+ */
+
+#ifndef _DA7280_REG_DEFS_H
+#define _DA7280_REG_DEFS_H
+
+#include <linux/bitops.h>
+
+/* Registers */
+
+#define DA7280_CHIP_REV 0x00
+#define DA7280_IRQ_EVENT1 0x03
+#define DA7280_IRQ_EVENT_WARNING_DIAG 0x04
+#define DA7280_IRQ_EVENT_SEQ_DIAG 0x05
+#define DA7280_IRQ_STATUS1 0x06
+#define DA7280_IRQ_MASK1 0x07
+#define DA7280_CIF_I2C1 0x08
+#define DA7280_FRQ_LRA_PER_H 0x0A
+#define DA7280_FRQ_LRA_PER_L 0x0B
+#define DA7280_ACTUATOR1 0x0C
+#define DA7280_ACTUATOR2 0x0D
+#define DA7280_ACTUATOR3 0x0E
+#define DA7280_CALIB_V2I_H 0x0F
+#define DA7280_CALIB_V2I_L 0x10
+#define DA7280_CALIB_IMP_H 0x11
+#define DA7280_CALIB_IMP_L 0x12
+#define DA7280_TOP_CFG1 0x13
+#define DA7280_TOP_CFG2 0x14
+#define DA7280_TOP_CFG3 0x15
+#define DA7280_TOP_CFG4 0x16
+#define DA7280_TOP_INT_CFG1 0x17
+#define DA7280_TOP_INT_CFG6_H 0x1C
+#define DA7280_TOP_INT_CFG6_L 0x1D
+#define DA7280_TOP_INT_CFG7_H 0x1E
+#define DA7280_TOP_INT_CFG7_L 0x1F
+#define DA7280_TOP_INT_CFG8 0x20
+#define DA7280_TOP_CTL1 0x22
+#define DA7280_TOP_CTL2 0x23
+#define DA7280_SEQ_CTL1 0x24
+#define DA7280_SWG_C1 0x25
+#define DA7280_SWG_C2 0x26
+#define DA7280_SWG_C3 0x27
+#define DA7280_SEQ_CTL2 0x28
+#define DA7280_GPI_0_CTL 0x29
+#define DA7280_GPI_1_CTL 0x2A
+#define DA7280_GPI_2_CTL 0x2B
+#define DA7280_MEM_CTL1 0x2C
+#define DA7280_MEM_CTL2 0x2D
+#define DA7280_ADC_DATA_H1 0x2E
+#define DA7280_ADC_DATA_L1 0x2F
+#define DA7280_POLARITY 0x43
+#define DA7280_LRA_AVR_H 0x44
+#define DA7280_LRA_AVR_L 0x45
+#define DA7280_FRQ_LRA_PER_ACT_H 0x46
+#define DA7280_FRQ_LRA_PER_ACT_L 0x47
+#define DA7280_FRQ_PHASE_H 0x48
+#define DA7280_FRQ_PHASE_L 0x49
+#define DA7280_FRQ_CTL 0x4C
+#define DA7280_TRIM3 0x5F
+#define DA7280_TRIM4 0x60
+#define DA7280_TRIM6 0x62
+#define DA7280_TOP_CFG5 0x6E
+#define DA7280_IRQ_EVENT_ACTUATOR_FAULT 0x81
+#define DA7280_IRQ_STATUS2 0x82
+#define DA7280_IRQ_MASK2 0x83
+#define DA7280_SNP_MEM_0 0x84
+#define DA7280_SNP_MEM_99 0xE7
+
+/* DA7280_CHIP_REV (Address 0x00) */
+#define DA7280_CHIP_REV_MAJOR_SHIFT 0
+#define DA7280_CHIP_REV_MAJOR_MASK (15 << 0)
+#define DA7280_CHIP_REV_MINOR_SHIFT 4
+#define DA7280_CHIP_REV_MINOR_MASK (15 << 4)
+
+/* DA7280_IRQ_EVENT1 (Address 0x03) */
+#define DA7280_E_SEQ_CONTINUE_SHIFT 0
+#define DA7280_E_SEQ_CONTINUE_MASK BIT(0)
+#define DA7280_E_UVLO_SHIFT 1
+#define DA7280_E_UVLO_MASK BIT(1)
+#define DA7280_E_SEQ_DONE_SHIFT 2
+#define DA7280_E_SEQ_DONE_MASK BIT(2)
+#define DA7280_E_OVERTEMP_CRIT_SHIFT 3
+#define DA7280_E_OVERTEMP_CRIT_MASK BIT(3)
+#define DA7280_E_SEQ_FAULT_SHIFT 4
+#define DA7280_E_SEQ_FAULT_MASK BIT(4)
+#define DA7280_E_WARNING_SHIFT 5
+#define DA7280_E_WARNING_MASK BIT(5)
+#define DA7280_E_ACTUATOR_FAULT_SHIFT 6
+#define DA7280_E_ACTUATOR_FAULT_MASK BIT(6)
+#define DA7280_E_OC_FAULT_SHIFT 7
+#define DA7280_E_OC_FAULT_MASK BIT(7)
+
+/* DA7280_IRQ_EVENT_WARNING_DIAG (Address 0x04) */
+#define DA7280_E_OVERTEMP_WARN_SHIFT 3
+#define DA7280_E_OVERTEMP_WARN_MASK BIT(3)
+#define DA7280_E_MEM_TYPE_SHIFT 4
+#define DA7280_E_MEM_TYPE_MASK BIT(4)
+#define DA7280_E_LIM_DRIVE_ACC_SHIFT 6
+#define DA7280_E_LIM_DRIVE_ACC_MASK BIT(6)
+#define DA7280_E_LIM_DRIVE_SHIFT 7
+#define DA7280_E_LIM_DRIVE_MASK BIT(7)
+
+/* DA7280_IRQ_EVENT_PAT_DIAG (Address 0x05) */
+#define DA7280_E_PWM_FAULT_SHIFT 5
+#define DA7280_E_PWM_FAULT_MASK BIT(5)
+#define DA7280_E_MEM_FAULT_SHIFT 6
+#define DA7280_E_MEM_FAULT_MASK BIT(6)
+#define DA7280_E_SEQ_ID_FAULT_SHIFT 7
+#define DA7280_E_SEQ_ID_FAULT_MASK BIT(7)
+
+/* DA7280_IRQ_STATUS1 (Address 0x06) */
+#define DA7280_STA_SEQ_CONTINUE_SHIFT 0
+#define DA7280_STA_SEQ_CONTINUE_MASK BIT(0)
+#define DA7280_STA_UVLO_VBAT_OK_SHIFT 1
+#define DA7280_STA_UVLO_VBAT_OK_MASK BIT(1)
+#define DA7280_STA_SEQ_DONE_SHIFT 2
+#define DA7280_STA_SEQ_DONE_MASK BIT(2)
+#define DA7280_STA_OVERTEMP_CRIT_SHIFT 3
+#define DA7280_STA_OVERTEMP_CRIT_MASK BIT(3)
+#define DA7280_STA_SEQ_FAULT_SHIFT 4
+#define DA7280_STA_SEQ_FAULT_MASK BIT(4)
+#define DA7280_STA_WARNING_SHIFT 5
+#define DA7280_STA_WARNING_MASK BIT(5)
+#define DA7280_STA_ACTUATOR_SHIFT 6
+#define DA7280_STA_ACTUATOR_MASK BIT(6)
+#define DA7280_STA_OC_SHIFT 7
+#define DA7280_STA_OC_MASK BIT(7)
+
+/* DA7280_IRQ_MASK1 (Address 0x07) */
+#define DA7280_SEQ_CONTINUE_M_SHIFT 0
+#define DA7280_SEQ_CONTINUE_M_MASK BIT(0)
+#define DA7280_E_UVLO_M_SHIFT 1
+#define DA7280_E_UVLO_M_MASK BIT(1)
+#define DA7280_SEQ_DONE_M_SHIFT 2
+#define DA7280_SEQ_DONE_M_MASK BIT(2)
+#define DA7280_OVERTEMP_CRIT_M_SHIFT 3
+#define DA7280_OVERTEMP_CRIT_M_MASK BIT(3)
+#define DA7280_SEQ_FAULT_M_SHIFT 4
+#define DA7280_SEQ_FAULT_M_MASK BIT(4)
+#define DA7280_WARNING_M_SHIFT 5
+#define DA7280_WARNING_M_MASK BIT(5)
+#define DA7280_ACTUATOR_M_SHIFT 6
+#define DA7280_ACTUATOR_M_MASK BIT(6)
+#define DA7280_OC_M_SHIFT 7
+#define DA7280_OC_M_MASK BIT(7)
+
+/* DA7280_CIF_I2C1 (Address 0x08) */
+#define DA7280_I2C_TO_ENABLE_SHIFT 6
+#define DA7280_I2C_TO_ENABLE_MASK BIT(6)
+#define DA7280_I2C_WR_MODE_SHIFT 7
+#define DA7280_I2C_WR_MODE_MASK BIT(7)
+
+/* DA7280_FRQ_LRA_PER_H (Address 0x0a) */
+#define DA7280_LRA_PER_H_SHIFT 0
+#define DA7280_LRA_PER_H_MASK (255 << 0)
+
+/* DA7280_FRQ_LRA_PER_L (Address 0x0b) */
+#define DA7280_LRA_PER_L_SHIFT 0
+#define DA7280_LRA_PER_L_MASK (127 << 0)
+
+/* DA7280_ACTUATOR1 (Address 0x0c) */
+#define DA7280_ACTUATOR_NOMMAX_SHIFT 0
+#define DA7280_ACTUATOR_NOMMAX_MASK (255 << 0)
+
+/* DA7280_ACTUATOR2 (Address 0x0d) */
+#define DA7280_ACTUATOR_ABSMAX_SHIFT 0
+#define DA7280_ACTUATOR_ABSMAX_MASK (255 << 0)
+
+/* DA7280_ACTUATOR3 (Address 0x0e) */
+#define DA7280_IMAX_SHIFT 0
+#define DA7280_IMAX_MASK (31 << 0)
+
+/* DA7280_CALIB_V2I_H (Address 0x0f) */
+#define DA7280_V2I_FACTOR_H_SHIFT 0
+#define DA7280_V2I_FACTOR_H_MASK (255 << 0)
+
+/* DA7280_CALIB_V2I_L (Address 0x10) */
+#define DA7280_V2I_FACTOR_L_SHIFT 0
+#define DA7280_V2I_FACTOR_L_MASK (255 << 0)
+
+/* DA7280_CALIB_IMP_H (Address 0x11) */
+#define DA7280_IMPEDANCE_H_SHIFT 0
+#define DA7280_IMPEDANCE_H_MASK (255 << 0)
+
+/* DA7280_CALIB_IMP_L (Address 0x12) */
+#define DA7280_IMPEDANCE_L_SHIFT 0
+#define DA7280_IMPEDANCE_L_MASK (3 << 0)
+
+/* DA7280_TOP_CFG1 (Address 0x13) */
+#define DA7280_AMP_PID_EN_SHIFT 0
+#define DA7280_AMP_PID_EN_MASK BIT(0)
+#define DA7280_RAPID_STOP_EN_SHIFT 1
+#define DA7280_RAPID_STOP_EN_MASK BIT(1)
+#define DA7280_ACCELERATION_EN_SHIFT 2
+#define DA7280_ACCELERATION_EN_MASK BIT(2)
+#define DA7280_FREQ_TRACK_EN_SHIFT 3
+#define DA7280_FREQ_TRACK_EN_MASK BIT(3)
+#define DA7280_BEMF_SENSE_EN_SHIFT 4
+#define DA7280_BEMF_SENSE_EN_MASK BIT(4)
+#define DA7280_ACTUATOR_TYPE_SHIFT 5
+#define DA7280_ACTUATOR_TYPE_MASK BIT(5)
+#define DA7280_EMBEDDED_MODE_SHIFT 7
+#define DA7280_EMBEDDED_MODE_MASK BIT(7)
+
+/* DA7280_TOP_CFG2 (Address 0x14) */
+#define DA7280_FULL_BRAKE_THR_SHIFT 0
+#define DA7280_FULL_BRAKE_THR_MASK (15 << 0)
+#define DA7280_MEM_DATA_SIGNED_SHIFT 4
+#define DA7280_MEM_DATA_SIGNED_MASK BIT(4)
+
+/* DA7280_TOP_CFG3 (Address 0x15) */
+#define DA7280_VDD_MARGIN_SHIFT 0
+#define DA7280_VDD_MARGIN_MASK (15 << 0)
+
+/* DA7280_TOP_CFG4 (Address 0x16) */
+#define DA7280_TST_CALIB_IMPEDANCE_DIS_SHIFT 6
+#define DA7280_TST_CALIB_IMPEDANCE_DIS_MASK BIT(6)
+#define DA7280_V2I_FACTOR_FREEZE_SHIFT 7
+#define DA7280_V2I_FACTOR_FREEZE_MASK BIT(7)
+
+/* DA7280_TOP_INT_CFG1 (Address 0x17) */
+#define DA7280_BEMF_FAULT_LIM_SHIFT 0
+#define DA7280_BEMF_FAULT_LIM_MASK (3 << 0)
+#define DA7280_FRQ_LOCKED_LIM_SHIFT 2
+#define DA7280_FRQ_LOCKED_LIM_MASK (63 << 2)
+
+/* DA7280_TOP_INT_CFG6_H (Address 0x1c) */
+#define DA7280_FRQ_PID_KP_H_SHIFT 0
+#define DA7280_FRQ_PID_KP_H_MASK (255 << 0)
+
+/* DA7280_TOP_INT_CFG6_L (Address 0x1d) */
+#define DA7280_FRQ_PID_KP_L_SHIFT 0
+#define DA7280_FRQ_PID_KP_L_MASK (255 << 0)
+
+/* DA7280_TOP_INT_CFG7_H (Address 0x1e) */
+#define DA7280_FRQ_PID_KI_H_SHIFT 0
+#define DA7280_FRQ_PID_KI_H_MASK (255 << 0)
+
+/* DA7280_TOP_INT_CFG7_L (Address 0x1f) */
+#define DA7280_FRQ_PID_KI_L_SHIFT 0
+#define DA7280_FRQ_PID_KI_L_MASK (255 << 0)
+
+/* DA7280_TOP_INT_CFG8 (Address 0x20) */
+#define DA7280_TST_FRQ_TRACK_BEMF_LIM_SHIFT 0
+#define DA7280_TST_FRQ_TRACK_BEMF_LIM_MASK (15 << 0)
+#define DA7280_TST_AMP_RAPID_STOP_LIM_SHIFT 4
+#define DA7280_TST_AMP_RAPID_STOP_LIM_MASK (7 << 4)
+
+/* DA7280_TOP_CTL1 (Address 0x22) */
+#define DA7280_OPERATION_MODE_SHIFT 0
+#define DA7280_OPERATION_MODE_MASK (7 << 0)
+#define DA7280_STANDBY_EN_SHIFT 3
+#define DA7280_STANDBY_EN_MASK BIT(3)
+#define DA7280_SEQ_START_SHIFT 4
+#define DA7280_SEQ_START_MASK BIT(4)
+
+/* DA7280_TOP_CTL2 (Address 0x23) */
+#define DA7280_OVERRIDE_VAL_SHIFT 0
+#define DA7280_OVERRIDE_VAL_MASK (255 << 0)
+
+/* DA7280_SEQ_CTL1 (Address 0x24) */
+#define DA7280_SEQ_CONTINUE_SHIFT 0
+#define DA7280_SEQ_CONTINUE_MASK BIT(0)
+#define DA7280_WAVEGEN_MODE_SHIFT 1
+#define DA7280_WAVEGEN_MODE_MASK BIT(1)
+#define DA7280_FREQ_WAVEFORM_TIMEBASE_SHIFT 2
+#define DA7280_FREQ_WAVEFORM_TIMEBASE_MASK BIT(2)
+
+/* DA7280_SWG_C1 (Address 0x25) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF1_SHIFT 0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF1_MASK (255 << 0)
+
+/* DA7280_SWG_C2 (Address 0x26) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF2_SHIFT 0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF2_MASK (255 << 0)
+
+/* DA7280_SWG_C3 (Address 0x27) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF3_SHIFT 0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF3_MASK (255 << 0)
+
+/* DA7280_SEQ_CTL2 (Address 0x28) */
+#define DA7280_PS_SEQ_ID_SHIFT 0
+#define DA7280_PS_SEQ_ID_MASK (15 << 0)
+#define DA7280_PS_SEQ_LOOP_SHIFT 4
+#define DA7280_PS_SEQ_LOOP_MASK (15 << 4)
+
+/* DA7280_GPIO_0_CTL (Address 0x29) */
+#define DA7280_GPI0_POLARITY_SHIFT 0
+#define DA7280_GPI0_POLARITY_MASK (3 << 0)
+#define DA7280_GPI0_MODE_SHIFT 2
+#define DA7280_GPI0_MODE_MASK BIT(2)
+#define DA7280_GPI0_SEQUENCE_ID_SHIFT 3
+#define DA7280_GPI0_SEQUENCE_ID_MASK (15 << 3)
+
+/* DA7280_GPIO_1_CTL (Address 0x2a) */
+#define DA7280_GPI1_POLARITY_SHIFT 0
+#define DA7280_GPI1_POLARITY_MASK (3 << 0)
+#define DA7280_GPI1_MODE_SHIFT 2
+#define DA7280_GPI1_MODE_MASK BIT(2)
+#define DA7280_GPI1_SEQUENCE_ID_SHIFT 3
+#define DA7280_GPI1_SEQUENCE_ID_MASK (15 << 3)
+
+/* DA7280_GPIO_2_CTL (Address 0x2b) */
+#define DA7280_GPI2_POLARITY_SHIFT 0
+#define DA7280_GPI2_POLARITY_MASK (3 << 0)
+#define DA7280_GPI2_MODE_SHIFT 2
+#define DA7280_GPI2_MODE_MASK BIT(2)
+#define DA7280_GPI2_SEQUENCE_ID_SHIFT 3
+#define DA7280_GPI2_SEQUENCE_ID_MASK (15 << 3)
+
+/* DA7280_MEM_CTL1 (Address 0x2c) */
+#define DA7280_WAV_MEM_BASE_ADDR_SHIFT 0
+#define DA7280_WAV_MEM_BASE_ADDR_MASK (255 << 0)
+
+/* DA7280_MEM_CTL2 (Address 0x2d) */
+#define DA7280_WAV_MEM_LOCK_SHIFT 7
+#define DA7280_WAV_MEM_LOCK_MASK BIT(7)
+
+/* DA7280_ADC_DATA_H1 (Address 0x2e) */
+#define DA7280_ADC_VDD_H_SHIFT 0
+#define DA7280_ADC_VDD_H_MASK (255 << 0)
+
+/* DA7280_ADC_DATA_L1 (Address 0x2f) */
+#define DA7280_ADC_VDD_L_SHIFT 0
+#define DA7280_ADC_VDD_L_MASK (127 << 0)
+
+/* DA7280_POLARITY (Address 0x43) */
+#define DA7280_POLARITY_SHIFT 0
+#define DA7280_POLARITY_MASK BIT(0)
+
+/* DA7280_LRA_AVR_H (Address 0x44) */
+#define DA7280_LRA_PER_AVERAGE_H_SHIFT 0
+#define DA7280_LRA_PER_AVERAGE_H_MASK (255 << 0)
+
+/* DA7280_LRA_AVR_L (Address 0x45) */
+#define DA7280_LRA_PER_AVERAGE_L_SHIFT 0
+#define DA7280_LRA_PER_AVERAGE_L_MASK (127 << 0)
+
+/* DA7280_FRQ_LRA_PER_ACT_H (Address 0x46) */
+#define DA7280_LRA_PER_ACTUAL_H_SHIFT 0
+#define DA7280_LRA_PER_ACTUAL_H_MASK (255 << 0)
+
+/* DA7280_FRQ_LRA_PER_ACT_L (Address 0x47) */
+#define DA7280_LRA_PER_ACTUAL_L_SHIFT 0
+#define DA7280_LRA_PER_ACTUAL_L_MASK (127 << 0)
+
+/* DA7280_FRQ_PHASE_H (Address 0x48) */
+#define DA7280_PHASE_DELAY_H_SHIFT 0
+#define DA7280_PHASE_DELAY_H_MASK (255 << 0)
+
+/* DA7280_FRQ_PHASE_L (Address 0x49) */
+#define DA7280_DELAY_SHIFT_L_SHIFT 0
+#define DA7280_DELAY_SHIFT_L_MASK (7 << 0)
+#define DA7280_DELAY_SHIFT_FREEZE_SHIFT 7
+#define DA7280_DELAY_SHIFT_FREEZE_MASK BIT(7)
+
+/* DA7280_FRQ_CTL (Address 0x4c) */
+#define DA7280_FREQ_TRACKING_FORCE_ON_SHIFT 0
+#define DA7280_FREQ_TRACKING_FORCE_ON_MASK BIT(0)
+#define DA7280_FREQ_TRACKING_AUTO_ADJ_SHIFT 1
+#define DA7280_FREQ_TRACKING_AUTO_ADJ_MASK BIT(1)
+
+/* DA7280_TRIM3 (Address 0x5f) */
+#define DA7280_REF_UVLO_THRES_SHIFT 3
+#define DA7280_REF_UVLO_THRES_MASK (3 << 3)
+#define DA7280_LOOP_FILT_LOW_BW_SHIFT 5
+#define DA7280_LOOP_FILT_LOW_BW_MASK BIT(5)
+#define DA7280_LOOP_IDAC_DOUBLE_RANGE_SHIFT 6
+#define DA7280_LOOP_IDAC_DOUBLE_RANGE_MASK BIT(6)
+
+/* DA7280_TRIM4 (Address 0x60) */
+#define DA7280_LOOP_FILT_RES_TRIM_SHIFT 0
+#define DA7280_LOOP_FILT_RES_TRIM_MASK (3 << 0)
+#define DA7280_LOOP_FILT_CAP_TRIM_SHIFT 2
+#define DA7280_LOOP_FILT_CAP_TRIM_MASK (3 << 2)
+
+/* DA7280_TRIM6 (Address 0x62) */
+#define DA7280_HBRIDGE_ERC_HS_TRIM_SHIFT 0
+#define DA7280_HBRIDGE_ERC_HS_TRIM_MASK (3 << 0)
+#define DA7280_HBRIDGE_ERC_LS_TRIM_SHIFT 2
+#define DA7280_HBRIDGE_ERC_LS_TRIM_MASK (3 << 2)
+
+/* DA7280_TOP_CFG5 (Address 0x6e) */
+#define DA7280_V2I_FACTOR_OFFSET_EN_SHIFT 0
+#define DA7280_V2I_FACTOR_OFFSET_EN_MASK BIT(0)
+#define DA7280_FRQ_PAUSE_ON_POLARITY_CHANGE_SHIFT 1
+#define DA7280_FRQ_PAUSE_ON_POLARITY_CHANGE_MASK BIT(1)
+#define DA7280_DELAY_BYPASS_SHIFT 2
+#define DA7280_DELAY_BYPASS_MASK BIT(2)
+
+/* DA7280_IRQ_EVENT_ACTUATOR_FAULT (Address 0x81) */
+#define DA7280_ADC_SAT_FAULT_SHIFT 2
+#define DA7280_ADC_SAT_FAULT_MASK BIT(2)
+
+/* DA7280_IRQ_STATUS2 (Address 0x82) */
+#define DA7280_STA_ADC_SAT_SHIFT 7
+#define DA7280_STA_ADC_SAT_MASK BIT(7)
+
+/* DA7280_IRQ_MASK2 (Address 0x83) */
+#define DA7280_ADC_SAT_M_SHIFT 7
+#define DA7280_ADC_SAT_M_MASK BIT(7)
+
+/* DA7280_SNP_MEM_XX (Address 0x84 ~ 0xe7) */
+#define DA7280_SNP_MEM_SHIFT 0
+#define DA7280_SNP_MEM_MASK (255 << 0)
+
+#endif
--
end-of-patch for PATCH V7
^ permalink raw reply related
* [PATCH V7 1/3] MAINTAINERS: da7280 updates to the Dialog Semiconductor search terms
From: Roy Im @ 2018-10-04 6:45 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring
Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
linux-kernel
In-Reply-To: <cover.1538635541.git.Roy.Im@diasemi.com>
This patch adds the da7280 bindings doc and driver to the Dialog
Semiconductor support list.
Signed-off-by: Roy Im <roy.im.opensource@diasemi.com>
---
v7: No changes.
v6: No changes.
v5: No changes.
v4: No changes.
v3: No changes.
v2: No changes.
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index b22e7fd..1af587f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4313,6 +4313,7 @@ S: Supported
F: Documentation/hwmon/da90??
F: Documentation/devicetree/bindings/mfd/da90*.txt
F: Documentation/devicetree/bindings/input/da90??-onkey.txt
+F: Documentation/devicetree/bindings/input/dlg,da72??.txt
F: Documentation/devicetree/bindings/thermal/da90??-thermal.txt
F: Documentation/devicetree/bindings/regulator/da92*.txt
F: Documentation/devicetree/bindings/watchdog/da90??-wdt.txt
@@ -4321,6 +4322,7 @@ F: drivers/gpio/gpio-da90??.c
F: drivers/hwmon/da90??-hwmon.c
F: drivers/iio/adc/da91??-*.c
F: drivers/input/misc/da90??_onkey.c
+F: drivers/input/misc/da72??.[ch]
F: drivers/input/touchscreen/da9052_tsi.c
F: drivers/leds/leds-da90??.c
F: drivers/mfd/da903x.c
--
end-of-patch for PATCH V7
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox