| 1 | |
| 2 | |
| 3 | * Generic driver for the OLPC Embedded Controller.
|
| 4 | *
|
| 5 | * Author: Andres Salomon <[email protected]>
|
| 6 | *
|
| 7 | * Copyright (C) 2011-2012 One Laptop per Child Foundation.
|
| 8 |
|
| 9 | #include <linux/completion.h>
|
| 10 | #include <linux/debugfs.h>
|
| 11 | #include <linux/spinlock.h>
|
| 12 | #include <linux/mutex.h>
|
| 13 | #include <linux/platform_device.h>
|
| 14 | #include <linux/slab.h>
|
| 15 | #include <linux/workqueue.h>
|
| 16 | #include <linux/init.h>
|
| 17 | #include <linux/list.h>
|
| 18 | #include <linux/regulator/driver.h>
|
| 19 | #include <linux/olpc-ec.h>
|
| 20 |
|
| 21 | struct ec_cmd_desc {
|
| 22 | u8 cmd;
|
| 23 | u8 *inbuf, *outbuf;
|
| 24 | size_t inlen, outlen;
|
| 25 |
|
| 26 | int err;
|
| 27 | struct completion finished;
|
| 28 | struct list_head node;
|
| 29 |
|
| 30 | void *priv;
|
| 31 | };
|
| 32 |
|
| 33 | struct olpc_ec_priv {
|
| 34 | struct olpc_ec_driver *drv;
|
| 35 | u8 version;
|
| 36 | struct work_struct worker;
|
| 37 | struct mutex cmd_lock;
|
| 38 |
|
| 39 |
|
| 40 | bool dcon_enabled;
|
| 41 |
|
| 42 |
|
| 43 | struct list_head cmd_q;
|
| 44 | spinlock_t cmd_q_lock;
|
| 45 |
|
| 46 | struct dentry *dbgfs_dir;
|
| 47 |
|
| 48 | |
| 49 | * EC event mask to be applied during suspend (defining wakeup
|
| 50 | * sources).
|
| 51 |
|
| 52 | u16 ec_wakeup_mask;
|
| 53 |
|
| 54 | |
| 55 | * Running an EC command while suspending means we don't always finish
|
| 56 | * the command before the machine suspends. This means that the EC
|
| 57 | * is expecting the command protocol to finish, but we after a period
|
| 58 | * of time (while the OS is asleep) the EC times out and restarts its
|
| 59 | * idle loop. Meanwhile, the OS wakes up, thinks it's still in the
|
| 60 | * middle of the command protocol, starts throwing random things at
|
| 61 | * the EC... and everyone's uphappy.
|
| 62 |
|
| 63 | bool suspended;
|
| 64 | };
|
| 65 |
|
| 66 | static struct olpc_ec_driver *ec_driver;
|
| 67 | static struct olpc_ec_priv *ec_priv;
|
| 68 | static void *ec_cb_arg;
|
| 69 |
|
| 70 | void olpc_ec_driver_register(struct olpc_ec_driver *drv, void *arg)
|
| 71 | {
|
| 72 | ec_driver = drv;
|
| 73 | ec_cb_arg = arg;
|
| 74 | }
|
| 75 | EXPORT_SYMBOL_GPL(olpc_ec_driver_register);
|
| 76 |
|
| 77 | static void olpc_ec_worker(struct work_struct *w)
|
| 78 | {
|
| 79 | struct olpc_ec_priv *ec = container_of(w, struct olpc_ec_priv, worker);
|
| 80 | struct ec_cmd_desc *desc = NULL;
|
| 81 | unsigned long flags;
|
| 82 |
|
| 83 |
|
| 84 | spin_lock_irqsave(&ec->cmd_q_lock, flags);
|
| 85 | if (!list_empty(&ec->cmd_q)) {
|
| 86 | desc = list_first_entry(&ec->cmd_q, struct ec_cmd_desc, node);
|
| 87 | list_del(&desc->node);
|
| 88 | }
|
| 89 | spin_unlock_irqrestore(&ec->cmd_q_lock, flags);
|
| 90 |
|
| 91 |
|
| 92 | if (!desc)
|
| 93 | return;
|
| 94 |
|
| 95 |
|
| 96 | mutex_lock(&ec->cmd_lock);
|
| 97 | desc->err = ec_driver->ec_cmd(desc->cmd, desc->inbuf, desc->inlen,
|
| 98 | desc->outbuf, desc->outlen, ec_cb_arg);
|
| 99 | mutex_unlock(&ec->cmd_lock);
|
| 100 |
|
| 101 |
|
| 102 | complete(&desc->finished);
|
| 103 |
|
| 104 |
|
| 105 | schedule_work(&ec->worker);
|
| 106 | }
|
| 107 |
|
| 108 | |
| 109 | * Throw a cmd descripter onto the list. We now have SMP OLPC machines, so
|
| 110 | * locking is pretty critical.
|
| 111 |
|
| 112 | static void queue_ec_descriptor(struct ec_cmd_desc *desc,
|
| 113 | struct olpc_ec_priv *ec)
|
| 114 | {
|
| 115 | unsigned long flags;
|
| 116 |
|
| 117 | INIT_LIST_HEAD(&desc->node);
|
| 118 |
|
| 119 | spin_lock_irqsave(&ec->cmd_q_lock, flags);
|
| 120 | list_add_tail(&desc->node, &ec->cmd_q);
|
| 121 | spin_unlock_irqrestore(&ec->cmd_q_lock, flags);
|
| 122 |
|
| 123 | schedule_work(&ec->worker);
|
| 124 | }
|
| 125 |
|
| 126 | int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen)
|
| 127 | {
|
| 128 | struct olpc_ec_priv *ec = ec_priv;
|
| 129 | struct ec_cmd_desc desc;
|
| 130 |
|
| 131 |
|
| 132 | if (!ec_driver)
|
| 133 | return -EPROBE_DEFER;
|
| 134 |
|
| 135 | if (WARN_ON(!ec_driver->ec_cmd))
|
| 136 | return -ENODEV;
|
| 137 |
|
| 138 | if (!ec)
|
| 139 | return -ENOMEM;
|
| 140 |
|
| 141 |
|
| 142 | if (WARN_ON(ec->suspended))
|
| 143 | return -EBUSY;
|
| 144 |
|
| 145 | might_sleep();
|
| 146 |
|
| 147 | desc.cmd = cmd;
|
| 148 | desc.inbuf = inbuf;
|
| 149 | desc.outbuf = outbuf;
|
| 150 | desc.inlen = inlen;
|
| 151 | desc.outlen = outlen;
|
| 152 | desc.err = 0;
|
| 153 | init_completion(&desc.finished);
|
| 154 |
|
| 155 | queue_ec_descriptor(&desc, ec);
|
| 156 |
|
| 157 |
|
| 158 | wait_for_completion(&desc.finished);
|
| 159 |
|
| 160 |
|
| 161 | return desc.err;
|
| 162 | }
|
| 163 | EXPORT_SYMBOL_GPL(olpc_ec_cmd);
|
| 164 |
|
| 165 | void olpc_ec_wakeup_set(u16 value)
|
| 166 | {
|
| 167 | struct olpc_ec_priv *ec = ec_priv;
|
| 168 |
|
| 169 | if (WARN_ON(!ec))
|
| 170 | return;
|
| 171 |
|
| 172 | ec->ec_wakeup_mask |= value;
|
| 173 | }
|
| 174 | EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set);
|
| 175 |
|
| 176 | void olpc_ec_wakeup_clear(u16 value)
|
| 177 | {
|
| 178 | struct olpc_ec_priv *ec = ec_priv;
|
| 179 |
|
| 180 | if (WARN_ON(!ec))
|
| 181 | return;
|
| 182 |
|
| 183 | ec->ec_wakeup_mask &= ~value;
|
| 184 | }
|
| 185 | EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear);
|
| 186 |
|
| 187 | int olpc_ec_mask_write(u16 bits)
|
| 188 | {
|
| 189 | struct olpc_ec_priv *ec = ec_priv;
|
| 190 |
|
| 191 | if (WARN_ON(!ec))
|
| 192 | return -ENODEV;
|
| 193 |
|
| 194 |
|
| 195 | if (ec->version >= 0x5f) {
|
| 196 | __be16 ec_word = cpu_to_be16(bits);
|
| 197 |
|
| 198 | return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *)&ec_word, 2, NULL, 0);
|
| 199 | } else {
|
| 200 | u8 ec_byte = bits & 0xff;
|
| 201 |
|
| 202 | return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0);
|
| 203 | }
|
| 204 | }
|
| 205 | EXPORT_SYMBOL_GPL(olpc_ec_mask_write);
|
| 206 |
|
| 207 | |
| 208 | * Returns true if the compile and runtime configurations allow for EC events
|
| 209 | * to wake the system.
|
| 210 |
|
| 211 | bool olpc_ec_wakeup_available(void)
|
| 212 | {
|
| 213 | if (WARN_ON(!ec_driver))
|
| 214 | return false;
|
| 215 |
|
| 216 | return ec_driver->wakeup_available;
|
| 217 | }
|
| 218 | EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available);
|
| 219 |
|
| 220 | int olpc_ec_sci_query(u16 *sci_value)
|
| 221 | {
|
| 222 | struct olpc_ec_priv *ec = ec_priv;
|
| 223 | int ret;
|
| 224 |
|
| 225 | if (WARN_ON(!ec))
|
| 226 | return -ENODEV;
|
| 227 |
|
| 228 |
|
| 229 | if (ec->version >= 0x5f) {
|
| 230 | __be16 ec_word;
|
| 231 |
|
| 232 | ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, NULL, 0, (void *)&ec_word, 2);
|
| 233 | if (ret == 0)
|
| 234 | *sci_value = be16_to_cpu(ec_word);
|
| 235 | } else {
|
| 236 | u8 ec_byte;
|
| 237 |
|
| 238 | ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1);
|
| 239 | if (ret == 0)
|
| 240 | *sci_value = ec_byte;
|
| 241 | }
|
| 242 |
|
| 243 | return ret;
|
| 244 | }
|
| 245 | EXPORT_SYMBOL_GPL(olpc_ec_sci_query);
|
| 246 |
|
| 247 | #ifdef CONFIG_DEBUG_FS
|
| 248 |
|
| 249 | |
| 250 | * debugfs support for "generic commands", to allow sending
|
| 251 | * arbitrary EC commands from userspace.
|
| 252 |
|
| 253 |
|
| 254 | #define EC_MAX_CMD_ARGS (5 + 1)
|
| 255 | #define EC_MAX_CMD_REPLY (8)
|
| 256 |
|
| 257 | static DEFINE_MUTEX(ec_dbgfs_lock);
|
| 258 | static unsigned char ec_dbgfs_resp[EC_MAX_CMD_REPLY];
|
| 259 | static unsigned int ec_dbgfs_resp_bytes;
|
| 260 |
|
| 261 | static ssize_t ec_dbgfs_cmd_write(struct file *file, const char __user *buf,
|
| 262 | size_t size, loff_t *ppos)
|
| 263 | {
|
| 264 | int i, m;
|
| 265 | unsigned char ec_cmd[EC_MAX_CMD_ARGS];
|
| 266 | unsigned int ec_cmd_int[EC_MAX_CMD_ARGS];
|
| 267 | char cmdbuf[64] = "";
|
| 268 | int ec_cmd_bytes;
|
| 269 |
|
| 270 | mutex_lock(&ec_dbgfs_lock);
|
| 271 |
|
| 272 | size = simple_write_to_buffer(cmdbuf, sizeof(cmdbuf), ppos, buf, size);
|
| 273 |
|
| 274 | m = sscanf(cmdbuf, "%x:%u %x %x %x %x %x", &ec_cmd_int[0],
|
| 275 | &ec_dbgfs_resp_bytes, &ec_cmd_int[1], &ec_cmd_int[2],
|
| 276 | &ec_cmd_int[3], &ec_cmd_int[4], &ec_cmd_int[5]);
|
| 277 | if (m < 2 || ec_dbgfs_resp_bytes > EC_MAX_CMD_REPLY) {
|
| 278 |
|
| 279 | ec_dbgfs_resp_bytes = 0;
|
| 280 |
|
| 281 | pr_debug("olpc-ec: bad ec cmd: cmd:response-count [arg1 [arg2 ...]]\n");
|
| 282 | size = -EINVAL;
|
| 283 | goto out;
|
| 284 | }
|
| 285 |
|
| 286 |
|
| 287 | ec_cmd_bytes = m - 2;
|
| 288 | for (i = 0; i <= ec_cmd_bytes; i++)
|
| 289 | ec_cmd[i] = ec_cmd_int[i];
|
| 290 |
|
| 291 | pr_debug("olpc-ec: debugfs cmd 0x%02x with %d args %5ph, want %d returns\n",
|
| 292 | ec_cmd[0], ec_cmd_bytes, ec_cmd + 1,
|
| 293 | ec_dbgfs_resp_bytes);
|
| 294 |
|
| 295 | olpc_ec_cmd(ec_cmd[0], (ec_cmd_bytes == 0) ? NULL : &ec_cmd[1],
|
| 296 | ec_cmd_bytes, ec_dbgfs_resp, ec_dbgfs_resp_bytes);
|
| 297 |
|
| 298 | pr_debug("olpc-ec: response %8ph (%d bytes expected)\n",
|
| 299 | ec_dbgfs_resp, ec_dbgfs_resp_bytes);
|
| 300 |
|
| 301 | out:
|
| 302 | mutex_unlock(&ec_dbgfs_lock);
|
| 303 | return size;
|
| 304 | }
|
| 305 |
|
| 306 | static ssize_t ec_dbgfs_cmd_read(struct file *file, char __user *buf,
|
| 307 | size_t size, loff_t *ppos)
|
| 308 | {
|
| 309 | unsigned int i, r;
|
| 310 | char *rp;
|
| 311 | char respbuf[64];
|
| 312 |
|
| 313 | mutex_lock(&ec_dbgfs_lock);
|
| 314 | rp = respbuf;
|
| 315 | rp += sprintf(rp, "%02x", ec_dbgfs_resp[0]);
|
| 316 | for (i = 1; i < ec_dbgfs_resp_bytes; i++)
|
| 317 | rp += sprintf(rp, ", %02x", ec_dbgfs_resp[i]);
|
| 318 | mutex_unlock(&ec_dbgfs_lock);
|
| 319 | rp += sprintf(rp, "\n");
|
| 320 |
|
| 321 | r = rp - respbuf;
|
| 322 | return simple_read_from_buffer(buf, size, ppos, respbuf, r);
|
| 323 | }
|
| 324 |
|
| 325 | static const struct file_operations ec_dbgfs_ops = {
|
| 326 | .write = ec_dbgfs_cmd_write,
|
| 327 | .read = ec_dbgfs_cmd_read,
|
| 328 | };
|
| 329 |
|
| 330 | static struct dentry *olpc_ec_setup_debugfs(void)
|
| 331 | {
|
| 332 | struct dentry *dbgfs_dir;
|
| 333 |
|
| 334 | dbgfs_dir = debugfs_create_dir("olpc-ec", NULL);
|
| 335 | debugfs_create_file("cmd", 0600, dbgfs_dir, NULL, &ec_dbgfs_ops);
|
| 336 |
|
| 337 | return dbgfs_dir;
|
| 338 | }
|
| 339 |
|
| 340 | #else
|
| 341 |
|
| 342 | static struct dentry *olpc_ec_setup_debugfs(void)
|
| 343 | {
|
| 344 | return NULL;
|
| 345 | }
|
| 346 |
|
| 347 | #endif
|
| 348 |
|
| 349 | static int olpc_ec_set_dcon_power(struct olpc_ec_priv *ec, bool state)
|
| 350 | {
|
| 351 | unsigned char ec_byte = state;
|
| 352 | int ret;
|
| 353 |
|
| 354 | if (ec->dcon_enabled == state)
|
| 355 | return 0;
|
| 356 |
|
| 357 | ret = olpc_ec_cmd(EC_DCON_POWER_MODE, &ec_byte, 1, NULL, 0);
|
| 358 | if (ret)
|
| 359 | return ret;
|
| 360 |
|
| 361 | ec->dcon_enabled = state;
|
| 362 | return 0;
|
| 363 | }
|
| 364 |
|
| 365 | static int dcon_regulator_enable(struct regulator_dev *rdev)
|
| 366 | {
|
| 367 | struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
|
| 368 |
|
| 369 | return olpc_ec_set_dcon_power(ec, true);
|
| 370 | }
|
| 371 |
|
| 372 | static int dcon_regulator_disable(struct regulator_dev *rdev)
|
| 373 | {
|
| 374 | struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
|
| 375 |
|
| 376 | return olpc_ec_set_dcon_power(ec, false);
|
| 377 | }
|
| 378 |
|
| 379 | static int dcon_regulator_is_enabled(struct regulator_dev *rdev)
|
| 380 | {
|
| 381 | struct olpc_ec_priv *ec = rdev_get_drvdata(rdev);
|
| 382 |
|
| 383 | return ec->dcon_enabled ? 1 : 0;
|
| 384 | }
|
| 385 |
|
| 386 | static const struct regulator_ops dcon_regulator_ops = {
|
| 387 | .enable = dcon_regulator_enable,
|
| 388 | .disable = dcon_regulator_disable,
|
| 389 | .is_enabled = dcon_regulator_is_enabled,
|
| 390 | };
|
| 391 |
|
| 392 | static const struct regulator_desc dcon_desc = {
|
| 393 | .name = "dcon",
|
| 394 | .id = 0,
|
| 395 | .ops = &dcon_regulator_ops,
|
| 396 | .type = REGULATOR_VOLTAGE,
|
| 397 | .owner = THIS_MODULE,
|
| 398 | .enable_time = 25000,
|
| 399 | };
|
| 400 |
|
| 401 | static int olpc_ec_probe(struct platform_device *pdev)
|
| 402 | {
|
| 403 | struct olpc_ec_priv *ec;
|
| 404 | struct regulator_config config = { };
|
| 405 | struct regulator_dev *regulator;
|
| 406 | int err;
|
| 407 |
|
| 408 | if (!ec_driver)
|
| 409 | return -ENODEV;
|
| 410 |
|
| 411 | ec = kzalloc_obj(*ec);
|
| 412 | if (!ec)
|
| 413 | return -ENOMEM;
|
| 414 |
|
| 415 | ec->drv = ec_driver;
|
| 416 | INIT_WORK(&ec->worker, olpc_ec_worker);
|
| 417 | mutex_init(&ec->cmd_lock);
|
| 418 |
|
| 419 | INIT_LIST_HEAD(&ec->cmd_q);
|
| 420 | spin_lock_init(&ec->cmd_q_lock);
|
| 421 |
|
| 422 | ec_priv = ec;
|
| 423 | platform_set_drvdata(pdev, ec);
|
| 424 |
|
| 425 |
|
| 426 | err = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ec->version, 1);
|
| 427 | if (err)
|
| 428 | goto error;
|
| 429 |
|
| 430 | config.dev = pdev->dev.parent;
|
| 431 | config.driver_data = ec;
|
| 432 | ec->dcon_enabled = true;
|
| 433 | regulator = devm_regulator_register(&pdev->dev, &dcon_desc, &config);
|
| 434 | if (IS_ERR(regulator)) {
|
| 435 | dev_err(&pdev->dev, "failed to register DCON regulator\n");
|
| 436 | err = PTR_ERR(regulator);
|
| 437 | goto error;
|
| 438 | }
|
| 439 |
|
| 440 | ec->dbgfs_dir = olpc_ec_setup_debugfs();
|
| 441 |
|
| 442 | return 0;
|
| 443 |
|
| 444 | error:
|
| 445 | ec_priv = NULL;
|
| 446 | kfree(ec);
|
| 447 | return err;
|
| 448 | }
|
| 449 |
|
| 450 | static int olpc_ec_suspend(struct device *dev)
|
| 451 | {
|
| 452 | struct platform_device *pdev = to_platform_device(dev);
|
| 453 | struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
|
| 454 | int err = 0;
|
| 455 |
|
| 456 | olpc_ec_mask_write(ec->ec_wakeup_mask);
|
| 457 |
|
| 458 | if (ec_driver->suspend)
|
| 459 | err = ec_driver->suspend(pdev);
|
| 460 | if (!err)
|
| 461 | ec->suspended = true;
|
| 462 |
|
| 463 | return err;
|
| 464 | }
|
| 465 |
|
| 466 | static int olpc_ec_resume(struct device *dev)
|
| 467 | {
|
| 468 | struct platform_device *pdev = to_platform_device(dev);
|
| 469 | struct olpc_ec_priv *ec = platform_get_drvdata(pdev);
|
| 470 |
|
| 471 | ec->suspended = false;
|
| 472 | return ec_driver->resume ? ec_driver->resume(pdev) : 0;
|
| 473 | }
|
| 474 |
|
| 475 | static const struct dev_pm_ops olpc_ec_pm_ops = {
|
| 476 | .suspend_late = olpc_ec_suspend,
|
| 477 | .resume_early = olpc_ec_resume,
|
| 478 | };
|
| 479 |
|
| 480 | static struct platform_driver olpc_ec_plat_driver = {
|
| 481 | .probe = olpc_ec_probe,
|
| 482 | .driver = {
|
| 483 | .name = "olpc-ec",
|
| 484 | .pm = &olpc_ec_pm_ops,
|
| 485 | },
|
| 486 | };
|
| 487 |
|
| 488 | static int __init olpc_ec_init_module(void)
|
| 489 | {
|
| 490 | return platform_driver_register(&olpc_ec_plat_driver);
|
| 491 | }
|
| 492 | arch_initcall(olpc_ec_init_module);
|