Allwinnner A13 / A23 / A33 based Q8 tablets are popular cheap 7" tablets of which a new batch is produced every few weeks. Each batch uses a different mix of touchscreen, accelerometer and wifi peripherals. Given that each batch is different creating a devicetree for each variant is not desirable. This commit adds a Q8 tablet hardware manager which auto-detects the touchscreen and accelerometer so that a single generic dts can be used for these tablets. The wifi is connected to a discoverable bus (sdio or usb) and will be autodetected by the mmc resp. usb subsystems. Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx> --- .../misc/allwinner,sunxi-q8-hardwaremgr.txt | 52 +++ drivers/misc/Kconfig | 12 + drivers/misc/Makefile | 1 + drivers/misc/q8-hardwaremgr.c | 512 +++++++++++++++++++++ 4 files changed, 577 insertions(+) create mode 100644 Documentation/devicetree/bindings/misc/allwinner,sunxi-q8-hardwaremgr.txt create mode 100644 drivers/misc/q8-hardwaremgr.c diff --git a/Documentation/devicetree/bindings/misc/allwinner,sunxi-q8-hardwaremgr.txt b/Documentation/devicetree/bindings/misc/allwinner,sunxi-q8-hardwaremgr.txt new file mode 100644 index 0000000..f428bf5 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/allwinner,sunxi-q8-hardwaremgr.txt @@ -0,0 +1,52 @@ +Q8 tablet hardware manager +-------------------------- + +Allwinnner A13 / A23 / A33 based Q8 tablets are popular cheap 7" tablets of +which a new batch is produced every few weeks. Each batch uses a different +mix of touchscreen, accelerometer and wifi peripherals. + +Given that each batch is different creating a devicetree for each variant is +not desirable. The Q8 tablet hardware manager bindings are bindings for an os +module which auto-detects the touchscreen so that a single +generic dts can be used for these tablets. + +The wifi is connected to a discoverable bus and will be autodetected by the os. + +Required properties: + - compatible : "allwinner,sunxi-q8-hardwaremgr" + - touchscreen : phandle of a template touchscreen node, this must be a + child node of the touchscreen i2c bus + +Optional properties: + - touchscreen-supply : regulator phandle for the touchscreen vdd supply + +touschreen node required properties: + - interrupt-parent : phandle pointing to the interrupt controller + serving the touchscreen interrupt + - interrupts : interrupt specification for the touchscreen interrupt + - power-gpios : Specification for the pin connected to the touchscreen's + enable / wake pin. This needs to be driven high to + enable the touchscreen controller + +Example: + +/ { + hwmgr { + compatible = "allwinner,sunxi-q8-hardwaremgr"; + touchscreen = <&touchscreen>; + touchscreen-supply = <®_ldo_io1>; + }; +}; + +&i2c0 { + touchscreen: touchscreen@0 { + interrupt-parent = <&pio>; + interrupts = <1 5 IRQ_TYPE_EDGE_FALLING>; /* PB5 */ + power-gpios = <&pio 7 1 GPIO_ACTIVE_HIGH>; /* PH1 */ + /* + * Enabled by sunxi-q8-hardwaremgr if it detects a + * known model touchscreen. + */ + status = "disabled"; + }; +}; diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index a216b46..c3e7772 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -804,6 +804,18 @@ config PANEL_BOOT_MESSAGE An empty message will only clear the display at driver init time. Any other printf()-formatted message is valid with newline and escape codes. +config Q8_HARDWAREMGR + tristate "Allwinner Q8 tablet hardware manager" + depends on GPIOLIB || COMPILE_TEST + depends on I2C + depends on OF + default n + help + This option enables support for autodetecting the touchscreen + on Allwinner Q8 tablets. + + If unsure, say N. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7410c6d..cac76b7 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_PANEL) += panel.o +obj-$(CONFIG_Q8_HARDWAREMGR) += q8-hardwaremgr.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o diff --git a/drivers/misc/q8-hardwaremgr.c b/drivers/misc/q8-hardwaremgr.c new file mode 100644 index 0000000..e75625e --- /dev/null +++ b/drivers/misc/q8-hardwaremgr.c @@ -0,0 +1,512 @@ +/* + * Allwinner q8 formfactor tablet hardware manager + * + * Copyright (C) 2016 Hans de Goede <hdegoede@xxxxxxxxxx> + * + * 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 <asm/unaligned.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +/* + * We can detect which touchscreen controller is used automatically, + * but some controllers can be wired up differently depending on the + * q8 PCB variant used, so they need different firmware files / settings. + * + * We allow the user to specify a firmware_variant to select a config + * from a list of known configs. We also allow overriding each setting + * individually. + */ + +static int touchscreen_variant = -1; +module_param(touchscreen_variant, int, 0444); +MODULE_PARM_DESC(touchscreen_variant, "Touchscreen variant 0-x, -1 for auto"); + +static int touchscreen_width = -1; +module_param(touchscreen_width, int, 0444); +MODULE_PARM_DESC(touchscreen_width, "Touchscreen width, -1 for auto"); + +static int touchscreen_height = -1; +module_param(touchscreen_height, int, 0444); +MODULE_PARM_DESC(touchscreen_height, "Touchscreen height, -1 for auto"); + +static int touchscreen_invert_x = -1; +module_param(touchscreen_invert_x, int, 0444); +MODULE_PARM_DESC(touchscreen_invert_x, "Touchscreen invert x, -1 for auto"); + +static int touchscreen_invert_y = -1; +module_param(touchscreen_invert_y, int, 0444); +MODULE_PARM_DESC(touchscreen_invert_y, "Touchscreen invert y, -1 for auto"); + +static int touchscreen_swap_x_y = -1; +module_param(touchscreen_swap_x_y, int, 0444); +MODULE_PARM_DESC(touchscreen_swap_x_y, "Touchscreen swap x y, -1 for auto"); + +static char *touchscreen_fw_name; +module_param(touchscreen_fw_name, charp, 0444); +MODULE_PARM_DESC(touchscreen_fw_name, "Touchscreen firmware filename"); + +#define TOUCHSCREEN_POWER_ON_DELAY 20 +#define SILEAD_REG_ID 0xFC +#define EKTF2127_RESPONSE 0x52 +#define EKTF2127_REQUEST 0x53 +#define EKTF2127_WIDTH 0x63 + +enum touchscreen_model { + touchscreen_unknown, + gsl1680_a082, + gsl1680_b482, + ektf2127, + zet6251, +}; + +struct q8_hardwaremgr_data { + struct device *dev; + bool touchscreen_needs_regulator; + enum touchscreen_model touchscreen_model; + int touchscreen_addr; + int touchscreen_variant; + int touchscreen_width; + int touchscreen_height; + int touchscreen_invert_x; + int touchscreen_invert_y; + int touchscreen_swap_x_y; + const char *touchscreen_compatible; + const char *touchscreen_fw_name; +}; + +typedef int (*probe_func)(struct q8_hardwaremgr_data *data, + struct i2c_adapter *adap); + +#if 0 + ret = i2c_smbus_xfer(adap, 0x40, 0, I2C_SMBUS_WRITE, 0, + I2C_SMBUS_QUICK, NULL); + if (ret < 0) + return -ENODEV; + +#endif + +static int q8_hardwaremgr_probe_touchscreen(struct q8_hardwaremgr_data *data, + struct i2c_adapter *adap) +{ + struct i2c_client *client; + unsigned char buff[24]; + __le32 chip_id; + int ret; + + msleep(TOUCHSCREEN_POWER_ON_DELAY); + + /* Check for silead touchsceen at addr 0x40 */ + client = i2c_new_dummy(adap, 0x40); + if (!client) + return -ENOMEM; + + ret = i2c_smbus_read_i2c_block_data(client, SILEAD_REG_ID, + sizeof(chip_id), (u8 *)&chip_id); + if (ret == sizeof(chip_id)) { + switch (le32_to_cpu(chip_id)) { + case 0xa0820000: + data->touchscreen_addr = 0x40; + data->touchscreen_compatible = "silead,gsl1680"; + data->touchscreen_model = gsl1680_a082; + dev_info(data->dev, "Found Silead touchscreen ID: 0xa0820000\n"); + break; + case 0xb4820000: + data->touchscreen_addr = 0x40; + data->touchscreen_compatible = "silead,gsl1680"; + data->touchscreen_model = gsl1680_b482; + dev_info(data->dev, "Found Silead touchscreen ID: 0xb4820000\n"); + break; + default: + dev_warn(data->dev, "Found Silead touchscreen with unknown ID: 0x%08x\n", + le32_to_cpu(chip_id)); + } + ret = 0; + } + i2c_unregister_device(client); + if (ret == 0 || ret == -ETIMEDOUT /* Bus stuck bail immediately */) + return ret; + + /* Check for Elan eKTF2127 touchsceen at addr 0x15 */ + client = i2c_new_dummy(adap, 0x15); + if (!client) + return -ENOMEM; + + do { + /* Read hello, ignore data, depends on initial power state */ + ret = i2c_master_recv(client, buff, 4); + if (ret != 4) + break; + + /* Request width */ + buff[0] = EKTF2127_REQUEST; + buff[1] = EKTF2127_WIDTH; + buff[2] = 0x00; + buff[3] = 0x00; + ret = i2c_master_send(client, buff, 4); + if (ret != 4) + break; + + msleep(20); + + /* Read response */ + ret = i2c_master_recv(client, buff, 4); + if (ret != 4) + break; + + if (buff[0] == EKTF2127_RESPONSE && buff[1] == EKTF2127_WIDTH) { + data->touchscreen_addr = 0x15; + data->touchscreen_compatible = "elan,ektf2127"; + data->touchscreen_model = ektf2127; + dev_info(data->dev, "Found Elan eKTF2127 touchscreen\n"); + ret = 0; + } + } while (0); + i2c_unregister_device(client); + if (ret == 0 || ret == -ETIMEDOUT /* Bus stuck bail immediately */) + return ret; + + /* Check for Zeitec zet6251 touchsceen at addr 0x76 */ + client = i2c_new_dummy(adap, 0x76); + if (!client) + return -ENOMEM; + + /* + * We only do a simple read finger data packet test, because some + * versions require firmware to be loaded. If not firmware is loaded + * the buffer will be filed with 0xff, so we ignore the contents. + */ + ret = i2c_master_recv(client, buff, 24); + if (ret == 24) { + data->touchscreen_addr = 0x76; + data->touchscreen_compatible = "zeitec,zet6251"; + data->touchscreen_model = zet6251; + dev_info(data->dev, "Found Zeitec zet6251 touchscreen\n"); + ret = 0; + } + i2c_unregister_device(client); + if (ret == 0 || ret == -ETIMEDOUT /* Bus stuck bail immediately */) + return ret; + + return -ENODEV; +} + +static int q8_hardwaremgr_do_probe(struct q8_hardwaremgr_data *data, + const char *prefix, probe_func func) +{ + struct device *dev = data->dev; + struct device_node *np; + struct i2c_adapter *adap; + struct regulator *reg; + struct gpio_desc *gpio; + int ret = 0; + + np = of_parse_phandle(dev->of_node, prefix, 0); + if (!np) { + dev_err(dev, "Error %s not set\n", prefix); + return -EINVAL; + } + + adap = of_get_i2c_adapter_by_node(np->parent); + if (!adap) { + ret = -EPROBE_DEFER; + goto put_node; + } + + reg = regulator_get_optional(dev, prefix); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + if (ret == -EPROBE_DEFER) + goto put_adapter; + reg = NULL; + } + + gpio = fwnode_get_named_gpiod(&np->fwnode, "power-gpios"); + if (IS_ERR(gpio)) { + ret = PTR_ERR(gpio); + if (ret == -EPROBE_DEFER) + goto put_reg; + gpio = NULL; + } + + /* First try with only the power gpio driven high */ + if (gpio) { + ret = gpiod_direction_output(gpio, 1); + if (ret) + goto put_gpio; + } + + dev_info(dev, "Looking for %s without a regulator\n", prefix); + ret = func(data, adap); + if (ret != 0 && reg) { + /* Second try, also enable the regulator */ + ret = regulator_enable(reg); + if (ret) + goto restore_gpio; + + dev_info(dev, "Looking for %s with a regulator\n", prefix); + ret = func(data, adap); + if (ret == 0) + data->touchscreen_needs_regulator = true; + + regulator_disable(reg); + } + ret = 0; /* Not finding a device is not an error */ + +restore_gpio: + if (gpio) + gpiod_direction_output(gpio, 0); +put_gpio: + if (gpio) + gpiod_put(gpio); +put_reg: + if (reg) + regulator_put(reg); +put_adapter: + i2c_put_adapter(adap); + +put_node: + of_node_put(np); + + return ret; +} + +static void q8_hardwaremgr_apply_gsl1680_a082_variant( + struct q8_hardwaremgr_data *data) +{ + if (touchscreen_variant != -1) { + data->touchscreen_variant = touchscreen_variant; + } else { + if (of_machine_is_compatible("allwinner,sun8i-a33")) + data->touchscreen_variant = 1; + else + data->touchscreen_variant = 0; + } + + switch (data->touchscreen_variant) { + default: + dev_warn(data->dev, "Error unknown touchscreen_variant %d using 0\n", + touchscreen_variant); + /* Fall through */ + case 0: + data->touchscreen_width = 1024; + data->touchscreen_height = 600; + data->touchscreen_fw_name = "gsl1680-a082-q8-700.fw"; + break; + case 1: + data->touchscreen_width = 480; + data->touchscreen_height = 800; + data->touchscreen_swap_x_y = 1; + data->touchscreen_fw_name = "gsl1680-a082-q8-a70.fw"; + break; + } +} + +static void q8_hardwaremgr_apply_gsl1680_b482_variant( + struct q8_hardwaremgr_data *data) +{ + if (touchscreen_variant != -1) + data->touchscreen_variant = touchscreen_variant; + + switch (data->touchscreen_variant) { + default: + dev_warn(data->dev, "Error unknown touchscreen_variant %d using 0\n", + touchscreen_variant); + /* Fall through */ + case 0: + data->touchscreen_width = 960; + data->touchscreen_height = 640; + data->touchscreen_fw_name = "gsl1680-b482-q8-d702.fw"; + break; + case 1: + data->touchscreen_width = 960; + data->touchscreen_height = 640; + data->touchscreen_fw_name = "gsl1680-b482-q8-a70.fw"; + break; + } +} + +static void q8_hardwaremgr_issue_gsl1680_warning( + struct q8_hardwaremgr_data *data) +{ + dev_warn(data->dev, "gsl1680 touchscreen may require kernel cmdline parameters to function properly\n"); + dev_warn(data->dev, "Try q8_hardwaremgr.touchscreen_invert_x=1 if x coordinates are inverted\n"); + dev_warn(data->dev, "Try q8_hardwaremgr.touchscreen_variant=%d if coordinates are all over the place\n", + !data->touchscreen_variant); + +#define show(x) \ + dev_info(data->dev, #x " %d (%s)\n", data->x, \ + (x == -1) ? "auto" : "user supplied") + + show(touchscreen_variant); + show(touchscreen_width); + show(touchscreen_height); + show(touchscreen_invert_x); + show(touchscreen_invert_y); + show(touchscreen_swap_x_y); + dev_info(data->dev, "touchscreen_fw_name %s (%s)\n", + data->touchscreen_fw_name, + (touchscreen_fw_name == NULL) ? "auto" : "user supplied"); +#undef show +} + +static void q8_hardwaremgr_apply_touchscreen(struct q8_hardwaremgr_data *data) +{ + struct device *dev = data->dev; + struct of_changeset cset; + struct device_node *np; + + switch (data->touchscreen_model) { + case touchscreen_unknown: + return; + case gsl1680_a082: + q8_hardwaremgr_apply_gsl1680_a082_variant(data); + break; + case gsl1680_b482: + q8_hardwaremgr_apply_gsl1680_b482_variant(data); + break; + case ektf2127: + case zet6251: + /* These have only 1 variant */ + break; + } + + if (touchscreen_width != -1) + data->touchscreen_width = touchscreen_width; + + if (touchscreen_height != -1) + data->touchscreen_height = touchscreen_height; + + if (touchscreen_invert_x != -1) + data->touchscreen_invert_x = touchscreen_invert_x; + + if (touchscreen_invert_y != -1) + data->touchscreen_invert_y = touchscreen_invert_y; + + if (touchscreen_swap_x_y != -1) + data->touchscreen_swap_x_y = touchscreen_swap_x_y; + + if (touchscreen_fw_name) + data->touchscreen_fw_name = touchscreen_fw_name; + + if (data->touchscreen_model == gsl1680_a082 || + data->touchscreen_model == gsl1680_b482) + q8_hardwaremgr_issue_gsl1680_warning(data); + + np = of_parse_phandle(data->dev->of_node, "touchscreen", 0); + /* Never happens already checked in q8_hardwaremgr_do_probe() */ + if (WARN_ON(!np)) + return; + + of_changeset_init(&cset); + of_changeset_add_property_u32(&cset, np, "reg", data->touchscreen_addr); + of_changeset_add_property_string(&cset, np, "compatible", + data->touchscreen_compatible); + + if (data->touchscreen_width) + of_changeset_add_property_u32(&cset, np, "touchscreen-size-x", + data->touchscreen_width); + if (data->touchscreen_height) + of_changeset_add_property_u32(&cset, np, "touchscreen-size-y", + data->touchscreen_height); + if (data->touchscreen_invert_x) + of_changeset_add_property_bool(&cset, np, + "touchscreen-inverted-x"); + if (data->touchscreen_invert_y) + of_changeset_add_property_bool(&cset, np, + "touchscreen-inverted-y"); + if (data->touchscreen_swap_x_y) + of_changeset_add_property_bool(&cset, np, + "touchscreen-swapped-x-y"); + if (data->touchscreen_fw_name) + of_changeset_add_property_string(&cset, np, "firmware-name", + data->touchscreen_fw_name); + if (data->touchscreen_needs_regulator) { + struct property *p; + + p = of_find_property(dev->of_node, "touchscreen-supply", NULL); + /* Never happens already checked in q8_hardwaremgr_do_probe() */ + if (WARN_ON(!p)) + return; + + of_changeset_add_property_copy(&cset, np, "vddio-supply", + p->value, p->length); + } + + of_changeset_update_property_string(&cset, np, "status", "okay"); + of_changeset_apply(&cset); + + of_node_put(np); +} + +static int q8_hardwaremgr_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct q8_hardwaremgr_data *data; + int ret = 0; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &pdev->dev; + + ret = q8_hardwaremgr_do_probe(data, "touchscreen", + q8_hardwaremgr_probe_touchscreen); + if (ret) + goto error; + + /* + * Our pinctrl may conflict with the pinctrl of the detected devices + * we're adding, so remove it before adding detected devices. + */ + if (dev->pins) { + devm_pinctrl_put(dev->pins->p); + devm_kfree(dev, dev->pins); + dev->pins = NULL; + } + + q8_hardwaremgr_apply_touchscreen(data); + +error: + kfree(data); + + return ret; +} + +static const struct of_device_id q8_hardwaremgr_of_match[] = { + { .compatible = "allwinner,sunxi-q8-hardwaremgr", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, q8_hardwaremgr_of_match); + +static struct platform_driver q8_hardwaremgr_driver = { + .driver = { + .name = "q8-hardwaremgr", + .of_match_table = of_match_ptr(q8_hardwaremgr_of_match), + }, + .probe = q8_hardwaremgr_probe, +}; + +module_platform_driver(q8_hardwaremgr_driver); + +MODULE_DESCRIPTION("Allwinner q8 formfactor tablet hardware manager"); +MODULE_AUTHOR("Hans de Goede <hdegoede@xxxxxxxxxx>"); +MODULE_LICENSE("GPL"); -- 2.9.3 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html