All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jeff LaBundy <jeff@labundy.com>
To: "dmitry.torokhov@gmail.com" <dmitry.torokhov@gmail.com>
Cc: "lee.jones@linaro.org" <lee.jones@linaro.org>,
	"thierry.reding@gmail.com" <thierry.reding@gmail.com>,
	"jic23@kernel.org" <jic23@kernel.org>,
	"devicetree@vger.kernel.org" <devicetree@vger.kernel.org>,
	"linux-input@vger.kernel.org" <linux-input@vger.kernel.org>,
	"u.kleine-koenig@pengutronix.de" <u.kleine-koenig@pengutronix.de>,
	"linux-pwm@vger.kernel.org" <linux-pwm@vger.kernel.org>,
	"knaack.h@gmx.de" <knaack.h@gmx.de>,
	"lars@metafoo.de" <lars@metafoo.de>,
	"pmeerw@pmeerw.net" <pmeerw@pmeerw.net>,
	"linux-iio@vger.kernel.org" <linux-iio@vger.kernel.org>,
	"robh+dt@kernel.org" <robh+dt@kernel.org>,
	"mark.rutland@arm.com" <mark.rutland@arm.com>
Subject: Re: [PATCH v4 3/7] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
Date: Sun, 19 Jan 2020 22:40:31 +0000	[thread overview]
Message-ID: <20200119224025.GA28865@labundy.com> (raw)
In-Reply-To: <20200117213330.GF47797@dtor-ws>

Hi Dmitry,

Thank you for your continued support on this project.

On Fri, Jan 17, 2020 at 01:33:30PM -0800, dmitry.torokhov@gmail.com wrote:
> Hi Jeff,
> 
> On Fri, Jan 17, 2020 at 02:35:46AM +0000, Jeff LaBundy wrote:
> > This patch adds key and switch support for the Azoteq IQS620A,
> > IQS621, IQS622, IQS624 and IQS625 multi-function sensors.
> > 
> > Signed-off-by: Jeff LaBundy <jeff@labundy.com>
> > ---
> > Changes in v4:
> >   - None
> > 
> > Changes in v3:
> >   - None
> > 
> > Changes in v2:
> >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> >   - Replaced 'error' with 'ret' throughout
> >   - Updated iqs62x_keys_parse_prop to use unified device property interface
> >   - Clarified the comment in iqs62x_keys_notifier to state that wheel up or
> >     down events elicit an emulated release cycle
> >   - Eliminated tabbed alignment of platform_driver struct members
> > 
> >  drivers/input/keyboard/Kconfig       |  10 ++
> >  drivers/input/keyboard/Makefile      |   1 +
> >  drivers/input/keyboard/iqs62x-keys.c | 340 +++++++++++++++++++++++++++++++++++
> >  3 files changed, 351 insertions(+)
> >  create mode 100644 drivers/input/keyboard/iqs62x-keys.c
> > 
> > diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> > index 4706ff0..28de965 100644
> > --- a/drivers/input/keyboard/Kconfig
> > +++ b/drivers/input/keyboard/Kconfig
> > @@ -663,6 +663,16 @@ config KEYBOARD_IPAQ_MICRO
> >  	  To compile this driver as a module, choose M here: the
> >  	  module will be called ipaq-micro-keys.
> > 
> > +config KEYBOARD_IQS62X
> > +	tristate "Azoteq IQS620A/621/622/624/625 keys and switches"
> > +	depends on MFD_IQS62X
> > +	help
> > +	  Say Y here to enable key and switch support for the Azoteq IQS620A,
> > +	  IQS621, IQS622, IQS624 and IQS625 multi-function sensors.
> > +
> > +	  To compile this driver as a module, choose M here: the module will
> > +	  be called iqs62x-keys.
> > +
> >  config KEYBOARD_OMAP
> >  	tristate "TI OMAP keypad support"
> >  	depends on ARCH_OMAP1
> > diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> > index f5b1752..1d689fd 100644
> > --- a/drivers/input/keyboard/Makefile
> > +++ b/drivers/input/keyboard/Makefile
> > @@ -28,6 +28,7 @@ obj-$(CONFIG_KEYBOARD_TCA8418)		+= tca8418_keypad.o
> >  obj-$(CONFIG_KEYBOARD_HIL)		+= hil_kbd.o
> >  obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
> >  obj-$(CONFIG_KEYBOARD_IPAQ_MICRO)	+= ipaq-micro-keys.o
> > +obj-$(CONFIG_KEYBOARD_IQS62X)		+= iqs62x-keys.o
> >  obj-$(CONFIG_KEYBOARD_IMX)		+= imx_keypad.o
> >  obj-$(CONFIG_KEYBOARD_IMX_SC_KEY)	+= imx_sc_key.o
> >  obj-$(CONFIG_KEYBOARD_HP6XX)		+= jornada680_kbd.o
> > diff --git a/drivers/input/keyboard/iqs62x-keys.c b/drivers/input/keyboard/iqs62x-keys.c
> > new file mode 100644
> > index 0000000..b477334
> > --- /dev/null
> > +++ b/drivers/input/keyboard/iqs62x-keys.c
> > @@ -0,0 +1,340 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Azoteq IQS620A/621/622/624/625 Keys and Switches
> > + *
> > + * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/input.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/iqs62x.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/property.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +
> > +enum {
> > +	IQS62X_SW_HALL_N,
> > +	IQS62X_SW_HALL_S,
> > +};
> > +
> > +static const char * const iqs62x_switch_names[] = {
> > +	[IQS62X_SW_HALL_N] = "hall-switch-north",
> > +	[IQS62X_SW_HALL_S] = "hall-switch-south",
> > +};
> > +
> > +struct iqs62x_switch_desc {
> > +	enum iqs62x_event_flag flag;
> > +	unsigned int code;
> > +	bool enabled;
> > +};
> > +
> > +struct iqs62x_keys_private {
> > +	struct iqs62x_core *iqs62x;
> > +	struct input_dev *input;
> > +	struct notifier_block notifier;
> > +	struct iqs62x_switch_desc switches[ARRAY_SIZE(iqs62x_switch_names)];
> > +	unsigned int keycode[IQS62X_NUM_KEYS];
> > +	unsigned int keycodemax;
> > +	u8 interval;
> > +};
> > +
> > +static int iqs62x_keys_parse_prop(struct platform_device *pdev,
> > +				  struct iqs62x_keys_private *iqs62x_keys)
> > +{
> > +	struct fwnode_handle *child;
> > +	unsigned int val;
> > +	int ret, i;
> > +
> > +	ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes",
> > +					     NULL, 0);
> 
> You can use device_property_count_u32().

Sure thing, will do.

> 
> > +	if (ret > IQS62X_NUM_KEYS) {
> > +		dev_err(&pdev->dev, "Too many keycodes present\n");
> > +		return -EINVAL;
> > +	} else if (ret < 0) {
> > +		dev_err(&pdev->dev, "Failed to count keycodes: %d\n", ret);
> > +		return ret;
> > +	}
> > +	iqs62x_keys->keycodemax = ret;
> > +
> > +	ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes",
> > +					     iqs62x_keys->keycode,
> > +					     iqs62x_keys->keycodemax);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Failed to read keycodes: %d\n", ret);
> > +		return ret;
> > +	}
> 
> I wonder why you can't simply use
> 
> 	error = device_property_read_u32_array(&pdev->dev, "linux,keycodes",
> 						iqs62x_keys->keycode,
> 						IQS62X_NUM_KEYS);
> 
> Are you concerned with someone trying to set up keys that are not
> actually exposed later via EVOCSKEYCODES and that is why you are
> limiting keycodemax?

When I try this, I find that device_property_read_u32_array returns -EOVERFLOW
for arrays with fewer than IQS62X_NUM_KEYS elements. To avoid forcing users to
pad the array all the way out to IQS62X_NUM_KEYS in the case of simple channel
assignments (like those in the example bindings), keycodemax must be passed to
device_property_read_u32_array which means it must be limited before-hand. The
same method seems to be used in other drivers as well (e.g. mpr121_touchkey).

> 
> > +
> > +	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) {
> > +		child = device_get_named_child_node(&pdev->dev,
> > +						    iqs62x_switch_names[i]);
> > +		if (!child)
> > +			continue;
> > +
> > +		ret = fwnode_property_read_u32(child, "linux,code", &val);
> > +		if (ret) {
> > +			dev_err(&pdev->dev, "Failed to read switch code: %d\n",
> > +				ret);
> > +			return ret;
> > +		}
> > +		iqs62x_keys->switches[i].code = val;
> > +		iqs62x_keys->switches[i].enabled = true;
> > +
> > +		if (fwnode_property_present(child, "azoteq,use-prox"))
> > +			iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ?
> > +							 IQS62X_EVENT_HALL_N_P :
> > +							 IQS62X_EVENT_HALL_S_P);
> > +		else
> > +			iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ?
> > +							 IQS62X_EVENT_HALL_N_T :
> > +							 IQS62X_EVENT_HALL_S_T);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int iqs62x_keys_init(struct iqs62x_keys_private *iqs62x_keys)
> > +{
> > +	struct iqs62x_core *iqs62x = iqs62x_keys->iqs62x;
> > +	enum iqs62x_event_flag flag;
> > +	unsigned int event_mask_reg;
> > +	unsigned int event_mask = 0;
> > +	unsigned int val;
> > +	int ret, i;
> > +
> > +	switch (iqs62x->dev_desc->prod_num) {
> > +	case IQS620_PROD_NUM:
> > +	case IQS621_PROD_NUM:
> > +	case IQS622_PROD_NUM:
> > +		event_mask_reg = IQS620_GLBL_EVENT_MASK;
> > +
> > +		/*
> > +		 * Discreet button, hysteresis and SAR UI flags represent keys
> > +		 * and are unmasked if mapped to a valid keycode.
> > +		 */
> > +		for (i = 0; i < iqs62x_keys->keycodemax; i++) {
> > +			if (iqs62x_keys->keycode[i] == KEY_RESERVED)
> > +				continue;
> > +
> > +			if (iqs62x_events[i].reg == IQS62X_EVENT_PROX)
> > +				event_mask |= iqs62x->dev_desc->prox_mask;
> > +			else if (iqs62x_events[i].reg == IQS62X_EVENT_HYST)
> > +				event_mask |= (iqs62x->dev_desc->hyst_mask |
> > +					       iqs62x->dev_desc->sar_mask);
> > +		}
> > +
> > +		ret = regmap_read(iqs62x->map, iqs62x->dev_desc->hall_flags,
> > +				  &val);
> > +		if (ret)
> > +			return ret;
> > +
> > +		/*
> > +		 * Hall UI flags represent switches and are unmasked if their
> > +		 * corresponding child nodes are present.
> > +		 */
> > +		for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) {
> > +			if (!(iqs62x_keys->switches[i].enabled))
> > +				continue;
> > +
> > +			flag = iqs62x_keys->switches[i].flag;
> > +
> > +			if (iqs62x_events[flag].reg != IQS62X_EVENT_HALL)
> > +				continue;
> > +
> > +			event_mask |= iqs62x->dev_desc->hall_mask;
> > +
> > +			input_report_switch(iqs62x_keys->input,
> > +					    iqs62x_keys->switches[i].code,
> > +					    (val & iqs62x_events[flag].mask) ==
> > +					    iqs62x_events[flag].val);
> > +		}
> > +
> > +		input_sync(iqs62x_keys->input);
> > +		break;
> > +
> > +	case IQS624_PROD_NUM:
> > +		event_mask_reg = IQS624_HALL_UI;
> > +
> > +		/*
> > +		 * Interval change events represent keys and are unmasked if
> > +		 * either wheel movement flag is mapped to a valid keycode.
> > +		 */
> > +		if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP] != KEY_RESERVED)
> > +			event_mask |= IQS624_HALL_UI_INT_EVENT;
> > +
> > +		if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN] != KEY_RESERVED)
> > +			event_mask |= IQS624_HALL_UI_INT_EVENT;
> > +
> > +		ret = regmap_read(iqs62x->map, iqs62x->dev_desc->interval,
> > +				  &val);
> > +		if (ret)
> > +			return ret;
> > +
> > +		iqs62x_keys->interval = val;
> > +		break;
> > +
> > +	default:
> > +		return 0;
> > +	}
> > +
> > +	return regmap_update_bits(iqs62x->map, event_mask_reg, event_mask, 0);
> > +}
> > +
> > +static int iqs62x_keys_notifier(struct notifier_block *notifier,
> > +				unsigned long event_flags, void *context)
> > +{
> > +	struct iqs62x_event_data *event_data = context;
> > +	struct iqs62x_keys_private *iqs62x_keys;
> > +	int ret, i;
> > +
> > +	iqs62x_keys = container_of(notifier, struct iqs62x_keys_private,
> > +				   notifier);
> > +
> > +	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
> > +		ret = iqs62x_keys_init(iqs62x_keys);
> > +		if (ret) {
> > +			dev_err(iqs62x_keys->input->dev.parent,
> > +				"Failed to re-initialize device: %d\n", ret);
> > +			return NOTIFY_BAD;
> > +		}
> > +
> > +		return NOTIFY_OK;
> > +	}
> > +
> > +	for (i = 0; i < iqs62x_keys->keycodemax; i++) {
> > +		if (iqs62x_events[i].reg == IQS62X_EVENT_WHEEL &&
> > +		    event_data->interval == iqs62x_keys->interval)
> > +			continue;
> > +
> > +		input_report_key(iqs62x_keys->input, iqs62x_keys->keycode[i],
> > +				 event_flags & BIT(i));
> > +	}
> > +
> > +	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++)
> > +		if (iqs62x_keys->switches[i].enabled)
> > +			input_report_switch(iqs62x_keys->input,
> > +					    iqs62x_keys->switches[i].code,
> > +					    event_flags &
> > +					    BIT(iqs62x_keys->switches[i].flag));
> > +
> > +	input_sync(iqs62x_keys->input);
> > +
> > +	if (event_data->interval == iqs62x_keys->interval)
> > +		return NOTIFY_OK;
> > +
> > +	/*
> > +	 * Each frame contains at most one wheel event (up or down), in which
> > +	 * case a complementary release cycle is emulated.
> > +	 */
> > +	if (event_flags & BIT(IQS62X_EVENT_WHEEL_UP)) {
> > +		input_report_key(iqs62x_keys->input,
> > +				 iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP],
> > +				 0);
> > +		input_sync(iqs62x_keys->input);
> > +	} else if (event_flags & BIT(IQS62X_EVENT_WHEEL_DN)) {
> > +		input_report_key(iqs62x_keys->input,
> > +				 iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN],
> > +				 0);
> > +		input_sync(iqs62x_keys->input);
> > +	}
> > +
> > +	iqs62x_keys->interval = event_data->interval;
> > +
> > +	return NOTIFY_OK;
> > +}
> > +
> > +static int iqs62x_keys_probe(struct platform_device *pdev)
> > +{
> > +	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
> > +	struct iqs62x_keys_private *iqs62x_keys;
> > +	struct input_dev *input;
> > +	int ret, i;
> > +
> > +	iqs62x_keys = devm_kzalloc(&pdev->dev, sizeof(*iqs62x_keys),
> > +				   GFP_KERNEL);
> > +	if (!iqs62x_keys)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, iqs62x_keys);
> > +
> > +	ret = iqs62x_keys_parse_prop(pdev, iqs62x_keys);
> > +	if (ret)
> > +		return ret;
> > +
> > +	input = devm_input_allocate_device(&pdev->dev);
> > +	if (!input)
> > +		return -ENOMEM;
> > +
> > +	input->keycodemax = iqs62x_keys->keycodemax;
> > +	input->keycode = iqs62x_keys->keycode;
> > +	input->keycodesize = sizeof(*iqs62x_keys->keycode);
> > +
> > +	input->name = iqs62x->dev_desc->dev_name;
> > +	input->id.bustype = BUS_I2C;
> > +
> > +	__set_bit(EV_KEY, input->evbit);
> > +
> > +	for (i = 0; i < iqs62x_keys->keycodemax; i++)
> > +		__set_bit(iqs62x_keys->keycode[i], input->keybit);
> > +
> > +	__clear_bit(KEY_RESERVED, input->keybit);

It occurs to me that we can also collapse the __set_bit ... __clear_bit logic
above into the following:

for (i = 0; i < iqs62x_keys->keycodemax; i++)
	if (iqs62x_keys->keycode[i] != KEY_RESERVED)
		input_set_capability(input, EV_KEY, iqs62x_keys->keycode[i]);

Since this seems straightforward enough, I plan on sneaking it into v5 along
with the change below while keeping your Acked-by. If you have any objection,
please let me know.

> > +
> > +	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++)
> > +		if (iqs62x_keys->switches[i].enabled) {
> > +			__set_bit(EV_SW, input->evbit);
> > +			__set_bit(iqs62x_keys->switches[i].code, input->swbit);
> 
> input_set_capability(input, EV_SW, iqs62x_keys->switches[i].code)

Sure thing, will do.

> 
> > +		}
> > +
> > +	iqs62x_keys->iqs62x = iqs62x;
> > +	iqs62x_keys->input = input;
> > +
> > +	ret = iqs62x_keys_init(iqs62x_keys);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Failed to initialize device: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = input_register_device(iqs62x_keys->input);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Failed to register device: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	iqs62x_keys->notifier.notifier_call = iqs62x_keys_notifier;
> > +	ret = blocking_notifier_chain_register(&iqs62x_keys->iqs62x->nh,
> > +					       &iqs62x_keys->notifier);
> > +	if (ret)
> > +		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int iqs62x_keys_remove(struct platform_device *pdev)
> > +{
> > +	struct iqs62x_keys_private *iqs62x_keys = platform_get_drvdata(pdev);
> > +	int ret;
> > +
> > +	ret = blocking_notifier_chain_unregister(&iqs62x_keys->iqs62x->nh,
> > +						 &iqs62x_keys->notifier);
> > +	if (ret)
> > +		dev_err(&pdev->dev, "Failed to unregister notifier: %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static struct platform_driver iqs62x_keys_platform_driver = {
> > +	.driver = {
> > +		.name = IQS62X_DRV_NAME_KEYS,
> > +	},
> > +	.probe = iqs62x_keys_probe,
> > +	.remove = iqs62x_keys_remove,
> > +};
> > +module_platform_driver(iqs62x_keys_platform_driver);
> > +
> > +MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
> > +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Keys and Switches");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" IQS62X_DRV_NAME_KEYS);
> 
> Otherwise
> 
> Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> 
> I suppose it will be merged through MFD?

That's the plan; Lee confirmed this would be OK once the series is ready. Just
as a heads up, I expect minor changes to this and other patches as iqs62x.h is
hardened (e.g. "iqs62x->map" --> "iqs62x->regmap"). I assume you're OK with me
keeping your Acked-by unless there are major changes, but let me know if you'd
prefer I didn't.

> 
> Thanks.
> 
> -- 
> Dmitry

Kind regards,
Jeff LaBundy

WARNING: multiple messages have this Message-ID (diff)
From: Jeff LaBundy <jeff-Sk+WRT7NHmFBDgjK7y7TUQ@public.gmane.org>
To: "dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org"
	<dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: "lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org"
	<lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>,
	"thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org"
	<thierry.reding-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	"jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org"
	<jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>,
	"devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org"
	<devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	"linux-input-u79uwXL29TY76Z2rM5mHXA@public.gmane.org"
	<linux-input-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	"u.kleine-koenig-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org"
	<u.kleine-koenig-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>,
	"linux-pwm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org"
	<linux-pwm-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	"knaack.h-Mmb7MZpHnFY@public.gmane.org"
	<knaack.h-Mmb7MZpHnFY@public.gmane.org>,
	"lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org"
	<lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org>,
	"pmeerw-jW+XmwGofnusTnJN9+BGXg@public.gmane.org"
	<pmeerw-jW+XmwGofnusTnJN9+BGXg@public.gmane.org>,
	"linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org"
	<linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	"robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org"
	<robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>,
	"mark.rutland-5wv7dgnIgG8@public.gmane.org"
	<mark.rutland-5wv7dgnIgG8@public.gmane.org>
Subject: Re: [PATCH v4 3/7] input: keyboard: Add support for Azoteq IQS620A/621/622/624/625
Date: Sun, 19 Jan 2020 22:40:31 +0000	[thread overview]
Message-ID: <20200119224025.GA28865@labundy.com> (raw)
In-Reply-To: <20200117213330.GF47797@dtor-ws>

Hi Dmitry,

Thank you for your continued support on this project.

On Fri, Jan 17, 2020 at 01:33:30PM -0800, dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:
> Hi Jeff,
> 
> On Fri, Jan 17, 2020 at 02:35:46AM +0000, Jeff LaBundy wrote:
> > This patch adds key and switch support for the Azoteq IQS620A,
> > IQS621, IQS622, IQS624 and IQS625 multi-function sensors.
> > 
> > Signed-off-by: Jeff LaBundy <jeff-Sk+WRT7NHmFBDgjK7y7TUQ@public.gmane.org>
> > ---
> > Changes in v4:
> >   - None
> > 
> > Changes in v3:
> >   - None
> > 
> > Changes in v2:
> >   - Merged 'Copyright' and 'Author' lines into one in introductory comments
> >   - Replaced 'error' with 'ret' throughout
> >   - Updated iqs62x_keys_parse_prop to use unified device property interface
> >   - Clarified the comment in iqs62x_keys_notifier to state that wheel up or
> >     down events elicit an emulated release cycle
> >   - Eliminated tabbed alignment of platform_driver struct members
> > 
> >  drivers/input/keyboard/Kconfig       |  10 ++
> >  drivers/input/keyboard/Makefile      |   1 +
> >  drivers/input/keyboard/iqs62x-keys.c | 340 +++++++++++++++++++++++++++++++++++
> >  3 files changed, 351 insertions(+)
> >  create mode 100644 drivers/input/keyboard/iqs62x-keys.c
> > 
> > diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
> > index 4706ff0..28de965 100644
> > --- a/drivers/input/keyboard/Kconfig
> > +++ b/drivers/input/keyboard/Kconfig
> > @@ -663,6 +663,16 @@ config KEYBOARD_IPAQ_MICRO
> >  	  To compile this driver as a module, choose M here: the
> >  	  module will be called ipaq-micro-keys.
> > 
> > +config KEYBOARD_IQS62X
> > +	tristate "Azoteq IQS620A/621/622/624/625 keys and switches"
> > +	depends on MFD_IQS62X
> > +	help
> > +	  Say Y here to enable key and switch support for the Azoteq IQS620A,
> > +	  IQS621, IQS622, IQS624 and IQS625 multi-function sensors.
> > +
> > +	  To compile this driver as a module, choose M here: the module will
> > +	  be called iqs62x-keys.
> > +
> >  config KEYBOARD_OMAP
> >  	tristate "TI OMAP keypad support"
> >  	depends on ARCH_OMAP1
> > diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
> > index f5b1752..1d689fd 100644
> > --- a/drivers/input/keyboard/Makefile
> > +++ b/drivers/input/keyboard/Makefile
> > @@ -28,6 +28,7 @@ obj-$(CONFIG_KEYBOARD_TCA8418)		+= tca8418_keypad.o
> >  obj-$(CONFIG_KEYBOARD_HIL)		+= hil_kbd.o
> >  obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
> >  obj-$(CONFIG_KEYBOARD_IPAQ_MICRO)	+= ipaq-micro-keys.o
> > +obj-$(CONFIG_KEYBOARD_IQS62X)		+= iqs62x-keys.o
> >  obj-$(CONFIG_KEYBOARD_IMX)		+= imx_keypad.o
> >  obj-$(CONFIG_KEYBOARD_IMX_SC_KEY)	+= imx_sc_key.o
> >  obj-$(CONFIG_KEYBOARD_HP6XX)		+= jornada680_kbd.o
> > diff --git a/drivers/input/keyboard/iqs62x-keys.c b/drivers/input/keyboard/iqs62x-keys.c
> > new file mode 100644
> > index 0000000..b477334
> > --- /dev/null
> > +++ b/drivers/input/keyboard/iqs62x-keys.c
> > @@ -0,0 +1,340 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Azoteq IQS620A/621/622/624/625 Keys and Switches
> > + *
> > + * Copyright (C) 2019 Jeff LaBundy <jeff-Sk+WRT7NHmFBDgjK7y7TUQ@public.gmane.org>
> > + */
> > +
> > +#include <linux/device.h>
> > +#include <linux/input.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/iqs62x.h>
> > +#include <linux/module.h>
> > +#include <linux/notifier.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/property.h>
> > +#include <linux/regmap.h>
> > +#include <linux/slab.h>
> > +
> > +enum {
> > +	IQS62X_SW_HALL_N,
> > +	IQS62X_SW_HALL_S,
> > +};
> > +
> > +static const char * const iqs62x_switch_names[] = {
> > +	[IQS62X_SW_HALL_N] = "hall-switch-north",
> > +	[IQS62X_SW_HALL_S] = "hall-switch-south",
> > +};
> > +
> > +struct iqs62x_switch_desc {
> > +	enum iqs62x_event_flag flag;
> > +	unsigned int code;
> > +	bool enabled;
> > +};
> > +
> > +struct iqs62x_keys_private {
> > +	struct iqs62x_core *iqs62x;
> > +	struct input_dev *input;
> > +	struct notifier_block notifier;
> > +	struct iqs62x_switch_desc switches[ARRAY_SIZE(iqs62x_switch_names)];
> > +	unsigned int keycode[IQS62X_NUM_KEYS];
> > +	unsigned int keycodemax;
> > +	u8 interval;
> > +};
> > +
> > +static int iqs62x_keys_parse_prop(struct platform_device *pdev,
> > +				  struct iqs62x_keys_private *iqs62x_keys)
> > +{
> > +	struct fwnode_handle *child;
> > +	unsigned int val;
> > +	int ret, i;
> > +
> > +	ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes",
> > +					     NULL, 0);
> 
> You can use device_property_count_u32().

Sure thing, will do.

> 
> > +	if (ret > IQS62X_NUM_KEYS) {
> > +		dev_err(&pdev->dev, "Too many keycodes present\n");
> > +		return -EINVAL;
> > +	} else if (ret < 0) {
> > +		dev_err(&pdev->dev, "Failed to count keycodes: %d\n", ret);
> > +		return ret;
> > +	}
> > +	iqs62x_keys->keycodemax = ret;
> > +
> > +	ret = device_property_read_u32_array(&pdev->dev, "linux,keycodes",
> > +					     iqs62x_keys->keycode,
> > +					     iqs62x_keys->keycodemax);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Failed to read keycodes: %d\n", ret);
> > +		return ret;
> > +	}
> 
> I wonder why you can't simply use
> 
> 	error = device_property_read_u32_array(&pdev->dev, "linux,keycodes",
> 						iqs62x_keys->keycode,
> 						IQS62X_NUM_KEYS);
> 
> Are you concerned with someone trying to set up keys that are not
> actually exposed later via EVOCSKEYCODES and that is why you are
> limiting keycodemax?

When I try this, I find that device_property_read_u32_array returns -EOVERFLOW
for arrays with fewer than IQS62X_NUM_KEYS elements. To avoid forcing users to
pad the array all the way out to IQS62X_NUM_KEYS in the case of simple channel
assignments (like those in the example bindings), keycodemax must be passed to
device_property_read_u32_array which means it must be limited before-hand. The
same method seems to be used in other drivers as well (e.g. mpr121_touchkey).

> 
> > +
> > +	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) {
> > +		child = device_get_named_child_node(&pdev->dev,
> > +						    iqs62x_switch_names[i]);
> > +		if (!child)
> > +			continue;
> > +
> > +		ret = fwnode_property_read_u32(child, "linux,code", &val);
> > +		if (ret) {
> > +			dev_err(&pdev->dev, "Failed to read switch code: %d\n",
> > +				ret);
> > +			return ret;
> > +		}
> > +		iqs62x_keys->switches[i].code = val;
> > +		iqs62x_keys->switches[i].enabled = true;
> > +
> > +		if (fwnode_property_present(child, "azoteq,use-prox"))
> > +			iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ?
> > +							 IQS62X_EVENT_HALL_N_P :
> > +							 IQS62X_EVENT_HALL_S_P);
> > +		else
> > +			iqs62x_keys->switches[i].flag = (i == IQS62X_SW_HALL_N ?
> > +							 IQS62X_EVENT_HALL_N_T :
> > +							 IQS62X_EVENT_HALL_S_T);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int iqs62x_keys_init(struct iqs62x_keys_private *iqs62x_keys)
> > +{
> > +	struct iqs62x_core *iqs62x = iqs62x_keys->iqs62x;
> > +	enum iqs62x_event_flag flag;
> > +	unsigned int event_mask_reg;
> > +	unsigned int event_mask = 0;
> > +	unsigned int val;
> > +	int ret, i;
> > +
> > +	switch (iqs62x->dev_desc->prod_num) {
> > +	case IQS620_PROD_NUM:
> > +	case IQS621_PROD_NUM:
> > +	case IQS622_PROD_NUM:
> > +		event_mask_reg = IQS620_GLBL_EVENT_MASK;
> > +
> > +		/*
> > +		 * Discreet button, hysteresis and SAR UI flags represent keys
> > +		 * and are unmasked if mapped to a valid keycode.
> > +		 */
> > +		for (i = 0; i < iqs62x_keys->keycodemax; i++) {
> > +			if (iqs62x_keys->keycode[i] == KEY_RESERVED)
> > +				continue;
> > +
> > +			if (iqs62x_events[i].reg == IQS62X_EVENT_PROX)
> > +				event_mask |= iqs62x->dev_desc->prox_mask;
> > +			else if (iqs62x_events[i].reg == IQS62X_EVENT_HYST)
> > +				event_mask |= (iqs62x->dev_desc->hyst_mask |
> > +					       iqs62x->dev_desc->sar_mask);
> > +		}
> > +
> > +		ret = regmap_read(iqs62x->map, iqs62x->dev_desc->hall_flags,
> > +				  &val);
> > +		if (ret)
> > +			return ret;
> > +
> > +		/*
> > +		 * Hall UI flags represent switches and are unmasked if their
> > +		 * corresponding child nodes are present.
> > +		 */
> > +		for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++) {
> > +			if (!(iqs62x_keys->switches[i].enabled))
> > +				continue;
> > +
> > +			flag = iqs62x_keys->switches[i].flag;
> > +
> > +			if (iqs62x_events[flag].reg != IQS62X_EVENT_HALL)
> > +				continue;
> > +
> > +			event_mask |= iqs62x->dev_desc->hall_mask;
> > +
> > +			input_report_switch(iqs62x_keys->input,
> > +					    iqs62x_keys->switches[i].code,
> > +					    (val & iqs62x_events[flag].mask) ==
> > +					    iqs62x_events[flag].val);
> > +		}
> > +
> > +		input_sync(iqs62x_keys->input);
> > +		break;
> > +
> > +	case IQS624_PROD_NUM:
> > +		event_mask_reg = IQS624_HALL_UI;
> > +
> > +		/*
> > +		 * Interval change events represent keys and are unmasked if
> > +		 * either wheel movement flag is mapped to a valid keycode.
> > +		 */
> > +		if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP] != KEY_RESERVED)
> > +			event_mask |= IQS624_HALL_UI_INT_EVENT;
> > +
> > +		if (iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN] != KEY_RESERVED)
> > +			event_mask |= IQS624_HALL_UI_INT_EVENT;
> > +
> > +		ret = regmap_read(iqs62x->map, iqs62x->dev_desc->interval,
> > +				  &val);
> > +		if (ret)
> > +			return ret;
> > +
> > +		iqs62x_keys->interval = val;
> > +		break;
> > +
> > +	default:
> > +		return 0;
> > +	}
> > +
> > +	return regmap_update_bits(iqs62x->map, event_mask_reg, event_mask, 0);
> > +}
> > +
> > +static int iqs62x_keys_notifier(struct notifier_block *notifier,
> > +				unsigned long event_flags, void *context)
> > +{
> > +	struct iqs62x_event_data *event_data = context;
> > +	struct iqs62x_keys_private *iqs62x_keys;
> > +	int ret, i;
> > +
> > +	iqs62x_keys = container_of(notifier, struct iqs62x_keys_private,
> > +				   notifier);
> > +
> > +	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
> > +		ret = iqs62x_keys_init(iqs62x_keys);
> > +		if (ret) {
> > +			dev_err(iqs62x_keys->input->dev.parent,
> > +				"Failed to re-initialize device: %d\n", ret);
> > +			return NOTIFY_BAD;
> > +		}
> > +
> > +		return NOTIFY_OK;
> > +	}
> > +
> > +	for (i = 0; i < iqs62x_keys->keycodemax; i++) {
> > +		if (iqs62x_events[i].reg == IQS62X_EVENT_WHEEL &&
> > +		    event_data->interval == iqs62x_keys->interval)
> > +			continue;
> > +
> > +		input_report_key(iqs62x_keys->input, iqs62x_keys->keycode[i],
> > +				 event_flags & BIT(i));
> > +	}
> > +
> > +	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++)
> > +		if (iqs62x_keys->switches[i].enabled)
> > +			input_report_switch(iqs62x_keys->input,
> > +					    iqs62x_keys->switches[i].code,
> > +					    event_flags &
> > +					    BIT(iqs62x_keys->switches[i].flag));
> > +
> > +	input_sync(iqs62x_keys->input);
> > +
> > +	if (event_data->interval == iqs62x_keys->interval)
> > +		return NOTIFY_OK;
> > +
> > +	/*
> > +	 * Each frame contains at most one wheel event (up or down), in which
> > +	 * case a complementary release cycle is emulated.
> > +	 */
> > +	if (event_flags & BIT(IQS62X_EVENT_WHEEL_UP)) {
> > +		input_report_key(iqs62x_keys->input,
> > +				 iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_UP],
> > +				 0);
> > +		input_sync(iqs62x_keys->input);
> > +	} else if (event_flags & BIT(IQS62X_EVENT_WHEEL_DN)) {
> > +		input_report_key(iqs62x_keys->input,
> > +				 iqs62x_keys->keycode[IQS62X_EVENT_WHEEL_DN],
> > +				 0);
> > +		input_sync(iqs62x_keys->input);
> > +	}
> > +
> > +	iqs62x_keys->interval = event_data->interval;
> > +
> > +	return NOTIFY_OK;
> > +}
> > +
> > +static int iqs62x_keys_probe(struct platform_device *pdev)
> > +{
> > +	struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
> > +	struct iqs62x_keys_private *iqs62x_keys;
> > +	struct input_dev *input;
> > +	int ret, i;
> > +
> > +	iqs62x_keys = devm_kzalloc(&pdev->dev, sizeof(*iqs62x_keys),
> > +				   GFP_KERNEL);
> > +	if (!iqs62x_keys)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, iqs62x_keys);
> > +
> > +	ret = iqs62x_keys_parse_prop(pdev, iqs62x_keys);
> > +	if (ret)
> > +		return ret;
> > +
> > +	input = devm_input_allocate_device(&pdev->dev);
> > +	if (!input)
> > +		return -ENOMEM;
> > +
> > +	input->keycodemax = iqs62x_keys->keycodemax;
> > +	input->keycode = iqs62x_keys->keycode;
> > +	input->keycodesize = sizeof(*iqs62x_keys->keycode);
> > +
> > +	input->name = iqs62x->dev_desc->dev_name;
> > +	input->id.bustype = BUS_I2C;
> > +
> > +	__set_bit(EV_KEY, input->evbit);
> > +
> > +	for (i = 0; i < iqs62x_keys->keycodemax; i++)
> > +		__set_bit(iqs62x_keys->keycode[i], input->keybit);
> > +
> > +	__clear_bit(KEY_RESERVED, input->keybit);

It occurs to me that we can also collapse the __set_bit ... __clear_bit logic
above into the following:

for (i = 0; i < iqs62x_keys->keycodemax; i++)
	if (iqs62x_keys->keycode[i] != KEY_RESERVED)
		input_set_capability(input, EV_KEY, iqs62x_keys->keycode[i]);

Since this seems straightforward enough, I plan on sneaking it into v5 along
with the change below while keeping your Acked-by. If you have any objection,
please let me know.

> > +
> > +	for (i = 0; i < ARRAY_SIZE(iqs62x_keys->switches); i++)
> > +		if (iqs62x_keys->switches[i].enabled) {
> > +			__set_bit(EV_SW, input->evbit);
> > +			__set_bit(iqs62x_keys->switches[i].code, input->swbit);
> 
> input_set_capability(input, EV_SW, iqs62x_keys->switches[i].code)

Sure thing, will do.

> 
> > +		}
> > +
> > +	iqs62x_keys->iqs62x = iqs62x;
> > +	iqs62x_keys->input = input;
> > +
> > +	ret = iqs62x_keys_init(iqs62x_keys);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Failed to initialize device: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = input_register_device(iqs62x_keys->input);
> > +	if (ret) {
> > +		dev_err(&pdev->dev, "Failed to register device: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	iqs62x_keys->notifier.notifier_call = iqs62x_keys_notifier;
> > +	ret = blocking_notifier_chain_register(&iqs62x_keys->iqs62x->nh,
> > +					       &iqs62x_keys->notifier);
> > +	if (ret)
> > +		dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int iqs62x_keys_remove(struct platform_device *pdev)
> > +{
> > +	struct iqs62x_keys_private *iqs62x_keys = platform_get_drvdata(pdev);
> > +	int ret;
> > +
> > +	ret = blocking_notifier_chain_unregister(&iqs62x_keys->iqs62x->nh,
> > +						 &iqs62x_keys->notifier);
> > +	if (ret)
> > +		dev_err(&pdev->dev, "Failed to unregister notifier: %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static struct platform_driver iqs62x_keys_platform_driver = {
> > +	.driver = {
> > +		.name = IQS62X_DRV_NAME_KEYS,
> > +	},
> > +	.probe = iqs62x_keys_probe,
> > +	.remove = iqs62x_keys_remove,
> > +};
> > +module_platform_driver(iqs62x_keys_platform_driver);
> > +
> > +MODULE_AUTHOR("Jeff LaBundy <jeff-Sk+WRT7NHmFBDgjK7y7TUQ@public.gmane.org>");
> > +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Keys and Switches");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS("platform:" IQS62X_DRV_NAME_KEYS);
> 
> Otherwise
> 
> Acked-by: Dmitry Torokhov <dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> 
> I suppose it will be merged through MFD?

That's the plan; Lee confirmed this would be OK once the series is ready. Just
as a heads up, I expect minor changes to this and other patches as iqs62x.h is
hardened (e.g. "iqs62x->map" --> "iqs62x->regmap"). I assume you're OK with me
keeping your Acked-by unless there are major changes, but let me know if you'd
prefer I didn't.

> 
> Thanks.
> 
> -- 
> Dmitry

Kind regards,
Jeff LaBundy

  reply	other threads:[~2020-01-19 22:40 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-01-17  2:35 [PATCH v4 0/7] Add support for Azoteq IQS620A/621/622/624/625 Jeff LaBundy
2020-01-17  2:35 ` Jeff LaBundy
2020-01-17  2:35 ` [PATCH v4 1/7] dt-bindings: Add bindings " Jeff LaBundy
2020-01-17  2:35   ` Jeff LaBundy
2020-01-17  2:35 ` [PATCH v4 2/7] mfd: Add support " Jeff LaBundy
2020-01-17  2:35   ` Jeff LaBundy
2020-01-17 13:21   ` Lee Jones
2020-01-20  4:23     ` Jeff LaBundy
2020-01-20  8:00       ` Lee Jones
2020-01-20  8:00         ` Lee Jones
2020-01-22  4:00         ` Jeff LaBundy
2020-01-17  2:35 ` [PATCH v4 3/7] input: keyboard: " Jeff LaBundy
2020-01-17  2:35   ` Jeff LaBundy
2020-01-17 21:33   ` dmitry.torokhov
2020-01-19 22:40     ` Jeff LaBundy [this message]
2020-01-19 22:40       ` Jeff LaBundy
2020-01-21  6:55       ` dmitry.torokhov
2020-01-17  2:35 ` [PATCH v4 4/7] pwm: Add support for Azoteq IQS620A PWM generator Jeff LaBundy
2020-01-17  7:34   ` Uwe Kleine-König
2020-01-17  7:34     ` Uwe Kleine-König
2020-01-19 23:32     ` Jeff LaBundy
2020-01-19 23:32       ` Jeff LaBundy
2020-01-20  7:27       ` Uwe Kleine-König
2020-01-20  7:27         ` Uwe Kleine-König
2020-01-22  3:56         ` Jeff LaBundy
2020-01-22  7:02           ` Uwe Kleine-König
2020-01-22  7:02             ` Uwe Kleine-König
2020-01-17  2:36 ` [PATCH v4 5/7] iio: temperature: Add support for Azoteq IQS620AT temperature sensor Jeff LaBundy
2020-01-17  2:36   ` Jeff LaBundy
2020-01-22  3:28   ` Jeff LaBundy
2020-01-28  9:10     ` Jonathan Cameron
2020-01-28  9:10       ` Jonathan Cameron
2020-01-17  2:36 ` [PATCH v4 6/7] iio: light: Add support for Azoteq IQS621/622 ambient light sensors Jeff LaBundy
2020-01-17  2:36   ` Jeff LaBundy
2020-01-17  2:36 ` [PATCH v4 7/7] iio: position: Add support for Azoteq IQS624/625 angle sensors Jeff LaBundy
2020-01-17  2:36   ` Jeff LaBundy

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200119224025.GA28865@labundy.com \
    --to=jeff@labundy.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=jic23@kernel.org \
    --cc=knaack.h@gmx.de \
    --cc=lars@metafoo.de \
    --cc=lee.jones@linaro.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=pmeerw@pmeerw.net \
    --cc=robh+dt@kernel.org \
    --cc=thierry.reding@gmail.com \
    --cc=u.kleine-koenig@pengutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.