linux/drivers/platform/chrome/chromeos_laptop.c
<<
>>
Prefs
   1/*
   2 *  chromeos_laptop.c - Driver to instantiate Chromebook i2c/smbus devices.
   3 *
   4 *  Author : Benson Leung <bleung@chromium.org>
   5 *
   6 *  Copyright (C) 2012 Google, Inc.
   7 *
   8 *  This program is free software; you can redistribute it and/or modify
   9 *  it under the terms of the GNU General Public License as published by
  10 *  the Free Software Foundation; either version 2 of the License, or
  11 *  (at your option) any later version.
  12 *
  13 *  This program is distributed in the hope that it will be useful,
  14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 *  GNU General Public License for more details.
  17 *
  18 *  You should have received a copy of the GNU General Public License
  19 *  along with this program; if not, write to the Free Software
  20 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21 *
  22 */
  23
  24#include <linux/dmi.h>
  25#include <linux/i2c.h>
  26#include <linux/platform_data/atmel_mxt_ts.h>
  27#include <linux/input.h>
  28#include <linux/interrupt.h>
  29#include <linux/module.h>
  30#include <linux/platform_device.h>
  31
  32#define ATMEL_TP_I2C_ADDR       0x4b
  33#define ATMEL_TP_I2C_BL_ADDR    0x25
  34#define ATMEL_TS_I2C_ADDR       0x4a
  35#define ATMEL_TS_I2C_BL_ADDR    0x26
  36#define CYAPA_TP_I2C_ADDR       0x67
  37#define ELAN_TP_I2C_ADDR        0x15
  38#define ISL_ALS_I2C_ADDR        0x44
  39#define TAOS_ALS_I2C_ADDR       0x29
  40
  41#define MAX_I2C_DEVICE_DEFERRALS        5
  42
  43static struct i2c_client *als;
  44static struct i2c_client *tp;
  45static struct i2c_client *ts;
  46
  47static const char *i2c_adapter_names[] = {
  48        "SMBus I801 adapter",
  49        "i915 gmbus vga",
  50        "i915 gmbus panel",
  51        "Synopsys DesignWare I2C adapter",
  52        "Synopsys DesignWare I2C adapter",
  53};
  54
  55/* Keep this enum consistent with i2c_adapter_names */
  56enum i2c_adapter_type {
  57        I2C_ADAPTER_SMBUS = 0,
  58        I2C_ADAPTER_VGADDC,
  59        I2C_ADAPTER_PANEL,
  60        I2C_ADAPTER_DESIGNWARE_0,
  61        I2C_ADAPTER_DESIGNWARE_1,
  62};
  63
  64enum i2c_peripheral_state {
  65        UNPROBED = 0,
  66        PROBED,
  67        TIMEDOUT,
  68};
  69
  70struct i2c_peripheral {
  71        int (*add)(enum i2c_adapter_type type);
  72        enum i2c_adapter_type type;
  73        enum i2c_peripheral_state state;
  74        int tries;
  75};
  76
  77#define MAX_I2C_PERIPHERALS 4
  78
  79struct chromeos_laptop {
  80        struct i2c_peripheral i2c_peripherals[MAX_I2C_PERIPHERALS];
  81};
  82
  83static struct chromeos_laptop *cros_laptop;
  84
  85static struct i2c_board_info cyapa_device = {
  86        I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR),
  87        .flags          = I2C_CLIENT_WAKE,
  88};
  89
  90static struct i2c_board_info elantech_device = {
  91        I2C_BOARD_INFO("elan_i2c", ELAN_TP_I2C_ADDR),
  92        .flags          = I2C_CLIENT_WAKE,
  93};
  94
  95static struct i2c_board_info isl_als_device = {
  96        I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR),
  97};
  98
  99static struct i2c_board_info tsl2583_als_device = {
 100        I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR),
 101};
 102
 103static struct i2c_board_info tsl2563_als_device = {
 104        I2C_BOARD_INFO("tsl2563", TAOS_ALS_I2C_ADDR),
 105};
 106
 107static int mxt_t19_keys[] = {
 108        KEY_RESERVED,
 109        KEY_RESERVED,
 110        KEY_RESERVED,
 111        KEY_RESERVED,
 112        KEY_RESERVED,
 113        BTN_LEFT
 114};
 115
 116static struct mxt_platform_data atmel_224s_tp_platform_data = {
 117        .irqflags               = IRQF_TRIGGER_FALLING,
 118        .t19_num_keys           = ARRAY_SIZE(mxt_t19_keys),
 119        .t19_keymap             = mxt_t19_keys,
 120        .suspend_mode           = MXT_SUSPEND_T9_CTRL,
 121};
 122
 123static struct i2c_board_info atmel_224s_tp_device = {
 124        I2C_BOARD_INFO("atmel_mxt_tp", ATMEL_TP_I2C_ADDR),
 125        .platform_data = &atmel_224s_tp_platform_data,
 126        .flags          = I2C_CLIENT_WAKE,
 127};
 128
 129static struct mxt_platform_data atmel_1664s_platform_data = {
 130        .irqflags               = IRQF_TRIGGER_FALLING,
 131        .suspend_mode           = MXT_SUSPEND_T9_CTRL,
 132};
 133
 134static struct i2c_board_info atmel_1664s_device = {
 135        I2C_BOARD_INFO("atmel_mxt_ts", ATMEL_TS_I2C_ADDR),
 136        .platform_data = &atmel_1664s_platform_data,
 137        .flags          = I2C_CLIENT_WAKE,
 138};
 139
 140static struct i2c_client *__add_probed_i2c_device(
 141                const char *name,
 142                int bus,
 143                struct i2c_board_info *info,
 144                const unsigned short *alt_addr_list)
 145{
 146        const struct dmi_device *dmi_dev;
 147        const struct dmi_dev_onboard *dev_data;
 148        struct i2c_adapter *adapter;
 149        struct i2c_client *client = NULL;
 150        const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END };
 151
 152        if (bus < 0)
 153                return NULL;
 154        /*
 155         * If a name is specified, look for irq platform information stashed
 156         * in DMI_DEV_TYPE_DEV_ONBOARD by the Chrome OS custom system firmware.
 157         */
 158        if (name) {
 159                dmi_dev = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD, name, NULL);
 160                if (!dmi_dev) {
 161                        pr_err("%s failed to dmi find device %s.\n",
 162                               __func__,
 163                               name);
 164                        return NULL;
 165                }
 166                dev_data = (struct dmi_dev_onboard *)dmi_dev->device_data;
 167                if (!dev_data) {
 168                        pr_err("%s failed to get data from dmi for %s.\n",
 169                               __func__, name);
 170                        return NULL;
 171                }
 172                info->irq = dev_data->instance;
 173        }
 174
 175        adapter = i2c_get_adapter(bus);
 176        if (!adapter) {
 177                pr_err("%s failed to get i2c adapter %d.\n", __func__, bus);
 178                return NULL;
 179        }
 180
 181        /*
 182         * Add the i2c device. If we can't detect it at the primary
 183         * address we scan secondary addresses. In any case the client
 184         * structure gets assigned primary address.
 185         */
 186        client = i2c_new_probed_device(adapter, info, addr_list, NULL);
 187        if (!client && alt_addr_list) {
 188                struct i2c_board_info dummy_info = {
 189                        I2C_BOARD_INFO("dummy", info->addr),
 190                };
 191                struct i2c_client *dummy;
 192
 193                dummy = i2c_new_probed_device(adapter, &dummy_info,
 194                                              alt_addr_list, NULL);
 195                if (dummy) {
 196                        pr_debug("%s %d-%02x is probed at %02x\n",
 197                                  __func__, bus, info->addr, dummy->addr);
 198                        i2c_unregister_device(dummy);
 199                        client = i2c_new_device(adapter, info);
 200                }
 201        }
 202
 203        if (!client)
 204                pr_notice("%s failed to register device %d-%02x\n",
 205                          __func__, bus, info->addr);
 206        else
 207                pr_debug("%s added i2c device %d-%02x\n",
 208                         __func__, bus, info->addr);
 209
 210        i2c_put_adapter(adapter);
 211        return client;
 212}
 213
 214struct i2c_lookup {
 215        const char *name;
 216        int instance;
 217        int n;
 218};
 219
 220static int __find_i2c_adap(struct device *dev, void *data)
 221{
 222        struct i2c_lookup *lookup = data;
 223        static const char *prefix = "i2c-";
 224        struct i2c_adapter *adapter;
 225
 226        if (strncmp(dev_name(dev), prefix, strlen(prefix)) != 0)
 227                return 0;
 228        adapter = to_i2c_adapter(dev);
 229        if (strncmp(adapter->name, lookup->name, strlen(lookup->name)) == 0 &&
 230            lookup->n++ == lookup->instance)
 231                return 1;
 232        return 0;
 233}
 234
 235static int find_i2c_adapter_num(enum i2c_adapter_type type)
 236{
 237        struct device *dev = NULL;
 238        struct i2c_adapter *adapter;
 239        struct i2c_lookup lookup;
 240
 241        memset(&lookup, 0, sizeof(lookup));
 242        lookup.name = i2c_adapter_names[type];
 243        lookup.instance = (type == I2C_ADAPTER_DESIGNWARE_1) ? 1 : 0;
 244
 245        /* find the adapter by name */
 246        dev = bus_find_device(&i2c_bus_type, NULL, &lookup, __find_i2c_adap);
 247        if (!dev) {
 248                /* Adapters may appear later. Deferred probing will retry */
 249                pr_notice("%s: i2c adapter %s not found on system.\n", __func__,
 250                          lookup.name);
 251                return -ENODEV;
 252        }
 253        adapter = to_i2c_adapter(dev);
 254        return adapter->nr;
 255}
 256
 257/*
 258 * Takes a list of addresses in addrs as such :
 259 * { addr1, ... , addrn, I2C_CLIENT_END };
 260 * add_probed_i2c_device will use i2c_new_probed_device
 261 * and probe for devices at all of the addresses listed.
 262 * Returns NULL if no devices found.
 263 * See Documentation/i2c/instantiating-devices for more information.
 264 */
 265static struct i2c_client *add_probed_i2c_device(
 266                const char *name,
 267                enum i2c_adapter_type type,
 268                struct i2c_board_info *info,
 269                const unsigned short *addrs)
 270{
 271        return __add_probed_i2c_device(name,
 272                                       find_i2c_adapter_num(type),
 273                                       info,
 274                                       addrs);
 275}
 276
 277/*
 278 * Probes for a device at a single address, the one provided by
 279 * info->addr.
 280 * Returns NULL if no device found.
 281 */
 282static struct i2c_client *add_i2c_device(const char *name,
 283                                                enum i2c_adapter_type type,
 284                                                struct i2c_board_info *info)
 285{
 286        return __add_probed_i2c_device(name,
 287                                       find_i2c_adapter_num(type),
 288                                       info,
 289                                       NULL);
 290}
 291
 292static int setup_cyapa_tp(enum i2c_adapter_type type)
 293{
 294        if (tp)
 295                return 0;
 296
 297        /* add cyapa touchpad */
 298        tp = add_i2c_device("trackpad", type, &cyapa_device);
 299        return (!tp) ? -EAGAIN : 0;
 300}
 301
 302static int setup_atmel_224s_tp(enum i2c_adapter_type type)
 303{
 304        const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR,
 305                                             I2C_CLIENT_END };
 306        if (tp)
 307                return 0;
 308
 309        /* add atmel mxt touchpad */
 310        tp = add_probed_i2c_device("trackpad", type,
 311                                   &atmel_224s_tp_device, addr_list);
 312        return (!tp) ? -EAGAIN : 0;
 313}
 314
 315static int setup_elantech_tp(enum i2c_adapter_type type)
 316{
 317        if (tp)
 318                return 0;
 319
 320        /* add elantech touchpad */
 321        tp = add_i2c_device("trackpad", type, &elantech_device);
 322        return (!tp) ? -EAGAIN : 0;
 323}
 324
 325static int setup_atmel_1664s_ts(enum i2c_adapter_type type)
 326{
 327        const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR,
 328                                             I2C_CLIENT_END };
 329        if (ts)
 330                return 0;
 331
 332        /* add atmel mxt touch device */
 333        ts = add_probed_i2c_device("touchscreen", type,
 334                                   &atmel_1664s_device, addr_list);
 335        return (!ts) ? -EAGAIN : 0;
 336}
 337
 338static int setup_isl29018_als(enum i2c_adapter_type type)
 339{
 340        if (als)
 341                return 0;
 342
 343        /* add isl29018 light sensor */
 344        als = add_i2c_device("lightsensor", type, &isl_als_device);
 345        return (!als) ? -EAGAIN : 0;
 346}
 347
 348static int setup_tsl2583_als(enum i2c_adapter_type type)
 349{
 350        if (als)
 351                return 0;
 352
 353        /* add tsl2583 light sensor */
 354        als = add_i2c_device(NULL, type, &tsl2583_als_device);
 355        return (!als) ? -EAGAIN : 0;
 356}
 357
 358static int setup_tsl2563_als(enum i2c_adapter_type type)
 359{
 360        if (als)
 361                return 0;
 362
 363        /* add tsl2563 light sensor */
 364        als = add_i2c_device(NULL, type, &tsl2563_als_device);
 365        return (!als) ? -EAGAIN : 0;
 366}
 367
 368static int __init chromeos_laptop_dmi_matched(const struct dmi_system_id *id)
 369{
 370        cros_laptop = (void *)id->driver_data;
 371        pr_debug("DMI Matched %s.\n", id->ident);
 372
 373        /* Indicate to dmi_scan that processing is done. */
 374        return 1;
 375}
 376
 377static int chromeos_laptop_probe(struct platform_device *pdev)
 378{
 379        int i;
 380        int ret = 0;
 381
 382        for (i = 0; i < MAX_I2C_PERIPHERALS; i++) {
 383                struct i2c_peripheral *i2c_dev;
 384
 385                i2c_dev = &cros_laptop->i2c_peripherals[i];
 386
 387                /* No more peripherals. */
 388                if (i2c_dev->add == NULL)
 389                        break;
 390
 391                if (i2c_dev->state == TIMEDOUT || i2c_dev->state == PROBED)
 392                        continue;
 393
 394                /*
 395                 * Check that the i2c adapter is present.
 396                 * -EPROBE_DEFER if missing as the adapter may appear much
 397                 * later.
 398                 */
 399                if (find_i2c_adapter_num(i2c_dev->type) == -ENODEV) {
 400                        ret = -EPROBE_DEFER;
 401                        continue;
 402                }
 403
 404                /* Add the device. */
 405                if (i2c_dev->add(i2c_dev->type) == -EAGAIN) {
 406                        /*
 407                         * Set -EPROBE_DEFER a limited num of times
 408                         * if device is not successfully added.
 409                         */
 410                        if (++i2c_dev->tries < MAX_I2C_DEVICE_DEFERRALS) {
 411                                ret = -EPROBE_DEFER;
 412                        } else {
 413                                /* Ran out of tries. */
 414                                pr_notice("%s: Ran out of tries for device.\n",
 415                                          __func__);
 416                                i2c_dev->state = TIMEDOUT;
 417                        }
 418                } else {
 419                        i2c_dev->state = PROBED;
 420                }
 421        }
 422
 423        return ret;
 424}
 425
 426static struct chromeos_laptop samsung_series_5_550 = {
 427        .i2c_peripherals = {
 428                /* Touchpad. */
 429                { .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS },
 430                /* Light Sensor. */
 431                { .add = setup_isl29018_als, I2C_ADAPTER_SMBUS },
 432        },
 433};
 434
 435static struct chromeos_laptop samsung_series_5 = {
 436        .i2c_peripherals = {
 437                /* Light Sensor. */
 438                { .add = setup_tsl2583_als, I2C_ADAPTER_SMBUS },
 439        },
 440};
 441
 442static struct chromeos_laptop chromebook_pixel = {
 443        .i2c_peripherals = {
 444                /* Touch Screen. */
 445                { .add = setup_atmel_1664s_ts, I2C_ADAPTER_PANEL },
 446                /* Touchpad. */
 447                { .add = setup_atmel_224s_tp, I2C_ADAPTER_VGADDC },
 448                /* Light Sensor. */
 449                { .add = setup_isl29018_als, I2C_ADAPTER_PANEL },
 450        },
 451};
 452
 453static struct chromeos_laptop hp_chromebook_14 = {
 454        .i2c_peripherals = {
 455                /* Touchpad. */
 456                { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
 457        },
 458};
 459
 460static struct chromeos_laptop dell_chromebook_11 = {
 461        .i2c_peripherals = {
 462                /* Touchpad. */
 463                { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
 464                /* Elan Touchpad option. */
 465                { .add = setup_elantech_tp, I2C_ADAPTER_DESIGNWARE_0 },
 466        },
 467};
 468
 469static struct chromeos_laptop toshiba_cb35 = {
 470        .i2c_peripherals = {
 471                /* Touchpad. */
 472                { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
 473        },
 474};
 475
 476static struct chromeos_laptop acer_c7_chromebook = {
 477        .i2c_peripherals = {
 478                /* Touchpad. */
 479                { .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS },
 480        },
 481};
 482
 483static struct chromeos_laptop acer_ac700 = {
 484        .i2c_peripherals = {
 485                /* Light Sensor. */
 486                { .add = setup_tsl2563_als, I2C_ADAPTER_SMBUS },
 487        },
 488};
 489
 490static struct chromeos_laptop acer_c720 = {
 491        .i2c_peripherals = {
 492                /* Touchscreen. */
 493                { .add = setup_atmel_1664s_ts, I2C_ADAPTER_DESIGNWARE_1 },
 494                /* Touchpad. */
 495                { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 },
 496                /* Elan Touchpad option. */
 497                { .add = setup_elantech_tp, I2C_ADAPTER_DESIGNWARE_0 },
 498                /* Light Sensor. */
 499                { .add = setup_isl29018_als, I2C_ADAPTER_DESIGNWARE_1 },
 500        },
 501};
 502
 503static struct chromeos_laptop hp_pavilion_14_chromebook = {
 504        .i2c_peripherals = {
 505                /* Touchpad. */
 506                { .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS },
 507        },
 508};
 509
 510static struct chromeos_laptop cr48 = {
 511        .i2c_peripherals = {
 512                /* Light Sensor. */
 513                { .add = setup_tsl2563_als, I2C_ADAPTER_SMBUS },
 514        },
 515};
 516
 517#define _CBDD(board_) \
 518        .callback = chromeos_laptop_dmi_matched, \
 519        .driver_data = (void *)&board_
 520
 521static struct dmi_system_id chromeos_laptop_dmi_table[] __initdata = {
 522        {
 523                .ident = "Samsung Series 5 550",
 524                .matches = {
 525                        DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"),
 526                        DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"),
 527                },
 528                _CBDD(samsung_series_5_550),
 529        },
 530        {
 531                .ident = "Samsung Series 5",
 532                .matches = {
 533                        DMI_MATCH(DMI_PRODUCT_NAME, "Alex"),
 534                },
 535                _CBDD(samsung_series_5),
 536        },
 537        {
 538                .ident = "Chromebook Pixel",
 539                .matches = {
 540                        DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"),
 541                        DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
 542                },
 543                _CBDD(chromebook_pixel),
 544        },
 545        {
 546                .ident = "Wolf",
 547                .matches = {
 548                        DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
 549                        DMI_MATCH(DMI_PRODUCT_NAME, "Wolf"),
 550                },
 551                _CBDD(dell_chromebook_11),
 552        },
 553        {
 554                .ident = "HP Chromebook 14",
 555                .matches = {
 556                        DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
 557                        DMI_MATCH(DMI_PRODUCT_NAME, "Falco"),
 558                },
 559                _CBDD(hp_chromebook_14),
 560        },
 561        {
 562                .ident = "Toshiba CB35",
 563                .matches = {
 564                        DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"),
 565                        DMI_MATCH(DMI_PRODUCT_NAME, "Leon"),
 566                },
 567                _CBDD(toshiba_cb35),
 568        },
 569        {
 570                .ident = "Acer C7 Chromebook",
 571                .matches = {
 572                        DMI_MATCH(DMI_PRODUCT_NAME, "Parrot"),
 573                },
 574                _CBDD(acer_c7_chromebook),
 575        },
 576        {
 577                .ident = "Acer AC700",
 578                .matches = {
 579                        DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"),
 580                },
 581                _CBDD(acer_ac700),
 582        },
 583        {
 584                .ident = "Acer C720",
 585                .matches = {
 586                        DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"),
 587                },
 588                _CBDD(acer_c720),
 589        },
 590        {
 591                .ident = "HP Pavilion 14 Chromebook",
 592                .matches = {
 593                        DMI_MATCH(DMI_PRODUCT_NAME, "Butterfly"),
 594                },
 595                _CBDD(hp_pavilion_14_chromebook),
 596        },
 597        {
 598                .ident = "Cr-48",
 599                .matches = {
 600                        DMI_MATCH(DMI_PRODUCT_NAME, "Mario"),
 601                },
 602                _CBDD(cr48),
 603        },
 604        { }
 605};
 606MODULE_DEVICE_TABLE(dmi, chromeos_laptop_dmi_table);
 607
 608static struct platform_device *cros_platform_device;
 609
 610static struct platform_driver cros_platform_driver = {
 611        .driver = {
 612                .name = "chromeos_laptop",
 613        },
 614        .probe = chromeos_laptop_probe,
 615};
 616
 617static int __init chromeos_laptop_init(void)
 618{
 619        int ret;
 620
 621        if (!dmi_check_system(chromeos_laptop_dmi_table)) {
 622                pr_debug("%s unsupported system.\n", __func__);
 623                return -ENODEV;
 624        }
 625
 626        ret = platform_driver_register(&cros_platform_driver);
 627        if (ret)
 628                return ret;
 629
 630        cros_platform_device = platform_device_alloc("chromeos_laptop", -1);
 631        if (!cros_platform_device) {
 632                ret = -ENOMEM;
 633                goto fail_platform_device1;
 634        }
 635
 636        ret = platform_device_add(cros_platform_device);
 637        if (ret)
 638                goto fail_platform_device2;
 639
 640        return 0;
 641
 642fail_platform_device2:
 643        platform_device_put(cros_platform_device);
 644fail_platform_device1:
 645        platform_driver_unregister(&cros_platform_driver);
 646        return ret;
 647}
 648
 649static void __exit chromeos_laptop_exit(void)
 650{
 651        if (als)
 652                i2c_unregister_device(als);
 653        if (tp)
 654                i2c_unregister_device(tp);
 655        if (ts)
 656                i2c_unregister_device(ts);
 657
 658        platform_device_unregister(cros_platform_device);
 659        platform_driver_unregister(&cros_platform_driver);
 660}
 661
 662module_init(chromeos_laptop_init);
 663module_exit(chromeos_laptop_exit);
 664
 665MODULE_DESCRIPTION("Chrome OS Laptop driver");
 666MODULE_AUTHOR("Benson Leung <bleung@chromium.org>");
 667MODULE_LICENSE("GPL");
 668