linux/drivers/platform/x86/intel_oaktrail.c
<<
>>
Prefs
   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");
 378