Message ID | 1526048528-3613-1-git-send-email-mark.jonas@de.bosch.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Fri, May 11, 2018 at 5:22 PM, Mark Jonas <mark.jonas@de.bosch.com> wrote: > Add Rohm BU21029 resistive touch panel controller support with I2C > interface. > +#include <linux/of.h> This becomes redundant (see below). > +#define STOP_DELAY_US 50L > +#define START_DELAY_MS 2L > +#define BUF_LEN 8L No need to use L for such small numbers. Integer promotion is a part of C standard. > +#define SCALE_12BIT (1 << 12) > +#define MAX_12BIT ((1 << 12) - 1) BIT(12) GENMASK(11, 0) > +static int bu21029_touch_report(struct bu21029_ts_data *bu21029) > +{ > + struct i2c_client *i2c = bu21029->client; > + u8 buf[BUF_LEN]; > + int error = bu21029_touch_report(bu21029); > + Redundant empty line. > + if (error) { > + dev_err(&i2c->dev, "failed to report (error: %d)\n", error); Potential spamming case. > + return IRQ_NONE; > + } > +static void bu21029_stop_chip(struct input_dev *dev) > +{ > + struct bu21029_ts_data *bu21029 = input_get_drvdata(dev); > + > + disable_irq(bu21029->client->irq); > + del_timer_sync(&bu21029->timer); > + > + /* put chip into reset */ > + gpiod_set_value_cansleep(bu21029->reset_gpios, 1); > + udelay(STOP_DELAY_US); udelay() ?! > +} > + > +static int bu21029_start_chip(struct input_dev *dev) > +{ > + u16 hwid; > + > + /* take chip out of reset */ > + gpiod_set_value_cansleep(bu21029->reset_gpios, 0); > + mdelay(START_DELAY_MS); mdelay()?! > + > + error = i2c_smbus_read_i2c_block_data(i2c, > + BU21029_HWID_REG, > + 2, > + (u8 *)&hwid); > + if (error < 0) { > + dev_err(&i2c->dev, "failed to read HW ID\n"); > + goto out; > + } > + > + if (cpu_to_be16(hwid) != SUPPORTED_HWID) { Hmm... Why cpu_to_be16() is required? > + dev_err(&i2c->dev, "unsupported HW ID 0x%x\n", hwid); > + error = -ENODEV; > + goto out; > + } > +} > +static int bu21029_parse_dt(struct bu21029_ts_data *bu21029) You can get rid of DT requirement by... > +{ > + struct device *dev = &bu21029->client->dev; > + struct device_node *np = dev->of_node; > + u32 val32; > + int error; > + if (!np) { > + dev_err(dev, "no device tree data\n"); > + return -EINVAL; > + } (this becomes redundant) > + > + bu21029->reset_gpios = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(bu21029->reset_gpios)) { > + error = PTR_ERR(bu21029->reset_gpios); > + if (error != -EPROBE_DEFER) > + dev_err(dev, "invalid 'reset-gpios':%d\n", error); > + return error; > + } > + > + if (of_property_read_u32(np, "rohm,x-plate-ohms", &val32)) { ...simple calling device_property_read_u32() instead. > + dev_err(dev, "invalid 'x-plate-ohms' supplied\n"); > + return -EINVAL; > + } > + bu21029->x_plate_ohms = val32; > + > + touchscreen_parse_properties(bu21029->in_dev, false, &bu21029->prop); > + > + return 0; > +} > +#ifdef CONFIG_PM_SLEEP Instead... > +static int bu21029_suspend(struct device *dev) ...use __maby_unused annotation. > +static int bu21029_resume(struct device *dev) Ditto.
Hello Andy, > > Add Rohm BU21029 resistive touch panel controller support with I2C > > interface. > > > +#include <linux/of.h> > > This becomes redundant (see below). Removed. > > +#define STOP_DELAY_US 50L > > +#define START_DELAY_MS 2L > > +#define BUF_LEN 8L > > No need to use L for such small numbers. Integer promotion is a part > of C standard. OK. > > +#define SCALE_12BIT (1 << 12) > > +#define MAX_12BIT ((1 << 12) - 1) > > BIT(12) > GENMASK(11, 0) We are not convinced that we should use BIT() and GENMASK() here. The reason is that SCALE_12BIT is actually not used as a bit but as an input value for DIV_ROUND_CLOSEST. We think that the BIT() macro will hide the meaning of the value. MAX_12BIT is also a value and not a bit mask. Thus, we also think that using the GENMASK() macro will hide its purpose. Also, the documentation of GENMASK() says that it is a mask and not a value. > > +static int bu21029_touch_report(struct bu21029_ts_data *bu21029) > > +{ > > + struct i2c_client *i2c = bu21029->client; > > + u8 buf[BUF_LEN]; > > + int error = bu21029_touch_report(bu21029); > > > + > > Redundant empty line. Removed. > > + if (error) { > > > + dev_err(&i2c->dev, "failed to report (error: %d)\n", error); > > Potential spamming case. > > > + return IRQ_NONE; > > + } You are right, we will remove the error message. > > +static void bu21029_stop_chip(struct input_dev *dev) > > +{ > > + struct bu21029_ts_data *bu21029 = input_get_drvdata(dev); > > + > > + disable_irq(bu21029->client->irq); > > + del_timer_sync(&bu21029->timer); > > + > > + /* put chip into reset */ > > + gpiod_set_value_cansleep(bu21029->reset_gpios, 1); > > > + udelay(STOP_DELAY_US); > > udelay() ?! > > > +} According to the datasheet disabling the chip will take 30 microseconds. In the defines we added a buffer of 20 microseconds and thus STOP_DELAY_US is 50. The function guarantees that the chip is stopped before it returns. We think that it is ok to use udelay() here because in normal operation the chip is not stopped. It is only stopped when loading or unloading the driver, or when the system suspends. We would like to keep it like it is. > > +static int bu21029_start_chip(struct input_dev *dev) > > +{ > > > + u16 hwid; > > + > > + /* take chip out of reset */ > > + gpiod_set_value_cansleep(bu21029->reset_gpios, 0); > > > + mdelay(START_DELAY_MS); > > mdelay()?! > > > + > > + error = i2c_smbus_read_i2c_block_data(i2c, > > + BU21029_HWID_REG, > > + 2, > > + (u8 *)&hwid); > > + if (error < 0) { > > + dev_err(&i2c->dev, "failed to read HW ID\n"); > > + goto out; > > + } After de-asserting the reset chip takes 1 millisecond until it is operational. We added a 1 millisecond buffer to it. Thus, START_DELAY_MS is 2. The following I2C read will not succeed without waiting for the chip being ready. > > + if (cpu_to_be16(hwid) != SUPPORTED_HWID) { > > Hmm... Why cpu_to_be16() is required? > > > + dev_err(&i2c->dev, "unsupported HW ID 0x%x\n", hwid); > > + error = -ENODEV; > > + goto out; > > + } > > +} You are right, it works but what we meant to do here is to convert the chip's value (big endian) into the CPU endianness. We will change it to be16_to_cpu(). > > +static int bu21029_parse_dt(struct bu21029_ts_data *bu21029) > > You can get rid of DT requirement by... > > > +{ > > + struct device *dev = &bu21029->client->dev; > > + struct device_node *np = dev->of_node; > > + u32 val32; > > + int error; > > > + if (!np) { > > + dev_err(dev, "no device tree data\n"); > > + return -EINVAL; > > + } > > (this becomes redundant) > > > + > > + bu21029->reset_gpios = devm_gpiod_get(dev, "reset", > GPIOD_OUT_HIGH); > > + if (IS_ERR(bu21029->reset_gpios)) { > > + error = PTR_ERR(bu21029->reset_gpios); > > + if (error != -EPROBE_DEFER) > > + dev_err(dev, "invalid 'reset-gpios':%d\n", error); > > + return error; > > + } > > + > > > + if (of_property_read_u32(np, "rohm,x-plate-ohms", &val32)) { > > ...simple calling device_property_read_u32() instead. > > > + dev_err(dev, "invalid 'x-plate-ohms' supplied\n"); > > + return -EINVAL; > > + } > > + bu21029->x_plate_ohms = val32; > > + > > + touchscreen_parse_properties(bu21029->in_dev, false, &bu21029->prop); > > + > > + return 0; > > +} Thank you, changed. > > +#ifdef CONFIG_PM_SLEEP > > Instead... > > > +static int bu21029_suspend(struct device *dev) > > ...use __maby_unused annotation. > > > +static int bu21029_resume(struct device *dev) > > Ditto. OK, added. Regards, Mark Mark Jonas Building Technologies, Panel Software Fire (BT-FIR/ENG1) Bosch Sicherheitssysteme GmbH | Postfach 11 11 | 85626 Grasbrunn | GERMANY | www.boschsecurity.com Sitz: Stuttgart, Registergericht: Amtsgericht Stuttgart HRB 23118 Aufsichtsratsvorsitzender: Stefan Hartung; Geschäftsführung: Gert van Iperen, Andreas Bartz, Thomas Quante, Bernhard Schuster
On Wed, May 16, 2018 at 01:24:24PM +0000, Jonas Mark (BT-FIR/ENG1) wrote: > Hello Andy, > > > > Add Rohm BU21029 resistive touch panel controller support with I2C > > > interface. > > > > > +#include <linux/of.h> > > > > This becomes redundant (see below). > > Removed. > > > > +#define STOP_DELAY_US 50L > > > +#define START_DELAY_MS 2L > > > +#define BUF_LEN 8L > > > > No need to use L for such small numbers. Integer promotion is a part > > of C standard. > > OK. > > > > +#define SCALE_12BIT (1 << 12) > > > +#define MAX_12BIT ((1 << 12) - 1) > > > > BIT(12) > > GENMASK(11, 0) > > We are not convinced that we should use BIT() and GENMASK() here. > > The reason is that SCALE_12BIT is actually not used as a bit but as an > input value for DIV_ROUND_CLOSEST. We think that the BIT() macro will > hide the meaning of the value. > > MAX_12BIT is also a value and not a bit mask. Thus, we also think that > using the GENMASK() macro will hide its purpose. Also, the > documentation of GENMASK() says that it is a mask and not a value. I agree. > > > > +static int bu21029_touch_report(struct bu21029_ts_data *bu21029) > > > +{ > > > + struct i2c_client *i2c = bu21029->client; > > > + u8 buf[BUF_LEN]; > > > + int error = bu21029_touch_report(bu21029); > > > > > + > > > > Redundant empty line. > > Removed. > > > > + if (error) { > > > > > + dev_err(&i2c->dev, "failed to report (error: %d)\n", error); > > > > Potential spamming case. > > > > > + return IRQ_NONE; > > > + } > > You are right, we will remove the error message. > > > > +static void bu21029_stop_chip(struct input_dev *dev) > > > +{ > > > + struct bu21029_ts_data *bu21029 = input_get_drvdata(dev); > > > + > > > + disable_irq(bu21029->client->irq); > > > + del_timer_sync(&bu21029->timer); > > > + > > > + /* put chip into reset */ > > > + gpiod_set_value_cansleep(bu21029->reset_gpios, 1); > > > > > + udelay(STOP_DELAY_US); > > > > udelay() ?! > > > > > +} > > According to the datasheet disabling the chip will take 30 microseconds. > In the defines we added a buffer of 20 microseconds and thus > STOP_DELAY_US is 50. The function guarantees that the chip is stopped > before it returns. > > We think that it is ok to use udelay() here because in normal operation > the chip is not stopped. It is only stopped when loading or unloading > the driver, or when the system suspends. > > We would like to keep it like it is. The issue is not with having delay here, but the kind of delay you are using: udelay makes CPU spin for given amount of time; you really want msleep() or usleep_range() here. > > > > +static int bu21029_start_chip(struct input_dev *dev) > > > +{ > > > > > + u16 hwid; > > > + > > > + /* take chip out of reset */ > > > + gpiod_set_value_cansleep(bu21029->reset_gpios, 0); > > > > > + mdelay(START_DELAY_MS); > > > > mdelay()?! Same here - replace with msleep(). > > > > > + > > > + error = i2c_smbus_read_i2c_block_data(i2c, > > > + BU21029_HWID_REG, > > > + 2, > > > + (u8 *)&hwid); > > > + if (error < 0) { > > > + dev_err(&i2c->dev, "failed to read HW ID\n"); > > > + goto out; > > > + } > > After de-asserting the reset chip takes 1 millisecond until it is > operational. We added a 1 millisecond buffer to it. Thus, > START_DELAY_MS is 2. > > The following I2C read will not succeed without waiting for the chip > being ready. > > > > + if (cpu_to_be16(hwid) != SUPPORTED_HWID) { > > > > Hmm... Why cpu_to_be16() is required? > > > > > + dev_err(&i2c->dev, "unsupported HW ID 0x%x\n", hwid); > > > + error = -ENODEV; > > > + goto out; > > > + } > > > +} > > You are right, it works but what we meant to do here is to convert the > chip's value (big endian) into the CPU endianness. We will change it to > be16_to_cpu(). > > > > +static int bu21029_parse_dt(struct bu21029_ts_data *bu21029) > > > > You can get rid of DT requirement by... > > > > > +{ > > > + struct device *dev = &bu21029->client->dev; > > > + struct device_node *np = dev->of_node; > > > + u32 val32; > > > + int error; > > > > > + if (!np) { > > > + dev_err(dev, "no device tree data\n"); > > > + return -EINVAL; > > > + } > > > > (this becomes redundant) > > > > > + > > > + bu21029->reset_gpios = devm_gpiod_get(dev, "reset", > > GPIOD_OUT_HIGH); > > > + if (IS_ERR(bu21029->reset_gpios)) { > > > + error = PTR_ERR(bu21029->reset_gpios); > > > + if (error != -EPROBE_DEFER) > > > + dev_err(dev, "invalid 'reset-gpios':%d\n", error); > > > + return error; > > > + } > > > + > > > > > + if (of_property_read_u32(np, "rohm,x-plate-ohms", &val32)) { > > > > ...simple calling device_property_read_u32() instead. > > > > > + dev_err(dev, "invalid 'x-plate-ohms' supplied\n"); > > > + return -EINVAL; > > > + } > > > + bu21029->x_plate_ohms = val32; > > > + > > > + touchscreen_parse_properties(bu21029->in_dev, false, &bu21029->prop); > > > + > > > + return 0; > > > +} > > Thank you, changed. > > > > +#ifdef CONFIG_PM_SLEEP > > > > Instead... > > > > > +static int bu21029_suspend(struct device *dev) > > > > ...use __maby_unused annotation. > > > > > +static int bu21029_resume(struct device *dev) > > > > Ditto. > > OK, added. You also need to drop #ifdef CONFIG_SLEEP. That's the point: we want to reduce amount of conditionally compiled code and use __maybe_unused to shut off compiler warning about some functions not used in certain configurations. We rely on linker to eliminate dead functions from executable. Thanks.
Hello Dmitry, > > > > +static void bu21029_stop_chip(struct input_dev *dev) > > > > +{ > > > > + struct bu21029_ts_data *bu21029 = input_get_drvdata(dev); > > > > + > > > > + disable_irq(bu21029->client->irq); > > > > + del_timer_sync(&bu21029->timer); > > > > + > > > > + /* put chip into reset */ > > > > + gpiod_set_value_cansleep(bu21029->reset_gpios, 1); > > > > > > > + udelay(STOP_DELAY_US); > > > > > > udelay() ?! > > > > > > > +} > > > > According to the datasheet disabling the chip will take 30 microseconds. > > In the defines we added a buffer of 20 microseconds and thus > > STOP_DELAY_US is 50. The function guarantees that the chip is stopped > > before it returns. > > > > We think that it is ok to use udelay() here because in normal operation > > the chip is not stopped. It is only stopped when loading or unloading > > the driver, or when the system suspends. > > > > We would like to keep it like it is. > > The issue is not with having delay here, but the kind of delay you are > using: udelay makes CPU spin for given amount of time; you really want > msleep() or usleep_range() here. Understood and changed. > > > > +static int bu21029_start_chip(struct input_dev *dev) > > > > +{ > > > > > > > + u16 hwid; > > > > + > > > > + /* take chip out of reset */ > > > > + gpiod_set_value_cansleep(bu21029->reset_gpios, 0); > > > > > > > + mdelay(START_DELAY_MS); > > > > > > mdelay()?! > > Same here - replace with msleep(). Replaced. > > > Instead... > > > > > > > +static int bu21029_suspend(struct device *dev) > > > > > > ...use __maby_unused annotation. > > > > > > > +static int bu21029_resume(struct device *dev) > > > > > > Ditto. > > > > OK, added. > > You also need to drop #ifdef CONFIG_SLEEP. That's the point: we want to > reduce amount of conditionally compiled code and use __maybe_unused to > shut off compiler warning about some functions not used in certain > configurations. We rely on linker to eliminate dead functions from > executable. Done. Greetings, Mark Mark Jonas Building Technologies, Panel Software Fire (BT-FIR/ENG1) Bosch Sicherheitssysteme GmbH | Postfach 11 11 | 85626 Grasbrunn | GERMANY | www.boschsecurity.com Sitz: Stuttgart, Registergericht: Amtsgericht Stuttgart HRB 23118 Aufsichtsratsvorsitzender: Stefan Hartung; Geschäftsführung: Gert van Iperen, Andreas Bartz, Thomas Quante, Bernhard Schuster -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Zhu, Thank you for the patch! Perhaps something to improve: [auto build test WARNING on v4.17-rc4] [also build test WARNING on next-20180615] [cannot apply to input/next] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system] url: https://github.com/0day-ci/linux/commits/Mark-Jonas/Input-add-bu21029-touch-driver/20180512-091305 reproduce: # apt-get install sparse make ARCH=x86_64 allmodconfig make C=1 CF=-D__CHECK_ENDIAN__ sparse warnings: (new ones prefixed by >>) >> drivers/input/touchscreen/bu21029_ts.c:293:13: sparse: restricted __be16 degrades to integer vim +293 drivers/input/touchscreen/bu21029_ts.c 262 263 static int bu21029_start_chip(struct input_dev *dev) 264 { 265 struct bu21029_ts_data *bu21029 = input_get_drvdata(dev); 266 struct i2c_client *i2c = bu21029->client; 267 struct { 268 u8 reg; 269 u8 value; 270 } init_table[] = { 271 {BU21029_CFR0_REG, CFR0_VALUE}, 272 {BU21029_CFR1_REG, CFR1_VALUE}, 273 {BU21029_CFR2_REG, CFR2_VALUE}, 274 {BU21029_CFR3_REG, CFR3_VALUE}, 275 {BU21029_LDO_REG, LDO_VALUE} 276 }; 277 int error, i; 278 u16 hwid; 279 280 /* take chip out of reset */ 281 gpiod_set_value_cansleep(bu21029->reset_gpios, 0); 282 mdelay(START_DELAY_MS); 283 284 error = i2c_smbus_read_i2c_block_data(i2c, 285 BU21029_HWID_REG, 286 2, 287 (u8 *)&hwid); 288 if (error < 0) { 289 dev_err(&i2c->dev, "failed to read HW ID\n"); 290 goto out; 291 } 292 > 293 if (cpu_to_be16(hwid) != SUPPORTED_HWID) { 294 dev_err(&i2c->dev, "unsupported HW ID 0x%x\n", hwid); 295 error = -ENODEV; 296 goto out; 297 } 298 299 for (i = 0; i < ARRAY_SIZE(init_table); ++i) { 300 error = i2c_smbus_write_byte_data(i2c, 301 init_table[i].reg, 302 init_table[i].value); 303 if (error < 0) { 304 dev_err(&i2c->dev, 305 "failed to write 0x%x to register 0x%x\n", 306 init_table[i].value, 307 init_table[i].reg); 308 goto out; 309 } 310 } 311 312 error = i2c_smbus_write_byte(i2c, BU21029_AUTOSCAN); 313 if (error < 0) { 314 dev_err(&i2c->dev, "failed to start autoscan\n"); 315 goto out; 316 } 317 318 enable_irq(bu21029->client->irq); 319 return 0; 320 321 out: 322 bu21029_stop_chip(dev); 323 return error; 324 } 325 --- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation -- To unsubscribe from this list: send the line "unsubscribe linux-input" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/Documentation/devicetree/bindings/input/touchscreen/bu21029.txt b/Documentation/devicetree/bindings/input/touchscreen/bu21029.txt new file mode 100644 index 0000000..030a888 --- /dev/null +++ b/Documentation/devicetree/bindings/input/touchscreen/bu21029.txt @@ -0,0 +1,34 @@ +* Rohm BU21029 Touch Screen Controller + +Required properties: + - compatible : must be "rohm,bu21029" + - reg : i2c device address of the chip (0x40 or 0x41) + - interrupt-parent : the phandle for the gpio controller + - interrupts : (gpio) interrupt to which the chip is connected + - reset-gpios : gpio pin to reset the chip (active low) + - rohm,x-plate-ohms : x-plate resistance in Ohm + +Optional properties: + - touchscreen-size-x : horizontal resolution of touchscreen (in pixels) + - touchscreen-size-y : vertical resolution of touchscreen (in pixels) + - touchscreen-max-pressure: maximum pressure value + +Example: + + &i2c1 { + /* ... */ + + bu21029: bu21029@40 { + compatible = "rohm,bu21029"; + reg = <0x40>; + interrupt-parent = <&gpio1>; + interrupts = <4 IRQ_TYPE_EDGE_FALLING>; + reset-gpios = <&gpio6 16 GPIO_ACTIVE_LOW>; + rohm,x-plate-ohms = <600>; + touchscreen-size-x = <800>; + touchscreen-size-y = <480>; + touchscreen-max-pressure = <4095>; + }; + + /* ... */ + }; diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 4f15496..e09fe8f 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -151,6 +151,18 @@ config TOUCHSCREEN_BU21013 To compile this driver as a module, choose M here: the module will be called bu21013_ts. +config TOUCHSCREEN_BU21029 + tristate "Rohm BU21029 based touch panel controllers" + depends on I2C + help + Say Y here if you have a Rohm BU21029 touchscreen controller + connected to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called bu21029_ts. + config TOUCHSCREEN_CHIPONE_ICN8318 tristate "chipone icn8318 touchscreen controller" depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index dddae79..f50624c 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_TOUCHSCREEN_AR1021_I2C) += ar1021_i2c.o obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT) += atmel_mxt_ts.o obj-$(CONFIG_TOUCHSCREEN_AUO_PIXCIR) += auo-pixcir-ts.o obj-$(CONFIG_TOUCHSCREEN_BU21013) += bu21013_ts.o +obj-$(CONFIG_TOUCHSCREEN_BU21029) += bu21029_ts.o obj-$(CONFIG_TOUCHSCREEN_CHIPONE_ICN8318) += chipone_icn8318.o obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110) += cy8ctmg110_ts.o obj-$(CONFIG_TOUCHSCREEN_CYTTSP_CORE) += cyttsp_core.o diff --git a/drivers/input/touchscreen/bu21029_ts.c b/drivers/input/touchscreen/bu21029_ts.c new file mode 100644 index 0000000..8d0cbe51 --- /dev/null +++ b/drivers/input/touchscreen/bu21029_ts.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rohm BU21029 touchscreen controller driver + * + * Copyright (C) 2015-2018 Bosch Sicherheitssysteme GmbH + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input/touchscreen.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/timer.h> + +/* + * HW_ID1 Register (PAGE=0, ADDR=0x0E, Reset value=0x02, Read only) + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | HW_IDH | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * HW_ID2 Register (PAGE=0, ADDR=0x0F, Reset value=0x29, Read only) + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | HW_IDL | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * HW_IDH: high 8bits of IC's ID + * HW_IDL: low 8bits of IC's ID + */ +#define BU21029_HWID_REG (0x0E << 3) +#define SUPPORTED_HWID 0x0229 + +/* + * CFR0 Register (PAGE=0, ADDR=0x00, Reset value=0x20) + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | 0 | 0 | CALIB | INTRM | 0 | 0 | 0 | 0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * CALIB: 0 = not to use calibration result (*) + * 1 = use calibration result + * INTRM: 0 = INT output depend on "pen down" (*) + * 1 = INT output always "0" + */ +#define BU21029_CFR0_REG (0x00 << 3) +#define CFR0_VALUE 0x00 + +/* + * CFR1 Register (PAGE=0, ADDR=0x01, Reset value=0xA6) + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | MAV | AVE[2:0] | 0 | SMPL[2:0] | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * MAV: 0 = median average filter off + * 1 = median average filter on (*) + * AVE: AVE+1 = number of average samples for MAV, + * if AVE>SMPL, then AVE=SMPL (=3) + * SMPL: SMPL+1 = number of conversion samples for MAV (=7) + */ +#define BU21029_CFR1_REG (0x01 << 3) +#define CFR1_VALUE 0xA6 + +/* + * CFR2 Register (PAGE=0, ADDR=0x02, Reset value=0x04) + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | INTVL_TIME[3:0] | TIME_ST_ADC[3:0] | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * INTVL_TIME: waiting time between completion of conversion + * and start of next conversion, only usable in + * autoscan mode (=20.480ms) + * TIME_ST_ADC: waiting time between application of voltage + * to panel and start of A/D conversion (=100us) + */ +#define BU21029_CFR2_REG (0x02 << 3) +#define CFR2_VALUE 0xC9 + +/* + * CFR3 Register (PAGE=0, ADDR=0x0B, Reset value=0x72) + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | RM8 | STRETCH| PU90K | DUAL | PIDAC_OFS[3:0] | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * RM8: 0 = coordinate resolution is 12bit (*) + * 1 = coordinate resolution is 8bit + * STRETCH: 0 = SCL_STRETCH function off + * 1 = SCL_STRETCH function on (*) + * PU90K: 0 = internal pull-up resistance for touch detection is ~50kohms (*) + * 1 = internal pull-up resistance for touch detection is ~90kohms + * DUAL: 0 = dual touch detection off (*) + * 1 = dual touch detection on + * PIDAC_OFS: dual touch detection circuit adjustment, it is not necessary + * to change this from initial value + */ +#define BU21029_CFR3_REG (0x0B << 3) +#define CFR3_VALUE 0x42 + +/* + * LDO Register (PAGE=0, ADDR=0x0C, Reset value=0x00) + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | 0 | PVDD[2:0] | 0 | AVDD[2:0] | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * PVDD: output voltage of panel output regulator (=2.000V) + * AVDD: output voltage of analog circuit regulator (=2.000V) + */ +#define BU21029_LDO_REG (0x0C << 3) +#define LDO_VALUE 0x77 + +/* + * Serial Interface Command Byte 1 (CID=1) + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * | 1 | CF | CMSK | PDM | STP | + * +--------+--------+--------+--------+--------+--------+--------+--------+ + * CF: conversion function, see table 3 in datasheet p6 (=0000, automatic scan) + * CMSK: 0 = executes convert function (*) + * 1 = reads the convert result + * PDM: 0 = power down after convert function stops (*) + * 1 = keep power on after convert function stops + * STP: 1 = abort current conversion and power down, set to "0" automatically + */ +#define BU21029_AUTOSCAN 0x80 + +/* + * The timeout value needs to be larger than INTVL_TIME + tConv4 (sample and + * conversion time), where tConv4 is calculated by formula: + * tPON + tDLY1 + (tTIME_ST_ADC + (tADC * tSMPL) * 2 + tDLY2) * 3 + * see figure 8 in datasheet p15 for details of each field. + */ +#define PEN_UP_TIMEOUT msecs_to_jiffies(50) + +#define STOP_DELAY_US 50L +#define START_DELAY_MS 2L +#define BUF_LEN 8L +#define SCALE_12BIT (1 << 12) +#define MAX_12BIT ((1 << 12) - 1) +#define DRIVER_NAME "bu21029" + +struct bu21029_ts_data { + struct i2c_client *client; + struct input_dev *in_dev; + struct timer_list timer; + struct gpio_desc *reset_gpios; + u32 x_plate_ohms; + struct touchscreen_properties prop; +}; + +static int bu21029_touch_report(struct bu21029_ts_data *bu21029) +{ + struct i2c_client *i2c = bu21029->client; + u8 buf[BUF_LEN]; + u16 x, y, z1, z2; + u32 rz; + s32 max_pressure = bu21029->in_dev->absinfo[ABS_PRESSURE].maximum; + + /* read touch data and deassert INT (by restarting the autoscan mode) */ + int error = i2c_smbus_read_i2c_block_data(i2c, + BU21029_AUTOSCAN, + BUF_LEN, + buf); + if (error < 0) + return error; + + /* + * compose upper 8 and lower 4 bits into a 12bit value: + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | ByteH | ByteL | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |b07|b06|b05|b04|b03|b02|b01|b00|b07|b06|b05|b04|b03|b02|b01|b00| + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |v11|v10|v09|v08|v07|v06|v05|v04|v03|v02|v01|v00| 0 | 0 | 0 | 0 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + */ + x = (buf[0] << 4) | (buf[1] >> 4); + y = (buf[2] << 4) | (buf[3] >> 4); + z1 = (buf[4] << 4) | (buf[5] >> 4); + z2 = (buf[6] << 4) | (buf[7] >> 4); + + if (z1 == 0 || z2 == 0) + return 0; + + /* + * calculate Rz (pressure resistance value) by equation: + * Rz = Rx * (x/Q) * ((z2/z1) - 1), where + * Rx is x-plate resistance, + * Q is the touch screen resolution (8bit = 256, 12bit = 4096) + * x, z1, z2 are the measured positions. + */ + rz = z2 - z1; + rz *= x; + rz *= bu21029->x_plate_ohms; + rz /= z1; + rz = DIV_ROUND_CLOSEST(rz, SCALE_12BIT); + if (rz <= max_pressure) { + touchscreen_report_pos(bu21029->in_dev, &bu21029->prop, + x, y, false); + input_report_abs(bu21029->in_dev, ABS_PRESSURE, + max_pressure - rz); + input_report_key(bu21029->in_dev, BTN_TOUCH, 1); + input_sync(bu21029->in_dev); + } + + return 0; +} + +static void bu21029_touch_release(struct timer_list *t) +{ + struct bu21029_ts_data *bu21029 = from_timer(bu21029, t, timer); + + input_report_abs(bu21029->in_dev, ABS_PRESSURE, 0); + input_report_key(bu21029->in_dev, BTN_TOUCH, 0); + input_sync(bu21029->in_dev); +} + +static irqreturn_t bu21029_touch_soft_irq(int irq, void *data) +{ + struct bu21029_ts_data *bu21029 = data; + struct i2c_client *i2c = bu21029->client; + + /* + * report touch and deassert interrupt (will assert again after + * INTVL_TIME + tConv4 for continuous touch) + */ + int error = bu21029_touch_report(bu21029); + + if (error) { + dev_err(&i2c->dev, "failed to report (error: %d)\n", error); + return IRQ_NONE; + } + + /* reset timer for pen up detection */ + mod_timer(&bu21029->timer, jiffies + PEN_UP_TIMEOUT); + + return IRQ_HANDLED; +} + +static void bu21029_stop_chip(struct input_dev *dev) +{ + struct bu21029_ts_data *bu21029 = input_get_drvdata(dev); + + disable_irq(bu21029->client->irq); + del_timer_sync(&bu21029->timer); + + /* put chip into reset */ + gpiod_set_value_cansleep(bu21029->reset_gpios, 1); + udelay(STOP_DELAY_US); +} + +static int bu21029_start_chip(struct input_dev *dev) +{ + struct bu21029_ts_data *bu21029 = input_get_drvdata(dev); + struct i2c_client *i2c = bu21029->client; + struct { + u8 reg; + u8 value; + } init_table[] = { + {BU21029_CFR0_REG, CFR0_VALUE}, + {BU21029_CFR1_REG, CFR1_VALUE}, + {BU21029_CFR2_REG, CFR2_VALUE}, + {BU21029_CFR3_REG, CFR3_VALUE}, + {BU21029_LDO_REG, LDO_VALUE} + }; + int error, i; + u16 hwid; + + /* take chip out of reset */ + gpiod_set_value_cansleep(bu21029->reset_gpios, 0); + mdelay(START_DELAY_MS); + + error = i2c_smbus_read_i2c_block_data(i2c, + BU21029_HWID_REG, + 2, + (u8 *)&hwid); + if (error < 0) { + dev_err(&i2c->dev, "failed to read HW ID\n"); + goto out; + } + + if (cpu_to_be16(hwid) != SUPPORTED_HWID) { + dev_err(&i2c->dev, "unsupported HW ID 0x%x\n", hwid); + error = -ENODEV; + goto out; + } + + for (i = 0; i < ARRAY_SIZE(init_table); ++i) { + error = i2c_smbus_write_byte_data(i2c, + init_table[i].reg, + init_table[i].value); + if (error < 0) { + dev_err(&i2c->dev, + "failed to write 0x%x to register 0x%x\n", + init_table[i].value, + init_table[i].reg); + goto out; + } + } + + error = i2c_smbus_write_byte(i2c, BU21029_AUTOSCAN); + if (error < 0) { + dev_err(&i2c->dev, "failed to start autoscan\n"); + goto out; + } + + enable_irq(bu21029->client->irq); + return 0; + +out: + bu21029_stop_chip(dev); + return error; +} + +static int bu21029_parse_dt(struct bu21029_ts_data *bu21029) +{ + struct device *dev = &bu21029->client->dev; + struct device_node *np = dev->of_node; + u32 val32; + int error; + + if (!np) { + dev_err(dev, "no device tree data\n"); + return -EINVAL; + } + + bu21029->reset_gpios = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(bu21029->reset_gpios)) { + error = PTR_ERR(bu21029->reset_gpios); + if (error != -EPROBE_DEFER) + dev_err(dev, "invalid 'reset-gpios':%d\n", error); + return error; + } + + if (of_property_read_u32(np, "rohm,x-plate-ohms", &val32)) { + dev_err(dev, "invalid 'x-plate-ohms' supplied\n"); + return -EINVAL; + } + bu21029->x_plate_ohms = val32; + + touchscreen_parse_properties(bu21029->in_dev, false, &bu21029->prop); + + return 0; +} + +static int bu21029_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bu21029_ts_data *bu21029; + struct input_dev *in_dev; + int error; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, + "i2c functionality support is not sufficient\n"); + return -EIO; + } + + bu21029 = devm_kzalloc(&client->dev, sizeof(*bu21029), GFP_KERNEL); + if (!bu21029) + return -ENOMEM; + + in_dev = devm_input_allocate_device(&client->dev); + if (!in_dev) { + dev_err(&client->dev, "unable to allocate input device\n"); + return -ENOMEM; + } + + bu21029->client = client; + bu21029->in_dev = in_dev; + timer_setup(&bu21029->timer, bu21029_touch_release, 0); + + in_dev->name = DRIVER_NAME; + in_dev->id.bustype = BUS_I2C; + in_dev->open = bu21029_start_chip; + in_dev->close = bu21029_stop_chip; + + input_set_capability(in_dev, EV_KEY, BTN_TOUCH); + input_set_abs_params(in_dev, ABS_X, 0, MAX_12BIT, 0, 0); + input_set_abs_params(in_dev, ABS_Y, 0, MAX_12BIT, 0, 0); + input_set_abs_params(in_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0); + + error = bu21029_parse_dt(bu21029); + if (error) + return error; + + input_set_drvdata(in_dev, bu21029); + + error = devm_request_threaded_irq(&client->dev, + client->irq, + NULL, + bu21029_touch_soft_irq, + IRQF_ONESHOT, + DRIVER_NAME, + bu21029); + if (error) { + dev_err(&client->dev, "unable to request touch irq\n"); + return error; + } + + bu21029_stop_chip(in_dev); + + error = input_register_device(in_dev); + if (error) { + dev_err(&client->dev, "unable to register input device\n"); + return error; + } + + i2c_set_clientdata(client, bu21029); + + return 0; +} + +static int bu21029_remove(struct i2c_client *client) +{ + struct bu21029_ts_data *bu21029 = i2c_get_clientdata(client); + + bu21029_stop_chip(bu21029->in_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bu21029_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct bu21029_ts_data *bu21029 = i2c_get_clientdata(i2c); + + mutex_lock(&bu21029->in_dev->mutex); + if (bu21029->in_dev->users) + bu21029_stop_chip(bu21029->in_dev); + mutex_unlock(&bu21029->in_dev->mutex); + + return 0; +} + +static int bu21029_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct bu21029_ts_data *bu21029 = i2c_get_clientdata(i2c); + + mutex_lock(&bu21029->in_dev->mutex); + if (bu21029->in_dev->users) + bu21029_start_chip(bu21029->in_dev); + mutex_unlock(&bu21029->in_dev->mutex); + + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(bu21029_pm_ops, bu21029_suspend, bu21029_resume); + +static const struct i2c_device_id bu21029_ids[] = { + {DRIVER_NAME, 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, bu21029_ids); + +static struct i2c_driver bu21029_driver = { + .driver = { + .name = DRIVER_NAME, + .pm = &bu21029_pm_ops, + }, + .id_table = bu21029_ids, + .probe = bu21029_probe, + .remove = bu21029_remove, +}; +module_i2c_driver(bu21029_driver); + +MODULE_AUTHOR("Zhu Yi <yi.zhu5@cn.bosch.com>"); +MODULE_DESCRIPTION("Rohm BU21029 touchscreen controller driver"); +MODULE_LICENSE("GPL v2");