* [PATCH] Fixes kernel panic with Null pointer in hid-appleir.c
From: Nicholas Krause @ 2014-06-20 16:25 UTC (permalink / raw)
To: jkosina; +Cc: linux-input, linux-kernel
In for loop of function appleir_input_configured we hit
a Null pointer after the for loop due to array_size not
being correct needs to be changed to input_dev->keycodemax.
Signed-off-by: Nicholas Krause <xerofoify@gmail.com>
---
drivers/hid/hid-appleir.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/hid/hid-appleir.c b/drivers/hid/hid-appleir.c
index 0e6a42d..ab0a702 100644
--- a/drivers/hid/hid-appleir.c
+++ b/drivers/hid/hid-appleir.c
@@ -272,7 +272,7 @@ static void appleir_input_configured(struct hid_device *hid,
input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
memcpy(appleir->keymap, appleir_key_table, sizeof(appleir->keymap));
- for (i = 0; i < ARRAY_SIZE(appleir_key_table); i++)
+ for (i = 0; i < ARRAY_SIZE(input_dev->keycodemax); i++)
set_bit(appleir->keymap[i], input_dev->keybit);
clear_bit(KEY_RESERVED, input_dev->keybit);
}
--
1.9.1
^ permalink raw reply related
* [PATCH] Input: synaptics - give touchpad 1 second to settle after S2R
From: Ahmet Inan @ 2014-06-21 8:33 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Ahmet Inan
Since we are resuming now in 0.5 Seconds, we have to give the touchpad
some time to settle before issuing a psmouse_reset.
This fixes the endless reconnect / resync cycle after resuming from S2R.
Signed-off-by: Ahmet Inan <ainan@mathematik.uni-freiburg.de>
---
drivers/input/mouse/synaptics.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
index c5ec703..059fca0 100644
--- a/drivers/input/mouse/synaptics.c
+++ b/drivers/input/mouse/synaptics.c
@@ -1473,6 +1473,7 @@ static int synaptics_reconnect(struct psmouse *psmouse)
int retry = 0;
int error;
+ ssleep(1);
do {
psmouse_reset(psmouse);
if (retry) {
--
1.8.5.5
^ permalink raw reply related
* Re: Re: [PATCH v6 3/7] mfd: AXP20x: Add bindings documentation
From: Carlo Caione @ 2014-06-21 8:52 UTC (permalink / raw)
To: Lee Jones
Cc: Boris BREZILLON, Carlo Caione,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
hdegoede-H+wXaHxf7aLQT0dZR+AlfA, emilio-0Z03zUJReD5OxF6Tv1QG9Q,
wens-jdAy2FN1RRM, sameo-VuQAYsv1563Yd54FQh9/CA,
dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA,
lgirdwood-Re5JQEeQqe8AvxtiuMwx3w, broonie-DgEjT+Ai2ygdnm+yROfE0A,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <20140618090616.GA23945@lee--X1>
On Wed, Jun 18, 2014 at 11:06 AM, Lee Jones <lee.jones-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org> wrote:
>> On 19/05/2014 21:47, Carlo Caione wrote:
>> > Bindings documentation for the AXP20x driver. In this file also
>> > sub-nodes are documented.
>>
>> I think this patch has been forgotten (I didn't find it in linus' tree
>> or linux-next tree).
>
> This patch-set became confusing. Some of the patches were applied in
> v5 and were still submitted for v6. I need Carlo to rebase on top of
> v3.16-rc1+ and re-submit with the Acks he's collected so far.
I'm confused too. At the time of the submission v6 was already the
patchset without the patches from v5 already applied and v6 already
contains all the ACKs I have collected (i.e. the input/misc driver has
been already ack-ed by Dmitry, yet it seems not merged).
So what exactly do you want me to resubmit? The whole v6 on top of v3.16-rc1+?
--
Carlo Caione
^ permalink raw reply
* Re: [PATCH v2 1/6] mfd: fsl imx25 Touchscreen ADC driver
From: Jonathan Cameron @ 2014-06-21 10:09 UTC (permalink / raw)
To: Denis Carikli, Shawn Guo, Samuel Ortiz, Dmitry Torokhov
Cc: Eric Bénard, Sascha Hauer,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Lee Jones,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA, Fabio Estevam,
Lars-Peter Clausen, Markus Pargmann
In-Reply-To: <1402672899-6995-2-git-send-email-denis-fO0SIAKYzcbQT0dZR+AlfA@public.gmane.org>
On 13/06/14 16:21, Denis Carikli wrote:
> From: Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
>
> This is the core driver for imx25 touchscreen/adc driver. The module
> has one shared ADC and two different conversion queues which use the
> ADC. The two queues are identical. Both can be used for general purpose
> ADC but one is meant to be used for touchscreens.
>
> This driver is the core which manages the central components and
> registers of the TSC/ADC unit. It manages the IRQs and forwards them to
> the correct components.
>
> Signed-off-by: Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> Signed-off-by: Denis Carikli <denis-fO0SIAKYzcbQT0dZR+AlfA@public.gmane.org>
Hi,
A couple of trivial bits inline. Otherwise,
Acked-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Note I have not dived into the datasheet for this review
(hence not a reviewed by!) Too many other things to do today!
> ---
> .../devicetree/bindings/mfd/fsl-imx25-tsadc.txt | 46 ++++
> drivers/mfd/Kconfig | 9 +
> drivers/mfd/Makefile | 2 +
> drivers/mfd/fsl-imx25-tsadc.c | 232 ++++++++++++++++++++
> include/linux/mfd/imx25-tsadc.h | 138 ++++++++++++
> 5 files changed, 427 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/fsl-imx25-tsadc.txt
> create mode 100644 drivers/mfd/fsl-imx25-tsadc.c
> create mode 100644 include/linux/mfd/imx25-tsadc.h
>
> diff --git a/Documentation/devicetree/bindings/mfd/fsl-imx25-tsadc.txt b/Documentation/devicetree/bindings/mfd/fsl-imx25-tsadc.txt
> new file mode 100644
> index 0000000..a857af0e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/fsl-imx25-tsadc.txt
> @@ -0,0 +1,46 @@
> +Freescale mx25 ADC/TSC multifunction device
> +
> +This device combines two general purpose conversion queues one used for general
> +ADC and the other used for touchscreens.
> +
> +Required properties:
> + - compatible: Should be "fsl,imx25-tsadc".
> + - reg: Memory range of the device.
> + - interrupts: Interrupt for this device as described in
> + interrupts/interrupts.txt
> + - clocks: An 'ipg' clock defined as described in clocks/clock.txt
> + - interrupt-controller: This device is an interrupt controller. It controls
> + the interrupts of both conversion queues.
> + - #interrupt-cells: Should be '<1>'.
> + - #address-cells: Should be '<1>'.
> + - #size-cells: Should be '<1>'.
> + - ranges
> +
> +This device includes two conversion queues which can be added as subnodes.
> +The first queue is for the touchscreen, the second for general purpose ADC.
> +
> +Example:
> + tscadc: tscadc@50030000 {
> + compatible = "fsl,imx25-tsadc";
> + reg = <0x50030000 0xc>;
> + interrupts = <46>;
> + clocks = <&clks 119>;
> + clock-names = "ipg";
> + interrupt-controller;
> + #interrupt-cells = <1>;
> + #address-cells = <1>;
> + #size-cells = <1>;
> + ranges;
> +
> + tsc: tcq@50030400 {
> + compatible = "fsl,imx25-tcq";
> + reg = <0x50030400 0x60>;
> + ...
> + };
> +
> + adc: gcq@50030800 {
> + compatible = "fsl,imx25-gcq";
> + reg = <0x50030800 0x60>;
> + ...
> + };
> + };
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index ee8204c..73f4e66 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -183,6 +183,15 @@ config MFD_DA9063
> Additional drivers must be enabled in order to use the functionality
> of the device.
>
> +config MFD_MX25_TSADC
> + tristate "Freescale i.MX25 integrated Touchscreen and ADC unit"
> + depends on ARCH_MXC
> + select REGMAP_MMIO
> + help
> + Enable support for the integrated Touchscreen and ADC unit of the
> + i.MX25 processors. They consist of a conversion queue for general
> + purpose ADC and a queue for Touchscreens.
> +
> config MFD_MC13XXX
> tristate
> depends on (SPI_MASTER || I2C)
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index 8afedba..7ff1013 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -78,6 +78,8 @@ obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o
> obj-$(CONFIG_MFD_TWL4030_AUDIO) += twl4030-audio.o
> obj-$(CONFIG_TWL6040_CORE) += twl6040.o
>
> +obj-$(CONFIG_MFD_MX25_TSADC) += fsl-imx25-tsadc.o
> +
> obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
> obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o
> obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
> diff --git a/drivers/mfd/fsl-imx25-tsadc.c b/drivers/mfd/fsl-imx25-tsadc.c
> new file mode 100644
> index 0000000..10332c2
> --- /dev/null
> +++ b/drivers/mfd/fsl-imx25-tsadc.c
> @@ -0,0 +1,232 @@
> +/*
> + * Copyright 2014 Markus Pargmann, Pengutronix <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/irqdesc.h>
> +#include <linux/irqdomain.h>
> +#include <linux/irqchip/chained_irq.h>
> +#include <linux/mfd/imx25-tsadc.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +struct mx25_tsadc_priv {
> + struct regmap *regs;
> + struct irq_domain *domain;
> + struct clk *clk;
> +};
> +
> +static struct regmap_config mx25_tsadc = {
> + .fast_io = true,
> + .max_register = 0x8,
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> +};
> +
> +struct regmap *mx25_tsadc_get_regmap(struct device *dev)
> +{
> + struct platform_device *pdev;
> + struct mx25_tsadc_priv *priv;
> +
> + if (!dev)
> + return NULL;
> +
> + pdev = container_of(dev, struct platform_device, dev);
> + priv = platform_get_drvdata(pdev);
> + if (IS_ERR_OR_NULL(priv->regs))
> + return NULL;
> +
> + return priv->regs;
> +}
> +EXPORT_SYMBOL(mx25_tsadc_get_regmap);
> +
> +struct clk *mx25_tsadc_get_ipg(struct device *dev)
> +{
> + struct platform_device *pdev;
> + struct mx25_tsadc_priv *priv;
> +
> + if (!dev)
> + return NULL;
> +
> + pdev = container_of(dev, struct platform_device, dev);
> + priv = platform_get_drvdata(pdev);
> + if (IS_ERR_OR_NULL(priv->clk))
> + return NULL;
> +
> + return priv->clk;
> +}
> +EXPORT_SYMBOL(mx25_tsadc_get_ipg);
> +
> +static void mx25_tsadc_irq_handler(u32 irq, struct irq_desc *desc)
> +{
> + struct mx25_tsadc_priv *priv = irq_desc_get_handler_data(desc);
> + struct irq_chip *chip = irq_get_chip(irq);
> + u32 status;
> +
> + chained_irq_enter(chip, desc);
> +
> + regmap_read(priv->regs, MX25_TSC_TGSR, &status);
> +
> + if (status & MX25_TGSR_GCQ_INT)
> + generic_handle_irq(irq_find_mapping(priv->domain, 1));
> +
> + if (status & MX25_TGSR_TCQ_INT)
> + generic_handle_irq(irq_find_mapping(priv->domain, 0));
> +
> + chained_irq_exit(chip, desc);
> +}
> +
> +static void mx25_tsadc_nop(struct irq_data *d)
> +{
> +}
> +
> +static int mx25_tsadc_set_wake_nop(struct irq_data *d, unsigned int state)
> +{
> + return 0;
> +}
> +
> +static struct irq_chip mx25_tsadc_irq_chip = {
> + .name = "mx25-tsadc",
> + .irq_ack = mx25_tsadc_nop,
> + .irq_mask = mx25_tsadc_nop,
> + .irq_unmask = mx25_tsadc_nop,
> + .irq_set_wake = mx25_tsadc_set_wake_nop,
Whilst this is not documented as being optional, in kernel/irq/manage.c it
clearly is. Otherwise, what you appear to have here is the same (up to
a name) as the dummy_irq_chip provided for just such dumb devices. Could
you use that?
> +};
> +
> +static int mx25_tsadc_domain_map(struct irq_domain *d, unsigned int irq,
> + irq_hw_number_t hwirq)
> +{
> + struct mx25_tsadc_priv *priv = d->host_data;
> +
> + irq_set_chip_data(irq, priv);
> + irq_set_chip_and_handler(irq, &mx25_tsadc_irq_chip,
> + handle_level_irq);
> +
> +
Bonus blank line here.
> + set_irq_flags(irq, IRQF_VALID);
> +
> + return 0;
> +}
> +
> +static struct irq_domain_ops mx25_tsadc_domain_ops = {
> + .map = mx25_tsadc_domain_map,
> + .xlate = irq_domain_xlate_onecell,
> +};
> +
> +static int mx25_tsadc_setup_irq(struct platform_device *pdev,
> + struct mx25_tsadc_priv *priv)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + int irq;
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(dev, "Failed to get irq\n");
> + return irq;
> + }
> +
> + priv->domain = irq_domain_add_simple(np, 2, 0, &mx25_tsadc_domain_ops,
> + priv);
> + if (!priv->domain) {
> + dev_err(dev, "Failed to add irq domain\n");
> + return -ENOMEM;
> + }
> +
> + irq_set_chained_handler(irq, mx25_tsadc_irq_handler);
> + irq_set_handler_data(irq, priv);
> +
> + return 0;
> +}
> +
> +static int mx25_tsadc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + struct mx25_tsadc_priv *priv;
> + struct resource *iores;
> + int ret;
> + void __iomem *iomem;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Worth checking whether this succeeded (!=NULL)?
> + iomem = devm_ioremap_resource(dev, iores);
> + if (IS_ERR(iomem)) {
> + dev_err(dev, "Failed to remap iomem\n");
> + return PTR_ERR(iomem);
> + }
> +
> + priv->regs = regmap_init_mmio(dev, iomem, &mx25_tsadc);
> + if (IS_ERR(priv->regs)) {
> + dev_err(dev, "Failed to initialize regmap\n");
> + return PTR_ERR(priv->regs);
> + }
> +
> + priv->clk = devm_clk_get(dev, "ipg");
> + if (IS_ERR(priv->clk)) {
> + dev_err(dev, "Failed to get ipg clock\n");
> + return PTR_ERR(priv->clk);
> + }
> +
> + /* Enable clock and reset the component */
> + regmap_update_bits(priv->regs, MX25_TSC_TGCR, MX25_TGCR_CLK_EN,
> + MX25_TGCR_CLK_EN);
> + regmap_update_bits(priv->regs, MX25_TSC_TGCR, MX25_TGCR_TSC_RST,
> + MX25_TGCR_TSC_RST);
> +
> + /* Setup powersaving mode, but enable internal reference voltage */
> + regmap_update_bits(priv->regs, MX25_TSC_TGCR, MX25_TGCR_POWERMODE_MASK,
> + MX25_TGCR_POWERMODE_SAVE);
> + regmap_update_bits(priv->regs, MX25_TSC_TGCR, MX25_TGCR_INTREFEN,
> + MX25_TGCR_INTREFEN);
> +
> + ret = mx25_tsadc_setup_irq(pdev, priv);
> + if (ret) {
> + dev_err(dev, "Failed to setup irqs\n");
> + return ret;
> + }
> +
> + platform_set_drvdata(pdev, priv);
> +
> + of_platform_populate(np, NULL, NULL, dev);
> +
> + dev_info(dev, "i.MX25 Touchscreen and ADC core driver loaded\n");
> +
> + return 0;
> +}
> +
> +static const struct of_device_id mx25_tsadc_ids[] = {
> + { .compatible = "fsl,imx25-tsadc", },
> + { /* Sentinel */ }
> +};
> +
> +static struct platform_driver mx25_tsadc_driver = {
> + .driver = {
> + .name = "mx25-tsadc",
> + .owner = THIS_MODULE,
> + .of_match_table = mx25_tsadc_ids,
> + },
> + .probe = mx25_tsadc_probe,
> +};
> +module_platform_driver(mx25_tsadc_driver);
> +
> +MODULE_DESCRIPTION("MFD for ADC/TSC for Freescale mx25");
> +MODULE_AUTHOR("Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_ALIAS("platform:mx25-tsadc");
> diff --git a/include/linux/mfd/imx25-tsadc.h b/include/linux/mfd/imx25-tsadc.h
> new file mode 100644
> index 0000000..6fba341
> --- /dev/null
> +++ b/include/linux/mfd/imx25-tsadc.h
> @@ -0,0 +1,138 @@
> +#ifndef _LINUX_INCLUDE_INPUT_IMX25_TSADC_H_
> +#define _LINUX_INCLUDE_INPUT_IMX25_TSADC_H_
> +
> +struct regmap;
> +struct device;
> +struct clk;
> +
> +struct regmap *mx25_tsadc_get_regmap(struct device *dev);
> +struct clk *mx25_tsadc_get_ipg(struct device *dev);
> +
> +#define MX25_TSC_TGCR 0x00
> +#define MX25_TSC_TGSR 0x04
> +#define MX25_TSC_TICR 0x08
> +
> +/* The same register layout for TC and GC queue */
> +#define MX25_ADCQ_FIFO 0x00
> +#define MX25_ADCQ_CR 0x04
> +#define MX25_ADCQ_SR 0x08
> +#define MX25_ADCQ_MR 0x0c
> +#define MX25_ADCQ_ITEM_7_0 0x20
> +#define MX25_ADCQ_ITEM_15_8 0x24
> +#define MX25_ADCQ_CFG(n) (0x40 + ((n) * 0x4))
> +
> +/* Register values */
> +/* Queue Config register */
> +#define MX25_ADCQ_MR_MASK 0xffffffff
> +
> +/* TGCR */
> +#define MX25_TGCR_PDBTIME(x) ((x) << 25)
> +#define MX25_TGCR_PDBTIME_MASK MX25_TGCR_PDBTIME(0x7f)
> +#define MX25_TGCR_PDBEN (1 << 24)
> +#define MX25_TGCR_PDEN (1 << 23)
> +#define MX25_TGCR_ADCCLKCFG(x) ((x) << 16)
> +#define MX25_TGCR_GET_ADCCLK(x) (((x) >> 16) & 0x1f)
> +#define MX25_TGCR_INTREFEN (1 << 10)
> +#define MX25_TGCR_POWERMODE_MASK (3 << 8)
> +#define MX25_TGCR_POWERMODE_SAVE (1 << 8)
> +#define MX25_TGCR_POWERMODE_ON (2 << 8)
> +#define MX25_TGCR_STLC (1 << 5)
> +#define MX25_TGCR_SLPC (1 << 4)
> +#define MX25_TGCR_FUNC_RST (1 << 2)
> +#define MX25_TGCR_TSC_RST (1 << 1)
> +#define MX25_TGCR_CLK_EN (1 << 0)
> +
> +/* TGSR */
> +#define MX25_TGSR_SLP_INT (1 << 2)
> +#define MX25_TGSR_GCQ_INT (1 << 1)
> +#define MX25_TGSR_TCQ_INT (1 << 0)
> +
> +/* ADCQ_ITEM_* */
> +#define _MX25_ADCQ_ITEM(item, x) ((x) << ((item) * 4))
> +#define MX25_ADCQ_ITEM(item, x) ((item) >= 8 ? \
> + _MX25_ADCQ_ITEM((item) - 8, (x)) : _MX25_ADCQ_ITEM((item), (x)))
> +
> +/* ADCQ_FIFO (TCQFIFO and GCQFIFO) */
> +#define MX25_ADCQ_FIFO_DATA(x) (((x) >> 4) & 0xfff)
> +#define MX25_ADCQ_FIFO_ID(x) ((x) & 0xf)
> +
> +/* ADCQ_CR (TCQR and GCQR) */
> +#define MX25_ADCQ_CR_PDCFG_LEVEL (1 << 19)
> +#define MX25_ADCQ_CR_PDMSK (1 << 18)
> +#define MX25_ADCQ_CR_FRST (1 << 17)
> +#define MX25_ADCQ_CR_QRST (1 << 16)
> +#define MX25_ADCQ_CR_RWAIT_MASK (0xf << 12)
> +#define MX25_ADCQ_CR_RWAIT(x) ((x) << 12)
> +#define MX25_ADCQ_CR_WMRK_MASK (0xf << 8)
> +#define MX25_ADCQ_CR_WMRK(x) ((x) << 8)
> +#define MX25_ADCQ_CR_LITEMID_MASK (0xf << 4)
> +#define MX25_ADCQ_CR_LITEMID(x) ((x) << 4)
> +#define MX25_ADCQ_CR_RPT (1 << 3)
> +#define MX25_ADCQ_CR_FQS (1 << 2)
> +#define MX25_ADCQ_CR_QSM_MASK 0x3
> +#define MX25_ADCQ_CR_QSM_PD 0x1
> +#define MX25_ADCQ_CR_QSM_FQS 0x2
> +#define MX25_ADCQ_CR_QSM_FQS_PD 0x3
> +
> +/* ADCQ_SR (TCQSR and GCQSR) */
> +#define MX25_ADCQ_SR_FDRY (1 << 15)
> +#define MX25_ADCQ_SR_FULL (1 << 14)
> +#define MX25_ADCQ_SR_EMPT (1 << 13)
> +#define MX25_ADCQ_SR_FDN(x) (((x) >> 8) & 0x1f)
> +#define MX25_ADCQ_SR_FRR (1 << 6)
> +#define MX25_ADCQ_SR_FUR (1 << 5)
> +#define MX25_ADCQ_SR_FOR (1 << 4)
> +#define MX25_ADCQ_SR_EOQ (1 << 1)
> +#define MX25_ADCQ_SR_PD (1 << 0)
> +
> +/* ADCQ_MR (TCQMR and GCQMR) */
> +#define MX25_ADCQ_MR_FDRY_DMA (1 << 31)
> +#define MX25_ADCQ_MR_FER_DMA (1 << 22)
> +#define MX25_ADCQ_MR_FUR_DMA (1 << 21)
> +#define MX25_ADCQ_MR_FOR_DMA (1 << 20)
> +#define MX25_ADCQ_MR_EOQ_DMA (1 << 17)
> +#define MX25_ADCQ_MR_PD_DMA (1 << 16)
> +#define MX25_ADCQ_MR_FDRY_IRQ (1 << 15)
> +#define MX25_ADCQ_MR_FER_IRQ (1 << 6)
> +#define MX25_ADCQ_MR_FUR_IRQ (1 << 5)
> +#define MX25_ADCQ_MR_FOR_IRQ (1 << 4)
> +#define MX25_ADCQ_MR_EOQ_IRQ (1 << 1)
> +#define MX25_ADCQ_MR_PD_IRQ (1 << 0)
> +
> +/* ADCQ_CFG (TICR, TCC0-7,GCC0-7) */
> +#define MX25_ADCQ_CFG_SETTLING_TIME(x) ((x) << 24)
> +#define MX25_ADCQ_CFG_IGS (1 << 20)
> +#define MX25_ADCQ_CFG_NOS_MASK (0xf << 16)
> +#define MX25_ADCQ_CFG_NOS(x) (((x) - 1) << 16)
> +#define MX25_ADCQ_CFG_WIPER (1 << 15)
> +#define MX25_ADCQ_CFG_YNLR (1 << 14)
> +#define MX25_ADCQ_CFG_YPLL_HIGH 0
> +#define MX25_ADCQ_CFG_YPLL_OFF (1 << 12)
> +#define MX25_ADCQ_CFG_YPLL_LOW (3 << 12)
> +#define MX25_ADCQ_CFG_XNUR_HIGH 0
> +#define MX25_ADCQ_CFG_XNUR_OFF (1 << 10)
> +#define MX25_ADCQ_CFG_XNUR_LOW (3 << 10)
> +#define MX25_ADCQ_CFG_XPUL_OFF (1 << 9)
> +#define MX25_ADCQ_CFG_XPUL_HIGH 0
> +#define MX25_ADCQ_CFG_REFP_YP 0
> +#define MX25_ADCQ_CFG_REFP_XP (1 << 7)
> +#define MX25_ADCQ_CFG_REFP_EXT (2 << 7)
> +#define MX25_ADCQ_CFG_REFP_INT (3 << 7)
> +#define MX25_ADCQ_CFG_REFP_MASK (3 << 7)
> +#define MX25_ADCQ_CFG_IN_XP 0
> +#define MX25_ADCQ_CFG_IN_YP (1 << 4)
> +#define MX25_ADCQ_CFG_IN_XN (2 << 4)
> +#define MX25_ADCQ_CFG_IN_YN (3 << 4)
> +#define MX25_ADCQ_CFG_IN_WIPER (4 << 4)
> +#define MX25_ADCQ_CFG_IN_AUX0 (5 << 4)
> +#define MX25_ADCQ_CFG_IN_AUX1 (6 << 4)
> +#define MX25_ADCQ_CFG_IN_AUX2 (7 << 4)
> +#define MX25_ADCQ_CFG_REFN_XN 0
> +#define MX25_ADCQ_CFG_REFN_YN (1 << 2)
> +#define MX25_ADCQ_CFG_REFN_NGND (2 << 2)
> +#define MX25_ADCQ_CFG_REFN_NGND2 (3 << 2)
> +#define MX25_ADCQ_CFG_REFN_MASK (3 << 2)
> +#define MX25_ADCQ_CFG_PENIACK (1 << 1)
> +
> +
> +#endif /* _LINUX_INCLUDE_INPUT_IMX25_TSADC_H_ */
>
^ permalink raw reply
* Re: [PATCH v2 2/6] input: touchscreen: imx25 tcq driver
From: Jonathan Cameron @ 2014-06-21 10:18 UTC (permalink / raw)
To: Denis Carikli, Shawn Guo, Samuel Ortiz, Dmitry Torokhov
Cc: Eric Bénard, Sascha Hauer, linux-arm-kernel, Lee Jones,
linux-input, linux-iio, Fabio Estevam, Lars-Peter Clausen,
Markus Pargmann
In-Reply-To: <1402672899-6995-3-git-send-email-denis@eukrea.com>
On 13/06/14 16:21, Denis Carikli wrote:
> From: Markus Pargmann <mpa@pengutronix.de>
>
> This is a driver for the imx25 ADC/TSC module. It controls the
> touchscreen conversion queue and creates a touchscreen input device.
> The driver currently only supports 4 wire touchscreens. The driver uses
> a simple conversion queue of precharge, touch detection, X measurement,
> Y measurement, precharge and another touch detection.
>
> This driver uses the regmap from the parent to setup some touch specific
> settings in the core driver and setup a idle configuration with touch
> detection.
>
> Signed-off-by: Markus Pargmann <mpa@pengutronix.de>
> Signed-off-by: Denis Carikli <denis@eukrea.com>
A couple of little bits inline.
I haven't looked in detail by the devm irq handling
looks dangerous as the clock is set to running before
this is enabled, but disabled before it is disabled.
Jonathan
> ---
> .../bindings/input/touchscreen/fsl-mx25-tcq.txt | 29 +
> drivers/input/touchscreen/Kconfig | 6 +
> drivers/input/touchscreen/Makefile | 1 +
> drivers/input/touchscreen/fsl-imx25-tcq.c | 576 ++++++++++++++++++++
> 4 files changed, 612 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/input/touchscreen/fsl-mx25-tcq.txt
> create mode 100644 drivers/input/touchscreen/fsl-imx25-tcq.c
>
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/fsl-mx25-tcq.txt b/Documentation/devicetree/bindings/input/touchscreen/fsl-mx25-tcq.txt
> new file mode 100644
> index 0000000..4214a99
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/touchscreen/fsl-mx25-tcq.txt
> @@ -0,0 +1,29 @@
> +Freescale mx25 TS conversion queue module
> +
> +mx25 touchscreen conversion queue module which controls the ADC unit of the
> +mx25 for attached touchscreens.
> +
> +Required properties:
> + - compatible: Should be "fsl,imx25-tcq".
> + - reg: Memory range of the device.
> + - interrupts: Should be the interrupt number associated with this module within
> + the tscadc unit (<0>).
> + - interrupt-parent: Should be a phandle to the tscadc unit.
> + - fsl,wires: Should be '<4>' or '<5>'
> +
> +Optional properties:
> + - fsl,pen-debounce: Pen debounce time.
> + - fsl,pen-threshold: Pen-down threshold for the touchscreen.
> + - fsl,settling-time: Settling time in nanoseconds.
> +
> +This device includes two conversion queues which can be added as subnodes.
> +The first queue is for the touchscreen, the second for general purpose ADC.
> +
> +Example:
> + tsc: tcq@50030400 {
> + compatible = "fsl,imx25-tcq";
> + reg = <0x50030400 0x60>;
> + interrupt-parent = <&tscadc>;
> + interrupts = <0>;
> + fsl,wires = <4>;
> + };
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index a23a94b..a2290b9 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -699,6 +699,12 @@ config TOUCHSCREEN_USB_COMPOSITE
> To compile this driver as a module, choose M here: the
> module will be called usbtouchscreen.
>
> +config TOUCHSCREEN_MX25
> + tristate "Freescale i.MX25 touchscreen input driver"
> + depends on MFD_MX25_TSADC
> + help
> + Enable support for touchscreen connected to your i.MX25.
> +
> config TOUCHSCREEN_MC13783
> tristate "Freescale MC13783 touchscreen input driver"
> depends on MFD_MC13XXX
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 126479d..537d591 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -38,6 +38,7 @@ obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o
> obj-$(CONFIG_TOUCHSCREEN_INTEL_MID) += intel-mid-touch.o
> obj-$(CONFIG_TOUCHSCREEN_LPC32XX) += lpc32xx_ts.o
> obj-$(CONFIG_TOUCHSCREEN_MAX11801) += max11801_ts.o
> +obj-$(CONFIG_TOUCHSCREEN_MX25) += fsl-imx25-tcq.o
> obj-$(CONFIG_TOUCHSCREEN_MC13783) += mc13783_ts.o
> obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o
> obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o
> diff --git a/drivers/input/touchscreen/fsl-imx25-tcq.c b/drivers/input/touchscreen/fsl-imx25-tcq.c
> new file mode 100644
> index 0000000..4ce576e
> --- /dev/null
> +++ b/drivers/input/touchscreen/fsl-imx25-tcq.c
> @@ -0,0 +1,576 @@
> +/*
> + * Copyright 2014 Markus Pargmann, Pengutronix <mpa@pengutronix.de>
> + * Based on driver from 2011:
> + * Juergen Beisert, Pengutronix <kernel@pengutronix.de>
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + *
> + * This is the driver for the imx25 TCQ (Touchscreen Conversion Queue)
> + * connected to the imx25 ADC.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/input.h>
> +#include <linux/mfd/imx25-tsadc.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +static const char mx25_tcq_name[] = "mx25-tcq";
> +
> +enum mx25_tcq_mode {
> + MX25_TS_4WIRE,
> +};
> +
> +struct mx25_tcq_priv {
> + struct regmap *regs;
> + struct regmap *core_regs;
> + struct input_dev *idev;
> + enum mx25_tcq_mode mode;
> + unsigned int pen_threshold;
> + unsigned int sample_count;
> + unsigned int expected_samples;
> + unsigned int pen_debounce;
> + unsigned int settling_time;
> + struct clk *clk;
> +};
> +
> +static struct regmap_config mx25_tcq_regconfig = {
> + .fast_io = true,
> + .max_register = 0x5c,
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> +};
> +
> +static struct of_device_id mx25_tcq_ids[] = {
> + { .compatible = "fsl,imx25-tcq", },
> + { /* Senitel */ }
> +};
> +
> +#define TSC_4WIRE_PRE_INDEX 0
> +#define TSC_4WIRE_X_INDEX 1
> +#define TSC_4WIRE_Y_INDEX 2
> +#define TSC_4WIRE_POST_INDEX 3
> +#define TSC_4WIRE_LEAVE 4
> +
> +#define MX25_TSC_DEF_THRESHOLD 80
> +#define TSC_MAX_SAMPLES 16
> +
> +
> +enum mx25_adc_configurations {
> + MX25_CFG_PRECHARGE = 0,
> + MX25_CFG_TOUCH_DETECT,
> + MX25_CFG_X_MEASUREMENT,
> + MX25_CFG_Y_MEASUREMENT,
> +};
> +
> +#define MX25_PRECHARGE_VALUE (\
> + MX25_ADCQ_CFG_YPLL_OFF | \
> + MX25_ADCQ_CFG_XNUR_OFF | \
> + MX25_ADCQ_CFG_XPUL_HIGH | \
> + MX25_ADCQ_CFG_REFP_INT | \
> + MX25_ADCQ_CFG_IN_XP | \
> + MX25_ADCQ_CFG_REFN_NGND2 | \
> + MX25_ADCQ_CFG_IGS)
> +
> +#define MX25_TOUCH_DETECT_VALUE (\
> + MX25_ADCQ_CFG_YNLR | \
> + MX25_ADCQ_CFG_YPLL_OFF | \
> + MX25_ADCQ_CFG_XNUR_OFF | \
> + MX25_ADCQ_CFG_XPUL_OFF | \
> + MX25_ADCQ_CFG_REFP_INT | \
> + MX25_ADCQ_CFG_IN_XP | \
> + MX25_ADCQ_CFG_REFN_NGND2 | \
> + MX25_ADCQ_CFG_PENIACK)
> +
> +static void imx25_setup_queue_cfgs(struct mx25_tcq_priv *priv,
> + unsigned int settling_time)
> +{
> + u32 precharge_cfg =
> + MX25_PRECHARGE_VALUE |
> + MX25_ADCQ_CFG_SETTLING_TIME(settling_time);
> + u32 touch_detect_cfg =
> + MX25_TOUCH_DETECT_VALUE |
> + MX25_ADCQ_CFG_NOS(1) |
> + MX25_ADCQ_CFG_SETTLING_TIME(settling_time);
> +
> + regmap_write(priv->core_regs, MX25_TSC_TICR, precharge_cfg);
> +
> + /* PRECHARGE */
> + regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_PRECHARGE),
> + precharge_cfg);
> +
> + /* TOUCH_DETECT */
> + regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_TOUCH_DETECT),
> + touch_detect_cfg);
> +
> + /* X Measurement */
> + regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_X_MEASUREMENT),
> + MX25_ADCQ_CFG_YPLL_OFF |
> + MX25_ADCQ_CFG_XNUR_LOW |
> + MX25_ADCQ_CFG_XPUL_HIGH |
> + MX25_ADCQ_CFG_REFP_XP |
> + MX25_ADCQ_CFG_IN_YP |
> + MX25_ADCQ_CFG_REFN_XN |
> + MX25_ADCQ_CFG_NOS(priv->sample_count) |
> + MX25_ADCQ_CFG_SETTLING_TIME(settling_time));
> +
> + /* Y Measurement */
> + regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_Y_MEASUREMENT),
> + MX25_ADCQ_CFG_YNLR |
> + MX25_ADCQ_CFG_YPLL_HIGH |
> + MX25_ADCQ_CFG_XNUR_OFF |
> + MX25_ADCQ_CFG_XPUL_OFF |
> + MX25_ADCQ_CFG_REFP_YP |
> + MX25_ADCQ_CFG_IN_XP |
> + MX25_ADCQ_CFG_REFN_YN |
> + MX25_ADCQ_CFG_NOS(priv->sample_count) |
> + MX25_ADCQ_CFG_SETTLING_TIME(settling_time));
> +
> + /* Enable the touch detection right now */
> + regmap_write(priv->core_regs, MX25_TSC_TICR, touch_detect_cfg |
> + MX25_ADCQ_CFG_IGS);
> +}
> +
> +static int imx25_setup_queue_4wire(struct mx25_tcq_priv *priv,
> + unsigned settling_time, int *items)
> +{
> + imx25_setup_queue_cfgs(priv, settling_time);
> +
> + /* Setup the conversion queue */
> + regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0,
> + MX25_ADCQ_ITEM(0, MX25_CFG_PRECHARGE) |
> + MX25_ADCQ_ITEM(1, MX25_CFG_TOUCH_DETECT) |
> + MX25_ADCQ_ITEM(2, MX25_CFG_X_MEASUREMENT) |
> + MX25_ADCQ_ITEM(3, MX25_CFG_Y_MEASUREMENT) |
> + MX25_ADCQ_ITEM(4, MX25_CFG_PRECHARGE) |
> + MX25_ADCQ_ITEM(5, MX25_CFG_TOUCH_DETECT));
> +
> + /* We measure X/Y with 'sample_count' number of samples and execute a
> + * touch detection twice, with 1 sample each */
> + priv->expected_samples = priv->sample_count * 2 + 2;
> + *items = 6;
> +
> + return 0;
> +}
> +
> +static void mx25_tcq_disable_touch_irq(struct mx25_tcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_PDMSK,
> + MX25_ADCQ_CR_PDMSK);
> +}
> +
> +static void mx25_tcq_enable_touch_irq(struct mx25_tcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_PDMSK, 0);
> +}
> +
> +static void mx25_tcq_disable_fifo_irq(struct mx25_tcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_FDRY_IRQ,
> + MX25_ADCQ_MR_FDRY_IRQ);
> +}
> +
> +static void mx25_tcq_enable_fifo_irq(struct mx25_tcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_FDRY_IRQ, 0);
> +}
> +
> +static void mx25_tcq_force_queue_start(struct mx25_tcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS,
> + MX25_ADCQ_CR_FQS);
> +
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, 0);
> +}
> +
> +static void mx25_tcq_force_queue_stop(struct mx25_tcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, 0);
> +}
> +
> +static void mx25_tcq_fifo_reset(struct mx25_tcq_priv *priv)
> +{
> + u32 tcqcr;
> +
> + regmap_read(priv->regs, MX25_ADCQ_CR, &tcqcr);
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FRST,
> + MX25_ADCQ_CR_FRST);
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FRST,
> + 0);
> + regmap_write(priv->regs, MX25_ADCQ_CR, tcqcr);
> +}
> +
> +static void mx25_tcq_re_enable_touch_detection(struct mx25_tcq_priv *priv)
> +{
> + /* stop the queue from looping */
> + mx25_tcq_force_queue_stop(priv);
> +
> + /* for a clean touch detection, preload the X plane */
> + regmap_write(priv->core_regs, MX25_TSC_TICR, MX25_PRECHARGE_VALUE);
> +
> + /* waste some time now to pre-load the X plate to high voltage */
> + mx25_tcq_fifo_reset(priv);
> +
> + /* re-enable the detection right now */
> + regmap_write(priv->core_regs, MX25_TSC_TICR, MX25_TOUCH_DETECT_VALUE |
> + MX25_ADCQ_CFG_IGS);
> +
> + regmap_update_bits(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_PD,
> + MX25_ADCQ_SR_PD);
> +
> + /* enable the pen down event to be a source for the interrupt */
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_PD_IRQ, 0);
> +
> + /* lets fire the next IRQ if someone touches the touchscreen */
> + mx25_tcq_enable_touch_irq(priv);
> +}
> +
> +static int mx25_tcq_create_event_for_4wire(struct mx25_tcq_priv *priv,
> + u32 *sample_buf, int samples)
> +{
> + unsigned int x_pos = 0;
> + unsigned int y_pos = 0;
> + unsigned int touch_pre = 0;
> + unsigned int touch_post = 0;
> + unsigned i;
> + int ret = 0;
> +
> + for (i = 0; i < samples; i++) {
> + unsigned int index = MX25_ADCQ_FIFO_ID(sample_buf[i]);
> + unsigned int val = MX25_ADCQ_FIFO_DATA(sample_buf[i]);
> +
> + switch (index) {
> + case 1:
> + touch_pre = val;
> + break;
> + case 2:
> + x_pos = val;
> + break;
> + case 3:
> + y_pos = val;
> + break;
> + case 5:
> + touch_post = val;
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> + }
> +
> + if (ret == 0 && samples != 0) {
> + /*
> + * only if both touch measures are below a treshold,
> + * the position is valid
> + */
> + if (touch_pre < priv->pen_threshold &&
> + touch_post < priv->pen_threshold) {
> + /* valid samples, generate a report */
> + x_pos /= priv->sample_count;
> + y_pos /= priv->sample_count;
> + input_report_abs(priv->idev, ABS_X, x_pos);
> + input_report_abs(priv->idev, ABS_Y, y_pos);
> + input_report_key(priv->idev, BTN_TOUCH,
> + 0xfff - ((touch_pre + touch_post) / 2));
> + input_sync(priv->idev);
> +
> + /* get next sample */
> + mx25_tcq_force_queue_start(priv);
> + mx25_tcq_enable_fifo_irq(priv);
> + } else if (touch_pre >= priv->pen_threshold &&
> + touch_post >= priv->pen_threshold) {
> + /*
> + * if both samples are invalid,
> + * generate a release report
> + */
> + input_report_key(priv->idev, BTN_TOUCH, 0);
> + input_sync(priv->idev);
> + mx25_tcq_re_enable_touch_detection(priv);
> + } else {
> + /*
> + * if only one of both touch measurements are
> + * below the threshold, still some bouncing
> + * happens. Take additional samples in this
> + * case to be sure
> + */
> + mx25_tcq_force_queue_start(priv);
> + mx25_tcq_enable_fifo_irq(priv);
> + }
> + }
> +
> + return ret;
> +}
> +
> +static irqreturn_t mx25_tcq_irq_thread(int irq, void *dev_id)
> +{
> + struct mx25_tcq_priv *priv = (struct mx25_tcq_priv *) dev_id;
> + u32 sample_buf[TSC_MAX_SAMPLES];
> + int samples = 0;
> +
> + /* read all samples */
> + while (1) {
> + u32 stats;
> +
> + regmap_read(priv->regs, MX25_ADCQ_SR, &stats);
> + if (stats & MX25_ADCQ_SR_EMPT)
> + break;
> +
> + if (samples < TSC_MAX_SAMPLES) {
> + regmap_read(priv->regs, MX25_ADCQ_FIFO,
> + &sample_buf[samples]);
> + ++samples;
> + } else {
> + u32 discarded;
> + /* discard samples */
> + regmap_read(priv->regs, MX25_ADCQ_FIFO, &discarded);
> + }
> + }
> +
> + mx25_tcq_create_event_for_4wire(priv, sample_buf, samples);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t mx25_tcq_irq(int irq, void *dev_id)
> +{
> + struct mx25_tcq_priv *priv = (struct mx25_tcq_priv *)dev_id;
> + u32 stat;
> + int ret = IRQ_HANDLED;
> +
> + regmap_read(priv->regs, MX25_ADCQ_SR, &stat);
> +
> + if (stat & (MX25_ADCQ_SR_FRR | MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR))
> + mx25_tcq_fifo_reset(priv);
> +
> + if (stat & MX25_ADCQ_SR_PD) {
> + mx25_tcq_disable_touch_irq(priv);
> + mx25_tcq_force_queue_start(priv);
> + mx25_tcq_enable_fifo_irq(priv);
> + }
> +
> + if (stat & MX25_ADCQ_SR_FDRY) {
> + mx25_tcq_disable_fifo_irq(priv);
> + ret = IRQ_WAKE_THREAD;
> + }
> +
> + regmap_update_bits(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR |
> + MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | MX25_ADCQ_SR_PD |
> + MX25_ADCQ_SR_EOQ,
> + MX25_ADCQ_SR_FRR |
> + MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | MX25_ADCQ_SR_PD |
> + MX25_ADCQ_SR_EOQ);
> +
> + return ret;
> +}
> +
> +/* configure the statemachine for a 4-wire touchscreen */
> +static int mx25_tcq_init(struct mx25_tcq_priv *priv)
> +{
> + u32 tgcr;
> + unsigned int ipg_div;
> + unsigned int adc_period;
> + unsigned int debounce_cnt;
> + unsigned int settling_time;
> + int itemct;
> + int ret;
> +
> + regmap_read(priv->core_regs, MX25_TSC_TGCR, &tgcr);
> + ipg_div = max_t(unsigned int, 4, MX25_TGCR_GET_ADCCLK(tgcr));
> + adc_period = clk_get_rate(priv->clk) / (ipg_div * 2 + 2);
> + debounce_cnt = DIV_ROUND_UP(priv->pen_debounce, adc_period * 8) - 1;
> + settling_time = DIV_ROUND_UP(priv->settling_time, adc_period);
> +
> +
> + /* Reset */
> + regmap_write(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_QRST |
> + MX25_ADCQ_CR_FRST);
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_QRST |
> + MX25_ADCQ_CR_FRST, 0);
> +
> + /* up to 128 * 8 ADC clocks are possible */
> + if (debounce_cnt > 127)
> + debounce_cnt = 127;
> +
> + ret = imx25_setup_queue_4wire(priv, 0x0, &itemct);
> + if (ret)
> + return ret;
> +
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_LITEMID_MASK |
> + MX25_ADCQ_CR_WMRK_MASK,
> + MX25_ADCQ_CR_LITEMID(itemct - 1) |
> + MX25_ADCQ_CR_WMRK(priv->expected_samples - 1));
> +
> + /* setup debounce count */
> + regmap_update_bits(priv->core_regs, MX25_TSC_TGCR,
> + MX25_TGCR_PDBTIME_MASK,
> + MX25_TGCR_PDBTIME(debounce_cnt));
> +
> + /* enable debounce */
> + regmap_update_bits(priv->core_regs, MX25_TSC_TGCR, MX25_TGCR_PDBEN,
> + MX25_TGCR_PDBEN);
> + regmap_update_bits(priv->core_regs, MX25_TSC_TGCR, MX25_TGCR_PDEN,
> + MX25_TGCR_PDEN);
> +
> + /* enable the engine on demand */
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_QSM_FQS,
> + MX25_ADCQ_CR_QSM_FQS);
> +
> + mx25_tcq_re_enable_touch_detection(priv);
> +
> + return 0;
> +}
> +
> +static int mx25_tcq_parse_dt(struct platform_device *pdev,
> + struct mx25_tcq_priv *priv)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + u32 wires;
> + int ret;
> +
> + /* Setup defaults */
> + priv->pen_threshold = 500;
> + priv->sample_count = 3;
> + priv->pen_debounce = 1000000;
> + priv->settling_time = 250000;
> +
> + ret = of_property_read_u32(np, "fsl,wires", &wires);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to find fsl,wires properties\n");
> + return ret;
> + }
> +
> + if (wires == 4) {
> + priv->mode = MX25_TS_4WIRE;
> + } else {
> + dev_err(&pdev->dev, "%u-wire mode not supported\n", wires);
> + return -EINVAL;
> + }
> +
> + /* These are optional, we don't care about the return values */
> + of_property_read_u32(np, "fsl,pen-threshold", &priv->pen_threshold);
> + of_property_read_u32(np, "fsl,settling-time", &priv->settling_time);
> + of_property_read_u32(np, "fsl,pen-debounce", &priv->pen_debounce);
> +
> + return 0;
> +}
> +
> +static int mx25_tcq_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct input_dev *idev;
> + struct mx25_tcq_priv *priv;
> + struct resource *res;
> + void __iomem *mem;
> + int ret;
> + int irq;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Could this not in theory fail?
> + mem = devm_ioremap_resource(dev, res);
> + if (!mem)
> + return -ENOMEM;
> +
> + ret = mx25_tcq_parse_dt(pdev, priv);
> + if (ret)
> + return ret;
> +
> + priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_tcq_regconfig);
> + if (IS_ERR(priv->regs)) {
> + dev_err(dev, "Failed to initialize regmap\n");
> + return PTR_ERR(priv->regs);
> + }
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq <= 0) {
> + dev_err(dev, "Failed to get IRQ\n");
> + return irq;
> + }
> +
> + idev = devm_input_allocate_device(dev);
> + if (!idev) {
> + dev_err(dev, "Failed to allocate input device\n");
> + return -ENOMEM;
> + }
> +
> + idev->name = mx25_tcq_name;
> + idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
> + idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
> + input_set_abs_params(idev, ABS_X, 0, 0xfff, 0, 0);
> + input_set_abs_params(idev, ABS_Y, 0, 0xfff, 0, 0);
> +
> + idev->id.bustype = BUS_HOST;
> +
> + ret = input_register_device(idev);
> + if (ret) {
> + dev_err(dev, "Failed to register input device\n");
> + return ret;
> + }
> +
> + priv->idev = idev;
> +
> + priv->core_regs = mx25_tsadc_get_regmap(pdev->dev.parent);
> + priv->clk = mx25_tsadc_get_ipg(pdev->dev.parent);
> +
> + ret = clk_prepare_enable(priv->clk);
> + if (ret) {
> + dev_err(dev, "Failed to enable ipg clock\n");
> + return ret;
> + }
> +
> + ret = devm_request_threaded_irq(dev, irq, mx25_tcq_irq,
> + mx25_tcq_irq_thread, IRQF_ONESHOT, pdev->name, priv);
> + if (ret) {
> + dev_err(dev, "Failed requesting IRQ\n");
> + return ret;
> + }
This use of devm_request_threaded_irq strikes me as potentially dangerous
as the order in removing the device has the clock being disabled before
the interrupt is removed whereas for a simple unwinding they would be in
the other order.
> +
> + ret = mx25_tcq_init(priv);
> + if (ret) {
> + dev_err(dev, "Failed to init tcq\n");
> + goto error_tcq_init;
> + }
> +
> + platform_set_drvdata(pdev, priv);
> +
> + return 0;
> +
> +error_tcq_init:
> + clk_disable_unprepare(priv->clk);
> + return ret;
> +}
> +
> +static int mx25_tcq_remove(struct platform_device *pdev)
> +{
> + struct mx25_tcq_priv *priv = platform_get_drvdata(pdev);
> +
> + clk_disable_unprepare(priv->clk);
> +
> + return 0;
> +}
> +
> +static struct platform_driver mx25_tcq_driver = {
> + .driver = {
> + .name = "mx25-tcq",
> + .owner = THIS_MODULE,
> + .of_match_table = mx25_tcq_ids,
> + },
> + .probe = mx25_tcq_probe,
> + .remove = mx25_tcq_remove,
> +};
> +module_platform_driver(mx25_tcq_driver);
> +
> +MODULE_DESCRIPTION("TS input driver for Freescale mx25");
> +MODULE_AUTHOR("Markus Pargmann <mpa@pengutronix.de>");
> +MODULE_LICENSE("GPL v2");
>
^ permalink raw reply
* Re: [PATCH v2 3/6] iio: adc: fsl,imx25-gcq driver
From: Jonathan Cameron @ 2014-06-21 10:30 UTC (permalink / raw)
To: Denis Carikli, Shawn Guo, Samuel Ortiz, Dmitry Torokhov
Cc: Eric Bénard, Sascha Hauer,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Lee Jones,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA, Fabio Estevam,
Lars-Peter Clausen, Markus Pargmann, Devicetree List
In-Reply-To: <1402672899-6995-4-git-send-email-denis-fO0SIAKYzcbQT0dZR+AlfA@public.gmane.org>
On 13/06/14 16:21, Denis Carikli wrote:
> From: Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
>
> This is a conversion queue driver for the mx25 SoC. It uses the central
> ADC which is used by two seperate independent queues. This driver
> prepares different conversion configurations for each possible input.
> For a conversion it creates a conversionqueue of one item with the
> correct configuration for the chosen channel. It then executes the queue
> once and disables the conversion queue afterwards.
>
> The reference voltages are configurable through devicetree subnodes,
> depending on the connections of the ADC inputs.
>
> Signed-off-by: Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> Signed-off-by: Denis Carikli <denis-fO0SIAKYzcbQT0dZR+AlfA@public.gmane.org>
Couple of little bits + I think we have enough 'interesting' bits
in the device tree binding that we are going to need an Ack from a device
tree maintainer. Note any patch at all with device tree bindings should
be cc'd to devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> ---
> .../devicetree/bindings/iio/adc/fsl,imx25-gcq.txt | 54 ++++
> drivers/iio/adc/Kconfig | 7 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/fsl-imx25-gcq.c | 338 ++++++++++++++++++++
> 4 files changed, 400 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> create mode 100644 drivers/iio/adc/fsl-imx25-gcq.c
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt b/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> new file mode 100644
> index 0000000..333fc55
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> @@ -0,0 +1,54 @@
> +Freescale i.MX25 ADC GCQ device
> +
> +This is a generic conversion queue device that can convert any of the analog
> +inputs using the ADC unit of the i.MX25.
> +
> +Required properties:
> + - compatible: Should be "fsl,imx25-gcq".
> + - reg: Should be the register range of the module.
> + - interrupts: Should be the interrupt number of the module. Typically this is <1>.
> + - interrupt-parent: phandle to the tsadc module of the i.MX25.
> + - #address-cells: Should be <1> (setting for the subnodes)
> + - #size-cells: Should be <0> (setting for the subnodes)
> +
> +Optionally you can define subnodes which define the positive and negative
> +reference voltage for one of the analog inputs.
> +
> +Required properties for subnodes:
> + - reg: Should be the number of the analog input.
> + 0: xp
> + 1: yp
> + 2: xn
> + 3: yn
> + 4: wiper
> + 5: inaux0
> + 6: inaux1
> + 7: inaux2
> + - fsl,adc-refp: Positive reference input
> + 0: yp
> + 1: xp
> + 2: External reference
> + 3: Internal reference
> + - fsl,adc-refn: Negative reference input
> + 0: xn
> + 1: yn
> + 2: ngnd_adc
> + 3: ngnd_adc
> +
> +
> +Example:
> +
> + adc: adc@50030800 {
> + compatible = "fsl,imx25-gcq";
> + reg = <0x50030800 0x60>;
> + interrupt-parent = <&tscadc>;
> + interrupts = <1>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + inaux@5 {
> + reg = <5>;
> + fsl,adc-refp = <3>;
> + fsl,adc-refn = <3>;
> + };
> + };
This lot is involved enough that we'll need a devicetree maintainer
ack I think...
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index a80d236..58efb8d 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -125,6 +125,13 @@ config EXYNOS_ADC
> of SoCs for drivers such as the touchscreen and hwmon to use to share
> this resource.
>
> +config FSL_MX25_ADC
> + tristate "Freescale MX25 ADC driver"
> + depends on MFD_MX25_TSADC
> + help
> + Generic Conversion Queue driver used for general purpose ADC in the
> + MX25. This driver supports single measurements using the MX25 ADC.
> +
> config LP8788_ADC
> tristate "LP8788 ADC driver"
> depends on MFD_LP8788
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 9d60f2d..2767fd6 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_AD7887) += ad7887.o
> obj-$(CONFIG_AD799X) += ad799x.o
> obj-$(CONFIG_AT91_ADC) += at91_adc.o
> obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
> +obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o
> obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
> obj-$(CONFIG_MAX1363) += max1363.o
> obj-$(CONFIG_MCP320X) += mcp320x.o
> diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c
> new file mode 100644
> index 0000000..1ae697c
> --- /dev/null
> +++ b/drivers/iio/adc/fsl-imx25-gcq.c
> @@ -0,0 +1,338 @@
> +/*
> + * Copyright 2014 Markus Pargmann, Pengutronix <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + *
> + * This is the driver for the imx25 GCQ (Generic Conversion Queue)
> + * connected to the imx25 ADC.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/iio/iio.h>
> +#include <linux/mfd/imx25-tsadc.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#define MX25_GCQ_TIMEOUT (msecs_to_jiffies(2000))
> +
> +enum mx25_gcq_cfgs {
> + MX25_CFG_XP = 0,
> + MX25_CFG_YP,
> + MX25_CFG_XN,
> + MX25_CFG_YN,
> + MX25_CFG_WIPER,
> + MX25_CFG_INAUX0,
> + MX25_CFG_INAUX1,
> + MX25_CFG_INAUX2,
> + MX25_NUM_CFGS,
> +};
> +
> +struct mx25_gcq_priv {
> + struct regmap *regs;
> + struct completion completed;
> + unsigned int settling_time;
> + struct clk *clk;
> + int irq;
> +};
> +
> +#define MX25_IIO_CHAN(chan, id) {\
> + .type = IIO_VOLTAGE,\
> + .indexed = 1,\
> + .channel = chan,\
> + .address = chan,\
No real point in setting address if it is equal to channel. Just use
channel instead. Address is just here for when it is convenient to store
something like a register address rather than for a copy of the index.
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
No known info on converting this to real voltages? I'd normally expect
a scale parameter at least with any raw channel output, but fair enough
if it's not obvous what this should be.
> + .datasheet_name = id, \
> + }
> +
> +static const struct iio_chan_spec mx25_gcq_channels[MX25_NUM_CFGS] = {
> + MX25_IIO_CHAN(0, "xp"),
> + MX25_IIO_CHAN(1, "yp"),
> + MX25_IIO_CHAN(2, "xn"),
> + MX25_IIO_CHAN(3, "yn"),
> + MX25_IIO_CHAN(4, "wiper"),
> + MX25_IIO_CHAN(5, "inaux0"),
> + MX25_IIO_CHAN(6, "inaux1"),
> + MX25_IIO_CHAN(7, "inaux2"),
> +};
> +
> +static void mx25_gcq_disable_eoq(struct mx25_gcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ,
> + MX25_ADCQ_MR_EOQ_IRQ);
> +}
> +
> +static void mx25_gcq_enable_eoq(struct mx25_gcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ, 0);
> +}
> +
> +static irqreturn_t mx25_gcq_irq(int irq, void *data)
> +{
I suppose there isn't that much in here, but could it be a threaded
irq without loosing anything significant? A general good practice
element rather than a specific comment on what you have here!
> + struct mx25_gcq_priv *priv = data;
> + u32 stats;
> +
> + regmap_read(priv->regs, MX25_ADCQ_SR, &stats);
> +
> + if (stats & MX25_ADCQ_SR_EOQ) {
> + mx25_gcq_disable_eoq(priv);
> + complete(&priv->completed);
> + }
> +
> + /* Disable conversion queue run */
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, 0);
> +
> + /* Acknowledge all possible irqs */
> + regmap_write(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR |
> + MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | MX25_ADCQ_SR_EOQ |
> + MX25_ADCQ_SR_PD);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int mx25_gcq_read_raw(struct iio_dev *idev,
> + struct iio_chan_spec const *chan, int *val, int *val2,
> + long mask)
> +{
> + struct mx25_gcq_priv *priv = iio_priv(idev);
> + unsigned long timeout;
> + u32 data;
> + int ret;
> +
> + if (mask != IIO_CHAN_INFO_RAW)
> + return -EINVAL;
> +
> + mutex_lock(&idev->mlock);
> +
> + /* Setup the configuration we want to use */
> + regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0,
> + MX25_ADCQ_ITEM(0, chan->address));
> +
> + mx25_gcq_enable_eoq(priv);
> +
> + /* Trigger queue for one run */
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS,
> + MX25_ADCQ_CR_FQS);
> +
> + timeout = wait_for_completion_interruptible_timeout(&priv->completed,
> + MX25_GCQ_TIMEOUT);
> + if (timeout < 0) {
> + dev_err(&idev->dev, "ADC wait for measurement failed\n");
> + ret = timeout;
> + goto out;
> + } else if (timeout == 0) {
> + dev_err(&idev->dev, "ADC timed out\n");
> + ret = -ETIMEDOUT;
> + goto out;
> + }
> +
> + regmap_read(priv->regs, MX25_ADCQ_FIFO, &data);
> + *val = MX25_ADCQ_FIFO_DATA(data);
> +
> + ret = IIO_VAL_INT;
> +
> +out:
> + mutex_unlock(&idev->mlock);
> +
> + return ret;
> +}
> +
> +static const struct iio_info mx25_gcq_iio_info = {
> + .read_raw = mx25_gcq_read_raw,
> +};
> +
> +static const struct regmap_config mx25_gcq_regconfig = {
> + .max_register = 0x5c,
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> +};
> +
> +static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
> + struct mx25_gcq_priv *priv)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct device_node *child;
> + struct device *dev = &pdev->dev;
> + int ret;
> + int i;
> +
> + /* Setup all configurations registers with a default conversion
> + * configuration for each input */
> + for (i = 0; i != MX25_NUM_CFGS; ++i)
> + regmap_write(priv->regs, MX25_ADCQ_CFG(i),
> + MX25_ADCQ_CFG_YPLL_OFF |
> + MX25_ADCQ_CFG_XNUR_OFF |
> + MX25_ADCQ_CFG_XPUL_OFF |
> + MX25_ADCQ_CFG_REFP_INT |
> + (i << 4) |
> + MX25_ADCQ_CFG_REFN_NGND2);
> +
> + for_each_child_of_node(np, child) {
> + u32 reg;
> + u32 refn;
> + u32 refp;
> +
> + ret = of_property_read_u32(child, "reg", ®);
> + if (ret) {
> + dev_err(dev, "Failed to get reg property\n");
> + return ret;
> + }
> + if (reg > MX25_NUM_CFGS) {
> + dev_err(dev, "reg value is greater than the number of available configuration registers\n");
> + return -EINVAL;
> + }
> +
> + ret = of_property_read_u32(child, "fsl,adc-refn", &refn);
> + if (ret) {
> + dev_err(dev, "Failed to get fsl,adc-refn property\n");
> + return ret;
> + }
> + if (refn < 0 || refn > 3) {
> + dev_err(dev, "Invalid fsl,adc-refn property value %d\n",
> + refn);
> + return -EINVAL
> + }
> +
> + ret = of_property_read_u32(child, "fsl,adc-refp", &refp);
> + if (ret) {
> + dev_err(dev, "Failed to get fsl,adc-refp property\n");
> + return ret;
> + }
> + if (refp < 0 || refp > 3) {
> + dev_err(dev, "Invalid fsl,adc-refp property value %d\n",
> + refp);
> + return -EINVAL;
> + }
> +
> + regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg),
> + MX25_ADCQ_CFG_REFP_MASK |
> + MX25_ADCQ_CFG_REFN_MASK,
> + (refp << 7) | (refn << 2));
> + }
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR,
> + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST,
> + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST);
> +
> + regmap_write(priv->regs, MX25_ADCQ_CR,
> + MX25_ADCQ_CR_PDMSK |
> + MX25_ADCQ_CR_QSM_FQS);
> +
> + return 0;
> +}
> +
> +static int mx25_gcq_probe(struct platform_device *pdev)
> +{
> + struct iio_dev *idev;
> + struct mx25_gcq_priv *priv;
> + struct resource *res;
> + struct device *dev = &pdev->dev;
> + int ret;
> + void __iomem *mem;
> +
> + idev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
> + if (!idev)
> + return -ENOMEM;
> +
> + priv = iio_priv(idev);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
check this succeeded.
> + mem = devm_ioremap_resource(dev, res);
> + if (!mem)
> + return -ENOMEM;
> +
> + priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig);
> + if (IS_ERR(priv->regs)) {
> + dev_err(dev, "Failed to initialize regmap\n");
> + return PTR_ERR(priv->regs);
> + }
> +
> + init_completion(&priv->completed);
> +
> + ret = mx25_gcq_setup_cfgs(pdev, priv);
> + if (ret)
This looks suspect given you haven't actually gained
the irq yet (any time we have a return ret after a goto
something tends to be interesting!)
> + goto err_irq_free;
> +
> + priv->clk = mx25_tsadc_get_ipg(pdev->dev.parent);
> + ret = clk_prepare_enable(priv->clk);
> + if (ret) {
> + dev_err(dev, "Failed to enable clock\n");
> + return ret;
> + }
> +
> + priv->irq = platform_get_irq(pdev, 0);
> + if (priv->irq <= 0) {
> + dev_err(dev, "Failed to get IRQ\n");
> + err = priv->irq;
> + goto err_clk_unprepare;
> + }
> +
> + ret = request_irq(priv->irq, mx25_gcq_irq, NULL, pdev->name,
> + priv);
As mentioned above, could this be a threaded irq without
causing significant problems?
> + if (ret) {
> + dev_err(dev, "Failed requesting IRQ\n");
> + goto err_clk_unprepare;
> + }
> +
> + idev->dev.parent = &pdev->dev;
> + idev->channels = mx25_gcq_channels;
> + idev->num_channels = ARRAY_SIZE(mx25_gcq_channels);
> + idev->info = &mx25_gcq_iio_info;
> +
> + ret = iio_device_register(idev);
> + if (ret) {
> + dev_err(dev, "Failed to register iio device\n");
> + goto err_irq_free;
> + }
> +
> + platform_set_drvdata(pdev, priv);
> +
> + return 0;
> +
> +err_irq_free:
> + free_irq(priv->irq, (void *)priv);
> +
> +err_clk_unprepare:
> + clk_disable_unprepare(priv->clk);
> +
> + return ret;
> +}
> +
> +static int mx25_gcq_remove(struct platform_device *pdev)
> +{
> + struct mx25_gcq_priv *priv = platform_get_drvdata(pdev);
> + struct iio_dev *idev = iio_priv_to_dev(pdev);
> +
> + iio_device_unregister(idev);
> + free_irq(priv->irq, (void *)priv);
> + clk_disable_unprepare(priv->clk);
> +
> + return 0;
> +}
> +
> +static struct of_device_id mx25_gcq_ids[] = {
> + { .compatible = "fsl,imx25-gcq", },
> + { /* Senitel */ }
> +};
> +
> +static struct platform_driver mx25_gcq_driver = {
> + .driver = {
> + .name = "mx25-gcq",
> + .owner = THIS_MODULE,
> + .of_match_table = mx25_gcq_ids,
> + },
> + .probe = mx25_gcq_probe,
> + .remove = mx25_gcq_remove,
> +};
> +module_platform_driver(mx25_gcq_driver);
> +
> +MODULE_DESCRIPTION("ADC driver for Freescale mx25");
> +MODULE_AUTHOR("Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
>
^ permalink raw reply
* Re: [PATCH v3 3/6] iio: adc: fsl,imx25-gcq driver
From: Jonathan Cameron @ 2014-06-21 10:45 UTC (permalink / raw)
To: Denis Carikli, Shawn Guo, Samuel Ortiz, Dmitry Torokhov,
Fabio Estevam, Peter Meerwald, Hartmut Knaack
Cc: Eric Bénard, Sascha Hauer,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Lee Jones,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA, Lars-Peter Clausen,
Markus Pargmann
In-Reply-To: <1402910933-20534-3-git-send-email-denis-fO0SIAKYzcbQT0dZR+AlfA@public.gmane.org>
On 16/06/14 10:28, Denis Carikli wrote:
> From: Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
>
> This is a conversion queue driver for the mx25 SoC. It uses the central
> ADC which is used by two seperate independent queues. This driver
> prepares different conversion configurations for each possible input.
> For a conversion it creates a conversionqueue of one item with the
> correct configuration for the chosen channel. It then executes the queue
> once and disables the conversion queue afterwards.
>
> The reference voltages are configurable through devicetree subnodes,
> depending on the connections of the ADC inputs.
>
> Signed-off-by: Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> Signed-off-by: Denis Carikli <denis-fO0SIAKYzcbQT0dZR+AlfA@public.gmane.org>
> ---
> Changelog v2->v3:
> - Fixed compilation: I forgott to tell that this IIO patch was untested
> in the previous cover letter. Now it is tested at runtime:
> I left the touchscreen connected but I configured the TSC as an ADC
> instead, set the refp to internal reference, and the refn to ngnd_adc(3)
> for the first 4 channels(xp,yp,xn,yn) and observed the values changing
> while touching the resistive 4-wire touchscreen.
> - MX25_IIO_CHAN now became MX25_GCQ_CHAN and its .address is now gone
> and the code using it adapted.
> - The instances of struct iio_dev were renamed from idev to indio_dev.
> - regmap_read return value is now checked in mx25_gcq_read_raw.
> - Comparisons with MX25_NUM_CFGS are now fixed
> - Cosmetics fix in a multiline comment.
> ---
> .../devicetree/bindings/iio/adc/fsl,imx25-gcq.txt | 54 ++++
> drivers/iio/adc/Kconfig | 7 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/fsl-imx25-gcq.c | 342 ++++++++++++++++++++
> 4 files changed, 404 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> create mode 100644 drivers/iio/adc/fsl-imx25-gcq.c
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt b/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> new file mode 100644
> index 0000000..333fc55
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> @@ -0,0 +1,54 @@
> +Freescale i.MX25 ADC GCQ device
> +
> +This is a generic conversion queue device that can convert any of the analog
> +inputs using the ADC unit of the i.MX25.
> +
> +Required properties:
> + - compatible: Should be "fsl,imx25-gcq".
> + - reg: Should be the register range of the module.
> + - interrupts: Should be the interrupt number of the module. Typically this is <1>.
> + - interrupt-parent: phandle to the tsadc module of the i.MX25.
> + - #address-cells: Should be <1> (setting for the subnodes)
> + - #size-cells: Should be <0> (setting for the subnodes)
> +
> +Optionally you can define subnodes which define the positive and negative
> +reference voltage for one of the analog inputs.
> +
> +Required properties for subnodes:
> + - reg: Should be the number of the analog input.
> + 0: xp
> + 1: yp
> + 2: xn
> + 3: yn
> + 4: wiper
> + 5: inaux0
> + 6: inaux1
> + 7: inaux2
> + - fsl,adc-refp: Positive reference input
> + 0: yp
> + 1: xp
> + 2: External reference
> + 3: Internal reference
> + - fsl,adc-refn: Negative reference input
> + 0: xn
> + 1: yn
> + 2: ngnd_adc
> + 3: ngnd_adc
> +
> +
> +Example:
> +
> + adc: adc@50030800 {
> + compatible = "fsl,imx25-gcq";
> + reg = <0x50030800 0x60>;
> + interrupt-parent = <&tscadc>;
> + interrupts = <1>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + inaux@5 {
> + reg = <5>;
> + fsl,adc-refp = <3>;
> + fsl,adc-refn = <3>;
> + };
> + };
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index a80d236..58efb8d 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -125,6 +125,13 @@ config EXYNOS_ADC
> of SoCs for drivers such as the touchscreen and hwmon to use to share
> this resource.
>
> +config FSL_MX25_ADC
> + tristate "Freescale MX25 ADC driver"
> + depends on MFD_MX25_TSADC
> + help
> + Generic Conversion Queue driver used for general purpose ADC in the
> + MX25. This driver supports single measurements using the MX25 ADC.
> +
> config LP8788_ADC
> tristate "LP8788 ADC driver"
> depends on MFD_LP8788
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 9d60f2d..2767fd6 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_AD7887) += ad7887.o
> obj-$(CONFIG_AD799X) += ad799x.o
> obj-$(CONFIG_AT91_ADC) += at91_adc.o
> obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
> +obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o
> obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
> obj-$(CONFIG_MAX1363) += max1363.o
> obj-$(CONFIG_MCP320X) += mcp320x.o
> diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c
> new file mode 100644
> index 0000000..685cac5
> --- /dev/null
> +++ b/drivers/iio/adc/fsl-imx25-gcq.c
> @@ -0,0 +1,342 @@
> +/*
> + * Copyright 2014 Markus Pargmann, Pengutronix <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + *
> + * This is the driver for the imx25 GCQ (Generic Conversion Queue)
> + * connected to the imx25 ADC.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/iio/iio.h>
> +#include <linux/mfd/imx25-tsadc.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#define MX25_GCQ_TIMEOUT (msecs_to_jiffies(2000))
> +
> +enum mx25_gcq_cfgs {
> + MX25_CFG_XP = 0,
> + MX25_CFG_YP,
> + MX25_CFG_XN,
> + MX25_CFG_YN,
> + MX25_CFG_WIPER,
> + MX25_CFG_INAUX0,
> + MX25_CFG_INAUX1,
> + MX25_CFG_INAUX2,
> + MX25_NUM_CFGS,
> +};
> +
> +struct mx25_gcq_priv {
> + struct regmap *regs;
> + struct completion completed;
> + unsigned int settling_time;
> + struct clk *clk;
> + int irq;
> +};
> +
> +#define MX25_CQG_CHAN(chan, id) {\
> + .type = IIO_VOLTAGE,\
> + .indexed = 1,\
> + .channel = chan,\
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .datasheet_name = id, \
> + }
> +
> +static const struct iio_chan_spec mx25_gcq_channels[MX25_NUM_CFGS] = {
> + MX25_CQG_CHAN(0, "xp"),
> + MX25_CQG_CHAN(1, "yp"),
> + MX25_CQG_CHAN(2, "xn"),
> + MX25_CQG_CHAN(3, "yn"),
> + MX25_CQG_CHAN(4, "wiper"),
> + MX25_CQG_CHAN(5, "inaux0"),
> + MX25_CQG_CHAN(6, "inaux1"),
> + MX25_CQG_CHAN(7, "inaux2"),
> +};
> +
> +static void mx25_gcq_disable_eoq(struct mx25_gcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ,
> + MX25_ADCQ_MR_EOQ_IRQ);
> +}
> +
> +static void mx25_gcq_enable_eoq(struct mx25_gcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ, 0);
> +}
> +
> +static irqreturn_t mx25_gcq_irq(int irq, void *data)
> +{
> + struct mx25_gcq_priv *priv = data;
> + u32 stats;
> +
> + regmap_read(priv->regs, MX25_ADCQ_SR, &stats);
> +
> + if (stats & MX25_ADCQ_SR_EOQ) {
> + mx25_gcq_disable_eoq(priv);
> + complete(&priv->completed);
> + }
> +
> + /* Disable conversion queue run */
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, 0);
> +
> + /* Acknowledge all possible irqs */
> + regmap_write(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR |
> + MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | MX25_ADCQ_SR_EOQ |
> + MX25_ADCQ_SR_PD);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int mx25_gcq_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val, int *val2,
> + long mask)
> +{
> + struct mx25_gcq_priv *priv = iio_priv(indio_dev);
> + long timeout;
> + u32 data;
> + int ret;
> +
> + if (mask != IIO_CHAN_INFO_RAW)
> + return -EINVAL;
> +
> + mutex_lock(&indio_dev->mlock);
> +
> + /* Setup the configuration we want to use */
> + regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0,
> + MX25_ADCQ_ITEM(0, chan->channel));
> +
> + mx25_gcq_enable_eoq(priv);
> +
> + /* Trigger queue for one run */
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS,
> + MX25_ADCQ_CR_FQS);
> +
> + timeout = wait_for_completion_interruptible_timeout(&priv->completed,
> + MX25_GCQ_TIMEOUT);
> + if (timeout < 0) {
> + dev_err(&indio_dev->dev, "ADC wait for measurement failed\n");
> + ret = timeout;
> + goto out;
> + } else if (timeout == 0) {
> + dev_err(&indio_dev->dev, "ADC timed out\n");
> + ret = -ETIMEDOUT;
> + goto out;
> + }
> +
> + ret = regmap_read(priv->regs, MX25_ADCQ_FIFO, &data);
> + if (ret)
> + return ret;
> +
> + *val = MX25_ADCQ_FIFO_DATA(data);
> +
> + ret = IIO_VAL_INT;
> +
> +out:
> + mutex_unlock(&indio_dev->mlock);
> +
> + return ret;
> +}
> +
> +static const struct iio_info mx25_gcq_iio_info = {
> + .read_raw = mx25_gcq_read_raw,
> +};
> +
> +static const struct regmap_config mx25_gcq_regconfig = {
> + .max_register = 0x5c,
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> +};
> +
> +static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
> + struct mx25_gcq_priv *priv)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct device_node *child;
> + struct device *dev = &pdev->dev;
> + int ret;
> + int i;
> +
> + /* Setup all configurations registers with a default conversion
> + * configuration for each input
> + */
> + for (i = 0; i < MX25_NUM_CFGS; ++i)
> + regmap_write(priv->regs, MX25_ADCQ_CFG(i),
> + MX25_ADCQ_CFG_YPLL_OFF |
> + MX25_ADCQ_CFG_XNUR_OFF |
> + MX25_ADCQ_CFG_XPUL_OFF |
> + MX25_ADCQ_CFG_REFP_INT |
> + (i << 4) |
> + MX25_ADCQ_CFG_REFN_NGND2);
> +
> + for_each_child_of_node(np, child) {
> + u32 reg;
> + u32 refn;
> + u32 refp;
> +
> + ret = of_property_read_u32(child, "reg", ®);
> + if (ret) {
> + dev_err(dev, "Failed to get reg property\n");
> + return ret;
> + }
> +
> + if (reg >= MX25_NUM_CFGS) {
> + dev_err(dev, "reg value is greater than the number of available configuration registers\n");
> + return -EINVAL;
> + }
> +
> + ret = of_property_read_u32(child, "fsl,adc-refn", &refn);
> + if (ret) {
> + dev_err(dev, "Failed to get fsl,adc-refn property\n");
> + return ret;
> + }
> + if (refn < 0 || refn > 3) {
> + dev_err(dev, "Invalid fsl,adc-refn property value %d\n",
> + refn);
> + return -EINVAL;
> + }
> +
> + ret = of_property_read_u32(child, "fsl,adc-refp", &refp);
> + if (ret) {
> + dev_err(dev, "Failed to get fsl,adc-refp property\n");
> + return ret;
> + }
> + if (refp < 0 || refp > 3) {
> + dev_err(dev, "Invalid fsl,adc-refp property value %d\n",
> + refp);
> + return -EINVAL;
> + }
> +
> + regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg),
> + MX25_ADCQ_CFG_REFP_MASK |
> + MX25_ADCQ_CFG_REFN_MASK,
> + (refp << 7) | (refn << 2));
> + }
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR,
> + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST,
> + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST);
> +
> + regmap_write(priv->regs, MX25_ADCQ_CR,
> + MX25_ADCQ_CR_PDMSK |
> + MX25_ADCQ_CR_QSM_FQS);
> +
> + return 0;
> +}
> +
> +static int mx25_gcq_probe(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev;
> + struct mx25_gcq_priv *priv;
> + struct resource *res;
> + struct device *dev = &pdev->dev;
> + int ret;
> + void __iomem *mem;
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + priv = iio_priv(indio_dev);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + mem = devm_ioremap_resource(dev, res);
> + if (!mem)
> + return -ENOMEM;
> +
> + priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig);
> + if (IS_ERR(priv->regs)) {
> + dev_err(dev, "Failed to initialize regmap\n");
> + return PTR_ERR(priv->regs);
> + }
> +
> + init_completion(&priv->completed);
> +
> + ret = mx25_gcq_setup_cfgs(pdev, priv);
> + if (ret)
> + goto err_irq_free;
> +
> + priv->clk = mx25_tsadc_get_ipg(pdev->dev.parent);
> + ret = clk_prepare_enable(priv->clk);
> + if (ret) {
> + dev_err(dev, "Failed to enable clock\n");
> + return ret;
> + }
> +
> + priv->irq = platform_get_irq(pdev, 0);
> + if (priv->irq <= 0) {
> + dev_err(dev, "Failed to get IRQ\n");
> + ret = priv->irq;
> + goto err_clk_unprepare;
> + }
> +
> + ret = request_irq(priv->irq, mx25_gcq_irq, 0, pdev->name,
> + priv);
> + if (ret) {
> + dev_err(dev, "Failed requesting IRQ\n");
> + goto err_clk_unprepare;
> + }
> +
> + indio_dev->dev.parent = &pdev->dev;
> + indio_dev->channels = mx25_gcq_channels;
> + indio_dev->num_channels = ARRAY_SIZE(mx25_gcq_channels);
> + indio_dev->info = &mx25_gcq_iio_info;
> +
> + ret = iio_device_register(indio_dev);
> + if (ret) {
> + dev_err(dev, "Failed to register iio device\n");
> + goto err_irq_free;
> + }
> +
> + platform_set_drvdata(pdev, priv);
> +
> + return 0;
> +
> +err_irq_free:
> + free_irq(priv->irq, (void *)priv);
> +
> +err_clk_unprepare:
> + clk_disable_unprepare(priv->clk);
> +
> + return ret;
> +}
> +
> +static int mx25_gcq_remove(struct platform_device *pdev)
> +{
> + struct mx25_gcq_priv *priv = platform_get_drvdata(pdev);
> + struct iio_dev *indio_dev = iio_priv_to_dev(pdev);
> +
> + iio_device_unregister(indio_dev);
> + free_irq(priv->irq, (void *)priv);
> + clk_disable_unprepare(priv->clk);
> +
> + return 0;
> +}
> +
> +static struct of_device_id mx25_gcq_ids[] = {
> + { .compatible = "fsl,imx25-gcq", },
> + { /* Sentinel */ }
> +};
> +
> +static struct platform_driver mx25_gcq_driver = {
> + .driver = {
> + .name = "mx25-gcq",
> + .owner = THIS_MODULE,
> + .of_match_table = mx25_gcq_ids,
> + },
> + .probe = mx25_gcq_probe,
> + .remove = mx25_gcq_remove,
> +};
> +module_platform_driver(mx25_gcq_driver);
> +
> +MODULE_DESCRIPTION("ADC driver for Freescale mx25");
> +MODULE_AUTHOR("Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
>
^ permalink raw reply
* Re: [PATCH v3 3/6] iio: adc: fsl,imx25-gcq driver
From: Jonathan Cameron @ 2014-06-21 10:51 UTC (permalink / raw)
To: Denis Carikli, Shawn Guo, Samuel Ortiz, Dmitry Torokhov,
Fabio Estevam, Peter Meerwald, Hartmut Knaack
Cc: Eric Bénard, Sascha Hauer,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Lee Jones,
linux-input-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA, Lars-Peter Clausen,
Markus Pargmann
In-Reply-To: <1402910933-20534-3-git-send-email-denis-fO0SIAKYzcbQT0dZR+AlfA@public.gmane.org>
On 16/06/14 10:28, Denis Carikli wrote:
> From: Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
>
> This is a conversion queue driver for the mx25 SoC. It uses the central
> ADC which is used by two seperate independent queues. This driver
> prepares different conversion configurations for each possible input.
> For a conversion it creates a conversionqueue of one item with the
> correct configuration for the chosen channel. It then executes the queue
> once and disables the conversion queue afterwards.
>
> The reference voltages are configurable through devicetree subnodes,
> depending on the connections of the ADC inputs.
>
> Signed-off-by: Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> Signed-off-by: Denis Carikli <denis-fO0SIAKYzcbQT0dZR+AlfA@public.gmane.org>
Having been unobservant and hence commented on previous version, I've
just quickly lifted the remaining points over from that review to here.
One little bug, but otherwise just small suggestions.
Jonathan
> ---
> Changelog v2->v3:
> - Fixed compilation: I forgott to tell that this IIO patch was untested
> in the previous cover letter. Now it is tested at runtime:
> I left the touchscreen connected but I configured the TSC as an ADC
> instead, set the refp to internal reference, and the refn to ngnd_adc(3)
> for the first 4 channels(xp,yp,xn,yn) and observed the values changing
> while touching the resistive 4-wire touchscreen.
> - MX25_IIO_CHAN now became MX25_GCQ_CHAN and its .address is now gone
> and the code using it adapted.
> - The instances of struct iio_dev were renamed from idev to indio_dev.
> - regmap_read return value is now checked in mx25_gcq_read_raw.
> - Comparisons with MX25_NUM_CFGS are now fixed
> - Cosmetics fix in a multiline comment.
> ---
> .../devicetree/bindings/iio/adc/fsl,imx25-gcq.txt | 54 ++++
> drivers/iio/adc/Kconfig | 7 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/fsl-imx25-gcq.c | 342 ++++++++++++++++++++
> 4 files changed, 404 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> create mode 100644 drivers/iio/adc/fsl-imx25-gcq.c
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt b/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> new file mode 100644
> index 0000000..333fc55
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/fsl,imx25-gcq.txt
> @@ -0,0 +1,54 @@
> +Freescale i.MX25 ADC GCQ device
> +
> +This is a generic conversion queue device that can convert any of the analog
> +inputs using the ADC unit of the i.MX25.
> +
> +Required properties:
> + - compatible: Should be "fsl,imx25-gcq".
> + - reg: Should be the register range of the module.
> + - interrupts: Should be the interrupt number of the module. Typically this is <1>.
> + - interrupt-parent: phandle to the tsadc module of the i.MX25.
> + - #address-cells: Should be <1> (setting for the subnodes)
> + - #size-cells: Should be <0> (setting for the subnodes)
> +
> +Optionally you can define subnodes which define the positive and negative
> +reference voltage for one of the analog inputs.
> +
> +Required properties for subnodes:
> + - reg: Should be the number of the analog input.
> + 0: xp
> + 1: yp
> + 2: xn
> + 3: yn
> + 4: wiper
> + 5: inaux0
> + 6: inaux1
> + 7: inaux2
> + - fsl,adc-refp: Positive reference input
> + 0: yp
> + 1: xp
> + 2: External reference
> + 3: Internal reference
> + - fsl,adc-refn: Negative reference input
> + 0: xn
> + 1: yn
> + 2: ngnd_adc
> + 3: ngnd_adc
> +
> +
> +Example:
> +
> + adc: adc@50030800 {
> + compatible = "fsl,imx25-gcq";
> + reg = <0x50030800 0x60>;
> + interrupt-parent = <&tscadc>;
> + interrupts = <1>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + inaux@5 {
> + reg = <5>;
> + fsl,adc-refp = <3>;
> + fsl,adc-refn = <3>;
> + };
> + };
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index a80d236..58efb8d 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -125,6 +125,13 @@ config EXYNOS_ADC
> of SoCs for drivers such as the touchscreen and hwmon to use to share
> this resource.
>
> +config FSL_MX25_ADC
> + tristate "Freescale MX25 ADC driver"
> + depends on MFD_MX25_TSADC
> + help
> + Generic Conversion Queue driver used for general purpose ADC in the
> + MX25. This driver supports single measurements using the MX25 ADC.
> +
> config LP8788_ADC
> tristate "LP8788 ADC driver"
> depends on MFD_LP8788
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 9d60f2d..2767fd6 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_AD7887) += ad7887.o
> obj-$(CONFIG_AD799X) += ad799x.o
> obj-$(CONFIG_AT91_ADC) += at91_adc.o
> obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o
> +obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o
> obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
> obj-$(CONFIG_MAX1363) += max1363.o
> obj-$(CONFIG_MCP320X) += mcp320x.o
> diff --git a/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c
> new file mode 100644
> index 0000000..685cac5
> --- /dev/null
> +++ b/drivers/iio/adc/fsl-imx25-gcq.c
> @@ -0,0 +1,342 @@
> +/*
> + * Copyright 2014 Markus Pargmann, Pengutronix <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
> + *
> + * The code contained herein is licensed under the GNU General Public
> + * License. You may obtain a copy of the GNU General Public License
> + * Version 2 or later at the following locations:
> + *
> + * http://www.opensource.org/licenses/gpl-license.html
> + * http://www.gnu.org/copyleft/gpl.html
> + *
> + * This is the driver for the imx25 GCQ (Generic Conversion Queue)
> + * connected to the imx25 ADC.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/iio/iio.h>
> +#include <linux/mfd/imx25-tsadc.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#define MX25_GCQ_TIMEOUT (msecs_to_jiffies(2000))
> +
> +enum mx25_gcq_cfgs {
> + MX25_CFG_XP = 0,
> + MX25_CFG_YP,
> + MX25_CFG_XN,
> + MX25_CFG_YN,
> + MX25_CFG_WIPER,
> + MX25_CFG_INAUX0,
> + MX25_CFG_INAUX1,
> + MX25_CFG_INAUX2,
> + MX25_NUM_CFGS,
> +};
> +
> +struct mx25_gcq_priv {
> + struct regmap *regs;
> + struct completion completed;
> + unsigned int settling_time;
> + struct clk *clk;
> + int irq;
> +};
> +
> +#define MX25_CQG_CHAN(chan, id) {\
> + .type = IIO_VOLTAGE,\
> + .indexed = 1,\
> + .channel = chan,\
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .datasheet_name = id, \
> + }
> +
> +static const struct iio_chan_spec mx25_gcq_channels[MX25_NUM_CFGS] = {
> + MX25_CQG_CHAN(0, "xp"),
> + MX25_CQG_CHAN(1, "yp"),
> + MX25_CQG_CHAN(2, "xn"),
> + MX25_CQG_CHAN(3, "yn"),
> + MX25_CQG_CHAN(4, "wiper"),
> + MX25_CQG_CHAN(5, "inaux0"),
> + MX25_CQG_CHAN(6, "inaux1"),
> + MX25_CQG_CHAN(7, "inaux2"),
> +};
> +
> +static void mx25_gcq_disable_eoq(struct mx25_gcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ,
> + MX25_ADCQ_MR_EOQ_IRQ);
> +}
> +
> +static void mx25_gcq_enable_eoq(struct mx25_gcq_priv *priv)
> +{
> + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ, 0);
> +}
> +
> +static irqreturn_t mx25_gcq_irq(int irq, void *data)
> +{
> + struct mx25_gcq_priv *priv = data;
> + u32 stats;
Not that much in here, but perhaps still makes sense for this to be a threaded
irq?
> +
> + regmap_read(priv->regs, MX25_ADCQ_SR, &stats);
> +
> + if (stats & MX25_ADCQ_SR_EOQ) {
> + mx25_gcq_disable_eoq(priv);
> + complete(&priv->completed);
> + }
> +
> + /* Disable conversion queue run */
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, 0);
> +
> + /* Acknowledge all possible irqs */
> + regmap_write(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR |
> + MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | MX25_ADCQ_SR_EOQ |
> + MX25_ADCQ_SR_PD);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int mx25_gcq_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val, int *val2,
> + long mask)
> +{
> + struct mx25_gcq_priv *priv = iio_priv(indio_dev);
> + long timeout;
> + u32 data;
> + int ret;
> +
> + if (mask != IIO_CHAN_INFO_RAW)
> + return -EINVAL;
> +
> + mutex_lock(&indio_dev->mlock);
> +
> + /* Setup the configuration we want to use */
> + regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0,
> + MX25_ADCQ_ITEM(0, chan->channel));
> +
> + mx25_gcq_enable_eoq(priv);
> +
> + /* Trigger queue for one run */
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS,
> + MX25_ADCQ_CR_FQS);
> +
> + timeout = wait_for_completion_interruptible_timeout(&priv->completed,
> + MX25_GCQ_TIMEOUT);
> + if (timeout < 0) {
> + dev_err(&indio_dev->dev, "ADC wait for measurement failed\n");
> + ret = timeout;
> + goto out;
> + } else if (timeout == 0) {
> + dev_err(&indio_dev->dev, "ADC timed out\n");
> + ret = -ETIMEDOUT;
> + goto out;
> + }
> +
> + ret = regmap_read(priv->regs, MX25_ADCQ_FIFO, &data);
> + if (ret)
> + return ret;
> +
> + *val = MX25_ADCQ_FIFO_DATA(data);
> +
> + ret = IIO_VAL_INT;
> +
> +out:
> + mutex_unlock(&indio_dev->mlock);
> +
> + return ret;
> +}
> +
> +static const struct iio_info mx25_gcq_iio_info = {
> + .read_raw = mx25_gcq_read_raw,
> +};
> +
> +static const struct regmap_config mx25_gcq_regconfig = {
> + .max_register = 0x5c,
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = 4,
> +};
> +
> +static int mx25_gcq_setup_cfgs(struct platform_device *pdev,
> + struct mx25_gcq_priv *priv)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct device_node *child;
> + struct device *dev = &pdev->dev;
> + int ret;
> + int i;
> +
> + /* Setup all configurations registers with a default conversion
> + * configuration for each input
> + */
> + for (i = 0; i < MX25_NUM_CFGS; ++i)
> + regmap_write(priv->regs, MX25_ADCQ_CFG(i),
> + MX25_ADCQ_CFG_YPLL_OFF |
> + MX25_ADCQ_CFG_XNUR_OFF |
> + MX25_ADCQ_CFG_XPUL_OFF |
> + MX25_ADCQ_CFG_REFP_INT |
> + (i << 4) |
> + MX25_ADCQ_CFG_REFN_NGND2);
> +
> + for_each_child_of_node(np, child) {
> + u32 reg;
> + u32 refn;
> + u32 refp;
> +
> + ret = of_property_read_u32(child, "reg", ®);
> + if (ret) {
> + dev_err(dev, "Failed to get reg property\n");
> + return ret;
> + }
> +
> + if (reg >= MX25_NUM_CFGS) {
> + dev_err(dev, "reg value is greater than the number of available configuration registers\n");
> + return -EINVAL;
> + }
> +
> + ret = of_property_read_u32(child, "fsl,adc-refn", &refn);
> + if (ret) {
> + dev_err(dev, "Failed to get fsl,adc-refn property\n");
> + return ret;
> + }
> + if (refn < 0 || refn > 3) {
> + dev_err(dev, "Invalid fsl,adc-refn property value %d\n",
> + refn);
> + return -EINVAL;
> + }
> +
> + ret = of_property_read_u32(child, "fsl,adc-refp", &refp);
> + if (ret) {
> + dev_err(dev, "Failed to get fsl,adc-refp property\n");
> + return ret;
> + }
> + if (refp < 0 || refp > 3) {
> + dev_err(dev, "Invalid fsl,adc-refp property value %d\n",
> + refp);
> + return -EINVAL;
> + }
> +
> + regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg),
> + MX25_ADCQ_CFG_REFP_MASK |
> + MX25_ADCQ_CFG_REFN_MASK,
> + (refp << 7) | (refn << 2));
> + }
> + regmap_update_bits(priv->regs, MX25_ADCQ_CR,
> + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST,
> + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST);
> +
> + regmap_write(priv->regs, MX25_ADCQ_CR,
> + MX25_ADCQ_CR_PDMSK |
> + MX25_ADCQ_CR_QSM_FQS);
> +
> + return 0;
> +}
> +
> +static int mx25_gcq_probe(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev;
> + struct mx25_gcq_priv *priv;
> + struct resource *res;
> + struct device *dev = &pdev->dev;
> + int ret;
> + void __iomem *mem;
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + priv = iio_priv(indio_dev);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Check this succeeded?
> + mem = devm_ioremap_resource(dev, res);
> + if (!mem)
> + return -ENOMEM;
> +
> + priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig);
> + if (IS_ERR(priv->regs)) {
> + dev_err(dev, "Failed to initialize regmap\n");
> + return PTR_ERR(priv->regs);
> + }
> +
> + init_completion(&priv->completed);
> +
> + ret = mx25_gcq_setup_cfgs(pdev, priv);
> + if (ret)
Why free an irq here that you haven't got yet? Guessing this is left
over from a different ordering of the code.
> + goto err_irq_free;
> +
> + priv->clk = mx25_tsadc_get_ipg(pdev->dev.parent);
> + ret = clk_prepare_enable(priv->clk);
> + if (ret) {
> + dev_err(dev, "Failed to enable clock\n");
> + return ret;
> + }
> +
> + priv->irq = platform_get_irq(pdev, 0);
> + if (priv->irq <= 0) {
> + dev_err(dev, "Failed to get IRQ\n");
> + ret = priv->irq;
> + goto err_clk_unprepare;
> + }
> +
> + ret = request_irq(priv->irq, mx25_gcq_irq, 0, pdev->name,
> + priv);
Any significant loss in making this a threaded irq? Just a general good
practice question if it doesn't matter.
> + if (ret) {
> + dev_err(dev, "Failed requesting IRQ\n");
> + goto err_clk_unprepare;
> + }
> +
> + indio_dev->dev.parent = &pdev->dev;
> + indio_dev->channels = mx25_gcq_channels;
> + indio_dev->num_channels = ARRAY_SIZE(mx25_gcq_channels);
> + indio_dev->info = &mx25_gcq_iio_info;
> +
> + ret = iio_device_register(indio_dev);
> + if (ret) {
> + dev_err(dev, "Failed to register iio device\n");
> + goto err_irq_free;
> + }
> +
> + platform_set_drvdata(pdev, priv);
> +
> + return 0;
> +
> +err_irq_free:
> + free_irq(priv->irq, (void *)priv);
> +
> +err_clk_unprepare:
> + clk_disable_unprepare(priv->clk);
> +
> + return ret;
> +}
> +
> +static int mx25_gcq_remove(struct platform_device *pdev)
> +{
> + struct mx25_gcq_priv *priv = platform_get_drvdata(pdev);
> + struct iio_dev *indio_dev = iio_priv_to_dev(pdev);
> +
> + iio_device_unregister(indio_dev);
> + free_irq(priv->irq, (void *)priv);
> + clk_disable_unprepare(priv->clk);
> +
> + return 0;
> +}
> +
> +static struct of_device_id mx25_gcq_ids[] = {
> + { .compatible = "fsl,imx25-gcq", },
> + { /* Sentinel */ }
> +};
> +
> +static struct platform_driver mx25_gcq_driver = {
> + .driver = {
> + .name = "mx25-gcq",
> + .owner = THIS_MODULE,
> + .of_match_table = mx25_gcq_ids,
> + },
> + .probe = mx25_gcq_probe,
> + .remove = mx25_gcq_remove,
> +};
> +module_platform_driver(mx25_gcq_driver);
> +
> +MODULE_DESCRIPTION("ADC driver for Freescale mx25");
> +MODULE_AUTHOR("Markus Pargmann <mpa-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
>
^ permalink raw reply
* Re: Mapping of F11 and F12 on new lenovo laptops and Lenovo Compact Keyboard
From: Jamie Lentin @ 2014-06-21 13:10 UTC (permalink / raw)
To: Hans de Goede
Cc: Jiri Kosina, Antonio Ospite, linux-usb,
platform-driver-x86@vger.kernel.org
In-Reply-To: <53A42CC2.9010608@redhat.com>
On Fri, 20 Jun 2014, Hans de Goede wrote:
> Hi,
>
> On 06/20/2014 02:00 PM, Jamie Lentin wrote:
>> On Thu, 19 Jun 2014, Hans de Goede wrote:
>>
>>> Hi Jamie,
>>>
>>> I saw your patch-set for the Lenovo Compact Keyboard on the lwn.net
>>> kernel page.
>>>
>>> This spiked my interest as I'm the author of this patch:
>>> https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/drivers/platform/x86/thinkpad_acpi.c?id=8b9dd4fab26a0f328420cbda0845a325f45bcd92
>>>
>>> Which adds mapping for the F9 - F12 on the Lenovo *40 series
>>> laptops, which have the same weird symbols on F11 and F12 as the
>>> Lenovo Compact Keyboard, the ones which you describe as:
>>>
>>> /* Fn-F11: View open applications (3 boxes) */
>>> /* Fn-F12: Open My computer (6 boxes) USB-only */
>>>
>>> You map these to:
>>> KEY_FN_F11
>>> KEY_FILE
>>>
>>> Where as my (already merged into Linus tree) patch maps these to:
>>>
>>> KEY_SCALE
>>> KEY_COMPUTER
>>>
>>> Which are defined in linux/uapi/input.h as:
>>>
>>> #define KEY_SCALE 120 /* AL Compiz Scale (Expose) */
>>> #define KEY_COMPUTER 157
>>>
>>> Which I believe maps closes to View open applications (which to me
>>> sounds like expose mode) and Open My computer.
>>
>> Yes, the Function keys look the same:-
>> http://www.lenovo.com/images/gallery/1060x596/lenovo-laptop-thinkpad-t440-overhead-keyboard-2.jpg
>> https://c2.staticflickr.com/6/5467/9092693533_440cfcf311_z.jpg
>>
>> I went with KEY_FILE on the USB keyboard, since this is what it's mapped to on the Bluetooth keyboard, as part of the CONSUMER usage page:-
>> http://lxr.free-electrons.com/source/drivers/hid/hid-input.c#L694
>
> Ah, well if the bluetooth version of the keyboard actually uses a standard
> HUT code for that and that maps to KEY_FILE, then I agree that it would
> be best to map the "F-12" key to KEY_FILE.
>
>> Personally I've no fondness to what the keys map to, since I remap them to how they are labelled on an Thinkpad X230, which is pause/nexttrack. However, it seems somewhat silly customising something that appears to be a standard mapping.
>>
>>> Note that on the laptops the keys have their special meaning by
>>> default and using Fn turns them back into normal F11 keys, so
>>> KEY_FN_F11 seems like a particular bad match as that suggests
>>> a key combo which it is not on the laptops.
>>
>> That's true. It works fine with Fn-ESC but yes, on these keyboards they are labelled such that F11 is the Fn-modified version.
>
> Right so lets just go with KEY_SCALE then? If you switch F-11 to
> KEY_SCALE in the next version of your patch set, then I'll send
> a followup patch for thinkpad-acpi to change F-12 to KEY_FILE, and
> then the 2 mappings will be in sync.
>
> Agreed ?
Seems like a fair compromise :) I'll do that (use KEY_SCALE / KEY_FILE) in
the next version of the patch.
>
> Regards,
>
> Hans
>
--
Jamie Lentin
^ permalink raw reply
* Re: [PATCH v2 2/6] input: touchscreen: imx25 tcq driver
From: Fabio Estevam @ 2014-06-21 19:55 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Denis Carikli, Shawn Guo, Samuel Ortiz, Dmitry Torokhov,
Eric Bénard, Sascha Hauer,
linux-arm-kernel@lists.infradead.org, Lee Jones, linux-input,
linux-iio, Lars-Peter Clausen, Markus Pargmann
In-Reply-To: <53A55BD8.7030905@kernel.org>
On Sat, Jun 21, 2014 at 7:18 AM, Jonathan Cameron <jic23@kernel.org> wrote:
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>
> Could this not in theory fail?
Yes, but devm_ioremap_resource will take care of it, so we don't need
to check for error here.
>
>> + mem = devm_ioremap_resource(dev, res);
>> + if (!mem)
>> + return -ENOMEM;
^ permalink raw reply
* [PATCH] Input: evdev - Fix incorrect kfree of err_free_client after vzalloc
From: Yongtaek Lee @ 2014-06-23 1:12 UTC (permalink / raw)
To: rydberg, dmitry.torokhov, daniels, rientjes, dh.herrmann
Cc: linux-input, linux-kernel, Yongtaek Lee
In-Reply-To: <CANq1E4T3v9DPnj8z7B3Qj0NT98M648r+KDWT7Os4SpbCjNxQ8A@mail.gmail.com>
This bug was introduced by commit 92eb77d ("Input: evdev - fall back
to vmalloc for client event buffer").
vzalloc is used to alloc memory as fallback in case of failure
of kzalloc. But err_free_client was not considered on below case.
1. kzalloc fail
2. vzalloc success
3. evdev_open_device fail
4. kfree
So that address checking is needed to call correct free function.
Signed-off-by: Yongtaek Lee <ytk.lee@samsung.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: David Herrmann <dh.herrmann@gmail.com>
Acked-by: David Rientjes <rientjes@google.com>
---
drivers/input/evdev.c | 5 ++++-
1 files changed, 4 insertions(+), 1 deletions(-)
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index ce953d8..f60daa0 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -422,7 +422,10 @@ static int evdev_open(struct inode *inode, struct file *file)
err_free_client:
evdev_detach_client(evdev, client);
- kfree(client);
+ if (is_vmalloc_addr(client))
+ vfree(client);
+ else
+ kfree(client);
return error;
}
--
1.7.1
^ permalink raw reply related
* Re: [PATCH v2 2/2] Input: uinput - add new UINPUT_DEV_SETUP ioctl
From: Peter Hutterer @ 2014-06-23 2:53 UTC (permalink / raw)
To: David Herrmann; +Cc: linux-input, Dmitry Torokhov, Benjamin Tissoires
In-Reply-To: <1403263600-24736-2-git-send-email-dh.herrmann@gmail.com>
On Fri, Jun 20, 2014 at 01:26:40PM +0200, David Herrmann wrote:
> This adds a new ioctl UINPUT_DEV_SETUP that replaces the old device setup
> method (by write()'ing "struct uinput_user_dev" to the node). The old
> method is not easily extendable and requires huge payloads. Furthermore,
> overloading write() without properly versioned objects is error-prone.
>
> Therefore, we introduce a new ioctl to replace the old method. The ioctl
> supports all features of the old method, plus a "resolution" field for
> absinfo. Furthermore, it's properly forward-compatible to new ABS codes
> and a growing "struct input_absinfo" structure.
>
> The ioctl also allows user-space to skip unknown axes if not set. The
> payload-size can now be specified by the caller. There is no need to copy
> the whole array temporarily into the kernel, but instead we can iterate
> over it and copy each value manually.
>
> Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
> ---
> v2:
> - drop "version" field
> - drop "size" field
> - only copy absinfo if UI_SET_ABSBIT was called for the axis
> - minor linguistic changes
>
> drivers/input/misc/uinput.c | 67 +++++++++++++++++++++++++++++++++++++++++++--
> include/uapi/linux/uinput.h | 55 +++++++++++++++++++++++++++++++++++--
> 2 files changed, 116 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c
> index a2a3895..5c125e8 100644
> --- a/drivers/input/misc/uinput.c
> +++ b/drivers/input/misc/uinput.c
> @@ -371,8 +371,65 @@ static int uinput_allocate_device(struct uinput_device *udev)
> return 0;
> }
>
> -static int uinput_setup_device(struct uinput_device *udev,
> - const char __user *buffer, size_t count)
> +static int uinput_dev_setup(struct uinput_device *udev,
> + struct uinput_setup __user *arg)
> +{
> + struct uinput_setup setup;
> + struct input_dev *dev;
> + int i, retval;
don't you want to a check for udev->state != UIST_CREATED here?
> +
> + if (copy_from_user(&setup, arg, sizeof(setup)))
> + return -EFAULT;
> + if (!setup.name[0])
> + return -EINVAL;
> + /* So far we only support the original "struct input_absinfo", but be
> + * forward compatible and allow larger payloads. */
> + if (setup.absinfo_size < sizeof(struct input_absinfo))
> + return -EINVAL;
> +
> + dev = udev->dev;
> + dev->id = setup.id;
> + udev->ff_effects_max = setup.ff_effects_max;
> +
> + kfree(dev->name);
> + dev->name = kstrndup(setup.name, UINPUT_MAX_NAME_SIZE, GFP_KERNEL);
> + if (!dev->name)
> + return -ENOMEM;
> +
> + if (setup.abs_cnt > ABS_CNT)
> + setup.abs_cnt = ABS_CNT;
> +
> + if (setup.abs_cnt > 0) {
> + u8 __user *p = (u8 __user*)arg->abs;
> +
> + input_alloc_absinfo(dev);
> + if (!dev->absinfo)
> + return -ENOMEM;
> +
> + for (i = 0; i < setup.abs_cnt; ++i, p += setup.absinfo_size) {
> + struct input_absinfo absinfo;
> +
> + if (!test_bit(i, dev->absbit))
> + continue;
> +
> + if (copy_from_user(&absinfo, p, sizeof(absinfo)))
> + return -EFAULT;
> +
> + dev->absinfo[i] = absinfo;
> + }
> + }
> +
> + retval = uinput_validate_absbits(dev);
> + if (retval < 0)
> + return retval;
> +
> + udev->state = UIST_SETUP_COMPLETE;
> + return 0;
> +}
> +
> +/* legacy setup via write() */
> +static int uinput_setup_device_legacy(struct uinput_device *udev,
> + const char __user *buffer, size_t count)
> {
> struct uinput_user_dev *user_dev;
> struct input_dev *dev;
> @@ -475,7 +532,7 @@ static ssize_t uinput_write(struct file *file, const char __user *buffer,
>
> retval = udev->state == UIST_CREATED ?
> uinput_inject_events(udev, buffer, count) :
> - uinput_setup_device(udev, buffer, count);
> + uinput_setup_device_legacy(udev, buffer, count);
>
> mutex_unlock(&udev->mutex);
>
> @@ -736,6 +793,10 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
> uinput_destroy_device(udev);
> goto out;
>
> + case UI_DEV_SETUP:
> + retval = uinput_dev_setup(udev, p);
> + goto out;
> +
> case UI_SET_EVBIT:
> retval = uinput_set_bit(arg, evbit, EV_MAX);
> goto out;
> diff --git a/include/uapi/linux/uinput.h b/include/uapi/linux/uinput.h
> index 19339cf..982144d 100644
> --- a/include/uapi/linux/uinput.h
> +++ b/include/uapi/linux/uinput.h
> @@ -20,6 +20,8 @@
> * Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
> *
> * Changes/Revisions:
> + * 5 06/20/2014 (David Herrmann <dh.herrmann@gmail.com>
> + * - add UI_DEV_SETUP ioctl
> * 0.4 01/09/2014 (Benjamin Tissoires <benjamin.tissoires@redhat.com>)
> * - add UI_GET_SYSNAME ioctl
> * 0.3 24/05/2006 (Anssi Hannula <anssi.hannulagmail.com>)
hm, I think you misunderstood my comment. I was hoping to fix all up in one
go, I had noticed that the others were 0.x as well :)
With the extra check, both patches Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
Cheers,
Peter
> @@ -37,8 +39,8 @@
> #include <linux/types.h>
> #include <linux/input.h>
>
> -#define UINPUT_VERSION 4
> -
> +#define UINPUT_VERSION 5
> +#define UINPUT_MAX_NAME_SIZE 80
>
> struct uinput_ff_upload {
> __u32 request_id;
> @@ -93,6 +95,54 @@ struct uinput_ff_erase {
> */
> #define UI_GET_VERSION _IOR(UINPUT_IOCTL_BASE, 301, unsigned int)
>
> +struct uinput_setup {
> + __u64 absinfo_size;
> + struct input_id id;
> + char name[UINPUT_MAX_NAME_SIZE];
> + __u32 ff_effects_max;
> + __u32 abs_cnt;
> + struct input_absinfo abs[];
> +};
> +
> +/**
> + * UI_DEV_SETUP - Set device parameters for setup
> + *
> + * This ioctl sets parameters for the input-device to be created. It must be
> + * issued *before* calling UI_DEV_CREATE or it will fail. This ioctl supersedes
> + * the old "struct uinput_user_dev" method, which wrote this data via write().
> + *
> + * This ioctl takes a "struct uinput_setup" object as argument. The fields of
> + * this object are as follows:
> + * absinfo_size: This field *must* be initialized to
> + * "sizeof(struct input_absinfo)". It allows to extend the API
> + * to support more absinfo fields. Older kernels ignore unknown
> + * extensions to "struct input_absinfo".
> + * id: See the description of "struct input_id". This field is
> + * copied unchanged into the new device.
> + * name: This is used unchanged as name for the new device.
> + * ff_effects_max: This limits the maximum numbers of force-feedback effects.
> + * See below for a description of FF with uinput.
> + * abs_cnt: This field defines the amount of elements in the following
> + * "abs" array. Axes beyond the kernel's ABS_CNT are ignored.
> + * abs: This is an appended array that contains parameters for ABS
> + * axes. See "struct input_absinfo" for a description of these
> + * fields. This array is copied unchanged into the kernel for
> + * all specified axes. Axes not enabled via UI_SET_ABSBIT are
> + * ignored.
> + *
> + * This ioctl can be called multiple times and will overwrite previous values.
> + * If this ioctl fails with -EINVAL, you're recommended to use the old
> + * "uinput_user_dev" method via write() as fallback, in case you run on an old
> + * kernel that does not support this ioctl.
> + *
> + * This ioctl may fail with -EINVAL if it is not supported or if you passed
> + * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the
> + * passed uinput_setup object cannot be read/written.
> + * If this call fails, partial data may have already been applied to the
> + * internal device.
> + */
> +#define UI_DEV_SETUP _IOWR(UINPUT_IOCTL_BASE, 302, struct uinput_setup)
> +
> /*
> * To write a force-feedback-capable driver, the upload_effect
> * and erase_effect callbacks in input_dev must be implemented.
> @@ -144,7 +194,6 @@ struct uinput_ff_erase {
> #define UI_FF_UPLOAD 1
> #define UI_FF_ERASE 2
>
> -#define UINPUT_MAX_NAME_SIZE 80
> struct uinput_user_dev {
> char name[UINPUT_MAX_NAME_SIZE];
> struct input_id id;
> --
> 2.0.0
>
^ permalink raw reply
* Humanitarian Work
From: Georgina Hope Rinehart @ 2014-06-23 0:05 UTC (permalink / raw)
Greetings in the Name of Our Lord,
I have a charity proposal for you, reply back for more information.
Thanks and God bless.
Gina Rinehart
^ permalink raw reply
* [PATCH 0/2] input: Add CSR SiRFSoC internal touchscreen driver
From: Barry Song @ 2014-06-23 9:42 UTC (permalink / raw)
To: dmitry.torokhov, dtor, akpm; +Cc: linux-input, workgroup.linux, Barry Song
From: Barry Song <Baohua.Song@csr.com>
patch "lib: int_sqrt: add int64_sqrt routine" add a int64_sqrt() API
which CSR SiRFSoC internal touchscreen driver needs. and patch "input:
sirfsoc_rs - add sirfsoc internal ADC-based touchscreen driver" adds
the new driver.
Guoying Zhang (1):
input: sirfsoc_rs - add sirfsoc internal ADC-based touchscreen driver
Yibo Cai (1):
lib: int_sqrt: add int64_sqrt routine
drivers/input/touchscreen/Kconfig | 10 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/sirfsoc_ts.c | 658 +++++++++++++++++++++++++++++++++
include/linux/kernel.h | 1 +
lib/int_sqrt.c | 27 ++
5 files changed, 697 insertions(+)
create mode 100644 drivers/input/touchscreen/sirfsoc_ts.c
--
1.9.3
^ permalink raw reply
* [PATCH 1/2] lib: int_sqrt: add int64_sqrt routine
From: Barry Song @ 2014-06-23 9:43 UTC (permalink / raw)
To: dmitry.torokhov, dtor, akpm
Cc: linux-input, workgroup.linux, Yibo Cai, Barry Song
In-Reply-To: <1403516581-12560-1-git-send-email-21cnbao@gmail.com>
From: Yibo Cai <yibo.cai@csr.com>
Get square root of a 64-bit digit on 32bit platforms. CSR SiRFSoC
touchscreen driver needs it.
Signed-off-by: Yibo Cai <yibo.cai@csr.com>
Signed-off-by: Barry Song <Baohua.Song@csr.com>
---
include/linux/kernel.h | 1 +
lib/int_sqrt.c | 27 +++++++++++++++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/include/linux/kernel.h b/include/linux/kernel.h
index 4c52907..6e63081 100644
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -418,6 +418,7 @@ struct pid;
extern struct pid *session_of_pgrp(struct pid *pgrp);
unsigned long int_sqrt(unsigned long);
+unsigned long long int64_sqrt(unsigned long long);
extern void bust_spinlocks(int yes);
extern int oops_in_progress; /* If set, an oops, panic(), BUG() or die() is in progress */
diff --git a/lib/int_sqrt.c b/lib/int_sqrt.c
index 1ef4cc3..2aa9fcc 100644
--- a/lib/int_sqrt.c
+++ b/lib/int_sqrt.c
@@ -36,3 +36,30 @@ unsigned long int_sqrt(unsigned long x)
return y;
}
EXPORT_SYMBOL(int_sqrt);
+
+/*
+ * Square root of a 64-bit digit.
+ * Same as int_sqrt on 64-bit platforms where "long" equals "long long"
+ */
+unsigned long long int64_sqrt(unsigned long long x)
+{
+ unsigned long long m = 0, y = 0, b = 0;
+
+ if (x <= 1)
+ return x;
+
+ m = 1ULL << (BITS_PER_LONG_LONG - 2);
+ while (m != 0) {
+ b = y + m;
+ y >>= 1;
+
+ if (x >= b) {
+ x -= b;
+ y += m;
+ }
+ m >>= 2;
+ }
+
+ return y;
+}
+EXPORT_SYMBOL(int64_sqrt);
--
1.9.3
^ permalink raw reply related
* [PATCH 2/2] input: sirfsoc_rs - add sirfsoc internal ADC-based touchscreen driver
From: Barry Song @ 2014-06-23 9:43 UTC (permalink / raw)
To: dmitry.torokhov, dtor, akpm
Cc: linux-input, workgroup.linux, Guoying Zhang, Jonathan Cameron,
Yibo Cai, Zhiwu Song, Barry Song
In-Reply-To: <1403516581-12560-1-git-send-email-21cnbao@gmail.com>
From: Guoying Zhang <Guoying.Zhang@csr.com>
The Touch Screen (TS) Controller is used for driving the touch screen to
perform coordinates and pressure measurements. It supports a 4-wire
resistive touch screen interface.
in this driver, we support both single and dual touch. The dual touch is
actually based on a software algorithm.
The idea is to recover original dual touch coordinates from AD samples.
- Equivalent schematic.
touch1 touch2
| |
v v
RX1 A RX2 D RX3
Upper Plane: XP ----/\/\/\----o----/\/\/\----o----/\/\/\---- XN
| |
/ /
Rtouch1 \ Rtouch2 \
/ /
\ \
| |
Lower Plane: YP ----/\/\/\----o----/\/\/\----o----/\/\/\---- YN
RY1 B RY2 C RY3
Touch points(touch1/2) introduce two resisters(Rtouch1/2) between
upper plane and lower plane, as well as cutting each plane into
three consecutive resisters(RX1/2/3, RY1/2/3).
- Eight AD samples can be got from sirf touchscreen controller, which
serve as known conditions to solve the above resister network.
---------------------------------
| Power | AD samples |
---------------------------------
| Vcc | GND | Sample1 | Sample2 |
---------------------------------
| XP | XN | YP | YN | \
--------------------------------- > Recover coordinates
| YP | YN | XP | XN | /
---------------------------------
| XP | YN | YP | XN | \
--------------------------------- > Calculate Rtouch
| YP | XN | XP | YN | /
---------------------------------
- Non-trivial calculation is required to recover dual touch coordinates
as we need to resolve some second order equations and deal with very
big or small float numbers. Some tricks are necessary to make it work.
* float numbers are represented by enlarged integers.
ex. (float)1.03 -> *1024 -> (u32)1054
* Sequence of multiply and division is important as it may result in
overflow or underflow if switched, though in theory it's nonsense.
* Single touch dominates dual touch. Single touch detection must be
kept fast and accurate.
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Guoying Zhang <Guoying.Zhang@csr.com>
Signed-off-by: Yibo Cai <Yibo.Cai@csr.com>
Signed-off-by: Zhiwu Song <Zhiwu.Song@csr.com>
Signed-off-by: Barry Song <Baohua.Song@csr.com>
---
drivers/input/touchscreen/Kconfig | 10 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/sirfsoc_ts.c | 658 +++++++++++++++++++++++++++++++++
3 files changed, 669 insertions(+)
create mode 100644 drivers/input/touchscreen/sirfsoc_ts.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index a23a94b..8312b7c 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -868,6 +868,16 @@ config TOUCHSCREEN_PCAP
To compile this driver as a module, choose M here: the
module will be called pcap_ts.
+config TOUCHSCREEN_SIRFSOC
+ tristate "SiRFSoC touchscreen"
+ depends on ARCH_SIRF
+ select IIO
+ help
+ Say Y here if you have a SiRFSoC internal ADC based touchscreen.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sirfsoc_ts.
+
config TOUCHSCREEN_ST1232
tristate "Sitronix ST1232 touchscreen controllers"
depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 126479d..ef438f7 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o
obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o
obj-$(CONFIG_TOUCHSCREEN_PIXCIR) += pixcir_i2c_ts.o
obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o
+obj-$(CONFIG_TOUCHSCREEN_SIRFSOC) += sirfsoc_ts.o
obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o
obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o
obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o
diff --git a/drivers/input/touchscreen/sirfsoc_ts.c b/drivers/input/touchscreen/sirfsoc_ts.c
new file mode 100644
index 0000000..77cde19
--- /dev/null
+++ b/drivers/input/touchscreen/sirfsoc_ts.c
@@ -0,0 +1,658 @@
+/*
+ * sirfsoc touch controller Driver
+ *
+ * Copyright (c) 2014 Cambridge Silicon Radio Limited, a CSR plc group company.
+ *
+ * Licensed under GPLv2 or later.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/reset.h>
+#include <linux/iio/consumer.h>
+
+#define DRIVER_NAME "sirfsoc_tsc"
+
+#define DATA_SHIFT_BITS 14
+#define DATA_XMASK (0x3FFF << 0)
+#define DATA_YMASK (0x3FFF << DATA_SHIFT_BITS)
+#define GETX(val) ((val) & DATA_XMASK)
+#define GETY(val) (((val) & DATA_YMASK) >> DATA_SHIFT_BITS)
+
+/*
+ * If new coordinates are close enough to last ones, last values
+ * should be used to suppress glitches.
+ */
+#define TS_GLITCH_GAP 60
+
+/* Dual touch: Configurable parameters */
+#define TS_PREC_BITS 10 /* Precision, must >= 2 */
+#define TS_DUAL_MIN 15 /* Min UAD/UBC of dual touch points */
+/* Dual touch: Fixed parameter */
+#define TS_V_MAX (1 << DATA_SHIFT_BITS) /* Full scale AD */
+/* Dual touch: Screen dependent parameters */
+#define TS_RX 694 /* X-plane resister */
+#define TS_RY 228 /* Y-plane resister */
+#define TS_V 10800 /* Max AD (LeftUpper corner) */
+#define TS_COEF_MIN ((u32)(0.100f * (1 << TS_PREC_BITS)))
+#define TS_COEF_MAX ((u32)(100.0f * (1 << TS_PREC_BITS)))
+#define TS_COEF_DEFAULT ((u32)(2.000f * (1 << TS_PREC_BITS)))
+#define TS_RTOUCH_NORMAL 700 /* Normal touch resister */
+#define TS_RTOUCH_MIN (70 << TS_PREC_BITS) /* 70 Ohm */
+#define TS_RTOUCH_MAX (1700 << TS_PREC_BITS) /* 1700 Ohm */
+#define TS_RTOUCH_DUAL_UP 350 /* Upper bound of dual touch */
+#define TS_RTOUCH_SINGLE_LOW 450 /* Lower bound of single touch */
+#define TS_SINGLE_MAXGAP_X 100 /* Max UAD of single touch point */
+#define TS_SINGLE_MAXGAP_Y 50 /* Max UBC of single touch point */
+
+/* Select AD samples to read (SEL bits in ADC_CONTROL1 register) */
+#define SIRFSOC_TS_SEL_X 0x01 /* x sample */
+#define SIRFSOC_TS_SEL_Y 0x02 /* y sample */
+#define SIRFSOC_TS_SEL_DUAL 0x0F /* eight samples for dual touch */
+#define SIRFSOC_TS_CTL1(sel) (ADC_POLL | ADC_SEL(sel) | ADC_DEL_SET(6) \
+ | ADC_FREQ_6K | ADC_TP_TIME(0) | ADC_SGAIN(0) \
+ | ADC_EXTCM(0) | ADC_RBAT_DISABLE | ADC_MORE_CTL1)
+
+/* AD sample indexes (single touch) */
+enum {
+ X,
+ Y,
+ AD_SAMPLE_COUNT_SINGLE
+};
+
+/* AD sample indexes (dual touch) */
+enum {
+ XPXN_YP,
+ YPYN_XP,
+ XPXN_YN,
+ YPYN_XN,
+ XPYN_YP,
+ XPYN_XN,
+ YPXN_XP,
+ YPXN_YN,
+ AD_SAMPLE_COUNT_DUAL
+};
+
+#define AD_REG_COUNT (AD_SAMPLE_COUNT_DUAL / 2)
+
+struct sirfsoc_ts {
+ char phys[32];
+
+ /* AD samples buffer of current detection */
+ u32 samples[AD_SAMPLE_COUNT_DUAL];
+
+ /* Calculated coordinates of current detection */
+ int sampled_x[2], sampled_y[2];
+ /* Last successfully calculated and issued coordinates */
+ int issued_x[2], issued_y[2];
+ bool press_down;
+
+ /* Fingers detected pressing on the screen
+ * always 1 for single touch driver
+ * maybe 1 or 2 for dual touch driver, depends on calculation
+ */
+ int fingers;
+
+ /* Last touching event is a dual touch */
+ bool last_is_dual;
+
+ struct input_dev *input;
+
+ /* Points to touch specific data */
+ const struct sirfsoc_ts_of_data_touch *touch;
+
+ /* ADC data channel for ts*/
+ struct iio_channel *chan;
+};
+
+/* Single/dual touch specific data and routines */
+struct sirfsoc_ts_of_data_touch {
+ /* Number of continuous stable samples for debouncing */
+ int debounce_rep;
+ /* Max deviation of AD values for stable samples */
+ int debounce_dev;
+ /* Callback routine to read and calculate coordinates */
+ int (*get_coord_and_pen)(struct sirfsoc_ts *);
+ /* Callback routine to read AD samples */
+ int (*read_samples)(struct sirfsoc_ts *, u32 *);
+};
+
+/*
+ * Debounce routine shared by single and dual touch
+ * sample_count: how many AD samples needs to take care
+ */
+static int sirfsoc_ts_debounce(struct sirfsoc_ts *ts, int sample_count)
+{
+ int i, j;
+ u32 samples[AD_SAMPLE_COUNT_DUAL]; /* Max of single/dual samples */
+
+ /* First sample */
+ if ((*(ts->touch->read_samples))(ts, ts->samples))
+ return -EBUSY;
+
+ /* Debouncing
+ * - Condition for a stable reading: Three (debounce_rep) continuous
+ * reading with AD samples deviation in bound (debounce_dev)
+ * - ts->samples[] stores sum of previous readings
+ * - samples[] stored current reading
+ */
+ for (i = 1; i < ts->touch->debounce_rep; i++) {
+ /* Get raw AD samples */
+ if ((*(ts->touch->read_samples))(ts, samples))
+ return -EBUSY;
+ /* Verify adjacent samples deviation */
+ for (j = 0; j < sample_count; j++) {
+ if (abs(ts->samples[j] / i - samples[j]) >
+ ts->touch->debounce_dev)
+ return -EINVAL;
+ ts->samples[j] += samples[j];
+ }
+ }
+
+ /* Average samples, store to ts->samples */
+ for (i = 0; i < ts->touch->debounce_rep; i++)
+ ts->samples[i] /= ts->touch->debounce_rep;
+
+ return 0;
+}
+
+static int sirfsoc_ts_read_samples_single(struct sirfsoc_ts *ts, u32 *samples)
+{
+ int raw_sample;
+ int ret;
+
+ ret = iio_read_channel_raw(ts->chan, &raw_sample);
+ if (ret < 0) {
+ ts->press_down = false;
+ return ret;
+ }
+
+ /* x sample */
+ samples[X] = GETX(raw_sample);
+ /* y sample */
+ samples[Y] = GETY(raw_sample);
+
+ ts->press_down = true;
+ return 0;
+}
+
+/*
+ * Calculate single touch coordinate
+ * coord: (ts->sampled_x[0], y[0])
+ */
+static int sirfsoc_ts_get_coord_and_pen_single(struct sirfsoc_ts *ts)
+{
+ int ret;
+
+ ret = sirfsoc_ts_debounce(ts, AD_SAMPLE_COUNT_SINGLE);
+ if (ret < 0)
+ return ret;
+
+ ts->sampled_x[0] = ts->samples[X];
+ ts->sampled_y[0] = ts->samples[Y];
+
+ return 0;
+}
+
+/* Read eight AD samples from adc */
+static int sirfsoc_ts_read_samples_dual(struct sirfsoc_ts *ts, u32 *samples)
+{
+ int i, ret;
+ int raw_samples[AD_REG_COUNT];
+
+ ret = iio_read_channel_raw(ts->chan, raw_samples);
+ if (ret < 0) {
+ ts->press_down = false;
+ return ret;
+ }
+
+ for (i = 0; i < AD_REG_COUNT; i++) {
+ *samples++ = GETX(raw_samples[i]);
+ *samples++ = GETY(raw_samples[i]);
+ }
+
+ ts->press_down = true;
+ return 0;
+}
+
+/*
+ * Calculate dual touch coordinates
+ *
+ * - Schematic
+ *
+ * RX1 A RX2 D RX3
+ * XP ----/\/\/\----o----/\/\/\----o----/\/\/\---- XN
+ * | |
+ * / /
+ * Rtouch \ Rtouch \
+ * / /
+ * \ \
+ * | |
+ * YP ----/\/\/\----o----/\/\/\----o----/\/\/\---- YN
+ * RY1 B RY2 C RY3
+ *
+ * coord1: (ts->sampled_x[0], y[0]); coord2: (ts->sampled_x[1], y[1])
+ */
+static int sirfsoc_ts_calculate_dual(struct sirfsoc_ts *ts)
+{
+ u32 tmp;
+ int ubc, uad; /* |UB-UC|, |UA-UD| */
+ u32 x, y, rx2, ry2; /* Intermediate results */
+ u64 a64, b64, c64; /* Equation coefficients */
+ u32 rtouch; /* Touch resister between X & Y planes */
+ bool neg_slope; /* Slope of the line connecting dual points,
+ negative if LeftUpper and RightDown */
+ u32 *samples = ts->samples;
+
+ /*
+ * Step 1: Calculate Rtouch
+ *
+ * Rtouch depends on pressure and single/dual touch,
+ * Dual touch halves the value approximately.
+ * Two equivalent approaches:
+ * 1. TS_RY * ypyn_xp * (xpyn_xn/xpyn_yp - 1) / TS_V
+ * 2. TS_RX * xpxn_yp * (ypxn_yn/ypxn_xp - 1) / TS_V
+ *
+ * CAUTION:
+ * rtouch is multiplied by 1024 to keep enough precision bits
+ */
+ a64 = TS_RY * samples[YPYN_XP];
+ a64 <<= TS_PREC_BITS; /* *1024 */
+ do_div(a64, TS_V);
+ b64 = a64 * samples[XPYN_XN];
+ do_div(b64, samples[XPYN_YP]);
+ rtouch = (u32)(b64 - a64);
+ if (rtouch < TS_RTOUCH_MIN || rtouch > TS_RTOUCH_MAX)
+ return -EINVAL; /* Unstable reading */
+
+ /*
+ * Step2: Check single touch
+ *
+ * As single touch is the dominant case, it should be handled first.
+ * Chance is prominent that we may skip all dual touch related messes.
+ */
+ ubc = samples[XPXN_YP] - samples[XPXN_YN];
+ uad = samples[YPYN_XP] - samples[YPYN_XN];
+ neg_slope = (ubc < 0);
+ ubc = abs(ubc);
+
+ /* UA-UD should follow the same sign as UB-UC */
+ if ((uad < 0) != neg_slope)
+ goto single_touch;
+ uad = abs(uad);
+ /* Very closed samples mean single touch or vertical/horizontal */
+ if (ubc <= TS_DUAL_MIN || uad <= TS_DUAL_MIN)
+ goto single_touch;
+ /* Fast scratching on the screen may cause misdetection */
+ if (rtouch > (TS_RTOUCH_SINGLE_LOW<<TS_PREC_BITS))
+ goto single_touch;
+
+ /*
+ * Step 3: Calculate intermediate value x (slope)
+ *
+ * CAUTION:
+ * x is multiplied by 1024 to keep enough precision bits
+ */
+ x = TS_COEF_DEFAULT; /* Fixed slope at about +/-45 degree */
+
+ /*
+ * Step4: Calculate RX2 & RY2
+ *
+ * Solve equation a*y^2 - b*y - c = 0, where:
+ * a = |UA-UD| + x * TS_V
+ * b = (1+x) * |UA-UD| * TS_RY
+ * c = 2 * |UA-UD| * TS_RY * RTouch
+ *
+ * CAUTION:
+ * RX2,RY2 is multiplied by 256 to keep enough precision bits
+ */
+ a64 = x;
+ a64 *= TS_V;
+ a64 >>= TS_PREC_BITS;
+ a64 += uad;
+ b64 = uad;
+ b64 *= TS_RY;
+ b64 *= ((1<<(TS_PREC_BITS-1)) + (x>>1)); /* *512 */
+ c64 = 2 * uad;
+ c64 *= TS_RY;
+ c64 *= TS_RTOUCH_NORMAL; /* Fixed RTouch */
+
+ /* a: normal; b: *512; c: normal */
+ do_div(b64, a64); /* (b/2a)*1024 */
+ do_div(c64, a64); /* (c/a) */
+
+ c64 <<= (2*TS_PREC_BITS);
+ a64 = int64_sqrt(b64*b64 + c64);
+ a64 += b64;
+ a64 >>= 2; /* *256 */
+ y = (u32)a64;
+
+ ry2 = y;
+ rx2 = (x * y) >> TS_PREC_BITS;
+ /* rx2, ry2: Left shifted 8 bits */
+
+ /* Step5: Calculate coordinates
+ *
+ * center: x0 = (UB+UC)/2, y0 = (UA+UD)/2
+ * delta : dx = 0.5 * TS_V * RX2 / TS_RX
+ * dy = 0.5 * TS_V * RY2 / TS_RY
+ * -------------------------
+ * |points |x | y |
+ * -------------------------
+ * |(x1,y1) |x0-dx | y0-dy |
+ * |(x2,y2) |x0+dx | y0+dy |
+ * -------------------------
+ */
+
+ /* x1, x2 */
+ x = (samples[XPXN_YP] + samples[XPXN_YN]) << (TS_PREC_BITS-2);
+ tmp = (TS_V * rx2) / TS_RX;
+ /* x = x0 * 512, tmp = dx * 512 */
+ if (unlikely(x <= tmp))
+ ts->sampled_x[0] = 1;
+ else
+ ts->sampled_x[0] = (x - tmp) >> (TS_PREC_BITS-1);
+ ts->sampled_x[1] = (x + tmp) >> (TS_PREC_BITS-1);
+
+ /* y1, y2 */
+ y = (samples[YPYN_XP] + samples[YPYN_XN]) << (TS_PREC_BITS-2);
+ tmp = (TS_V * ry2) / TS_RY;
+ /* y = y0 * 512, tmp = dy * 512 */
+ if (unlikely(y <= tmp))
+ ts->sampled_y[0] = 1;
+ else
+ ts->sampled_y[0] = (y - tmp) >> (TS_PREC_BITS-1);
+ ts->sampled_y[1] = (y + tmp) >> (TS_PREC_BITS-1);
+
+ /* Verify data */
+ if (unlikely(ts->sampled_x[0] > ts->sampled_x[1] ||
+ ts->sampled_y[0] > ts->sampled_y[1] ||
+ ts->sampled_x[1] >= TS_V_MAX ||
+ ts->sampled_y[1] >= TS_V_MAX))
+ return -EINVAL; /* Illegal data */
+ if (ts->sampled_x[1] >= TS_V)
+ ts->sampled_x[1] = TS_V - 1;
+ if (ts->sampled_y[1] >= TS_V)
+ ts->sampled_y[1] = TS_V - 1;
+
+ /* Swap x coordinates if negative slope */
+ if (neg_slope) {
+ tmp = ts->sampled_x[0];
+ ts->sampled_x[0] = ts->sampled_x[1];
+ ts->sampled_x[1] = tmp;
+ }
+
+ /* Dual touch detected */
+ ts->fingers = 2;
+ return 0;
+
+ /* Fall back to single touch */
+single_touch:
+ /*
+ * Make sure it's not a misdetected dual touch:
+ * - Touch resister must be above upper bound of dual touch
+ * - |UB-UC| and |UA-UD| are below upper bound of single touch
+ */
+ if (rtouch >= (TS_RTOUCH_DUAL_UP<<TS_PREC_BITS)
+ && ubc <= TS_SINGLE_MAXGAP_Y
+ && uad <= TS_SINGLE_MAXGAP_X) {
+ /* Get single touch coordinate */
+ ts->sampled_x[0] = (samples[XPXN_YP] + samples[XPXN_YN]) / 2;
+ ts->sampled_y[0] = (samples[YPYN_XP] + samples[YPYN_XN]) / 2;
+ /* Single touch detected */
+ ts->fingers = 1;
+ return 0;
+ } else {
+ return -EINVAL; /* Drop uncertainty */
+ }
+}
+
+static int sirfsoc_ts_get_coord_and_pen_dual(struct sirfsoc_ts *ts)
+{
+ int ret;
+ bool is_dual;
+
+ /* Read samples and do debouncing */
+ ret = sirfsoc_ts_debounce(ts, AD_SAMPLE_COUNT_DUAL);
+ if (ret < 0)
+ return ret;
+
+ /* Calculate coordinates */
+ ret = sirfsoc_ts_calculate_dual(ts);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * First switching from single to dual or dual to single are
+ * sometimes unstable, drop it.
+ */
+ is_dual = (ts->fingers == 2); /* Current is dual touch? */
+ if (is_dual != ts->last_is_dual) {
+ ts->last_is_dual = is_dual;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Report touch events to event driver */
+static void sirfsoc_ts_report_coord(struct sirfsoc_ts *ts)
+{
+ int i;
+
+ input_report_abs(ts->input, ABS_PRESSURE, 1);
+ input_report_key(ts->input, BTN_TOUCH, 1);
+
+ for (i = 0; i < ts->fingers; i++) {
+ /* Filter small glitches */
+ if (abs(ts->sampled_x[i] - ts->issued_x[i]) < TS_GLITCH_GAP &&
+ abs(ts->sampled_y[i] - ts->issued_y[i]) < TS_GLITCH_GAP) {
+ /*
+ * New coord is very close to last checking,
+ * adopt old coord instead
+ */
+ ts->sampled_x[i] = ts->issued_x[i];
+ ts->sampled_y[i] = ts->issued_y[i];
+ } else {
+ /* Save new coord for next time comparison */
+ ts->issued_x[i] = ts->sampled_x[i];
+ ts->issued_y[i] = ts->sampled_y[i];
+ }
+
+ input_report_abs(ts->input, ABS_MT_POSITION_X,
+ ts->sampled_x[i]);
+ input_report_abs(ts->input, ABS_MT_POSITION_Y,
+ ts->sampled_y[i]);
+ input_mt_sync(ts->input);
+ }
+
+ input_sync(ts->input);
+}
+
+static irqreturn_t sirfsoc_ts_thread_irq(int irq, void *handle)
+{
+ struct sirfsoc_ts *ts = (struct sirfsoc_ts *)handle;
+ struct input_dev *input = ts->input;
+ int ret;
+
+ /*
+ * we have pen down interrupt, but before pen up, no more will come
+ */
+ do {
+ ret = (*(ts->touch->get_coord_and_pen))(ts);
+ if (!ret)
+ sirfsoc_ts_report_coord(ts);
+
+ usleep_range(5000, 8000);
+ } while (ts->press_down);
+
+ input_report_key(input, BTN_TOUCH, 0);
+ input_report_abs(input, ABS_PRESSURE, 0);
+ input_sync(input);
+
+ return IRQ_HANDLED;
+}
+
+static const struct sirfsoc_ts_of_data_touch sirfsoc_ts_of_data_single = {
+ .debounce_rep = 3,
+ .debounce_dev = 200,
+ .get_coord_and_pen = sirfsoc_ts_get_coord_and_pen_single,
+ .read_samples = sirfsoc_ts_read_samples_single,
+};
+
+static const struct sirfsoc_ts_of_data_touch sirfsoc_ts_of_data_dual = {
+ .debounce_rep = 4,
+ .debounce_dev = 500,
+ .get_coord_and_pen = sirfsoc_ts_get_coord_and_pen_dual,
+ .read_samples = sirfsoc_ts_read_samples_dual,
+};
+
+static const struct of_device_id sirfsoc_ts_of_match[] = {
+ { .compatible = "sirf,prima2-tsc",
+ .data = &sirfsoc_ts_of_data_single },
+ { .compatible = "sirf,dualtouch-tsc",
+ .data = &sirfsoc_ts_of_data_dual },
+ {}
+};
+
+static int sirfsoc_ts_probe(struct platform_device *pdev)
+{
+ struct input_dev *input_dev;
+ struct sirfsoc_ts *ts;
+ int ret;
+ int i;
+ int irq;
+ const struct of_device_id *match;
+
+ const unsigned int codes[] = {
+ KEY_HOME, KEY_MENU, KEY_BACK, KEY_SEARCH,
+ };
+
+ ts = devm_kzalloc(&pdev->dev, sizeof(struct sirfsoc_ts), GFP_KERNEL);
+ if (!ts) {
+ dev_err(&pdev->dev,
+ "sirfsoc ts: Cant allocate driver private data\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, ts);
+
+ ts->chan = iio_channel_get(&pdev->dev, NULL);
+ if (IS_ERR(ts->chan)) {
+ dev_err(&pdev->dev, "sirfsoc ts: Unable to get the adc channel\n");
+ ret = PTR_ERR(ts->chan);
+ goto out0;
+ }
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ dev_err(&pdev->dev,
+ "sirfsoc ts: Unable to allocate input device\n");
+ ret = -ENOMEM;
+ goto out1;
+ }
+
+ snprintf(ts->phys, sizeof("SIRFSOC-TS"), "SIRFSOC-TS");
+ input_dev->name = "sirfsoc_touchscreen";
+ input_dev->phys = ts->phys;
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS)
+ | BIT_MASK(EV_SYN);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ __set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, 1, 0, 0);
+ input_set_abs_params(input_dev, ABS_TOOL_WIDTH, 0, 15, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 1, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0);
+
+ for (i = 0; i < ARRAY_SIZE(codes); i++)
+ input_set_capability(input_dev, EV_KEY, codes[i]);
+
+ ret = input_register_device(input_dev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "sirfsoc ts: Unable to register input device\n");
+ goto out2;
+ }
+ ts->input = input_dev;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "sirfsoc tsc: get irq failed!\n");
+ ret = -ENOMEM;
+ goto out3;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ sirfsoc_ts_thread_irq, IRQF_ONESHOT, DRIVER_NAME, ts);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "sirfsoc ts: regist irq handler failed!\n");
+ ret = -ENODEV;
+ goto out3;
+ }
+
+ input_set_abs_params(input_dev, ABS_X, 0, 0x3FFF, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 0x3FFF, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 0x3FFF, 0, 0);
+ input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 0x3FFF, 0, 0);
+
+ /* Default to single touch */
+ ts->fingers = 1;
+
+ /* Touch specific data */
+ match = of_match_device(of_match_ptr(sirfsoc_ts_of_match), &pdev->dev);
+ ts->touch = match->data;
+
+ return 0;
+out3:
+ input_unregister_device(input_dev);
+out2:
+ input_free_device(input_dev);
+out1:
+ iio_channel_release(ts->chan);
+out0:
+ return ret;
+}
+
+static int sirfsoc_ts_remove(struct platform_device *pdev)
+{
+ struct sirfsoc_ts *ts = platform_get_drvdata(pdev);
+
+ input_unregister_device(ts->input);
+ iio_channel_release(ts->chan);
+
+ return 0;
+}
+
+static void sirfsoc_ts_shutdown(struct platform_device *dev)
+{
+ sirfsoc_ts_remove(dev);
+}
+
+static struct platform_driver tsc_sirfsoc_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = sirfsoc_ts_of_match,
+ },
+ .probe = sirfsoc_ts_probe,
+ .remove = sirfsoc_ts_remove,
+ .shutdown = sirfsoc_ts_shutdown,
+};
+
+module_platform_driver(tsc_sirfsoc_driver);
+
+MODULE_AUTHOR("Guoying Zhang <Guoying.Zhang@csr.com>");
+MODULE_AUTHOR("Yibo Cai <Yibo.Cai@csr.com>");
+MODULE_AUTHOR("Sober Song <Zhiwu.Song@csr.com>");
+MODULE_DESCRIPTION("SiRF SoC On-chip Touch screen driver");
+MODULE_LICENSE("GPL v2");
--
1.9.3
^ permalink raw reply related
* Re: [PATCH v2 2/2] Input: uinput - add new UINPUT_DEV_SETUP ioctl
From: David Herrmann @ 2014-06-23 9:50 UTC (permalink / raw)
To: Peter Hutterer
Cc: open list:HID CORE LAYER, Dmitry Torokhov, Benjamin Tissoires
In-Reply-To: <20140623025357.GA13743@jelly.redhat.com>
Hi
On Mon, Jun 23, 2014 at 4:53 AM, Peter Hutterer
<peter.hutterer@who-t.net> wrote:
> On Fri, Jun 20, 2014 at 01:26:40PM +0200, David Herrmann wrote:
>> This adds a new ioctl UINPUT_DEV_SETUP that replaces the old device setup
>> method (by write()'ing "struct uinput_user_dev" to the node). The old
>> method is not easily extendable and requires huge payloads. Furthermore,
>> overloading write() without properly versioned objects is error-prone.
>>
>> Therefore, we introduce a new ioctl to replace the old method. The ioctl
>> supports all features of the old method, plus a "resolution" field for
>> absinfo. Furthermore, it's properly forward-compatible to new ABS codes
>> and a growing "struct input_absinfo" structure.
>>
>> The ioctl also allows user-space to skip unknown axes if not set. The
>> payload-size can now be specified by the caller. There is no need to copy
>> the whole array temporarily into the kernel, but instead we can iterate
>> over it and copy each value manually.
>>
>> Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
>> ---
>> v2:
>> - drop "version" field
>> - drop "size" field
>> - only copy absinfo if UI_SET_ABSBIT was called for the axis
>> - minor linguistic changes
>>
>> drivers/input/misc/uinput.c | 67 +++++++++++++++++++++++++++++++++++++++++++--
>> include/uapi/linux/uinput.h | 55 +++++++++++++++++++++++++++++++++++--
>> 2 files changed, 116 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c
>> index a2a3895..5c125e8 100644
>> --- a/drivers/input/misc/uinput.c
>> +++ b/drivers/input/misc/uinput.c
>> @@ -371,8 +371,65 @@ static int uinput_allocate_device(struct uinput_device *udev)
>> return 0;
>> }
>>
>> -static int uinput_setup_device(struct uinput_device *udev,
>> - const char __user *buffer, size_t count)
>> +static int uinput_dev_setup(struct uinput_device *udev,
>> + struct uinput_setup __user *arg)
>> +{
>> + struct uinput_setup setup;
>> + struct input_dev *dev;
>> + int i, retval;
>
> don't you want to a check for udev->state != UIST_CREATED here?
Nice catch, added!
>> +
>> + if (copy_from_user(&setup, arg, sizeof(setup)))
>> + return -EFAULT;
>> + if (!setup.name[0])
>> + return -EINVAL;
>> + /* So far we only support the original "struct input_absinfo", but be
>> + * forward compatible and allow larger payloads. */
>> + if (setup.absinfo_size < sizeof(struct input_absinfo))
>> + return -EINVAL;
>> +
>> + dev = udev->dev;
>> + dev->id = setup.id;
>> + udev->ff_effects_max = setup.ff_effects_max;
>> +
>> + kfree(dev->name);
>> + dev->name = kstrndup(setup.name, UINPUT_MAX_NAME_SIZE, GFP_KERNEL);
>> + if (!dev->name)
>> + return -ENOMEM;
>> +
>> + if (setup.abs_cnt > ABS_CNT)
>> + setup.abs_cnt = ABS_CNT;
>> +
>> + if (setup.abs_cnt > 0) {
>> + u8 __user *p = (u8 __user*)arg->abs;
>> +
>> + input_alloc_absinfo(dev);
>> + if (!dev->absinfo)
>> + return -ENOMEM;
>> +
>> + for (i = 0; i < setup.abs_cnt; ++i, p += setup.absinfo_size) {
>> + struct input_absinfo absinfo;
>> +
>> + if (!test_bit(i, dev->absbit))
>> + continue;
>> +
>> + if (copy_from_user(&absinfo, p, sizeof(absinfo)))
>> + return -EFAULT;
>> +
>> + dev->absinfo[i] = absinfo;
>> + }
>> + }
>> +
>> + retval = uinput_validate_absbits(dev);
>> + if (retval < 0)
>> + return retval;
>> +
>> + udev->state = UIST_SETUP_COMPLETE;
>> + return 0;
>> +}
>> +
>> +/* legacy setup via write() */
>> +static int uinput_setup_device_legacy(struct uinput_device *udev,
>> + const char __user *buffer, size_t count)
>> {
>> struct uinput_user_dev *user_dev;
>> struct input_dev *dev;
>> @@ -475,7 +532,7 @@ static ssize_t uinput_write(struct file *file, const char __user *buffer,
>>
>> retval = udev->state == UIST_CREATED ?
>> uinput_inject_events(udev, buffer, count) :
>> - uinput_setup_device(udev, buffer, count);
>> + uinput_setup_device_legacy(udev, buffer, count);
>>
>> mutex_unlock(&udev->mutex);
>>
>> @@ -736,6 +793,10 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
>> uinput_destroy_device(udev);
>> goto out;
>>
>> + case UI_DEV_SETUP:
>> + retval = uinput_dev_setup(udev, p);
>> + goto out;
>> +
>> case UI_SET_EVBIT:
>> retval = uinput_set_bit(arg, evbit, EV_MAX);
>> goto out;
>> diff --git a/include/uapi/linux/uinput.h b/include/uapi/linux/uinput.h
>> index 19339cf..982144d 100644
>> --- a/include/uapi/linux/uinput.h
>> +++ b/include/uapi/linux/uinput.h
>> @@ -20,6 +20,8 @@
>> * Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
>> *
>> * Changes/Revisions:
>> + * 5 06/20/2014 (David Herrmann <dh.herrmann@gmail.com>
>> + * - add UI_DEV_SETUP ioctl
>> * 0.4 01/09/2014 (Benjamin Tissoires <benjamin.tissoires@redhat.com>)
>> * - add UI_GET_SYSNAME ioctl
>> * 0.3 24/05/2006 (Anssi Hannula <anssi.hannulagmail.com>)
>
> hm, I think you misunderstood my comment. I was hoping to fix all up in one
> go, I had noticed that the others were 0.x as well :)
>
> With the extra check, both patches Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
Not sure I like doing that in this patch. I think I will keep 0.5 and
send a followup to fix all of these.
Thanks for reviewing!
David
^ permalink raw reply
* Re: [PATCH 1/2] lib: int_sqrt: add int64_sqrt routine
From: Peter Meerwald @ 2014-06-23 9:52 UTC (permalink / raw)
To: Barry Song
Cc: dmitry.torokhov, dtor, akpm, linux-input, workgroup.linux,
Yibo Cai, Barry Song
In-Reply-To: <1403516581-12560-2-git-send-email-21cnbao@gmail.com>
> Get square root of a 64-bit digit on 32bit platforms. CSR SiRFSoC
> touchscreen driver needs it.
maybe the code could be #MACRO()'d to avoid duplication
see comment below
> diff --git a/include/linux/kernel.h b/include/linux/kernel.h
> index 4c52907..6e63081 100644
> --- a/include/linux/kernel.h
> +++ b/include/linux/kernel.h
> @@ -418,6 +418,7 @@ struct pid;
> extern struct pid *session_of_pgrp(struct pid *pgrp);
>
> unsigned long int_sqrt(unsigned long);
> +unsigned long long int64_sqrt(unsigned long long);
>
> extern void bust_spinlocks(int yes);
> extern int oops_in_progress; /* If set, an oops, panic(), BUG() or die() is in progress */
> diff --git a/lib/int_sqrt.c b/lib/int_sqrt.c
> index 1ef4cc3..2aa9fcc 100644
> --- a/lib/int_sqrt.c
> +++ b/lib/int_sqrt.c
> @@ -36,3 +36,30 @@ unsigned long int_sqrt(unsigned long x)
> return y;
> }
> EXPORT_SYMBOL(int_sqrt);
> +
> +/*
> + * Square root of a 64-bit digit.
> + * Same as int_sqrt on 64-bit platforms where "long" equals "long long"
> + */
> +unsigned long long int64_sqrt(unsigned long long x)
> +{
> + unsigned long long m = 0, y = 0, b = 0;
initialization for b, m not needed
the original int_sqrt() code correctly skips initialization
> +
> + if (x <= 1)
> + return x;
> +
> + m = 1ULL << (BITS_PER_LONG_LONG - 2);
> + while (m != 0) {
> + b = y + m;
> + y >>= 1;
> +
> + if (x >= b) {
> + x -= b;
> + y += m;
> + }
> + m >>= 2;
> + }
> +
> + return y;
> +}
> +EXPORT_SYMBOL(int64_sqrt);
>
--
Peter Meerwald
+43-664-2444418 (mobile)
^ permalink raw reply
* Re: [PATCH v9] leds: USB: HID: Add support for MSI GT683R led panels
From: Oliver Neukum @ 2014-06-23 14:35 UTC (permalink / raw)
To: Janne Kanniainen
Cc: jkosina, johan, cooloney, linux-kernel, linux-leds, linux-usb,
linux-input
In-Reply-To: <1403107502-14106-1-git-send-email-janne.kanniainen@gmail.com>
On Wed, 2014-06-18 at 19:05 +0300, Janne Kanniainen wrote:
> This driver adds support for USB controlled led panels that exists in
> MSI GT683R laptop
>
> +static int gt683r_led_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + int i;
> + int ret;
> + int name_sz;
> + char *name;
> + struct gt683r_led *led;
> +
> + led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL);
> + if (!led)
> + return -ENOMEM;
> +
> + led->mode = GT683R_LED_NORMAL;
> + led->hdev = hdev;
> + hid_set_drvdata(hdev, led);
> +
> + ret = hid_parse(hdev);
> + if (ret) {
> + hid_err(hdev, "hid parsing failed\n");
> + return ret;
> + }
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> + if (ret) {
> + hid_err(hdev, "hw start failed\n");
> + return ret;
> + }
> +
> + for (i = 0; i < GT683R_LED_COUNT; i++) {
> + name_sz = strlen(dev_name(&hdev->dev)) +
> + strlen(gt683r_panel_names[i]) + 3;
> +
> + name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
> + if (!name) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + snprintf(name, name_sz, "%s::%s",
> + dev_name(&hdev->dev), gt683r_panel_names[i]);
> + led->led_devs[i].name = name;
> + led->led_devs[i].max_brightness = 1;
> + led->led_devs[i].brightness_set = gt683r_brightness_set;
> + ret = led_classdev_register(&hdev->dev, &led->led_devs[i]);
> + if (ret) {
> + hid_err(hdev, "could not register led device\n");
> + goto fail;
> + }
> + }
> +
> + ret = device_create_file(&led->hdev->dev, &dev_attr_leds_mode);
> + if (ret) {
> + hid_err(hdev, "could not make mode attribute file\n");
> + goto fail;
> + }
> +
This is the window.
> + mutex_init(&led->lock);
> + INIT_WORK(&led->work, gt683r_led_work);
> +
And here we have a problem. This is a race condition.
At this time you've already created the sysfs files. So
their methods can be called. They will lock a mutex and/or
schedule work that hasn't been initialized.
The initialization must be done before anything in sysfs
is created.
> + return 0;
> +
> +fail:
> + for (i = i - 1; i >= 0; i--)
> + led_classdev_unregister(&led->led_devs[i]);
> + hid_hw_stop(hdev);
> + return ret;
> +}
> +
Sorry for noticing this thread late.
Regards
Oliver
^ permalink raw reply
* Re: [PATCH v9] leds: USB: HID: Add support for MSI GT683R led panels
From: Johan Hovold @ 2014-06-23 14:42 UTC (permalink / raw)
To: Oliver Neukum
Cc: Janne Kanniainen, jkosina, johan, cooloney, linux-kernel,
linux-leds, linux-usb, linux-input
In-Reply-To: <1403534113.10017.22.camel@linux-fkkt.site>
On Mon, Jun 23, 2014 at 04:35:13PM +0200, Oliver Neukum wrote:
> On Wed, 2014-06-18 at 19:05 +0300, Janne Kanniainen wrote:
> > This driver adds support for USB controlled led panels that exists in
> > MSI GT683R laptop
> >
>
> > +static int gt683r_led_probe(struct hid_device *hdev,
> > + const struct hid_device_id *id)
> > +{
> > + int i;
> > + int ret;
> > + int name_sz;
> > + char *name;
> > + struct gt683r_led *led;
> > +
> > + led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL);
> > + if (!led)
> > + return -ENOMEM;
> > +
> > + led->mode = GT683R_LED_NORMAL;
> > + led->hdev = hdev;
> > + hid_set_drvdata(hdev, led);
> > +
> > + ret = hid_parse(hdev);
> > + if (ret) {
> > + hid_err(hdev, "hid parsing failed\n");
> > + return ret;
> > + }
> > +
> > + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> > + if (ret) {
> > + hid_err(hdev, "hw start failed\n");
> > + return ret;
> > + }
> > +
> > + for (i = 0; i < GT683R_LED_COUNT; i++) {
> > + name_sz = strlen(dev_name(&hdev->dev)) +
> > + strlen(gt683r_panel_names[i]) + 3;
> > +
> > + name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
> > + if (!name) {
> > + ret = -ENOMEM;
> > + goto fail;
> > + }
> > +
> > + snprintf(name, name_sz, "%s::%s",
> > + dev_name(&hdev->dev), gt683r_panel_names[i]);
> > + led->led_devs[i].name = name;
> > + led->led_devs[i].max_brightness = 1;
> > + led->led_devs[i].brightness_set = gt683r_brightness_set;
> > + ret = led_classdev_register(&hdev->dev, &led->led_devs[i]);
> > + if (ret) {
> > + hid_err(hdev, "could not register led device\n");
> > + goto fail;
> > + }
> > + }
> > +
> > + ret = device_create_file(&led->hdev->dev, &dev_attr_leds_mode);
> > + if (ret) {
> > + hid_err(hdev, "could not make mode attribute file\n");
> > + goto fail;
> > + }
> > +
>
> This is the window.
>
> > + mutex_init(&led->lock);
> > + INIT_WORK(&led->work, gt683r_led_work);
> > +
>
> And here we have a problem. This is a race condition.
> At this time you've already created the sysfs files. So
> their methods can be called. They will lock a mutex and/or
> schedule work that hasn't been initialized.
> The initialization must be done before anything in sysfs
> is created.
Just move the initialisation of the lock and work to the other private
data initialisations directly after it's allocated.
Can you send a follow up patch, Janne?
Thanks Oliver!
Johan
^ permalink raw reply
* Re: [PATCH v9] leds: USB: HID: Add support for MSI GT683R led panels
From: Greg KH @ 2014-06-23 16:17 UTC (permalink / raw)
To: Johan Hovold
Cc: Oliver Neukum, Janne Kanniainen, jkosina, cooloney, linux-kernel,
linux-leds, linux-usb, linux-input
In-Reply-To: <20140623144255.GA19911@localhost>
On Mon, Jun 23, 2014 at 04:42:55PM +0200, Johan Hovold wrote:
> On Mon, Jun 23, 2014 at 04:35:13PM +0200, Oliver Neukum wrote:
> > On Wed, 2014-06-18 at 19:05 +0300, Janne Kanniainen wrote:
> > > This driver adds support for USB controlled led panels that exists in
> > > MSI GT683R laptop
> > >
> >
> > > +static int gt683r_led_probe(struct hid_device *hdev,
> > > + const struct hid_device_id *id)
> > > +{
> > > + int i;
> > > + int ret;
> > > + int name_sz;
> > > + char *name;
> > > + struct gt683r_led *led;
> > > +
> > > + led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL);
> > > + if (!led)
> > > + return -ENOMEM;
> > > +
> > > + led->mode = GT683R_LED_NORMAL;
> > > + led->hdev = hdev;
> > > + hid_set_drvdata(hdev, led);
> > > +
> > > + ret = hid_parse(hdev);
> > > + if (ret) {
> > > + hid_err(hdev, "hid parsing failed\n");
> > > + return ret;
> > > + }
> > > +
> > > + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> > > + if (ret) {
> > > + hid_err(hdev, "hw start failed\n");
> > > + return ret;
> > > + }
> > > +
> > > + for (i = 0; i < GT683R_LED_COUNT; i++) {
> > > + name_sz = strlen(dev_name(&hdev->dev)) +
> > > + strlen(gt683r_panel_names[i]) + 3;
> > > +
> > > + name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
> > > + if (!name) {
> > > + ret = -ENOMEM;
> > > + goto fail;
> > > + }
> > > +
> > > + snprintf(name, name_sz, "%s::%s",
> > > + dev_name(&hdev->dev), gt683r_panel_names[i]);
> > > + led->led_devs[i].name = name;
> > > + led->led_devs[i].max_brightness = 1;
> > > + led->led_devs[i].brightness_set = gt683r_brightness_set;
> > > + ret = led_classdev_register(&hdev->dev, &led->led_devs[i]);
> > > + if (ret) {
> > > + hid_err(hdev, "could not register led device\n");
> > > + goto fail;
> > > + }
> > > + }
> > > +
> > > + ret = device_create_file(&led->hdev->dev, &dev_attr_leds_mode);
> > > + if (ret) {
> > > + hid_err(hdev, "could not make mode attribute file\n");
> > > + goto fail;
> > > + }
> > > +
> >
> > This is the window.
> >
> > > + mutex_init(&led->lock);
> > > + INIT_WORK(&led->work, gt683r_led_work);
> > > +
> >
> > And here we have a problem. This is a race condition.
> > At this time you've already created the sysfs files. So
> > their methods can be called. They will lock a mutex and/or
> > schedule work that hasn't been initialized.
> > The initialization must be done before anything in sysfs
> > is created.
>
> Just move the initialisation of the lock and work to the other private
> data initialisations directly after it's allocated.
>
> Can you send a follow up patch, Janne?
No driver should be creating sysfs files directly, use the proper
attribute group interface instead. That way the files are created
_before_ the device is announced to userspace, instead of after, which
is a race condition there as well.
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v9] leds: USB: HID: Add support for MSI GT683R led panels
From: Janne Kanniainen @ 2014-06-23 16:20 UTC (permalink / raw)
To: Johan Hovold
Cc: Oliver Neukum, Jiri Kosina, Bryan Wu, linux-kernel, linux-leds,
linux-usb, linux-input
In-Reply-To: <20140623144255.GA19911@localhost>
>> Sorry for noticing this thread late.
No problem. Good that you noticed it now! Thank you.
> Just move the initialisation of the lock and work to the other private
> data initialisations directly after it's allocated.
>
> Can you send a follow up patch, Janne?
Yes I can. Just a moment.
Janne
^ permalink raw reply
* Re: [PATCH v3 3/6] iio: adc: fsl,imx25-gcq driver
From: Denis Carikli @ 2014-06-23 16:43 UTC (permalink / raw)
To: Jonathan Cameron, Shawn Guo, Samuel Ortiz, Dmitry Torokhov,
Fabio Estevam, Peter Meerwald, Hartmut Knaack
Cc: Eric Bénard, Sascha Hauer, linux-arm-kernel, Lee Jones,
linux-input, linux-iio, Lars-Peter Clausen, Markus Pargmann
In-Reply-To: <53A563B0.60708@kernel.org>
On 06/21/2014 12:51 PM, Jonathan Cameron wrote:
>> +static irqreturn_t mx25_gcq_irq(int irq, void *data)
>> +{
>> + struct mx25_gcq_priv *priv = data;
>> + u32 stats;
> Not that much in here, but perhaps still makes sense for this to be a
> threaded
> irq?
What would be the advantage of converting it to be a threaded IRQ?
I guess that there will be no use of IRQF_SHARED and that only this
driver will have to handle the GCQ IRQ in the future.
Denis.
^ permalink raw reply
* [PATCH v10] leds: USB: HID: Add support for MSI GT683R led panels
From: Janne Kanniainen @ 2014-06-23 17:16 UTC (permalink / raw)
To: johan, greg, jkosina
Cc: cooloney, linux-kernel, linux-leds, linux-usb, linux-input,
Janne Kanniainen
In-Reply-To: <20140623161723.GA20421@kroah.com>
This driver adds support for USB controlled led panels that exists in
MSI GT683R laptop
Signed-off-by: Janne Kanniainen <janne.kanniainen@gmail.com>
---
Changes in v2:
- sorted headers to alphabetic order
- using devm_kzalloc
- using BIT(n)
- using usb_control_msg instead of usb_submit_urb
- removing unneeded code
Changes in v3:
- implemented as HID device
- some cleanups and bug fixes
Changes in v4:
- more cleanups
- support for selecting leds
- suppport for selecting status
Changes in v5:
- mode attribute documented under Documentation/ABI
- made array for led_classdev
- led devices uses now recommended naming scheme
Changes in v6:
- flush_work added
- using hid device name instead of hard coded gt683r
- allocating name buffers with devm_kzalloc
Changes in v7:
- buf with for fixed
Changes in v8:
- some cleanups and bugs fixed
Changes in v9:
- few style issues fixed
Changes in v10:
- race condition fixed
- using proper attribute group
.../ABI/testing/sysfs-class-hid-driver-gt683r | 14 +
drivers/hid/Kconfig | 14 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-core.c | 1 +
drivers/hid/hid-gt683r.c | 318 +++++++++++++++++++++
drivers/hid/hid-ids.h | 2 +-
drivers/hid/usbhid/hid-quirks.c | 2 +-
7 files changed, 350 insertions(+), 2 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-hid-driver-gt683r
create mode 100644 drivers/hid/hid-gt683r.c
diff --git a/Documentation/ABI/testing/sysfs-class-hid-driver-gt683r b/Documentation/ABI/testing/sysfs-class-hid-driver-gt683r
new file mode 100644
index 0000000..317e9d5
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-hid-driver-gt683r
@@ -0,0 +1,14 @@
+What: /sys/class/hidraw/<hidraw>/device/leds_mode
+Date: Jun 2014
+KernelVersion: 3.17
+Contact: Janne Kanniainen <janne.kanniainen@gmail.com>
+Description:
+ Set the mode of LEDs
+
+ 0 - normal
+ 1 - audio
+ 2 - breathing
+
+ Normal: LEDs are fully on when enabled
+ Audio: LEDs brightness depends on sound level
+ Breathing: LEDs brightness varies at human breathing rate
\ No newline at end of file
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 7af9d0b..e2f4590 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -261,6 +261,20 @@ config HOLTEK_FF
Say Y here if you have a Holtek On Line Grip based game controller
and want to have force feedback support for it.
+config HID_GT683R
+ tristate "MSI GT68xR LED support"
+ depends on LEDS_CLASS && USB_HID
+ ---help---
+ Say Y here if you want to enable support for the three MSI GT68xR LEDs
+
+ This driver support following modes:
+ - Normal: LEDs are fully on when enabled
+ - Audio: LEDs brightness depends on sound level
+ - Breathing: LEDs brightness varies at human breathing rate
+
+ Currently the following devices are know to be supported:
+ - MSI GT683R
+
config HID_HUION
tristate "Huion tablets"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index fc712dd..7129311 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
obj-$(CONFIG_HID_ELO) += hid-elo.o
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
+obj-$(CONFIG_HID_GT683R) += hid-gt683r.o
obj-$(CONFIG_HID_GYRATION) += hid-gyration.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index da52279..ec88fdb 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1827,6 +1827,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_OFFICE_KB) },
{ HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1) },
{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2) },
diff --git a/drivers/hid/hid-gt683r.c b/drivers/hid/hid-gt683r.c
new file mode 100644
index 0000000..5bd0113
--- /dev/null
+++ b/drivers/hid/hid-gt683r.c
@@ -0,0 +1,318 @@
+/*
+ * MSI GT683R led driver
+ *
+ * Copyright (c) 2014 Janne Kanniainen <janne.kanniainen@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define GT683R_BUFFER_SIZE 8
+
+/*
+ * GT683R_LED_OFF: all LEDs are off
+ * GT683R_LED_AUDIO: LEDs brightness depends on sound level
+ * GT683R_LED_BREATHING: LEDs brightness varies at human breathing rate
+ * GT683R_LED_NORMAL: LEDs are fully on when enabled
+ */
+enum gt683r_led_mode {
+ GT683R_LED_OFF = 0,
+ GT683R_LED_AUDIO = 2,
+ GT683R_LED_BREATHING = 3,
+ GT683R_LED_NORMAL = 5
+};
+
+enum gt683r_panels {
+ GT683R_LED_BACK = 0,
+ GT683R_LED_SIDE = 1,
+ GT683R_LED_FRONT = 2,
+ GT683R_LED_COUNT,
+};
+
+static const char * const gt683r_panel_names[] = {
+ "back",
+ "side",
+ "front",
+};
+
+struct gt683r_led {
+ struct hid_device *hdev;
+ struct led_classdev led_devs[GT683R_LED_COUNT];
+ struct mutex lock;
+ struct work_struct work;
+ enum led_brightness brightnesses[GT683R_LED_COUNT];
+ enum gt683r_led_mode mode;
+};
+
+static const struct hid_device_id gt683r_led_id[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
+ { }
+};
+
+static void gt683r_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ int i;
+ struct device *dev = led_cdev->dev->parent;
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+ struct gt683r_led *led = hid_get_drvdata(hdev);
+
+ for (i = 0; i < GT683R_LED_COUNT; i++) {
+ if (led_cdev == &led->led_devs[i])
+ break;
+ }
+
+ if (i < GT683R_LED_COUNT) {
+ led->brightnesses[i] = brightness;
+ schedule_work(&led->work);
+ }
+}
+
+static ssize_t leds_mode_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 sysfs_mode;
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+ struct gt683r_led *led = hid_get_drvdata(hdev);
+
+ if (led->mode == GT683R_LED_NORMAL)
+ sysfs_mode = 0;
+ else if (led->mode == GT683R_LED_AUDIO)
+ sysfs_mode = 1;
+ else
+ sysfs_mode = 2;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", sysfs_mode);
+}
+
+static ssize_t leds_mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ u8 sysfs_mode;
+ struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+ struct gt683r_led *led = hid_get_drvdata(hdev);
+
+
+ if (kstrtou8(buf, 10, &sysfs_mode) || sysfs_mode > 2)
+ return -EINVAL;
+
+ mutex_lock(&led->lock);
+
+ if (sysfs_mode == 0)
+ led->mode = GT683R_LED_NORMAL;
+ else if (sysfs_mode == 1)
+ led->mode = GT683R_LED_AUDIO;
+ else
+ led->mode = GT683R_LED_BREATHING;
+
+ mutex_unlock(&led->lock);
+ schedule_work(&led->work);
+
+ return count;
+}
+
+static int gt683r_led_snd_msg(struct gt683r_led *led, u8 *msg)
+{
+ int ret;
+
+ ret = hid_hw_raw_request(led->hdev, msg[0], msg, GT683R_BUFFER_SIZE,
+ HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+ if (ret != GT683R_BUFFER_SIZE) {
+ hid_err(led->hdev,
+ "failed to send set report request: %i\n", ret);
+ if (ret < 0)
+ return ret;
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int gt683r_leds_set(struct gt683r_led *led, u8 leds)
+{
+ int ret;
+ u8 *buffer;
+
+ buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ buffer[0] = 0x01;
+ buffer[1] = 0x02;
+ buffer[2] = 0x30;
+ buffer[3] = leds;
+ ret = gt683r_led_snd_msg(led, buffer);
+
+ kfree(buffer);
+ return ret;
+}
+
+static int gt683r_mode_set(struct gt683r_led *led, u8 mode)
+{
+ int ret;
+ u8 *buffer;
+
+ buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ buffer[0] = 0x01;
+ buffer[1] = 0x02;
+ buffer[2] = 0x20;
+ buffer[3] = mode;
+ buffer[4] = 0x01;
+ ret = gt683r_led_snd_msg(led, buffer);
+
+ kfree(buffer);
+ return ret;
+}
+
+static void gt683r_led_work(struct work_struct *work)
+{
+ int i;
+ u8 leds = 0;
+ u8 mode;
+ struct gt683r_led *led = container_of(work, struct gt683r_led, work);
+
+ mutex_lock(&led->lock);
+
+ for (i = 0; i < GT683R_LED_COUNT; i++) {
+ if (led->brightnesses[i])
+ leds |= BIT(i);
+ }
+
+ if (gt683r_leds_set(led, leds))
+ goto fail;
+
+ if (leds)
+ mode = led->mode;
+ else
+ mode = GT683R_LED_OFF;
+
+ gt683r_mode_set(led, mode);
+fail:
+ mutex_unlock(&led->lock);
+}
+
+static DEVICE_ATTR_RW(leds_mode);
+
+static struct attribute *gt683r_attributes[] = {
+ &dev_attr_leds_mode.attr,
+ NULL,
+};
+
+static struct attribute_group gt683r_attribute_group = {
+ .attrs = gt683r_attributes
+};
+
+static int gt683r_led_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int i;
+ int ret;
+ int name_sz;
+ char *name;
+ struct gt683r_led *led;
+
+ led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ mutex_init(&led->lock);
+ INIT_WORK(&led->work, gt683r_led_work);
+
+ led->mode = GT683R_LED_NORMAL;
+ led->hdev = hdev;
+ hid_set_drvdata(hdev, led);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parsing failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ for (i = 0; i < GT683R_LED_COUNT; i++) {
+ name_sz = strlen(dev_name(&hdev->dev)) +
+ strlen(gt683r_panel_names[i]) + 3;
+
+ name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+ if (!name) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ snprintf(name, name_sz, "%s::%s",
+ dev_name(&hdev->dev), gt683r_panel_names[i]);
+ led->led_devs[i].name = name;
+ led->led_devs[i].max_brightness = 1;
+ led->led_devs[i].brightness_set = gt683r_brightness_set;
+ ret = led_classdev_register(&hdev->dev, &led->led_devs[i]);
+ if (ret) {
+ hid_err(hdev, "could not register led device\n");
+ goto fail;
+ }
+ }
+
+ ret = sysfs_create_group(&led->hdev->dev.kobj, >683r_attribute_group);
+ if (ret) {
+ hid_err(hdev, "failed to create sysfs attributes\n");
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ for (i = i - 1; i >= 0; i--)
+ led_classdev_unregister(&led->led_devs[i]);
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void gt683r_led_remove(struct hid_device *hdev)
+{
+ int i;
+ struct gt683r_led *led = hid_get_drvdata(hdev);
+
+ sysfs_remove_group(&led->hdev->dev.kobj, >683r_attribute_group);
+ for (i = 0; i < GT683R_LED_COUNT; i++)
+ led_classdev_unregister(&led->led_devs[i]);
+ flush_work(&led->work);
+ hid_hw_stop(hdev);
+}
+
+static struct hid_driver gt683r_led_driver = {
+ .probe = gt683r_led_probe,
+ .remove = gt683r_led_remove,
+ .name = "gt683r_led",
+ .id_table = gt683r_led_id,
+};
+
+module_hid_driver(gt683r_led_driver);
+
+MODULE_AUTHOR("Janne Kanniainen");
+MODULE_DESCRIPTION("MSI GT683R led driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 34bb220..3692d37 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -641,7 +641,7 @@
#define USB_DEVICE_ID_GENIUS_KB29E 0x3004
#define USB_VENDOR_ID_MSI 0x1770
-#define USB_DEVICE_ID_MSI_GX680R_LED_PANEL 0xff00
+#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
#define USB_DEVICE_ID_N_S_HARMONY 0xc359
diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
index 8e4ddb3..c640e1d 100644
--- a/drivers/hid/usbhid/hid-quirks.c
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -73,7 +73,7 @@ static const struct hid_blacklist {
{ USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET },
{ USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET },
- { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GX680R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
+ { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE, HID_QUIRK_NO_INIT_REPORTS },
{ USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NO_INIT_REPORTS },
--
1.9.2
^ permalink raw reply related
* Re: [PATCH v10] leds: USB: HID: Add support for MSI GT683R led panels
From: Johan Hovold @ 2014-06-23 17:27 UTC (permalink / raw)
To: Janne Kanniainen
Cc: johan, greg, jkosina, cooloney, linux-kernel, linux-leds,
linux-usb, linux-input
In-Reply-To: <1403543808-8228-1-git-send-email-janne.kanniainen@gmail.com>
On Mon, Jun 23, 2014 at 08:16:48PM +0300, Janne Kanniainen wrote:
> This driver adds support for USB controlled led panels that exists in
> MSI GT683R laptop
>
> Signed-off-by: Janne Kanniainen <janne.kanniainen@gmail.com>
> ---
> Changes in v2:
> - sorted headers to alphabetic order
> - using devm_kzalloc
> - using BIT(n)
> - using usb_control_msg instead of usb_submit_urb
> - removing unneeded code
> Changes in v3:
> - implemented as HID device
> - some cleanups and bug fixes
> Changes in v4:
> - more cleanups
> - support for selecting leds
> - suppport for selecting status
>
> Changes in v5:
> - mode attribute documented under Documentation/ABI
> - made array for led_classdev
> - led devices uses now recommended naming scheme
>
> Changes in v6:
> - flush_work added
> - using hid device name instead of hard coded gt683r
> - allocating name buffers with devm_kzalloc
>
> Changes in v7:
> - buf with for fixed
>
> Changes in v8:
> - some cleanups and bugs fixed
>
> Changes in v9:
> - few style issues fixed
>
> Changes in v10:
> - race condition fixed
> - using proper attribute group
You need to send a separate patch on top of v9, which Jiri has already
applied to his tree.
<snip>
> +static DEVICE_ATTR_RW(leds_mode);
> +
> +static struct attribute *gt683r_attributes[] = {
> + &dev_attr_leds_mode.attr,
> + NULL,
> +};
> +
> +static struct attribute_group gt683r_attribute_group = {
> + .attrs = gt683r_attributes
> +};
> +
> +static int gt683r_led_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
> + int i;
> + int ret;
> + int name_sz;
> + char *name;
> + struct gt683r_led *led;
> +
> + led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL);
> + if (!led)
> + return -ENOMEM;
> +
> + mutex_init(&led->lock);
> + INIT_WORK(&led->work, gt683r_led_work);
> +
> + led->mode = GT683R_LED_NORMAL;
> + led->hdev = hdev;
> + hid_set_drvdata(hdev, led);
> +
> + ret = hid_parse(hdev);
> + if (ret) {
> + hid_err(hdev, "hid parsing failed\n");
> + return ret;
> + }
> +
> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> + if (ret) {
> + hid_err(hdev, "hw start failed\n");
> + return ret;
> + }
> +
> + for (i = 0; i < GT683R_LED_COUNT; i++) {
> + name_sz = strlen(dev_name(&hdev->dev)) +
> + strlen(gt683r_panel_names[i]) + 3;
> +
> + name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
> + if (!name) {
> + ret = -ENOMEM;
> + goto fail;
> + }
> +
> + snprintf(name, name_sz, "%s::%s",
> + dev_name(&hdev->dev), gt683r_panel_names[i]);
> + led->led_devs[i].name = name;
> + led->led_devs[i].max_brightness = 1;
> + led->led_devs[i].brightness_set = gt683r_brightness_set;
> + ret = led_classdev_register(&hdev->dev, &led->led_devs[i]);
> + if (ret) {
> + hid_err(hdev, "could not register led device\n");
> + goto fail;
> + }
> + }
> +
> + ret = sysfs_create_group(&led->hdev->dev.kobj, >683r_attribute_group);
> + if (ret) {
> + hid_err(hdev, "failed to create sysfs attributes\n");
> + goto fail;
> + }
This does not solve the race Greg is referring to. There's some more
info here:
http://kroah.com/log/blog/2013/06/26/how-to-create-a-sysfs-file-correctly/
but I'm not sure exactly how you'd apply that to an HID driver. Perhaps
you could use the struct device_driver in struct hid_driver (although it
is currently marked as "private").
Johan
> +
> + return 0;
> +
> +fail:
> + for (i = i - 1; i >= 0; i--)
> + led_classdev_unregister(&led->led_devs[i]);
> + hid_hw_stop(hdev);
> + return ret;
> +}
> +
> +static void gt683r_led_remove(struct hid_device *hdev)
> +{
> + int i;
> + struct gt683r_led *led = hid_get_drvdata(hdev);
> +
> + sysfs_remove_group(&led->hdev->dev.kobj, >683r_attribute_group);
> + for (i = 0; i < GT683R_LED_COUNT; i++)
> + led_classdev_unregister(&led->led_devs[i]);
> + flush_work(&led->work);
> + hid_hw_stop(hdev);
> +}
> +
> +static struct hid_driver gt683r_led_driver = {
> + .probe = gt683r_led_probe,
> + .remove = gt683r_led_remove,
> + .name = "gt683r_led",
> + .id_table = gt683r_led_id,
> +};
> +
> +module_hid_driver(gt683r_led_driver);
> +
> +MODULE_AUTHOR("Janne Kanniainen");
> +MODULE_DESCRIPTION("MSI GT683R led driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 34bb220..3692d37 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -641,7 +641,7 @@
> #define USB_DEVICE_ID_GENIUS_KB29E 0x3004
>
> #define USB_VENDOR_ID_MSI 0x1770
> -#define USB_DEVICE_ID_MSI_GX680R_LED_PANEL 0xff00
> +#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
>
> #define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
> #define USB_DEVICE_ID_N_S_HARMONY 0xc359
> diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
> index 8e4ddb3..c640e1d 100644
> --- a/drivers/hid/usbhid/hid-quirks.c
> +++ b/drivers/hid/usbhid/hid-quirks.c
> @@ -73,7 +73,7 @@ static const struct hid_blacklist {
> { USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER, HID_QUIRK_NO_INIT_REPORTS },
> { USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28, HID_QUIRK_NOGET },
> { USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_NOGET },
> - { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GX680R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
> + { USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL, HID_QUIRK_NO_INIT_REPORTS },
> { USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750, HID_QUIRK_NO_INIT_REPORTS },
> { USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE, HID_QUIRK_NO_INIT_REPORTS },
> { USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN, HID_QUIRK_NO_INIT_REPORTS },
^ permalink raw reply
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