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 31 #define ATMEL_TP_I2C_ADDR 0x4b 32 #define ATMEL_TP_I2C_BL_ADDR 0x25 33 #define ATMEL_TS_I2C_ADDR 0x4a 34 #define ATMEL_TS_I2C_BL_ADDR 0x26 35 #define CYAPA_TP_I2C_ADDR 0x67 36 #define ISL_ALS_I2C_ADDR 0x44 37 #define TAOS_ALS_I2C_ADDR 0x29 38 39 static struct i2c_client *als; 40 static struct i2c_client *tp; 41 static struct i2c_client *ts; 42 43 const char *i2c_adapter_names[] = { 44 "SMBus I801 adapter", 45 "i915 gmbus vga", 46 "i915 gmbus panel", 47 }; 48 49 /* Keep this enum consistent with i2c_adapter_names */ 50 enum i2c_adapter_type { 51 I2C_ADAPTER_SMBUS = 0, 52 I2C_ADAPTER_VGADDC, 53 I2C_ADAPTER_PANEL, 54 }; 55 56 static struct i2c_board_info __initdata cyapa_device = { 57 I2C_BOARD_INFO("cyapa", CYAPA_TP_I2C_ADDR), 58 .flags = I2C_CLIENT_WAKE, 59 }; 60 61 static struct i2c_board_info __initdata isl_als_device = { 62 I2C_BOARD_INFO("isl29018", ISL_ALS_I2C_ADDR), 63 }; 64 65 static struct i2c_board_info __initdata tsl2583_als_device = { 66 I2C_BOARD_INFO("tsl2583", TAOS_ALS_I2C_ADDR), 67 }; 68 69 static struct i2c_board_info __initdata tsl2563_als_device = { 70 I2C_BOARD_INFO("tsl2563", TAOS_ALS_I2C_ADDR), 71 }; 72 73 static struct mxt_platform_data atmel_224s_tp_platform_data = { 74 .x_line = 18, 75 .y_line = 12, 76 .x_size = 102*20, 77 .y_size = 68*20, 78 .blen = 0x80, /* Gain setting is in upper 4 bits */ 79 .threshold = 0x32, 80 .voltage = 0, /* 3.3V */ 81 .orient = MXT_VERTICAL_FLIP, 82 .irqflags = IRQF_TRIGGER_FALLING, 83 .is_tp = true, 84 .key_map = { KEY_RESERVED, 85 KEY_RESERVED, 86 KEY_RESERVED, 87 BTN_LEFT }, 88 .config = NULL, 89 .config_length = 0, 90 }; 91 92 static struct i2c_board_info __initdata atmel_224s_tp_device = { 93 I2C_BOARD_INFO("atmel_mxt_tp", ATMEL_TP_I2C_ADDR), 94 .platform_data = &atmel_224s_tp_platform_data, 95 .flags = I2C_CLIENT_WAKE, 96 }; 97 98 static struct mxt_platform_data atmel_1664s_platform_data = { 99 .x_line = 32, 100 .y_line = 50, 101 .x_size = 1700, 102 .y_size = 2560, 103 .blen = 0x89, /* Gain setting is in upper 4 bits */ 104 .threshold = 0x28, 105 .voltage = 0, /* 3.3V */ 106 .orient = MXT_ROTATED_90_COUNTER, 107 .irqflags = IRQF_TRIGGER_FALLING, 108 .is_tp = false, 109 .config = NULL, 110 .config_length = 0, 111 }; 112 113 static struct i2c_board_info __initdata atmel_1664s_device = { 114 I2C_BOARD_INFO("atmel_mxt_ts", ATMEL_TS_I2C_ADDR), 115 .platform_data = &atmel_1664s_platform_data, 116 .flags = I2C_CLIENT_WAKE, 117 }; 118 119 static struct i2c_client __init *__add_probed_i2c_device( 120 const char *name, 121 int bus, 122 struct i2c_board_info *info, 123 const unsigned short *addrs) 124 { 125 const struct dmi_device *dmi_dev; 126 const struct dmi_dev_onboard *dev_data; 127 struct i2c_adapter *adapter; 128 struct i2c_client *client; 129 130 if (bus < 0) 131 return NULL; 132 /* 133 * If a name is specified, look for irq platform information stashed 134 * in DMI_DEV_TYPE_DEV_ONBOARD by the Chrome OS custom system firmware. 135 */ 136 if (name) { 137 dmi_dev = dmi_find_device(DMI_DEV_TYPE_DEV_ONBOARD, name, NULL); 138 if (!dmi_dev) { 139 pr_err("%s failed to dmi find device %s.\n", 140 __func__, 141 name); 142 return NULL; 143 } 144 dev_data = (struct dmi_dev_onboard *)dmi_dev->device_data; 145 if (!dev_data) { 146 pr_err("%s failed to get data from dmi for %s.\n", 147 __func__, name); 148 return NULL; 149 } 150 info->irq = dev_data->instance; 151 } 152 153 adapter = i2c_get_adapter(bus); 154 if (!adapter) { 155 pr_err("%s failed to get i2c adapter %d.\n", __func__, bus); 156 return NULL; 157 } 158 159 /* add the i2c device */ 160 client = i2c_new_probed_device(adapter, info, addrs, NULL); 161 if (!client) 162 pr_err("%s failed to register device %d-%02x\n", 163 __func__, bus, info->addr); 164 else 165 pr_debug("%s added i2c device %d-%02x\n", 166 __func__, bus, info->addr); 167 168 i2c_put_adapter(adapter); 169 return client; 170 } 171 172 static int __init __find_i2c_adap(struct device *dev, void *data) 173 { 174 const char *name = data; 175 static const char *prefix = "i2c-"; 176 struct i2c_adapter *adapter; 177 if (strncmp(dev_name(dev), prefix, strlen(prefix)) != 0) 178 return 0; 179 adapter = to_i2c_adapter(dev); 180 return (strncmp(adapter->name, name, strlen(name)) == 0); 181 } 182 183 static int __init find_i2c_adapter_num(enum i2c_adapter_type type) 184 { 185 struct device *dev = NULL; 186 struct i2c_adapter *adapter; 187 const char *name = i2c_adapter_names[type]; 188 /* find the adapter by name */ 189 dev = bus_find_device(&i2c_bus_type, NULL, (void *)name, 190 __find_i2c_adap); 191 if (!dev) { 192 pr_err("%s: i2c adapter %s not found on system.\n", __func__, 193 name); 194 return -ENODEV; 195 } 196 adapter = to_i2c_adapter(dev); 197 return adapter->nr; 198 } 199 200 /* 201 * Takes a list of addresses in addrs as such : 202 * { addr1, ... , addrn, I2C_CLIENT_END }; 203 * add_probed_i2c_device will use i2c_new_probed_device 204 * and probe for devices at all of the addresses listed. 205 * Returns NULL if no devices found. 206 * See Documentation/i2c/instantiating-devices for more information. 207 */ 208 static __init struct i2c_client *add_probed_i2c_device( 209 const char *name, 210 enum i2c_adapter_type type, 211 struct i2c_board_info *info, 212 const unsigned short *addrs) 213 { 214 return __add_probed_i2c_device(name, 215 find_i2c_adapter_num(type), 216 info, 217 addrs); 218 } 219 220 /* 221 * Probes for a device at a single address, the one provided by 222 * info->addr. 223 * Returns NULL if no device found. 224 */ 225 static __init struct i2c_client *add_i2c_device(const char *name, 226 enum i2c_adapter_type type, 227 struct i2c_board_info *info) 228 { 229 const unsigned short addr_list[] = { info->addr, I2C_CLIENT_END }; 230 return __add_probed_i2c_device(name, 231 find_i2c_adapter_num(type), 232 info, 233 addr_list); 234 } 235 236 237 static struct i2c_client __init *add_smbus_device(const char *name, 238 struct i2c_board_info *info) 239 { 240 return add_i2c_device(name, I2C_ADAPTER_SMBUS, info); 241 } 242 243 static int __init setup_cyapa_smbus_tp(const struct dmi_system_id *id) 244 { 245 /* add cyapa touchpad on smbus */ 246 tp = add_smbus_device("trackpad", &cyapa_device); 247 return 0; 248 } 249 250 static int __init setup_atmel_224s_tp(const struct dmi_system_id *id) 251 { 252 const unsigned short addr_list[] = { ATMEL_TP_I2C_BL_ADDR, 253 ATMEL_TP_I2C_ADDR, 254 I2C_CLIENT_END }; 255 256 /* add atmel mxt touchpad on VGA DDC GMBus */ 257 tp = add_probed_i2c_device("trackpad", I2C_ADAPTER_VGADDC, 258 &atmel_224s_tp_device, addr_list); 259 return 0; 260 } 261 262 static int __init setup_atmel_1664s_ts(const struct dmi_system_id *id) 263 { 264 const unsigned short addr_list[] = { ATMEL_TS_I2C_BL_ADDR, 265 ATMEL_TS_I2C_ADDR, 266 I2C_CLIENT_END }; 267 268 /* add atmel mxt touch device on PANEL GMBus */ 269 ts = add_probed_i2c_device("touchscreen", I2C_ADAPTER_PANEL, 270 &atmel_1664s_device, addr_list); 271 return 0; 272 } 273 274 275 static int __init setup_isl29018_als(const struct dmi_system_id *id) 276 { 277 /* add isl29018 light sensor */ 278 als = add_smbus_device("lightsensor", &isl_als_device); 279 return 0; 280 } 281 282 static int __init setup_isl29023_als(const struct dmi_system_id *id) 283 { 284 /* add isl29023 light sensor on Panel GMBus */ 285 als = add_i2c_device("lightsensor", I2C_ADAPTER_PANEL, 286 &isl_als_device); 287 return 0; 288 } 289 290 static int __init setup_tsl2583_als(const struct dmi_system_id *id) 291 { 292 /* add tsl2583 light sensor on smbus */ 293 als = add_smbus_device(NULL, &tsl2583_als_device); 294 return 0; 295 } 296 297 static int __init setup_tsl2563_als(const struct dmi_system_id *id) 298 { 299 /* add tsl2563 light sensor on smbus */ 300 als = add_smbus_device(NULL, &tsl2563_als_device); 301 return 0; 302 } 303 304 static struct dmi_system_id __initdata chromeos_laptop_dmi_table[] = { 305 { 306 .ident = "Samsung Series 5 550 - Touchpad", 307 .matches = { 308 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"), 309 DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"), 310 }, 311 .callback = setup_cyapa_smbus_tp, 312 }, 313 { 314 .ident = "Chromebook Pixel - Touchscreen", 315 .matches = { 316 DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), 317 DMI_MATCH(DMI_PRODUCT_NAME, "Link"), 318 }, 319 .callback = setup_atmel_1664s_ts, 320 }, 321 { 322 .ident = "Chromebook Pixel - Touchpad", 323 .matches = { 324 DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), 325 DMI_MATCH(DMI_PRODUCT_NAME, "Link"), 326 }, 327 .callback = setup_atmel_224s_tp, 328 }, 329 { 330 .ident = "Samsung Series 5 550 - Light Sensor", 331 .matches = { 332 DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG"), 333 DMI_MATCH(DMI_PRODUCT_NAME, "Lumpy"), 334 }, 335 .callback = setup_isl29018_als, 336 }, 337 { 338 .ident = "Chromebook Pixel - Light Sensor", 339 .matches = { 340 DMI_MATCH(DMI_SYS_VENDOR, "GOOGLE"), 341 DMI_MATCH(DMI_PRODUCT_NAME, "Link"), 342 }, 343 .callback = setup_isl29023_als, 344 }, 345 { 346 .ident = "Acer C7 Chromebook - Touchpad", 347 .matches = { 348 DMI_MATCH(DMI_PRODUCT_NAME, "Parrot"), 349 }, 350 .callback = setup_cyapa_smbus_tp, 351 }, 352 { 353 .ident = "HP Pavilion 14 Chromebook - Touchpad", 354 .matches = { 355 DMI_MATCH(DMI_PRODUCT_NAME, "Butterfly"), 356 }, 357 .callback = setup_cyapa_smbus_tp, 358 }, 359 { 360 .ident = "Samsung Series 5 - Light Sensor", 361 .matches = { 362 DMI_MATCH(DMI_PRODUCT_NAME, "Alex"), 363 }, 364 .callback = setup_tsl2583_als, 365 }, 366 { 367 .ident = "Cr-48 - Light Sensor", 368 .matches = { 369 DMI_MATCH(DMI_PRODUCT_NAME, "Mario"), 370 }, 371 .callback = setup_tsl2563_als, 372 }, 373 { 374 .ident = "Acer AC700 - Light Sensor", 375 .matches = { 376 DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"), 377 }, 378 .callback = setup_tsl2563_als, 379 }, 380 { } 381 }; 382 MODULE_DEVICE_TABLE(dmi, chromeos_laptop_dmi_table); 383 384 static int __init chromeos_laptop_init(void) 385 { 386 if (!dmi_check_system(chromeos_laptop_dmi_table)) { 387 pr_debug("%s unsupported system.\n", __func__); 388 return -ENODEV; 389 } 390 return 0; 391 } 392 393 static void __exit chromeos_laptop_exit(void) 394 { 395 if (als) 396 i2c_unregister_device(als); 397 if (tp) 398 i2c_unregister_device(tp); 399 if (ts) 400 i2c_unregister_device(ts); 401 } 402 403 module_init(chromeos_laptop_init); 404 module_exit(chromeos_laptop_exit); 405 406 MODULE_DESCRIPTION("Chrome OS Laptop driver"); 407 MODULE_AUTHOR("Benson Leung <bleung@chromium.org>"); 408 MODULE_LICENSE("GPL"); 409