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