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#include <acpi/video.h> 54 55#define DRIVER_NAME "intel_oaktrail" 56#define DRIVER_VERSION "0.4ac1" 57 58/* 59 * This is the devices status address in EC space, and the control bits 60 * definition: 61 * 62 * (1 << 0): Camera enable/disable, RW. 63 * (1 << 1): Bluetooth enable/disable, RW. 64 * (1 << 2): GPS enable/disable, RW. 65 * (1 << 3): WiFi enable/disable, RW. 66 * (1 << 4): WWAN (3G) enable/disable, RW. 67 * (1 << 5): Touchscreen enable/disable, Read Only. 68 */ 69#define OT_EC_DEVICE_STATE_ADDRESS 0xD6 70 71#define OT_EC_CAMERA_MASK (1 << 0) 72#define OT_EC_BT_MASK (1 << 1) 73#define OT_EC_GPS_MASK (1 << 2) 74#define OT_EC_WIFI_MASK (1 << 3) 75#define OT_EC_WWAN_MASK (1 << 4) 76#define OT_EC_TS_MASK (1 << 5) 77 78/* 79 * This is the address in EC space and commands used to control LCD backlight: 80 * 81 * Two steps needed to change the LCD backlight: 82 * 1. write the backlight percentage into OT_EC_BL_BRIGHTNESS_ADDRESS; 83 * 2. write OT_EC_BL_CONTROL_ON_DATA into OT_EC_BL_CONTROL_ADDRESS. 84 * 85 * To read the LCD back light, just read out the value from 86 * OT_EC_BL_BRIGHTNESS_ADDRESS. 87 * 88 * LCD backlight brightness range: 0 - 100 (OT_EC_BL_BRIGHTNESS_MAX) 89 */ 90#define OT_EC_BL_BRIGHTNESS_ADDRESS 0x44 91#define OT_EC_BL_BRIGHTNESS_MAX 100 92#define OT_EC_BL_CONTROL_ADDRESS 0x3A 93#define OT_EC_BL_CONTROL_ON_DATA 0x1A 94 95 96static bool force; 97module_param(force, bool, 0); 98MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); 99 100static struct platform_device *oaktrail_device; 101static struct backlight_device *oaktrail_bl_device; 102static struct rfkill *bt_rfkill; 103static struct rfkill *gps_rfkill; 104static struct rfkill *wifi_rfkill; 105static struct rfkill *wwan_rfkill; 106 107 108/* rfkill */ 109static int oaktrail_rfkill_set(void *data, bool blocked) 110{ 111 u8 value; 112 u8 result; 113 unsigned long radio = (unsigned long) data; 114 115 ec_read(OT_EC_DEVICE_STATE_ADDRESS, &result); 116 117 if (!blocked) 118 value = (u8) (result | radio); 119 else 120 value = (u8) (result & ~radio); 121 122 ec_write(OT_EC_DEVICE_STATE_ADDRESS, value); 123 124 return 0; 125} 126 127static const struct rfkill_ops oaktrail_rfkill_ops = { 128 .set_block = oaktrail_rfkill_set, 129}; 130 131static struct rfkill *oaktrail_rfkill_new(char *name, enum rfkill_type type, 132 unsigned long mask) 133{ 134 struct rfkill *rfkill_dev; 135 u8 value; 136 int err; 137 138 rfkill_dev = rfkill_alloc(name, &oaktrail_device->dev, type, 139 &oaktrail_rfkill_ops, (void *)mask); 140 if (!rfkill_dev) 141 return ERR_PTR(-ENOMEM); 142 143 ec_read(OT_EC_DEVICE_STATE_ADDRESS, &value); 144 rfkill_init_sw_state(rfkill_dev, (value & mask) != 1); 145 146 err = rfkill_register(rfkill_dev); 147 if (err) { 148 rfkill_destroy(rfkill_dev); 149 return ERR_PTR(err); 150 } 151 152 return rfkill_dev; 153} 154 155static inline void __oaktrail_rfkill_cleanup(struct rfkill *rf) 156{ 157 if (rf) { 158 rfkill_unregister(rf); 159 rfkill_destroy(rf); 160 } 161} 162 163static void oaktrail_rfkill_cleanup(void) 164{ 165 __oaktrail_rfkill_cleanup(wifi_rfkill); 166 __oaktrail_rfkill_cleanup(bt_rfkill); 167 __oaktrail_rfkill_cleanup(gps_rfkill); 168 __oaktrail_rfkill_cleanup(wwan_rfkill); 169} 170 171static int oaktrail_rfkill_init(void) 172{ 173 int ret; 174 175 wifi_rfkill = oaktrail_rfkill_new("oaktrail-wifi", 176 RFKILL_TYPE_WLAN, 177 OT_EC_WIFI_MASK); 178 if (IS_ERR(wifi_rfkill)) { 179 ret = PTR_ERR(wifi_rfkill); 180 wifi_rfkill = NULL; 181 goto cleanup; 182 } 183 184 bt_rfkill = oaktrail_rfkill_new("oaktrail-bluetooth", 185 RFKILL_TYPE_BLUETOOTH, 186 OT_EC_BT_MASK); 187 if (IS_ERR(bt_rfkill)) { 188 ret = PTR_ERR(bt_rfkill); 189 bt_rfkill = NULL; 190 goto cleanup; 191 } 192 193 gps_rfkill = oaktrail_rfkill_new("oaktrail-gps", 194 RFKILL_TYPE_GPS, 195 OT_EC_GPS_MASK); 196 if (IS_ERR(gps_rfkill)) { 197 ret = PTR_ERR(gps_rfkill); 198 gps_rfkill = NULL; 199 goto cleanup; 200 } 201 202 wwan_rfkill = oaktrail_rfkill_new("oaktrail-wwan", 203 RFKILL_TYPE_WWAN, 204 OT_EC_WWAN_MASK); 205 if (IS_ERR(wwan_rfkill)) { 206 ret = PTR_ERR(wwan_rfkill); 207 wwan_rfkill = NULL; 208 goto cleanup; 209 } 210 211 return 0; 212 213cleanup: 214 oaktrail_rfkill_cleanup(); 215 return ret; 216} 217 218 219/* backlight */ 220static int get_backlight_brightness(struct backlight_device *b) 221{ 222 u8 value; 223 ec_read(OT_EC_BL_BRIGHTNESS_ADDRESS, &value); 224 225 return value; 226} 227 228static int set_backlight_brightness(struct backlight_device *b) 229{ 230 u8 percent = (u8) b->props.brightness; 231 if (percent < 0 || percent > OT_EC_BL_BRIGHTNESS_MAX) 232 return -EINVAL; 233 234 ec_write(OT_EC_BL_BRIGHTNESS_ADDRESS, percent); 235 ec_write(OT_EC_BL_CONTROL_ADDRESS, OT_EC_BL_CONTROL_ON_DATA); 236 237 return 0; 238} 239 240static const struct backlight_ops oaktrail_bl_ops = { 241 .get_brightness = get_backlight_brightness, 242 .update_status = set_backlight_brightness, 243}; 244 245static int oaktrail_backlight_init(void) 246{ 247 struct backlight_device *bd; 248 struct backlight_properties props; 249 250 memset(&props, 0, sizeof(struct backlight_properties)); 251 props.type = BACKLIGHT_PLATFORM; 252 props.max_brightness = OT_EC_BL_BRIGHTNESS_MAX; 253 bd = backlight_device_register(DRIVER_NAME, 254 &oaktrail_device->dev, NULL, 255 &oaktrail_bl_ops, 256 &props); 257 258 if (IS_ERR(bd)) { 259 oaktrail_bl_device = NULL; 260 pr_warning("Unable to register backlight device\n"); 261 return PTR_ERR(bd); 262 } 263 264 oaktrail_bl_device = bd; 265 266 bd->props.brightness = get_backlight_brightness(bd); 267 bd->props.power = FB_BLANK_UNBLANK; 268 backlight_update_status(bd); 269 270 return 0; 271} 272 273static void oaktrail_backlight_exit(void) 274{ 275 backlight_device_unregister(oaktrail_bl_device); 276} 277 278static int oaktrail_probe(struct platform_device *pdev) 279{ 280 return 0; 281} 282 283static int oaktrail_remove(struct platform_device *pdev) 284{ 285 return 0; 286} 287 288static struct platform_driver oaktrail_driver = { 289 .driver = { 290 .name = DRIVER_NAME, 291 }, 292 .probe = oaktrail_probe, 293 .remove = oaktrail_remove, 294}; 295 296static int dmi_check_cb(const struct dmi_system_id *id) 297{ 298 pr_info("Identified model '%s'\n", id->ident); 299 return 0; 300} 301 302static struct dmi_system_id __initdata oaktrail_dmi_table[] = { 303 { 304 .ident = "OakTrail platform", 305 .matches = { 306 DMI_MATCH(DMI_PRODUCT_NAME, "OakTrail platform"), 307 }, 308 .callback = dmi_check_cb 309 }, 310 { } 311}; 312MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table); 313 314static int __init oaktrail_init(void) 315{ 316 int ret; 317 318 if (acpi_disabled) { 319 pr_err("ACPI needs to be enabled for this driver to work!\n"); 320 return -ENODEV; 321 } 322 323 if (!force && !dmi_check_system(oaktrail_dmi_table)) { 324 pr_err("Platform not recognized (You could try the module's force-parameter)"); 325 return -ENODEV; 326 } 327 328 ret = platform_driver_register(&oaktrail_driver); 329 if (ret) { 330 pr_warning("Unable to register platform driver\n"); 331 goto err_driver_reg; 332 } 333 334 oaktrail_device = platform_device_alloc(DRIVER_NAME, -1); 335 if (!oaktrail_device) { 336 pr_warning("Unable to allocate platform device\n"); 337 ret = -ENOMEM; 338 goto err_device_alloc; 339 } 340 341 ret = platform_device_add(oaktrail_device); 342 if (ret) { 343 pr_warning("Unable to add platform device\n"); 344 goto err_device_add; 345 } 346 347 if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { 348 ret = oaktrail_backlight_init(); 349 if (ret) 350 goto err_backlight; 351 } 352 353 ret = oaktrail_rfkill_init(); 354 if (ret) { 355 pr_warning("Setup rfkill failed\n"); 356 goto err_rfkill; 357 } 358 359 pr_info("Driver "DRIVER_VERSION" successfully loaded\n"); 360 return 0; 361 362err_rfkill: 363 oaktrail_backlight_exit(); 364err_backlight: 365 platform_device_del(oaktrail_device); 366err_device_add: 367 platform_device_put(oaktrail_device); 368err_device_alloc: 369 platform_driver_unregister(&oaktrail_driver); 370err_driver_reg: 371 372 return ret; 373} 374 375static void __exit oaktrail_cleanup(void) 376{ 377 oaktrail_backlight_exit(); 378 oaktrail_rfkill_cleanup(); 379 platform_device_unregister(oaktrail_device); 380 platform_driver_unregister(&oaktrail_driver); 381 382 pr_info("Driver unloaded\n"); 383} 384 385module_init(oaktrail_init); 386module_exit(oaktrail_cleanup); 387 388MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)"); 389MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras"); 390MODULE_VERSION(DRIVER_VERSION); 391MODULE_LICENSE("GPL"); 392