1/* 2 * intel_oaktrail.c - Intel OakTrail Platform support. 3 * 4 * Copyright (C) 2010-2011 Intel Corporation 5 * Author: Yin Kangkai (kangkai.yin@intel.com) 6 * 7 * based on Compal driver, Copyright (C) 2008 Cezary Jackiewicz 8 * <cezary.jackiewicz (at) gmail.com>, based on MSI driver 9 * Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, but 17 * WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 * General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 24 * 02110-1301, USA. 25 * 26 * This driver does below things: 27 * 1. registers itself in the Linux backlight control in 28 * /sys/class/backlight/intel_oaktrail/ 29 * 30 * 2. registers in the rfkill subsystem here: /sys/class/rfkill/rfkillX/ 31 * for these components: wifi, bluetooth, wwan (3g), gps 32 * 33 * This driver might work on other products based on Oaktrail. If you 34 * want to try it you can pass force=1 as argument to the module which 35 * will force it to load even when the DMI data doesn't identify the 36 * product as compatible. 37 */ 38 39#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 40 41#include <linux/module.h> 42#include <linux/kernel.h> 43#include <linux/init.h> 44#include <linux/acpi.h> 45#include <linux/fb.h> 46#include <linux/mutex.h> 47#include <linux/err.h> 48#include <linux/i2c.h> 49#include <linux/backlight.h> 50#include <linux/platform_device.h> 51#include <linux/dmi.h> 52#include <linux/rfkill.h> 53 54#define DRIVER_NAME "intel_oaktrail" 55#define DRIVER_VERSION "0.4ac1" 56 57/* 58 * This is the devices status address in EC space, and the control bits 59 * definition: 60 * 61 * (1 << 0): Camera enable/disable, RW. 62 * (1 << 1): Bluetooth enable/disable, RW. 63 * (1 << 2): GPS enable/disable, RW. 64 * (1 << 3): WiFi enable/disable, RW. 65 * (1 << 4): WWAN (3G) enable/disable, RW. 66 * (1 << 5): Touchscreen enable/disable, Read Only. 67 */ 68#define OT_EC_DEVICE_STATE_ADDRESS 0xD6 69 70#define OT_EC_CAMERA_MASK (1 << 0) 71#define OT_EC_BT_MASK (1 << 1) 72#define OT_EC_GPS_MASK (1 << 2) 73#define OT_EC_WIFI_MASK (1 << 3) 74#define OT_EC_WWAN_MASK (1 << 4) 75#define OT_EC_TS_MASK (1 << 5) 76 77/* 78 * This is the address in EC space and commands used to control LCD backlight: 79 * 80 * Two steps needed to change the LCD backlight: 81 * 1. write the backlight percentage into OT_EC_BL_BRIGHTNESS_ADDRESS; 82 * 2. write OT_EC_BL_CONTROL_ON_DATA into OT_EC_BL_CONTROL_ADDRESS. 83 * 84 * To read the LCD back light, just read out the value from 85 * OT_EC_BL_BRIGHTNESS_ADDRESS. 86 * 87 * LCD backlight brightness range: 0 - 100 (OT_EC_BL_BRIGHTNESS_MAX) 88 */ 89#define OT_EC_BL_BRIGHTNESS_ADDRESS 0x44 90#define OT_EC_BL_BRIGHTNESS_MAX 100 91#define OT_EC_BL_CONTROL_ADDRESS 0x3A 92#define OT_EC_BL_CONTROL_ON_DATA 0x1A 93 94 95static bool force; 96module_param(force, bool, 0); 97MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); 98 99static struct platform_device *oaktrail_device; 100static struct backlight_device *oaktrail_bl_device; 101static struct rfkill *bt_rfkill; 102static struct rfkill *gps_rfkill; 103static struct rfkill *wifi_rfkill; 104static struct rfkill *wwan_rfkill; 105 106 107/* rfkill */ 108static int oaktrail_rfkill_set(void *data, bool blocked) 109{ 110 u8 value; 111 u8 result; 112 unsigned long radio = (unsigned long) data; 113 114 ec_read(OT_EC_DEVICE_STATE_ADDRESS, &result); 115 116 if (!blocked) 117 value = (u8) (result | radio); 118 else 119 value = (u8) (result & ~radio); 120 121 ec_write(OT_EC_DEVICE_STATE_ADDRESS, value); 122 123 return 0; 124} 125 126static const struct rfkill_ops oaktrail_rfkill_ops = { 127 .set_block = oaktrail_rfkill_set, 128}; 129 130static struct rfkill *oaktrail_rfkill_new(char *name, enum rfkill_type type, 131 unsigned long mask) 132{ 133 struct rfkill *rfkill_dev; 134 u8 value; 135 int err; 136 137 rfkill_dev = rfkill_alloc(name, &oaktrail_device->dev, type, 138 &oaktrail_rfkill_ops, (void *)mask); 139 if (!rfkill_dev) 140 return ERR_PTR(-ENOMEM); 141 142 ec_read(OT_EC_DEVICE_STATE_ADDRESS, &value); 143 rfkill_init_sw_state(rfkill_dev, (value & mask) != 1); 144 145 err = rfkill_register(rfkill_dev); 146 if (err) { 147 rfkill_destroy(rfkill_dev); 148 return ERR_PTR(err); 149 } 150 151 return rfkill_dev; 152} 153 154static inline void __oaktrail_rfkill_cleanup(struct rfkill *rf) 155{ 156 if (rf) { 157 rfkill_unregister(rf); 158 rfkill_destroy(rf); 159 } 160} 161 162static void oaktrail_rfkill_cleanup(void) 163{ 164 __oaktrail_rfkill_cleanup(wifi_rfkill); 165 __oaktrail_rfkill_cleanup(bt_rfkill); 166 __oaktrail_rfkill_cleanup(gps_rfkill); 167 __oaktrail_rfkill_cleanup(wwan_rfkill); 168} 169 170static int oaktrail_rfkill_init(void) 171{ 172 int ret; 173 174 wifi_rfkill = oaktrail_rfkill_new("oaktrail-wifi", 175 RFKILL_TYPE_WLAN, 176 OT_EC_WIFI_MASK); 177 if (IS_ERR(wifi_rfkill)) { 178 ret = PTR_ERR(wifi_rfkill); 179 wifi_rfkill = NULL; 180 goto cleanup; 181 } 182 183 bt_rfkill = oaktrail_rfkill_new("oaktrail-bluetooth", 184 RFKILL_TYPE_BLUETOOTH, 185 OT_EC_BT_MASK); 186 if (IS_ERR(bt_rfkill)) { 187 ret = PTR_ERR(bt_rfkill); 188 bt_rfkill = NULL; 189 goto cleanup; 190 } 191 192 gps_rfkill = oaktrail_rfkill_new("oaktrail-gps", 193 RFKILL_TYPE_GPS, 194 OT_EC_GPS_MASK); 195 if (IS_ERR(gps_rfkill)) { 196 ret = PTR_ERR(gps_rfkill); 197 gps_rfkill = NULL; 198 goto cleanup; 199 } 200 201 wwan_rfkill = oaktrail_rfkill_new("oaktrail-wwan", 202 RFKILL_TYPE_WWAN, 203 OT_EC_WWAN_MASK); 204 if (IS_ERR(wwan_rfkill)) { 205 ret = PTR_ERR(wwan_rfkill); 206 wwan_rfkill = NULL; 207 goto cleanup; 208 } 209 210 return 0; 211 212cleanup: 213 oaktrail_rfkill_cleanup(); 214 return ret; 215} 216 217 218/* backlight */ 219static int get_backlight_brightness(struct backlight_device *b) 220{ 221 u8 value; 222 ec_read(OT_EC_BL_BRIGHTNESS_ADDRESS, &value); 223 224 return value; 225} 226 227static int set_backlight_brightness(struct backlight_device *b) 228{ 229 u8 percent = (u8) b->props.brightness; 230 if (percent < 0 || percent > OT_EC_BL_BRIGHTNESS_MAX) 231 return -EINVAL; 232 233 ec_write(OT_EC_BL_BRIGHTNESS_ADDRESS, percent); 234 ec_write(OT_EC_BL_CONTROL_ADDRESS, OT_EC_BL_CONTROL_ON_DATA); 235 236 return 0; 237} 238 239static const struct backlight_ops oaktrail_bl_ops = { 240 .get_brightness = get_backlight_brightness, 241 .update_status = set_backlight_brightness, 242}; 243 244static int oaktrail_backlight_init(void) 245{ 246 struct backlight_device *bd; 247 struct backlight_properties props; 248 249 memset(&props, 0, sizeof(struct backlight_properties)); 250 props.type = BACKLIGHT_PLATFORM; 251 props.max_brightness = OT_EC_BL_BRIGHTNESS_MAX; 252 bd = backlight_device_register(DRIVER_NAME, 253 &oaktrail_device->dev, NULL, 254 &oaktrail_bl_ops, 255 &props); 256 257 if (IS_ERR(bd)) { 258 oaktrail_bl_device = NULL; 259 pr_warning("Unable to register backlight device\n"); 260 return PTR_ERR(bd); 261 } 262 263 oaktrail_bl_device = bd; 264 265 bd->props.brightness = get_backlight_brightness(bd); 266 bd->props.power = FB_BLANK_UNBLANK; 267 backlight_update_status(bd); 268 269 return 0; 270} 271 272static void oaktrail_backlight_exit(void) 273{ 274 backlight_device_unregister(oaktrail_bl_device); 275} 276 277static int oaktrail_probe(struct platform_device *pdev) 278{ 279 return 0; 280} 281 282static int oaktrail_remove(struct platform_device *pdev) 283{ 284 return 0; 285} 286 287static struct platform_driver oaktrail_driver = { 288 .driver = { 289 .name = DRIVER_NAME, 290 }, 291 .probe = oaktrail_probe, 292 .remove = oaktrail_remove, 293}; 294 295static int dmi_check_cb(const struct dmi_system_id *id) 296{ 297 pr_info("Identified model '%s'\n", id->ident); 298 return 0; 299} 300 301static struct dmi_system_id __initdata oaktrail_dmi_table[] = { 302 { 303 .ident = "OakTrail platform", 304 .matches = { 305 DMI_MATCH(DMI_PRODUCT_NAME, "OakTrail platform"), 306 }, 307 .callback = dmi_check_cb 308 }, 309 { } 310}; 311MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table); 312 313static int __init oaktrail_init(void) 314{ 315 int ret; 316 317 if (acpi_disabled) { 318 pr_err("ACPI needs to be enabled for this driver to work!\n"); 319 return -ENODEV; 320 } 321 322 if (!force && !dmi_check_system(oaktrail_dmi_table)) { 323 pr_err("Platform not recognized (You could try the module's force-parameter)"); 324 return -ENODEV; 325 } 326 327 ret = platform_driver_register(&oaktrail_driver); 328 if (ret) { 329 pr_warning("Unable to register platform driver\n"); 330 goto err_driver_reg; 331 } 332 333 oaktrail_device = platform_device_alloc(DRIVER_NAME, -1); 334 if (!oaktrail_device) { 335 pr_warning("Unable to allocate platform device\n"); 336 ret = -ENOMEM; 337 goto err_device_alloc; 338 } 339 340 ret = platform_device_add(oaktrail_device); 341 if (ret) { 342 pr_warning("Unable to add platform device\n"); 343 goto err_device_add; 344 } 345 346 if (!acpi_video_backlight_support()) { 347 ret = oaktrail_backlight_init(); 348 if (ret) 349 goto err_backlight; 350 351 } else 352 pr_info("Backlight controlled by ACPI video driver\n"); 353 354 ret = oaktrail_rfkill_init(); 355 if (ret) { 356 pr_warning("Setup rfkill failed\n"); 357 goto err_rfkill; 358 } 359 360 pr_info("Driver "DRIVER_VERSION" successfully loaded\n"); 361 return 0; 362 363err_rfkill: 364 oaktrail_backlight_exit(); 365err_backlight: 366 platform_device_del(oaktrail_device); 367err_device_add: 368 platform_device_put(oaktrail_device); 369err_device_alloc: 370 platform_driver_unregister(&oaktrail_driver); 371err_driver_reg: 372 373 return ret; 374} 375 376static void __exit oaktrail_cleanup(void) 377{ 378 oaktrail_backlight_exit(); 379 oaktrail_rfkill_cleanup(); 380 platform_device_unregister(oaktrail_device); 381 platform_driver_unregister(&oaktrail_driver); 382 383 pr_info("Driver unloaded\n"); 384} 385 386module_init(oaktrail_init); 387module_exit(oaktrail_cleanup); 388 389MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)"); 390MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras"); 391MODULE_VERSION(DRIVER_VERSION); 392MODULE_LICENSE("GPL"); 393