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