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 82 static bool force; 83 module_param(force, bool, 0); 84 MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); 85 86 static struct platform_device *oaktrail_device; 87 static struct backlight_device *oaktrail_bl_device; 88 static struct rfkill *bt_rfkill; 89 static struct rfkill *gps_rfkill; 90 static struct rfkill *wifi_rfkill; 91 static struct rfkill *wwan_rfkill; 92 93 94 /* rfkill */ 95 static 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 113 static const struct rfkill_ops oaktrail_rfkill_ops = { 114 .set_block = oaktrail_rfkill_set, 115 }; 116 117 static 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 141 static inline void __oaktrail_rfkill_cleanup(struct rfkill *rf) 142 { 143 if (rf) { 144 rfkill_unregister(rf); 145 rfkill_destroy(rf); 146 } 147 } 148 149 static 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 157 static 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 199 cleanup: 200 oaktrail_rfkill_cleanup(); 201 return ret; 202 } 203 204 205 /* backlight */ 206 static 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 214 static 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 226 static const struct backlight_ops oaktrail_bl_ops = { 227 .get_brightness = get_backlight_brightness, 228 .update_status = set_backlight_brightness, 229 }; 230 231 static 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 259 static void oaktrail_backlight_exit(void) 260 { 261 backlight_device_unregister(oaktrail_bl_device); 262 } 263 264 static int oaktrail_probe(struct platform_device *pdev) 265 { 266 return 0; 267 } 268 269 static int oaktrail_remove(struct platform_device *pdev) 270 { 271 return 0; 272 } 273 274 static struct platform_driver oaktrail_driver = { 275 .driver = { 276 .name = DRIVER_NAME, 277 }, 278 .probe = oaktrail_probe, 279 .remove = oaktrail_remove, 280 }; 281 282 static 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 288 static 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 }; 298 MODULE_DEVICE_TABLE(dmi, oaktrail_dmi_table); 299 300 static 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, PLATFORM_DEVID_NONE); 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 348 err_rfkill: 349 oaktrail_backlight_exit(); 350 err_backlight: 351 platform_device_del(oaktrail_device); 352 err_device_add: 353 platform_device_put(oaktrail_device); 354 err_device_alloc: 355 platform_driver_unregister(&oaktrail_driver); 356 err_driver_reg: 357 358 return ret; 359 } 360 361 static 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 371 module_init(oaktrail_init); 372 module_exit(oaktrail_cleanup); 373 374 MODULE_AUTHOR("Yin Kangkai (kangkai.yin@intel.com)"); 375 MODULE_DESCRIPTION("Intel Oaktrail Platform ACPI Extras"); 376 MODULE_VERSION(DRIVER_VERSION); 377 MODULE_LICENSE("GPL"); 378