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