diff options
Diffstat (limited to 'drivers/platform/chrome/chromeos_laptop.c')
-rw-r--r-- | drivers/platform/chrome/chromeos_laptop.c | 896 |
1 files changed, 456 insertions, 440 deletions
diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index d8599736a41a..5c47f451e43b 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -1,33 +1,20 @@ -/* - * chromeos_laptop.c - Driver to instantiate Chromebook i2c/smbus devices. - * - * Author : Benson Leung <bleung@chromium.org> - * - * Copyright (C) 2012 Google, Inc. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ +// SPDX-License-Identifier: GPL-2.0+ +// Driver to instantiate Chromebook i2c/smbus devices. +// +// Copyright (C) 2012 Google, Inc. +// Author: Benson Leung <bleung@chromium.org> + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/dmi.h> #include <linux/i2c.h> -#include <linux/platform_data/atmel_mxt_ts.h> #include <linux/input.h> #include <linux/interrupt.h> +#include <linux/ioport.h> #include <linux/module.h> +#include <linux/pci.h> #include <linux/platform_device.h> +#include <linux/property.h> #define ATMEL_TP_I2C_ADDR 0x4b #define ATMEL_TP_I2C_BL_ADDR 0x25 @@ -38,18 +25,11 @@ #define ISL_ALS_I2C_ADDR 0x44 #define TAOS_ALS_I2C_ADDR 0x29 -#define MAX_I2C_DEVICE_DEFERRALS 5 - -static struct i2c_client *als; -static struct i2c_client *tp; -static struct i2c_client *ts; - static const char *i2c_adapter_names[] = { "SMBus I801 adapter", "i915 gmbus vga", "i915 gmbus panel", "Synopsys DesignWare I2C adapter", - "Synopsys DesignWare I2C adapter", }; /* Keep this enum consistent with i2c_adapter_names */ @@ -57,126 +37,41 @@ enum i2c_adapter_type { I2C_ADAPTER_SMBUS = 0, I2C_ADAPTER_VGADDC, I2C_ADAPTER_PANEL, - I2C_ADAPTER_DESIGNWARE_0, - I2C_ADAPTER_DESIGNWARE_1, -}; - -enum i2c_peripheral_state { - UNPROBED = 0, - PROBED, - TIMEDOUT, + I2C_ADAPTER_DESIGNWARE, }; struct i2c_peripheral { - int (*add)(enum i2c_adapter_type type); - enum i2c_adapter_type type; - enum i2c_peripheral_state state; - int tries; -}; - -#define MAX_I2C_PERIPHERALS 4 + struct i2c_board_info board_info; + unsigned short alt_addr; -struct chromeos_laptop { - struct i2c_peripheral i2c_peripherals[MAX_I2C_PERIPHERALS]; -}; - -static struct chromeos_laptop *cros_laptop; - -static struct i2c_board_info cyapa_device = { - I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), - .flags = I2C_CLIENT_WAKE, -}; + const char *dmi_name; + unsigned long irqflags; + struct resource irq_resource; -static struct i2c_board_info elantech_device = { - I2C_BOARD_INFO("elan_i2c", ELAN_TP_I2C_ADDR), - .flags = I2C_CLIENT_WAKE, -}; - -static struct i2c_board_info isl_als_device = { - I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR), -}; - -static struct i2c_board_info tsl2583_als_device = { - I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR), -}; - -static struct i2c_board_info tsl2563_als_device = { - I2C_BOARD_INFO("tsl2563", TAOS_ALS_I2C_ADDR), -}; - -static int mxt_t19_keys[] = { - KEY_RESERVED, - KEY_RESERVED, - KEY_RESERVED, - KEY_RESERVED, - KEY_RESERVED, - BTN_LEFT -}; - -static struct mxt_platform_data atmel_224s_tp_platform_data = { - .irqflags = IRQF_TRIGGER_FALLING, - .t19_num_keys = ARRAY_SIZE(mxt_t19_keys), - .t19_keymap = mxt_t19_keys, - .suspend_mode = MXT_SUSPEND_T9_CTRL, -}; + enum i2c_adapter_type type; + u32 pci_devid; -static struct i2c_board_info atmel_224s_tp_device = { - I2C_BOARD_INFO("atmel_mxt_tp", ATMEL_TP_I2C_ADDR), - .platform_data = &atmel_224s_tp_platform_data, - .flags = I2C_CLIENT_WAKE, + struct i2c_client *client; }; -static struct mxt_platform_data atmel_1664s_platform_data = { - .irqflags = IRQF_TRIGGER_FALLING, - .suspend_mode = MXT_SUSPEND_T9_CTRL, +struct chromeos_laptop { + /* + * Note that we can't mark this pointer as const because + * i2c_new_probed_device() changes passed in I2C board info, so. + */ + struct i2c_peripheral *i2c_peripherals; + unsigned int num_i2c_peripherals; }; -static struct i2c_board_info atmel_1664s_device = { - I2C_BOARD_INFO("atmel_mxt_ts", ATMEL_TS_I2C_ADDR), - .platform_data = &atmel_1664s_platform_data, - .flags = I2C_CLIENT_WAKE, -}; +static const struct chromeos_laptop *cros_laptop; -static struct i2c_client *__add_probed_i2c_device( - const char *name, - int bus, - struct i2c_board_info *info, - const unsigned short *alt_addr_list) +static struct i2c_client * +chromes_laptop_instantiate_i2c_device(struct i2c_adapter *adapter, + struct i2c_board_info *info, + unsigned short alt_addr) { - const struct dmi_device *dmi_dev; - const struct dmi_dev_onboard *dev_data; - struct i2c_adapter *adapter; - struct i2c_client *client = NULL; const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END }; - - if (bus < 0) - return NULL; - /* - * If a name is specified, look for irq platform information stashed - * in DMI_DEV_TYPE_DEV_ONBOARD by the Chrome OS custom system firmware. - */ - if (name) { - dmi_dev = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD, name, NULL); - if (!dmi_dev) { - pr_err("%s failed to dmi find device %s.\n", - __func__, - name); - return NULL; - } - dev_data = (struct dmi_dev_onboard *)dmi_dev->device_data; - if (!dev_data) { - pr_err("%s failed to get data from dmi for %s.\n", - __func__, name); - return NULL; - } - info->irq = dev_data->instance; - } - - adapter = i2c_get_adapter(bus); - if (!adapter) { - pr_err("%s failed to get i2c adapter %d.\n", __func__, bus); - return NULL; - } + struct i2c_client *client; /* * Add the i2c device. If we can't detect it at the primary @@ -184,339 +79,345 @@ static struct i2c_client *__add_probed_i2c_device( * structure gets assigned primary address. */ client = i2c_new_probed_device(adapter, info, addr_list, NULL); - if (!client && alt_addr_list) { + if (!client && alt_addr) { struct i2c_board_info dummy_info = { I2C_BOARD_INFO("dummy", info->addr), }; + const unsigned short alt_addr_list[] = { + alt_addr, I2C_CLIENT_END + }; struct i2c_client *dummy; dummy = i2c_new_probed_device(adapter, &dummy_info, alt_addr_list, NULL); if (dummy) { - pr_debug("%s %d-%02x is probed at %02x\n", - __func__, bus, info->addr, dummy->addr); + pr_debug("%d-%02x is probed at %02x\n", + adapter->nr, info->addr, dummy->addr); i2c_unregister_device(dummy); client = i2c_new_device(adapter, info); } } if (!client) - pr_notice("%s failed to register device %d-%02x\n", - __func__, bus, info->addr); + pr_debug("failed to register device %d-%02x\n", + adapter->nr, info->addr); else - pr_debug("%s added i2c device %d-%02x\n", - __func__, bus, info->addr); + pr_debug("added i2c device %d-%02x\n", + adapter->nr, info->addr); - i2c_put_adapter(adapter); return client; } -struct i2c_lookup { - const char *name; - int instance; - int n; -}; - -static int __find_i2c_adap(struct device *dev, void *data) -{ - struct i2c_lookup *lookup = data; - static const char *prefix = "i2c-"; - struct i2c_adapter *adapter; - - if (strncmp(dev_name(dev), prefix, strlen(prefix)) != 0) - return 0; - adapter = to_i2c_adapter(dev); - if (strncmp(adapter->name, lookup->name, strlen(lookup->name)) == 0 && - lookup->n++ == lookup->instance) - return 1; - return 0; -} - -static int find_i2c_adapter_num(enum i2c_adapter_type type) -{ - struct device *dev = NULL; - struct i2c_adapter *adapter; - struct i2c_lookup lookup; - - memset(&lookup, 0, sizeof(lookup)); - lookup.name = i2c_adapter_names[type]; - lookup.instance = (type == I2C_ADAPTER_DESIGNWARE_1) ? 1 : 0; - - /* find the adapter by name */ - dev = bus_find_device(&i2c_bus_type, NULL, &lookup, __find_i2c_adap); - if (!dev) { - /* Adapters may appear later. Deferred probing will retry */ - pr_notice("%s: i2c adapter %s not found on system.\n", __func__, - lookup.name); - return -ENODEV; - } - adapter = to_i2c_adapter(dev); - return adapter->nr; -} - -/* - * Takes a list of addresses in addrs as such : - * { addr1, ... , addrn, I2C_CLIENT_END }; - * add_probed_i2c_device will use i2c_new_probed_device - * and probe for devices at all of the addresses listed. - * Returns NULL if no devices found. - * See Documentation/i2c/instantiating-devices for more information. - */ -static struct i2c_client *add_probed_i2c_device( - const char *name, - enum i2c_adapter_type type, - struct i2c_board_info *info, - const unsigned short *addrs) +static bool chromeos_laptop_match_adapter_devid(struct device *dev, u32 devid) { - return __add_probed_i2c_device(name, - find_i2c_adapter_num(type), - info, - addrs); -} + struct pci_dev *pdev; -/* - * Probes for a device at a single address, the one provided by - * info->addr. - * Returns NULL if no device found. - */ -static struct i2c_client *add_i2c_device(const char *name, - enum i2c_adapter_type type, - struct i2c_board_info *info) -{ - return __add_probed_i2c_device(name, - find_i2c_adapter_num(type), - info, - NULL); -} - -static int setup_cyapa_tp(enum i2c_adapter_type type) -{ - if (tp) - return 0; + if (!dev_is_pci(dev)) + return false; - /* add cyapa touchpad */ - tp = add_i2c_device("trackpad", type, &cyapa_device); - return (!tp) ? -EAGAIN : 0; + pdev = to_pci_dev(dev); + return devid == PCI_DEVID(pdev->bus->number, pdev->devfn); } -static int setup_atmel_224s_tp(enum i2c_adapter_type type) +static void chromeos_laptop_check_adapter(struct i2c_adapter *adapter) { - const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR, - I2C_CLIENT_END }; - if (tp) - return 0; - - /* add atmel mxt touchpad */ - tp = add_probed_i2c_device("trackpad", type, - &atmel_224s_tp_device, addr_list); - return (!tp) ? -EAGAIN : 0; -} + struct i2c_peripheral *i2c_dev; + int i; -static int setup_elantech_tp(enum i2c_adapter_type type) -{ - if (tp) - return 0; + for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { + i2c_dev = &cros_laptop->i2c_peripherals[i]; - /* add elantech touchpad */ - tp = add_i2c_device("trackpad", type, &elantech_device); - return (!tp) ? -EAGAIN : 0; -} + /* Skip devices already created */ + if (i2c_dev->client) + continue; -static int setup_atmel_1664s_ts(enum i2c_adapter_type type) -{ - const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR, - I2C_CLIENT_END }; - if (ts) - return 0; - - /* add atmel mxt touch device */ - ts = add_probed_i2c_device("touchscreen", type, - &atmel_1664s_device, addr_list); - return (!ts) ? -EAGAIN : 0; -} + if (strncmp(adapter->name, i2c_adapter_names[i2c_dev->type], + strlen(i2c_adapter_names[i2c_dev->type]))) + continue; -static int setup_isl29018_als(enum i2c_adapter_type type) -{ - if (als) - return 0; + if (i2c_dev->pci_devid && + !chromeos_laptop_match_adapter_devid(adapter->dev.parent, + i2c_dev->pci_devid)) { + continue; + } - /* add isl29018 light sensor */ - als = add_i2c_device("lightsensor", type, &isl_als_device); - return (!als) ? -EAGAIN : 0; + i2c_dev->client = + chromes_laptop_instantiate_i2c_device(adapter, + &i2c_dev->board_info, + i2c_dev->alt_addr); + } } -static int setup_tsl2583_als(enum i2c_adapter_type type) +static void chromeos_laptop_detach_i2c_client(struct i2c_client *client) { - if (als) - return 0; - - /* add tsl2583 light sensor */ - als = add_i2c_device(NULL, type, &tsl2583_als_device); - return (!als) ? -EAGAIN : 0; -} + struct i2c_peripheral *i2c_dev; + int i; -static int setup_tsl2563_als(enum i2c_adapter_type type) -{ - if (als) - return 0; + for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { + i2c_dev = &cros_laptop->i2c_peripherals[i]; - /* add tsl2563 light sensor */ - als = add_i2c_device(NULL, type, &tsl2563_als_device); - return (!als) ? -EAGAIN : 0; + if (i2c_dev->client == client) + i2c_dev->client = NULL; + } } -static int __init chromeos_laptop_dmi_matched(const struct dmi_system_id *id) +static int chromeos_laptop_i2c_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) { - cros_laptop = (void *)id->driver_data; - pr_debug("DMI Matched %s.\n", id->ident); + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + if (dev->type == &i2c_adapter_type) + chromeos_laptop_check_adapter(to_i2c_adapter(dev)); + break; + + case BUS_NOTIFY_REMOVED_DEVICE: + if (dev->type == &i2c_client_type) + chromeos_laptop_detach_i2c_client(to_i2c_client(dev)); + break; + } - /* Indicate to dmi_scan that processing is done. */ - return 1; + return 0; } -static int chromeos_laptop_probe(struct platform_device *pdev) -{ - int i; - int ret = 0; - - for (i = 0; i < MAX_I2C_PERIPHERALS; i++) { - struct i2c_peripheral *i2c_dev; - - i2c_dev = &cros_laptop->i2c_peripherals[i]; - - /* No more peripherals. */ - if (i2c_dev->add == NULL) - break; - - if (i2c_dev->state == TIMEDOUT || i2c_dev->state == PROBED) - continue; - - /* - * Check that the i2c adapter is present. - * -EPROBE_DEFER if missing as the adapter may appear much - * later. - */ - if (find_i2c_adapter_num(i2c_dev->type) == -ENODEV) { - ret = -EPROBE_DEFER; - continue; - } - - /* Add the device. */ - if (i2c_dev->add(i2c_dev->type) == -EAGAIN) { - /* - * Set -EPROBE_DEFER a limited num of times - * if device is not successfully added. - */ - if (++i2c_dev->tries < MAX_I2C_DEVICE_DEFERRALS) { - ret = -EPROBE_DEFER; - } else { - /* Ran out of tries. */ - pr_notice("%s: Ran out of tries for device.\n", - __func__); - i2c_dev->state = TIMEDOUT; - } - } else { - i2c_dev->state = PROBED; - } - } +static struct notifier_block chromeos_laptop_i2c_notifier = { + .notifier_call = chromeos_laptop_i2c_notifier_call, +}; - return ret; +#define DECLARE_CROS_LAPTOP(_name) \ +static const struct chromeos_laptop _name __initconst = { \ + .i2c_peripherals = _name##_peripherals, \ + .num_i2c_peripherals = ARRAY_SIZE(_name##_peripherals), \ } -static struct chromeos_laptop samsung_series_5_550 = { - .i2c_peripherals = { - /* Touchpad. */ - { .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS }, - /* Light Sensor. */ - { .add = setup_isl29018_als, I2C_ADAPTER_SMBUS }, +static struct i2c_peripheral samsung_series_5_550_peripherals[] __initdata = { + /* Touchpad. */ + { + .board_info = { + I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_SMBUS, + }, + /* Light Sensor. */ + { + .board_info = { + I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR), + }, + .dmi_name = "lightsensor", + .type = I2C_ADAPTER_SMBUS, }, }; +DECLARE_CROS_LAPTOP(samsung_series_5_550); -static struct chromeos_laptop samsung_series_5 = { - .i2c_peripherals = { - /* Light Sensor. */ - { .add = setup_tsl2583_als, I2C_ADAPTER_SMBUS }, +static struct i2c_peripheral samsung_series_5_peripherals[] __initdata = { + /* Light Sensor. */ + { + .board_info = { + I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR), + }, + .type = I2C_ADAPTER_SMBUS, }, }; +DECLARE_CROS_LAPTOP(samsung_series_5); -static struct chromeos_laptop chromebook_pixel = { - .i2c_peripherals = { - /* Touch Screen. */ - { .add = setup_atmel_1664s_ts, I2C_ADAPTER_PANEL }, - /* Touchpad. */ - { .add = setup_atmel_224s_tp, I2C_ADAPTER_VGADDC }, - /* Light Sensor. */ - { .add = setup_isl29018_als, I2C_ADAPTER_PANEL }, - }, +static const int chromebook_pixel_tp_keys[] __initconst = { + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + BTN_LEFT }; -static struct chromeos_laptop hp_chromebook_14 = { - .i2c_peripherals = { - /* Touchpad. */ - { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 }, +static const struct property_entry +chromebook_pixel_trackpad_props[] __initconst = { + PROPERTY_ENTRY_U32_ARRAY("linux,gpio-keymap", chromebook_pixel_tp_keys), + { } +}; + +static struct i2c_peripheral chromebook_pixel_peripherals[] __initdata = { + /* Touch Screen. */ + { + .board_info = { + I2C_BOARD_INFO("atmel_mxt_ts", + ATMEL_TS_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "touchscreen", + .irqflags = IRQF_TRIGGER_FALLING, + .type = I2C_ADAPTER_PANEL, + .alt_addr = ATMEL_TS_I2C_BL_ADDR, + }, + /* Touchpad. */ + { + .board_info = { + I2C_BOARD_INFO("atmel_mxt_tp", + ATMEL_TP_I2C_ADDR), + .properties = + chromebook_pixel_trackpad_props, + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .irqflags = IRQF_TRIGGER_FALLING, + .type = I2C_ADAPTER_VGADDC, + .alt_addr = ATMEL_TP_I2C_BL_ADDR, + }, + /* Light Sensor. */ + { + .board_info = { + I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR), + }, + .dmi_name = "lightsensor", + .type = I2C_ADAPTER_PANEL, }, }; +DECLARE_CROS_LAPTOP(chromebook_pixel); -static struct chromeos_laptop dell_chromebook_11 = { - .i2c_peripherals = { - /* Touchpad. */ - { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 }, - /* Elan Touchpad option. */ - { .add = setup_elantech_tp, I2C_ADAPTER_DESIGNWARE_0 }, +static struct i2c_peripheral hp_chromebook_14_peripherals[] __initdata = { + /* Touchpad. */ + { + .board_info = { + I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_DESIGNWARE, }, }; +DECLARE_CROS_LAPTOP(hp_chromebook_14); -static struct chromeos_laptop toshiba_cb35 = { - .i2c_peripherals = { - /* Touchpad. */ - { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 }, +static struct i2c_peripheral dell_chromebook_11_peripherals[] __initdata = { + /* Touchpad. */ + { + .board_info = { + I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_DESIGNWARE, + }, + /* Elan Touchpad option. */ + { + .board_info = { + I2C_BOARD_INFO("elan_i2c", ELAN_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_DESIGNWARE, }, }; +DECLARE_CROS_LAPTOP(dell_chromebook_11); -static struct chromeos_laptop acer_c7_chromebook = { - .i2c_peripherals = { - /* Touchpad. */ - { .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS }, +static struct i2c_peripheral toshiba_cb35_peripherals[] __initdata = { + /* Touchpad. */ + { + .board_info = { + I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_DESIGNWARE, }, }; +DECLARE_CROS_LAPTOP(toshiba_cb35); -static struct chromeos_laptop acer_ac700 = { - .i2c_peripherals = { - /* Light Sensor. */ - { .add = setup_tsl2563_als, I2C_ADAPTER_SMBUS }, +static struct i2c_peripheral acer_c7_chromebook_peripherals[] __initdata = { + /* Touchpad. */ + { + .board_info = { + I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_SMBUS, }, }; +DECLARE_CROS_LAPTOP(acer_c7_chromebook); -static struct chromeos_laptop acer_c720 = { - .i2c_peripherals = { - /* Touchscreen. */ - { .add = setup_atmel_1664s_ts, I2C_ADAPTER_DESIGNWARE_1 }, - /* Touchpad. */ - { .add = setup_cyapa_tp, I2C_ADAPTER_DESIGNWARE_0 }, - /* Elan Touchpad option. */ - { .add = setup_elantech_tp, I2C_ADAPTER_DESIGNWARE_0 }, - /* Light Sensor. */ - { .add = setup_isl29018_als, I2C_ADAPTER_DESIGNWARE_1 }, +static struct i2c_peripheral acer_ac700_peripherals[] __initdata = { + /* Light Sensor. */ + { + .board_info = { + I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR), + }, + .type = I2C_ADAPTER_SMBUS, }, }; +DECLARE_CROS_LAPTOP(acer_ac700); -static struct chromeos_laptop hp_pavilion_14_chromebook = { - .i2c_peripherals = { - /* Touchpad. */ - { .add = setup_cyapa_tp, I2C_ADAPTER_SMBUS }, +static struct i2c_peripheral acer_c720_peripherals[] __initdata = { + /* Touchscreen. */ + { + .board_info = { + I2C_BOARD_INFO("atmel_mxt_ts", + ATMEL_TS_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "touchscreen", + .irqflags = IRQF_TRIGGER_FALLING, + .type = I2C_ADAPTER_DESIGNWARE, + .pci_devid = PCI_DEVID(0, PCI_DEVFN(0x15, 0x2)), + .alt_addr = ATMEL_TS_I2C_BL_ADDR, + }, + /* Touchpad. */ + { + .board_info = { + I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_DESIGNWARE, + .pci_devid = PCI_DEVID(0, PCI_DEVFN(0x15, 0x1)), + }, + /* Elan Touchpad option. */ + { + .board_info = { + I2C_BOARD_INFO("elan_i2c", ELAN_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_DESIGNWARE, + .pci_devid = PCI_DEVID(0, PCI_DEVFN(0x15, 0x1)), + }, + /* Light Sensor. */ + { + .board_info = { + I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR), + }, + .dmi_name = "lightsensor", + .type = I2C_ADAPTER_DESIGNWARE, + .pci_devid = PCI_DEVID(0, PCI_DEVFN(0x15, 0x2)), }, }; +DECLARE_CROS_LAPTOP(acer_c720); -static struct chromeos_laptop cr48 = { - .i2c_peripherals = { - /* Light Sensor. */ - { .add = setup_tsl2563_als, I2C_ADAPTER_SMBUS }, +static struct i2c_peripheral +hp_pavilion_14_chromebook_peripherals[] __initdata = { + /* Touchpad. */ + { + .board_info = { + I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), + .flags = I2C_CLIENT_WAKE, + }, + .dmi_name = "trackpad", + .type = I2C_ADAPTER_SMBUS, }, }; +DECLARE_CROS_LAPTOP(hp_pavilion_14_chromebook); -#define _CBDD(board_) \ - .callback = chromeos_laptop_dmi_matched, \ - .driver_data = (void *)&board_ +static struct i2c_peripheral cr48_peripherals[] __initdata = { + /* Light Sensor. */ + { + .board_info = { + I2C_BOARD_INFO("tsl2563", TAOS_ALS_I2C_ADDR), + }, + .type = I2C_ADAPTER_SMBUS, + }, +}; +DECLARE_CROS_LAPTOP(cr48); static const struct dmi_system_id chromeos_laptop_dmi_table[] __initconst = { { @@ -525,14 +426,14 @@ static const struct dmi_system_id chromeos_laptop_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"), DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"), }, - _CBDD(samsung_series_5_550), + .driver_data = (void *)&samsung_series_5_550, }, { .ident = "Samsung Series 5", .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Alex"), }, - _CBDD(samsung_series_5), + .driver_data = (void *)&samsung_series_5, }, { .ident = "Chromebook Pixel", @@ -540,7 +441,7 @@ static const struct dmi_system_id chromeos_laptop_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), DMI_MATCH(DMI_PRODUCT_NAME, "Link"), }, - _CBDD(chromebook_pixel), + .driver_data = (void *)&chromebook_pixel, }, { .ident = "Wolf", @@ -548,7 +449,7 @@ static const struct dmi_system_id chromeos_laptop_dmi_table[] __initconst = { DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), DMI_MATCH(DMI_PRODUCT_NAME, "Wolf"), }, - _CBDD(dell_chromebook_11), + .driver_data = (void *)&dell_chromebook_11, }, { .ident = "HP Chromebook 14", @@ -556,7 +457,7 @@ static const struct dmi_system_id chromeos_laptop_dmi_table[] __initconst = { DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), DMI_MATCH(DMI_PRODUCT_NAME, "Falco"), }, - _CBDD(hp_chromebook_14), + .driver_data = (void *)&hp_chromebook_14, }, { .ident = "Toshiba CB35", @@ -564,99 +465,214 @@ static const struct dmi_system_id chromeos_laptop_dmi_table[] __initconst = { DMI_MATCH(DMI_BIOS_VENDOR, "coreboot"), DMI_MATCH(DMI_PRODUCT_NAME, "Leon"), }, - _CBDD(toshiba_cb35), + .driver_data = (void *)&toshiba_cb35, }, { .ident = "Acer C7 Chromebook", .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Parrot"), }, - _CBDD(acer_c7_chromebook), + .driver_data = (void *)&acer_c7_chromebook, }, { .ident = "Acer AC700", .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"), }, - _CBDD(acer_ac700), + .driver_data = (void *)&acer_ac700, }, { .ident = "Acer C720", .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Peppy"), }, - _CBDD(acer_c720), + .driver_data = (void *)&acer_c720, }, { .ident = "HP Pavilion 14 Chromebook", .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Butterfly"), }, - _CBDD(hp_pavilion_14_chromebook), + .driver_data = (void *)&hp_pavilion_14_chromebook, }, { .ident = "Cr-48", .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Mario"), }, - _CBDD(cr48), + .driver_data = (void *)&cr48, }, { } }; MODULE_DEVICE_TABLE(dmi, chromeos_laptop_dmi_table); -static struct platform_device *cros_platform_device; +static int __init chromeos_laptop_scan_adapter(struct device *dev, void *data) +{ + struct i2c_adapter *adapter; -static struct platform_driver cros_platform_driver = { - .driver = { - .name = "chromeos_laptop", - }, - .probe = chromeos_laptop_probe, -}; + adapter = i2c_verify_adapter(dev); + if (adapter) + chromeos_laptop_check_adapter(adapter); + + return 0; +} + +static int __init chromeos_laptop_get_irq_from_dmi(const char *dmi_name) +{ + const struct dmi_device *dmi_dev; + const struct dmi_dev_onboard *dev_data; + + dmi_dev = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD, dmi_name, NULL); + if (!dmi_dev) { + pr_err("failed to find DMI device '%s'\n", dmi_name); + return -ENOENT; + } + + dev_data = dmi_dev->device_data; + if (!dev_data) { + pr_err("failed to get data from DMI for '%s'\n", dmi_name); + return -EINVAL; + } + + return dev_data->instance; +} + +static int __init chromeos_laptop_setup_irq(struct i2c_peripheral *i2c_dev) +{ + int irq; + + if (i2c_dev->dmi_name) { + irq = chromeos_laptop_get_irq_from_dmi(i2c_dev->dmi_name); + if (irq < 0) + return irq; + + i2c_dev->irq_resource = (struct resource) + DEFINE_RES_NAMED(irq, 1, NULL, + IORESOURCE_IRQ | i2c_dev->irqflags); + i2c_dev->board_info.resources = &i2c_dev->irq_resource; + i2c_dev->board_info.num_resources = 1; + } + + return 0; +} + +static struct chromeos_laptop * __init +chromeos_laptop_prepare(const struct chromeos_laptop *src) +{ + struct chromeos_laptop *cros_laptop; + struct i2c_peripheral *i2c_dev; + struct i2c_board_info *info; + int error; + int i; + + cros_laptop = kzalloc(sizeof(*cros_laptop), GFP_KERNEL); + if (!cros_laptop) + return ERR_PTR(-ENOMEM); + + cros_laptop->i2c_peripherals = kmemdup(src->i2c_peripherals, + src->num_i2c_peripherals * + sizeof(*src->i2c_peripherals), + GFP_KERNEL); + if (!cros_laptop->i2c_peripherals) { + error = -ENOMEM; + goto err_free_cros_laptop; + } + + cros_laptop->num_i2c_peripherals = src->num_i2c_peripherals; + + for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { + i2c_dev = &cros_laptop->i2c_peripherals[i]; + info = &i2c_dev->board_info; + + error = chromeos_laptop_setup_irq(i2c_dev); + if (error) + goto err_destroy_cros_peripherals; + + /* We need to deep-copy properties */ + if (info->properties) { + info->properties = + property_entries_dup(info->properties); + if (IS_ERR(info->properties)) { + error = PTR_ERR(info->properties); + goto err_destroy_cros_peripherals; + } + } + } + + return cros_laptop; + +err_destroy_cros_peripherals: + while (--i >= 0) { + i2c_dev = &cros_laptop->i2c_peripherals[i]; + info = &i2c_dev->board_info; + if (info->properties) + property_entries_free(info->properties); + } + kfree(cros_laptop->i2c_peripherals); +err_free_cros_laptop: + kfree(cros_laptop); + return ERR_PTR(error); +} + +static void chromeos_laptop_destroy(const struct chromeos_laptop *cros_laptop) +{ + struct i2c_peripheral *i2c_dev; + struct i2c_board_info *info; + int i; + + for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { + i2c_dev = &cros_laptop->i2c_peripherals[i]; + info = &i2c_dev->board_info; + + if (i2c_dev->client) + i2c_unregister_device(i2c_dev->client); + + if (info->properties) + property_entries_free(info->properties); + } + + kfree(cros_laptop->i2c_peripherals); + kfree(cros_laptop); +} static int __init chromeos_laptop_init(void) { - int ret; + const struct dmi_system_id *dmi_id; + int error; - if (!dmi_check_system(chromeos_laptop_dmi_table)) { - pr_debug("%s unsupported system.\n", __func__); + dmi_id = dmi_first_match(chromeos_laptop_dmi_table); + if (!dmi_id) { + pr_debug("unsupported system\n"); return -ENODEV; } - ret = platform_driver_register(&cros_platform_driver); - if (ret) - return ret; + pr_debug("DMI Matched %s\n", dmi_id->ident); + + cros_laptop = chromeos_laptop_prepare((void *)dmi_id->driver_data); + if (IS_ERR(cros_laptop)) + return PTR_ERR(cros_laptop); - cros_platform_device = platform_device_alloc("chromeos_laptop", -1); - if (!cros_platform_device) { - ret = -ENOMEM; - goto fail_platform_device1; + error = bus_register_notifier(&i2c_bus_type, + &chromeos_laptop_i2c_notifier); + if (error) { + pr_err("failed to register i2c bus notifier: %d\n", error); + chromeos_laptop_destroy(cros_laptop); + return error; } - ret = platform_device_add(cros_platform_device); - if (ret) - goto fail_platform_device2; + /* + * Scan adapters that have been registered before we installed + * the notifier to make sure we do not miss any devices. + */ + i2c_for_each_dev(NULL, chromeos_laptop_scan_adapter); return 0; - -fail_platform_device2: - platform_device_put(cros_platform_device); -fail_platform_device1: - platform_driver_unregister(&cros_platform_driver); - return ret; } static void __exit chromeos_laptop_exit(void) { - if (als) - i2c_unregister_device(als); - if (tp) - i2c_unregister_device(tp); - if (ts) - i2c_unregister_device(ts); - - platform_device_unregister(cros_platform_device); - platform_driver_unregister(&cros_platform_driver); + bus_unregister_notifier(&i2c_bus_type, &chromeos_laptop_i2c_notifier); + chromeos_laptop_destroy(cros_laptop); } module_init(chromeos_laptop_init); |