linux/drivers/input/touchscreen/sun4i-ts.c
<<
>>
Prefs
   1/*
   2 * Allwinner sunxi resistive touchscreen controller driver
   3 *
   4 * Copyright (C) 2013 - 2014 Hans de Goede <hdegoede@redhat.com>
   5 *
   6 * The hwmon parts are based on work by Corentin LABBE which is:
   7 * Copyright (C) 2013 Corentin LABBE <clabbe.montjoie@gmail.com>
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License as published by
  11 * the Free Software Foundation; either version 2 of the License, or
  12 * (at your option) any later version.
  13 *
  14 * This program is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17 * GNU General Public License for more details.
  18 */
  19
  20/*
  21 * The sun4i-ts controller is capable of detecting a second touch, but when a
  22 * second touch is present then the accuracy becomes so bad the reported touch
  23 * location is not useable.
  24 *
  25 * The original android driver contains some complicated heuristics using the
  26 * aprox. distance between the 2 touches to see if the user is making a pinch
  27 * open / close movement, and then reports emulated multi-touch events around
  28 * the last touch coordinate (as the dual-touch coordinates are worthless).
  29 *
  30 * These kinds of heuristics are just asking for trouble (and don't belong
  31 * in the kernel). So this driver offers straight forward, reliable single
  32 * touch functionality only.
  33 *
  34 * s.a. A20 User Manual "1.15 TP" (Documentation/arm/sunxi/README)
  35 * (looks like the description in the A20 User Manual v1.3 is better
  36 * than the one in the A10 User Manual v.1.5)
  37 */
  38
  39#include <linux/err.h>
  40#include <linux/hwmon.h>
  41#include <linux/thermal.h>
  42#include <linux/init.h>
  43#include <linux/input.h>
  44#include <linux/interrupt.h>
  45#include <linux/io.h>
  46#include <linux/module.h>
  47#include <linux/of_platform.h>
  48#include <linux/platform_device.h>
  49#include <linux/slab.h>
  50
  51#define TP_CTRL0                0x00
  52#define TP_CTRL1                0x04
  53#define TP_CTRL2                0x08
  54#define TP_CTRL3                0x0c
  55#define TP_INT_FIFOC            0x10
  56#define TP_INT_FIFOS            0x14
  57#define TP_TPR                  0x18
  58#define TP_CDAT                 0x1c
  59#define TEMP_DATA               0x20
  60#define TP_DATA                 0x24
  61
  62/* TP_CTRL0 bits */
  63#define ADC_FIRST_DLY(x)        ((x) << 24) /* 8 bits */
  64#define ADC_FIRST_DLY_MODE(x)   ((x) << 23)
  65#define ADC_CLK_SEL(x)          ((x) << 22)
  66#define ADC_CLK_DIV(x)          ((x) << 20) /* 3 bits */
  67#define FS_DIV(x)               ((x) << 16) /* 4 bits */
  68#define T_ACQ(x)                ((x) << 0) /* 16 bits */
  69
  70/* TP_CTRL1 bits */
  71#define STYLUS_UP_DEBOUN(x)     ((x) << 12) /* 8 bits */
  72#define STYLUS_UP_DEBOUN_EN(x)  ((x) << 9)
  73#define TOUCH_PAN_CALI_EN(x)    ((x) << 6)
  74#define TP_DUAL_EN(x)           ((x) << 5)
  75#define TP_MODE_EN(x)           ((x) << 4)
  76#define TP_ADC_SELECT(x)        ((x) << 3)
  77#define ADC_CHAN_SELECT(x)      ((x) << 0)  /* 3 bits */
  78
  79/* on sun6i, bits 3~6 are left shifted by 1 to 4~7 */
  80#define SUN6I_TP_MODE_EN(x)     ((x) << 5)
  81
  82/* TP_CTRL2 bits */
  83#define TP_SENSITIVE_ADJUST(x)  ((x) << 28) /* 4 bits */
  84#define TP_MODE_SELECT(x)       ((x) << 26) /* 2 bits */
  85#define PRE_MEA_EN(x)           ((x) << 24)
  86#define PRE_MEA_THRE_CNT(x)     ((x) << 0) /* 24 bits */
  87
  88/* TP_CTRL3 bits */
  89#define FILTER_EN(x)            ((x) << 2)
  90#define FILTER_TYPE(x)          ((x) << 0)  /* 2 bits */
  91
  92/* TP_INT_FIFOC irq and fifo mask / control bits */
  93#define TEMP_IRQ_EN(x)          ((x) << 18)
  94#define OVERRUN_IRQ_EN(x)       ((x) << 17)
  95#define DATA_IRQ_EN(x)          ((x) << 16)
  96#define TP_DATA_XY_CHANGE(x)    ((x) << 13)
  97#define FIFO_TRIG(x)            ((x) << 8)  /* 5 bits */
  98#define DATA_DRQ_EN(x)          ((x) << 7)
  99#define FIFO_FLUSH(x)           ((x) << 4)
 100#define TP_UP_IRQ_EN(x)         ((x) << 1)
 101#define TP_DOWN_IRQ_EN(x)       ((x) << 0)
 102
 103/* TP_INT_FIFOS irq and fifo status bits */
 104#define TEMP_DATA_PENDING       BIT(18)
 105#define FIFO_OVERRUN_PENDING    BIT(17)
 106#define FIFO_DATA_PENDING       BIT(16)
 107#define TP_IDLE_FLG             BIT(2)
 108#define TP_UP_PENDING           BIT(1)
 109#define TP_DOWN_PENDING         BIT(0)
 110
 111/* TP_TPR bits */
 112#define TEMP_ENABLE(x)          ((x) << 16)
 113#define TEMP_PERIOD(x)          ((x) << 0)  /* t = x * 256 * 16 / clkin */
 114
 115struct sun4i_ts_data {
 116        struct device *dev;
 117        struct input_dev *input;
 118        void __iomem *base;
 119        unsigned int irq;
 120        bool ignore_fifo_data;
 121        int temp_data;
 122        int temp_offset;
 123        int temp_step;
 124};
 125
 126static void sun4i_ts_irq_handle_input(struct sun4i_ts_data *ts, u32 reg_val)
 127{
 128        u32 x, y;
 129
 130        if (reg_val & FIFO_DATA_PENDING) {
 131                x = readl(ts->base + TP_DATA);
 132                y = readl(ts->base + TP_DATA);
 133                /* The 1st location reported after an up event is unreliable */
 134                if (!ts->ignore_fifo_data) {
 135                        input_report_abs(ts->input, ABS_X, x);
 136                        input_report_abs(ts->input, ABS_Y, y);
 137                        /*
 138                         * The hardware has a separate down status bit, but
 139                         * that gets set before we get the first location,
 140                         * resulting in reporting a click on the old location.
 141                         */
 142                        input_report_key(ts->input, BTN_TOUCH, 1);
 143                        input_sync(ts->input);
 144                } else {
 145                        ts->ignore_fifo_data = false;
 146                }
 147        }
 148
 149        if (reg_val & TP_UP_PENDING) {
 150                ts->ignore_fifo_data = true;
 151                input_report_key(ts->input, BTN_TOUCH, 0);
 152                input_sync(ts->input);
 153        }
 154}
 155
 156static irqreturn_t sun4i_ts_irq(int irq, void *dev_id)
 157{
 158        struct sun4i_ts_data *ts = dev_id;
 159        u32 reg_val;
 160
 161        reg_val  = readl(ts->base + TP_INT_FIFOS);
 162
 163        if (reg_val & TEMP_DATA_PENDING)
 164                ts->temp_data = readl(ts->base + TEMP_DATA);
 165
 166        if (ts->input)
 167                sun4i_ts_irq_handle_input(ts, reg_val);
 168
 169        writel(reg_val, ts->base + TP_INT_FIFOS);
 170
 171        return IRQ_HANDLED;
 172}
 173
 174static int sun4i_ts_open(struct input_dev *dev)
 175{
 176        struct sun4i_ts_data *ts = input_get_drvdata(dev);
 177
 178        /* Flush, set trig level to 1, enable temp, data and up irqs */
 179        writel(TEMP_IRQ_EN(1) | DATA_IRQ_EN(1) | FIFO_TRIG(1) | FIFO_FLUSH(1) |
 180                TP_UP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
 181
 182        return 0;
 183}
 184
 185static void sun4i_ts_close(struct input_dev *dev)
 186{
 187        struct sun4i_ts_data *ts = input_get_drvdata(dev);
 188
 189        /* Deactivate all input IRQs */
 190        writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
 191}
 192
 193static int sun4i_get_temp(const struct sun4i_ts_data *ts, int *temp)
 194{
 195        /* No temp_data until the first irq */
 196        if (ts->temp_data == -1)
 197                return -EAGAIN;
 198
 199        *temp = ts->temp_data * ts->temp_step - ts->temp_offset;
 200
 201        return 0;
 202}
 203
 204static int sun4i_get_tz_temp(void *data, int *temp)
 205{
 206        return sun4i_get_temp(data, temp);
 207}
 208
 209static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
 210        .get_temp = sun4i_get_tz_temp,
 211};
 212
 213static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
 214                         char *buf)
 215{
 216        struct sun4i_ts_data *ts = dev_get_drvdata(dev);
 217        int temp;
 218        int error;
 219
 220        error = sun4i_get_temp(ts, &temp);
 221        if (error)
 222                return error;
 223
 224        return sprintf(buf, "%d\n", temp);
 225}
 226
 227static ssize_t show_temp_label(struct device *dev,
 228                              struct device_attribute *devattr, char *buf)
 229{
 230        return sprintf(buf, "SoC temperature\n");
 231}
 232
 233static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL);
 234static DEVICE_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL);
 235
 236static struct attribute *sun4i_ts_attrs[] = {
 237        &dev_attr_temp1_input.attr,
 238        &dev_attr_temp1_label.attr,
 239        NULL
 240};
 241ATTRIBUTE_GROUPS(sun4i_ts);
 242
 243static int sun4i_ts_probe(struct platform_device *pdev)
 244{
 245        struct sun4i_ts_data *ts;
 246        struct device *dev = &pdev->dev;
 247        struct device_node *np = dev->of_node;
 248        struct device *hwmon;
 249        int error;
 250        u32 reg;
 251        bool ts_attached;
 252        u32 tp_sensitive_adjust = 15;
 253        u32 filter_type = 1;
 254
 255        ts = devm_kzalloc(dev, sizeof(struct sun4i_ts_data), GFP_KERNEL);
 256        if (!ts)
 257                return -ENOMEM;
 258
 259        ts->dev = dev;
 260        ts->ignore_fifo_data = true;
 261        ts->temp_data = -1;
 262        if (of_device_is_compatible(np, "allwinner,sun6i-a31-ts")) {
 263                /* Allwinner SDK has temperature (C) = (value / 6) - 271 */
 264                ts->temp_offset = 271000;
 265                ts->temp_step = 167;
 266        } else if (of_device_is_compatible(np, "allwinner,sun4i-a10-ts")) {
 267                /*
 268                 * The A10 temperature sensor has quite a wide spread, these
 269                 * parameters are based on the averaging of the calibration
 270                 * results of 4 completely different boards, with a spread of
 271                 * temp_step from 0.096 - 0.170 and temp_offset from 176 - 331.
 272                 */
 273                ts->temp_offset = 257000;
 274                ts->temp_step = 133;
 275        } else {
 276                /*
 277                 * The user manuals do not contain the formula for calculating
 278                 * the temperature. The formula used here is from the AXP209,
 279                 * which is designed by X-Powers, an affiliate of Allwinner:
 280                 *
 281                 *     temperature (C) = (value * 0.1) - 144.7
 282                 *
 283                 * Allwinner does not have any documentation whatsoever for
 284                 * this hardware. Moreover, it is claimed that the sensor
 285                 * is inaccurate and cannot work properly.
 286                 */
 287                ts->temp_offset = 144700;
 288                ts->temp_step = 100;
 289        }
 290
 291        ts_attached = of_property_read_bool(np, "allwinner,ts-attached");
 292        if (ts_attached) {
 293                ts->input = devm_input_allocate_device(dev);
 294                if (!ts->input)
 295                        return -ENOMEM;
 296
 297                ts->input->name = pdev->name;
 298                ts->input->phys = "sun4i_ts/input0";
 299                ts->input->open = sun4i_ts_open;
 300                ts->input->close = sun4i_ts_close;
 301                ts->input->id.bustype = BUS_HOST;
 302                ts->input->id.vendor = 0x0001;
 303                ts->input->id.product = 0x0001;
 304                ts->input->id.version = 0x0100;
 305                ts->input->evbit[0] =  BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);
 306                __set_bit(BTN_TOUCH, ts->input->keybit);
 307                input_set_abs_params(ts->input, ABS_X, 0, 4095, 0, 0);
 308                input_set_abs_params(ts->input, ABS_Y, 0, 4095, 0, 0);
 309                input_set_drvdata(ts->input, ts);
 310        }
 311
 312        ts->base = devm_ioremap_resource(dev,
 313                              platform_get_resource(pdev, IORESOURCE_MEM, 0));
 314        if (IS_ERR(ts->base))
 315                return PTR_ERR(ts->base);
 316
 317        ts->irq = platform_get_irq(pdev, 0);
 318        error = devm_request_irq(dev, ts->irq, sun4i_ts_irq, 0, "sun4i-ts", ts);
 319        if (error)
 320                return error;
 321
 322        /*
 323         * Select HOSC clk, clkin = clk / 6, adc samplefreq = clkin / 8192,
 324         * t_acq = clkin / (16 * 64)
 325         */
 326        writel(ADC_CLK_SEL(0) | ADC_CLK_DIV(2) | FS_DIV(7) | T_ACQ(63),
 327               ts->base + TP_CTRL0);
 328
 329        /*
 330         * tp_sensitive_adjust is an optional property
 331         * tp_mode = 0 : only x and y coordinates, as we don't use dual touch
 332         */
 333        of_property_read_u32(np, "allwinner,tp-sensitive-adjust",
 334                             &tp_sensitive_adjust);
 335        writel(TP_SENSITIVE_ADJUST(tp_sensitive_adjust) | TP_MODE_SELECT(0),
 336               ts->base + TP_CTRL2);
 337
 338        /*
 339         * Enable median and averaging filter, optional property for
 340         * filter type.
 341         */
 342        of_property_read_u32(np, "allwinner,filter-type", &filter_type);
 343        writel(FILTER_EN(1) | FILTER_TYPE(filter_type), ts->base + TP_CTRL3);
 344
 345        /* Enable temperature measurement, period 1953 (2 seconds) */
 346        writel(TEMP_ENABLE(1) | TEMP_PERIOD(1953), ts->base + TP_TPR);
 347
 348        /*
 349         * Set stylus up debounce to aprox 10 ms, enable debounce, and
 350         * finally enable tp mode.
 351         */
 352        reg = STYLUS_UP_DEBOUN(5) | STYLUS_UP_DEBOUN_EN(1);
 353        if (of_device_is_compatible(np, "allwinner,sun6i-a31-ts"))
 354                reg |= SUN6I_TP_MODE_EN(1);
 355        else
 356                reg |= TP_MODE_EN(1);
 357        writel(reg, ts->base + TP_CTRL1);
 358
 359        /*
 360         * The thermal core does not register hwmon devices for DT-based
 361         * thermal zone sensors, such as this one.
 362         */
 363        hwmon = devm_hwmon_device_register_with_groups(ts->dev, "sun4i_ts",
 364                                                       ts, sun4i_ts_groups);
 365        if (IS_ERR(hwmon))
 366                return PTR_ERR(hwmon);
 367
 368        devm_thermal_zone_of_sensor_register(ts->dev, 0, ts, &sun4i_ts_tz_ops);
 369
 370        writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC);
 371
 372        if (ts_attached) {
 373                error = input_register_device(ts->input);
 374                if (error) {
 375                        writel(0, ts->base + TP_INT_FIFOC);
 376                        return error;
 377                }
 378        }
 379
 380        platform_set_drvdata(pdev, ts);
 381        return 0;
 382}
 383
 384static int sun4i_ts_remove(struct platform_device *pdev)
 385{
 386        struct sun4i_ts_data *ts = platform_get_drvdata(pdev);
 387
 388        /* Explicit unregister to avoid open/close changing the imask later */
 389        if (ts->input)
 390                input_unregister_device(ts->input);
 391
 392        /* Deactivate all IRQs */
 393        writel(0, ts->base + TP_INT_FIFOC);
 394
 395        return 0;
 396}
 397
 398static const struct of_device_id sun4i_ts_of_match[] = {
 399        { .compatible = "allwinner,sun4i-a10-ts", },
 400        { .compatible = "allwinner,sun5i-a13-ts", },
 401        { .compatible = "allwinner,sun6i-a31-ts", },
 402        { /* sentinel */ }
 403};
 404MODULE_DEVICE_TABLE(of, sun4i_ts_of_match);
 405
 406static struct platform_driver sun4i_ts_driver = {
 407        .driver = {
 408                .name   = "sun4i-ts",
 409                .of_match_table = of_match_ptr(sun4i_ts_of_match),
 410        },
 411        .probe  = sun4i_ts_probe,
 412        .remove = sun4i_ts_remove,
 413};
 414
 415module_platform_driver(sun4i_ts_driver);
 416
 417MODULE_DESCRIPTION("Allwinner sun4i resistive touchscreen controller driver");
 418MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
 419MODULE_LICENSE("GPL");
 420